Landin library reference source

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