1-- Linux's system-call interface, on x86-64, arm64 and RV64, with no C
2-- library beneath it. Linux promises this interface, so a Linux program
3-- reaches the kernel directly: each call is one assembly block, and a
4-- failure comes back in the result as minus the errno, with no errno cell.
5-- The `*at` calls and `statx` keep one record layout on all three
6-- architectures. The module also holds the program-owned process entry,
7-- `_landin_start`, which a static Linux executable starts at.
8--
9-- Every public routine carries a link name of its own, so none of them is
10-- mistaken for, or replaces, the C library's function of the same name in
11-- a program that also links libc.
12--
13-- Everything here is declared on Linux only. Landin has no conditional
14-- import, so hosted/io and hosted/heap import this module on every hosted
15-- system, and on Darwin and FreeBSD it declares nothing.
16
17fixed if compiler.os == linux then
18 fixed if compiler.arch == x86_64 then
19 -- rax names the call and carries its result; rdi, rsi, rdx, r10,
20 -- r8 and r9 carry its arguments; the kernel overwrites rcx and
21 -- r11.
22 call6: (number: i64, a: i64, b: i64, c: i64, d: i64, e: i64,
23 f: i64) -> (result: i64) =
24 result = assembler.block("syscall", inout n: i64 at rax = number,
25 in p: i64 at rdi = a,
26 in q: i64 at rsi = b,
27 in r: i64 at rdx = c,
28 in s: i64 at r10 = d,
29 in t: i64 at r8 = e,
30 in u: i64 at r9 = f, out _ at rcx,
31 out _ at r11)
32 end call6
33
34 read_number: i64 = 0
35 write_number: i64 = 1
36 close_number: i64 = 3
37 mmap_number: i64 = 9
38 munmap_number: i64 = 11
39 exit_group_number: i64 = 231
40 openat_number: i64 = 257
41 statx_number: i64 = 332
42
43 --- The process entry: the kernel starts a static Linux executable
44 --- here, with argc on top of the stack and no return address. It
45 --- clears the frame pointer so a debugger stops at this frame,
46 --- aligns the stack to sixteen bytes and calls
47 --- `_landin_start_main` with the initial stack's address.
48 public extern(naked) _landin_start: () -> none =
49 assembler.block("xorl %ebp, %ebp\nmovq %rsp, %rdi\nandq $-16, %rsp\ncall _landin_start_main")
50 end _landin_start
51 elsif compiler.arch == arm64 then
52 -- x8 names the call; x0 to x5 carry its arguments and x0 its
53 -- result.
54 call6: (number: i64, a: i64, b: i64, c: i64, d: i64, e: i64,
55 f: i64) -> (result: i64) =
56 result = assembler.block("svc #0", in n: i64 at x8 = number,
57 inout p: i64 at x0 = a,
58 in q: i64 at x1 = b,
59 in r: i64 at x2 = c,
60 in s: i64 at x3 = d,
61 in t: i64 at x4 = e,
62 in u: i64 at x5 = f)
63 end call6
64
65 read_number: i64 = 63
66 write_number: i64 = 64
67 close_number: i64 = 57
68 mmap_number: i64 = 222
69 munmap_number: i64 = 215
70 exit_group_number: i64 = 94
71 openat_number: i64 = 56
72 statx_number: i64 = 291
73
74 --- The process entry: the kernel starts a static Linux executable
75 --- here, with argc at the sixteen-byte-aligned stack pointer and no
76 --- return address. It clears the frame and link registers so a
77 --- debugger stops at this frame and calls `_landin_start_main` with
78 --- the initial stack's address.
79 public extern(naked) _landin_start: () -> none =
80 assembler.block("mov x29, #0\nmov x30, #0\nmov x0, sp\nbl _landin_start_main")
81 end _landin_start
82 else
83 -- a7 names the call; a0 to a5 carry its arguments and a0 its
84 -- result.
85 call6: (number: i64, a: i64, b: i64, c: i64, d: i64, e: i64,
86 f: i64) -> (result: i64) =
87 result = assembler.block("ecall", in n: i64 at x17 = number,
88 inout p: i64 at x10 = a,
89 in q: i64 at x11 = b,
90 in r: i64 at x12 = c,
91 in s: i64 at x13 = d,
92 in t: i64 at x14 = e,
93 in u: i64 at x15 = f)
94 end call6
95
96 read_number: i64 = 63
97 write_number: i64 = 64
98 close_number: i64 = 57
99 mmap_number: i64 = 222
100 munmap_number: i64 = 215
101 exit_group_number: i64 = 94
102 openat_number: i64 = 56
103 statx_number: i64 = 291
104
105 --- The process entry: the kernel starts a static Linux executable
106 --- here, with argc on top of the stack and no return address. It
107 --- clears the frame pointer and return address so a debugger stops
108 --- at this frame, aligns the stack to sixteen bytes and calls
109 --- `_landin_start_main` with the initial stack's address.
110 public extern(naked) _landin_start: () -> none =
111 assembler.block("li x8, 0\nli ra, 0\nmv a0, sp\nandi sp, sp, -16\ncall _landin_start_main")
112 end _landin_start
113 end if
114
115 --- The directory descriptor that makes an `*at` call's relative path
116 --- relative to the working directory.
117 public at_fdcwd: i32 = -100
118
119 --- `openat` flag: read only.
120 public o_rdonly: i32 = 0
121 --- `openat` flag: write only.
122 public o_wronly: i32 = 1
123 --- `openat` flag: create the file if it does not exist.
124 public o_creat: i32 = 64
125 --- `openat` flag: truncate an existing regular file to length zero.
126 public o_trunc: i32 = 512
127
128 --- errno: the operation is not permitted.
129 public eperm: i32 = 1
130 --- errno: no such file or directory.
131 public enoent: i32 = 2
132 --- errno: interrupted by a signal before anything was transferred.
133 public eintr: i32 = 4
134 --- errno: permission denied.
135 public eacces: i32 = 13
136 --- errno: a path component is not a directory.
137 public enotdir: i32 = 20
138 --- errno: the file system is read-only.
139 public erofs: i32 = 30
140
141 --- `mmap` protection: the pages may be read.
142 public prot_read: i32 = 1
143 --- `mmap` protection: the pages may be written.
144 public prot_write: i32 = 2
145 --- `mmap` flag: the mapping is private to this process.
146 public map_private: i32 = 2
147 --- `mmap` flag: the mapping is backed by no file and starts zeroed.
148 public map_anonymous: i32 = 32
149
150 --- `statx` mask: the inode number, beside the basic fields every call
151 --- fills.
152 public statx_ino: u32 = 256
153
154 --- A `statx` time: whole seconds and the nanoseconds after them.
155 public statx_time: type = struct
156 seconds: i64
157 nanoseconds: u32
158 reserved: i32
159 end statx_time
160
161 --- The kernel's `struct statx`, one layout on every architecture. The
162 --- device is `device_major` and `device_minor`; `mask` says which other
163 --- fields the call filled.
164 public file_status: type = struct
165 mask: u32
166 block_size: u32
167 attributes: u64
168 links: u32
169 uid: u32
170 gid: u32
171 mode: u16
172 spare0: u16
173 inode: u64
174 size: u64
175 blocks: u64
176 attributes_mask: u64
177 accessed: statx_time
178 born: statx_time
179 changed: statx_time
180 modified: statx_time
181 special_device_major: u32
182 special_device_minor: u32
183 device_major: u32
184 device_minor: u32
185 mount_id: u64
186 spare: [13]u64
187 end file_status
188
189 --- Read at most `length` bytes from `descriptor` into `data`: the
190 --- count read, zero at end of file, or minus the errno.
191 public link(symbol: "_landin_linux_read") read:
192 (descriptor: i32, data: ptr mut u8, length: usize) -> (result: isize) =
193 result = isize(call6(read_number, i64(descriptor),
194 i64(usize(data)), i64(length), 0, 0, 0))
195 end read
196
197 --- Write at most `length` bytes from `data` to `descriptor`: the count
198 --- accepted, which may be short, or minus the errno.
199 public link(symbol: "_landin_linux_write") write:
200 (descriptor: i32, data: ptr u8, length: usize) -> (result: isize) =
201 result = isize(call6(write_number, i64(descriptor),
202 i64(usize(data)), i64(length), 0, 0, 0))
203 end write
204
205 --- Open `path`, relative to `directory` (or `at_fdcwd`), with `flags`
206 --- and, for `o_creat`, the permission bits `mode`: the new descriptor,
207 --- or minus the errno.
208 public link(symbol: "_landin_linux_openat") openat:
209 (directory: i32, path: ptr u8, flags: i32, mode: u32)
210 -> (result: i32) =
211 result = i32(call6(openat_number, i64(directory), i64(usize(path)),
212 i64(flags), i64(mode), 0, 0))
213 end openat
214
215 --- Close `descriptor`: zero, or minus the errno. The descriptor is
216 --- released even when this fails, so a failed close is never retried.
217 public link(symbol: "_landin_linux_close") close:
218 (descriptor: i32) -> (result: i32) =
219 result = i32(call6(close_number, i64(descriptor), 0, 0, 0, 0, 0))
220 end close
221
222 --- Fill `into` with the status of `path`, relative to `directory`:
223 --- zero, or minus the errno. `flags` zero follows symbolic links;
224 --- `mask` asks for fields beyond the basic ones.
225 public link(symbol: "_landin_linux_statx") statx:
226 (directory: i32, path: ptr u8, flags: i32, mask: u32,
227 into: ptr mut file_status) -> (result: i32) =
228 result = i32(call6(statx_number, i64(directory), i64(usize(path)),
229 i64(flags), i64(mask), i64(usize(into)), 0))
230 end statx
231
232 --- Map `length` bytes: the mapping's address, or minus the errno.
233 --- With `map_anonymous` the descriptor is -1 and the offset zero.
234 public link(symbol: "_landin_linux_mmap") mmap:
235 (address: usize, length: usize, protection: i32, flags: i32,
236 descriptor: i32, offset: i64) -> (result: i64) =
237 result = call6(mmap_number, i64(address), i64(length),
238 i64(protection), i64(flags), i64(descriptor), offset)
239 end mmap
240
241 --- Unmap the `length` bytes at `address`: zero, or minus the errno.
242 public link(symbol: "_landin_linux_munmap") munmap:
243 (address: usize, length: usize) -> (result: i32) =
244 result = i32(call6(munmap_number, i64(address), i64(length), 0, 0,
245 0, 0))
246 end munmap
247
248 --- End every thread of the process with `status`. It does not return.
249 public link(symbol: "_landin_linux_exit_group") exit_group:
250 (status: i32) -> noreturn =
251 _ = call6(exit_group_number, i64(status), 0, 0, 0, 0, 0)
252 compiler.unreachable()
253 end exit_group
254
255 word: usize = sizeof usize
256
257 -- The environment and the auxiliary vector as the process entry found
258 -- them, or zero when the C runtime started the program instead.
259 mut environment_root: usize
260 mut auxiliary_root: usize
261
262 --- What `environment` returns when the C runtime, not
263 --- `_landin_start`, started the program.
264 public no_environment: atom
265
266 --- The environment table: NUL-terminated `NAME=value` strings ending
267 --- at a null entry, or `no_environment`.
268 public environment_table: type = no_environment | ptr ptr u8
269
270 --- The environment `_landin_start` found on the initial stack.
271 public link(symbol: "_landin_linux_environment") environment:
272 () -> (table: environment_table) =
273 if environment_root == 0 then
274 table = no_environment
275 else
276 first: ptr ptr u8 = ptr(environment_root)
277 table = first
278 end if
279 end environment
280
281 --- The auxiliary-vector entry tagged `tag` (`6` is the page size), as
282 --- `_landin_start` found it: whether there is one, and its value.
283 public link(symbol: "_landin_linux_auxiliary") auxiliary:
284 (tag: u64) -> (found: bool, value: u64) =
285 found = false
286 value = 0
287 return when auxiliary_root == 0
288 mut at: usize = auxiliary_root
289 loop do
290 key: ptr u64 = ptr(at)
291 break when key.val == 0
292 if key.val == tag then
293 datum: ptr u64 = ptr(at + word)
294 found = true
295 value = datum.val
296 return
297 end if
298 at += 2 * word
299 end loop
300 end auxiliary
301
302 --- The page size the kernel reported, or 4096 when it reported none.
303 public link(symbol: "_landin_linux_page_size") page_size:
304 () -> (bytes: usize) =
305 (found, value) := auxiliary(6)
306 bytes = if found then usize(value) else usize(4096) end if
307 end page_size
308
309 -- The CPU feature level (D255) the executable was built for is a
310 -- compile-time fact, `compiler.feature.NAME` for each of its features.
311 -- The process entry holds the processor to that level before `main`
312 -- runs, as a dynamic loader holds it to an x86-64 executable's ISA
313 -- note: each check below exists only at a level that needs it, and the
314 -- baseline's check does nothing. The checks read words the processor
315 -- or the kernel gives and test constant masks against them, so they
316 -- use no instruction the level adds.
317 fixed if compiler.arch == x86_64 then
318 --- What the processor says of itself: `cpuid` leaf 1's ecx, leaf
319 --- 7's ebx, leaf 0x80000001's ecx, and the state the operating
320 --- system enables, XCR0's low half, which is zero unless both the
321 --- level needs AVX state and `cpuid` reports OSXSAVE. A leaf the
322 --- processor does not have reads as zero.
323 public cpu_words: type = struct
324 standard_ecx: u32
325 structured_ebx: u32
326 extended_ecx: u32
327 enabled_state: u32
328 end cpu_words
329
330 -- `cpuid`'s ebx, ecx or eax for `leaf`, subleaf zero.
331 cpuid_b: (leaf: u32) -> (word: u32) =
332 (a, b, c, d) :=
333 assembler.block("cpuid", inout a: u32 at rax = leaf,
334 out b: u32 at rbx, inout c: u32 at rcx = 0,
335 out d: u32 at rdx)
336 _ = a
337 _ = c
338 _ = d
339 word = b
340 end cpuid_b
341
342 cpuid_c: (leaf: u32) -> (word: u32) =
343 (a, b, c, d) :=
344 assembler.block("cpuid", inout a: u32 at rax = leaf,
345 out b: u32 at rbx, inout c: u32 at rcx = 0,
346 out d: u32 at rdx)
347 _ = a
348 _ = b
349 _ = d
350 word = c
351 end cpuid_c
352
353 cpuid_a: (leaf: u32) -> (word: u32) =
354 (a, b, c, d) :=
355 assembler.block("cpuid", inout a: u32 at rax = leaf,
356 out b: u32 at rbx, inout c: u32 at rcx = 0,
357 out d: u32 at rdx)
358 _ = b
359 _ = c
360 _ = d
361 word = a
362 end cpuid_a
363
364 -- OSXSAVE in leaf 1's ecx: the system enabled XGETBV and XCR0.
365 osxsave: u32 = 0x08000000
366
367 fixed if compiler.feature.avx then
368 -- `xgetbv` is XSAVE's, which only an AVX level assembles.
369 enabled_state: (standard_ecx: u32) -> (state: u32) =
370 state = 0
371 return when (standard_ecx & osxsave) == 0
372 (low, high) := assembler.block("xgetbv",
373 in c: u32 at rcx = 0,
374 out low: u32 at rax,
375 out high: u32 at rdx)
376 _ = high
377 state = low
378 end enabled_state
379 else
380 enabled_state: (standard_ecx: u32) -> (state: u32) =
381 _ = standard_ecx
382 state = 0
383 end enabled_state
384 end if
385
386 --- The words this processor gives.
387 public link(symbol: "_landin_linux_read_cpu_words") read_cpu_words:
388 () -> (words: cpu_words) =
389 top: u32 = cpuid_a(0)
390 extended_top: u32 = cpuid_a(0x80000000)
391 standard: u32 = if top >= 1 then cpuid_c(1) else 0 end if
392 structured: u32 = if top >= 7 then cpuid_b(7) else 0 end if
393 reach: bool = extended_top >= 0x80000001
394 extended: u32 = if reach then cpuid_c(0x80000001) else 0 end if
395 words = (standard_ecx: standard, structured_ebx: structured,
396 extended_ecx: extended,
397 enabled_state: enabled_state(standard))
398 end read_cpu_words
399
400 -- x86-64-v2's features, in D255's order, or "" when all are there.
401 missing_v2: (words: cpu_words) -> (name: []u8) =
402 ecx: u32 = words.standard_ecx
403 name = if (ecx & 0x00002000) == 0 then "cmpxchg16b"
404 elsif (words.extended_ecx & 0x00000001) == 0 then "lahf"
405 elsif (ecx & 0x00800000) == 0 then "popcnt"
406 elsif (ecx & 0x00000001) == 0 then "sse3"
407 elsif (ecx & 0x00000200) == 0 then "ssse3"
408 elsif (ecx & 0x00080000) == 0 then "sse4_1"
409 elsif (ecx & 0x00100000) == 0 then "sse4_2"
410 else "" end if
411 end missing_v2
412
413 -- x86-64-v3's, then the AVX state the system must save: XCR0's
414 -- SSE and AVX bits.
415 missing_v3: (words: cpu_words) -> (name: []u8) =
416 ecx: u32 = words.standard_ecx
417 ebx: u32 = words.structured_ebx
418 state: u32 = words.enabled_state
419 saved: bool = (ecx & osxsave) <> 0 and (state & 0x06) == 0x06
420 name = if (ecx & 0x10000000) == 0 then "avx"
421 elsif (ebx & 0x00000020) == 0 then "avx2"
422 elsif (ebx & 0x00000008) == 0 then "bmi1"
423 elsif (ebx & 0x00000100) == 0 then "bmi2"
424 elsif (ecx & 0x20000000) == 0 then "f16c"
425 elsif (ecx & 0x00001000) == 0 then "fma"
426 elsif (words.extended_ecx & 0x00000020) == 0 then "lzcnt"
427 elsif (ecx & 0x00400000) == 0 then "movbe"
428 elsif (ecx & 0x04000000) == 0 then "xsave"
429 elsif not saved then "avx state"
430 else "" end if
431 end missing_v3
432
433 -- x86-64-v4's, then the AVX-512 state: XCR0's opmask and upper
434 -- ZMM bits beside SSE and AVX.
435 missing_v4: (words: cpu_words) -> (name: []u8) =
436 ebx: u32 = words.structured_ebx
437 name = if (ebx & 0x00010000) == 0 then "avx512f"
438 elsif (ebx & 0x40000000) == 0 then "avx512bw"
439 elsif (ebx & 0x10000000) == 0 then "avx512cd"
440 elsif (ebx & 0x00020000) == 0 then "avx512dq"
441 elsif (ebx & 0x80000000) == 0 then "avx512vl"
442 elsif (words.enabled_state & 0xE6) <> 0xE6 then
443 "avx512 state"
444 else "" end if
445 end missing_v4
446
447 fixed if compiler.feature.avx512f then
448 --- The level this executable was built for.
449 public link(symbol: "_landin_linux_level") level:
450 () -> (name: []u8) =
451 name = "x86-64-v4"
452 end level
453
454 -- Hold the processor to this level before `main` runs.
455 check_level: () -> none =
456 hold_level()
457 end check_level
458
459 --- The first feature of this executable's level that `words`
460 --- lacks, by the name `compiler.feature` gives it, or "" when
461 --- the level is all there.
462 public link(symbol: "_landin_linux_missing_feature") missing_feature:
463 (words: cpu_words) -> (name: []u8) =
464 name = missing_v2(words)
465 return when lenof name <> 0
466 name = missing_v3(words)
467 return when lenof name <> 0
468 name = missing_v4(words)
469 end missing_feature
470 elsif compiler.feature.avx then
471 --- The level this executable was built for.
472 public link(symbol: "_landin_linux_level") level:
473 () -> (name: []u8) =
474 name = "x86-64-v3"
475 end level
476
477 -- Hold the processor to this level before `main` runs.
478 check_level: () -> none =
479 hold_level()
480 end check_level
481
482 --- The first feature of this executable's level that `words`
483 --- lacks, by the name `compiler.feature` gives it, or "" when
484 --- the level is all there.
485 public link(symbol: "_landin_linux_missing_feature") missing_feature:
486 (words: cpu_words) -> (name: []u8) =
487 name = missing_v2(words)
488 return when lenof name <> 0
489 name = missing_v3(words)
490 end missing_feature
491 elsif compiler.feature.popcnt then
492 --- The level this executable was built for.
493 public link(symbol: "_landin_linux_level") level:
494 () -> (name: []u8) =
495 name = "x86-64-v2"
496 end level
497
498 -- Hold the processor to this level before `main` runs.
499 check_level: () -> none =
500 hold_level()
501 end check_level
502
503 --- The first feature of this executable's level that `words`
504 --- lacks, by the name `compiler.feature` gives it, or "" when
505 --- the level is all there.
506 public link(symbol: "_landin_linux_missing_feature") missing_feature:
507 (words: cpu_words) -> (name: []u8) =
508 name = missing_v2(words)
509 end missing_feature
510 else
511 --- The level this executable was built for.
512 public link(symbol: "_landin_linux_level") level:
513 () -> (name: []u8) =
514 name = "x86-64-v1"
515 end level
516
517 -- The family's baseline needs no check, and runs none.
518 check_level: () -> none =
519 end check_level
520
521 --- The first feature of this executable's level that `words`
522 --- lacks, by the name `compiler.feature` gives it, or "" when
523 --- the level is all there.
524 public link(symbol: "_landin_linux_missing_feature") missing_feature:
525 (words: cpu_words) -> (name: []u8) =
526 _ = words
527 name = ""
528 end missing_feature
529 end if
530
531 elsif compiler.arch == arm64 then
532 --- What the kernel says of the processor: the auxiliary vector's
533 --- AT_HWCAP, as `_landin_start` found it, or zero.
534 public cpu_words: type = struct
535 hwcap: u64
536 end cpu_words
537
538 --- The words this processor gives.
539 public link(symbol: "_landin_linux_read_cpu_words") read_cpu_words:
540 () -> (words: cpu_words) =
541 (found, value) := auxiliary(16)
542 words = (hwcap: if found then value else 0 end if)
543 end read_cpu_words
544
545 fixed if compiler.feature.lse then
546 --- The level this executable was built for.
547 public link(symbol: "_landin_linux_level") level:
548 () -> (name: []u8) =
549 name = "armv8.1-a"
550 end level
551
552 -- Hold the processor to this level before `main` runs.
553 check_level: () -> none =
554 hold_level()
555 end check_level
556
557 --- The first feature of this executable's level that `words`
558 --- lacks, by the name `compiler.feature` gives it, or "" when
559 --- the level is all there: HWCAP_ATOMICS, HWCAP_CRC32 and
560 --- HWCAP_ASIMDRDM.
561 public link(symbol: "_landin_linux_missing_feature") missing_feature:
562 (words: cpu_words) -> (name: []u8) =
563 hwcap: u64 = words.hwcap
564 name = if (hwcap & 0x100) == 0 then "lse"
565 elsif (hwcap & 0x80) == 0 then "crc32"
566 elsif (hwcap & 0x1000) == 0 then "rdm"
567 else "" end if
568 end missing_feature
569 else
570 --- The level this executable was built for.
571 public link(symbol: "_landin_linux_level") level:
572 () -> (name: []u8) =
573 name = "armv8-a"
574 end level
575
576 -- The family's baseline needs no check, and runs none.
577 check_level: () -> none =
578 end check_level
579
580 --- The first feature of this executable's level that `words`
581 --- lacks, by the name `compiler.feature` gives it, or "" when
582 --- the level is all there.
583 public link(symbol: "_landin_linux_missing_feature") missing_feature:
584 (words: cpu_words) -> (name: []u8) =
585 _ = words
586 name = ""
587 end missing_feature
588 end if
589
590 else
591 --- What the kernel says of the processor: `riscv_hwprobe`'s
592 --- RISCV_HWPROBE_KEY_IMA_EXT_0, or zero when the kernel cannot
593 --- answer. Neither it nor AT_HWCAP reports a vendor extension, so
594 --- XTheadBa is not among what an executable is held to.
595 public cpu_words: type = struct
596 extensions: u64
597 end cpu_words
598
599 probe_pair: type = struct
600 key: i64
601 value: u64
602 end probe_pair
603
604 hwprobe_number: i64 = 258
605
606 --- The words this processor gives.
607 public link(symbol: "_landin_linux_read_cpu_words") read_cpu_words:
608 () -> (words: cpu_words) =
609 mut pair: probe_pair = (key: 4, value: 0)
610 result: i64 = call6(hwprobe_number, i64(usize(addr pair)), 1, 0,
611 0, 0, 0)
612 answered: bool = result == 0 and pair.key == 4
613 words = (extensions: if answered then pair.value else 0 end if)
614 end read_cpu_words
615
616 fixed if compiler.feature.zba then
617 fixed if compiler.feature.xtheadba then
618 --- The level this executable was built for.
619 public link(symbol: "_landin_linux_level") level:
620 () -> (name: []u8) =
621 name = "rv64gc_zba_xtheadba"
622 end level
623 else
624 --- The level this executable was built for.
625 public link(symbol: "_landin_linux_level") level:
626 () -> (name: []u8) =
627 name = "rv64gc_zba"
628 end level
629 end if
630
631 -- Hold the processor to this level before `main` runs.
632 check_level: () -> none =
633 hold_level()
634 end check_level
635
636 --- The first feature of this executable's level that `words`
637 --- lacks, by the name `compiler.feature` gives it, or "" when
638 --- the level is all there: RISCV_HWPROBE_EXT_ZBA.
639 public link(symbol: "_landin_linux_missing_feature") missing_feature:
640 (words: cpu_words) -> (name: []u8) =
641 name = if (words.extensions & 0x8) == 0 then "zba"
642 else "" end if
643 end missing_feature
644 else
645 fixed if compiler.feature.xtheadba then
646 --- The level this executable was built for.
647 public link(symbol: "_landin_linux_level") level:
648 () -> (name: []u8) =
649 name = "rv64gc_xtheadba"
650 end level
651 else
652 --- The level this executable was built for.
653 public link(symbol: "_landin_linux_level") level:
654 () -> (name: []u8) =
655 name = "rv64gc"
656 end level
657 end if
658
659 -- The family's baseline needs no check, and runs none.
660 check_level: () -> none =
661 end check_level
662
663 --- The first feature of this executable's level that `words`
664 --- lacks, by the name `compiler.feature` gives it, or "" when
665 --- the level is all there.
666 public link(symbol: "_landin_linux_missing_feature") missing_feature:
667 (words: cpu_words) -> (name: []u8) =
668 _ = words
669 name = ""
670 end missing_feature
671 end if
672
673 end if
674
675 -- The status a dynamic loader exits with when it refuses to start a
676 -- program, which glibc's loader gives an executable whose x86-64 ISA
677 -- note this processor does not meet: so a libc-linked and a static
678 -- build of one program end alike on one machine.
679 refused_status: i32 = 127
680
681 write_all: (text: []u8) -> none =
682 mut sent: usize = 0
683 while sent < lenof text do
684 data: ptr u8 = ptr(usize(addr text[0]) + sent)
685 result: isize = write(2, data, lenof text - sent)
686 return when result <= 0
687 sent += usize(result)
688 end while
689 end write_all
690
691 -- Hold the processor to the executable's level, or say which feature
692 -- it lacks on standard error and end the process.
693 hold_level: () -> none =
694 missing: []u8 = missing_feature(read_cpu_words())
695 return when lenof missing == 0
696 write_all("landin: this executable needs ")
697 write_all(level())
698 write_all(", and the processor lacks ")
699 write_all(missing)
700 write_all("\n")
701 exit_group(refused_status)
702 end hold_level
703
704 -- C's view of the program's hosted entry: the process entry calls it
705 -- as C's runtime would.
706 extern(c) link(symbol: "main") program_main:
707 (count: i32, values: ptr ptr u8, environment: ptr ptr u8)
708 -> (code: i32)
709
710 --- What `_landin_start` runs: it reads argc, argv, the environment and
711 --- the auxiliary vector from the initial stack at `stack`, keeps the
712 --- environment and the vector, holds the processor to the executable's
713 --- CPU feature level, calls the program's `main` with them as C's
714 --- runtime would, and ends the process with main's result. A processor
715 --- without a feature of the level ends the process with status 127 and
716 --- one line on standard error before `main` runs. No C library
717 --- initialisation runs and no thread-local storage exists.
718 public extern(c) _landin_start_main: (stack: ptr usize) -> none =
719 count: usize = stack.val
720 values: usize = usize(stack) + word
721 environment_first: usize = values + (count + 1) * word
722 mut at: usize = environment_first
723 loop do
724 entry: ptr usize = ptr(at)
725 at += word
726 break when entry.val == 0
727 end loop
728 environment_root = environment_first
729 auxiliary_root = at
730 check_level()
731 arguments: ptr ptr u8 = ptr(values)
732 variables: ptr ptr u8 = ptr(environment_first)
733 code: i32 = program_main(i32(count), arguments, variables)
734 exit_group(code)
735 end _landin_start_main
736end if