The 5 literal os.write diagnostics in obj.ww ("cannot read object",
"missing .text", "missing .symtab", and two "duplicate symbol") passed
hand-counted byte lengths that were each short by one, dropping the
trailing '\n' so every diagnostic printed without its newline. Replace
each magic length with the string's own .len via the local-binding
idiom (let m: str = "..."; os.write(2, m.ptr, m.len: u64);) — the
established wcc/err.ww + w6c/w6l/main.ww pattern — which fixes the
off-by-one and closes the hand-count class by construction. Uses
str-variable .len (correct on both stages), not "literal".len (cstage
miscompile, #14), so this is byte-identical cs==ww.
Also fold two trivially-safe nested-if collapses in the same file:
the archive-member skip guard (three sequential `if (first != ...)`
with no else → one &&-chain) and the text/data exclusivity guard
(`if (intext) { if (indt) ...`→ `if (intext && indt)`).
Verified: w6l_ww on a missing object now writes the full
"w6l: cannot read object\n"; w6c and w6c_ww emit byte-identical asm
for the regenerated main.combined.ww.
5388 lines
171 KiB
Plaintext
5388 lines
171 KiB
Plaintext
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
|
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
|
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
|
// sleep live in separate Hare modules and graduate when callers /
|
|
// supporting stdlib arrive.
|
|
//
|
|
// `duration` is a NAMED alias of i64 (lib/math/random precedent
|
|
// at lib/math/random/random.ww:8); ww treats NAMED as a newtype,
|
|
// so cross-i64 arithmetic inside this module needs explicit casts.
|
|
// Hare's structural alias semantics let those casts vanish, but
|
|
// our type checker is strict.
|
|
|
|
package time;
|
|
|
|
@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64;
|
|
@symbol("rt_abort") fn abort(msg: str) void;
|
|
|
|
def SYS_CLOCK_GETTIME: i64 = 228;
|
|
|
|
// ref/hare/time/duration.ha:6. 290y representable range.
|
|
export type duration = i64;
|
|
|
|
// ref/hare/time/duration.ha:9-18. Plan-9 naming (lowercase)
|
|
// diverges from Hare's uppercase per project rule 4.
|
|
export def nanosecond: duration = 1i64;
|
|
export def microsecond: duration = 1000i64;
|
|
export def millisecond: duration = 1000000i64;
|
|
export def second: duration = 1000000000i64;
|
|
|
|
// ref/hare/time/instant.ha:9. (sec, nsec) pair — NOT POSIX struct
|
|
// timespec (which uses u32 nsec). Layout matches Linux's struct
|
|
// timespec on 64-bit (i64+i64) so we can pass &instant directly
|
|
// to clock_gettime.
|
|
export type instant = struct {
|
|
sec: i64,
|
|
nsec: i64,
|
|
};
|
|
|
|
// ref/hare/time/+linux/functions.ha:84. First cut exposes only
|
|
// realtime and monotonic; Hare's process_cpu / thread_cpu / boot /
|
|
// realtime_alarm / boot_alarm / tai graduate when a caller needs
|
|
// them (CLAUDE.md rule 9 — Hare-fidelity, no premature surface).
|
|
export type clock = enum i32 {
|
|
realtime = 0,
|
|
monotonic = 1,
|
|
};
|
|
|
|
// ref/hare/time/+linux/functions.ha:138. Hare's now() also aborts
|
|
// on impossible errnos. (instant | oserror) is deliberately not
|
|
// the return shape — EINVAL / EFAULT are programmer errors (bad
|
|
// clock id, bad ptr), and a 1-word-payload sum return walks into
|
|
// task #9's cgen-divergence trap.
|
|
export fn now(c: clock) instant = {
|
|
let i: instant;
|
|
let rc = syscall2(SYS_CLOCK_GETTIME, (c as i32): i64, (&i): i64);
|
|
if (rc != 0i64) { abort("time.now: clock_gettime failed"); };
|
|
return i;
|
|
};
|
|
|
|
// ref/hare/time/arithm.ha:9. Adds duration to instant. The
|
|
// negative-duration branch normalises nsec into [0, second).
|
|
export fn add(i: instant, x: duration) instant = {
|
|
let r: instant;
|
|
let xi: i64 = x: i64;
|
|
let sec: i64 = second: i64;
|
|
let nsec: i64 = nanosecond: i64;
|
|
if (xi == 0i64) {
|
|
r.sec = i.sec;
|
|
r.nsec = i.nsec;
|
|
return r;
|
|
};
|
|
if (xi > 0i64) {
|
|
r.sec = i.sec + (i.nsec + xi) / sec;
|
|
r.nsec = (i.nsec + xi) % sec;
|
|
return r;
|
|
};
|
|
r.sec = i.sec + (i.nsec + xi - sec + nsec) / sec;
|
|
r.nsec = (i.nsec + (xi % sec) + sec) % sec;
|
|
return r;
|
|
};
|
|
|
|
// ref/hare/time/arithm.ha:26. Returns duration from a to b.
|
|
// Sign convention: b - a.
|
|
export fn diff(a: instant, b: instant) duration = {
|
|
let sec: i64 = second: i64;
|
|
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);
|
|
return v: duration;
|
|
};
|
|
|
|
// ref/hare/time/arithm.ha:32. -1 if a < b, 0 if equal, +1 if a > b.
|
|
export fn compare(a: instant, b: instant) i8 = {
|
|
if (a.sec < b.sec) { return -1i8; };
|
|
if (a.sec > b.sec) { return 1i8; };
|
|
if (a.nsec < b.nsec) { return -1i8; };
|
|
if (a.nsec > b.nsec) { return 1i8; };
|
|
return 0i8;
|
|
};
|
|
|
|
// os — process and filesystem facade. The body of each call lands
|
|
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
|
// depending on how the program was linked.
|
|
|
|
package os;
|
|
|
|
import time;
|
|
|
|
@symbol("rt_syscall") fn syscall0(num: nr) i64;
|
|
@symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64;
|
|
@symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64;
|
|
@symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64;
|
|
@symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64;
|
|
|
|
@symbol("rt_free") export fn free(p: *void, n: u64) void;
|
|
@symbol("rt_abort") fn abort(msg: str) void;
|
|
|
|
// Hare-style runtime check. Caller passes a message that's printed
|
|
// to stderr before exit(1).
|
|
export fn assert(cond: bool, msg: str) void = {
|
|
if (!cond) { abort(msg); };
|
|
};
|
|
|
|
// Linux amd64 syscall numbers. Internal to this module — passed as
|
|
// the first arg of syscall0..4 via libwwrt's rt_syscall trampoline.
|
|
// `nr` is the type so the call sites can't accidentally pass an
|
|
// arbitrary i64 (`syscall1(0i64, ...)` no longer typechecks).
|
|
type nr = enum i64 {
|
|
READ = 0,
|
|
WRITE = 1,
|
|
OPEN = 2,
|
|
CLOSE = 3,
|
|
LSEEK = 8,
|
|
ACCESS = 21,
|
|
DUP2 = 33,
|
|
GETPID = 39,
|
|
FORK = 57,
|
|
EXECVE = 59,
|
|
EXIT = 60,
|
|
WAIT4 = 61,
|
|
MKDIR = 83,
|
|
RMDIR = 84,
|
|
UNLINK = 87,
|
|
GETCWD = 79,
|
|
GETDENTS64 = 217,
|
|
NEWFSTATAT = 262,
|
|
};
|
|
|
|
// open(2) flags. Linux values, matching <fcntl.h>. Hare names them
|
|
// `fs::flag::RDONLY` etc; we use the same leaf names so callers say
|
|
// `os.flag.RDONLY` and `os.flag.WRONLY | os.flag.CREATE`.
|
|
export type flag = enum i32 {
|
|
RDONLY = 0,
|
|
WRONLY = 1,
|
|
RDWR = 2,
|
|
CREATE = 64, // 0x40
|
|
EXCL = 128, // 0x80 — pair with CREATE to fail on existing path
|
|
TRUNC = 512, // 0x200
|
|
};
|
|
|
|
// lseek(2) whence. Hare names it `io::whence`.
|
|
export type whence = enum i32 {
|
|
SET = 0,
|
|
CUR = 1,
|
|
END = 2,
|
|
};
|
|
|
|
export fn exit(code: i32) void = {
|
|
syscall1(nr.EXIT, code: i64);
|
|
};
|
|
|
|
// PATH_MAX / pathbuf / kpath — port of Hare's ref/hare/sys/+linux/
|
|
// syscalls.ha:25,27,29-55. Hare's `path` accepts a sum `(str |
|
|
// []u8 | *const u8)`; ww's lib/os public surface narrows to `str`
|
|
// (the Hare-faithful surface at ref/hare/os/os.ha:37,47,50 etc).
|
|
// Internally, [[kpath]] copies the `str` bytes into a single
|
|
// module-level [[pathbuf]] scratch slot and NUL-terminates so the
|
|
// raw Linux syscalls (which require C strings) see a valid
|
|
// terminator. Same precedent as Hare's static `pathbuf`.
|
|
//
|
|
// Non-reentrant: one buffer, every [[stat]] / [[open]] / etc.
|
|
// rewrites it. Same caveat as strconv's `*tos` family (overwritten
|
|
// on next call). Caller must NOT hold a kpath-returned pointer
|
|
// across another lib/os path call. Graduates when ww grows a
|
|
// thread story.
|
|
//
|
|
// `nil`-as-overflow over `(*u8 | oserror)`: wwstage over-allocates
|
|
// 1-word-payload tagged returns to 24B (cstage emits 16B).
|
|
// Task #9; revert at task #10 when fixed. Repro at
|
|
// .ai/probe_tagged_return_pointer_payload.ww.
|
|
export def PATH_MAX: i32 = 4096;
|
|
let pathbuf: [4096]u8;
|
|
|
|
// ref/hare/sys/+linux/types.ha:886-888. ww folds `sys` into `os`, so the
|
|
// std fd NUMBERS live here (the sys role). Typed i32, NOT io.file as in
|
|
// Hare's os::stdout_file (ref/hare/os/+linux/stdfd.ha:28): Hare's `os`
|
|
// imports `io`, but ww's `os` is the import floor and must never import
|
|
// io (lib/CLAUDE.md) — so the io.file/io.handle binding can't live here.
|
|
// Consumers (lib/fmt's stdio wrappers) cast i32→io.file at the use site,
|
|
// where the handle layer is already in scope.
|
|
export def STDIN_FILENO: i32 = 0;
|
|
export def STDOUT_FILENO: i32 = 1;
|
|
export def STDERR_FILENO: i32 = 2;
|
|
|
|
fn kpath(p: str) *u8 = {
|
|
if (p.len + 1 >= PATH_MAX) { return nil: *u8; }; // ENAMETOOLONG
|
|
let i: i32 = 0;
|
|
for (i < p.len) { pathbuf[i] = p[i]; i += 1; };
|
|
pathbuf[p.len] = 0u8;
|
|
return &pathbuf[0];
|
|
};
|
|
|
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
|
// Hare-style fallible API use the wrappers below.
|
|
export fn write(fd: i32, buf: *u8, n: u64) i64 = {
|
|
return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64);
|
|
};
|
|
|
|
export fn read(fd: i32, buf: *u8, n: u64) i64 = {
|
|
return syscall3(nr.READ, fd: i64, buf: i64, n: i64);
|
|
};
|
|
|
|
export fn close(fd: i32) i32 = {
|
|
return syscall1(nr.CLOSE, fd: i64): i32;
|
|
};
|
|
|
|
// dup2(2): make `newfd` refer to the same description as `oldfd`,
|
|
// closing `newfd` first if open. Returns `newfd` on success or a
|
|
// negative errno. Used by w6c_ww to redirect stdout into an output
|
|
// file without changing the cgen emit path.
|
|
export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
|
return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32;
|
|
};
|
|
|
|
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
|
|
// -errno). The sum type makes success/failure explicit and lets
|
|
// callers `?` the result up the stack.
|
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|
let r: i64 = read(fd, buf, n);
|
|
if (r < 0) { return r: oserror; };
|
|
return r;
|
|
};
|
|
|
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|
let r: i64 = write(fd, buf, n);
|
|
if (r < 0) { return r: oserror; };
|
|
return r;
|
|
};
|
|
|
|
// open — Linux open(2). Returns -errno on failure, fd otherwise.
|
|
// Higher-level callers prefer `tryopen`. Mirrors Hare's os::open
|
|
// (ref/hare/os/os.ha:117); kpath lands the bytes in pathbuf.
|
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
|
export fn open(path: str, flags: flag, mode: i32) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; }; // ENAMETOOLONG
|
|
return syscall3(nr.OPEN, p: i64, (flags as i32): i64, mode: i64): i32;
|
|
};
|
|
|
|
export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
|
|
let fd: i32 = open(path, flags, mode);
|
|
if (fd < 0) { return fd: i64: oserror; };
|
|
return fd;
|
|
};
|
|
|
|
// lseek — set/inspect the fd's position. Returns the new offset or
|
|
// a negative errno. We use this for fstat-free file-size discovery
|
|
// (open ⇒ lseek to end ⇒ lseek back).
|
|
export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
|
return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64);
|
|
};
|
|
|
|
// oserror — the underlying errno from a failed syscall, as a
|
|
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
|
// `!`-flagged alias makes ?-propagation pick this variant as the
|
|
// error half of any (T | oserror) shape. Hare's analogue is
|
|
// errors::errno carried inside io::error.
|
|
export type oserror = !i64;
|
|
|
|
// errno — the raw Linux errno as a positive code (ref/hare/sys/+linux/
|
|
// errno.ha:5, `errno = !int`). ww folds Hare's `sys` role into os
|
|
// (lib/CLAUDE.md), so the sys::errno machinery lands here. Spelled i32
|
|
// rather than int: Linux errnos are kernel ints (32-bit), keeping os's
|
|
// kernel-facing surface uniformly i32. Distinct from [[oserror]] (!i64,
|
|
// the syscall's *negative* raw return) — the two model different
|
|
// things, so they are not unified; the negative→positive normalization
|
|
// lives at the oserror→errors.error boundary in those callers.
|
|
export type errno = !i32;
|
|
|
|
// Mapped errno values, ref/hare/sys/+linux/errno.ha:559-682. Positive,
|
|
// matching Hare's defs (the kernel returns -N; the wrap-to-positive is
|
|
// the caller's concern). Subset: exactly the errnos [[errors.errno]]
|
|
// maps to a named condition; grow as callers surface more.
|
|
export def ENOENT: errno = 2;
|
|
export def EINTR: errno = 4;
|
|
export def EAGAIN: errno = 11;
|
|
export def EACCES: errno = 13;
|
|
export def EBUSY: errno = 16;
|
|
export def EEXIST: errno = 17;
|
|
export def EINVAL: errno = 22;
|
|
export def EOVERFLOW: errno = 75;
|
|
export def ENETUNREACH: errno = 101;
|
|
export def ETIMEDOUT: errno = 110;
|
|
export def ECONNREFUSED: errno = 111;
|
|
export def ECANCELED: errno = 125;
|
|
|
|
// strerror — human-readable text for an [[errno]] (Hare's
|
|
// sys::strerror, ref/hare/sys/+linux/errno.ha:18). FAITHFUL MINIMAL
|
|
// SUBSET: the mapped errnos above plus a generic fallback; grow the
|
|
// switch as callers surface more (lib/CLAUDE.md documented-subset, not
|
|
// a workaround). Messages verbatim from the reference. Hare's
|
|
// unknown_errno formats the numeric value; that is deferred.
|
|
export fn strerror(err: errno) str = {
|
|
switch (err) {
|
|
case ENOENT: return "No such file or directory";
|
|
case EINTR: return "Interrupted system call";
|
|
case EAGAIN: return "Resource temporarily unavailable";
|
|
case EACCES: return "Permission denied";
|
|
case EBUSY: return "Device or resource busy";
|
|
case EEXIST: return "File exists";
|
|
case EINVAL: return "Invalid argument";
|
|
case EOVERFLOW: return "Value too large for defined data type";
|
|
case ENETUNREACH: return "Network is unreachable";
|
|
case ETIMEDOUT: return "Connection timed out";
|
|
case ECONNREFUSED: return "Connection refused";
|
|
case ECANCELED: return "Operation canceled";
|
|
};
|
|
return "Unknown error";
|
|
};
|
|
|
|
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
|
let end: i64 = lseek(fd, 0i64, whence.END);
|
|
if (end < 0) { return end: oserror; };
|
|
let r: i64 = lseek(fd, 0i64, whence.SET);
|
|
if (r < 0) { return r: oserror; };
|
|
return end;
|
|
};
|
|
|
|
// readall — keep reading until `n` bytes have arrived or the fd
|
|
// closes early. Hare name (io::readall); the buffer is caller-
|
|
// supplied, matching the Plan 9 subset convention.
|
|
export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|
let got: u64 = 0u64;
|
|
for (got < n) {
|
|
let r: i64 = read(fd, buf + got, n - got);
|
|
if (r < 0) { return r: oserror; };
|
|
if (r == 0) { return got: i64; }; // short read: caller decides
|
|
got += r: u64;
|
|
};
|
|
return got: i64;
|
|
};
|
|
|
|
// writeall — keep writing until `n` bytes have been accepted or the
|
|
// fd refuses progress. Hare name (io::writeall).
|
|
export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|
let sent: u64 = 0u64;
|
|
for (sent < n) {
|
|
let r: i64 = write(fd, buf + sent, n - sent);
|
|
if (r < 0) { return r: oserror; };
|
|
if (r == 0) { return sent: i64; };
|
|
sent += r: u64;
|
|
};
|
|
return sent: i64;
|
|
};
|
|
|
|
// ---- process and filesystem helpers used by the `ww` driver ----------
|
|
|
|
// access(2): returns 0 if the file is reachable, negative errno
|
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
|
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
|
// Returns -ENAMETOOLONG (-36) if the path overflows PATH_MAX.
|
|
export fn access(path: str, mode: i32) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; };
|
|
return syscall2(nr.ACCESS, p: i64, mode: i64): i32;
|
|
};
|
|
|
|
// remove — unlink(2). Mirrors Hare's os::remove
|
|
// (ref/hare/os/os.ha:12).
|
|
export fn remove(path: str) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; };
|
|
return syscall1(nr.UNLINK, p: i64): i32;
|
|
};
|
|
|
|
// mkdir — mkdir(2). Mode is the unix permission bitset (e.g. 0o700).
|
|
// Returns 0 on success, negative errno otherwise. Mirrors Hare's
|
|
// os::mkdir (ref/hare/os/os.ha:50).
|
|
export fn mkdir(path: str, mode: i32) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; };
|
|
return syscall2(nr.MKDIR, p: i64, mode: i64): i32;
|
|
};
|
|
|
|
// rmdir — rmdir(2). Mirrors Hare's os::rmdir
|
|
// (ref/hare/os/os.ha:58).
|
|
export fn rmdir(path: str) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; };
|
|
return syscall1(nr.RMDIR, p: i64): i32;
|
|
};
|
|
|
|
// mkdirs — recursive mkdir. Creates `path` and any non-existent
|
|
// parent directories with the given mode. EEXIST is silently
|
|
// accepted (matches Hare's `errors::exists` skip in os::mkdirs);
|
|
// any other syscall failure surfaces as `oserror`.
|
|
//
|
|
// Mirrors Hare's os::mkdirs (ref/hare/os/os.ha:54). The in-place
|
|
// '/' → NUL splice walks the kpath-loaded [[pathbuf]] directly
|
|
// instead of recursing through [[mkdir]] — re-entering kpath would
|
|
// clobber the buffer mid-walk (single static slot, see kpath's
|
|
// non-reentrancy note above).
|
|
export fn mkdirs(path: str, mode: i32) (void | oserror) = {
|
|
let cp: *u8 = kpath(path);
|
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
|
let n: i32 = path.len;
|
|
if (n == 0) { return; };
|
|
|
|
// Walk forward; at each '/' boundary, NUL-terminate the prefix,
|
|
// raw MKDIR syscall on pathbuf, restore the slash, continue.
|
|
// Skip index 0 so a leading '/' on absolute paths doesn't
|
|
// trigger an empty mkdir.
|
|
let i: i32 = 1;
|
|
for (i < n) {
|
|
if (pathbuf[i] == '/') {
|
|
pathbuf[i] = 0u8;
|
|
let r: i32 = syscall2(nr.MKDIR,
|
|
(&pathbuf[0]): i64, mode: i64): i32;
|
|
pathbuf[i] = 47u8;
|
|
if (r < 0) {
|
|
if (r != -17) { return r: i64: oserror; };
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
|
|
let r: i32 = syscall2(nr.MKDIR,
|
|
(&pathbuf[0]): i64, mode: i64): i32;
|
|
if (r < 0) {
|
|
if (r != -17) { return r: i64: oserror; };
|
|
};
|
|
return;
|
|
};
|
|
|
|
// getpid(2). Used by the driver to mint unique scratch paths.
|
|
export fn getpid() i32 = {
|
|
return syscall0(nr.GETPID): i32;
|
|
};
|
|
|
|
// fork(2): 0 in the child, child pid in the parent, negative errno
|
|
// on failure.
|
|
export fn fork() i32 = {
|
|
return syscall0(nr.FORK): i32;
|
|
};
|
|
|
|
// execve(2): on success, does not return. Mirrors Hare's
|
|
// os::exec::exec path arg (str). argv/envp stay `**u8` — the
|
|
// kernel takes a NUL-pointer-terminated table of NUL-terminated
|
|
// C strings, a different shape from a path.
|
|
export fn execve(path: str, argv: **u8, envp: **u8) i32 = {
|
|
let p: *u8 = kpath(path);
|
|
if (p == nil: *u8) { return -36i32; };
|
|
return syscall3(nr.EXECVE, p: i64, argv: i64, envp: i64): i32;
|
|
};
|
|
|
|
// wait4(2): wait for `pid` (or any child if -1), store status in
|
|
// `*status`, return the pid that ended (or negative errno).
|
|
export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = {
|
|
return syscall4(nr.WAIT4, pid: i64, status: i64,
|
|
options: i64, rusage: i64): i32;
|
|
};
|
|
|
|
// getcwd(2) — Linux flavour. Writes the NUL-terminated cwd into `buf`
|
|
// and returns the number of bytes written (including the NUL), or a
|
|
// negative errno. The driver uses it to expand `.` to the cwd's
|
|
// basename for `ww build` / `ww test`.
|
|
export fn getcwd(buf: *u8, n: u64) i64 = {
|
|
return syscall2(nr.GETCWD, buf: i64, n: i64);
|
|
};
|
|
|
|
// getdents64(2) — Linux directory enumeration. The fd must be opened
|
|
// with O_RDONLY on a directory. `buf` receives a packed sequence of
|
|
// linux_dirent64 records:
|
|
//
|
|
// struct linux_dirent64 {
|
|
// u64 d_ino; // 0..7
|
|
// i64 d_off; // 8..15
|
|
// u16 d_reclen; // 16..17 — total bytes for this record
|
|
// u8 d_type; // 18 — DT_REG/DT_DIR/...
|
|
// u8 d_name[]; // 19.. — NUL-terminated name + padding
|
|
// };
|
|
//
|
|
// Returns bytes written into `buf` (advance by d_reclen to walk),
|
|
// 0 at end-of-directory, or a negative errno.
|
|
export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
|
return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64);
|
|
};
|
|
|
|
// ---- environment ------------------------------------------------------
|
|
|
|
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
|
|
// slot before calling main; this binding lifts the captured pointer
|
|
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
|
|
// symbol the linker resolves. The returned `**u8` is a NUL-terminated
|
|
// table of `*u8` entries, each pointing at a NUL-terminated
|
|
// "NAME=VALUE" byte sequence.
|
|
//
|
|
// We don't expose `rtenvp` directly; [[getenv]] is the only consumer.
|
|
@symbol("rt_envp") fn rtenvp() **u8;
|
|
|
|
// getenv — POSIX getenv. Returns a borrowed `str` view over the value
|
|
// bytes of the named environment variable, or void if the name is not
|
|
// present. The view is valid for the process lifetime — the bytes
|
|
// live in the kernel-supplied envp table at process entry. A future
|
|
// `setenv` (separate task) that grows the table behind the scenes
|
|
// would invalidate prior views; v1 has no setenv, so callers can
|
|
// hold the view indefinitely.
|
|
//
|
|
// Mirrors Hare's os::tryenv shape (returns void rather than panicking
|
|
// on missing). Hare also ships os::getenv (`(str | void)`) and
|
|
// os::mustenv (panic-on-missing); ww collapses to the single
|
|
// `(str | void)` form for now — consumers wanting "must" semantics
|
|
// abort at the call site.
|
|
//
|
|
// Algorithm: walk the NUL-pointer-terminated `environ` table doing a
|
|
// "name=" prefix match against each entry, byte-wise. NUL inside
|
|
// `name` would never match a real env var (env var names cannot
|
|
// contain '\0'), so we don't filter — POSIX puts that responsibility
|
|
// on the caller.
|
|
export fn getenv(name: str) (str | void) = {
|
|
let envp: **u8 = rtenvp();
|
|
let i: i32 = 0;
|
|
for (true) {
|
|
let entry: *u8 = envp[i];
|
|
if (entry == nil: *u8) { return; };
|
|
let j: i32 = 0;
|
|
let matched: bool = true;
|
|
for (j < name.len) {
|
|
if (entry[j] == 0u8) { matched = false; break; };
|
|
if (entry[j] != name[j]) { matched = false; break; };
|
|
j += 1;
|
|
};
|
|
if (matched) {
|
|
if (entry[name.len] == '=') {
|
|
let val: *u8 = entry + ((name.len + 1): u64);
|
|
let n: i32 = 0;
|
|
for (val[n] != 0u8) { n += 1; };
|
|
let r: str;
|
|
r.ptr = val;
|
|
r.len = n;
|
|
return r;
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
return;
|
|
};
|
|
|
|
// ---- stat / lstat / fstat / exists -----------------------------------
|
|
//
|
|
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
|
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
|
|
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
|
|
// STATUS task #21) and filestat is 80B, so [[stat]] / [[lstat]] /
|
|
// [[fstat]] take an out-parameter and return `(void | oserror)`.
|
|
// Re-evaluate the by-value shape when full sret lands.
|
|
//
|
|
// `filestat`, `mode`, and `stat_mask` live in lib/os because ww has
|
|
// no lib/fs yet; Hare puts them in `fs::`. These types graduate to
|
|
// lib/fs when that module ships — callers should expect a future
|
|
// re-export.
|
|
//
|
|
// Underlying syscall is SYS_newfstatat (262), which unifies
|
|
// stat/lstat/fstat through the `dirfd + flags` triple:
|
|
// stat = newfstatat(AT_FDCWD, path, 0)
|
|
// lstat = newfstatat(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW)
|
|
// fstat = newfstatat(fd, "", AT_EMPTY_PATH)
|
|
// Avoiding SYS_statx — its 256B variable layout would buy btime,
|
|
// but Hare's filestat doesn't expose btime either, so we stay on
|
|
// the simpler 144B kernel struct.
|
|
|
|
// fstatat(2) flag values. Linux constants from <linux/fcntl.h>.
|
|
// Names mirror Hare's ref/hare/sys/+linux/types.ha:45-51 (capital-
|
|
// AT_ prefix, top-level `def`s).
|
|
export def AT_FDCWD: i32 = -100;
|
|
export def AT_SYMLINK_NOFOLLOW: i32 = 256; // 0x100
|
|
export def AT_EMPTY_PATH: i32 = 4096; // 0x1000
|
|
|
|
// mode — file-mode bits. Mirrors Hare's fs::mode (ref/hare/fs/
|
|
// types.ha:63). Permission bits are the standard Unix octal subset;
|
|
// type bits live in the S_IFMT = 0o170000 region. Type-bit test:
|
|
//
|
|
// let t: u32 = (fi.mode as u32) & 61440u32; // 0o170000 mask
|
|
// if (t == os.mode.DIR as u32) { /* directory */ };
|
|
//
|
|
// Numeric values are octal in Hare's source; ww has no octal
|
|
// literals so they're written as decimal with the octal in a
|
|
// trailing comment.
|
|
export type mode = enum u32 {
|
|
// permission bits
|
|
USER_RWX = 448u32, // 0o700
|
|
USER_RW = 384u32, // 0o600
|
|
USER_RX = 320u32, // 0o500
|
|
USER_R = 256u32, // 0o400
|
|
USER_W = 128u32, // 0o200
|
|
USER_X = 64u32, // 0o100
|
|
GROUP_RWX = 56u32, // 0o070
|
|
GROUP_RW = 48u32, // 0o060
|
|
GROUP_RX = 40u32, // 0o050
|
|
GROUP_R = 32u32, // 0o040
|
|
GROUP_W = 16u32, // 0o020
|
|
GROUP_X = 8u32, // 0o010
|
|
OTHER_RWX = 7u32, // 0o007
|
|
OTHER_RW = 6u32, // 0o006
|
|
OTHER_RX = 5u32, // 0o005
|
|
OTHER_R = 4u32, // 0o004
|
|
OTHER_W = 2u32, // 0o002
|
|
OTHER_X = 1u32, // 0o001
|
|
SETUID = 2048u32, // 0o4000
|
|
SETGID = 1024u32, // 0o2000
|
|
STICKY = 512u32, // 0o1000
|
|
// file-type bits (S_IFMT mask = 0o170000 = 61440)
|
|
UNKNOWN = 0u32,
|
|
FIFO = 4096u32, // 0o010000
|
|
CHR = 8192u32, // 0o020000
|
|
DIR = 16384u32, // 0o040000
|
|
BLK = 24576u32, // 0o060000
|
|
REG = 32768u32, // 0o100000
|
|
LINK = 40960u32, // 0o120000
|
|
SOCK = 49152u32, // 0o140000
|
|
};
|
|
|
|
// stat_mask — which filestat fields the call populated. Mirrors
|
|
// Hare's fs::stat_mask (ref/hare/fs/types.ha:129). newfstatat fills
|
|
// every field, so [[stat]] / [[lstat]] / [[fstat]] always set all
|
|
// seven bits OR-folded (see [[fillfilestat]]); per-bit testing is
|
|
// the documented sparse-backend pattern (cf. Hare's fs::fs network
|
|
// backends that only populate mtime+size).
|
|
export type stat_mask = enum u32 {
|
|
UID = 1u32,
|
|
GID = 2u32,
|
|
SIZE = 4u32,
|
|
INODE = 8u32,
|
|
ATIME = 16u32,
|
|
MTIME = 32u32,
|
|
CTIME = 64u32,
|
|
};
|
|
|
|
// filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80
|
|
// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the
|
|
// canonical Hare shape. See module-header note re: graduation to
|
|
// lib/fs.
|
|
export type filestat = struct {
|
|
mask: stat_mask, // 0 (4)
|
|
mode: mode, // 4 (4)
|
|
uid: u32, // 8 (4)
|
|
gid: u32, // 12 (4)
|
|
sz: u64, // 16 (8)
|
|
inode: u64, // 24 (8)
|
|
atime: time.instant, // 32 (16)
|
|
mtime: time.instant, // 48 (16)
|
|
ctime: time.instant, // 64 (16) — ends at 80
|
|
};
|
|
|
|
// kstat — x86_64 kernel `struct stat` layout. Mirrors
|
|
// arch/x86/include/uapi/asm/stat.h (`__kernel_ulong_t`-keyed
|
|
// fields). 144 bytes. Module-internal; SYS_newfstatat writes into
|
|
// this buffer and the public stat fns then copy the bits into the
|
|
// Hare-shaped [[filestat]].
|
|
type kstat = struct {
|
|
dev: u64, // 0
|
|
ino: u64, // 8
|
|
nlink: u64, // 16
|
|
mode: u32, // 24
|
|
uid: u32, // 28
|
|
gid: u32, // 32
|
|
pad0: u32, // 36
|
|
rdev: u64, // 40
|
|
sz: i64, // 48
|
|
blksize: i64, // 56
|
|
blocks: i64, // 64
|
|
atime_sec: i64, // 72
|
|
atime_nsec: i64, // 80
|
|
mtime_sec: i64, // 88
|
|
mtime_nsec: i64, // 96
|
|
ctime_sec: i64, // 104
|
|
ctime_nsec: i64, // 112
|
|
unused0: i64, // 120
|
|
unused1: i64, // 128
|
|
unused2: i64, // 136 — ends at 144
|
|
};
|
|
|
|
// emptypath — single-NUL byte used as the `pathname` arg to
|
|
// newfstatat with AT_EMPTY_PATH. The kernel requires a non-NULL
|
|
// pointer to a zero-length C string, NOT a null pointer. Bytes are
|
|
// read-only from the kernel's view; ww has no module-level const so
|
|
// this is a writable `let`.
|
|
let emptypath: [1]u8 = [0u8];
|
|
|
|
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
|
// filestat. Internal helper used by all three public entry points.
|
|
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
|
// newfstatat populates every field, so the mask is the OR-fold of
|
|
// all seven Hare stat_mask bits.
|
|
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
|
out.mask = stat_mask.UID | stat_mask.GID | stat_mask.SIZE
|
|
| stat_mask.INODE | stat_mask.ATIME | stat_mask.MTIME
|
|
| stat_mask.CTIME;
|
|
out.mode = k.mode: mode;
|
|
out.uid = k.uid;
|
|
out.gid = k.gid;
|
|
out.sz = k.sz: u64;
|
|
out.inode = k.ino;
|
|
out.atime.sec = k.atime_sec;
|
|
out.atime.nsec = k.atime_nsec;
|
|
out.mtime.sec = k.mtime_sec;
|
|
out.mtime.nsec = k.mtime_nsec;
|
|
out.ctime.sec = k.ctime_sec;
|
|
out.ctime.nsec = k.ctime_nsec;
|
|
};
|
|
|
|
// stat — fill *out with metadata for `path`. Follows symlinks.
|
|
// Returns ENAMETOOLONG (-36) as `oserror` if the path overflows
|
|
// PATH_MAX.
|
|
//
|
|
// Mirrors Hare's sys::stat (ref/hare/sys/+linux/stat.ha:51) modulo
|
|
// the out-param shape forced by the cgreturn 24B cap. Note: Hare's
|
|
// higher-level fs::stat (ref/hare/fs/fs.ha:172) instead has lstat
|
|
// semantics — we follow sys::stat's POSIX-stat behavior here.
|
|
export fn stat(out: *filestat, path: str) (void | oserror) = {
|
|
let cp: *u8 = kpath(path);
|
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
|
let k: kstat;
|
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
|
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
|
if (r < 0) { return r: oserror; };
|
|
fillfilestat(out, &k);
|
|
};
|
|
|
|
// lstat — like [[stat]] but does NOT follow a terminal symlink.
|
|
// Mirrors Hare's sys::lstat (ref/hare/sys/+linux/stat.ha:57).
|
|
export fn lstat(out: *filestat, path: str) (void | oserror) = {
|
|
let cp: *u8 = kpath(path);
|
|
if (cp == nil: *u8) { return -36i64: oserror; };
|
|
let k: kstat;
|
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
|
AT_FDCWD: i64, cp: i64, (&k): i64,
|
|
AT_SYMLINK_NOFOLLOW: i64);
|
|
if (r < 0) { return r: oserror; };
|
|
fillfilestat(out, &k);
|
|
};
|
|
|
|
// fstat — like [[stat]] but addresses the file by fd. Uses
|
|
// newfstatat(fd, "", AT_EMPTY_PATH); the kernel resolves the fd
|
|
// directly. Mirrors Hare's sys::fstat (ref/hare/sys/+linux/stat.ha:54).
|
|
export fn fstat(out: *filestat, fd: i32) (void | oserror) = {
|
|
let k: kstat;
|
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
|
fd: i64, (&emptypath[0]): i64, (&k): i64,
|
|
AT_EMPTY_PATH: i64);
|
|
if (r < 0) { return r: oserror; };
|
|
fillfilestat(out, &k);
|
|
};
|
|
|
|
// exists — true if `path` resolves to anything (regular file,
|
|
// directory, symlink, ...). Stat-shaped (Hare's `fs::exists`,
|
|
// ref/hare/fs/fs.ha:196) — no separate syscall. Symlinks are
|
|
// followed; a dangling symlink is `false`. ENAMETOOLONG is
|
|
// swallowed as `false` — Hare's os::exists doc says "true if a
|
|
// node exists at the given path, or false if not."
|
|
//
|
|
// Race warning: prefer "open and handle the error" over "exists
|
|
// then open" in real code (Hare's docstring carries the same
|
|
// note). The race is unavoidable in this shape.
|
|
//
|
|
// Goes through SYS_newfstatat directly rather than match'ing on
|
|
// [[stat]]'s `(void | oserror)` return. Functionally identical;
|
|
// the direct shape sidesteps a cstage/wwstage cgen disagreement
|
|
// on the slot size of `(void | oserror)` (cstage 16B, wwstage 24B
|
|
// — same class as STATUS #22, surfaced first time a match on this
|
|
// shape combined with an 80B local-struct local frame). Use the
|
|
// match shape once #22 lands.
|
|
export fn exists(path: str) bool = {
|
|
let cp: *u8 = kpath(path);
|
|
if (cp == nil: *u8) { return false; };
|
|
let k: kstat;
|
|
let r: i64 = syscall4(nr.NEWFSTATAT,
|
|
AT_FDCWD: i64, cp: i64, (&k): i64, 0i64);
|
|
return r >= 0i64;
|
|
};
|
|
|
|
// rt — runtime primitives exposed to ww programs.
|
|
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
|
|
|
package rt;
|
|
|
|
// malloc — mmap-backed page allocator. Untyped: `malloc(n)` returns a
|
|
// `*void`; callers cast to the target type. Diverges from Hare: Hare
|
|
// exposes `alloc` / `free` as typed language builtins that the
|
|
// compiler lowers to rt::malloc/rt::free; ww has no such builtins,
|
|
// so the rt-symbol surface is exposed directly. Stdlib callers that
|
|
// need a typed allocation pattern wrap this with a cast plus a stored
|
|
// capacity (see [[strings.dup]], [[memio.dynamic]]).
|
|
//
|
|
// OOM: rt_malloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no
|
|
// error path. The raw Linux mmap syscall returns a negative errno cast
|
|
// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the
|
|
// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that
|
|
// rt_malloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches
|
|
// it; any deref of such a return faults. Today the stdlib does not
|
|
// check; OOM faults on first dereference. A typed fallible variant is
|
|
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
|
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
|
|
|
// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
|
|
//
|
|
// Linker symbol table. Singly-linked list, usually a few hundred
|
|
// entries; hashing isn't worth it yet.
|
|
|
|
package w6l;
|
|
|
|
type lsym = struct {
|
|
name: str,
|
|
val: u64, // offset within combined .text (or .data when
|
|
// indata=1) once linked
|
|
defined: i32, // 1 if some lobj defines this symbol
|
|
indata: i32, // 1 if defined in .data (writable globals)
|
|
owner: *lobj,
|
|
idxinowner: i32,
|
|
// Dynamic-linking fields. Set by resolve when an undefined sym
|
|
// is provided by some loaded lso. pltidx and dynsymidx default
|
|
// to -1 (set explicitly by resolve; alloc-zeroing gives 0, not -1).
|
|
isdyn: i32,
|
|
dynlib: *lso,
|
|
dynversion: str, // matched export's version; len 0 if none
|
|
pltidx: i32,
|
|
dynsymidx: i32,
|
|
snext: *lsym,
|
|
};
|
|
|
|
type lrel = struct {
|
|
off: u64, // offset within the relocation's section
|
|
section: i32, // 0 = .text, 1 = .data
|
|
kind: i32, // R_X86_64_*
|
|
sym: *lsym,
|
|
addend: i64,
|
|
rnext: *lrel,
|
|
};
|
|
|
|
type lobj = struct {
|
|
path: str,
|
|
buf: *u8, // object bytes
|
|
len: u64,
|
|
textoff: u64, // offset of .text in combined output
|
|
textsize: u64,
|
|
dataoff: u64, // offset of .data in combined output
|
|
datasize: u64, // bytes contributed to combined .data (0 if none)
|
|
onext: *lobj,
|
|
};
|
|
|
|
// lexport — one entry per GLOBAL/WEAK symbol exported by a loaded .so.
|
|
// Stored as a chain in the order the .so's dynsym presents them, so
|
|
// soprovides_v's first-match semantics agree with the C version.
|
|
type lexport = struct {
|
|
name: str,
|
|
version: str, // len 0 for unversioned globals
|
|
enext: *lexport,
|
|
};
|
|
|
|
type lso = struct {
|
|
path: str, // full filesystem path used to load
|
|
soname: str, // DT_SONAME, or basename if missing
|
|
exports: *lexport, // dynsym-order chain of exported names
|
|
sonext: *lso,
|
|
};
|
|
|
|
type lnk = struct {
|
|
objs: *lobj,
|
|
sos: *lso,
|
|
syms: *lsym,
|
|
rels: *lrel,
|
|
text: *u8, // combined .text
|
|
textcap: u64,
|
|
textlen: u64,
|
|
// Combined .data (writable). Empty unless any input .o has a
|
|
// .data PROGBITS section.
|
|
data: *u8,
|
|
datacap: u64,
|
|
datalen: u64,
|
|
errs: i32,
|
|
dynn: i32, // number of syms routed through PLT
|
|
};
|
|
|
|
fn streq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
export fn intern(l: *lnk, name: str) *lsym = {
|
|
let s: *lsym = l.syms;
|
|
for (s != nil) {
|
|
if (streq(s.name, name)) { return s; };
|
|
s = s.snext;
|
|
};
|
|
let n: *lsym = alloc(lsym { name = name, snext = l.syms })!;
|
|
l.syms = n;
|
|
return n;
|
|
};
|
|
|
|
export fn lookup(l: *lnk, name: str) *lsym = {
|
|
let s: *lsym = l.syms;
|
|
for (s != nil) {
|
|
if (streq(s.name, name)) { return s; };
|
|
s = s.snext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
|
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
|
// Hare's split between types::limits and math::.
|
|
|
|
package types;
|
|
|
|
def I8_MAX: i8 = 127;
|
|
def I16_MAX: i16 = 32767;
|
|
def I32_MAX: i32 = 2147483647;
|
|
def I64_MAX: i64 = 9223372036854775807;
|
|
|
|
def I8_MIN: i8 = -128;
|
|
def I16_MIN: i16 = -32768;
|
|
def I32_MIN: i32 = -2147483648;
|
|
def I64_MIN: i64 = -9223372036854775808;
|
|
|
|
def U8_MAX: u8 = 255;
|
|
def U16_MAX: u16 = 65535;
|
|
def U32_MAX: u32 = 4294967295;
|
|
def U64_MAX: u64 = 18446744073709551615;
|
|
|
|
def U8_MIN: u8 = 0;
|
|
def U16_MIN: u16 = 0;
|
|
def U32_MIN: u32 = 0;
|
|
def U64_MIN: u64 = 0;
|
|
|
|
// int/uint are machine-word (Go-style, type.c:58); limits derived from
|
|
// size(int) per #114 + user ruling; cf Go math.MaxInt; diverges from
|
|
// Hare's per-arch literal (arch+x86_64.ha) because ww's int is 64-bit.
|
|
def INT_MAX: int = (1 << (size(int)*8 - 1)) - 1;
|
|
def INT_MIN: int = -1 << (size(int)*8 - 1);
|
|
def UINT_MIN: uint = 0;
|
|
def UINT_MAX: uint = ~(0: uint);
|
|
|
|
// size is 8B on amd64; no cast needed (size ∈ unsigned class per #113).
|
|
def SIZE_MIN: size = U64_MIN;
|
|
def SIZE_MAX: size = U64_MAX;
|
|
|
|
// uintptr not in the unsigned class, so the cast is required (Hare's form).
|
|
def UINTPTR_MIN: uintptr = U64_MIN: uintptr;
|
|
def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
|
|
|
def RUNE_MIN: rune = '\0';
|
|
|
|
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
|
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
|
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
|
//
|
|
// Documented divergences from Hare:
|
|
// - index_slice / rindex_slice use naive O(n·m); Hare specialises
|
|
// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin)
|
|
// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha).
|
|
// Correctness equivalent.
|
|
// - peek_token dispatches index/rindex by branching on `reverse`
|
|
// rather than a function-pointer `ifunc` (ref/hare/bytes/tokenize.ha:97).
|
|
// ww has no fn pointers in scope yet — same pattern as lib/strings
|
|
// `move`. Outwardly identical.
|
|
// - tokenize / rtokenize zero the `delim` field on the constructed
|
|
// tokenizer when `in` is empty, rather than mutating the variadic
|
|
// param before the struct write (ref/hare/bytes/tokenize.ha:26-28).
|
|
// Semantically identical; the variadic param is borrowed and
|
|
// captured-by-value into the struct, so mutating either side
|
|
// yields the same observable state.
|
|
|
|
package bytes;
|
|
|
|
import os;
|
|
import types;
|
|
|
|
// done — iteration sentinel returned by next_token / peek_token at
|
|
// end-of-input. ref/hare/bytes/tokenize.ha uses the built-in `done`
|
|
// token; ww spells it per-package the same way lib/encoding/utf8 does
|
|
// (utf8.ww:36). Plain `void` (not `!void`): continuation signal.
|
|
export type done = void;
|
|
|
|
// tokenizer — cursor over an input slice. Layout mirrors
|
|
// ref/hare/bytes/tokenize.ha:6-10. `p` is the cached peek-position;
|
|
// I64_MAX (forward) / I64_MIN (reverse) are the unprimed sentinels.
|
|
// p < 0 also identifies a reverse-direction iterator.
|
|
export type tokenizer = struct {
|
|
in: []u8,
|
|
delim: []u8,
|
|
p: i64,
|
|
};
|
|
|
|
// equal — true iff `a` and `b` have the same length and contents.
|
|
// ref/hare/bytes/equal.ha:9.
|
|
export fn equal(a: []u8, b: []u8) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// index — first offset of `needle` in `s`. u8 needle scans for the
|
|
// byte; []u8 needle scans for the substring. void if absent.
|
|
// ref/hare/bytes/index.ha:6.
|
|
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
|
match (needle) {
|
|
case let c: u8 => {
|
|
let i: i32 = 0;
|
|
for (i < s.len) {
|
|
if (s[i] == c) { return i; };
|
|
i += 1;
|
|
};
|
|
return;
|
|
};
|
|
case let sub: []u8 => {
|
|
if (sub.len == 0) { return 0; };
|
|
if (sub.len > s.len) { return; };
|
|
let last: i32 = s.len - sub.len;
|
|
let i: i32 = 0;
|
|
for (i <= last) {
|
|
let j: i32 = 0;
|
|
let ok: bool = true;
|
|
for (j < sub.len) {
|
|
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
|
else { j += 1; };
|
|
};
|
|
if (ok) { return i; };
|
|
i += 1;
|
|
};
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
// rindex — last offset of `needle` in `s`. Empty []u8 needle returns
|
|
// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0).
|
|
// ref/hare/bytes/index.ha:86.
|
|
export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
|
match (needle) {
|
|
case let c: u8 => {
|
|
let i: i32 = s.len - 1;
|
|
for (i >= 0) {
|
|
if (s[i] == c) { return i; };
|
|
i -= 1;
|
|
};
|
|
return;
|
|
};
|
|
case let sub: []u8 => {
|
|
if (sub.len == 0) { return s.len; };
|
|
if (sub.len > s.len) { return; };
|
|
let i: i32 = s.len - sub.len;
|
|
for (i >= 0) {
|
|
let j: i32 = 0;
|
|
let ok: bool = true;
|
|
for (j < sub.len) {
|
|
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
|
else { j += 1; };
|
|
};
|
|
if (ok) { return i; };
|
|
i -= 1;
|
|
};
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
// contains — true iff any of `needles` (byte or sub-slice) appears in `s`.
|
|
// ref/hare/bytes/contains.ha:6.
|
|
export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
|
|
let i: i32 = 0;
|
|
for (i < needles.len) {
|
|
match (needles[i]) {
|
|
case let b: u8 => {
|
|
match (index(s, b)) {
|
|
case let bo: i32 => return true;
|
|
case void => void;
|
|
};
|
|
};
|
|
case let n: []u8 => {
|
|
match (index(s, n)) {
|
|
case let bo: i32 => return true;
|
|
case void => void;
|
|
};
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
|
|
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
|
|
export fn ltrim(in: []u8, trim: u8...) []u8 = {
|
|
os.assert(trim.len > 0, "bytes.ltrim called with empty trim set");
|
|
let i: i32 = 0;
|
|
for (i < in.len && contains(trim, in[i])) { i += 1; };
|
|
let r: []u8;
|
|
r.ptr = in.ptr + (i: u64);
|
|
r.len = in.len - i;
|
|
r.cap = r.len;
|
|
return r;
|
|
};
|
|
|
|
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
|
|
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
|
|
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
|
|
// the equivalent termination is spelled `i >= 0` explicitly.
|
|
export fn rtrim(in: []u8, trim: u8...) []u8 = {
|
|
os.assert(trim.len > 0, "bytes.rtrim called with empty trim set");
|
|
let i: i32 = in.len - 1;
|
|
for (i >= 0 && contains(trim, in[i])) { i -= 1; };
|
|
let r: []u8;
|
|
r.ptr = in.ptr;
|
|
r.len = i + 1;
|
|
r.cap = r.len;
|
|
return r;
|
|
};
|
|
|
|
// trim — borrowed view of `in` with both ends in `trim` stripped.
|
|
// ref/hare/bytes/trim.ha:27.
|
|
export fn trim(in: []u8, trim: u8...) []u8 = {
|
|
return ltrim(rtrim(in, trim...), trim...);
|
|
};
|
|
|
|
// hasprefix — true iff `s` starts with `pre`.
|
|
// ref/hare/bytes/contains.ha:21.
|
|
export fn hasprefix(s: []u8, pre: []u8) bool = {
|
|
if (pre.len > s.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < pre.len) {
|
|
if (s[i] != pre[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// hassuffix — true iff `s` ends with `suf`.
|
|
// ref/hare/bytes/contains.ha:35.
|
|
export fn hassuffix(s: []u8, suf: []u8) bool = {
|
|
if (suf.len > s.len) { return false; };
|
|
let off: i32 = s.len - suf.len;
|
|
let i: i32 = 0;
|
|
for (i < suf.len) {
|
|
if (s[off + i] != suf[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// reverse — in-place reverse of `s`. ref/hare/bytes/reverse.ha:5.
|
|
export fn reverse(s: []u8) void = {
|
|
let i: i32 = 0;
|
|
let j: i32 = s.len - 1;
|
|
for (i < j) {
|
|
let t: u8 = s[i];
|
|
s[i] = s[j];
|
|
s[j] = t;
|
|
i += 1;
|
|
j -= 1;
|
|
};
|
|
};
|
|
|
|
// zero — set every byte of `s` to 0. ref/hare/bytes/zero.ha:5.
|
|
export fn zero(s: []u8) void = {
|
|
let i: i32 = 0;
|
|
for (i < s.len) {
|
|
s[i] = 0u8;
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
// tokenize — iterator yielding tokens from `in` separated by any byte
|
|
// in `delim`. Leading / trailing / adjacent delims yield empty tokens.
|
|
// `delim` is borrowed; caller keeps it valid for the tokenizer's
|
|
// lifetime. ref/hare/bytes/tokenize.ha:22.
|
|
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
|
os.assert(delim.len > 0, "bytes.tokenize called with empty slice");
|
|
os.assert((in.len: i64) < types.I64_MAX,
|
|
"bytes.tokenize: input length exceeds I64_MAX");
|
|
let t: tokenizer;
|
|
t.in = in;
|
|
t.delim = delim;
|
|
if (in.len == 0) {
|
|
t.delim.len = 0;
|
|
t.delim.cap = 0;
|
|
};
|
|
t.p = types.I64_MAX;
|
|
return t;
|
|
};
|
|
|
|
// rtokenize — reverse-direction tokenize. First next_token yields the
|
|
// last token, last next_token yields the first. ref/hare/bytes/tokenize.ha:40.
|
|
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
|
|
os.assert(delim.len > 0, "bytes.rtokenize called with empty slice");
|
|
os.assert((in.len: i64) < types.I64_MAX,
|
|
"bytes.rtokenize: input length exceeds I64_MAX");
|
|
let t: tokenizer;
|
|
t.in = in;
|
|
t.delim = delim;
|
|
if (in.len == 0) {
|
|
t.delim.len = 0;
|
|
t.delim.cap = 0;
|
|
};
|
|
t.p = types.I64_MIN;
|
|
return t;
|
|
};
|
|
|
|
// peek_token — next token without advancing the cursor. Returns done
|
|
// once `s.delim` has been zeroed by a prior past-end next_token.
|
|
// ref/hare/bytes/tokenize.ha:91.
|
|
export fn peek_token(s: *tokenizer) ([]u8 | done) = {
|
|
if (s.delim.len == 0) {
|
|
let d: done; return d;
|
|
};
|
|
|
|
let reverse: bool = s.p < 0i64;
|
|
let known: bool = false;
|
|
if (reverse) {
|
|
if (s.p != types.I64_MIN) { known = true; };
|
|
} else {
|
|
if (s.p != types.I64_MAX) { known = true; };
|
|
};
|
|
if (!known) {
|
|
let i: i64 = types.I64_MAX;
|
|
if (reverse) { i = types.I64_MIN; };
|
|
let dlen: i64 = 0i64;
|
|
let slen: i64 = s.in.len: i64;
|
|
|
|
let k: i32 = 0;
|
|
for (k < s.delim.len) {
|
|
let d: u8 = s.delim[k];
|
|
let ix_found: bool = false;
|
|
let ix_val: i32 = 0;
|
|
if (reverse) {
|
|
match (rindex(s.in, d)) {
|
|
case let v: i32 => { ix_found = true; ix_val = v; };
|
|
case void => void;
|
|
};
|
|
} else {
|
|
match (index(s.in, d)) {
|
|
case let v: i32 => { ix_found = true; ix_val = v; };
|
|
case void => void;
|
|
};
|
|
};
|
|
if (ix_found) {
|
|
if (!reverse) {
|
|
if ((ix_val: i64) < i) { i = ix_val: i64; dlen = 1i64; };
|
|
} else {
|
|
if ((ix_val: i64) > i) { i = ix_val: i64; dlen = 1i64; };
|
|
};
|
|
} else {
|
|
if (!reverse) {
|
|
if (slen < i) { i = slen; };
|
|
} else {
|
|
if (0i64 > i) { i = 0i64; };
|
|
};
|
|
};
|
|
k += 1;
|
|
};
|
|
|
|
if (reverse) {
|
|
if (i == slen) {
|
|
s.p = -(slen + 1i64);
|
|
} else {
|
|
s.p = i + dlen - slen - 1i64;
|
|
};
|
|
} else {
|
|
s.p = i;
|
|
};
|
|
};
|
|
|
|
let r: []u8;
|
|
if (reverse) {
|
|
let start: i32 = (s.in.len: i64 + s.p + 1i64): i32;
|
|
r.ptr = s.in.ptr + (start: u64);
|
|
r.len = s.in.len - start;
|
|
r.cap = r.len;
|
|
} else {
|
|
let end: i32 = s.p: i32;
|
|
r.ptr = s.in.ptr;
|
|
r.len = end;
|
|
r.cap = end;
|
|
};
|
|
return r;
|
|
};
|
|
|
|
// next_token — current token, then advance past it and the delim.
|
|
// Once the input is exhausted, returns done and zeros `s.delim` so
|
|
// subsequent peeks short-circuit. ref/hare/bytes/tokenize.ha:59.
|
|
export fn next_token(s: *tokenizer) ([]u8 | done) = {
|
|
let b: []u8;
|
|
match (peek_token(s)) {
|
|
case let v: []u8 => { b = v; };
|
|
case done => { let d: done; return d; };
|
|
};
|
|
|
|
let slen: i64 = s.in.len: i64;
|
|
let reverse: bool = s.p < 0i64;
|
|
if (reverse) {
|
|
if (slen + s.p + 1i64 == 0i64) {
|
|
s.delim.len = 0;
|
|
s.delim.cap = 0;
|
|
s.in.len = 0;
|
|
s.in.cap = 0;
|
|
} else {
|
|
let end: i32 = (slen + s.p + 1i64 - 1i64): i32;
|
|
s.in.len = end;
|
|
s.in.cap = end;
|
|
};
|
|
s.p = types.I64_MIN;
|
|
} else {
|
|
if (s.p == slen) {
|
|
s.delim.len = 0;
|
|
s.delim.cap = 0;
|
|
s.in.len = 0;
|
|
s.in.cap = 0;
|
|
} else {
|
|
let adv: u64 = (s.p: u64) + 1u64;
|
|
let adv_i32: i32 = (s.p: i32) + 1;
|
|
s.in.ptr = s.in.ptr + adv;
|
|
s.in.len = s.in.len - adv_i32;
|
|
s.in.cap = s.in.cap - adv_i32;
|
|
};
|
|
s.p = types.I64_MAX;
|
|
};
|
|
return b;
|
|
};
|
|
|
|
// remaining_tokens — the unconsumed portion of `s.in`. Read-only view.
|
|
// ref/hare/bytes/tokenize.ha:145.
|
|
export fn remaining_tokens(s: *tokenizer) []u8 = {
|
|
return s.in;
|
|
};
|
|
|
|
// rt_ensure is the runtime slice-growth helper invoked by the
|
|
// `append(s, v)` builtin. We bind it directly because the builtin's
|
|
// expansion stores only 8 bytes of the new element (cgen emits a
|
|
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
|
|
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
|
|
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
|
|
// append builtin learns to store the full element width.
|
|
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
|
|
|
|
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
|
|
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
|
|
// `append` builtin's first-8B-only-store gap for a slice-element.
|
|
fn appendslice(slice: *[][]u8, item: []u8) void = {
|
|
let newlen: i32 = slice.len + 1;
|
|
slice.len = newlen;
|
|
rtensure(slice: *void, 24u64);
|
|
let dst: *[]u8 = &slice.ptr[newlen - 1];
|
|
dst.ptr = item.ptr;
|
|
dst.len = item.len;
|
|
dst.cap = item.cap;
|
|
};
|
|
|
|
// splitn — split `in` on any byte in `delim`, returning up to `n`
|
|
// tokens via forward iteration. The trailing slot (when more than
|
|
// `n - 1` tokens exist) holds the unconsumed remainder.
|
|
//
|
|
// The caller frees the returned slice via
|
|
// `os.free(r.ptr: *void, (r.cap: u64) * 24u64)`. Element bytes are
|
|
// borrowed from `in`.
|
|
//
|
|
// Hare's `([][]u8 | nomem)` collapses to `[][]u8` here: ww os.alloc
|
|
// has no recoverable failure path. Same precedent as
|
|
// shlex.split / getopt.tryparse.
|
|
//
|
|
// ref/hare/bytes/tokenize.ha:156.
|
|
export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
|
|
os.assert(delim.len > 0,
|
|
"bytes.splitn must not be called with an empty delimiter");
|
|
let toks: [][]u8;
|
|
toks.ptr = nil: *[]u8;
|
|
toks.len = 0;
|
|
toks.cap = 0;
|
|
let tok: tokenizer = tokenize(in, delim...);
|
|
let i: i32 = 0;
|
|
for (i < n - 1) {
|
|
match (next_token(&tok)) {
|
|
case let s: []u8 => { appendslice(&toks, s); };
|
|
case done => { return toks; };
|
|
};
|
|
i += 1;
|
|
};
|
|
match (peek_token(&tok)) {
|
|
case done => void;
|
|
case let pk: []u8 => {
|
|
let r: []u8 = remaining_tokens(&tok);
|
|
appendslice(&toks, r);
|
|
};
|
|
};
|
|
return toks;
|
|
};
|
|
|
|
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
|
|
// collected from the end of `in`. The trailing slot holds the
|
|
// unconsumed prefix (everything before the n-th-from-last delim hit).
|
|
//
|
|
// When the input has fewer than n tokens, the `done` short-circuit
|
|
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
|
|
// at ref/hare/bytes/tokenize.ha:196-199 where the in-place reverse
|
|
// step is gated behind the n-1 loop running to completion. Only the
|
|
// "loop ran to completion AND peek saw a remainder" path applies the
|
|
// reverse; both early-exit paths skip it.
|
|
//
|
|
// ref/hare/bytes/tokenize.ha:186.
|
|
export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
|
|
os.assert(delim.len > 0,
|
|
"bytes.rsplitn called with empty delimiter");
|
|
let toks: [][]u8;
|
|
toks.ptr = nil: *[]u8;
|
|
toks.len = 0;
|
|
toks.cap = 0;
|
|
let tok: tokenizer = rtokenize(in, delim...);
|
|
let i: i32 = 0;
|
|
for (i < n - 1) {
|
|
match (next_token(&tok)) {
|
|
case let s: []u8 => { appendslice(&toks, s); };
|
|
case done => { return toks; };
|
|
};
|
|
i += 1;
|
|
};
|
|
match (peek_token(&tok)) {
|
|
case done => void;
|
|
case let pk: []u8 => {
|
|
let r: []u8 = remaining_tokens(&tok);
|
|
appendslice(&toks, r);
|
|
};
|
|
};
|
|
|
|
// In-place reverse so callers see argv-order, matching Hare
|
|
// (ref/hare/bytes/tokenize.ha:207). Element copy is field-wise
|
|
// through `*[]u8` because `toks[i] = toks[j]` (full 24B slice
|
|
// store) lands in the multi-word-store gap noted at
|
|
// cmd/w6c/cgen.c:6515-6523.
|
|
let a: i32 = 0;
|
|
let b: i32 = toks.len - 1;
|
|
for (a < b) {
|
|
let pa: *[]u8 = &toks.ptr[a];
|
|
let pb: *[]u8 = &toks.ptr[b];
|
|
let tp: *u8 = pa.ptr;
|
|
let tl: i32 = pa.len;
|
|
let tc: i32 = pa.cap;
|
|
pa.ptr = pb.ptr;
|
|
pa.len = pb.len;
|
|
pa.cap = pb.cap;
|
|
pb.ptr = tp;
|
|
pb.len = tl;
|
|
pb.cap = tc;
|
|
a += 1;
|
|
b -= 1;
|
|
};
|
|
return toks;
|
|
};
|
|
|
|
// split — full split of `in` on `delim` (no token cap). Mirrors
|
|
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
|
// because the index type is i32 (lib/CLAUDE.md).
|
|
//
|
|
// ref/hare/bytes/tokenize.ha:225.
|
|
export fn split(in: []u8, delim: []u8) [][]u8 = {
|
|
return splitn(in, delim, types.I32_MAX);
|
|
};
|
|
|
|
// cut — split `in` along the first instance of `delim`, returning the
|
|
// portion before and the portion after the delimiter as a borrowed
|
|
// tuple. When `delim` is absent, the whole input is the first half and
|
|
// the second is empty. ref/hare/bytes/tokenize.ha:392.
|
|
//
|
|
// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91);
|
|
// the tagged union is an unordered set, so this is the same type as
|
|
// Hare's ([]u8 | u8), not a divergence.
|
|
export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
|
let ln: i32 = match (delim) {
|
|
case let c: u8 => yield 1i32;
|
|
case let sub: []u8 => {
|
|
os.assert(sub.len > 0,
|
|
"bytes.cut called with empty delimiter");
|
|
yield sub.len;
|
|
};
|
|
};
|
|
match (index(in, delim)) {
|
|
case let i: i32 => {
|
|
let lo: i32 = i + ln;
|
|
return (in[0:i], in[lo:in.len]);
|
|
};
|
|
case void => {
|
|
let empty: []u8;
|
|
empty.ptr = nil; empty.len = 0; empty.cap = 0;
|
|
return (in, empty);
|
|
};
|
|
};
|
|
};
|
|
|
|
// rcut — like [[cut]] but splits along the last instance of `delim`.
|
|
// ref/hare/bytes/tokenize.ha:413.
|
|
export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
|
let ln: i32 = match (delim) {
|
|
case let c: u8 => yield 1i32;
|
|
case let sub: []u8 => {
|
|
os.assert(sub.len > 0,
|
|
"bytes.rcut called with empty delimiter");
|
|
yield sub.len;
|
|
};
|
|
};
|
|
match (rindex(in, delim)) {
|
|
case let i: i32 => {
|
|
let lo: i32 = i + ln;
|
|
return (in[0:i], in[lo:in.len]);
|
|
};
|
|
case void => {
|
|
let empty: []u8;
|
|
empty.ptr = nil; empty.len = 0; empty.cap = 0;
|
|
return (in, empty);
|
|
};
|
|
};
|
|
};
|
|
|
|
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
|
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
|
//
|
|
// The decoder is Hoehrmann's branchless DFA, originally published
|
|
// at <https://bjoern.hoehrmann.de/utf-8/decoder/dfa/>. Hare's
|
|
// ref/hare/encoding/utf8/decodetable.ha:4 restructures Hoehrmann's
|
|
// flat table to 2D `[8][256]i8`; we flatten back to 1D `[2048]i8`
|
|
// because ww cgen does not yet ship 2D arrays (task #20).
|
|
//
|
|
// Surface deviation from ref/hare/encoding/utf8:
|
|
//
|
|
// - `encoderune` takes a caller-supplied `out: []u8` and returns
|
|
// the byte count. Hare returns a slice into a `static let buf`;
|
|
// the caller-buffer form skips the static-buffer/slice-return pair.
|
|
//
|
|
// Deferred (no in-tree caller, follow-up tasks): `appendrune`,
|
|
// `strencode`, `strdecode`. Hare's string-iteration surface
|
|
// (`strings::iterator`/`strings::next` — ref/hare/strings/iter.ha)
|
|
// lives under lib/strings, not here.
|
|
|
|
// ref/hare/encoding/utf8/types.ha:6 — incomplete trailing sequence.
|
|
// Plain `void` (not `!void`): a truncated tail is a control-flow
|
|
// signal, not an error caller can ignore.
|
|
package utf8;
|
|
|
|
export type more = void;
|
|
|
|
// ref/hare/encoding/utf8/types.ha:9 — invalid UTF-8 sequence.
|
|
export type invalid = !void;
|
|
|
|
// ref/hare/encoding/utf8/types.ha:12 — fixed message; `invalid` carries
|
|
// no payload, so the rendering is constant.
|
|
export fn strerror(err: invalid) str = {
|
|
return "Invalid UTF-8";
|
|
};
|
|
|
|
// `done` is not a built-in singleton in ww (Hare ships it as part of
|
|
// the type system). Plain `void` (not `!void`): end-of-input is a
|
|
// continuation signal, not an error. lib/io spells its EOF the same
|
|
// way (lib/io/io.ww:8-11).
|
|
export type done = void;
|
|
|
|
// ref/hare/encoding/utf8/decodetable.ha:4 — Hoehrmann's UTF-8 DFA,
|
|
// flat 1D `[2048]i8`. Layout: dfa[state*256 + byte] gives the next
|
|
// state (>0), the accept transition (0 — emit rune), or invalid (-1).
|
|
// Values match ref/hare/encoding/utf8/decodetable.ha verbatim.
|
|
let dfa: [2048]i8 = [
|
|
// state 0 — initial byte: ASCII accepts (0), continuation/illegal
|
|
// byte rejects (-1), legal multibyte start emits a state.
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
3i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 4i8, 2i8, 2i8,
|
|
5i8, 6i8, 6i8, 6i8, 7i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 1 — expecting one continuation byte (0x80..0xBF).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 2 — expecting one continuation byte (full 0x80..0xBF range).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 3 — first byte was 0xE0; continuation byte must be 0xA0..0xBF
|
|
// (rejects overlong 3-byte encodings).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 4 — first byte was 0xED; continuation byte must be 0x80..0x9F
|
|
// (rejects UTF-16 surrogate codepoints U+D800..U+DFFF).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 5 — first byte was 0xF0; continuation byte must be 0x90..0xBF
|
|
// (rejects overlong 4-byte encodings).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 6 — middle continuation byte of a 4-byte sequence (0x80..0xBF).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
|
|
// state 7 — first byte was 0xF4; continuation byte must be 0x80..0x8F
|
|
// (rejects codepoints above U+10FFFF).
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
-1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8,
|
|
];
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:17 — payload-bit masks. Hare's
|
|
// [2][8]u8 flattened to 1D [16]u8; row 0 (offsets 0..7) is the
|
|
// continuation-byte mask (always 0x3F), row 1 (offsets 8..15) is the
|
|
// initial-byte payload mask indexed by the transition class.
|
|
let masks: [16]u8 = [
|
|
0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8,
|
|
0x7fu8, 0x1fu8, 0x0fu8, 0x0fu8, 0x0fu8, 0x07u8, 0x07u8, 0x07u8,
|
|
];
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:6 — incremental decoder state.
|
|
export type decoder = struct {
|
|
offs: i32,
|
|
src: []u8,
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:12.
|
|
export fn decode(src: []u8) decoder = {
|
|
let d: decoder;
|
|
d.src = src;
|
|
d.offs = 0;
|
|
return d;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:27. Returns the next rune from a
|
|
// decoder, `done` at end-of-input, `more` on truncated trailing
|
|
// sequence, `invalid` on malformed input (overlong, surrogate,
|
|
// out-of-range, bad continuation).
|
|
//
|
|
// Algorithm is verbatim Hoehrmann (see file header). One structural
|
|
// rewrite: Hare encodes the "initial vs continuation byte" decision
|
|
// as the branchless `(state - 1): uint >> 31`, which assumes a 32-bit
|
|
// uint. ww's uint is 64-bit (cmd/wcc/type.c:58), so the shift answer
|
|
// would be 0x1_ffff_ffff rather than 1. We spell the same predicate
|
|
// with an explicit conditional.
|
|
export fn next(d: *decoder) (rune | done | more | invalid) = {
|
|
if (d.offs == d.src.len) {
|
|
let dn: done; return dn;
|
|
};
|
|
let nx: i32 = 0;
|
|
let state: i32 = 0;
|
|
let r: u32 = 0u32;
|
|
for (d.offs < d.src.len) {
|
|
let b: u8 = d.src[d.offs];
|
|
let bi: i32 = b: i32;
|
|
let row: i32 = state * 256 + bi;
|
|
let cell: i8 = dfa[row];
|
|
nx = cell: i32;
|
|
let mi: i32 = 0;
|
|
if (state == 0) { mi = 1; };
|
|
let m: u8 = masks[mi * 8 + (nx & 7)];
|
|
r = (r << 6u32) | ((b & m): u32);
|
|
if (nx <= 0) {
|
|
d.offs += 1;
|
|
if (nx == 0) { return r: rune; };
|
|
let e: invalid; return e;
|
|
};
|
|
state = nx;
|
|
d.offs += 1;
|
|
};
|
|
let mr: more; return mr;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:207. Strict whole-input check.
|
|
// The hot path: tight DFA loop, no rune assembly. Bails the moment
|
|
// the table returns -1 so malformed inputs don't pay for the rest
|
|
// of the buffer.
|
|
export fn validate(src: []u8) (void | invalid) = {
|
|
let state: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < src.len) {
|
|
if (state < 0) { break; };
|
|
let bi: i32 = src[i]: i32;
|
|
let cell: i8 = dfa[state * 256 + bi];
|
|
state = cell: i32;
|
|
i += 1;
|
|
};
|
|
if (state == 0) { return; };
|
|
let e: invalid; return e;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/rune.ha:5. Encoded byte length of `r` as
|
|
// UTF-8. Callers in ww use this to size the buffer they hand to
|
|
// [[encoderune]]; values >0x10FFFF or negative are not legal Unicode
|
|
// codepoints and Hare aborts on them in `encoderune` itself, so we
|
|
// keep `runesz` infallible (matches Hare).
|
|
export fn runesz(r: rune) i32 = {
|
|
let ch: u32 = r: u32;
|
|
if (ch < 128u32) { return 1; };
|
|
if (ch < 2048u32) { return 2; };
|
|
if (ch < 65536u32) { return 3; };
|
|
return 4;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/rune.ha:15. Expected byte length of the
|
|
// codepoint that starts with `c`, or `invalid` if `c` cannot start
|
|
// a legal UTF-8 sequence. Constants written in decimal because ww
|
|
// doesn't accept Hare's `0b1000_0000` binary syntax: 0x80=128,
|
|
// 0xC2=194, 0xE0=224, 0xF0=240, 0xF8=248.
|
|
export fn utf8sz(c: u8) (i32 | invalid) = {
|
|
if (c < 128u8) { return 1; };
|
|
if (c < 194u8) { let e: invalid; return e; };
|
|
if (c >= 248u8) { let e: invalid; return e; };
|
|
if (c < 224u8) { return 2; };
|
|
if (c < 240u8) { return 3; };
|
|
return 4;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/encode.ha:7. Encode `r` into `out` (caller-
|
|
// supplied; must hold at least [[runesz]](r) bytes) and return the
|
|
// byte count. ABORT if `r` is a UTF-16 surrogate or above U+10FFFF —
|
|
// same precondition Hare asserts at ref/hare/encoding/utf8/encode.ha:9.
|
|
//
|
|
// Surface deviation: Hare returns `[]u8` (slice into a static buf).
|
|
// ww uses the caller-buffer form; caller can reuse a [4]u8 stack
|
|
// scratch across encodes.
|
|
export fn encoderune(out: []u8, r: rune) i32 = {
|
|
let ch: u32 = r: u32;
|
|
if (ch >= 0xD800u32) {
|
|
if (ch <= 0xDFFFu32) {
|
|
abort("utf8.encoderune: surrogate codepoint");
|
|
};
|
|
};
|
|
if (ch > 0x10FFFFu32) {
|
|
abort("utf8.encoderune: codepoint > U+10FFFF");
|
|
};
|
|
|
|
let n: i32 = 0;
|
|
let first: u8 = 0u8;
|
|
if (ch < 0x80u32) {
|
|
first = 0u8; n = 1;
|
|
} else if (ch < 0x800u32) {
|
|
first = 0xC0u8; n = 2;
|
|
} else if (ch < 0x10000u32) {
|
|
first = 0xE0u8; n = 3;
|
|
} else {
|
|
first = 0xF0u8; n = 4;
|
|
};
|
|
|
|
let v: u32 = ch;
|
|
let i: i32 = n - 1;
|
|
for (i > 0) {
|
|
out[i] = ((v: u8) & 0x3Fu8) | 0x80u8;
|
|
v = v >> 6u32;
|
|
i -= 1;
|
|
};
|
|
out[0] = (v: u8) | first;
|
|
return n;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:52. Walks back from `d.offs` to a
|
|
// byte that could start a codepoint (state-0 dfa cell != -1), re-decodes
|
|
// forward from there, and confirms the forward decode lands back at the
|
|
// original offset. Returns `done` at start-of-input; `invalid` if no
|
|
// initial byte appears within 4 steps (no legal UTF-8 codepoint exceeds
|
|
// 4 bytes), if the forward decode returns `more`/`invalid`, or if it
|
|
// lands at a different offset than expected. Returns `more` when the
|
|
// walk reaches byte 0 without finding any initial byte.
|
|
//
|
|
// Hare's `for (d.offs < len(d.src); d.offs -= 1)` relies on size_t
|
|
// wrap-around to exit when offs underflows past 0; ww's offs is i32,
|
|
// so we spell the same exit as `d.offs >= 0`. Hare's `defer d.offs = t`
|
|
// is inlined in each match arm — ww has no defer.
|
|
export fn prev(d: *decoder) (rune | done | more | invalid) = {
|
|
if (d.offs == 0) {
|
|
let dn: done; return dn;
|
|
};
|
|
let n: i32 = d.offs;
|
|
d.offs -= 1;
|
|
for (d.offs >= 0) {
|
|
let b: u8 = d.src[d.offs];
|
|
let bi: i32 = b: i32;
|
|
let cell: i8 = dfa[bi];
|
|
if (cell: i32 != -1) {
|
|
let t: i32 = d.offs;
|
|
match (next(d)) {
|
|
case let r: rune => {
|
|
let landed: i32 = d.offs;
|
|
d.offs = t;
|
|
if (landed != n) {
|
|
let e: invalid; return e;
|
|
};
|
|
return r;
|
|
};
|
|
case let dn: done => {
|
|
d.offs = t;
|
|
let e: invalid; return e;
|
|
};
|
|
case let m: more => {
|
|
d.offs = t;
|
|
let e: invalid; return e;
|
|
};
|
|
case let e: invalid => {
|
|
d.offs = t;
|
|
let e2: invalid; return e2;
|
|
};
|
|
};
|
|
};
|
|
if (n - d.offs == 4) {
|
|
let e: invalid; return e;
|
|
};
|
|
d.offs -= 1;
|
|
};
|
|
let mr: more; return mr;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:74. Borrowed view of the bytes from
|
|
// the decoder's current position to the end of its source.
|
|
export fn remaining(d: *decoder) []u8 = {
|
|
let r: []u8;
|
|
r.ptr = d.src.ptr + (d.offs: u64);
|
|
r.len = d.src.len - d.offs;
|
|
r.cap = d.src.len - d.offs;
|
|
return r;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:80. Borrowed view of the bytes
|
|
// between two decoders' positions. Precondition (Hare asserts both):
|
|
// the decoders share the same source, and `begin.offs <= end.offs`.
|
|
export fn slice(begin: *decoder, end: *decoder) []u8 = {
|
|
if (begin.src.ptr != end.src.ptr) {
|
|
abort("utf8.slice: decoders from different sources");
|
|
};
|
|
if (begin.offs > end.offs) {
|
|
abort("utf8.slice: begin past end");
|
|
};
|
|
let r: []u8;
|
|
r.ptr = begin.src.ptr + (begin.offs: u64);
|
|
r.len = end.offs - begin.offs;
|
|
r.cap = end.offs - begin.offs;
|
|
return r;
|
|
};
|
|
|
|
// ref/hare/encoding/utf8/decode.ha:203. Byte position of the decoder
|
|
// in its source.
|
|
export fn position(d: *decoder) i32 = {
|
|
return d.offs;
|
|
};
|
|
|
|
|
|
// strings — operations over str ({ptr,len}). Hare port; see
|
|
// ref/hare/strings/.
|
|
//
|
|
// Documented divergences from Hare:
|
|
//
|
|
// - `byteindex` / `rbyteindex` rune arms encode via
|
|
// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an
|
|
// undocumented ASCII-only restriction that silently dropped
|
|
// to the wrong byte for U+80..U+7FF and higher).
|
|
// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's
|
|
// `os.alloc` aborts on OOM (no `nomem` type), so we return plain
|
|
// `str`. Empty input returns `{nil, 0}`; Hare returns the static
|
|
// empty string — same observable result.
|
|
// - `iterator` is flattened (`offs`, `src`, `reverse` fields).
|
|
// Hare uses anonymous-embedded `utf8::decoder`
|
|
// (ref/hare/strings/iter.ha:6-9); ww has no anonymous-embed
|
|
// syntax, so `next`/`prev`/`slice` copy `offs`/`src` into a
|
|
// local `utf8.decoder` for the call (and `next`/`prev` write
|
|
// `offs` back).
|
|
// - Hare's private `move()` helper dispatches on a `forward: bool`
|
|
// using a function-pointer `let fun = if (forward) &utf8::next
|
|
// else &utf8::prev`. ww has no fn-pointers in scope yet, so the
|
|
// dispatch is a branch on `forward` selecting the call site.
|
|
|
|
package strings;
|
|
|
|
import bytes;
|
|
import encoding.utf8;
|
|
import os;
|
|
import rt;
|
|
import types;
|
|
|
|
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.
|
|
// `cap` equals `len`; the slice does not own a separate allocation.
|
|
export fn toutf8(s: str) []u8 = {
|
|
let r: []u8;
|
|
r.ptr = s.ptr;
|
|
r.len = s.len;
|
|
r.cap = s.len;
|
|
return r;
|
|
};
|
|
|
|
// frombytes — borrowed str view of `in`. Pure reinterpret per
|
|
// CLAUDE.md rule 9 carve-out; ref/hare/strings/utf8.ha:10.
|
|
export fn frombytes(in: []u8) str = {
|
|
let r: str;
|
|
r.ptr = in.ptr;
|
|
r.len = in.len;
|
|
return r;
|
|
};
|
|
|
|
// compare — three-way bytewise codepoint-order comparison. Return is
|
|
// a sign (neg/zero/pos), not an index, so it tracks Hare's `int`
|
|
// rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
|
|
export fn compare(a: str, b: str) int = {
|
|
let n: i32 = a.len;
|
|
if (b.len < n) { n = b.len; };
|
|
let i: i32 = 0;
|
|
for (i < n) {
|
|
if (a[i] != b[i]) { return (a[i]: int) - (b[i]: int); };
|
|
i += 1;
|
|
};
|
|
return (a.len: int) - (b.len: int);
|
|
};
|
|
|
|
// dup — allocate a fresh copy of `s`. Caller releases with
|
|
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7.
|
|
export fn dup(s: str) str = {
|
|
let r: str;
|
|
r.ptr = nil;
|
|
r.len = 0;
|
|
if (s.len == 0) { return r; };
|
|
let buf: []u8 = alloc([], s.len: u64)!;
|
|
let i: i32 = 0;
|
|
for (i < s.len) { buf[i] = s[i]; i += 1; };
|
|
buf.len = s.len;
|
|
return frombytes(buf);
|
|
};
|
|
|
|
// dupall — fresh `[]str` whose elements are independent copies of
|
|
// `s`'s elements. Caller releases via [[freeall]].
|
|
// ref/hare/strings/dup.ha:26 (#6).
|
|
//
|
|
// Hare gates the per-element dup behind `?` and rolls back via
|
|
// `defer if (!ok) freeall(newsl)`. ww has no `defer if`; more
|
|
// importantly, ww's [[dup]] is still unchecked (returns plain `str`,
|
|
// aborts via os.alloc on OOM — see top-of-file divergence note),
|
|
// so the only nomem propagation point is the initial slice alloc.
|
|
// With no inner failure path, the rollback is structurally a no-op
|
|
// and is omitted; it returns once dup graduates to `(str | nomem)`
|
|
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's
|
|
// rt_ensure call never reaches the grow branch.
|
|
//
|
|
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
|
|
// which returns -EINVAL, and the alloc-slice `?` shortcut routes
|
|
// that through nomem — Hare's heap allocator hands back a sentinel
|
|
// instead (#47). Return `{nil, 0, 0}` directly so callers get the
|
|
// Hare-observable shape (len==0, freeall is a no-op via cap==0).
|
|
export fn dupall(s: []str) ([]str | nomem) = {
|
|
if (s.len == 0) {
|
|
let r: []str;
|
|
r.ptr = nil: *str;
|
|
r.len = 0;
|
|
r.cap = 0;
|
|
return r;
|
|
};
|
|
let newsl: []str = alloc([], s.len)?;
|
|
let i: i32 = 0;
|
|
for (i < s.len) {
|
|
appendstr(&newsl, dup(s[i]));
|
|
i += 1;
|
|
};
|
|
return newsl;
|
|
};
|
|
|
|
// freeall — release each element + the slice header. The natural
|
|
// disposer for any `[]str` of dup'd elements (e.g. shlex.split).
|
|
// ref/hare/strings/dup.ha:38.
|
|
//
|
|
// Empty elements (`{nil, 0}` from a zero-length dup) are skipped:
|
|
// os.free on a nil pointer at len 0 tickles the rt_free guard. The
|
|
// slice header itself is freed at `cap * size(str)` — the literal
|
|
// would drift under #1's str-layout bump, so route through the
|
|
// typ.ww SSoT. A never-grown slice (cap == 0) skips the header free.
|
|
export fn freeall(s: []str) void = {
|
|
let i: i32 = 0;
|
|
for (i < s.len) {
|
|
if (s[i].len > 0) {
|
|
os.free(s[i].ptr: *void, s[i].len: u64);
|
|
};
|
|
i += 1;
|
|
};
|
|
if (s.cap > 0) {
|
|
os.free(s.ptr: *void, (s.cap: u64) * size(str): u64);
|
|
};
|
|
};
|
|
|
|
// concat — fresh allocation containing each element of `strs` in
|
|
// order. Caller releases with `os.free(r.ptr, r.len: u64)`.
|
|
// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped:
|
|
// `os.alloc` aborts on OOM.
|
|
export fn concat(strs: str...) str = {
|
|
let total: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < strs.len) { total += strs[i].len; i += 1; };
|
|
let r: str;
|
|
r.ptr = nil;
|
|
r.len = 0;
|
|
if (total == 0) { return r; };
|
|
let buf: []u8 = alloc([], total: u64)!;
|
|
let off: i32 = 0;
|
|
i = 0;
|
|
for (i < strs.len) {
|
|
let j: i32 = 0;
|
|
for (j < strs[i].len) {
|
|
buf[off + j] = strs[i][j];
|
|
j += 1;
|
|
};
|
|
off += strs[i].len;
|
|
i += 1;
|
|
};
|
|
buf.len = total;
|
|
return frombytes(buf);
|
|
};
|
|
|
|
// join — fresh allocation with `delim` placed between each element of
|
|
// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`.
|
|
// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped:
|
|
// `os.alloc` aborts on OOM.
|
|
export fn join(delim: str, strs: str...) str = {
|
|
let total: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < strs.len) {
|
|
total += strs[i].len;
|
|
if (i + 1 < strs.len) { total += delim.len; };
|
|
i += 1;
|
|
};
|
|
let r: str;
|
|
r.ptr = nil;
|
|
r.len = 0;
|
|
if (total == 0) { return r; };
|
|
let buf: []u8 = alloc([], total: u64)!;
|
|
let off: i32 = 0;
|
|
i = 0;
|
|
for (i < strs.len) {
|
|
let j: i32 = 0;
|
|
for (j < strs[i].len) {
|
|
buf[off + j] = strs[i][j];
|
|
j += 1;
|
|
};
|
|
off += strs[i].len;
|
|
if (i + 1 < strs.len) {
|
|
j = 0;
|
|
for (j < delim.len) {
|
|
buf[off + j] = delim[j];
|
|
j += 1;
|
|
};
|
|
off += delim.len;
|
|
};
|
|
i += 1;
|
|
};
|
|
buf.len = total;
|
|
return frombytes(buf);
|
|
};
|
|
|
|
// utf8bytelenbounded — walk `it` forward `end` runes and return the
|
|
// resulting byte offset. ref/hare/strings/sub.ha:10. Aborts on
|
|
// short input per Hare's contract for the rune-wise [[sub]].
|
|
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
|
|
let i: i32 = 0;
|
|
for (i < end) {
|
|
match (next(it)) {
|
|
case let r: rune => void;
|
|
case utf8.done => abort("strings.sub: index exceeds string length");
|
|
};
|
|
i += 1;
|
|
};
|
|
return it.offs;
|
|
};
|
|
|
|
// sub — borrowed substring [start, end) where start/end are rune
|
|
// indices. ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
|
|
// defaulting end=END is omitted: ww has no default-parameter syntax
|
|
// (filed as #37). Byte-indexed counterpart: [[bytesub]].
|
|
export fn sub(s: str, start: i32, end: i32) str = {
|
|
os.assert(start <= end, "strings.sub: start is higher than end");
|
|
let it: iterator = iter(s);
|
|
let starti: i32 = utf8bytelenbounded(&it, start);
|
|
let endi: i32 = utf8bytelenbounded(&it, end - start);
|
|
let r: str;
|
|
r.ptr = s.ptr + (starti: u64);
|
|
r.len = endi - starti;
|
|
return r;
|
|
};
|
|
|
|
// bytesub — borrowed substring [start, end) where start/end are byte
|
|
// offsets. ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
|
|
// either endpoint lands on a continuation byte (would split a
|
|
// codepoint); the equivalent Hare predicate is `s[i] & 0xc0 == 0x80`
|
|
// at ref/hare/strings/sub.ha:72-73.
|
|
export fn bytesub(s: str, start: i32, end: i32) (str | utf8.invalid) = {
|
|
os.assert(start <= end, "strings.bytesub: start is higher than end");
|
|
os.assert(end <= s.len, "strings.bytesub: end exceeds string length");
|
|
if (start < s.len && (s[start] & 0xC0u8) == 0x80u8) {
|
|
let e: utf8.invalid; return e;
|
|
};
|
|
if (end < s.len && (s[end] & 0xC0u8) == 0x80u8) {
|
|
let e: utf8.invalid; return e;
|
|
};
|
|
let r: str;
|
|
r.ptr = s.ptr + (start: u64);
|
|
r.len = end - start;
|
|
return r;
|
|
};
|
|
|
|
// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes)
|
|
// and return the borrowed slice trimmed to the encoded length. Hare
|
|
// inlines the same shape at ref/hare/strings/index.ha:132.
|
|
fn runebytes(scratch: []u8, r: rune) []u8 = {
|
|
let n: i32 = utf8.encoderune(scratch, r);
|
|
let s: []u8;
|
|
s.ptr = scratch.ptr;
|
|
s.len = n;
|
|
s.cap = n;
|
|
return s;
|
|
};
|
|
|
|
// hasprefix — true iff `in` begins with `prefix`.
|
|
// ref/hare/strings/suffix.ha:8.
|
|
export fn hasprefix(in: str, prefix: (str | rune)) bool = {
|
|
let scratch: [4]u8;
|
|
let p: []u8 = match (prefix) {
|
|
case let s: str => yield toutf8(s);
|
|
case let r: rune => yield runebytes(scratch[0:4], r);
|
|
};
|
|
return bytes.hasprefix(toutf8(in), p);
|
|
};
|
|
|
|
// hassuffix — true iff `in` ends with `suff`.
|
|
// ref/hare/strings/suffix.ha:26.
|
|
export fn hassuffix(in: str, suff: (str | rune)) bool = {
|
|
let scratch: [4]u8;
|
|
let s: []u8 = match (suff) {
|
|
case let v: str => yield toutf8(v);
|
|
case let r: rune => yield runebytes(scratch[0:4], r);
|
|
};
|
|
return bytes.hassuffix(toutf8(in), s);
|
|
};
|
|
|
|
// byteindex — byte-wise offset of `needle` in `haystack`, or void if
|
|
// absent. ref/hare/strings/index.ha:127. Rune arm encodes via
|
|
// utf8.encoderune (Hare passes the encoded slice straight to
|
|
// bytes::index).
|
|
export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|
let scratch: [4]u8;
|
|
let n: []u8 = match (needle) {
|
|
case let s: str => yield toutf8(s);
|
|
case let r: rune => yield runebytes(scratch[0:4], r);
|
|
};
|
|
return bytes.index(toutf8(haystack), n);
|
|
};
|
|
|
|
// rbyteindex — byte-wise offset of the last `needle` in `haystack`.
|
|
// ref/hare/strings/index.ha:138.
|
|
export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|
let scratch: [4]u8;
|
|
let n: []u8 = match (needle) {
|
|
case let s: str => yield toutf8(s);
|
|
case let r: rune => yield runebytes(scratch[0:4], r);
|
|
};
|
|
return bytes.rindex(toutf8(haystack), n);
|
|
};
|
|
|
|
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
|
|
// candidate rune index `i`, compare `haystack` from that position
|
|
// against `needle` rune-by-rune until needle is exhausted (match) or
|
|
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
|
|
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
|
|
// directly via struct assignment; ww re-seats `rest_iter` field-wise
|
|
// because the let-init struct-copy form diverges between cstage and
|
|
// wwstage on this iterator type (993_ww_ww + 995_self_rebuild fail,
|
|
// filed as #41) and rule #10 (CLAUDE.md) forbids stage asymmetry.
|
|
fn indexstring(haystack: str, needle: str) (i32 | void) = {
|
|
let s_iter: iterator = iter(haystack);
|
|
let i: i32 = 0;
|
|
for (true) {
|
|
let rest_iter: iterator;
|
|
rest_iter.src = s_iter.src;
|
|
rest_iter.offs = s_iter.offs;
|
|
rest_iter.reverse = s_iter.reverse;
|
|
let needle_iter: iterator = iter(needle);
|
|
let matched: bool = false;
|
|
for (true) {
|
|
let rest_done: bool = false;
|
|
let rest_r: rune;
|
|
match (next(&rest_iter)) {
|
|
case let r: rune => rest_r = r;
|
|
case utf8.done => rest_done = true;
|
|
};
|
|
let needle_done: bool = false;
|
|
let needle_r: rune;
|
|
match (next(&needle_iter)) {
|
|
case let r: rune => needle_r = r;
|
|
case utf8.done => needle_done = true;
|
|
};
|
|
if (rest_done && !needle_done) { break; };
|
|
if (needle_done) { matched = true; break; };
|
|
if (rest_r != needle_r) { break; };
|
|
};
|
|
if (matched) { return i; };
|
|
match (next(&s_iter)) {
|
|
case let r: rune => i += 1;
|
|
case utf8.done => return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
// index — rune-wise offset of `needle`'s first occurrence in
|
|
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
|
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
|
|
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
|
|
// `index_rune` (ref/hare/strings/index.ha:31).
|
|
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|
match (needle) {
|
|
case let s: str => return indexstring(haystack, s);
|
|
case let r: rune => {
|
|
let it: iterator = iter(haystack);
|
|
let i: i32 = 0;
|
|
for (true) {
|
|
match (next(&it)) {
|
|
case let n: rune => {
|
|
if (n == r) { return i; };
|
|
i += 1;
|
|
};
|
|
case utf8.done => return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
// rindex — rune-wise offset of `needle`'s last occurrence in
|
|
// `haystack`, or void if absent. ref/hare/strings/index.ha:22. The
|
|
// str-arm reuses `rbyteindex`; the rune-arm walks forward tracking
|
|
// the most recent matching rune index (Hare's `rindex_rune` with
|
|
// `riter` returns a byte-offset value for multibyte strings, which
|
|
// disagrees with the rune-wise docstring; we keep the docstring's
|
|
// contract).
|
|
export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|
match (needle) {
|
|
case let s: str => {
|
|
match (rbyteindex(haystack, s)) {
|
|
case void => return;
|
|
case let bo: i32 => {
|
|
let it: iterator = iter(haystack);
|
|
let i: i32 = 0;
|
|
for (position(&it) < bo) {
|
|
match (next(&it)) {
|
|
case let r: rune => i += 1;
|
|
case utf8.done => break;
|
|
};
|
|
};
|
|
return i;
|
|
};
|
|
};
|
|
};
|
|
case let r: rune => {
|
|
let it: iterator = iter(haystack);
|
|
let i: i32 = 0;
|
|
let last: i32 = -1;
|
|
for (true) {
|
|
match (next(&it)) {
|
|
case let n: rune => {
|
|
if (n == r) { last = i; };
|
|
i += 1;
|
|
};
|
|
case utf8.done => break;
|
|
};
|
|
};
|
|
if (last < 0) { return; };
|
|
return last;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
// contains — true iff any of `needles` occurs in `haystack`.
|
|
// ref/hare/strings/contains.ha:9.
|
|
export fn contains(haystack: str, needles: (str | rune)...) bool = {
|
|
let i: i32 = 0;
|
|
for (i < needles.len) {
|
|
match (needles[i]) {
|
|
case let s: str => {
|
|
match (byteindex(haystack, s)) {
|
|
case let bo: i32 => return true;
|
|
case void => void;
|
|
};
|
|
};
|
|
case let r: rune => {
|
|
match (byteindex(haystack, r)) {
|
|
case let bo: i32 => return true;
|
|
case void => void;
|
|
};
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// trimprefix — `s` with `prefix` stripped from the front, or `s`
|
|
// unchanged if it doesn't start with `prefix`. Borrowed view.
|
|
// ref/hare/strings/trim.ha:60.
|
|
export fn trimprefix(input: str, prefix: str) str = {
|
|
if (!hasprefix(input, prefix)) { return input; };
|
|
let r: str;
|
|
r.ptr = input.ptr + (prefix.len: u64);
|
|
r.len = input.len - prefix.len;
|
|
return r;
|
|
};
|
|
|
|
// trimsuffix — symmetric. ref/hare/strings/trim.ha:69.
|
|
export fn trimsuffix(input: str, suffix: str) str = {
|
|
if (!hassuffix(input, suffix)) { return input; };
|
|
let r: str;
|
|
r.ptr = input.ptr;
|
|
r.len = input.len - suffix.len;
|
|
return r;
|
|
};
|
|
|
|
// whitespace — ASCII whitespace set used by the 0-arg ltrim/rtrim/trim
|
|
// branches (#9). ref/hare/strings/trim.ha:6.
|
|
let whitespace: [4]u8 = [0x20u8, 0x0Au8, 0x09u8, 0x0Du8];
|
|
|
|
// ltrim — strip leading runes that occur in `trim`. Borrowed view.
|
|
// 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
|
|
// ref/hare/strings/trim.ha:11. The spread expression is inlined
|
|
// because `let ws: []u8 = whitespace[0:4]` produces a slice whose
|
|
// ptr doesn't track the module-level array storage (filed as #40);
|
|
// `b.flush = flushdefault[0:1]` in lib/bufio is the same shape via
|
|
// the working field-assign path.
|
|
export fn ltrim(input: str, trim: rune...) str = {
|
|
if (trim.len == 0) {
|
|
return frombytes(bytes.ltrim(toutf8(input), whitespace[0:4]...));
|
|
};
|
|
let it: iterator = iter(input);
|
|
for (true) {
|
|
match (next(&it)) {
|
|
case let r: rune => {
|
|
let j: i32 = 0;
|
|
let found: bool = false;
|
|
for (j < trim.len) {
|
|
if (r == trim[j]) { found = true; j = trim.len; }
|
|
else { j += 1; };
|
|
};
|
|
if (!found) {
|
|
match (prev(&it)) {
|
|
case let r2: rune => void;
|
|
case utf8.done => void;
|
|
};
|
|
break;
|
|
};
|
|
};
|
|
case utf8.done => break;
|
|
};
|
|
};
|
|
return iterstr(&it);
|
|
};
|
|
|
|
// rtrim — strip trailing runes that occur in `trim`. Borrowed view.
|
|
// 0-arg strips ASCII whitespace via [[bytes.rtrim]] (#9). Spread is
|
|
// inlined to dodge #40 — see [[ltrim]].
|
|
// ref/hare/strings/trim.ha:32.
|
|
export fn rtrim(input: str, trim: rune...) str = {
|
|
if (trim.len == 0) {
|
|
return frombytes(bytes.rtrim(toutf8(input), whitespace[0:4]...));
|
|
};
|
|
let it: iterator = riter(input);
|
|
for (true) {
|
|
match (next(&it)) {
|
|
case let r: rune => {
|
|
let j: i32 = 0;
|
|
let found: bool = false;
|
|
for (j < trim.len) {
|
|
if (r == trim[j]) { found = true; j = trim.len; }
|
|
else { j += 1; };
|
|
};
|
|
if (!found) {
|
|
match (prev(&it)) {
|
|
case let r2: rune => void;
|
|
case utf8.done => void;
|
|
};
|
|
break;
|
|
};
|
|
};
|
|
case utf8.done => break;
|
|
};
|
|
};
|
|
return iterstr(&it);
|
|
};
|
|
|
|
// trim — strip from both ends. ref/hare/strings/trim.ha:54.
|
|
export fn trim(input: str, trim: rune...) str = {
|
|
return ltrim(rtrim(input, trim...), trim...);
|
|
};
|
|
|
|
// iterator — UTF-8 rune cursor over a `str`. Layout flattens Hare's
|
|
// anonymous-embedded `utf8::decoder` (ref/hare/strings/iter.ha:6-9) to
|
|
// explicit fields. `reverse` selects walk direction: forward iterators
|
|
// (`iter`) advance through utf8.next; reverse iterators (`riter`) advance
|
|
// through utf8.prev. May be copied to save state.
|
|
export type iterator = struct {
|
|
offs: i32,
|
|
src: []u8,
|
|
reverse: bool,
|
|
};
|
|
|
|
// iter — initialize a forward iterator at the start of `src`.
|
|
// ref/hare/strings/iter.ha:24.
|
|
export fn iter(src: str) iterator = {
|
|
let r: iterator;
|
|
r.src = toutf8(src);
|
|
r.offs = 0;
|
|
r.reverse = false;
|
|
return r;
|
|
};
|
|
|
|
// riter — initialize a reverse iterator at the end of `src`. `next`
|
|
// on a reverse iterator walks back through the string.
|
|
// ref/hare/strings/iter.ha:32.
|
|
export fn riter(src: str) iterator = {
|
|
let r: iterator;
|
|
r.src = toutf8(src);
|
|
r.offs = src.len;
|
|
r.reverse = true;
|
|
return r;
|
|
};
|
|
|
|
// move — private dispatch shared by next/prev. `forward` selects
|
|
// utf8.next vs utf8.prev. Aborts on more/invalid per Hare's
|
|
// ref/hare/strings/iter.ha:51-58 ("Invalid UTF-8 string (this should
|
|
// not happen)"). Hare picks the utf8 function via a fn-pointer; ww
|
|
// branches on `forward` at each call site instead.
|
|
fn move(forward: bool, it: *iterator) (rune | utf8.done) = {
|
|
let d: utf8.decoder;
|
|
d.src = it.src;
|
|
d.offs = it.offs;
|
|
if (forward) {
|
|
match (utf8.next(&d)) {
|
|
case let r: rune => { it.offs = d.offs; return r; };
|
|
case let dn: utf8.done => return dn;
|
|
case let m: utf8.more => abort("strings.move: invalid UTF-8");
|
|
case let e: utf8.invalid => abort("strings.move: invalid UTF-8");
|
|
};
|
|
} else {
|
|
match (utf8.prev(&d)) {
|
|
case let r: rune => { it.offs = d.offs; return r; };
|
|
case let dn: utf8.done => return dn;
|
|
case let m: utf8.more => abort("strings.move: invalid UTF-8");
|
|
case let e: utf8.invalid => abort("strings.move: invalid UTF-8");
|
|
};
|
|
};
|
|
};
|
|
|
|
// next — advance the iterator one rune. Forward iterators step
|
|
// through utf8.next; reverse iterators (riter) step backward through
|
|
// utf8.prev. Returns utf8.done at end-of-walk. ref/hare/strings/iter.ha:45.
|
|
export fn next(it: *iterator) (rune | utf8.done) = {
|
|
return move(!it.reverse, it);
|
|
};
|
|
|
|
// prev — step back one rune. Dual to next: on a forward iterator
|
|
// this walks utf8.prev; on a reverse iterator (riter) it walks
|
|
// utf8.next. ref/hare/strings/iter.ha:49.
|
|
export fn prev(it: *iterator) (rune | utf8.done) = {
|
|
return move(it.reverse, it);
|
|
};
|
|
|
|
// iterstr — borrowed view of the bytes remaining in the iterator's
|
|
// walk direction. Forward iter: bytes from offs to end; reverse iter:
|
|
// bytes from start to offs. ref/hare/strings/iter.ha:63.
|
|
export fn iterstr(it: *iterator) str = {
|
|
let r: []u8;
|
|
if (it.reverse) {
|
|
r = it.src[0:it.offs];
|
|
} else {
|
|
r = it.src[it.offs:it.src.len];
|
|
};
|
|
return frombytes(r);
|
|
};
|
|
|
|
// slice — borrowed substring between two iterator positions.
|
|
// ref/hare/strings/iter.ha:75. Hare passes `*iterator` directly where
|
|
// `*utf8::decoder` is expected via anonymous-embed coercion; ww has
|
|
// no anonymous embed, so we reconstruct a local utf8.decoder for each
|
|
// endpoint and forward — same pattern as `move` above.
|
|
export fn slice(begin: *iterator, end: *iterator) str = {
|
|
let b: utf8.decoder;
|
|
b.src = begin.src;
|
|
b.offs = begin.offs;
|
|
let e: utf8.decoder;
|
|
e.src = end.src;
|
|
e.offs = end.offs;
|
|
return frombytes(utf8.slice(&b, &e));
|
|
};
|
|
|
|
// position — byte-wise offset of the iterator in its source.
|
|
// ref/hare/strings/iter.ha:82.
|
|
export fn position(it: *iterator) i32 = {
|
|
return it.offs;
|
|
};
|
|
|
|
// tokenizer — re-export of bytes.tokenizer. ref/hare/strings/tokenize.ha:7.
|
|
// First cross-module type alias in tree; needs #22's transitive
|
|
// alias-chain unwrap (cstage type_chase_named + wwstage
|
|
// structlookupchain) to walk struct fields through the chain.
|
|
export type tokenizer = bytes.tokenizer;
|
|
|
|
// tokenize — yield substrings of `s` split on any byte in `delim`.
|
|
// Leading / trailing / adjacent delims yield empty tokens. `s` and
|
|
// `delim` are borrowed; caller keeps them live for the tokenizer's
|
|
// lifetime. ref/hare/strings/tokenize.ha:32. ASCII-only delim
|
|
// asserted per Hare lines 35-37: a multibyte rune in delim would
|
|
// split on a single continuation byte and yield invalid UTF-8.
|
|
export fn tokenize(s: str, delim: str) tokenizer = {
|
|
let d: []u8 = toutf8(delim);
|
|
let i: i32 = 0;
|
|
for (i < d.len) {
|
|
os.assert((d[i] & 0x80u8) == 0u8,
|
|
"strings.tokenize cannot tokenize on non-ASCII delimiters");
|
|
i += 1;
|
|
};
|
|
return bytes.tokenize(toutf8(s), d...);
|
|
};
|
|
|
|
// rtokenize — reverse-direction counterpart to [[tokenize]]. First
|
|
// next_token yields the last token, last yields the first.
|
|
// ref/hare/strings/tokenize.ha:44.
|
|
export fn rtokenize(s: str, delim: str) tokenizer = {
|
|
let d: []u8 = toutf8(delim);
|
|
let i: i32 = 0;
|
|
for (i < d.len) {
|
|
os.assert((d[i] & 0x80u8) == 0u8,
|
|
"strings.rtokenize cannot tokenize on non-ASCII delimiters");
|
|
i += 1;
|
|
};
|
|
return bytes.rtokenize(toutf8(s), d...);
|
|
};
|
|
|
|
// next_token — current token, advancing the cursor.
|
|
// ref/hare/strings/tokenize.ha:62.
|
|
export fn next_token(s: *tokenizer) (str | bytes.done) = {
|
|
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
|
match (bytes.next_token(b)) {
|
|
case let v: []u8 => return frombytes(v);
|
|
case bytes.done => { let d: bytes.done; return d; };
|
|
};
|
|
};
|
|
|
|
// peek_token — current token without advancing.
|
|
// ref/hare/strings/tokenize.ha:71.
|
|
export fn peek_token(s: *tokenizer) (str | bytes.done) = {
|
|
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
|
match (bytes.peek_token(b)) {
|
|
case let v: []u8 => return frombytes(v);
|
|
case bytes.done => { let d: bytes.done; return d; };
|
|
};
|
|
};
|
|
|
|
// remaining_tokens — unconsumed portion of the input ahead of the
|
|
// cursor. ref/hare/strings/tokenize.ha:79.
|
|
export fn remaining_tokens(s: *tokenizer) str = {
|
|
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
|
return frombytes(bytes.remaining_tokens(b));
|
|
};
|
|
|
|
// cut — split `in` along the first instance of `delim`, returning the
|
|
// portions before and after it. When `delim` is absent the whole input
|
|
// is the first half and the second is empty. Both halves are borrowed
|
|
// from `in`; caller ensures `delim` is non-empty.
|
|
// ref/hare/strings/tokenize.ha:288.
|
|
export fn cut(in: str, delim: str) (str, str) = {
|
|
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
|
|
return (frombytes(a), frombytes(b));
|
|
};
|
|
|
|
// rcut — like [[cut]] but split along the LAST instance of `delim`.
|
|
// ref/hare/strings/tokenize.ha:302.
|
|
export fn rcut(in: str, delim: str) (str, str) = {
|
|
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
|
|
return (frombytes(a), frombytes(b));
|
|
};
|
|
|
|
// rt_ensure is the runtime slice-growth helper invoked by the
|
|
// `append(s, v)` builtin. Direct bind for the same reason as
|
|
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
|
|
// only 8B of the new element, losing the `.len` half of a `str`.
|
|
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
|
|
|
|
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
|
|
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
|
|
// to store the full element width.
|
|
fn appendstr(slice: *[]str, item: str) void = {
|
|
let newlen: i32 = slice.len + 1;
|
|
slice.len = newlen;
|
|
rtensure(slice: *void, size(str): u64);
|
|
let dst: *str = &slice.ptr[newlen - 1];
|
|
dst.ptr = item.ptr;
|
|
dst.len = item.len;
|
|
};
|
|
|
|
// splitn — split `in` on any byte in `delim`, returning up to `n`
|
|
// tokens via forward iteration. The trailing slot (when more than
|
|
// `n - 1` tokens exist) holds the unconsumed remainder. Strings
|
|
// within the result are borrowed from `in`.
|
|
//
|
|
// The caller frees the returned slice via
|
|
// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`.
|
|
//
|
|
// Hare's `([]str | nomem)` collapses to `[]str` here: ww os.alloc
|
|
// has no recoverable failure path. Same precedent as
|
|
// shlex.split / bytes.splitn.
|
|
//
|
|
// ref/hare/strings/tokenize.ha:172.
|
|
export fn splitn(in: str, delim: str, n: i32) []str = {
|
|
let toks: []str;
|
|
toks.ptr = nil: *str;
|
|
toks.len = 0;
|
|
toks.cap = 0;
|
|
let tok: tokenizer = tokenize(in, delim);
|
|
let i: i32 = 0;
|
|
for (i < n - 1) {
|
|
match (next_token(&tok)) {
|
|
case let s: str => { appendstr(&toks, s); };
|
|
case bytes.done => { return toks; };
|
|
};
|
|
i += 1;
|
|
};
|
|
match (peek_token(&tok)) {
|
|
case bytes.done => void;
|
|
case let pk: str => {
|
|
let r: str = remaining_tokens(&tok);
|
|
appendstr(&toks, r);
|
|
};
|
|
};
|
|
return toks;
|
|
};
|
|
|
|
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
|
|
// collected from the end of `in`. The trailing slot holds the
|
|
// unconsumed prefix (everything before the n-th-from-last delim hit).
|
|
//
|
|
// When the input has fewer than n tokens, the `done` short-circuit
|
|
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
|
|
// at ref/hare/strings/tokenize.ha:219-224 where the in-place reverse
|
|
// step is gated behind the n-1 loop running to completion.
|
|
//
|
|
// ref/hare/strings/tokenize.ha:200.
|
|
export fn rsplitn(in: str, delim: str, n: i32) []str = {
|
|
let toks: []str;
|
|
toks.ptr = nil: *str;
|
|
toks.len = 0;
|
|
toks.cap = 0;
|
|
let tok: tokenizer = rtokenize(in, delim);
|
|
let i: i32 = 0;
|
|
for (i < n - 1) {
|
|
match (next_token(&tok)) {
|
|
case let s: str => { appendstr(&toks, s); };
|
|
case bytes.done => { return toks; };
|
|
};
|
|
i += 1;
|
|
};
|
|
match (peek_token(&tok)) {
|
|
case bytes.done => void;
|
|
case let pk: str => {
|
|
let r: str = remaining_tokens(&tok);
|
|
appendstr(&toks, r);
|
|
};
|
|
};
|
|
|
|
// In-place reverse so callers see argv-order, matching Hare
|
|
// (ref/hare/strings/tokenize.ha:220). Element copy is field-wise
|
|
// through `*str` because `toks[i] = toks[j]` (full 16B str store)
|
|
// lands in the multi-word-store gap noted at cmd/w6c/cgen.c:6515.
|
|
let a: i32 = 0;
|
|
let b: i32 = toks.len - 1;
|
|
for (a < b) {
|
|
let pa: *str = &toks.ptr[a];
|
|
let pb: *str = &toks.ptr[b];
|
|
let tp: *u8 = pa.ptr;
|
|
let tl: i32 = pa.len;
|
|
pa.ptr = pb.ptr;
|
|
pa.len = pb.len;
|
|
pb.ptr = tp;
|
|
pb.len = tl;
|
|
a += 1;
|
|
b -= 1;
|
|
};
|
|
return toks;
|
|
};
|
|
|
|
// split — full split of `in` on `delim` (no token cap). Mirrors
|
|
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
|
// because the index type is i32 (lib/CLAUDE.md).
|
|
//
|
|
// ref/hare/strings/tokenize.ha:242.
|
|
export fn split(in: str, delim: str) []str = {
|
|
return splitn(in, delim, types.I32_MAX);
|
|
};
|
|
|
|
// lpad — left-pad `s` with `p` rune until the result reaches `maxlen`
|
|
// bytes. Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
|
|
// at ref/hare/strings/pad.ha:9. A multibyte `p` whose encoded width
|
|
// doesn't divide `maxlen - s.len` evenly leaves a trailing pad byte
|
|
// pair sliced mid-codepoint at byte `maxlen-1`, exactly as Hare's
|
|
// `res[..maxlen]` does (ref/hare/strings/pad.ha:20). When
|
|
// `(maxlen - s.len) * pad.len >= maxlen` (multibyte pad overflows the
|
|
// budget), `s` is entirely sliced off — same as Hare. Caller releases
|
|
// with `os.free(r.ptr, r.len: u64)`. Hare's `nomem` return is dropped:
|
|
// `os.alloc` aborts on OOM. Buf size == r.len keeps the free-contract
|
|
// shape of [[dup]] / [[concat]] / [[join]]; Hare's `alloc([], maxlen)!`
|
|
// over-allocs via append then slices, but Hare's slice-free recovers
|
|
// the true capacity from the heap allocator (rt/ensure.ha:24), which
|
|
// ww's munmap-based `os.free` cannot do.
|
|
export fn lpad(s: str, p: rune, maxlen: i32) str = {
|
|
if (s.len >= maxlen) { return dup(s); };
|
|
let scratch: [4]u8;
|
|
let pad: []u8 = runebytes(scratch[0:4], p);
|
|
let buf: []u8 = alloc([], maxlen: u64)!;
|
|
let padwrite: i32 = (maxlen - s.len) * pad.len;
|
|
if (padwrite > maxlen) { padwrite = maxlen; };
|
|
let off: i32 = 0;
|
|
for (off < padwrite) {
|
|
buf[off] = pad.ptr[off % pad.len];
|
|
off += 1;
|
|
};
|
|
let k: i32 = 0;
|
|
let srem: i32 = maxlen - off;
|
|
if (srem > s.len) { srem = s.len; };
|
|
for (k < srem) {
|
|
buf[off + k] = s[k];
|
|
k += 1;
|
|
};
|
|
buf.len = maxlen;
|
|
return frombytes(buf);
|
|
};
|
|
|
|
// replace — fresh allocation of `s` with every non-overlapping
|
|
// occurrence of `needle` replaced by `target`. Caller releases with
|
|
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
|
|
//
|
|
// Hare delegates to [[multireplace]] with a single pair; ww has no
|
|
// `(str, str)` variadic shape today (#39), so this is a standalone
|
|
// two-pass implementation: pass 1 counts matches to size the result,
|
|
// pass 2 copies chunks and `target` into a single fresh buffer.
|
|
// Single nomem path (the `alloc([], total)?`) preserves Hare's
|
|
// signature without a per-write `append(...)?` (ww's append builtin
|
|
// aborts on OOM, #11). Empty `needle` would hasprefix-match every
|
|
// position with a zero stride — same infinite loop Hare exhibits at
|
|
// ref/hare/strings/replace.ha:31; not gated.
|
|
export fn replace(s: str, needle: str, target: str) (str | nomem) = {
|
|
let sb: []u8 = toutf8(s);
|
|
let nb: []u8 = toutf8(needle);
|
|
let tb: []u8 = toutf8(target);
|
|
let count: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < sb.len) {
|
|
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
|
count += 1;
|
|
i += nb.len;
|
|
} else {
|
|
i += 1;
|
|
};
|
|
};
|
|
let total: i32 = sb.len + count * (tb.len - nb.len);
|
|
if (total == 0) {
|
|
let r: str;
|
|
r.ptr = nil;
|
|
r.len = 0;
|
|
return r;
|
|
};
|
|
let res: []u8 = alloc([], total)?;
|
|
let off: i32 = 0;
|
|
i = 0;
|
|
for (i < sb.len) {
|
|
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
|
let j: i32 = 0;
|
|
for (j < tb.len) {
|
|
res.ptr[off + j] = tb.ptr[j];
|
|
j += 1;
|
|
};
|
|
off += tb.len;
|
|
i += nb.len;
|
|
} else {
|
|
res.ptr[off] = sb.ptr[i];
|
|
off += 1;
|
|
i += 1;
|
|
};
|
|
};
|
|
res.len = total;
|
|
return frombytes(res);
|
|
};
|
|
|
|
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
|
|
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
|
|
export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
|
if (s.len >= maxlen) { return dup(s); };
|
|
let scratch: [4]u8;
|
|
let pad: []u8 = runebytes(scratch[0:4], p);
|
|
let buf: []u8 = alloc([], maxlen: u64)!;
|
|
let k: i32 = 0;
|
|
for (k < s.len) {
|
|
buf[k] = s[k];
|
|
k += 1;
|
|
};
|
|
let padwrite: i32 = maxlen - s.len;
|
|
let i: i32 = 0;
|
|
for (i < padwrite) {
|
|
buf[s.len + i] = pad.ptr[i % pad.len];
|
|
i += 1;
|
|
};
|
|
buf.len = maxlen;
|
|
return frombytes(buf);
|
|
};
|
|
|
|
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c.
|
|
//
|
|
// Loads relocatable ELF64 .o files emitted by w6a, appends .text to
|
|
// the combined image, and pulls in symbols + relocations with
|
|
// offsets adjusted to the combined section.
|
|
//
|
|
// Also handles SysV `ar` archives (libwwrt.a). The two-pass loader
|
|
// indexes members on the first pass and iteratively pulls members
|
|
// that define currently-undefined symbols on subsequent passes.
|
|
|
|
package w6l;
|
|
|
|
import os;
|
|
import rt;
|
|
import strings;
|
|
import sym;
|
|
|
|
def ET_REL: i32 = 1;
|
|
def EM_X86_64: i32 = 62;
|
|
def SHT_PROGBITS: i32 = 1;
|
|
def SHT_SYMTAB: i32 = 2;
|
|
def SHT_STRTAB: i32 = 3;
|
|
def SHT_RELA: i32 = 4;
|
|
|
|
// ---- little-endian byte readers ----------------------------------------
|
|
// w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off
|
|
// the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't).
|
|
|
|
fn rdu16(p: *u8, off: u64) u16 = {
|
|
let b0: u16 = p[off]: u16;
|
|
let b1: u16 = p[off + 1u64]: u16;
|
|
return b0 | (b1 << 8u16);
|
|
};
|
|
|
|
fn rdu32(p: *u8, off: u64) u32 = {
|
|
let b0: u32 = p[off]: u32;
|
|
let b1: u32 = p[off + 1u64]: u32;
|
|
let b2: u32 = p[off + 2u64]: u32;
|
|
let b3: u32 = p[off + 3u64]: u32;
|
|
return b0 | (b1 << 8u32) | (b2 << 16u32) | (b3 << 24u32);
|
|
};
|
|
|
|
fn rdu64(p: *u8, off: u64) u64 = {
|
|
let lo: u64 = rdu32(p, off): u64;
|
|
let hi: u64 = rdu32(p, off + 4u64): u64;
|
|
return lo | (hi << 32u64);
|
|
};
|
|
|
|
// ---- ELF64 section header offsets (40 bytes total) --------------------
|
|
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
|
|
def SHDR_NAME: u64 = 0u64;
|
|
def SHDR_TYPE: u64 = 4u64;
|
|
def SHDR_OFFSET: u64 = 24u64;
|
|
def SHDR_SIZE_F: u64 = 32u64;
|
|
def SHDR_LINK: u64 = 40u64;
|
|
|
|
// ELF64 ehdr field offsets
|
|
def EHDR_SIZE: u64 = 64u64;
|
|
def EHDR_TYPE: u64 = 16u64;
|
|
def EHDR_MACHINE: u64 = 18u64;
|
|
def EHDR_SHOFF: u64 = 40u64;
|
|
def EHDR_SHENTSIZE: u64 = 58u64;
|
|
def EHDR_SHNUM: u64 = 60u64;
|
|
def EHDR_SHSTRNDX: u64 = 62u64;
|
|
|
|
// ELF64 sym entry: 24 bytes
|
|
def SYM_SIZE: u64 = 24u64;
|
|
def SYM_NAME: u64 = 0u64;
|
|
def SYM_INFO: u64 = 4u64;
|
|
def SYM_SHNDX: u64 = 6u64;
|
|
def SYM_VALUE: u64 = 8u64;
|
|
|
|
// ELF64 RELA entry: 24 bytes
|
|
def RELA_SIZE: u64 = 24u64;
|
|
def RELA_OFFSET: u64 = 0u64;
|
|
def RELA_INFO: u64 = 8u64;
|
|
def RELA_ADDEND: u64 = 16u64;
|
|
|
|
// ---- file slurp --------------------------------------------------------
|
|
|
|
fn slurp(path: *u8) (*u8, u64) = {
|
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
|
if (fd < 0) { return nil, 0u64; };
|
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
|
let n: i64 = 0i64;
|
|
match (szr) {
|
|
case let v: i64 => n = v;
|
|
case let e: os.oserror => { os.close(fd); return nil, 0u64; };
|
|
};
|
|
let buf: []u8 = alloc([], n: u64)!;
|
|
buf.len = n: i32;
|
|
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, n: u64);
|
|
os.close(fd);
|
|
let got: i64 = 0i64;
|
|
match (rr) {
|
|
case let v: i64 => got = v;
|
|
case let e: os.oserror => return nil, 0u64;
|
|
};
|
|
if (got != n) { return nil, 0u64; };
|
|
return buf.ptr, n: u64;
|
|
};
|
|
|
|
// ---- text buffer growth ------------------------------------------------
|
|
|
|
fn emittext(l: *lnk, src: *u8, n: u64) void = {
|
|
if (l.textlen + n > l.textcap) {
|
|
let nc: u64 = l.textcap;
|
|
if (nc == 0u64) { nc = 4096u64; };
|
|
for (nc < l.textlen + n) { nc = nc * 2u64; };
|
|
// Grow by mmap'ing a fresh region and copying. The old buffer
|
|
// is leaked into the page allocator; for a linker run this is
|
|
// trivial waste.
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < l.textlen) {
|
|
nb[i] = l.text[i];
|
|
i += 1u64;
|
|
};
|
|
l.text = nb.ptr;
|
|
l.textcap = nc;
|
|
};
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
l.text[l.textlen + i] = src[i];
|
|
i += 1u64;
|
|
};
|
|
l.textlen += n;
|
|
};
|
|
|
|
fn emitdata(l: *lnk, src: *u8, n: u64) void = {
|
|
if (l.datalen + n > l.datacap) {
|
|
let nc: u64 = l.datacap;
|
|
if (nc == 0u64) { nc = 256u64; };
|
|
for (nc < l.datalen + n) { nc = nc * 2u64; };
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < l.datalen) {
|
|
nb[i] = l.data[i];
|
|
i += 1u64;
|
|
};
|
|
l.data = nb.ptr;
|
|
l.datacap = nc;
|
|
};
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
l.data[l.datalen + i] = src[i];
|
|
i += 1u64;
|
|
};
|
|
l.datalen += n;
|
|
};
|
|
|
|
// ---- C-string helpers --------------------------------------------------
|
|
|
|
fn cstrlen(p: *u8) u64 = {
|
|
let n: u64 = 0u64;
|
|
for (p[n] != 0u8) { n += 1u64; };
|
|
return n;
|
|
};
|
|
|
|
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style
|
|
// callers to lib/os entrypoints (str post-task-#23). Shared with
|
|
// main.ww and dyn.ww via the w6l bundle.
|
|
fn pathstr(p: *u8) str = {
|
|
let r: str;
|
|
r.ptr = p;
|
|
r.len = cstrlen(p): i32;
|
|
return r;
|
|
};
|
|
|
|
fn cstreq(p: *u8, lit: str) bool = {
|
|
let n: u64 = lit.len: u64;
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
let li: i32 = i: i32;
|
|
if (p[i] != lit[li]) { return false; };
|
|
i += 1u64;
|
|
};
|
|
if (p[i] != 0u8) { return false; };
|
|
return true;
|
|
};
|
|
|
|
// Build a ww str from a NUL-terminated *u8 (for passing to intern).
|
|
fn cstrtostr(p: *u8) str = {
|
|
let n: u64 = cstrlen(p);
|
|
let view: str;
|
|
view.ptr = p;
|
|
view.len = n: i32;
|
|
return strings.dup(view);
|
|
};
|
|
|
|
// ---- archive (SysV ar) types and helpers -------------------------------
|
|
//
|
|
// Each archive member starts with a 60-byte ar_hdr. The fields we care
|
|
// about are the first byte (member type) and the size at offset 48 (a
|
|
// 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
|
|
|
|
type defent = struct {
|
|
name: str,
|
|
dnext: *defent,
|
|
};
|
|
|
|
type armember = struct {
|
|
data: *u8, // owned heap copy of the member's ELF bytes
|
|
size: u64,
|
|
defs: *defent, // linked list of defined globals
|
|
loaded: i32,
|
|
mnext: *armember,
|
|
};
|
|
|
|
fn isarchive(p: *u8, len: u64) bool = {
|
|
if (len < 8u64) { return false; };
|
|
if (p[0u64] != '!' || p[1u64] != '<' || p[2u64] != 'a' || p[3u64] != 'r' ||
|
|
p[4u64] != 'c' || p[5u64] != 'h' || p[6u64] != '>' ||
|
|
p[7u64] != '\n') {
|
|
return false;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// arfield — parse a space-padded decimal integer of width n.
|
|
fn arfield(p: *u8, n: u64) u64 = {
|
|
let v: u64 = 0u64;
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
let c: u8 = p[i];
|
|
if (c < 48u8) { return v; }; // space, NUL, etc.
|
|
if (c > 57u8) { return v; };
|
|
v = v * 10u64 + ((c - 48u8): u64);
|
|
i += 1u64;
|
|
};
|
|
return v;
|
|
};
|
|
|
|
// elfglobals — return a linked list of names of globally-defined
|
|
// (STB_GLOBAL) symbols whose section is `.text`. Names are owned
|
|
// heap copies, so the source ELF buffer can be freed afterward.
|
|
fn elfglobals(buf: *u8, len: u64) *defent = {
|
|
if (len < EHDR_SIZE) { return nil; };
|
|
if (buf[0u64] != 127u8) { return nil; };
|
|
if (buf[1u64] != 'E') { return nil; };
|
|
if (buf[2u64] != 'L') { return nil; };
|
|
if (buf[3u64] != 'F') { return nil; };
|
|
|
|
let shoff: u64 = rdu64(buf, EHDR_SHOFF);
|
|
let shnum: u32 = rdu16(buf, EHDR_SHNUM): u32;
|
|
let shstrndx: u32 = rdu16(buf, EHDR_SHSTRNDX): u32;
|
|
|
|
let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
|
let shstr: *u8 = buf + shstrshoff;
|
|
|
|
let idxtext: i32 = -1;
|
|
let idxdata: i32 = -1;
|
|
let idxsymtab: i32 = -1;
|
|
let i: u32 = 0u32;
|
|
for (i < shnum) {
|
|
let secoff: u64 = shoff + (i: u64) * SHDR_SIZE;
|
|
let shtype: u32 = rdu32(buf, secoff + SHDR_TYPE);
|
|
let shname: u32 = rdu32(buf, secoff + SHDR_NAME);
|
|
let nm: *u8 = shstr + (shname: u64);
|
|
if (shtype == SHT_PROGBITS: u32) {
|
|
if (cstreq(nm, ".text")) { idxtext = i: i32; };
|
|
if (cstreq(nm, ".data")) { idxdata = i: i32; };
|
|
};
|
|
if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; };
|
|
i += 1u32;
|
|
};
|
|
if (idxtext < 0) { return nil; };
|
|
if (idxsymtab < 0) { return nil; };
|
|
|
|
let symsh: u64 = shoff + (idxsymtab: u64) * SHDR_SIZE;
|
|
let symoff: u64 = rdu64(buf, symsh + SHDR_OFFSET);
|
|
let symsize: u64 = rdu64(buf, symsh + SHDR_SIZE_F);
|
|
let symlink: u32 = rdu32(buf, symsh + SHDR_LINK);
|
|
let nsyms: u64 = symsize / SYM_SIZE;
|
|
|
|
let strsh: u64 = shoff + (symlink: u64) * SHDR_SIZE;
|
|
let stroff: u64 = rdu64(buf, strsh + SHDR_OFFSET);
|
|
let strtab: *u8 = buf + stroff;
|
|
|
|
let head: *defent = nil;
|
|
let si: u64 = 1u64;
|
|
for (si < nsyms) {
|
|
let symp: u64 = symoff + si * SYM_SIZE;
|
|
let stname: u32 = rdu32(buf, symp + SYM_NAME);
|
|
let stinfo: u8 = buf[symp + SYM_INFO];
|
|
let stshndx: u16 = rdu16(buf, symp + SYM_SHNDX);
|
|
let bind: u32 = (stinfo: u32) >> 4u32;
|
|
// STB_GLOBAL = 1; defined in .text or .data. Both are
|
|
// included so an archive member that owns a data global
|
|
// gets pulled in when something references it.
|
|
if (bind == 1u32) {
|
|
if (stshndx != 0u16) {
|
|
let intext: bool = (stshndx: i32) == idxtext;
|
|
let indt: bool = false;
|
|
if (idxdata >= 0) {
|
|
indt = (stshndx: i32) == idxdata;
|
|
};
|
|
if (intext || indt) {
|
|
let nmp: *u8 = strtab + (stname: u64);
|
|
if (nmp[0u64] != 0u8) {
|
|
let nm: str = cstrtostr(nmp);
|
|
let de: *defent = alloc(defent { name = nm, dnext = head })!;
|
|
head = de;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
si += 1u64;
|
|
};
|
|
return head;
|
|
};
|
|
|
|
// memberdefinesundef — true if any of m's defined globals matches a
|
|
// currently-undefined symbol in the linker's symbol table. Names not
|
|
// already interned are uninteresting (the link doesn't need them yet).
|
|
fn memberdefinesundef(l: *lnk, m: *armember) bool = {
|
|
let de: *defent = m.defs;
|
|
for (de != nil) {
|
|
let s: *lsym = lookup(l, de.name);
|
|
if (s != nil) {
|
|
if (s.defined == 0) { return true; };
|
|
};
|
|
de = de.dnext;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// loadarchive — port of cmd/w6l/obj.c:load_archive.
|
|
//
|
|
// Pass 1 indexes every regular member. Pass 2 iteratively pulls in any
|
|
// member that supplies a currently-undefined symbol; each pull may
|
|
// introduce fresh undefs, so we loop until quiescent.
|
|
fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|
let head: *armember = nil;
|
|
let tail: *armember = nil;
|
|
let pos: u64 = 8u64; // past "!<arch>\n"
|
|
for (pos + 60u64 <= len) {
|
|
let hdrsize: u64 = arfield(buf + pos + 48u64, 10u64);
|
|
let hdrend: u64 = pos + 60u64;
|
|
if (hdrend + hdrsize > len) { break; };
|
|
let first: u8 = buf[pos];
|
|
// Skip the symbol table ('/'), long-name table ('//'), and
|
|
// any padding entries (NUL or space leading byte).
|
|
if (first != '/' && first != 0u8 && first != ' ') {
|
|
let m: *armember = alloc(armember { size = hdrsize })!;
|
|
let mbs: []u8 = alloc([], hdrsize)!;
|
|
let mb: *u8 = mbs.ptr;
|
|
let i: u64 = 0u64;
|
|
for (i < hdrsize) {
|
|
mb[i] = buf[hdrend + i];
|
|
i += 1u64;
|
|
};
|
|
m.data = mb;
|
|
m.defs = elfglobals(mb, hdrsize);
|
|
if (head == nil) { head = m; }
|
|
else { tail.mnext = m; };
|
|
tail = m;
|
|
};
|
|
pos = hdrend + hdrsize;
|
|
if ((hdrsize & 1u64) != 0u64) { pos = pos + 1u64; };
|
|
};
|
|
|
|
let changed: i32 = 1;
|
|
for (changed != 0) {
|
|
changed = 0;
|
|
let m: *armember = head;
|
|
for (m != nil) {
|
|
if (m.loaded == 0) {
|
|
if (memberdefinesundef(l, m)) {
|
|
if (loadimage(l, path, m.data, m.size) == 0) {
|
|
m.loaded = 1;
|
|
changed = 1;
|
|
};
|
|
};
|
|
};
|
|
m = m.mnext;
|
|
};
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// ---- main loader -------------------------------------------------------
|
|
|
|
export fn load(l: *lnk, path: *u8) i32 = {
|
|
let bufp: *u8;
|
|
let buflen: u64;
|
|
bufp, buflen = slurp(path);
|
|
if (bufp == nil) {
|
|
let m: str = "w6l: cannot read object\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return -1;
|
|
};
|
|
if (isarchive(bufp, buflen)) {
|
|
return loadarchive(l, path, bufp, buflen);
|
|
};
|
|
return loadimage(l, path, bufp, buflen);
|
|
};
|
|
|
|
fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
|
if (len < EHDR_SIZE) { return -1; };
|
|
// magic: 0x7f, 'E', 'L', 'F'
|
|
if (buf[0u64] != 127u8) { return -1; };
|
|
if (buf[1u64] != 'E') { return -1; };
|
|
if (buf[2u64] != 'L') { return -1; };
|
|
if (buf[3u64] != 'F') { return -1; };
|
|
if (buf[4u64] != 2u8) { return -1; }; // ELFCLASS64
|
|
if (rdu16(buf, EHDR_TYPE) != ET_REL: u16) { return -1; };
|
|
if (rdu16(buf, EHDR_MACHINE) != EM_X86_64: u16) { return -1; };
|
|
|
|
let shoff: u64 = rdu64(buf, EHDR_SHOFF);
|
|
let shnum: u32 = rdu16(buf, EHDR_SHNUM): u32;
|
|
let shstrndx: u32 = rdu16(buf, EHDR_SHSTRNDX): u32;
|
|
|
|
let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
|
let shstr: *u8 = buf + shstrshoff;
|
|
|
|
// find .text, .data, .symtab, .rela.text, .rela.data
|
|
let idxtext: i32 = -1;
|
|
let idxdata: i32 = -1;
|
|
let idxsymtab: i32 = -1;
|
|
let idxrela: i32 = -1;
|
|
let idxrelad: i32 = -1;
|
|
let i: u32 = 0u32;
|
|
for (i < shnum) {
|
|
let secoff: u64 = shoff + (i: u64) * SHDR_SIZE;
|
|
let shtype: u32 = rdu32(buf, secoff + SHDR_TYPE);
|
|
let shname: u32 = rdu32(buf, secoff + SHDR_NAME);
|
|
let nm: *u8 = shstr + (shname: u64);
|
|
if (shtype == SHT_PROGBITS: u32) {
|
|
if (cstreq(nm, ".text")) { idxtext = i: i32; };
|
|
if (cstreq(nm, ".data")) { idxdata = i: i32; };
|
|
};
|
|
if (shtype == SHT_SYMTAB: u32) { idxsymtab = i: i32; };
|
|
if (shtype == SHT_RELA: u32) {
|
|
if (cstreq(nm, ".rela.text")) { idxrela = i: i32; };
|
|
if (cstreq(nm, ".rela.data")) { idxrelad = i: i32; };
|
|
};
|
|
i += 1u32;
|
|
};
|
|
if (idxtext < 0) {
|
|
let m: str = "w6l: missing .text\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return -1;
|
|
};
|
|
if (idxsymtab < 0) {
|
|
let m: str = "w6l: missing .symtab\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return -1;
|
|
};
|
|
|
|
let textsh: u64 = shoff + (idxtext: u64) * SHDR_SIZE;
|
|
let textoff: u64 = rdu64(buf, textsh + SHDR_OFFSET);
|
|
let textsize: u64 = rdu64(buf, textsh + SHDR_SIZE_F);
|
|
|
|
let symsh: u64 = shoff + (idxsymtab: u64) * SHDR_SIZE;
|
|
let symoff: u64 = rdu64(buf, symsh + SHDR_OFFSET);
|
|
let symsize: u64 = rdu64(buf, symsh + SHDR_SIZE_F);
|
|
let symlink: u32 = rdu32(buf, symsh + SHDR_LINK);
|
|
let nsyms: u64 = symsize / SYM_SIZE;
|
|
|
|
let strsh: u64 = shoff + (symlink: u64) * SHDR_SIZE;
|
|
let stroff: u64 = rdu64(buf, strsh + SHDR_OFFSET);
|
|
let strtab: *u8 = buf + stroff;
|
|
|
|
let datasize: u64 = 0u64;
|
|
let dataoff: u64 = 0u64;
|
|
if (idxdata >= 0) {
|
|
let datash: u64 = shoff + (idxdata: u64) * SHDR_SIZE;
|
|
dataoff = rdu64(buf, datash + SHDR_OFFSET);
|
|
datasize = rdu64(buf, datash + SHDR_SIZE_F);
|
|
};
|
|
|
|
// Track this object.
|
|
let ob: *lobj = alloc(lobj {
|
|
path = cstrtostr(path),
|
|
buf = buf,
|
|
len = len,
|
|
textoff = l.textlen,
|
|
textsize = textsize,
|
|
dataoff = l.datalen,
|
|
datasize = datasize,
|
|
onext = l.objs,
|
|
})!;
|
|
l.objs = ob;
|
|
|
|
// Append .text bytes to the combined image.
|
|
emittext(l, buf + textoff, textsize);
|
|
// Append .data bytes (if present) to the combined .data buffer.
|
|
if (idxdata >= 0) {
|
|
if (datasize > 0u64) {
|
|
emitdata(l, buf + dataoff, datasize);
|
|
};
|
|
};
|
|
|
|
// Walk symbols. We don't keep a per-object map[] of *lsym. Instead
|
|
// the reloc loop re-walks symtab and re-interns by name. Simpler
|
|
// than dancing around the cgen's u64-shift gaps.
|
|
let si: u64 = 1u64; // skip index 0 (always undef sentinel)
|
|
for (si < nsyms) {
|
|
let symp: u64 = symoff + si * SYM_SIZE;
|
|
let stname: u32 = rdu32(buf, symp + SYM_NAME);
|
|
let stshndx: u16 = rdu16(buf, symp + SYM_SHNDX);
|
|
let stvalue: u64 = rdu64(buf, symp + SYM_VALUE);
|
|
let nmp: *u8 = strtab + (stname: u64);
|
|
if (nmp[0u64] != 0u8) {
|
|
let nm: str = cstrtostr(nmp);
|
|
let gs: *lsym = intern(l, nm);
|
|
if (stshndx != 0u16) {
|
|
let intext: bool = (stshndx: i32) == idxtext;
|
|
let indt: bool = false;
|
|
if (idxdata >= 0) {
|
|
indt = (stshndx: i32) == idxdata;
|
|
};
|
|
if (intext && indt) { indt = false; };
|
|
if (intext) {
|
|
if (gs.defined != 0) {
|
|
let m: str = "w6l: duplicate symbol\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
l.errs += 1;
|
|
} else {
|
|
gs.defined = 1;
|
|
gs.owner = ob;
|
|
gs.idxinowner = si: i32;
|
|
gs.val = ob.textoff + stvalue;
|
|
};
|
|
};
|
|
if (indt) {
|
|
if (gs.defined != 0) {
|
|
let m: str = "w6l: duplicate symbol\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
l.errs += 1;
|
|
} else {
|
|
gs.defined = 1;
|
|
gs.indata = 1;
|
|
gs.owner = ob;
|
|
gs.idxinowner = si: i32;
|
|
gs.val = ob.dataoff + stvalue;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
si += 1u64;
|
|
};
|
|
|
|
// Per-object relocation collection.
|
|
if (idxrela >= 0) {
|
|
let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE;
|
|
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
|
let relasize: u64 = rdu64(buf, relash + SHDR_SIZE_F);
|
|
let nrel: u64 = relasize / RELA_SIZE;
|
|
let ri: u64 = 0u64;
|
|
for (ri < nrel) {
|
|
let rp: u64 = relaoff + ri * RELA_SIZE;
|
|
let roff: u64 = rdu64(buf, rp + RELA_OFFSET);
|
|
let rinfo: u64 = rdu64(buf, rp + RELA_INFO);
|
|
let raddend: u64 = rdu64(buf, rp + RELA_ADDEND);
|
|
let rsymidx: u32 = (rinfo >> 32u64): u32;
|
|
let rkind: i32 = ((rinfo & 4294967295u64): u32): i32;
|
|
let nr: *lrel = alloc(lrel {
|
|
off = ob.textoff + roff,
|
|
section = 0,
|
|
kind = rkind,
|
|
addend = raddend: i64,
|
|
rnext = l.rels,
|
|
})!;
|
|
// Look up the referenced sym by name (re-walk symtab).
|
|
if ((rsymidx: u64) < nsyms) {
|
|
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
|
|
let sname: u32 = rdu32(buf, sp + SYM_NAME);
|
|
let snm: *u8 = strtab + (sname: u64);
|
|
if (snm[0u64] != 0u8) {
|
|
let nm: str = cstrtostr(snm);
|
|
nr.sym = intern(l, nm);
|
|
};
|
|
};
|
|
l.rels = nr;
|
|
ri += 1u64;
|
|
};
|
|
};
|
|
|
|
// Data-reloc collection. Offsets land in .data, shifted by
|
|
// this object's data_off so they index the combined buffer.
|
|
if (idxrelad >= 0) {
|
|
let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE;
|
|
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);
|
|
let relasize: u64 = rdu64(buf, relash + SHDR_SIZE_F);
|
|
let nrel: u64 = relasize / RELA_SIZE;
|
|
let ri: u64 = 0u64;
|
|
for (ri < nrel) {
|
|
let rp: u64 = relaoff + ri * RELA_SIZE;
|
|
let roff: u64 = rdu64(buf, rp + RELA_OFFSET);
|
|
let rinfo: u64 = rdu64(buf, rp + RELA_INFO);
|
|
let raddend: u64 = rdu64(buf, rp + RELA_ADDEND);
|
|
let rsymidx: u32 = (rinfo >> 32u64): u32;
|
|
let rkind: i32 = ((rinfo & 4294967295u64): u32): i32;
|
|
let nr: *lrel = alloc(lrel {
|
|
off = ob.dataoff + roff,
|
|
section = 1,
|
|
kind = rkind,
|
|
addend = raddend: i64,
|
|
rnext = l.rels,
|
|
})!;
|
|
if ((rsymidx: u64) < nsyms) {
|
|
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
|
|
let sname: u32 = rdu32(buf, sp + SYM_NAME);
|
|
let snm: *u8 = strtab + (sname: u64);
|
|
if (snm[0u64] != 0u8) {
|
|
let nm: str = cstrtostr(snm);
|
|
nr.sym = intern(l, nm);
|
|
};
|
|
};
|
|
l.rels = nr;
|
|
ri += 1u64;
|
|
};
|
|
};
|
|
|
|
return 0;
|
|
};
|
|
|
|
// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c.
|
|
//
|
|
// Load a shared object (ET_DYN) so the linker knows which symbols it
|
|
// exports and which DT_NEEDED entry to record. We do not pull bytes
|
|
// from the .so; the dynamic loader maps it at runtime.
|
|
//
|
|
// Each call appends one lso to lnk->sos. l_so_provides_v answers
|
|
// "does this .so export the named symbol, and at which version?" —
|
|
// l_resolve uses that to promote unresolved references to dynamic.
|
|
|
|
package w6l;
|
|
|
|
import os;
|
|
import rt;
|
|
import strings;
|
|
import sym;
|
|
|
|
def ET_DYN_SO: u16 = 3u16;
|
|
def EM_X86_64_SO: u16 = 62u16;
|
|
|
|
def SHT_DYNAMIC: u32 = 6u32;
|
|
def SHT_DYNSYM: u32 = 11u32;
|
|
// GNU extensions, sh_type values.
|
|
def SHT_GNU_VERDEF: u32 = 1879048189u32; // 0x6ffffffd
|
|
def SHT_GNU_VERNEED: u32 = 1879048190u32; // 0x6ffffffe
|
|
def SHT_GNU_VERSYM: u32 = 1879048191u32; // 0x6fffffff
|
|
|
|
def DT_NULL_TAG: i64 = 0i64;
|
|
def DT_SONAME_TAG: i64 = 14i64;
|
|
|
|
// Versym special values.
|
|
def VER_NDX_LOCAL_C: u16 = 0u16;
|
|
def VER_NDX_GLOBAL_C: u16 = 1u16;
|
|
def VERSYM_HIDDEN_C: u16 = 32768u16; // 0x8000
|
|
def VERSYM_VERSION_C: u16 = 32767u16; // 0x7fff
|
|
|
|
// ELF64 ehdr field offsets (subset)
|
|
def EH_SHOFF: u64 = 40u64;
|
|
def EH_ETYPE: u64 = 16u64;
|
|
def EH_EMACHINE: u64 = 18u64;
|
|
def EH_SHENTSIZE: u64 = 58u64;
|
|
def EH_SHNUM: u64 = 60u64;
|
|
def EH_SHSTRNDX: u64 = 62u64;
|
|
|
|
// ELF64 Shdr (64 bytes)
|
|
def SH_SIZE: u64 = 64u64;
|
|
def SH_TYPE: u64 = 4u64;
|
|
def SH_OFFSET: u64 = 24u64;
|
|
def SH_SIZE_F: u64 = 32u64;
|
|
def SH_LINK: u64 = 40u64;
|
|
def SH_ENTSIZE: u64 = 56u64;
|
|
|
|
// ELF64 Sym (24 bytes)
|
|
def SY_SIZE: u64 = 24u64;
|
|
def SY_NAME: u64 = 0u64;
|
|
def SY_INFO: u64 = 4u64;
|
|
def SY_SHNDX: u64 = 6u64;
|
|
|
|
// ELF64 Dyn (16 bytes)
|
|
def DY_SIZE: u64 = 16u64;
|
|
def DY_TAG: u64 = 0u64;
|
|
def DY_VAL: u64 = 8u64;
|
|
|
|
// Verdef (20 bytes)
|
|
def VD_SIZE: u64 = 20u64;
|
|
def VD_NDX: u64 = 4u64;
|
|
def VD_CNT: u64 = 6u64;
|
|
def VD_AUX: u64 = 12u64;
|
|
def VD_NEXT: u64 = 16u64;
|
|
|
|
// Verdaux (8 bytes)
|
|
def VA_NAME: u64 = 0u64;
|
|
def VA_NEXT: u64 = 4u64;
|
|
|
|
// ---- little-endian byte readers ---------------------------------------
|
|
|
|
fn du16(p: *u8, off: u64) u16 = {
|
|
let b0: u16 = p[off]: u16;
|
|
let b1: u16 = p[off + 1u64]: u16;
|
|
return b0 | (b1 << 8u16);
|
|
};
|
|
|
|
fn du32(p: *u8, off: u64) u32 = {
|
|
let b0: u32 = p[off]: u32;
|
|
let b1: u32 = p[off + 1u64]: u32;
|
|
let b2: u32 = p[off + 2u64]: u32;
|
|
let b3: u32 = p[off + 3u64]: u32;
|
|
return b0 | (b1 << 8u32) | (b2 << 16u32) | (b3 << 24u32);
|
|
};
|
|
|
|
fn du64(p: *u8, off: u64) u64 = {
|
|
let lo: u64 = du32(p, off): u64;
|
|
let hi: u64 = du32(p, off + 4u64): u64;
|
|
return lo | (hi << 32u64);
|
|
};
|
|
|
|
fn di64(p: *u8, off: u64) i64 = {
|
|
return du64(p, off): i64;
|
|
};
|
|
|
|
// ---- C-string helpers --------------------------------------------------
|
|
|
|
fn dcstrlen(p: *u8) u64 = {
|
|
let n: u64 = 0u64;
|
|
for (p[n] != 0u8) { n += 1u64; };
|
|
return n;
|
|
};
|
|
|
|
fn dcstrtostr(p: *u8) str = {
|
|
let n: u64 = dcstrlen(p);
|
|
let view: str;
|
|
view.ptr = p;
|
|
view.len = n: i32;
|
|
return strings.dup(view);
|
|
};
|
|
|
|
// basename: scan for last '/' and return pointer past it.
|
|
fn dbasename(p: *u8) *u8 = {
|
|
let n: u64 = dcstrlen(p);
|
|
let i: u64 = n;
|
|
for (i > 0u64) {
|
|
i -= 1u64;
|
|
if (p[i] == '/') {
|
|
return p + i + 1u64;
|
|
};
|
|
};
|
|
return p;
|
|
};
|
|
|
|
// ---- file slurp --------------------------------------------------------
|
|
|
|
fn slurpso(path: *u8) (*u8, u64) = {
|
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
|
if (fd < 0) { return nil, 0u64; };
|
|
let szr: (i64 | os.oserror) = os.filesize(fd);
|
|
let n: i64 = 0i64;
|
|
match (szr) {
|
|
case let v: i64 => n = v;
|
|
case let e: os.oserror => { os.close(fd); return nil, 0u64; };
|
|
};
|
|
let buf: []u8 = alloc([], n: u64)!;
|
|
buf.len = n: i32;
|
|
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, n: u64);
|
|
os.close(fd);
|
|
let got: i64 = 0i64;
|
|
match (rr) {
|
|
case let v: i64 => got = v;
|
|
case let e: os.oserror => return nil, 0u64;
|
|
};
|
|
if (got != n) { return nil, 0u64; };
|
|
return buf.ptr, n: u64;
|
|
};
|
|
|
|
// ---- verdef helpers ----------------------------------------------------
|
|
|
|
// vdnameat — walk verdef records and return the name (as *u8 into
|
|
// the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name
|
|
// is the first Verdaux's vda_name (subsequent auxes are predecessor
|
|
// names). Returns nil if no entry matches.
|
|
fn vdnameat(buf: *u8, verdefoff: u64, verdefsize: u64,
|
|
verstr: *u8, ndx: u16) *u8 = {
|
|
let off: u64 = 0u64;
|
|
for (off < verdefsize) {
|
|
let vdp: u64 = verdefoff + off;
|
|
let vdndx: u16 = du16(buf, vdp + VD_NDX);
|
|
let vdaux: u32 = du32(buf, vdp + VD_AUX);
|
|
let vdnext: u32 = du32(buf, vdp + VD_NEXT);
|
|
if (vdndx == ndx) {
|
|
let auxp: u64 = vdp + (vdaux: u64);
|
|
let vdaname: u32 = du32(buf, auxp + VA_NAME);
|
|
return verstr + (vdaname: u64);
|
|
};
|
|
if (vdnext == 0u32) { return nil; };
|
|
off += vdnext: u64;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// ---- entry points ------------------------------------------------------
|
|
|
|
export fn loadso(l: *lnk, path: *u8) i32 = {
|
|
let buf: *u8;
|
|
let blen: u64;
|
|
buf, blen = slurpso(path);
|
|
if (buf == nil) {
|
|
os.write(2, "w6l: cannot read .so\n".ptr, 20u64);
|
|
return -1;
|
|
};
|
|
if (blen < 64u64) {
|
|
os.write(2, "w6l: short ELF\n".ptr, 14u64);
|
|
return -1;
|
|
};
|
|
if (buf[0u64] != 127u8) { return soerr("not ELF"); };
|
|
if (buf[1u64] != 'E') { return soerr("not ELF"); };
|
|
if (buf[2u64] != 'L') { return soerr("not ELF"); };
|
|
if (buf[3u64] != 'F') { return soerr("not ELF"); };
|
|
if (buf[4u64] != 2u8) { return soerr("not ELFCLASS64"); };
|
|
if (du16(buf, EH_EMACHINE) != EM_X86_64_SO) {
|
|
return soerr("not amd64");
|
|
};
|
|
if (du16(buf, EH_ETYPE) != ET_DYN_SO) {
|
|
return soerr("not ET_DYN");
|
|
};
|
|
|
|
let shoff: u64 = du64(buf, EH_SHOFF);
|
|
let shnum: u32 = du16(buf, EH_SHNUM): u32;
|
|
if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
|
|
if (shnum == 0u32) { return soerr("stripped .so unsupported"); };
|
|
|
|
// Locate the four sections we care about.
|
|
let idxdynsym: i32 = -1;
|
|
let idxdynamic: i32 = -1;
|
|
let idxversym: i32 = -1;
|
|
let idxverdef: i32 = -1;
|
|
let i: u32 = 0u32;
|
|
for (i < shnum) {
|
|
let shp: u64 = shoff + (i: u64) * SH_SIZE;
|
|
let shtype: u32 = du32(buf, shp + SH_TYPE);
|
|
if (shtype == SHT_DYNSYM) { idxdynsym = i: i32; };
|
|
if (shtype == SHT_DYNAMIC) { idxdynamic = i: i32; };
|
|
if (shtype == SHT_GNU_VERSYM) { idxversym = i: i32; };
|
|
if (shtype == SHT_GNU_VERDEF) { idxverdef = i: i32; };
|
|
i += 1u32;
|
|
};
|
|
if (idxdynsym < 0) {
|
|
return soerr("no .dynsym");
|
|
};
|
|
|
|
let dynsymsh: u64 = shoff + (idxdynsym: u64) * SH_SIZE;
|
|
let dynsymoff: u64 = du64(buf, dynsymsh + SH_OFFSET);
|
|
let dynsymsize: u64 = du64(buf, dynsymsh + SH_SIZE_F);
|
|
let dynsymlink: u32 = du32(buf, dynsymsh + SH_LINK);
|
|
let nsyms: u64 = dynsymsize / SY_SIZE;
|
|
|
|
let dynstrsh: u64 = shoff + (dynsymlink: u64) * SH_SIZE;
|
|
let dynstroff: u64 = du64(buf, dynstrsh + SH_OFFSET);
|
|
let dynstr: *u8 = buf + dynstroff;
|
|
|
|
// SONAME: .dynamic strings live in the section pointed at by its
|
|
// sh_link (almost always .dynstr).
|
|
let sonamecs: *u8 = nil;
|
|
if (idxdynamic >= 0) {
|
|
let dynsh: u64 = shoff + (idxdynamic: u64) * SH_SIZE;
|
|
let dynoff: u64 = du64(buf, dynsh + SH_OFFSET);
|
|
let dynsize: u64 = du64(buf, dynsh + SH_SIZE_F);
|
|
let dynlink: u32 = du32(buf, dynsh + SH_LINK);
|
|
let dstrsh: u64 = shoff + (dynlink: u64) * SH_SIZE;
|
|
let dstroff: u64 = du64(buf, dstrsh + SH_OFFSET);
|
|
let dstr: *u8 = buf + dstroff;
|
|
let nd: u64 = dynsize / DY_SIZE;
|
|
let di: u64 = 0u64;
|
|
for (di < nd) {
|
|
let dp: u64 = dynoff + di * DY_SIZE;
|
|
let dtag: i64 = di64(buf, dp + DY_TAG);
|
|
if (dtag == DT_NULL_TAG) {
|
|
di = nd; // break
|
|
} else {
|
|
if (dtag == DT_SONAME_TAG) {
|
|
let dval: u64 = du64(buf, dp + DY_VAL);
|
|
sonamecs = dstr + dval;
|
|
di = nd; // break
|
|
} else {
|
|
di += 1u64;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (sonamecs == nil) {
|
|
sonamecs = dbasename(path);
|
|
};
|
|
|
|
// Versym is one u16 per dynsym entry.
|
|
let versymoff: u64 = 0u64;
|
|
let hasversym: i32 = 0;
|
|
if (idxversym >= 0) {
|
|
let vssh: u64 = shoff + (idxversym: u64) * SH_SIZE;
|
|
versymoff = du64(buf, vssh + SH_OFFSET);
|
|
hasversym = 1;
|
|
};
|
|
|
|
// Verdef section bounds + the .dynstr-like string section it uses.
|
|
let verdefoff: u64 = 0u64;
|
|
let verdefsize: u64 = 0u64;
|
|
let verstr: *u8 = nil;
|
|
if (idxverdef >= 0) {
|
|
let vdsh: u64 = shoff + (idxverdef: u64) * SH_SIZE;
|
|
verdefoff = du64(buf, vdsh + SH_OFFSET);
|
|
verdefsize = du64(buf, vdsh + SH_SIZE_F);
|
|
let vdlink: u32 = du32(buf, vdsh + SH_LINK);
|
|
let vstrsh: u64 = shoff + (vdlink: u64) * SH_SIZE;
|
|
let vstroff: u64 = du64(buf, vstrsh + SH_OFFSET);
|
|
verstr = buf + vstroff;
|
|
};
|
|
|
|
// Build the lso. Exports are appended in dynsym order so
|
|
// soprovides_v's first-match semantics match the C version.
|
|
let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!;
|
|
let tail: *lexport = nil;
|
|
|
|
let si: u64 = 1u64;
|
|
for (si < nsyms) {
|
|
let sp: u64 = dynsymoff + si * SY_SIZE;
|
|
let stshndx: u16 = du16(buf, sp + SY_SHNDX);
|
|
if (stshndx == 0u16) { si += 1u64; } else {
|
|
let stinfo: u8 = buf[sp + SY_INFO];
|
|
let bind: u32 = (stinfo: u32) >> 4u32;
|
|
if (bind != 1u32) { if (bind != 2u32) {
|
|
// not GLOBAL/WEAK
|
|
si += 1u64;
|
|
continue;
|
|
}; };
|
|
let stname: u32 = du32(buf, sp + SY_NAME);
|
|
let nmp: *u8 = dynstr + (stname: u64);
|
|
if (nmp[0u64] == 0u8) {
|
|
si += 1u64;
|
|
continue;
|
|
};
|
|
|
|
// Determine version. Skip non-default (hidden) and
|
|
// local entries.
|
|
let vernamecs: *u8 = nil;
|
|
let keep: i32 = 1;
|
|
if (hasversym != 0) {
|
|
let v: u16 = du16(buf, versymoff + si * 2u64);
|
|
if ((v & VERSYM_HIDDEN_C) != 0u16) {
|
|
keep = 0; // non-default
|
|
} else {
|
|
let vidx: u16 = v & VERSYM_VERSION_C;
|
|
if (vidx == VER_NDX_LOCAL_C) {
|
|
keep = 0; // not exported
|
|
} else { if (vidx == VER_NDX_GLOBAL_C) {
|
|
vernamecs = nil;
|
|
} else { if (vidx == 1u16) {
|
|
// glibc's BASE entry: treat as
|
|
// unversioned. (The C version
|
|
// notes that vidx==1 in Verdef
|
|
// maps to the SONAME BASE.)
|
|
vernamecs = nil;
|
|
} else {
|
|
if (verstr != nil) {
|
|
let nm: *u8 = vdnameat(buf, verdefoff, verdefsize, verstr, vidx);
|
|
vernamecs = nm;
|
|
};
|
|
}; }; };
|
|
};
|
|
};
|
|
|
|
if (keep != 0) {
|
|
let e: *lexport = alloc(lexport { name = dcstrtostr(nmp) })!;
|
|
if (vernamecs != nil) {
|
|
e.version = dcstrtostr(vernamecs);
|
|
};
|
|
if (tail == nil) {
|
|
so.exports = e;
|
|
} else {
|
|
tail.enext = e;
|
|
};
|
|
tail = e;
|
|
};
|
|
si += 1u64;
|
|
};
|
|
};
|
|
|
|
so.sonext = l.sos;
|
|
l.sos = so;
|
|
return 0;
|
|
};
|
|
|
|
fn soerr(msg: str) i32 = {
|
|
os.write(2, "w6l: ".ptr, 4u64);
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.write(2, "\n".ptr, 1u64);
|
|
return -1;
|
|
};
|
|
|
|
// soprovides — 1 if so exports name, 0 otherwise.
|
|
export fn soprovides(so: *lso, name: str) i32 = {
|
|
if (so == nil) { return 0; };
|
|
let e: *lexport = so.exports;
|
|
for (e != nil) {
|
|
if (streq(e.name, name)) { return 1; };
|
|
e = e.enext;
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// soversion — the version of so's export named `name`, or an empty
|
|
// str (ptr=nil, len=0) if the export is unversioned or not present.
|
|
export fn soversion(so: *lso, name: str) str = {
|
|
let result: str;
|
|
result.ptr = nil;
|
|
result.len = 0i32;
|
|
if (so == nil) { return result; };
|
|
let e: *lexport = so.exports;
|
|
for (e != nil) {
|
|
if (streq(e.name, name)) {
|
|
result.ptr = e.version.ptr;
|
|
result.len = e.version.len;
|
|
return result;
|
|
};
|
|
e = e.enext;
|
|
};
|
|
return result;
|
|
};
|
|
|
|
// `streq` lives in sym.ww — same bundle, single definition.
|
|
|
|
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c.
|
|
//
|
|
// Resolution + relocation. l_resolve flags every undefined symbol
|
|
// referenced by a relocation, and promotes those provided by some
|
|
// loaded .so to "dynamic" with a freshly-assigned PLT slot.
|
|
// l_relocate walks the rel list and patches the .text bytes in place
|
|
// once the final virtual base is known. Dynamic refs are deferred:
|
|
// their site is patched later in dynout, once the PLT vaddr is known.
|
|
//
|
|
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
|
|
// PC-relative displacements (PLT32 == PC32 for static).
|
|
|
|
package w6l;
|
|
|
|
import os;
|
|
import sym;
|
|
import dyn;
|
|
|
|
def R_X86_64_64: i32 = 1;
|
|
def R_X86_64_PC32: i32 = 2;
|
|
def R_X86_64_PLT32: i32 = 4;
|
|
|
|
export fn resolve(l: *lnk) i32 = {
|
|
// Initialise dynamic-linking sentinels. alloc(T{})! zeroes, so
|
|
// isdyn/dynlib start clean — but pltidx and dynsymidx
|
|
// must be -1, not 0.
|
|
let si: *lsym = l.syms;
|
|
for (si != nil) {
|
|
si.pltidx = -1;
|
|
si.dynsymidx = -1;
|
|
si = si.snext;
|
|
};
|
|
|
|
// Promote each undefined sym that some lso exports to dynamic
|
|
// and hand it a PLT slot. Iteration order over the relocation
|
|
// list determines slot numbering and is stable across runs.
|
|
let r: *lrel = l.rels;
|
|
for (r != nil) {
|
|
if (r.sym != nil) {
|
|
if (r.sym.defined == 0) {
|
|
if (r.sym.isdyn == 0) {
|
|
let so: *lso = l.sos;
|
|
for (so != nil) {
|
|
if (soprovides(so, r.sym.name) != 0) {
|
|
r.sym.isdyn = 1;
|
|
r.sym.dynlib = so;
|
|
r.sym.pltidx = l.dynn;
|
|
l.dynn += 1;
|
|
so = nil; // break
|
|
} else {
|
|
so = so.sonext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
r = r.rnext;
|
|
};
|
|
|
|
// What remains undefined truly is undefined.
|
|
let r2: *lrel = l.rels;
|
|
for (r2 != nil) {
|
|
if (r2.sym != nil) {
|
|
if (r2.sym.defined == 0) {
|
|
if (r2.sym.isdyn == 0) {
|
|
os.write(2, "w6l: undefined reference to '".ptr, 28u64);
|
|
let nm: str = r2.sym.name;
|
|
os.write(2, nm.ptr, nm.len: u64);
|
|
os.write(2, "'\n".ptr, 2u64);
|
|
l.errs += 1;
|
|
};
|
|
};
|
|
};
|
|
r2 = r2.rnext;
|
|
};
|
|
return l.errs;
|
|
};
|
|
|
|
fn patchu32(p: *u8, v: u32) void = {
|
|
p[0] = (v & 255u32): u8;
|
|
p[1] = ((v >> 8u32) & 255u32): u8;
|
|
p[2] = ((v >> 16u32) & 255u32): u8;
|
|
p[3] = ((v >> 24u32) & 255u32): u8;
|
|
};
|
|
|
|
fn patchu64(p: *u8, v: u64) void = {
|
|
let i: i32 = 0;
|
|
for (i < 8) {
|
|
p[i] = ((v >> (i: u64 * 8u64)) & 255u64): u8;
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = {
|
|
let r: *lrel = l.rels;
|
|
for (r != nil) {
|
|
if (r.sym != nil) {
|
|
// Dynamic refs are patched later in dynout once the
|
|
// PLT vaddr is known.
|
|
if (r.sym.isdyn != 0) {
|
|
r = r.rnext;
|
|
continue;
|
|
};
|
|
if (r.sym.defined != 0) {
|
|
let symva: u64 = textva + r.sym.val;
|
|
if (r.sym.indata != 0) { symva = datava + r.sym.val; };
|
|
let k: i32 = r.kind;
|
|
if (k == R_X86_64_PC32) {
|
|
let site: u64 = textva + r.off;
|
|
let rel: i64 = (symva: i64 - site: i64) + r.addend;
|
|
patchu32(l.text + r.off, rel: u32);
|
|
} else { if (k == R_X86_64_PLT32) {
|
|
let site: u64 = textva + r.off;
|
|
let rel: i64 = (symva: i64 - site: i64) + r.addend;
|
|
patchu32(l.text + r.off, rel: u32);
|
|
} else { if (k == R_X86_64_64) {
|
|
// Absolute 64-bit. Currently used only
|
|
// for DATAR slots in .data.
|
|
let v: u64 = (symva: i64 + r.addend): u64;
|
|
if (r.section == 1) {
|
|
patchu64(l.data + r.off, v);
|
|
} else {
|
|
patchu64(l.text + r.off, v);
|
|
};
|
|
} else {
|
|
os.write(2, "w6l: unsupported reloc kind\n".ptr, 27u64);
|
|
l.errs += 1;
|
|
};};};
|
|
};
|
|
};
|
|
r = r.rnext;
|
|
};
|
|
return l.errs;
|
|
};
|
|
|
|
// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c.
|
|
//
|
|
// Emit a dynamic-linked ELF executable. The shape is the simplest
|
|
// valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader
|
|
// resolves every PLT slot at startup (no lazy binding, no PLT0
|
|
// trampoline). SysV .hash, not .gnu.hash. Non-PIE, fixed base.
|
|
//
|
|
// Layout:
|
|
// [0] Ehdr
|
|
// [64] Phdrs (PT_LOAD R+X, PT_LOAD R+W, PT_INTERP, PT_DYNAMIC)
|
|
// [interp_off] "/lib64/ld-linux-x86-64.so.2\0"
|
|
// [dynstr_off] .dynstr
|
|
// [dynsym_off] .dynsym
|
|
// [hash_off] .hash
|
|
// [versym_off] .gnu.version
|
|
// [verneed_off] .gnu.version_r
|
|
// [relaplt_off] .rela.plt
|
|
// [pad to 0x1000]
|
|
// [text_off] .text
|
|
// [plt_off] .plt
|
|
// [pad to next page]
|
|
// [gotplt_off] .got.plt (writable; mapped by PT_LOAD #2)
|
|
// [dynamic_off] .dynamic (writable; covered by PT_DYNAMIC)
|
|
|
|
package w6l;
|
|
|
|
import os;
|
|
import rt;
|
|
import sym;
|
|
|
|
// ELF constants
|
|
def ET_EXEC_D: u16 = 2u16;
|
|
def EM_X86_64_D: u16 = 62u16;
|
|
def EV_CURRENT_D: u32 = 1u32;
|
|
def ELFCLASS64_D: u8 = 2u8;
|
|
def ELFDATA2LSB_D: u8 = 1u8;
|
|
|
|
def PT_LOAD_D: u32 = 1u32;
|
|
def PT_DYNAMIC_D: u32 = 2u32;
|
|
def PT_INTERP_D: u32 = 3u32;
|
|
def PF_X_D: u32 = 1u32;
|
|
def PF_W_D: u32 = 2u32;
|
|
def PF_R_D: u32 = 4u32;
|
|
|
|
def DT_NULL: i64 = 0i64;
|
|
def DT_NEEDED: i64 = 1i64;
|
|
def DT_PLTRELSZ: i64 = 2i64;
|
|
def DT_PLTGOT: i64 = 3i64;
|
|
def DT_HASH: i64 = 4i64;
|
|
def DT_STRTAB: i64 = 5i64;
|
|
def DT_SYMTAB: i64 = 6i64;
|
|
def DT_STRSZ: i64 = 10i64;
|
|
def DT_SYMENT: i64 = 11i64;
|
|
def DT_PLTREL: i64 = 20i64;
|
|
def DT_RELA: i64 = 7i64;
|
|
def DT_JMPREL: i64 = 23i64;
|
|
def DT_BIND_NOW: i64 = 24i64;
|
|
def DT_VERSYM: i64 = 1879048176i64; // 0x6ffffff0
|
|
def DT_VERNEED: i64 = 1879048190i64; // 0x6ffffffe
|
|
def DT_VERNEEDNUM: i64 = 1879048191i64; // 0x6fffffff
|
|
|
|
def VER_NDX_LOCAL_D: u16 = 0u16;
|
|
def VER_NDX_GLOBAL_D: u16 = 1u16;
|
|
|
|
def R_X86_64_PC32_D: i32 = 2;
|
|
def R_X86_64_PLT32_D: i32 = 4;
|
|
def R_X86_64_JUMP_SLOT_D: u32 = 7u32;
|
|
|
|
def STB_GLOBAL_D: u8 = 1u8;
|
|
def STT_FUNC_D: u8 = 2u8;
|
|
|
|
def PLT_STUB_BYTES_D: u64 = 8u64;
|
|
def PAGE: u64 = 4096u64;
|
|
|
|
def INTERP: str = "/lib64/ld-linux-x86-64.so.2";
|
|
|
|
// ---- byte writers ------------------------------------------------------
|
|
|
|
fn dwr8(buf: *u8, off: u64, v: u8) void = {
|
|
buf[off] = v;
|
|
};
|
|
|
|
fn dwr16(buf: *u8, off: u64, v: u16) void = {
|
|
buf[off] = (v & 255u16): u8;
|
|
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
|
};
|
|
|
|
fn dwr32(buf: *u8, off: u64, v: u32) void = {
|
|
buf[off] = (v & 255u32): u8;
|
|
buf[off + 1u64] = ((v >> 8u32) & 255u32): u8;
|
|
buf[off + 2u64] = ((v >> 16u32) & 255u32): u8;
|
|
buf[off + 3u64] = ((v >> 24u32) & 255u32): u8;
|
|
};
|
|
|
|
fn dwr64(buf: *u8, off: u64, v: u64) void = {
|
|
dwr32(buf, off, (v & 4294967295u64): u32);
|
|
dwr32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
|
};
|
|
|
|
fn dwri64(buf: *u8, off: u64, v: i64) void = {
|
|
dwr64(buf, off, v: u64);
|
|
};
|
|
|
|
fn dwri32(buf: *u8, off: u64, v: i32) void = {
|
|
dwr32(buf, off, v: u32);
|
|
};
|
|
|
|
// ---- byte readers ------------------------------------------------------
|
|
|
|
fn drdu16(p: *u8, off: u64) u16 = {
|
|
let b0: u16 = p[off]: u16;
|
|
let b1: u16 = p[off + 1u64]: u16;
|
|
return b0 | (b1 << 8u16);
|
|
};
|
|
|
|
fn drdu32(p: *u8, off: u64) u32 = {
|
|
let b0: u32 = p[off]: u32;
|
|
let b1: u32 = p[off + 1u64]: u32;
|
|
let b2: u32 = p[off + 2u64]: u32;
|
|
let b3: u32 = p[off + 3u64]: u32;
|
|
return b0 | (b1 << 8u32) | (b2 << 16u32) | (b3 << 24u32);
|
|
};
|
|
|
|
fn drdi32(p: *u8, off: u64) i32 = {
|
|
return drdu32(p, off): i32;
|
|
};
|
|
|
|
fn dbcopy(dst: *u8, off: u64, src: *u8, n: u64) void = {
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
dst[off + i] = src[i];
|
|
i += 1u64;
|
|
};
|
|
};
|
|
|
|
// elfhash — SysV ELF hash. Used for .gnu.version_r's vna_hash.
|
|
fn elfhash(name: str) u32 = {
|
|
let h: u32 = 0u32;
|
|
let i: i32 = 0;
|
|
for (i < name.len) {
|
|
let c: u32 = (name[i]: u8): u32;
|
|
h = (h << 4u32) + c;
|
|
let g: u32 = h & 4026531840u32; // 0xf0000000
|
|
if (g != 0u32) { h = h ^ (g >> 24u32); };
|
|
h = h & ~g;
|
|
i += 1;
|
|
};
|
|
return h;
|
|
};
|
|
|
|
fn alignup(off: u64, a: u64) u64 = {
|
|
return (off + a - 1u64) & ~(a - 1u64);
|
|
};
|
|
|
|
fn streqd(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// ---- main entry --------------------------------------------------------
|
|
|
|
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|
// .data shares the R+W PT_LOAD with .got.plt and .dynamic.
|
|
// Placed after .dynamic so the segment is one contiguous run;
|
|
// relocate runs from here so the dyn layout's datava lands in
|
|
// patched offsets.
|
|
let n: i32 = l.dynn;
|
|
let nu: u64 = n: u64;
|
|
|
|
// ---- collect dyn syms into a plt_idx-indexed array ----
|
|
let dynsyms: []*lsym = alloc([], nu)!;
|
|
let s: *lsym = l.syms;
|
|
for (s != nil) {
|
|
if (s.isdyn != 0) {
|
|
if (s.pltidx >= 0) {
|
|
if (s.pltidx < n) {
|
|
dynsyms[s.pltidx] = s;
|
|
};
|
|
};
|
|
};
|
|
s = s.snext;
|
|
};
|
|
let i: i32 = 0;
|
|
for (i < n) {
|
|
if (dynsyms[i] == nil) {
|
|
os.write(2, "w6l: dynout: no sym for plt_idx\n".ptr, 31u64);
|
|
return 1;
|
|
};
|
|
i += 1;
|
|
};
|
|
|
|
// ---- collect used .so's (in l.sos order) ----
|
|
let maxsos: i32 = 0;
|
|
let so: *lso = l.sos;
|
|
for (so != nil) { maxsos += 1; so = so.sonext; };
|
|
let sosused: []*lso = alloc([], maxsos: u64)!;
|
|
let nsos: i32 = 0;
|
|
so = l.sos;
|
|
for (so != nil) {
|
|
let used: i32 = 0;
|
|
let j: i32 = 0;
|
|
for (j < n) {
|
|
let dsm: *lsym = dynsyms[j];
|
|
let dl: *lso = dsm.dynlib;
|
|
if (dl == so) { used = 1; j = n; }
|
|
else { j += 1; };
|
|
};
|
|
if (used != 0) {
|
|
sosused[nsos] = so;
|
|
nsos += 1;
|
|
};
|
|
so = so.sonext;
|
|
};
|
|
|
|
// ---- build flat version table grouped by vlib ----
|
|
// vlib_sos_idx[k] = sos_used index for vlib k.
|
|
// vlib_first[k] = ver index of first version under vlib k.
|
|
// vlib_count[k] = number of versions under vlib k.
|
|
// ver_lib_idx[v] = vlib index that version v belongs to.
|
|
// ver_name_ptr_arr[v] = name's *u8 (interned in the .so's verdef strings).
|
|
// ver_name_len_buf[v] = name length (i32).
|
|
// ver_dynstr_off[v] = offset within .dynstr (assigned after layout).
|
|
// ver_vna_other[v] = versym index (starting at 2).
|
|
|
|
let vlibsosidxbuf: []u8 = alloc([], (maxsos: u64) * 4u64)!;
|
|
let vlibfirstbuf: []u8 = alloc([], (maxsos: u64) * 4u64)!;
|
|
let vlibcountbuf: []u8 = alloc([], (maxsos: u64) * 4u64)!;
|
|
let nvlibs: i32 = 0;
|
|
|
|
let verlibidxbuf: []u8 = alloc([], nu * 4u64)!;
|
|
let vernameptrarr: []*u8 = alloc([], nu)!;
|
|
let vernamelenbuf: []u8 = alloc([], nu * 4u64)!;
|
|
let verdynstroff: []u8 = alloc([], nu * 4u64)!;
|
|
let vervnaother: []u8 = alloc([], nu * 2u64)!;
|
|
let nvers: i32 = 0;
|
|
|
|
let si: i32 = 0;
|
|
for (si < nsos) {
|
|
let curso: *lso = sosused[si];
|
|
let has: i32 = 0;
|
|
let j: i32 = 0;
|
|
for (j < n) {
|
|
let dsm: *lsym = dynsyms[j];
|
|
let dl: *lso = dsm.dynlib;
|
|
if (dl == curso) {
|
|
let nm0: str = dsm.name;
|
|
let dv: str = soversion(curso, nm0);
|
|
if (dv.len > 0) {
|
|
has = 1; j = n;
|
|
} else { j += 1; };
|
|
} else { j += 1; };
|
|
};
|
|
if (has != 0) {
|
|
dwr32(vlibsosidxbuf.ptr, (nvlibs: u64) * 4u64, si: u32);
|
|
dwr32(vlibfirstbuf.ptr, (nvlibs: u64) * 4u64, nvers: u32);
|
|
let added: i32 = 0;
|
|
let jj: i32 = 0;
|
|
for (jj < n) {
|
|
let dsm2: *lsym = dynsyms[jj];
|
|
let dl2: *lso = dsm2.dynlib;
|
|
if (dl2 == curso) {
|
|
let nm2: str = dsm2.name;
|
|
let vname: str = soversion(curso, nm2);
|
|
if (vname.len > 0) {
|
|
let seen: i32 = 0;
|
|
let k: i32 = 0;
|
|
for (k < added) {
|
|
let kk: i32 = nvers - added + k;
|
|
let existing: str;
|
|
existing.ptr = vernameptrarr[kk];
|
|
existing.len = drdi32(vernamelenbuf.ptr, (kk: u64) * 4u64);
|
|
if (streqd(existing, vname)) {
|
|
seen = 1; k = added;
|
|
} else { k += 1; };
|
|
};
|
|
if (seen == 0) {
|
|
dwr32(verlibidxbuf.ptr, (nvers: u64) * 4u64, nvlibs: u32);
|
|
vernameptrarr[nvers] = vname.ptr;
|
|
dwri32(vernamelenbuf.ptr, (nvers: u64) * 4u64, vname.len);
|
|
nvers += 1;
|
|
added += 1;
|
|
};
|
|
};
|
|
};
|
|
jj += 1;
|
|
};
|
|
dwr32(vlibcountbuf.ptr, (nvlibs: u64) * 4u64, added: u32);
|
|
nvlibs += 1;
|
|
};
|
|
si += 1;
|
|
};
|
|
|
|
// Assign vna_other indices starting at 2, walking vlib then per-version.
|
|
let nextvna: u16 = 2u16;
|
|
let vi: i32 = 0;
|
|
for (vi < nvlibs) {
|
|
let first: i32 = drdi32(vlibfirstbuf.ptr, (vi: u64) * 4u64);
|
|
let cnt: i32 = drdi32(vlibcountbuf.ptr, (vi: u64) * 4u64);
|
|
let k: i32 = 0;
|
|
for (k < cnt) {
|
|
dwr16(vervnaother.ptr, ((first + k): u64) * 2u64, nextvna);
|
|
nextvna += 1u16;
|
|
k += 1;
|
|
};
|
|
vi += 1;
|
|
};
|
|
|
|
// ---- compute dynstr size ----
|
|
let dynstrsz: u64 = 1u64; // leading NUL
|
|
let pi: i32 = 0;
|
|
for (pi < nsos) {
|
|
let so4: *lso = sosused[pi];
|
|
dynstrsz += so4.soname.len: u64;
|
|
dynstrsz += 1u64;
|
|
pi += 1;
|
|
};
|
|
pi = 0;
|
|
for (pi < n) {
|
|
let dsm4: *lsym = dynsyms[pi];
|
|
dynstrsz += dsm4.name.len: u64;
|
|
dynstrsz += 1u64;
|
|
pi += 1;
|
|
};
|
|
pi = 0;
|
|
for (pi < nvers) {
|
|
let nmlen: i32 = drdi32(vernamelenbuf.ptr, (pi: u64) * 4u64);
|
|
dynstrsz += nmlen: u64;
|
|
dynstrsz += 1u64;
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- fill dynstr ----
|
|
let dynstr: []u8 = alloc([], dynstrsz)!;
|
|
let dynstrpos: u64 = 1u64; // past leading NUL
|
|
|
|
let sonamestr: []u8 = alloc([], (nsos: u64) * 4u64)!;
|
|
pi = 0;
|
|
for (pi < nsos) {
|
|
dwr32(sonamestr.ptr, (pi: u64) * 4u64, dynstrpos: u32);
|
|
let so2: *lso = sosused[pi];
|
|
let snm: str = so2.soname;
|
|
dbcopy(dynstr.ptr, dynstrpos, snm.ptr, snm.len: u64);
|
|
dynstrpos += snm.len: u64;
|
|
dynstr[dynstrpos] = 0u8;
|
|
dynstrpos += 1u64;
|
|
pi += 1;
|
|
};
|
|
let symnamestr: []u8 = alloc([], nu * 4u64)!;
|
|
pi = 0;
|
|
for (pi < n) {
|
|
dwr32(symnamestr.ptr, (pi: u64) * 4u64, dynstrpos: u32);
|
|
let dsm: *lsym = dynsyms[pi];
|
|
let snm: str = dsm.name;
|
|
dbcopy(dynstr.ptr, dynstrpos, snm.ptr, snm.len: u64);
|
|
dynstrpos += snm.len: u64;
|
|
dynstr[dynstrpos] = 0u8;
|
|
dynstrpos += 1u64;
|
|
pi += 1;
|
|
};
|
|
pi = 0;
|
|
for (pi < nvers) {
|
|
dwr32(verdynstroff.ptr, (pi: u64) * 4u64, dynstrpos: u32);
|
|
let nmp: *u8 = vernameptrarr[pi];
|
|
let nmlen: i32 = drdi32(vernamelenbuf.ptr, (pi: u64) * 4u64);
|
|
dbcopy(dynstr.ptr, dynstrpos, nmp, nmlen: u64);
|
|
dynstrpos += nmlen: u64;
|
|
dynstr[dynstrpos] = 0u8;
|
|
dynstrpos += 1u64;
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- per-dyn-sym versym index ----
|
|
let versymfor: []u8 = alloc([], nu * 2u64)!;
|
|
pi = 0;
|
|
for (pi < n) {
|
|
let dsm3: *lsym = dynsyms[pi];
|
|
let dl3: *lso = dsm3.dynlib;
|
|
let nm3: str = dsm3.name;
|
|
let vname: str = soversion(dl3, nm3);
|
|
if (vname.len == 0) {
|
|
dwr16(versymfor.ptr, (pi: u64) * 2u64, VER_NDX_GLOBAL_D);
|
|
} else {
|
|
let matched: i32 = 0;
|
|
let vk: i32 = 0;
|
|
for (vk < nvers) {
|
|
let vlx: i32 = drdi32(verlibidxbuf.ptr, (vk: u64) * 4u64);
|
|
let sosx: i32 = drdi32(vlibsosidxbuf.ptr, (vlx: u64) * 4u64);
|
|
if (sosused[sosx] == dl3) {
|
|
let exi: str;
|
|
exi.ptr = vernameptrarr[vk];
|
|
exi.len = drdi32(vernamelenbuf.ptr, (vk: u64) * 4u64);
|
|
if (streqd(exi, vname)) {
|
|
let other: u16 = drdu16(vervnaother.ptr, (vk: u64) * 2u64);
|
|
dwr16(versymfor.ptr, (pi: u64) * 2u64, other);
|
|
matched = 1;
|
|
vk = nvers;
|
|
} else { vk += 1; };
|
|
} else { vk += 1; };
|
|
};
|
|
if (matched == 0) {
|
|
dwr16(versymfor.ptr, (pi: u64) * 2u64, VER_NDX_GLOBAL_D);
|
|
};
|
|
};
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- compute byte sizes ----
|
|
let ehdrsz: u64 = 64u64;
|
|
let nphdrs: u64 = 4u64;
|
|
let phdrsz: u64 = nphdrs * 56u64;
|
|
let interpsz: u64 = (INTERP.len: u64) + 1u64;
|
|
|
|
let nsymstotal: u64 = 1u64 + nu;
|
|
let dynsymsz: u64 = nsymstotal * 24u64;
|
|
|
|
let nbuckets: u32 = 1u32;
|
|
let nchain: u32 = nsymstotal: u32;
|
|
let hashsz: u64 = (2u64 + (nbuckets: u64) + (nchain: u64)) * 4u64;
|
|
|
|
let relapltsz: u64 = nu * 24u64;
|
|
let pltsz: u64 = nu * PLT_STUB_BYTES_D;
|
|
let gotpltsz: u64 = (3u64 + nu) * 8u64;
|
|
let versymsz: u64 = nsymstotal * 2u64;
|
|
|
|
let verneedsz: u64 = 0u64;
|
|
let vli: i32 = 0;
|
|
for (vli < nvlibs) {
|
|
let cnt: i32 = drdi32(vlibcountbuf.ptr, (vli: u64) * 4u64);
|
|
verneedsz += 16u64 + 16u64 * (cnt: u64);
|
|
vli += 1;
|
|
};
|
|
|
|
let withver: i32 = 0;
|
|
if (nvlibs > 0) { withver = 1; };
|
|
let extra: u64 = 0u64;
|
|
if (withver != 0) { extra = 3u64; };
|
|
let ndyn: u64 = (nsos: u64) + 11u64 + extra;
|
|
let dynamicsz: u64 = ndyn * 16u64;
|
|
|
|
// ---- compute file offsets ----
|
|
let off: u64 = ehdrsz + phdrsz;
|
|
let interpoff: u64 = off; off += interpsz;
|
|
off = alignup(off, 8u64);
|
|
let dynstroff: u64 = off; off += dynstrsz;
|
|
off = alignup(off, 8u64);
|
|
let dynsymoff: u64 = off; off += dynsymsz;
|
|
let hashoff: u64 = off; off += hashsz;
|
|
off = alignup(off, 2u64);
|
|
let versymoff: u64 = off; off += versymsz;
|
|
off = alignup(off, 4u64);
|
|
let verneedoff: u64 = off; off += verneedsz;
|
|
off = alignup(off, 8u64);
|
|
let relapltoff: u64 = off; off += relapltsz;
|
|
|
|
let textoff: u64 = alignup(off, PAGE);
|
|
let pltoff: u64 = textoff + l.textlen;
|
|
let rxend: u64 = pltoff + pltsz;
|
|
|
|
let gotpltoff: u64 = alignup(rxend, PAGE);
|
|
let dynamicoff: u64 = gotpltoff + gotpltsz;
|
|
let dataoff: u64 = dynamicoff + dynamicsz;
|
|
let fileend: u64 = dataoff + l.datalen;
|
|
|
|
let interpva: u64 = base + interpoff;
|
|
let dynstrva: u64 = base + dynstroff;
|
|
let dynsymva: u64 = base + dynsymoff;
|
|
let hashva: u64 = base + hashoff;
|
|
let versymva: u64 = base + versymoff;
|
|
let verneedva: u64 = base + verneedoff;
|
|
let relapltva: u64 = base + relapltoff;
|
|
let textva: u64 = base + textoff;
|
|
let pltva: u64 = base + pltoff;
|
|
let gotpltva: u64 = base + gotpltoff;
|
|
let dynamicva: u64 = base + dynamicoff;
|
|
let datava: u64 = base + dataoff;
|
|
|
|
// Apply relocations now that the dyn layout's textva/datava are
|
|
// pinned. main.ww defers this so each path uses its own VAs.
|
|
if (relocate(l, textva, datava) != 0) { return 1; };
|
|
|
|
// BSS optimisation — same trailing-zero scan as out.ww.
|
|
let bsslen: u64 = 0u64;
|
|
if (l.datalen > 0u64) {
|
|
for (bsslen < l.datalen) {
|
|
let b: u8 = l.data[l.datalen - 1u64 - bsslen];
|
|
if (b != 0u8) { break; };
|
|
bsslen += 1u64;
|
|
};
|
|
};
|
|
let datafilelen: u64 = l.datalen - bsslen;
|
|
let filedataend: u64 = dataoff + datafilelen;
|
|
|
|
// ---- build .dynsym ----
|
|
let dynsymbuf: []u8 = alloc([], dynsymsz)!;
|
|
pi = 0;
|
|
for (pi < n) {
|
|
let eoff: u64 = (1u64 + (pi: u64)) * 24u64;
|
|
dwr32(dynsymbuf.ptr, eoff + 0u64, drdu32(symnamestr.ptr, (pi: u64) * 4u64));
|
|
dwr8(dynsymbuf.ptr, eoff + 4u64, (STB_GLOBAL_D << 4u8) | (STT_FUNC_D & 15u8));
|
|
dwr8(dynsymbuf.ptr, eoff + 5u64, 0u8);
|
|
dwr16(dynsymbuf.ptr, eoff + 6u64, 0u16);
|
|
dwr64(dynsymbuf.ptr, eoff + 8u64, 0u64);
|
|
dwr64(dynsymbuf.ptr, eoff + 16u64, 0u64);
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- build .hash (SysV, 1 bucket) ----
|
|
let hashbuf: []u8 = alloc([], hashsz)!;
|
|
dwr32(hashbuf.ptr, 0u64, nbuckets);
|
|
dwr32(hashbuf.ptr, 4u64, nchain);
|
|
let bucket0: u32 = 0u32;
|
|
if (nsymstotal > 1u64) { bucket0 = 1u32; };
|
|
dwr32(hashbuf.ptr, 8u64, bucket0);
|
|
let ci: u64 = 1u64;
|
|
for (ci < nsymstotal) {
|
|
let nxt: u32 = 0u32;
|
|
if (ci + 1u64 < nsymstotal) { nxt = (ci + 1u64): u32; };
|
|
dwr32(hashbuf.ptr, 8u64 + (nbuckets: u64) * 4u64 + ci * 4u64, nxt);
|
|
ci += 1u64;
|
|
};
|
|
|
|
// ---- build .rela.plt ----
|
|
let relapltbuf: []u8 = alloc([], relapltsz)!;
|
|
pi = 0;
|
|
for (pi < n) {
|
|
let roff: u64 = (pi: u64) * 24u64;
|
|
dwr64(relapltbuf.ptr, roff + 0u64, gotpltva + (3u64 + (pi: u64)) * 8u64);
|
|
let info: u64 = ((1u64 + (pi: u64)) << 32u64) | (R_X86_64_JUMP_SLOT_D: u64);
|
|
dwr64(relapltbuf.ptr, roff + 8u64, info);
|
|
dwri64(relapltbuf.ptr, roff + 16u64, 0i64);
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- build .gnu.version (u16 per dynsym entry) ----
|
|
let versymbuf: []u8 = alloc([], versymsz)!;
|
|
dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D);
|
|
pi = 0;
|
|
for (pi < n) {
|
|
dwr16(versymbuf.ptr, 2u64 + (pi: u64) * 2u64, drdu16(versymfor.ptr, (pi: u64) * 2u64));
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- build .gnu.version_r ----
|
|
let verneedbuf: []u8 = alloc([], verneedsz)!;
|
|
if (verneedsz > 0u64) {
|
|
let vnoff: u64 = 0u64;
|
|
vli = 0;
|
|
for (vli < nvlibs) {
|
|
let sosidx: i32 = drdi32(vlibsosidxbuf.ptr, (vli: u64) * 4u64);
|
|
let first: i32 = drdi32(vlibfirstbuf.ptr, (vli: u64) * 4u64);
|
|
let cnt: i32 = drdi32(vlibcountbuf.ptr, (vli: u64) * 4u64);
|
|
let vnstart: u64 = vnoff;
|
|
dwr16(verneedbuf.ptr, vnoff + 0u64, 1u16);
|
|
dwr16(verneedbuf.ptr, vnoff + 2u64, cnt: u16);
|
|
dwr32(verneedbuf.ptr, vnoff + 4u64, drdu32(sonamestr.ptr, (sosidx: u64) * 4u64));
|
|
dwr32(verneedbuf.ptr, vnoff + 8u64, 16u32);
|
|
vnoff += 16u64;
|
|
let k: i32 = 0;
|
|
for (k < cnt) {
|
|
let vk: i32 = first + k;
|
|
let nm: str;
|
|
nm.ptr = vernameptrarr[vk];
|
|
nm.len = drdi32(vernamelenbuf.ptr, (vk: u64) * 4u64);
|
|
let h: u32 = elfhash(nm);
|
|
dwr32(verneedbuf.ptr, vnoff + 0u64, h);
|
|
dwr16(verneedbuf.ptr, vnoff + 4u64, 0u16);
|
|
dwr16(verneedbuf.ptr, vnoff + 6u64, drdu16(vervnaother.ptr, (vk: u64) * 2u64));
|
|
dwr32(verneedbuf.ptr, vnoff + 8u64, drdu32(verdynstroff.ptr, (vk: u64) * 4u64));
|
|
let nxt: u32 = 0u32;
|
|
if (k + 1 < cnt) { nxt = 16u32; };
|
|
dwr32(verneedbuf.ptr, vnoff + 12u64, nxt);
|
|
vnoff += 16u64;
|
|
k += 1;
|
|
};
|
|
let vnnxt: u32 = 0u32;
|
|
if (vli + 1 < nvlibs) { vnnxt = (vnoff - vnstart): u32; };
|
|
dwr32(verneedbuf.ptr, vnstart + 12u64, vnnxt);
|
|
vli += 1;
|
|
};
|
|
};
|
|
|
|
// ---- build .plt ----
|
|
let pltbuf: []u8 = alloc([], pltsz)!;
|
|
pi = 0;
|
|
for (pi < n) {
|
|
let poff: u64 = (pi: u64) * PLT_STUB_BYTES_D;
|
|
let stubva: u64 = pltva + poff;
|
|
let nextip: u64 = stubva + 6u64;
|
|
let slotva: u64 = gotpltva + (3u64 + (pi: u64)) * 8u64;
|
|
let disp: i64 = (slotva: i64) - (nextip: i64);
|
|
dwr8(pltbuf.ptr, poff + 0u64, 255u8);
|
|
dwr8(pltbuf.ptr, poff + 1u64, 37u8);
|
|
dwr32(pltbuf.ptr, poff + 2u64, (disp: i32): u32);
|
|
pi += 1;
|
|
};
|
|
|
|
// ---- build .got.plt ----
|
|
let gotpltbuf: []u8 = alloc([], gotpltsz)!;
|
|
dwr64(gotpltbuf.ptr, 0u64, dynamicva);
|
|
|
|
// ---- build .dynamic ----
|
|
let dynamicbuf: []u8 = alloc([], dynamicsz)!;
|
|
let dk: u64 = 0u64;
|
|
pi = 0;
|
|
for (pi < nsos) {
|
|
dwri64(dynamicbuf.ptr, dk * 16u64 + 0u64, DT_NEEDED);
|
|
dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, drdu32(sonamestr.ptr, (pi: u64) * 4u64): u64);
|
|
dk += 1u64;
|
|
pi += 1;
|
|
};
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_HASH); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, hashva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_STRTAB); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, dynstrva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_SYMTAB); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, dynsymva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_STRSZ); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, dynstrsz); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_SYMENT); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, 24u64); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_PLTGOT); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, gotpltva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_PLTRELSZ); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, relapltsz); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_PLTREL); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, DT_RELA: u64);dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_JMPREL); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, relapltva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_BIND_NOW); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, 0u64); dk += 1u64;
|
|
if (withver != 0) {
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_VERSYM); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, versymva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_VERNEED); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, verneedva); dk += 1u64;
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_VERNEEDNUM); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, nvlibs: u64);dk += 1u64;
|
|
};
|
|
dwri64(dynamicbuf.ptr, dk * 16u64, DT_NULL); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, 0u64); dk += 1u64;
|
|
if (dk != ndyn) {
|
|
os.write(2, "w6l: dynamic entry count mismatch\n".ptr, 33u64);
|
|
return 1;
|
|
};
|
|
|
|
// ---- patch .text relocs targeting dynamic syms ----
|
|
let r: *lrel = l.rels;
|
|
for (r != nil) {
|
|
if (r.sym != nil) {
|
|
let rsym: *lsym = r.sym;
|
|
if (rsym.isdyn != 0) {
|
|
if (r.kind != R_X86_64_PC32_D) {
|
|
if (r.kind != R_X86_64_PLT32_D) {
|
|
os.write(2, "w6l: dynamic reloc kind unsupported\n".ptr, 35u64);
|
|
return 1;
|
|
};
|
|
};
|
|
let site: u64 = textva + r.off;
|
|
let stub: u64 = pltva + (rsym.pltidx: u64) * PLT_STUB_BYTES_D;
|
|
let disp: i64 = (stub: i64) - (site: i64) + r.addend;
|
|
dwr32(l.text, r.off, (disp: i32): u32);
|
|
};
|
|
};
|
|
r = r.rnext;
|
|
};
|
|
|
|
// ---- assemble file buffer ----
|
|
let filebuf: []u8 = alloc([], fileend)!;
|
|
filebuf.len = fileend: i32;
|
|
|
|
// Ehdr
|
|
dwr8(filebuf.ptr, 0u64, 127u8);
|
|
dwr8(filebuf.ptr, 1u64, 69u8);
|
|
dwr8(filebuf.ptr, 2u64, 76u8);
|
|
dwr8(filebuf.ptr, 3u64, 70u8);
|
|
dwr8(filebuf.ptr, 4u64, ELFCLASS64_D);
|
|
dwr8(filebuf.ptr, 5u64, ELFDATA2LSB_D);
|
|
dwr8(filebuf.ptr, 6u64, EV_CURRENT_D: u8);
|
|
dwr16(filebuf.ptr, 16u64, ET_EXEC_D);
|
|
dwr16(filebuf.ptr, 18u64, EM_X86_64_D);
|
|
dwr32(filebuf.ptr, 20u64, EV_CURRENT_D);
|
|
dwr64(filebuf.ptr, 24u64, entry);
|
|
dwr64(filebuf.ptr, 32u64, ehdrsz);
|
|
dwr64(filebuf.ptr, 40u64, 0u64);
|
|
dwr32(filebuf.ptr, 48u64, 0u32);
|
|
dwr16(filebuf.ptr, 52u64, ehdrsz: u16);
|
|
dwr16(filebuf.ptr, 54u64, 56u16);
|
|
dwr16(filebuf.ptr, 56u64, nphdrs: u16);
|
|
dwr16(filebuf.ptr, 58u64, 0u16);
|
|
dwr16(filebuf.ptr, 60u64, 0u16);
|
|
dwr16(filebuf.ptr, 62u64, 0u16);
|
|
|
|
// Phdrs at offset 64.
|
|
let p0: u64 = 64u64;
|
|
dwr32(filebuf.ptr, p0 + 0u64, PT_LOAD_D);
|
|
dwr32(filebuf.ptr, p0 + 4u64, PF_R_D | PF_X_D);
|
|
dwr64(filebuf.ptr, p0 + 8u64, 0u64);
|
|
dwr64(filebuf.ptr, p0 + 16u64, base);
|
|
dwr64(filebuf.ptr, p0 + 24u64, base);
|
|
dwr64(filebuf.ptr, p0 + 32u64, rxend);
|
|
dwr64(filebuf.ptr, p0 + 40u64, rxend);
|
|
dwr64(filebuf.ptr, p0 + 48u64, PAGE);
|
|
|
|
let p1: u64 = 64u64 + 56u64;
|
|
dwr32(filebuf.ptr, p1 + 0u64, PT_LOAD_D);
|
|
dwr32(filebuf.ptr, p1 + 4u64, PF_R_D | PF_W_D);
|
|
dwr64(filebuf.ptr, p1 + 8u64, gotpltoff);
|
|
dwr64(filebuf.ptr, p1 + 16u64, gotpltva);
|
|
dwr64(filebuf.ptr, p1 + 24u64, gotpltva);
|
|
// filesz trims the .data trailing zeros (BSS); memsz covers
|
|
// .got.plt + .dynamic + the full .data so the loader zero-fills.
|
|
dwr64(filebuf.ptr, p1 + 32u64, filedataend - gotpltoff);
|
|
dwr64(filebuf.ptr, p1 + 40u64, fileend - gotpltoff);
|
|
dwr64(filebuf.ptr, p1 + 48u64, PAGE);
|
|
|
|
let p2: u64 = 64u64 + 112u64;
|
|
dwr32(filebuf.ptr, p2 + 0u64, PT_INTERP_D);
|
|
dwr32(filebuf.ptr, p2 + 4u64, PF_R_D);
|
|
dwr64(filebuf.ptr, p2 + 8u64, interpoff);
|
|
dwr64(filebuf.ptr, p2 + 16u64, interpva);
|
|
dwr64(filebuf.ptr, p2 + 24u64, interpva);
|
|
dwr64(filebuf.ptr, p2 + 32u64, interpsz);
|
|
dwr64(filebuf.ptr, p2 + 40u64, interpsz);
|
|
dwr64(filebuf.ptr, p2 + 48u64, 1u64);
|
|
|
|
let p3: u64 = 64u64 + 168u64;
|
|
dwr32(filebuf.ptr, p3 + 0u64, PT_DYNAMIC_D);
|
|
dwr32(filebuf.ptr, p3 + 4u64, PF_R_D | PF_W_D);
|
|
dwr64(filebuf.ptr, p3 + 8u64, dynamicoff);
|
|
dwr64(filebuf.ptr, p3 + 16u64, dynamicva);
|
|
dwr64(filebuf.ptr, p3 + 24u64, dynamicva);
|
|
dwr64(filebuf.ptr, p3 + 32u64, dynamicsz);
|
|
dwr64(filebuf.ptr, p3 + 40u64, dynamicsz);
|
|
dwr64(filebuf.ptr, p3 + 48u64, 8u64);
|
|
|
|
// Sections.
|
|
dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64);
|
|
dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8);
|
|
dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz);
|
|
dbcopy(filebuf.ptr, dynsymoff, dynsymbuf.ptr, dynsymsz);
|
|
dbcopy(filebuf.ptr, hashoff, hashbuf.ptr, hashsz);
|
|
dbcopy(filebuf.ptr, versymoff, versymbuf.ptr, versymsz);
|
|
if (verneedsz > 0u64) {
|
|
dbcopy(filebuf.ptr, verneedoff, verneedbuf.ptr, verneedsz);
|
|
};
|
|
dbcopy(filebuf.ptr, relapltoff, relapltbuf.ptr, relapltsz);
|
|
if (l.textlen > 0u64) {
|
|
dbcopy(filebuf.ptr, textoff, l.text, l.textlen);
|
|
};
|
|
dbcopy(filebuf.ptr, pltoff, pltbuf.ptr, pltsz);
|
|
dbcopy(filebuf.ptr, gotpltoff, gotpltbuf.ptr, gotpltsz);
|
|
dbcopy(filebuf.ptr, dynamicoff, dynamicbuf.ptr, dynamicsz);
|
|
if (datafilelen > 0u64) {
|
|
dbcopy(filebuf.ptr, dataoff, l.data, datafilelen);
|
|
};
|
|
|
|
let wr: (i64 | os.oserror) = os.writeall(fd, filebuf.ptr, filedataend);
|
|
match (wr) {
|
|
case let v: i64 => { if (v != filedataend: i64) { return 1; }; };
|
|
case let e: os.oserror => return 1;
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c.
|
|
//
|
|
// Emit a static ELF64 executable. File layout (per the C original):
|
|
// [0..64) Ehdr
|
|
// [64..120) Phdr (one PT_LOAD)
|
|
// [120..0x1000) zero pad
|
|
// [0x1000..) .text bytes
|
|
// Single PT_LOAD covers the whole file, R+X. No interpreter, no .bss.
|
|
|
|
package w6l;
|
|
|
|
import os;
|
|
import rt;
|
|
import sym;
|
|
import dynout;
|
|
|
|
def ET_EXEC: u16 = 2u16;
|
|
def EM_X86_64_W: u16 = 62u16;
|
|
def EV_CURRENT: u32 = 1u32;
|
|
def ELFCLASS64: u8 = 2u8;
|
|
def ELFDATA2LSB: u8 = 1u8;
|
|
def PT_LOAD: u32 = 1u32;
|
|
def PF_X: u32 = 1u32;
|
|
def PF_W: u32 = 2u32;
|
|
def PF_R: u32 = 4u32;
|
|
|
|
def TEXT_OFF: u64 = 4096u64; // 0x1000
|
|
def PAGE_SZ: u64 = 4096u64;
|
|
|
|
// ---- little-endian byte writers ----------------------------------------
|
|
|
|
fn wru16(buf: *u8, off: u64, v: u16) void = {
|
|
buf[off] = (v & 255u16): u8;
|
|
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
|
};
|
|
|
|
fn wru32(buf: *u8, off: u64, v: u32) void = {
|
|
buf[off] = (v & 255u32): u8;
|
|
buf[off + 1u64] = ((v >> 8u32) & 255u32): u8;
|
|
buf[off + 2u64] = ((v >> 16u32) & 255u32): u8;
|
|
buf[off + 3u64] = ((v >> 24u32) & 255u32): u8;
|
|
};
|
|
|
|
fn wru64(buf: *u8, off: u64, v: u64) void = {
|
|
wru32(buf, off, (v & 4294967295u64): u32);
|
|
wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
|
};
|
|
|
|
// ---- emit ---------------------------------------------------------------
|
|
|
|
export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
|
// Dispatch: any loaded shared object plus any dynamic ref means
|
|
// we owe the loader a real PT_INTERP/PT_DYNAMIC binary.
|
|
if (l.sos != nil) {
|
|
if (l.dynn > 0) {
|
|
return emitdynelf(l, fd, base, entry);
|
|
};
|
|
};
|
|
|
|
let hasdata: bool = l.datalen > 0u64;
|
|
let rxend: u64 = TEXT_OFF + l.textlen;
|
|
// .data lands at the next page boundary so the loader can give
|
|
// it fresh R+W permissions without overlapping the R+X mapping.
|
|
let dataoff: u64 = 0u64;
|
|
let datava: u64 = 0u64;
|
|
if (hasdata) {
|
|
dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64);
|
|
datava = base + dataoff;
|
|
};
|
|
|
|
// Apply relocations now that the layout's textva/datava are
|
|
// known. Deferred from main.ww so the dyn path uses its own
|
|
// datava.
|
|
if (relocate(l, base + TEXT_OFF, datava) != 0) { return -1; };
|
|
|
|
// BSS optimisation: trailing zero bytes in .data can be left
|
|
// out of the file. The loader zero-fills the gap between
|
|
// p_filesz and p_memsz. Scan after l_relocate has applied any
|
|
// DATAR patches — anything still zero at the tail genuinely is
|
|
// zero-init. Matches cmd/w6l/out.c byte-for-byte.
|
|
let bsslen: u64 = 0u64;
|
|
if (hasdata) {
|
|
for (bsslen < l.datalen) {
|
|
let b: u8 = l.data[l.datalen - 1u64 - bsslen];
|
|
if (b != 0u8) { break; };
|
|
bsslen += 1u64;
|
|
};
|
|
};
|
|
let datafilelen: u64 = l.datalen - bsslen;
|
|
|
|
// One contiguous header buffer covering [0..0x1000), then .text.
|
|
let hdr: []u8 = alloc([], TEXT_OFF)!;
|
|
hdr.len = TEXT_OFF: i32;
|
|
|
|
// --- Ehdr (64 bytes) ---
|
|
hdr[0u64] = 127u8; // 0x7f
|
|
hdr[1u64] = 'E';
|
|
hdr[2u64] = 'L';
|
|
hdr[3u64] = 'F';
|
|
hdr[4u64] = ELFCLASS64;
|
|
hdr[5u64] = ELFDATA2LSB;
|
|
hdr[6u64] = EV_CURRENT: u8;
|
|
wru16(hdr.ptr, 16u64, ET_EXEC); // e_type
|
|
wru16(hdr.ptr, 18u64, EM_X86_64_W); // e_machine
|
|
wru32(hdr.ptr, 20u64, EV_CURRENT); // e_version
|
|
wru64(hdr.ptr, 24u64, entry); // e_entry
|
|
wru64(hdr.ptr, 32u64, 64u64); // e_phoff = sizeof(Ehdr)
|
|
wru64(hdr.ptr, 40u64, 0u64); // e_shoff
|
|
wru32(hdr.ptr, 48u64, 0u32); // e_flags
|
|
wru16(hdr.ptr, 52u64, 64u16); // e_ehsize
|
|
wru16(hdr.ptr, 54u64, 56u16); // e_phentsize
|
|
if (hasdata) { wru16(hdr.ptr, 56u64, 2u16); }
|
|
else { wru16(hdr.ptr, 56u64, 1u16); };
|
|
wru16(hdr.ptr, 58u64, 0u16); // e_shentsize
|
|
wru16(hdr.ptr, 60u64, 0u16); // e_shnum
|
|
wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx
|
|
|
|
// --- Phdr #1 (R+X) at offset 64 ---
|
|
wru32(hdr.ptr, 64u64, PT_LOAD);
|
|
wru32(hdr.ptr, 68u64, PF_R | PF_X);
|
|
wru64(hdr.ptr, 72u64, 0u64); // p_offset
|
|
wru64(hdr.ptr, 80u64, base); // p_vaddr
|
|
wru64(hdr.ptr, 88u64, base); // p_paddr
|
|
wru64(hdr.ptr, 96u64, rxend); // p_filesz
|
|
wru64(hdr.ptr, 104u64, rxend); // p_memsz
|
|
wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align
|
|
|
|
if (hasdata) {
|
|
// --- Phdr #2 (R+W) at offset 64+56=120 ---
|
|
wru32(hdr.ptr, 120u64, PT_LOAD);
|
|
wru32(hdr.ptr, 124u64, PF_R | PF_W);
|
|
wru64(hdr.ptr, 128u64, dataoff); // p_offset
|
|
wru64(hdr.ptr, 136u64, base + dataoff); // p_vaddr
|
|
wru64(hdr.ptr, 144u64, base + dataoff); // p_paddr
|
|
wru64(hdr.ptr, 152u64, datafilelen); // p_filesz
|
|
wru64(hdr.ptr, 160u64, l.datalen); // p_memsz
|
|
wru64(hdr.ptr, 168u64, PAGE_SZ); // p_align
|
|
};
|
|
|
|
// Write [0..0x1000) then .text.
|
|
let r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF);
|
|
let n1: i64 = 0i64;
|
|
match (r1) {
|
|
case let v: i64 => n1 = v;
|
|
case let e: os.oserror => return -1;
|
|
};
|
|
if (n1 != TEXT_OFF: i64) { return -1; };
|
|
if (l.textlen > 0u64) {
|
|
let r2: (i64 | os.oserror) = os.writeall(fd, l.text, l.textlen);
|
|
let n2: i64 = 0i64;
|
|
match (r2) {
|
|
case let v: i64 => n2 = v;
|
|
case let e: os.oserror => return -1;
|
|
};
|
|
if (n2 != l.textlen: i64) { return -1; };
|
|
};
|
|
if (hasdata && datafilelen > 0u64) {
|
|
// Pad to the page-aligned data offset, then write only
|
|
// the non-zero prefix of .data. The rest is BSS — the
|
|
// loader zero-fills from p_filesz to p_memsz.
|
|
let here: u64 = TEXT_OFF + l.textlen;
|
|
let zero: u8 = 0u8;
|
|
for (here < dataoff) {
|
|
let r3: (i64 | os.oserror) = os.writeall(fd, &zero, 1u64);
|
|
match (r3) {
|
|
case let v: i64 => { };
|
|
case let e: os.oserror => return -1;
|
|
};
|
|
here += 1u64;
|
|
};
|
|
let r4: (i64 | os.oserror) = os.writeall(fd, l.data, datafilelen);
|
|
let n4: i64 = 0i64;
|
|
match (r4) {
|
|
case let v: i64 => n4 = v;
|
|
case let e: os.oserror => return -1;
|
|
};
|
|
if (n4 != datafilelen: i64) { return -1; };
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c.
|
|
//
|
|
// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar`
|
|
// archives, and shared objects (ET_DYN). Resolves symbols, applies
|
|
// relocations, writes a static or dynamic-linked ELF executable.
|
|
//
|
|
// w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
|
|
|
package main;
|
|
|
|
import os;
|
|
import rt;
|
|
import sym;
|
|
import obj;
|
|
import dyn;
|
|
import pass;
|
|
import out;
|
|
|
|
def BASE: u64 = 4194304u64; // 0x400000
|
|
def CODE_VA_OFF: u64 = 4096u64; // .text starts at base + 0x1000
|
|
|
|
fn mklnk() *lnk = {
|
|
let l: *lnk = alloc(lnk { })!;
|
|
return l;
|
|
};
|
|
|
|
// `cstreq` lives in obj.ww — same bundle, single definition.
|
|
|
|
// `cstrlen` lives in obj.ww — same bundle, single definition.
|
|
|
|
// Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total
|
|
// length excluding NUL. dst must be large enough.
|
|
fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = {
|
|
let i: u64 = 0u64;
|
|
let dn: u64 = cstrlen(dir);
|
|
let nn: u64 = cstrlen(name);
|
|
let k: u64 = 0u64;
|
|
for (k < dn) { dst[i] = dir[k]; i += 1u64; k += 1u64; };
|
|
dst[i] = '/';
|
|
i += 1u64;
|
|
dst[i] = 'l';
|
|
i += 1u64;
|
|
dst[i] = 'i';
|
|
i += 1u64;
|
|
dst[i] = 'b';
|
|
i += 1u64;
|
|
k = 0u64;
|
|
for (k < nn) { dst[i] = name[k]; i += 1u64; k += 1u64; };
|
|
k = 0u64;
|
|
for (k < ext.len: u64) {
|
|
let li: i32 = k: i32;
|
|
dst[i] = ext[li];
|
|
i += 1u64;
|
|
k += 1u64;
|
|
};
|
|
dst[i] = 0u8;
|
|
return i;
|
|
};
|
|
|
|
// Append decimal n to dst at offset i. Returns new offset.
|
|
fn appenddec(dst: *u8, i: u64, n: u64) u64 = {
|
|
if (n == 0u64) {
|
|
dst[i] = 48u8;
|
|
return i + 1u64;
|
|
};
|
|
let buf: [16]u8;
|
|
let k: u64 = 0u64;
|
|
let v: u64 = n;
|
|
for (v > 0u64) {
|
|
buf[k] = (v % 10u64): u8 + 48u8;
|
|
v = v / 10u64;
|
|
k += 1u64;
|
|
};
|
|
let oi: u64 = i;
|
|
for (k > 0u64) {
|
|
k -= 1u64;
|
|
dst[oi] = buf[k];
|
|
oi += 1u64;
|
|
};
|
|
return oi;
|
|
};
|
|
|
|
fn buildpathv(dst: *u8, dir: *u8, name: *u8, v: u64) u64 = {
|
|
let i: u64 = 0u64;
|
|
let dn: u64 = cstrlen(dir);
|
|
let nn: u64 = cstrlen(name);
|
|
let k: u64 = 0u64;
|
|
for (k < dn) { dst[i] = dir[k]; i += 1u64; k += 1u64; };
|
|
dst[i] = '/'; i += 1u64;
|
|
dst[i] = 'l'; i += 1u64;
|
|
dst[i] = 'i'; i += 1u64;
|
|
dst[i] = 'b'; i += 1u64;
|
|
k = 0u64;
|
|
for (k < nn) { dst[i] = name[k]; i += 1u64; k += 1u64; };
|
|
dst[i] = '.'; i += 1u64; dst[i] = 's'; i += 1u64; dst[i] = 'o'; i += 1u64; dst[i] = '.'; i += 1u64;
|
|
i = appenddec(dst, i, v);
|
|
dst[i] = 0u8;
|
|
return i;
|
|
};
|
|
|
|
// islinkable: read first 8 bytes; require !<arch>\n or \x7fELF.
|
|
fn islinkable(path: *u8) bool = {
|
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
|
if (fd < 0) { return false; };
|
|
let mp: [8]u8;
|
|
let n: i64 = os.read(fd, &mp[0], 8u64);
|
|
os.close(fd);
|
|
if (n < 4i64) { return false; };
|
|
// archive: "!<arch>\n"
|
|
if (n >= 8i64) {
|
|
if (mp[0u64] == '!') { if (mp[1u64] == '<') {
|
|
if (mp[2u64] == 'a') { if (mp[3u64] == 'r') {
|
|
if (mp[4u64] == 'c') { if (mp[5u64] == 'h') {
|
|
if (mp[6u64] == '>') { if (mp[7u64] == '\n') {
|
|
return true;
|
|
}; }; }; }; }; }; }; };
|
|
};
|
|
// ELF: "\x7fELF"
|
|
if (mp[0u64] == 127u8) {
|
|
if (mp[1u64] == 'E') {
|
|
if (mp[2u64] == 'L') {
|
|
if (mp[3u64] == 'F') { return true; };
|
|
};
|
|
};
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// Walk libdirs[0..n) trying lib<name>.so, then lib<name>.so.{0..8},
|
|
// then lib<name>.a. Return a heap-allocated NUL-terminated path on
|
|
// success, nil on miss.
|
|
fn resolvelib(name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = {
|
|
let bufp: []u8 = alloc([], 1024u64)!;
|
|
bufp.len = 1024;
|
|
let i: i32 = 0;
|
|
for (i < nlibdirs) {
|
|
let dir: *u8 = libdirs[i];
|
|
let _l1: u64 = buildpath(bufp.ptr, dir, name, ".so");
|
|
if (islinkable(bufp.ptr)) {
|
|
let pl: u64 = cstrlen(bufp.ptr);
|
|
let p: []u8 = alloc([], pl + 1u64)!;
|
|
let k: u64 = 0u64;
|
|
for (k <= pl) { p[k] = bufp[k]; k += 1u64; };
|
|
return p.ptr;
|
|
};
|
|
let v: u64 = 0u64;
|
|
for (v <= 8u64) {
|
|
let _l2: u64 = buildpathv(bufp.ptr, dir, name, v);
|
|
if (islinkable(bufp.ptr)) {
|
|
let pl2: u64 = cstrlen(bufp.ptr);
|
|
let p2: []u8 = alloc([], pl2 + 1u64)!;
|
|
let k2: u64 = 0u64;
|
|
for (k2 <= pl2) { p2[k2] = bufp[k2]; k2 += 1u64; };
|
|
return p2.ptr;
|
|
};
|
|
v += 1u64;
|
|
};
|
|
let _l3: u64 = buildpath(bufp.ptr, dir, name, ".a");
|
|
if (islinkable(bufp.ptr)) {
|
|
let pl3: u64 = cstrlen(bufp.ptr);
|
|
let p3: []u8 = alloc([], pl3 + 1u64)!;
|
|
let k3: u64 = 0u64;
|
|
for (k3 <= pl3) { p3[k3] = bufp[k3]; k3 += 1u64; };
|
|
return p3.ptr;
|
|
};
|
|
i += 1;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// Read first 20 bytes; return 1 for ET_DYN .so, 0 for ar/.o.
|
|
fn isso(path: *u8) i32 = {
|
|
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
|
|
if (fd < 0) { return 0; };
|
|
let mp: [20]u8;
|
|
let n: i64 = os.read(fd, &mp[0], 20u64);
|
|
os.close(fd);
|
|
if (n < 20i64) { return 0; };
|
|
if (mp[0u64] != 127u8) { return 0; };
|
|
if (mp[1u64] != 'E') { return 0; };
|
|
if (mp[2u64] != 'L') { return 0; };
|
|
if (mp[3u64] != 'F') { return 0; };
|
|
// e_type at offset 16, u16 little-endian
|
|
let t: u16 = (mp[16u64]: u16) | ((mp[17u64]: u16) << 8u16);
|
|
if (t == 3u16) { return 1; };
|
|
return 0;
|
|
};
|
|
|
|
export fn main(argc: i32, argv: **u8) i32 = {
|
|
let outpath: *u8 = nil;
|
|
let maxinputs: i32 = 64;
|
|
let inputs: []*u8 = alloc([], maxinputs: u64)!;
|
|
inputs.len = maxinputs;
|
|
let ninputs: i32 = 0;
|
|
let libdirs: []*u8 = alloc([], maxinputs: u64)!;
|
|
libdirs.len = maxinputs;
|
|
let nlibdirs: i32 = 0;
|
|
let lflags: []*u8 = alloc([], maxinputs: u64)!;
|
|
lflags.len = maxinputs;
|
|
let nlflags: i32 = 0;
|
|
|
|
let i: i32 = 1;
|
|
for (i < argc) {
|
|
let a: *u8 = argv[i];
|
|
if (cstreq(a, "-o")) {
|
|
i += 1;
|
|
if (i >= argc) {
|
|
let m: str = "w6l: -o requires argument\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
outpath = argv[i];
|
|
} else { if (cstreq(a, "-L")) {
|
|
i += 1;
|
|
if (i >= argc) {
|
|
let m: str = "w6l: -L requires argument\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
libdirs[nlibdirs] = argv[i];
|
|
nlibdirs += 1;
|
|
} else { if (cstreq(a, "-l")) {
|
|
i += 1;
|
|
if (i >= argc) {
|
|
let m: str = "w6l: -l requires argument\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
lflags[nlflags] = argv[i];
|
|
nlflags += 1;
|
|
} else { if (a[0u64] == '-') {
|
|
// -L<dir> joined form.
|
|
if (a[1u64] == 'L') {
|
|
if (a[2u64] != 0u8) {
|
|
libdirs[nlibdirs] = a + 2u64;
|
|
nlibdirs += 1;
|
|
} else {
|
|
let m: str = "w6l: bare -L\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
} else { if (a[1u64] == 'l') {
|
|
if (a[2u64] != 0u8) {
|
|
lflags[nlflags] = a + 2u64;
|
|
nlflags += 1;
|
|
} else {
|
|
let m: str = "w6l: bare -l\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
} else {
|
|
let m: str = "w6l: unknown flag\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};};
|
|
} else {
|
|
if (ninputs >= maxinputs) {
|
|
let m: str = "w6l: too many inputs\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
inputs[ninputs] = a;
|
|
ninputs += 1;
|
|
};};};};
|
|
i += 1;
|
|
};
|
|
|
|
if (outpath == nil) {
|
|
let m: str = "usage: w6l_ww -o exe [-L<dir>...] [-l<name>...] file1.o [file2.o...]\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
if (ninputs == 0) {
|
|
let m: str = "w6l: no inputs\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 2;
|
|
};
|
|
|
|
let l: *lnk = mklnk();
|
|
|
|
// Seed _start so libwwrt-style start.o is recognised as wanted.
|
|
intern(l, "_start");
|
|
|
|
// Load positional inputs first (preserving order).
|
|
let k: i32 = 0;
|
|
for (k < ninputs) {
|
|
if (load(l, inputs[k]) != 0) {
|
|
return 1;
|
|
};
|
|
k += 1;
|
|
};
|
|
|
|
// Then resolve -l flags and load each. Archives append; shared
|
|
// objects register their exports.
|
|
let lf: i32 = 0;
|
|
for (lf < nlflags) {
|
|
let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs);
|
|
if (p == nil) {
|
|
let m: str = "w6l: cannot find -l";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
let nm: *u8 = lflags[lf];
|
|
os.write(2, nm, cstrlen(nm));
|
|
let nl: str = "\n";
|
|
os.write(2, nl.ptr, nl.len: u64);
|
|
return 1;
|
|
};
|
|
if (isso(p) != 0) {
|
|
if (loadso(l, p) != 0) { return 1; };
|
|
} else {
|
|
if (load(l, p) != 0) { return 1; };
|
|
};
|
|
lf += 1;
|
|
};
|
|
|
|
if (resolve(l) != 0) { return 1; };
|
|
// Relocation is deferred to the emit functions — each path
|
|
// knows its own layout (textva, datava); the static and dyn
|
|
// paths place .data at different VAs.
|
|
|
|
let entrysym: *lsym = lookup(l, "_start");
|
|
if (entrysym == nil) { entrysym = lookup(l, "main"); }
|
|
else { if (entrysym.defined == 0) { entrysym = lookup(l, "main"); }; };
|
|
if (entrysym == nil) {
|
|
let m: str = "w6l: no _start or main symbol\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 1;
|
|
};
|
|
if (entrysym.defined == 0) {
|
|
let m: str = "w6l: no _start or main symbol\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 1;
|
|
};
|
|
|
|
let flags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC;
|
|
let fd: i32 = os.open(pathstr(outpath), flags, 493i32); // 0o755
|
|
if (fd < 0) {
|
|
let m: str = "w6l: cannot open output\n";
|
|
os.write(2, m.ptr, m.len: u64);
|
|
return 1;
|
|
};
|
|
|
|
let entryva: u64 = BASE + CODE_VA_OFF + entrysym.val;
|
|
let rc: i32 = emitelf(l, fd, BASE, entryva);
|
|
os.close(fd);
|
|
return rc;
|
|
};
|
|
|