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