1-- A thread of a static Linux program, raw (D494, D516, D517): `clone`
2-- makes it, sharing the address space, the descriptors, the signal
3-- actions and the thread group, and it ends with the thread's own `exit`,
4-- which clears a word of its creator's that a futex wait is woken on.
5-- Under --link=libc the C library owns its threads and this is not used:
6-- platform/posix starts them with `pthread_create` there.
7--
8-- The `clone` call is a naked routine, because the thread it makes
9-- returns from the system call on a stack that is not its creator's, into
10-- no frame of a Landin routine. Its text has no label and no branch: the
11-- creator and the new thread run the same instructions after the call,
12-- and each picks its own next step from the call's result with a
13-- conditional move, the creator returning to its caller and the new
14-- thread entering the routine it was given. Ordinary code reaches the
15-- text through `clone_thread`, the bodyless C view of the routine's
16-- symbol (D516). Declared on Linux only, as the module is.
17
18fixed if compiler.os == linux then
19 --- `clone` flags for a thread: the address space, the file-system
20 --- information, the descriptor table, the signal actions, the thread
21 --- group and System V semaphore adjustments shared with the creator;
22 --- the new thread's ID written to the parent's word before the call
23 --- returns, and the child's word cleared, with a futex wake, when the
24 --- thread ends. CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,
25 --- CLONE_THREAD, CLONE_SYSVSEM, CLONE_PARENT_SETTID and
26 --- CLONE_CHILD_CLEARTID; no exit signal. The same on every
27 --- architecture.
28 public clone_thread_flags: u64 = 3477248
29
30 --- `clone` flag: the new thread's thread pointer is the call's `tls`
31 --- argument, CLONE_SETTLS.
32 public clone_settls: u64 = 524288
33
34 --- What a thread `clone_thread` makes runs first: a C routine called
35 --- with the argument it was given, on the stack it was given, with no
36 --- frame below it. It never returns: it ends with `exit_thread`.
37 public thread_entry: type = extern(c) (argument: usize) -> none
38
39 fixed if compiler.arch == x86_64 then
40 -- The clone itself. The C call's arguments are rdi, rsi, rdx,
41 -- rcx, r8 and r9, and the seventh, the entry, is above the
42 -- return address; the system call takes rdi, rsi, rdx, r10 and
43 -- r8, `clone`'s flags, stack, parent word, child word and thread
44 -- pointer. The new thread's stack is laid out first, 16 bytes
45 -- below its top: the entry, which its `ret` takes, and a zero
46 -- return address above it, so the entry is called as C calls a
47 -- routine, and a backtrace ends there. After the call, rax is
48 -- zero in the new thread alone: it clears its frame pointer, takes
49 -- the argument from r9, which the call keeps, and returns into
50 -- the entry; the creator keeps its own and returns to its caller.
51 link(symbol: "_landin_linux_clone")
52 extern(naked) clone_text: () -> none =
53 assembler.block("""
54 movq 8(%rsp), %rax
55 subq $16, %rsi
56 movq %rax, (%rsi)
57 movq $0, 8(%rsi)
58 movq %rcx, %r10
59 movl $56, %eax
60 syscall
61 xorl %ecx, %ecx
62 testq %rax, %rax
63 cmovzq %rcx, %rbp
64 cmovzq %r9, %rdi
65 ret
66 """)
67 end clone_text
68 elsif compiler.arch == arm64 then
69 -- The clone itself. The C call's arguments are x0 to x6; the
70 -- system call takes x0 to x4, `clone`'s flags, stack, parent
71 -- word, thread pointer and child word, the last two in the
72 -- other order from the C view's. After the call, x0 is zero in
73 -- the new thread alone: it clears its frame pointer and link
74 -- register, takes the argument from x5 and branches to the entry
75 -- in x6, both of which the call keeps; the creator keeps its own
76 -- and branches back to its caller.
77 link(symbol: "_landin_linux_clone")
78 extern(naked) clone_text: () -> none =
79 assembler.block("""
80 mov x9, x3
81 mov x3, x4
82 mov x4, x9
83 mov x8, #220
84 svc #0
85 cmp x0, #0
86 csel x16, x6, x30, eq
87 csel x30, xzr, x30, eq
88 csel x29, xzr, x29, eq
89 csel x0, x5, x0, eq
90 br x16
91 """)
92 end clone_text
93 else
94 -- The clone itself. The C call's arguments are a0 to a6; the
95 -- system call takes a0 to a4, `clone`'s flags, stack, parent
96 -- word, thread pointer and child word, the last two in the
97 -- other order from the C view's. RV64 has no conditional move,
98 -- so a mask chooses: all ones in the new thread, whose a0 is
99 -- zero, and zero in the creator. The new thread clears its frame
100 -- pointer and return address, takes the argument from a5 and
101 -- jumps to the entry in a6, both of which the call keeps; the
102 -- creator jumps back to its caller.
103 link(symbol: "_landin_linux_clone")
104 extern(naked) clone_text: () -> none =
105 assembler.block("""
106 mv t0, a3
107 mv a3, a4
108 mv a4, t0
109 li a7, 220
110 ecall
111 seqz t0, a0
112 neg t0, t0
113 not t2, t0
114 and t1, a6, t0
115 and t3, ra, t2
116 or t1, t1, t3
117 and t4, a5, t0
118 or a0, a0, t4
119 and s0, s0, t2
120 and ra, ra, t2
121 jr t1
122 """)
123 end clone_text
124 end if
125
126 --- Start a thread with `clone`: `flags` are `clone_thread_flags`, with
127 --- `clone_settls` when `tls` is the new thread's thread pointer; the
128 --- thread runs `entry(argument)` on the stack whose top, sixteen-byte
129 --- aligned, is `stack`, and the kernel writes its thread ID to
130 --- `parent` before this returns and clears `child` when it ends. The
131 --- new thread's ID, or minus the errno. The new thread starts with the
132 --- creator's signal mask, so a creator that blocks a signal around this
133 --- call starts a thread that does not receive it.
134 public extern(c) link(symbol: "_landin_linux_clone") clone_thread:
135 (flags: u64, stack: usize, parent: ptr mut u32, child: ptr mut u32,
136 tls: usize, argument: usize, entry: thread_entry)
137 -> (result: i64)
138
139 --- End the calling thread alone, the `exit` call: the kernel clears the
140 --- word `clone_thread` was given as `child` and wakes a futex waiter
141 --- on it, after which nothing of the thread runs. The process goes on
142 --- while another thread does; `exit_group` ends it.
143 public exit_thread:
144 (status: i32) -> noreturn =
145 _ = system_call(exit_number, i64(status), 0, 0, 0, 0, 0)
146 compiler.unreachable()
147 end exit_thread
148
149 -- `futex` operation FUTEX_WAIT, not private. The kernel wakes the
150 -- word a thread's end clears without FUTEX_PRIVATE_FLAG, and a
151 -- private wait is not woken by a shared wake on the same word, so a
152 -- join cannot sleep through `futex_wait`: it slept for ever when it
153 -- did (D517). glibc's and musl's joins wait shared for the same
154 -- reason.
155 futex_wait_shared: i64 = 0
156
157 --- Wait until the thread whose `child` word is `word` has ended: until
158 --- the kernel has cleared the word, as it does once nothing of that
159 --- thread runs. The word's acquire load that reads zero is the join's
160 --- edge with everything the thread did (D227). The word is the
161 --- kernel's to write, so it is read as a `u32` cell would be and not
162 --- through one.
163 public wait_until_cleared:
164 (word: ptr mut u32) -> none =
165 loop do
166 seen: u32 = compiler.atomic_load(word, compiler.acquire)
167 break when seen == 0
168 -- EAGAIN when the word changed first, EINTR for a signal:
169 -- both go round again.
170 _ = system_call(futex_number, i64(usize(word)),
171 futex_wait_shared, i64(seen), 0, 0, 0)
172 end loop
173 end wait_until_cleared
174end if