summaryrefslogtreecommitdiff
path: root/libgo/runtime/go-rec-small.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/runtime/go-rec-small.c')
-rw-r--r--libgo/runtime/go-rec-small.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/libgo/runtime/go-rec-small.c b/libgo/runtime/go-rec-small.c
new file mode 100644
index 000000000..765e8d310
--- /dev/null
+++ b/libgo/runtime/go-rec-small.c
@@ -0,0 +1,289 @@
+/* go-rec-small.c -- receive something smaller than 64 bits on a channel.
+
+ Copyright 2009 The Go Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file. */
+
+#include <stdint.h>
+
+#include "go-assert.h"
+#include "go-panic.h"
+#include "channel.h"
+
+/* This mutex controls access to the selected field of struct
+ __go_channel_select. While this mutex is held, no other mutexes
+ may be acquired. */
+
+pthread_mutex_t __go_select_data_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Try to synchronize with a select waiting on a sychronized channel.
+ This is used by a send or receive. The channel is locked. This
+ returns true if it was able to synch. */
+
+_Bool
+__go_synch_with_select (struct __go_channel *channel, _Bool is_send)
+{
+ struct __go_channel_select *p;
+ int i;
+
+ __go_assert (channel->num_entries == 0);
+
+ i = pthread_mutex_lock (&__go_select_data_mutex);
+ __go_assert (i == 0);
+
+ for (p = (is_send
+ ? channel->select_receive_queue
+ : channel->select_send_queue);
+ p != NULL;
+ p = p->next)
+ {
+ if (*p->selected == NULL)
+ {
+ *p->selected = channel;
+ *p->is_read = !is_send;
+ if (is_send)
+ channel->selected_for_receive = 1;
+ else
+ channel->selected_for_send = 1;
+ break;
+ }
+ }
+
+ i = pthread_mutex_unlock (&__go_select_data_mutex);
+ __go_assert (i == 0);
+
+ /* The caller is responsible for signalling the select condition
+ variable so that the other select knows that something has
+ changed. We can't signal it here because we can't acquire the
+ select mutex while we hold a channel lock. */
+
+ return p != NULL;
+}
+
+/* If we synch with a select, then we need to signal the select that
+ something has changed. This requires grabbing the select mutex,
+ which can only be done when the channel is unlocked. This routine
+ does the signalling. It is called with the channel locked. It
+ unlocks the channel, broadcasts the signal and relocks the
+ channel. */
+
+void
+__go_broadcast_to_select (struct __go_channel *channel)
+{
+ pthread_mutex_t *select_mutex;
+ pthread_cond_t *select_cond;
+ int i;
+
+ select_mutex = channel->select_mutex;
+ select_cond = channel->select_cond;
+
+ i = pthread_mutex_unlock (&channel->lock);
+ __go_assert (i == 0);
+
+ __go_assert (select_mutex != NULL && select_cond != NULL);
+
+ i = pthread_mutex_lock (select_mutex);
+ __go_assert (i == 0);
+
+ i = pthread_cond_broadcast (select_cond);
+ __go_assert (i == 0);
+
+ i = pthread_mutex_unlock (select_mutex);
+ __go_assert (i == 0);
+
+ i = pthread_mutex_lock (&channel->lock);
+ __go_assert (i == 0);
+}
+
+/* Prepare to receive something on a channel. Return true if the
+ channel is acquired, false if it is closed. */
+
+_Bool
+__go_receive_acquire (struct __go_channel *channel, _Bool for_select)
+{
+ int i;
+ _Bool my_wait_lock;
+ _Bool synched_with_select;
+
+ my_wait_lock = 0;
+ synched_with_select = 0;
+
+ i = pthread_mutex_lock (&channel->lock);
+ __go_assert (i == 0);
+
+ while (1)
+ {
+ _Bool need_broadcast;
+
+ need_broadcast = 0;
+
+ /* Check whether the channel is closed. */
+ if (channel->is_closed
+ && (channel->num_entries == 0
+ ? channel->next_store == 0
+ : channel->next_fetch == channel->next_store))
+ {
+ if (channel->saw_close)
+ {
+ ++channel->closed_op_count;
+ if (channel->closed_op_count >= MAX_CLOSED_OPERATIONS)
+ __go_panic_msg ("too many operations on closed channel");
+ }
+ channel->saw_close = 1;
+ channel->selected_for_receive = 0;
+ __go_unlock_and_notify_selects (channel);
+ return 0;
+ }
+
+ /* If somebody else has the channel locked for receiving, we
+ have to wait. If FOR_SELECT is true, then we are the one
+ with the lock. */
+ if (!channel->selected_for_receive || for_select)
+ {
+ if (channel->num_entries == 0)
+ {
+ /* If somebody else is waiting to receive, we have to
+ wait. */
+ if (!channel->waiting_to_receive || my_wait_lock)
+ {
+ _Bool was_marked;
+
+ /* Lock the channel so that we get to receive
+ next. */
+ was_marked = channel->waiting_to_receive;
+ channel->waiting_to_receive = 1;
+ my_wait_lock = 1;
+
+ /* See if there is a value to receive. */
+ if (channel->next_store > 0)
+ return 1;
+
+ /* If we haven't already done so, try to synch with
+ a select waiting to send on this channel. If we
+ have already synched with a select, we are just
+ looping until the select eventually causes
+ something to be sent. */
+ if (!synched_with_select && !for_select)
+ {
+ if (__go_synch_with_select (channel, 0))
+ {
+ synched_with_select = 1;
+ need_broadcast = 1;
+ }
+ }
+
+ /* If we marked the channel as waiting, we need to
+ signal, because something changed. It needs to
+ be a broadcast since there might be other
+ receivers waiting. */
+ if (!was_marked)
+ {
+ i = pthread_cond_broadcast (&channel->cond);
+ __go_assert (i == 0);
+ }
+ }
+ }
+ else
+ {
+ /* If there is a value on the channel, we are OK. */
+ if (channel->next_fetch != channel->next_store)
+ return 1;
+ }
+ }
+
+ /* If we just synched with a select, then we need to signal the
+ select condition variable. We can only do that if we unlock
+ the channel. So we need to unlock, signal, lock, and go
+ around the loop again without waiting. */
+ if (need_broadcast)
+ {
+ __go_broadcast_to_select (channel);
+ continue;
+ }
+
+ /* Wait for something to change, then loop around and try
+ again. */
+
+ i = pthread_cond_wait (&channel->cond, &channel->lock);
+ __go_assert (i == 0);
+ }
+}
+
+/* Finished receiving something on a channel. */
+
+void
+__go_receive_release (struct __go_channel *channel)
+{
+ int i;
+
+ if (channel->num_entries != 0)
+ channel->next_fetch = (channel->next_fetch + 1) % channel->num_entries;
+ else
+ {
+ /* For a synchronous receiver, we tell the sender that we picked
+ up the value by setting the next_store field back to 0.
+ Using the mutexes should implement a memory barrier. */
+ __go_assert (channel->next_store == 1);
+ channel->next_store = 0;
+
+ channel->waiting_to_receive = 0;
+ }
+
+ channel->selected_for_receive = 0;
+
+ /* This is a broadcast to make sure that a synchronous sender sees
+ it. */
+ i = pthread_cond_broadcast (&channel->cond);
+ __go_assert (i == 0);
+
+ __go_unlock_and_notify_selects (channel);
+}
+
+/* Unlock a channel and notify any waiting selects that something
+ happened. */
+
+void
+__go_unlock_and_notify_selects (struct __go_channel *channel)
+{
+ pthread_mutex_t* select_mutex;
+ pthread_cond_t* select_cond;
+ int i;
+
+ select_mutex = channel->select_mutex;
+ select_cond = channel->select_cond;
+
+ i = pthread_mutex_unlock (&channel->lock);
+ __go_assert (i == 0);
+
+ if (select_mutex != NULL)
+ {
+ i = pthread_mutex_lock (select_mutex);
+ __go_assert (i == 0);
+ i = pthread_cond_broadcast (select_cond);
+ __go_assert (i == 0);
+ i = pthread_mutex_unlock (select_mutex);
+ __go_assert (i == 0);
+ }
+}
+
+/* Receive something 64 bits or smaller on a channel. */
+
+uint64_t
+__go_receive_small (struct __go_channel *channel, _Bool for_select)
+{
+ uint64_t ret;
+
+ if (channel == NULL)
+ __go_panic_msg ("receive from nil channel");
+
+ __go_assert (channel->element_size <= sizeof (uint64_t));
+
+ if (!__go_receive_acquire (channel, for_select))
+ return 0;
+
+ ret = channel->data[channel->next_fetch];
+
+ __go_receive_release (channel);
+
+ return ret;
+}