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
|
/* hold blocks (spec 4.16): the host side of a monitor. a function of a hold block
stores op 57 (enter) at its start and op 58 (leave) at its exit, with the block's
rank and the address of its descriptor word. the descriptor points at a table in vm
memory: n, then n pairs (address, words) of the block's shared variables.
the shared values live here, outside every vm, so all actors (each with its own
memory) see one copy. entering a block from outside takes its lock and copies the
values into the vm's variables; leaving copies them back and unlocks. inside, they
are plain globals. the lock is per block and reentrant per thread (a nested call
only counts), and blocks are taken in rank (declaration) order: entering a block
while holding a later one is an error, which rules out lock-order deadlocks between
blocks. a worker that ends while holding blocks releases them without writing back.
included by src/core/core.c and src/micro/micro.c; HOLD_THREADS selects pthreads,
otherwise a single machine needs no locks (wasm, bare metal). no malloc. */
#ifndef HOLD_H
#define HOLD_H
#define HOLD_BLOCKS 256
#define HOLD_CELLS 65536
#ifdef HOLD_THREADS
#include <pthread.h>
static pthread_mutex_t hold_lock[HOLD_BLOCKS], hold_meta = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t hold_once = PTHREAD_ONCE_INIT;
static void hold_init(void) { int i; for (i = 0; i < HOLD_BLOCKS; i++) pthread_mutex_init(&hold_lock[i], 0); }
#define HOLD_TLS _Thread_local
#else
#define HOLD_TLS
#endif
static int64_t hold_cells[HOLD_CELLS];
static int hold_base[HOLD_BLOCKS], hold_size[HOLD_BLOCKS], hold_ready[HOLD_BLOCKS], hold_next;
static HOLD_TLS int hold_depth[HOLD_BLOCKS], hold_top; /* this thread: nesting per block, highest rank held */
static HOLD_TLS unsigned char hold_order[HOLD_BLOCKS]; /* ranks held, in the order taken */
static HOLD_TLS int hold_n;
/* the table's entries, checked against the machine. -1 when it is malformed */
static int64_t hold_table(int64_t *mem, uint64_t words, int64_t desc, int64_t *n)
{
int64_t t, i;
if (desc <= 0 || (uint64_t)desc >= words) return -1;
t = mem[desc];
if (t <= 0 || (uint64_t)t >= words) return -1;
*n = mem[t];
if (*n < 0 || *n > 64 || (uint64_t)(t + 1 + 2 * *n) > words) return -1;
for (i = 0; i < *n; i++) {
int64_t a = mem[t + 1 + 2 * i], w = mem[t + 2 + 2 * i];
if (a <= 0 || w <= 0 || (uint64_t)(a + w) > words) return -1;
}
return t;
}
static void hold_copy(int64_t *mem, int64_t t, int64_t n, int rank, int in)
{
int64_t i, k, c = hold_base[rank];
for (i = 0; i < n; i++) {
int64_t a = mem[t + 1 + 2 * i], w = mem[t + 2 + 2 * i];
for (k = 0; k < w; k++, c++) { if (in) mem[a + k] = hold_cells[c]; else hold_cells[c] = mem[a + k]; }
}
}
/* op 57: 0, or -1 (bad block), or -2 (taken out of rank order; the caller stops) */
static int64_t hold_enter(int64_t *mem, uint64_t words, int64_t rank, int64_t desc)
{
int64_t t, n, i, total = 0;
if (rank <= 0 || rank >= HOLD_BLOCKS) return -1;
if (hold_depth[rank]) { hold_depth[rank]++; return 0; }
if (hold_top > rank) return -2;
t = hold_table(mem, words, desc, &n); if (t < 0) return -1;
for (i = 0; i < n; i++) total += mem[t + 2 + 2 * i];
#ifdef HOLD_THREADS
pthread_once(&hold_once, hold_init);
pthread_mutex_lock(&hold_lock[rank]);
pthread_mutex_lock(&hold_meta);
#endif
if (!hold_ready[rank]) { /* the first entry anywhere: the image's initial values become the shared ones */
if (hold_next + total > HOLD_CELLS) {
#ifdef HOLD_THREADS
pthread_mutex_unlock(&hold_meta); pthread_mutex_unlock(&hold_lock[rank]);
#endif
return -1;
}
hold_base[rank] = hold_next; hold_size[rank] = (int)total; hold_next += (int)total; hold_ready[rank] = 1;
hold_copy(mem, t, n, (int)rank, 0);
}
#ifdef HOLD_THREADS
pthread_mutex_unlock(&hold_meta);
#endif
hold_copy(mem, t, n, (int)rank, 1);
hold_depth[rank] = 1; hold_order[hold_n++] = (unsigned char)rank; hold_top = (int)rank;
return 0;
}
/* op 58 */
static int64_t hold_leave(int64_t *mem, uint64_t words, int64_t rank, int64_t desc)
{
int64_t t, n;
if (rank <= 0 || rank >= HOLD_BLOCKS || !hold_depth[rank]) return -1;
if (--hold_depth[rank]) return 0;
t = hold_table(mem, words, desc, &n);
if (t >= 0) hold_copy(mem, t, n, (int)rank, 0);
if (hold_n > 0 && hold_order[hold_n - 1] == rank) hold_n--;
hold_top = hold_n ? hold_order[hold_n - 1] : 0;
#ifdef HOLD_THREADS
pthread_mutex_unlock(&hold_lock[rank]);
#endif
return 0;
}
/* a machine that ends while inside blocks gives them up, without writing back */
static void hold_release_all(void)
{
while (hold_n > 0) {
int r = hold_order[--hold_n];
hold_depth[r] = 0;
#ifdef HOLD_THREADS
pthread_mutex_unlock(&hold_lock[r]);
#endif
}
hold_top = 0;
}
#endif
|