summaryrefslogtreecommitdiff
path: root/src/common/rswitch.c
blob: 9373b4eff7e7d8c108f263c1751fe72fee67efe4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
 * rswitch.c -- context switch code using POSIX threads and semaphores
 *
 * This code implements co-expression context switching on any system that
 * provides POSIX threads and semaphores.
 *
 * Anonymous semaphores are used unless NamedSemaphores is defined.
 * (This is for MacOS which does not have anonymous semaphores.)
 */

#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

#include "../h/define.h"

extern void new_context(int, void *);
extern void syserr(char *msg);
extern void *alloc(unsigned int n);

extern long stksize;		/* value of COEXPSIZE */

static int inited = 0;		/* has first-time initialization been done? */
static pthread_attr_t attribs;	/* thread creation attributes */

/*
 * Define a "context" struct to hold the thread information we need.
 */
typedef struct {
   pthread_t thread;	/* thread ID (thread handle) */
   sem_t sema;		/* synchronization semaphore (if unnamed) */
   sem_t *semp;		/* pointer to semaphore */
   int alive;		/* set zero when thread is to die */
   } context;

static void makesem(context *ctx);
static void *nctramp(void *arg);
static void uerror(char *msg);

/*
 * Treat an Icon "cstate" array as an array of context pointers.
 * cstate[0] is used by Icon code that thinks it's setting a stack pointer.
 * We use cstate[1] to point to the actual context struct.
 * (Both of these are initialized to NULL by Icon 9.4.1 or later.)
 */
typedef context **cstate;

/*
 * coswitch(old, new, first) -- switch contexts.
 */
int coswitch(void *o, void *n, int first) {

   cstate ocs = o;			/* old cstate pointer */
   cstate ncs = n;			/* new cstate pointer */
   context *old, *new;			/* old and new context pointers */
   size_t newsize;			/* stack size for new thread */
   size_t pagesize;			/* system page size */

   if (inited)				/* if not first call */
      old = ocs[1];			/* load current context pointer */
   else {
      /*
       * This is the first coswitch() call.
       * Allocate and initialize the context struct for &main.
       */
      old = ocs[1] = alloc(sizeof(context));
      makesem(old);
      old->thread = pthread_self();
      old->alive = 1;

      /*
       * Set up thread attributes to honor COEXPSIZE for setting stack size.
       */
      pagesize = sysconf(_SC_PAGESIZE);
      newsize = stksize;
      #ifdef PTHREAD_STACK_MIN
         if (newsize < PTHREAD_STACK_MIN)   /* ensure system minimum is met */
            newsize = PTHREAD_STACK_MIN;
      #endif
      if (pagesize > 0 && (newsize % pagesize) != 0) {
         /* some systems require an exact multiple of the system page size */
         newsize = newsize + pagesize - (newsize % pagesize);
      }
      pthread_attr_init(&attribs);
      if (pthread_attr_setstacksize(&attribs, newsize) != 0) {
         uerror("cannot set stacksize for thread");
      }

      inited = 1;
      }

   if (first != 0)			/* if not first call for this cstate */
      new = ncs[1];			/* load new context pointer */
   else {
      /*
       * This is a newly allocated cstate array.
       * Allocate and initialize a context struct.
       */
      new = ncs[1] = alloc(sizeof(context));
      makesem(new);
      if (pthread_create(&new->thread, &attribs, nctramp, new) != 0)
         uerror("cannot create thread");
      new->alive = 1;
      }

   sem_post(new->semp);			/* unblock the new thread */
   sem_wait(old->semp);			/* block this thread */

   if (!old->alive)		
      pthread_exit(NULL);		/* if unblocked because unwanted */
   return 0;				/* else return to continue running */
   }

/*
 * coclean(old) -- clean up co-expression state before freeing.
 */
void coclean(void *o) {
   cstate ocs = o;			/* old cstate pointer */
   context *old = ocs[1];		/* old context pointer */
   if (old == NULL)			/* if never initialized, do nothing */
      return;
   old->alive = 0;			/* signal thread to exit */
   sem_post(old->semp);			/* unblock it */
   pthread_join(old->thread, NULL);	/* wait for thread to exit */
   #ifdef NamedSemaphores
      sem_close(old->semp);		/* close associated semaphore */
   #else
      sem_destroy(old->semp);		/* destroy associated semaphore */
   #endif
   free(old);				/* free context block */
   }

/*
 * makesem(ctx) -- initialize semaphore in context struct.
 */
static void makesem(context *ctx) {
   #ifdef NamedSemaphores		/* if cannot use unnamed semaphores */
      char name[50];
      sprintf(name, "i%ld.sem", (long)getpid());
      ctx->semp = sem_open(name, O_CREAT, S_IRUSR | S_IWUSR, 0);
      if (ctx->semp == (sem_t *)SEM_FAILED)
         uerror("cannot create semaphore");
      sem_unlink(name);
   #else				/* NamedSemaphores */
      if (sem_init(&ctx->sema, 0, 0) == -1)
         uerror("cannot init semaphore");
      ctx->semp = &ctx->sema;
   #endif				/* NamedSemaphores */
   }

/*
 * nctramp() -- trampoline for calling new_context(0,0).
 */
static void *nctramp(void *arg) {
   context *new = arg;			/* new context pointer */
   sem_wait(new->semp);			/* wait for signal */
   new_context(0, 0);			/* call new_context; will not return */
   syserr("new_context returned to nctramp");
   return NULL;
   }

/*
 * uerror(s) -- abort due to Unix error.
 */
static void uerror(char *msg) {
   perror(msg);
   syserr(NULL);
   }