Landin library reference source

hosted/linux/linux.ldn

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