1-- Threads: starting one on a stack area its caller lends and waiting for
2-- its end (D494, D517, D518). A static Linux program makes the thread
3-- itself, with platform/linux's `clone_thread`, and waits on the word the
4-- kernel clears when it ends; under --link=libc, on Darwin and on FreeBSD
5-- the C library starts it with `pthread_create` on the area, given with
6-- `pthread_attr_setstack`, and `pthread_join` waits. Either way the
7-- thread starts with every asynchronous signal blocked, so a signal the
8-- process is sent reaches a thread that reads it, through a signalfd or kqueue, and
9-- never a new thread's default disposition. Nothing here allocates; the
10-- C library, where it starts the thread, keeps its own record of it as it
11-- chooses (D518).
12
13import platform/libc
14import platform/linux
15
16--- The routine a started thread runs, as the C library's threads run
17--- one: called with the argument `start_thread` was given, on the area's
18--- stack. Its result is discarded.
19public thread_routine: type = libc.thread_routine
20
21--- The caller's record of one started thread, from `start_thread` until
22--- `join_thread` returns: the word a static Linux program's kernel clears
23--- when the thread ends, the C library's handle, and what the thread is
24--- to run. Declare it `zeroed`; it must stay where it is meanwhile.
25public thread: type = struct
26 word: u32
27 reserved: u32
28 handle: usize
29 routine: thread_routine
30 argument: usize
31end thread
32
33-- The signals a thread starts with blocked are the asynchronous ones,
34-- every signal but the six a fault raises in the thread that made it:
35-- SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV and SIGSYS (D521). Linux
36-- delivers a fault's signal even while it is blocked, but Darwin leaves
37-- it pending and the faulting instruction runs again, so a trap report's
38-- terminal instruction in a thread would never end the process.
39fixed if compiler.os == linux then
40 synchronous_signals: [6]i32 = [4, 5, 7, 8, 11, 31]
41else
42 synchronous_signals: [6]i32 = [4, 5, 8, 10, 11, 12]
43end if
44
45fixed if compiler.os == linux and not compiler.c_runtime then
46 -- What runs on the area besides the thread's routine: this routine's
47 -- frame, and above it, at the area's top, the two words
48 -- `clone_thread` lays.
49 area_least: usize = 2048
50
51 -- Every signal but the synchronous ones, in the kernel's mask, whose
52 -- bit N - 1 is signal N.
53 asynchronous: () -> (mask: u64) =
54 mask = 0 -% 1
55 for synchronous in synchronous_signals do
56 mask = mask & ~(u64(1) << u64(synchronous - 1))
57 end for
58 end asynchronous
59
60 --- The bytes a thread's start area takes: its thread-local block and
61 --- 2 KiB for the frames that start it.
62 public thread_start_bytes:
63 () -> (bytes: usize) =
64 block: usize = linux.thread_block_bytes()
65 bytes = area_least + block + (16 - block % 16) % 16
66 end thread_start_bytes
67
68 --- The alignment a start area's lowest byte and its length keep.
69 public thread_start_alignment:
70 () -> (bytes: usize) =
71 bytes = 16
72 end thread_start_alignment
73
74 -- The new thread's first routine: run the caller's, then end the
75 -- thread, which clears the record's word.
76 extern(c) begin_thread: (argument: usize) -> none =
77 -- [0810]: the argument is the record `start_thread` was given.
78 record: ptr mut thread = ptr(argument)
79 _ = record.val.routine(record.val.argument)
80 linux.exit_thread(0)
81 end begin_thread
82
83 system_start: (area: []mut u8, record: ptr mut thread) -> none ! errno =
84 low: usize = usize(addr area[0])
85 block: usize = linux.thread_block_bytes()
86 mut tls: usize = 0
87 if block > 0 then
88 tls = linux.thread_block_lay(area[0..<block])
89 end if
90 mut flags: u64 = linux.clone_thread_flags
91 if tls <> 0 then flags = flags | linux.clone_settls end if
92 top: usize = (low + lenof area) - (low + lenof area) % 16
93 every: u64 = asynchronous()
94 mut kept: u64 = 0
95 blocked: i32 = linux.rt_sigprocmask(linux.sig_setmask, addr every,
96 addr kept)
97 fail errno_of(-blocked) when blocked < 0
98 result: i64 = linux.clone_thread(flags, top, addr record.val.word,
99 addr record.val.word, tls,
100 usize(record), begin_thread)
101 _ = linux.rt_sigprocmask(linux.sig_setmask, addr kept, linux.no_mask)
102 fail errno_of(i32(-result)) when result < 0
103 end system_start
104
105 system_join: (record: ptr mut thread) -> none ! errno =
106 linux.wait_until_cleared(addr record.val.word)
107 end system_join
108else
109 fixed if compiler.os == linux and compiler.arch == arm64 then
110 -- glibc's PTHREAD_STACK_MIN on arm64, whose pages may be 64 KiB.
111 area_least: usize = 131072
112 else
113 -- PTHREAD_STACK_MIN is 16 KiB under glibc on x86-64 and RV64 and
114 -- on Darwin; FreeBSD's, 2 KiB on x86-64 and 4 KiB on arm64, is
115 -- too little for libthr's own start and end, whose calls the
116 -- run-time linker binds lazily on this stack (D491), so FreeBSD
117 -- takes 16 KiB too.
118 area_least: usize = 16384
119 end if
120
121 fixed if compiler.os == darwin then
122 -- Darwin takes a stack only at a page boundary and in whole
123 -- pages, of 16 KiB on arm64.
124 area_alignment: usize = 16384
125 else
126 area_alignment: usize = 16
127 end if
128
129 --- The bytes a thread's start area takes: the C library's least
130 --- stack, which holds its own start frames and, under glibc, the
131 --- thread's block.
132 public thread_start_bytes:
133 () -> (bytes: usize) =
134 bytes = area_least
135 end thread_start_bytes
136
137 --- The alignment a start area's lowest byte and its length keep:
138 --- Darwin's page, 16 bytes elsewhere.
139 public thread_start_alignment:
140 () -> (bytes: usize) =
141 bytes = area_alignment
142 end thread_start_alignment
143
144 system_start: (area: []mut u8, record: ptr mut thread) -> none ! errno =
145 mut attributes: libc.thread_attributes = zeroed
146 made: i32 = libc.pthread_attr_init(addr attributes)
147 fail errno_of(made) when made <> 0
148 mut status: i32 = libc.pthread_attr_setstack(
149 addr attributes, usize(addr area[0]), lenof area)
150 if status == 0 then
151 mut every: libc.signal_set = zeroed
152 mut kept: libc.signal_set = zeroed
153 _ = libc.sigfillset(addr every)
154 for synchronous in synchronous_signals do
155 _ = libc.sigdelset(addr every, synchronous)
156 end for
157 status = libc.pthread_sigmask(libc.sig_setmask, addr every,
158 addr kept)
159 if status == 0 then
160 status = libc.pthread_create(addr record.val.handle,
161 addr attributes,
162 record.val.routine,
163 record.val.argument)
164 _ = libc.pthread_sigmask(libc.sig_setmask, addr kept,
165 libc.no_address)
166 end if
167 end if
168 _ = libc.pthread_attr_destroy(addr attributes)
169 fail errno_of(status) when status <> 0
170 end system_start
171
172 system_join: (record: ptr mut thread) -> none ! errno =
173 status: i32 = libc.pthread_join(record.val.handle, libc.no_address)
174 fail errno_of(status) when status <> 0
175 end system_join
176end if
177
178--- Start a thread that calls `routine(argument)` on `area`, its stack,
179--- which holds `thread_start_bytes()` from a byte at
180--- `thread_start_alignment()`, keeping its record in `record` until
181--- `join_thread`. Every asynchronous signal is blocked while it starts,
182--- and the thread begins with them blocked, a fault's own alone not;
183--- the caller's own mask is as it was. It fails `einval` for an area too
184--- small or not aligned, and with the errno the system refused the thread
185--- with, `eagain` when it lacks what another thread needs; a failed start
186--- starts nothing. A successful start synchronizes with the thread's first
187--- action (D227).
188public start_thread:
189 (area: []mut u8, record: ptr mut thread, routine: thread_routine,
190 argument: usize) -> none ! errno =
191 fail einval when lenof area < thread_start_bytes()
192 alignment: usize = thread_start_alignment()
193 fail einval when usize(addr area[0]) % alignment <> 0
194 or lenof area % alignment <> 0
195 record.val = (word: 0, reserved: 0, handle: 0, routine: routine,
196 argument: argument)
197 try system_start(area, record)
198end start_thread
199
200--- Wait until the thread `record` was started for has ended, blocking the
201--- calling thread. Its end synchronizes with this return (D227), after
202--- which nothing of the thread runs and its area may be used again. A
203--- thread is joined once; a record no thread was started for fails
204--- `einval` or `esrch` under a C library and is never waited for.
205public join_thread:
206 (record: ptr mut thread) -> none ! errno =
207 try system_join(record)
208end join_thread