Landin library reference discuss source

platform/posix/threads.ldn

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