mem.ww was retired in Phase-0; these two imports survived only because the driver silently skipped unresolvable imports. Provably dead: no mem module exists and both units build identically without them. Removal precedes the loud driver, which would otherwise fatal on them.
5069 lines
162 KiB
Plaintext
5069 lines
162 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;
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
// 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 = {
|
|
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 = {
|
|
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 = {
|
|
assert(delim.len > 0, "bytes.tokenize called with empty slice");
|
|
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 = {
|
|
assert(delim.len > 0, "bytes.rtokenize called with empty slice");
|
|
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;
|
|
};
|
|
|
|
// 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 = {
|
|
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 => { append(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);
|
|
append(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 = {
|
|
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 => { append(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);
|
|
append(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 => {
|
|
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 => {
|
|
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 append'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) {
|
|
append(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 = {
|
|
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) = {
|
|
assert(start <= end, "strings.bytesub: start is higher than end");
|
|
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) {
|
|
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) {
|
|
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));
|
|
};
|
|
|
|
// 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 => { append(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);
|
|
append(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 => { append(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);
|
|
append(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/w6a/opcodes.ww — types + constants shared across the
|
|
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
|
|
|
|
package w6a;
|
|
|
|
// ---- registers + operand kinds (from 6.out.h) -------------------------
|
|
// These must stay numerically aligned with the C enum so that ww-cgen
|
|
// output (which reads them via `D_AX(SB)` etc.) lands on the same
|
|
// integers when read by ww-w6a.
|
|
def D_NONE: i32 = 0;
|
|
|
|
def D_AX: i32 = 1;
|
|
def D_CX: i32 = 2;
|
|
def D_DX: i32 = 3;
|
|
def D_BX: i32 = 4;
|
|
def D_SP: i32 = 5;
|
|
def D_BP: i32 = 6;
|
|
def D_SI: i32 = 7;
|
|
def D_DI: i32 = 8;
|
|
def D_R8: i32 = 9;
|
|
def D_R9: i32 = 10;
|
|
def D_R10: i32 = 11;
|
|
def D_R11: i32 = 12;
|
|
def D_R12: i32 = 13;
|
|
def D_R13: i32 = 14;
|
|
def D_R14: i32 = 15;
|
|
def D_R15: i32 = 16;
|
|
|
|
def D_X0: i32 = 17;
|
|
def D_X1: i32 = 18;
|
|
def D_X2: i32 = 19;
|
|
def D_X3: i32 = 20;
|
|
def D_X4: i32 = 21;
|
|
def D_X5: i32 = 22;
|
|
def D_X6: i32 = 23;
|
|
def D_X7: i32 = 24;
|
|
def D_X8: i32 = 25;
|
|
def D_X9: i32 = 26;
|
|
def D_X10: i32 = 27;
|
|
def D_X11: i32 = 28;
|
|
def D_X12: i32 = 29;
|
|
def D_X13: i32 = 30;
|
|
def D_X14: i32 = 31;
|
|
def D_X15: i32 = 32;
|
|
|
|
def D_PSP: i32 = 33;
|
|
def D_PFP: i32 = 34;
|
|
def D_PSB: i32 = 35;
|
|
|
|
def D_CONST: i32 = 36;
|
|
def D_BRANCH: i32 = 37;
|
|
def D_EXTERN: i32 = 38;
|
|
def D_INDIR: i32 = 39;
|
|
|
|
// ---- opcodes ----------------------------------------------------------
|
|
def A_NOP: i32 = 0;
|
|
def A_TEXT: i32 = 1;
|
|
def A_DATA: i32 = 2;
|
|
def A_GLOBL: i32 = 3;
|
|
def A_END: i32 = 4;
|
|
|
|
def A_MOVQ: i32 = 5;
|
|
def A_MOVL: i32 = 6;
|
|
def A_MOVB: i32 = 7;
|
|
def A_MOVZBQ: i32 = 8;
|
|
def A_MOVSXD: i32 = 9;
|
|
def A_MOVW: i32 = 62;
|
|
def A_MOVZWQ: i32 = 63;
|
|
def A_MOVSWQ: i32 = 64;
|
|
def A_MOVSBQ: i32 = 65;
|
|
|
|
def A_MOVSD: i32 = 10;
|
|
def A_ADDSD: i32 = 11;
|
|
def A_SUBSD: i32 = 12;
|
|
def A_MULSD: i32 = 13;
|
|
def A_DIVSD: i32 = 14;
|
|
def A_UCOMISD: i32 = 15;
|
|
def A_CVTTSD2SI: i32 = 16;
|
|
def A_CVTSI2SD: i32 = 17;
|
|
|
|
def A_MOVSS: i32 = 18;
|
|
def A_ADDSS: i32 = 19;
|
|
def A_SUBSS: i32 = 20;
|
|
def A_MULSS: i32 = 21;
|
|
def A_DIVSS: i32 = 22;
|
|
def A_UCOMISS: i32 = 23;
|
|
def A_CVTTSS2SI: i32 = 24;
|
|
def A_CVTSI2SS: i32 = 25;
|
|
def A_CVTSD2SS: i32 = 26;
|
|
def A_CVTSS2SD: i32 = 27;
|
|
|
|
def A_ADDQ: i32 = 28;
|
|
def A_SUBQ: i32 = 29;
|
|
def A_IMULQ: i32 = 30;
|
|
def A_IDIVQ: i32 = 31;
|
|
def A_DIVQ: i32 = 32;
|
|
def A_NEGQ: i32 = 33;
|
|
def A_NOTQ: i32 = 34;
|
|
def A_ANDQ: i32 = 35;
|
|
def A_ORQ: i32 = 36;
|
|
def A_XORQ: i32 = 37;
|
|
def A_SHLQ: i32 = 38;
|
|
def A_SHRQ: i32 = 39;
|
|
def A_CMPQ: i32 = 40;
|
|
|
|
def A_PUSHQ: i32 = 41;
|
|
def A_POPQ: i32 = 42;
|
|
def A_LEAQ: i32 = 43;
|
|
|
|
def A_CALL: i32 = 44;
|
|
def A_RET: i32 = 45;
|
|
def A_JMP: i32 = 46;
|
|
def A_JE: i32 = 47;
|
|
def A_JNE: i32 = 48;
|
|
def A_JL: i32 = 49;
|
|
def A_JLE: i32 = 50;
|
|
def A_JG: i32 = 51;
|
|
def A_JGE: i32 = 52;
|
|
def A_JB: i32 = 53;
|
|
def A_JBE: i32 = 54;
|
|
def A_JA: i32 = 55;
|
|
def A_JAE: i32 = 56;
|
|
def A_JZ: i32 = 57;
|
|
def A_JNZ: i32 = 58;
|
|
// 67 (next free above A_CQO=66): appended so the existing A_MOV*/
|
|
// A_SYSCALL/A_DATAW/A_DATAR/A_CQO numbers stay put. Jump on
|
|
// parity (PF=1): UCOMISD unordered (#97).
|
|
def A_JP: i32 = 67;
|
|
// #136: arithmetic right-shift, sign-extends MSB. SHR injects
|
|
// zeros and is wrong for signed operands; cgen routes signed
|
|
// `>>` / `>>=` through SAR after this opcode landed.
|
|
def A_SARQ: i32 = 68;
|
|
|
|
def A_SYSCALL: i32 = 59;
|
|
|
|
// Writable data + reloc-only data. Mirror cmd/w6c/6.out.h.
|
|
// A_DATAW: bytes land in .data (RW) instead of .text.
|
|
// A_DATAR: record an R_X86_64_64 reloc at a .data slot, patched
|
|
// to a target symbol's runtime VA at link time.
|
|
def A_DATAW: i32 = 60;
|
|
def A_DATAR: i32 = 61;
|
|
|
|
// REX.W 99 — sign-extend RAX into RDX:RAX. Pairs with IDIVQ for
|
|
// signed division; pendant to the MOVQ $0, DX zero-fill that pairs
|
|
// with DIVQ.
|
|
def A_CQO: i32 = 66;
|
|
|
|
// ---- structs (mirror cmd/w6a/a.h) --------------------------------------
|
|
|
|
type aoperand = struct {
|
|
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
|
|
reg: i32,
|
|
offset: i64,
|
|
asym: str,
|
|
};
|
|
|
|
// `from` and `to` are pointer-to-aoperand (rather than embedded).
|
|
// The C cgen doesn't support chained-dot through embedded value
|
|
// fields, so allocating each operand once per prog lets us write
|
|
// `p.to.atype` directly.
|
|
type aprog = struct {
|
|
as_: i32,
|
|
from: *aoperand,
|
|
to: *aoperand,
|
|
line: i32,
|
|
label: str,
|
|
link: *aprog,
|
|
bytes: *u8, // payload for A_DATA
|
|
nbytes: u64,
|
|
};
|
|
|
|
type asym = struct {
|
|
name: str,
|
|
defined: i32,
|
|
istext: i32,
|
|
isdata: i32, // mutually exclusive with istext; DATAW symbols
|
|
isglobal: i32,
|
|
addr: u64, // offset within its section (.text or .data)
|
|
idx: i32,
|
|
snext: *asym,
|
|
};
|
|
|
|
type areloc = struct {
|
|
off: u64,
|
|
section: i32, // 0 = .text, 1 = .data
|
|
kind: i32,
|
|
asy: *asym,
|
|
addend: i64,
|
|
rnext: *areloc,
|
|
};
|
|
|
|
type afixup = struct {
|
|
off: u64, // where the rel32 lands in .text
|
|
label: str,
|
|
fnext: *afixup,
|
|
};
|
|
|
|
type asm_ = struct {
|
|
file: str,
|
|
src: *u8,
|
|
srclen: u64,
|
|
pos: u64,
|
|
line: i32,
|
|
|
|
head: *aprog,
|
|
tail: *aprog,
|
|
|
|
text: *u8,
|
|
textcap: u64,
|
|
textlen: u64,
|
|
|
|
// Writable .data. Empty unless any DATAW directive was seen;
|
|
// obj.ww emits the extra section conditionally so .o output
|
|
// stays byte-identical for inputs that don't use DATAW (test
|
|
// 991 byte-diff invariant).
|
|
data: *u8,
|
|
datacap: u64,
|
|
datalen: u64,
|
|
|
|
syms: *asym,
|
|
relocs: *areloc,
|
|
fixups: *afixup,
|
|
|
|
errs: i32,
|
|
};
|
|
|
|
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c.
|
|
//
|
|
// Character-level helpers for w6a's line-oriented parser. The parser
|
|
// itself is in parse.ww; here we keep tokenisers for identifiers and
|
|
// numbers so parse.ww stays focused on syntax.
|
|
|
|
package w6a;
|
|
|
|
export fn isidstart(c: i32) bool = {
|
|
if (c == 95) { return true; };
|
|
if (c >= 65) { if (c <= 90) { return true; }; }; // A-Z
|
|
if (c >= 97) { if (c <= 122) { return true; }; }; // a-z
|
|
return false;
|
|
};
|
|
|
|
export fn isidcont(c: i32) bool = {
|
|
if (isidstart(c)) { return true; };
|
|
if (c >= 48) { if (c <= 57) { return true; }; }; // 0-9
|
|
if (c == 46) { return true; }; // .
|
|
return false;
|
|
};
|
|
|
|
// parsenum — read a leading [+-]?[0x|0X|0]?digits from p[0..n-1].
|
|
// Returns (value, consumed). Stops at first non-digit.
|
|
// Plain Plan 9-style: $123 / $0x1f / $-7. Decimal default; 0x prefix
|
|
// for hex; 0 prefix for octal when followed by a digit (else just 0).
|
|
export fn parsenum(p: *u8, n: u64) (i64, u64) = {
|
|
let i: u64 = 0u64;
|
|
let neg: bool = false;
|
|
if (i < n) {
|
|
if (p[i] == 45u8) { neg = true; i += 1u64; }
|
|
else { if (p[i] == 43u8) { i += 1u64; }; };
|
|
};
|
|
let base: i64 = 10i64;
|
|
if (i + 1u64 < n) {
|
|
if (p[i] == 48u8) {
|
|
if (p[i + 1u64] == 120u8) { base = 16i64; i += 2u64; }
|
|
else { if (p[i + 1u64] == 88u8) { base = 16i64; i += 2u64; }
|
|
else { if (p[i + 1u64] >= 48u8) { if (p[i + 1u64] <= 55u8) {
|
|
base = 8i64; i += 1u64;
|
|
};};};};
|
|
};
|
|
};
|
|
let v: i64 = 0i64;
|
|
let scan: bool = true;
|
|
for (scan) {
|
|
if (i >= n) { scan = false; }
|
|
else {
|
|
let c: u8 = p[i];
|
|
let d: i64 = -1i64;
|
|
if (c >= 48u8) { if (c <= 57u8) { d = (c - 48u8): i64; }; };
|
|
if (d < 0i64) {
|
|
if (base == 16i64) {
|
|
if (c >= 97u8) { if (c <= 102u8) { d = (c - 97u8): i64 + 10i64; }; };
|
|
if (c >= 65u8) { if (c <= 70u8) { d = (c - 65u8): i64 + 10i64; }; };
|
|
};
|
|
};
|
|
if (d < 0i64) { scan = false; }
|
|
else { if (d >= base) { scan = false; }
|
|
else {
|
|
v = v * base + d;
|
|
i += 1u64;
|
|
}; };
|
|
};
|
|
};
|
|
if (neg) { v = -v; };
|
|
return v, i;
|
|
};
|
|
|
|
// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c.
|
|
//
|
|
// Line-oriented parser for the asm subset emitted by w6c.
|
|
// Grammar:
|
|
// line := blank | comment | label | text | instr
|
|
// blank := /^\s*$/
|
|
// comment := /^\s*\/\/.*$/
|
|
// label := /^IDENT:$/
|
|
// text := TEXT name,$framesize
|
|
// instr := \tMNEM\t[OP1[, OP2]]
|
|
// OP := $NUM | REG | NUM(REG) | (REG) | name(SB) | label
|
|
|
|
package w6a;
|
|
|
|
import os;
|
|
import strings;
|
|
import lex;
|
|
import opcodes;
|
|
|
|
fn streqlit(p: *u8, n: u64, lit: str) bool = {
|
|
if (n != lit.len: u64) { return false; };
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
let li: i32 = i: i32;
|
|
if (p[i] != lit[li]) { return false; };
|
|
i += 1u64;
|
|
};
|
|
return true;
|
|
};
|
|
|
|
// opcodelookup — name (length-bounded *u8) → A_*. Returns 0 (A_NOP)
|
|
// if not found.
|
|
fn opcodelookup(p: *u8, n: u64) i32 = {
|
|
if (streqlit(p, n, "MOVQ")) { return A_MOVQ; };
|
|
if (streqlit(p, n, "MOVL")) { return A_MOVL; };
|
|
if (streqlit(p, n, "MOVW")) { return A_MOVW; };
|
|
if (streqlit(p, n, "MOVB")) { return A_MOVB; };
|
|
if (streqlit(p, n, "MOVZBQ")) { return A_MOVZBQ; };
|
|
if (streqlit(p, n, "MOVZWQ")) { return A_MOVZWQ; };
|
|
if (streqlit(p, n, "MOVSXD")) { return A_MOVSXD; };
|
|
if (streqlit(p, n, "MOVSWQ")) { return A_MOVSWQ; };
|
|
if (streqlit(p, n, "MOVSBQ")) { return A_MOVSBQ; };
|
|
if (streqlit(p, n, "MOVSD")) { return A_MOVSD; };
|
|
if (streqlit(p, n, "ADDSD")) { return A_ADDSD; };
|
|
if (streqlit(p, n, "SUBSD")) { return A_SUBSD; };
|
|
if (streqlit(p, n, "MULSD")) { return A_MULSD; };
|
|
if (streqlit(p, n, "DIVSD")) { return A_DIVSD; };
|
|
if (streqlit(p, n, "UCOMISD")) { return A_UCOMISD; };
|
|
if (streqlit(p, n, "CVTTSD2SI")) { return A_CVTTSD2SI; };
|
|
if (streqlit(p, n, "CVTSI2SD")) { return A_CVTSI2SD; };
|
|
if (streqlit(p, n, "MOVSS")) { return A_MOVSS; };
|
|
if (streqlit(p, n, "ADDSS")) { return A_ADDSS; };
|
|
if (streqlit(p, n, "SUBSS")) { return A_SUBSS; };
|
|
if (streqlit(p, n, "MULSS")) { return A_MULSS; };
|
|
if (streqlit(p, n, "DIVSS")) { return A_DIVSS; };
|
|
if (streqlit(p, n, "UCOMISS")) { return A_UCOMISS; };
|
|
if (streqlit(p, n, "CVTTSS2SI")) { return A_CVTTSS2SI; };
|
|
if (streqlit(p, n, "CVTSI2SS")) { return A_CVTSI2SS; };
|
|
if (streqlit(p, n, "CVTSD2SS")) { return A_CVTSD2SS; };
|
|
if (streqlit(p, n, "CVTSS2SD")) { return A_CVTSS2SD; };
|
|
if (streqlit(p, n, "ADDQ")) { return A_ADDQ; };
|
|
if (streqlit(p, n, "SUBQ")) { return A_SUBQ; };
|
|
if (streqlit(p, n, "IMULQ")) { return A_IMULQ; };
|
|
if (streqlit(p, n, "IDIVQ")) { return A_IDIVQ; };
|
|
if (streqlit(p, n, "DIVQ")) { return A_DIVQ; };
|
|
if (streqlit(p, n, "CQO")) { return A_CQO; };
|
|
if (streqlit(p, n, "NEGQ")) { return A_NEGQ; };
|
|
if (streqlit(p, n, "NOTQ")) { return A_NOTQ; };
|
|
if (streqlit(p, n, "ANDQ")) { return A_ANDQ; };
|
|
if (streqlit(p, n, "ORQ")) { return A_ORQ; };
|
|
if (streqlit(p, n, "XORQ")) { return A_XORQ; };
|
|
if (streqlit(p, n, "SHLQ")) { return A_SHLQ; };
|
|
if (streqlit(p, n, "SHRQ")) { return A_SHRQ; };
|
|
if (streqlit(p, n, "SARQ")) { return A_SARQ; };
|
|
if (streqlit(p, n, "CMPQ")) { return A_CMPQ; };
|
|
if (streqlit(p, n, "PUSHQ")) { return A_PUSHQ; };
|
|
if (streqlit(p, n, "POPQ")) { return A_POPQ; };
|
|
if (streqlit(p, n, "LEAQ")) { return A_LEAQ; };
|
|
if (streqlit(p, n, "CALL")) { return A_CALL; };
|
|
if (streqlit(p, n, "RET")) { return A_RET; };
|
|
if (streqlit(p, n, "JMP")) { return A_JMP; };
|
|
if (streqlit(p, n, "JE")) { return A_JE; };
|
|
if (streqlit(p, n, "JNE")) { return A_JNE; };
|
|
if (streqlit(p, n, "JL")) { return A_JL; };
|
|
if (streqlit(p, n, "JLE")) { return A_JLE; };
|
|
if (streqlit(p, n, "JG")) { return A_JG; };
|
|
if (streqlit(p, n, "JGE")) { return A_JGE; };
|
|
if (streqlit(p, n, "JB")) { return A_JB; };
|
|
if (streqlit(p, n, "JBE")) { return A_JBE; };
|
|
if (streqlit(p, n, "JA")) { return A_JA; };
|
|
if (streqlit(p, n, "JAE")) { return A_JAE; };
|
|
if (streqlit(p, n, "JZ")) { return A_JZ; };
|
|
if (streqlit(p, n, "JNZ")) { return A_JNZ; };
|
|
if (streqlit(p, n, "JP")) { return A_JP; };
|
|
if (streqlit(p, n, "SYSCALL")) { return A_SYSCALL; };
|
|
if (streqlit(p, n, "TEXT")) { return A_TEXT; };
|
|
if (streqlit(p, n, "DATA")) { return A_DATA; };
|
|
if (streqlit(p, n, "DATAW")) { return A_DATAW; };
|
|
if (streqlit(p, n, "DATAR")) { return A_DATAR; };
|
|
return A_NOP;
|
|
};
|
|
|
|
// reglookup — name → D_*. Returns D_NONE if not found.
|
|
fn reglookup(p: *u8, n: u64) i32 = {
|
|
if (streqlit(p, n, "AX")) { return D_AX; };
|
|
if (streqlit(p, n, "BX")) { return D_BX; };
|
|
if (streqlit(p, n, "CX")) { return D_CX; };
|
|
if (streqlit(p, n, "DX")) { return D_DX; };
|
|
if (streqlit(p, n, "SP")) { return D_SP; };
|
|
if (streqlit(p, n, "BP")) { return D_BP; };
|
|
if (streqlit(p, n, "SI")) { return D_SI; };
|
|
if (streqlit(p, n, "DI")) { return D_DI; };
|
|
if (streqlit(p, n, "R8")) { return D_R8; };
|
|
if (streqlit(p, n, "R9")) { return D_R9; };
|
|
if (streqlit(p, n, "R10")) { return D_R10; };
|
|
if (streqlit(p, n, "R11")) { return D_R11; };
|
|
if (streqlit(p, n, "R12")) { return D_R12; };
|
|
if (streqlit(p, n, "R13")) { return D_R13; };
|
|
if (streqlit(p, n, "R14")) { return D_R14; };
|
|
if (streqlit(p, n, "R15")) { return D_R15; };
|
|
if (streqlit(p, n, "X0")) { return D_X0; };
|
|
if (streqlit(p, n, "X1")) { return D_X1; };
|
|
if (streqlit(p, n, "X2")) { return D_X2; };
|
|
if (streqlit(p, n, "X3")) { return D_X3; };
|
|
if (streqlit(p, n, "X4")) { return D_X4; };
|
|
if (streqlit(p, n, "X5")) { return D_X5; };
|
|
if (streqlit(p, n, "X6")) { return D_X6; };
|
|
if (streqlit(p, n, "X7")) { return D_X7; };
|
|
if (streqlit(p, n, "X8")) { return D_X8; };
|
|
if (streqlit(p, n, "X9")) { return D_X9; };
|
|
if (streqlit(p, n, "X10")) { return D_X10; };
|
|
if (streqlit(p, n, "X11")) { return D_X11; };
|
|
if (streqlit(p, n, "X12")) { return D_X12; };
|
|
if (streqlit(p, n, "X13")) { return D_X13; };
|
|
if (streqlit(p, n, "X14")) { return D_X14; };
|
|
if (streqlit(p, n, "X15")) { return D_X15; };
|
|
if (streqlit(p, n, "SB")) { return D_PSB; };
|
|
if (streqlit(p, n, "FP")) { return D_PFP; };
|
|
return D_NONE;
|
|
};
|
|
|
|
export fn init(a: *asm_, file: str, src: *u8, len: u64) void = {
|
|
a.file = file;
|
|
a.src = src;
|
|
a.srclen = len;
|
|
a.pos = 0u64;
|
|
a.line = 1;
|
|
a.head = nil;
|
|
a.tail = nil;
|
|
a.text = nil;
|
|
a.textcap = 0u64;
|
|
a.textlen = 0u64;
|
|
a.syms = nil;
|
|
a.relocs = nil;
|
|
a.fixups = nil;
|
|
a.errs = 0;
|
|
};
|
|
|
|
export fn intern(a: *asm_, name: str) *asym = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (strings.compare(s.name, name) == 0) { return s; };
|
|
s = s.snext;
|
|
};
|
|
let n: *asym = alloc(asym { name = name, snext = a.syms })!;
|
|
a.syms = n;
|
|
return n;
|
|
};
|
|
|
|
fn perr(a: *asm_, msg: str) void = {
|
|
let pfx: str = "w6a: ";
|
|
os.write(2, pfx.ptr, pfx.len: u64);
|
|
let f: str = a.file;
|
|
os.write(2, f.ptr, f.len: u64);
|
|
let sep: str = ": ";
|
|
os.write(2, sep.ptr, sep.len: u64);
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
let nl: str = "\n";
|
|
os.write(2, nl.ptr, nl.len: u64);
|
|
a.errs += 1;
|
|
};
|
|
|
|
// dupstr — copy n bytes from p into a fresh heap str.
|
|
fn dupstr(p: *u8, n: u64) str = {
|
|
let view: str;
|
|
view.ptr = p;
|
|
view.len = n: i32;
|
|
return strings.dup(view);
|
|
};
|
|
|
|
// ---- line iteration & whitespace --------------------------------------
|
|
|
|
// Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0)
|
|
// at EOF. Advances a.pos past the newline.
|
|
fn nextline(a: *asm_) (*u8, u64) = {
|
|
if (a.pos >= a.srclen) { return nil, 0u64; };
|
|
let start: u64 = a.pos;
|
|
for (a.pos < a.srclen) {
|
|
if (a.src[a.pos] == '\n') { a.pos = a.pos; a.pos += 0u64; } // no-op; explicit break via condition
|
|
else { a.pos += 1u64; continue; };
|
|
// hit newline
|
|
let n: u64 = a.pos - start;
|
|
let buf: []u8 = alloc([], n + 1u64)!;
|
|
let i: u64 = 0u64;
|
|
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
|
|
buf[n] = 0u8;
|
|
a.pos += 1u64; // skip newline
|
|
return buf.ptr, n;
|
|
};
|
|
// EOF without trailing newline
|
|
let n: u64 = a.pos - start;
|
|
if (n == 0u64) { return nil, 0u64; };
|
|
let buf: []u8 = alloc([], n + 1u64)!;
|
|
let i: u64 = 0u64;
|
|
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
|
|
buf[n] = 0u8;
|
|
return buf.ptr, n;
|
|
};
|
|
|
|
fn skipws(p: *u8, off: u64, n: u64) u64 = {
|
|
let i: u64 = off;
|
|
for (i < n) {
|
|
if (p[i] != ' ') { if (p[i] != '\t') { return i; }; };
|
|
i += 1u64;
|
|
};
|
|
return i;
|
|
};
|
|
|
|
// parseoperand — parse one operand from p[off..n), populate out.
|
|
// Returns new offset (clamped to n on error).
|
|
fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
|
|
let off: u64 = skipws(p, offin, n);
|
|
out.atype = D_NONE;
|
|
out.reg = 0;
|
|
out.offset = 0i64;
|
|
let empty: str;
|
|
empty.ptr = nil; empty.len = 0;
|
|
out.asym = empty;
|
|
if (off >= n) { return off; };
|
|
let c0: u8 = p[off];
|
|
|
|
// $NUM
|
|
if (c0 == '$') {
|
|
off += 1u64;
|
|
let v: i64;
|
|
let used: u64;
|
|
v, used = parsenum(p + off, n - off);
|
|
out.atype = D_CONST;
|
|
out.offset = v;
|
|
return off + used;
|
|
};
|
|
|
|
// (REG)
|
|
if (c0 == '(') {
|
|
off += 1u64;
|
|
let rstart: u64 = off;
|
|
for (off < n) {
|
|
if (p[off] == ')') { off = off; off += 0u64; } // no-op marker
|
|
else { off += 1u64; continue; };
|
|
let rn: u64 = off - rstart;
|
|
let r: i32 = reglookup(p + rstart, rn);
|
|
if (r == 0) { perr(a, "bad register in indirect"); return n; };
|
|
out.atype = D_INDIR;
|
|
out.reg = r;
|
|
out.offset = 0i64;
|
|
return off + 1u64; // past ')'
|
|
};
|
|
perr(a, "missing ')' in indirect");
|
|
return n;
|
|
};
|
|
|
|
// number(REG) — possibly signed — or bare $NUM-less constant
|
|
let cur: u64 = off;
|
|
let isnum: bool = false;
|
|
if (cur < n) {
|
|
if (p[cur] == '-') { isnum = true; }
|
|
else { if (p[cur] >= '0') { if (p[cur] <= '9') { isnum = true; }; }; };
|
|
};
|
|
if (isnum) {
|
|
let v: i64;
|
|
let used: u64;
|
|
v, used = parsenum(p + off, n - off);
|
|
let after: u64 = off + used;
|
|
if (after < n) { if (p[after] == '(') {
|
|
let rstart: u64 = after + 1u64;
|
|
let cur2: u64 = rstart;
|
|
for (cur2 < n) {
|
|
if (p[cur2] == ')') { cur2 = cur2; cur2 += 0u64; }
|
|
else { cur2 += 1u64; continue; };
|
|
let rn: u64 = cur2 - rstart;
|
|
let r: i32 = reglookup(p + rstart, rn);
|
|
if (r == 0) { perr(a, "bad register"); return n; };
|
|
out.atype = D_INDIR;
|
|
out.reg = r;
|
|
out.offset = v;
|
|
return cur2 + 1u64;
|
|
};
|
|
perr(a, "missing ')'");
|
|
return n;
|
|
};};
|
|
out.atype = D_CONST;
|
|
out.offset = v;
|
|
return after;
|
|
};
|
|
|
|
// IDENT — register, symbol(SB), symbol+disp(SB), or branch label
|
|
if (isidstart(c0: i32)) {
|
|
let istart: u64 = off;
|
|
for (off < n) {
|
|
if (isidcont(p[off]: i32)) { off += 1u64; continue; };
|
|
off = off; off += 0u64; // loop break
|
|
let in_: u64 = off - istart;
|
|
// Optional `+disp` between the ident and `(SB)`. Used
|
|
// by DATAR to address bytes within a previously-defined
|
|
// .data slot (e.g. `DATAR s+8(SB),...`).
|
|
let symdisp: i64 = 0i64;
|
|
if (off < n) { if (p[off] == '+') {
|
|
off += 1u64;
|
|
let v: i64;
|
|
let used: u64;
|
|
v, used = parsenum(p + off, n - off);
|
|
symdisp = v;
|
|
off += used;
|
|
};};
|
|
// IDENT(SB) — external
|
|
if (off < n) { if (p[off] == '(') {
|
|
let rstart: u64 = off + 1u64;
|
|
let cur2: u64 = rstart;
|
|
for (cur2 < n) {
|
|
if (p[cur2] == ')') { cur2 = cur2; cur2 += 0u64; }
|
|
else { cur2 += 1u64; continue; };
|
|
let rn: u64 = cur2 - rstart;
|
|
let r: i32 = reglookup(p + rstart, rn);
|
|
if (r == D_PSB) {
|
|
out.atype = D_EXTERN;
|
|
out.asym = dupstr(p + istart, in_);
|
|
out.offset = symdisp;
|
|
} else {
|
|
out.atype = D_INDIR;
|
|
out.reg = r;
|
|
out.offset = 0i64;
|
|
};
|
|
return cur2 + 1u64;
|
|
};
|
|
perr(a, "missing ')'");
|
|
return n;
|
|
};};
|
|
let r: i32 = reglookup(p + istart, in_);
|
|
if (r != D_NONE) {
|
|
out.atype = r;
|
|
return off;
|
|
};
|
|
out.atype = D_BRANCH;
|
|
out.asym = dupstr(p + istart, in_);
|
|
return off;
|
|
};
|
|
// EOF inside ident
|
|
let in_: u64 = off - istart;
|
|
let r: i32 = reglookup(p + istart, in_);
|
|
if (r != D_NONE) { out.atype = r; return off; };
|
|
out.atype = D_BRANCH;
|
|
out.asym = dupstr(p + istart, in_);
|
|
return off;
|
|
};
|
|
|
|
perr(a, "unrecognised operand");
|
|
return n;
|
|
};
|
|
|
|
// Append a fresh aprog to the list with given opcode and label.
|
|
fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = {
|
|
let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!;
|
|
pr.from = alloc(aoperand { })!;
|
|
pr.to = alloc(aoperand { })!;
|
|
if (a.head == nil) { a.head = pr; }
|
|
else { a.tail.link = pr; };
|
|
a.tail = pr;
|
|
return pr;
|
|
};
|
|
|
|
export fn parse(a: *asm_) i32 = {
|
|
let pending: str;
|
|
pending.ptr = nil; pending.len = 0;
|
|
|
|
for (true) {
|
|
let line: *u8;
|
|
let n: u64;
|
|
line, n = nextline(a);
|
|
if (line == nil) { return a.errs; };
|
|
|
|
// skip leading ws
|
|
let i: u64 = skipws(line, 0u64, n);
|
|
// blank or //-comment
|
|
if (i >= n) { a.line += 1; continue; };
|
|
if (i + 1u64 < n) {
|
|
if (line[i] == '/') { if (line[i + 1u64] == '/') {
|
|
a.line += 1; continue;
|
|
};};
|
|
};
|
|
|
|
// Label? IDENT: starting at column 0 (no leading tab).
|
|
// Only if the identifier is followed by ':'. Otherwise, fall
|
|
// through to mnemonic parsing so e.g. `TEXT foo,$0` (which
|
|
// also starts with an idchar in column 0) gets parsed.
|
|
if (line[0u64] != '\t') {
|
|
if (isidstart(line[i]: i32)) {
|
|
let q: u64 = i;
|
|
let scanid: bool = true;
|
|
for (scanid) {
|
|
if (q >= n) { scanid = false; }
|
|
else { if (isidcont(line[q]: i32)) { q += 1u64; }
|
|
else { scanid = false; }; };
|
|
};
|
|
if (q < n) { if (line[q] == ':') {
|
|
let nm: str = dupstr(line + i, q - i);
|
|
// Pending label gets a NOP prog so addresses pin.
|
|
if (pending.len > 0) {
|
|
let np: *aprog = addprog(a, A_NOP, pending);
|
|
};
|
|
pending = nm;
|
|
a.line += 1;
|
|
continue;
|
|
};};
|
|
// not a label — fall through to mnemonic parse
|
|
};
|
|
};
|
|
|
|
// MNEMONIC at the start of the rest. Scan to first ws/EOL.
|
|
let mstart: u64 = i;
|
|
let m: u64 = mstart;
|
|
let scan: bool = true;
|
|
for (scan) {
|
|
if (m >= n) { scan = false; }
|
|
else { if (line[m] == ' ') { scan = false; }
|
|
else { if (line[m] == '\t') { scan = false; }
|
|
else { m += 1u64; }; }; };
|
|
};
|
|
let mlen: u64 = m - mstart;
|
|
let opc: i32 = opcodelookup(line + mstart, mlen);
|
|
if (opc == 0) {
|
|
if (mlen > 0u64) {
|
|
perr(a, "unknown opcode");
|
|
};
|
|
pending.ptr = nil; pending.len = 0;
|
|
a.line += 1; continue;
|
|
};
|
|
|
|
let pr: *aprog = addprog(a, opc, pending);
|
|
pending.ptr = nil; pending.len = 0;
|
|
|
|
// Skip ws after mnemonic
|
|
let r0: u64 = skipws(line, m, n);
|
|
|
|
if (opc == A_TEXT) {
|
|
// TEXT name,$framesize — find first ',' as the end of name.
|
|
let q: u64 = r0;
|
|
let commapos: u64 = n;
|
|
let scant: bool = true;
|
|
for (scant) {
|
|
if (q >= n) { scant = false; }
|
|
else { if (line[q] == ',') { commapos = q; scant = false; }
|
|
else { q += 1u64; }; };
|
|
};
|
|
let toop: *aoperand = pr.to;
|
|
toop.atype = D_EXTERN;
|
|
toop.asym = dupstr(line + r0, commapos - r0);
|
|
if (commapos < n) {
|
|
let p2: u64 = commapos + 1u64;
|
|
p2 = skipws(line, p2, n);
|
|
if (p2 < n) { if (line[p2] == '$') { p2 += 1u64; }; };
|
|
let v: i64;
|
|
let used: u64;
|
|
v, used = parsenum(line + p2, n - p2);
|
|
let fromop: *aoperand = pr.from;
|
|
fromop.atype = D_CONST;
|
|
fromop.offset = v;
|
|
};
|
|
a.line += 1; continue;
|
|
};
|
|
|
|
if (opc == A_DATA || opc == A_DATAW) {
|
|
// DATA / DATAW name(SB),"escaped bytes" — same syntax,
|
|
// different destination section (.text vs .data).
|
|
let q: u64 = r0;
|
|
let lparen: u64 = n;
|
|
let scand: bool = true;
|
|
for (scand) {
|
|
if (q >= n) { scand = false; }
|
|
else { if (line[q] == '(') { lparen = q; scand = false; }
|
|
else { q += 1u64; }; };
|
|
};
|
|
let toop: *aoperand = pr.to;
|
|
toop.atype = D_EXTERN;
|
|
toop.asym = dupstr(line + r0, lparen - r0);
|
|
// Skip past `(SB)` to land just after ')'.
|
|
let p2: u64 = lparen;
|
|
let scand2: bool = true;
|
|
for (scand2) {
|
|
if (p2 >= n) { scand2 = false; }
|
|
else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; }
|
|
else { p2 += 1u64; }; };
|
|
};
|
|
// Skip ws / ',' / tab between `)` and the `"`.
|
|
let scand3: bool = true;
|
|
for (scand3) {
|
|
if (p2 >= n) { scand3 = false; }
|
|
else { if (line[p2] == ' ') { p2 += 1u64; }
|
|
else { if (line[p2] == ',') { p2 += 1u64; }
|
|
else { if (line[p2] == '\t') { p2 += 1u64; }
|
|
else { scand3 = false; }; }; }; };
|
|
};
|
|
if (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; };
|
|
if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; };
|
|
p2 += 1u64; // past opening "
|
|
// Parse escape sequence into a fresh growable buffer.
|
|
let cap: u64 = 32u64;
|
|
let blen: u64 = 0u64;
|
|
let dbuf: []u8 = alloc([], cap)!;
|
|
for (p2 < n) {
|
|
if (line[p2] == '"') { p2 = p2; p2 += 0u64; p2 = n + 1u64; }
|
|
else {
|
|
let ch: u8 = line[p2];
|
|
p2 += 1u64;
|
|
if (ch == '\\') {
|
|
if (p2 < n) {
|
|
let e: u8 = line[p2];
|
|
p2 += 1u64;
|
|
if (e == 'n') { ch = '\n'; }
|
|
else { if (e == 't') { ch = '\t'; }
|
|
else { if (e == 'r') { ch = '\r'; }
|
|
else { if (e == '\\') { ch = '\\'; }
|
|
else { if (e == '"') { ch = '"'; }
|
|
else { if (e == '0') { ch = '\0'; }
|
|
else { if (e == 'x') {
|
|
if (p2 + 1u64 < n) {
|
|
let hi: u8 = line[p2];
|
|
let lo: u8 = line[p2 + 1u64];
|
|
p2 += 2u64;
|
|
let h: u8 = 0u8;
|
|
let l: u8 = 0u8;
|
|
if (hi <= 57u8) { h = hi - 48u8; }
|
|
else { h = (hi | 32u8) - 97u8 + 10u8; };
|
|
if (lo <= 57u8) { l = lo - 48u8; }
|
|
else { l = (lo | 32u8) - 97u8 + 10u8; };
|
|
ch = (h << 4u8) | l;
|
|
};
|
|
}
|
|
else { ch = e; };};};};};};};
|
|
};
|
|
};
|
|
if (blen + 1u64 > cap) {
|
|
let ncap: u64 = cap * 2u64;
|
|
let nb: []u8 = alloc([], ncap)!;
|
|
let bi: u64 = 0u64;
|
|
for (bi < blen) { nb[bi] = dbuf[bi]; bi += 1u64; };
|
|
dbuf = nb;
|
|
cap = ncap;
|
|
};
|
|
dbuf[blen] = ch;
|
|
blen += 1u64;
|
|
};
|
|
};
|
|
pr.bytes = dbuf.ptr;
|
|
pr.nbytes = blen;
|
|
a.line += 1; continue;
|
|
};
|
|
|
|
// Generic instruction: 0/1/2 operands separated by ','.
|
|
// Find top-level comma.
|
|
let comma: i64 = -1i64;
|
|
let q: u64 = r0;
|
|
for (q < n) {
|
|
if (line[q] == ',') {
|
|
if (comma < 0i64) { comma = q: i64; };
|
|
};
|
|
q += 1u64;
|
|
};
|
|
if (comma >= 0i64) {
|
|
let cu: u64 = comma: u64;
|
|
parseoperand(a, line, r0, cu, pr.from);
|
|
parseoperand(a, line + (cu + 1u64), 0u64, n - (cu + 1u64), pr.to);
|
|
} else { if (r0 < n) {
|
|
parseoperand(a, line, r0, n, pr.to);
|
|
};};
|
|
|
|
a.line += 1;
|
|
};
|
|
return a.errs;
|
|
};
|
|
|
|
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c.
|
|
//
|
|
// Encode the parsed aprog list into amd64 machine bytes, appending to
|
|
// asm_.text. Relocations for CALL/branch targets that resolve to
|
|
// externals are queued in asm_.relocs.
|
|
//
|
|
// Encoding subset matches what w6c emits — see cmd/w6a/asm.c for the
|
|
// authoritative list. Helpers (rcode/rhi/modrm/emitrex etc.) are
|
|
// fully ported; encode itself is still a stub pending the full
|
|
// switch over A_*.
|
|
|
|
package w6a;
|
|
|
|
import os;
|
|
import rt;
|
|
import opcodes;
|
|
import strings;
|
|
|
|
// ---- text buffer growth ------------------------------------------------
|
|
|
|
export fn emitbyte(a: *asm_, b: u8) void = {
|
|
if (a.textlen + 1u64 > a.textcap) {
|
|
let nc: u64 = a.textcap;
|
|
if (nc == 0u64) { nc = 4096u64; };
|
|
nc = nc * 2u64;
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; };
|
|
a.text = nb.ptr;
|
|
a.textcap = nc;
|
|
};
|
|
a.text[a.textlen] = b;
|
|
a.textlen += 1u64;
|
|
};
|
|
|
|
export fn emitu32(a: *asm_, v: u32) void = {
|
|
emitbyte(a, (v & 255u32): u8);
|
|
emitbyte(a, ((v >> 8u32) & 255u32): u8);
|
|
emitbyte(a, ((v >> 16u32) & 255u32): u8);
|
|
emitbyte(a, ((v >> 24u32) & 255u32): u8);
|
|
};
|
|
|
|
export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
|
let r: *areloc = alloc(areloc { off = off, section = 0, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
|
|
a.relocs = r;
|
|
};
|
|
|
|
// Record a relocation that lives in the .data section. Used by
|
|
// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww
|
|
// separates these into .rela.data when emitting the .o.
|
|
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = {
|
|
let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
|
|
a.relocs = r;
|
|
};
|
|
|
|
// Append one byte to the writable .data buffer. Mirrors emitbyte
|
|
// but targets a.data instead of a.text.
|
|
export fn emitdatabyte(a: *asm_, b: u8) void = {
|
|
if (a.datalen + 1u64 > a.datacap) {
|
|
let nc: u64 = a.datacap;
|
|
if (nc == 0u64) { nc = 256u64; };
|
|
nc = nc * 2u64;
|
|
let nb: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; };
|
|
a.data = nb.ptr;
|
|
a.datacap = nc;
|
|
};
|
|
a.data[a.datalen] = b;
|
|
a.datalen += 1u64;
|
|
};
|
|
|
|
// ---- register codes ----------------------------------------------------
|
|
|
|
// Low 3 bits of register encoding.
|
|
fn rcode(r: i32) i32 = {
|
|
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
|
|
if (r == D_DX) { return 2; }; if (r == D_BX) { return 3; };
|
|
if (r == D_SP) { return 4; }; if (r == D_BP) { return 5; };
|
|
if (r == D_SI) { return 6; }; if (r == D_DI) { return 7; };
|
|
if (r == D_R8) { return 0; }; if (r == D_R9) { return 1; };
|
|
if (r == D_R10) { return 2; }; if (r == D_R11) { return 3; };
|
|
if (r == D_R12) { return 4; }; if (r == D_R13) { return 5; };
|
|
if (r == D_R14) { return 6; }; if (r == D_R15) { return 7; };
|
|
if (r == D_X0) { return 0; }; if (r == D_X1) { return 1; };
|
|
if (r == D_X2) { return 2; }; if (r == D_X3) { return 3; };
|
|
if (r == D_X4) { return 4; }; if (r == D_X5) { return 5; };
|
|
if (r == D_X6) { return 6; }; if (r == D_X7) { return 7; };
|
|
if (r == D_X8) { return 0; }; if (r == D_X9) { return 1; };
|
|
if (r == D_X10) { return 2; }; if (r == D_X11) { return 3; };
|
|
if (r == D_X12) { return 4; }; if (r == D_X13) { return 5; };
|
|
if (r == D_X14) { return 6; }; if (r == D_X15) { return 7; };
|
|
return 0;
|
|
};
|
|
|
|
// 1 if r needs the REX high bit (R8..R15 or X8..X15).
|
|
fn rhi(r: i32) i32 = {
|
|
if (r >= D_R8) { if (r <= D_R15) { return 1; }; };
|
|
if (r >= D_X8) { if (r <= D_X15) { return 1; }; };
|
|
return 0;
|
|
};
|
|
|
|
fn isxmm(r: i32) bool = {
|
|
if (r >= D_X0) { if (r <= D_X15) { return true; }; };
|
|
return false;
|
|
};
|
|
|
|
// ModR/M byte builder.
|
|
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
|
|
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8;
|
|
};
|
|
|
|
// REX prefix; W=1 for 64-bit operand size.
|
|
fn emitrex(a: *asm_, regbit: i32, rmbit: i32, w: i32) void = {
|
|
let b: u8 = 64u8; // 0x40
|
|
if (w != 0) { b = b | 8u8; };
|
|
if (regbit != 0) { b = b | 4u8; };
|
|
if (rmbit != 0) { b = b | 1u8; };
|
|
if (b != 64u8) { emitbyte(a, b); }
|
|
else { if (w != 0) { emitbyte(a, b); }; };
|
|
};
|
|
|
|
// ModR/M + (optional) SIB + displacement for [base+disp].
|
|
// Special-cases SP (needs SIB) and BP (forces explicit disp).
|
|
fn emitmodrmmem(a: *asm_, regfield: i32, base: i32, disp: i64) void = {
|
|
let rm: i32 = rcode(base);
|
|
let needsib: bool = (rm == 4);
|
|
let forceddisp: bool = false;
|
|
if (rm == 5) { if (disp == 0i64) { forceddisp = true; }; };
|
|
|
|
let mod: i32 = 2;
|
|
if (disp == 0i64) {
|
|
if (!forceddisp) { mod = 0; }
|
|
else { mod = 1; };
|
|
} else {
|
|
if (disp >= -128i64) { if (disp <= 127i64) { mod = 1; }; };
|
|
};
|
|
|
|
emitbyte(a, modrmbyte(mod, regfield, rm));
|
|
if (needsib) {
|
|
emitbyte(a, 36u8); // 0x24: scale=0 idx=4(none) base=4
|
|
};
|
|
if (mod == 1) {
|
|
emitbyte(a, (disp: u64 & 255u64): u8);
|
|
} else { if (mod == 2) {
|
|
emitu32(a, disp: u32);
|
|
};};
|
|
};
|
|
|
|
// reg→reg "src, dst" generic encoding (89 /r, 01 /r, etc.).
|
|
fn encoderr(a: *asm_, opcode: u8, src: i32, dst: i32) void = {
|
|
emitrex(a, rhi(src), rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(3, rcode(src), rcode(dst)));
|
|
};
|
|
|
|
// reg→mem(base, disp) (e.g. MOVQ src reg into mem; opcode = 0x89).
|
|
fn encoderm(a: *asm_, opcode: u8, srcreg: i32, base: i32, disp: i64) void = {
|
|
emitrex(a, rhi(srcreg), rhi(base), 1);
|
|
emitbyte(a, opcode);
|
|
emitmodrmmem(a, rcode(srcreg), base, disp);
|
|
};
|
|
|
|
// mem(base, disp) → reg (e.g. MOVQ mem into reg; opcode = 0x8B).
|
|
fn encodemr(a: *asm_, opcode: u8, dstreg: i32, base: i32, disp: i64) void = {
|
|
emitrex(a, rhi(dstreg), rhi(base), 1);
|
|
emitbyte(a, opcode);
|
|
emitmodrmmem(a, rcode(dstreg), base, disp);
|
|
};
|
|
|
|
// OPCODE /n imm32 reg form (e.g. ADDQ $imm, reg).
|
|
fn encoderiimm32(a: *asm_, opcode: u8, subop: i32, dst: i32, imm: i32) void = {
|
|
emitrex(a, 0, rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(3, subop, rcode(dst)));
|
|
emitu32(a, imm: u32);
|
|
};
|
|
|
|
// Unary on reg: F7 /n reg, etc.
|
|
fn encodeunary(a: *asm_, opcode: u8, subop: i32, dst: i32) void = {
|
|
emitrex(a, 0, rhi(dst), 1);
|
|
emitbyte(a, opcode);
|
|
emitbyte(a, modrmbyte(3, subop, rcode(dst)));
|
|
};
|
|
|
|
// SSE2 helpers. Plan 9 syntax: source first, destination second.
|
|
// For ADDSD-style ops we put dst in the reg field, src in r/m.
|
|
fn sserr(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(rmop), 0);
|
|
emitbyte(a, 15u8); // 0x0F
|
|
emitbyte(a, op2);
|
|
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
|
};
|
|
|
|
fn ssemrload(a: *asm_, prefix: u8, op2: u8, regop: i32, base: i32, disp: i64) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(base), 0);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, op2);
|
|
emitmodrmmem(a, rcode(regop), base, disp);
|
|
};
|
|
|
|
// REX.W variant of sse_rr (CVTTSD2SI / CVTSI2SD).
|
|
fn sserrw(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = {
|
|
if (prefix != 0u8) { emitbyte(a, prefix); };
|
|
emitrex(a, rhi(regop), rhi(rmop), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, op2);
|
|
emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
|
|
};
|
|
|
|
// ---- label resolution / fixups ----------------------------------------
|
|
|
|
fn resolvelabel(a: *asm_, name: str) u64 = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (s.defined != 0) { if (strings.compare(s.name, name) == 0) { return s.addr; }; };
|
|
s = s.snext;
|
|
};
|
|
return 0u64;
|
|
};
|
|
|
|
fn labeldefined(a: *asm_, name: str) bool = {
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
if (s.defined != 0) { if (strings.compare(s.name, name) == 0) { return true; }; };
|
|
s = s.snext;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// ---- fixup helper -----------------------------------------------------
|
|
|
|
fn addfixup(a: *asm_, off: u64, label: str) void = {
|
|
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
|
|
a.fixups = f;
|
|
};
|
|
|
|
fn isgpr(t: i32) bool = {
|
|
if (t >= D_AX) { if (t <= D_R15) { return true; }; };
|
|
return false;
|
|
};
|
|
|
|
// `intern` lives in parse.ww — flat-scope concat lets us call it
|
|
// directly without an @symbol declaration here.
|
|
|
|
// ---- encode ----------------------------------------------------------
|
|
|
|
export fn encode(a: *asm_) i32 = {
|
|
let p: *aprog = a.head;
|
|
for (p != nil) {
|
|
// Define any pending label at the current PC.
|
|
if (p.label.len > 0) {
|
|
let s: *asym = intern(a, p.label);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.addr = a.textlen;
|
|
};
|
|
let op: i32 = p.as_;
|
|
|
|
if (op == A_NOP) {
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_TEXT) {
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.textlen;
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATA) {
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.istext = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.textlen;
|
|
let i: u64 = 0u64;
|
|
for (i < p.nbytes) { emitbyte(a, p.bytes[i]); i += 1u64; };
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATAW) {
|
|
// Writable variant: bytes go into .data instead of
|
|
// .text. obj.ww emits the extra section conditionally
|
|
// on datalen > 0 so .o output stays byte-identical
|
|
// for inputs that don't use DATAW.
|
|
let s: *asym = intern(a, p.to.asym);
|
|
s.defined = 1;
|
|
s.isdata = 1;
|
|
s.isglobal = 1;
|
|
s.addr = a.datalen;
|
|
let i: u64 = 0u64;
|
|
for (i < p.nbytes) { emitdatabyte(a, p.bytes[i]); i += 1u64; };
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_DATAR) {
|
|
// DATAR slot+off(SB), target(SB) — record an
|
|
// R_X86_64_64 relocation at slot+off in .data
|
|
// pointing at target. Slot must already be defined
|
|
// by a prior DATAW.
|
|
let holder: *asym = intern(a, p.from.asym);
|
|
if (holder.defined == 0) {
|
|
p = p.link; continue;
|
|
};
|
|
if (holder.isdata == 0) {
|
|
p = p.link; continue;
|
|
};
|
|
let target: *asym = intern(a, p.to.asym);
|
|
let reloff: u64 = holder.addr + p.from.offset: u64;
|
|
addrelocdata(a, reloff, 1 /* R_X86_64_64 */,
|
|
target, 0i64);
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_RET) {
|
|
emitbyte(a, 195u8); // 0xC3
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_SYSCALL) {
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 5u8);
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_PUSHQ) {
|
|
if (rhi(p.to.atype) != 0) { emitbyte(a, 65u8); }; // 0x41
|
|
emitbyte(a, (80 + rcode(p.to.atype)): u8); // 0x50
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_POPQ) {
|
|
if (rhi(p.to.atype) != 0) { emitbyte(a, 65u8); };
|
|
emitbyte(a, (88 + rcode(p.to.atype)): u8); // 0x58
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_NEGQ) { encodeunary(a, 247u8, 3, p.to.atype); p = p.link; continue; };
|
|
if (op == A_NOTQ) { encodeunary(a, 247u8, 2, p.to.atype); p = p.link; continue; };
|
|
if (op == A_IDIVQ) { encodeunary(a, 247u8, 7, p.to.atype); p = p.link; continue; };
|
|
if (op == A_DIVQ) { encodeunary(a, 247u8, 6, p.to.atype); p = p.link; continue; };
|
|
if (op == A_CQO) {
|
|
emitbyte(a, 72u8); // REX.W (0x48)
|
|
emitbyte(a, 153u8); // 0x99
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
let v: i64 = p.from.offset;
|
|
if (v >= -2147483648i64) { if (v <= 2147483647i64) {
|
|
encoderiimm32(a, 199u8, 0, tt, v: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
// movabs r64, imm64: REX.W B8+rd imm64
|
|
emitrex(a, 0, rhi(tt), 1);
|
|
emitbyte(a, (184 + rcode(tt)): u8);
|
|
let k: i32 = 0;
|
|
for (k < 8) {
|
|
emitbyte(a, ((v: u64 >> (k: u64 * 8u64)) & 255u64): u8);
|
|
k += 1;
|
|
};
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
encoderr(a, 137u8, ft, tt); // 0x89
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 139u8, tt, p.from.reg, p.from.offset); // 0x8B
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 137u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 199u8);
|
|
emitmodrmmem(a, 0, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_EXTERN) { if (isgpr(tt)) {
|
|
// RIP-relative load: 48 8B /r mod=00 rm=5 disp32
|
|
emitrex(a, rhi(tt), 0, 1);
|
|
emitbyte(a, 139u8);
|
|
emitbyte(a, modrmbyte(0, rcode(tt), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.from.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_EXTERN) {
|
|
// RIP-relative store: 48 89 /r mod=00 rm=5 disp32
|
|
emitrex(a, rhi(ft), 0, 1);
|
|
emitbyte(a, 137u8);
|
|
emitbyte(a, modrmbyte(0, rcode(ft), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.to.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVQ shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVB) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 136u8); // 0x88
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 138u8); // 0x8A
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVB shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVW) {
|
|
// 16-bit MOV: 0x66 operand-size prefix + the 32-bit
|
|
// MOV opcodes 0x89 / 0x8B. No REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitbyte(a, 102u8); // 0x66
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 137u8); // 0x89
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitbyte(a, 102u8); // 0x66
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 139u8); // 0x8B
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVW shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVZWQ) {
|
|
// MOVZX r64, r/m16 — 0F B7 /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 183u8); // 0xB7
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVZWQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSWQ) {
|
|
// MOVSX r64, r/m16 — 0F BF /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 191u8); // 0xBF
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 191u8); // 0xBF
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSWQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSBQ) {
|
|
// MOVSX r64, r/m8 — 0F BE /r with REX.W.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 190u8); // 0xBE
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 190u8); // 0xBE
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSBQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVZBQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 182u8); // 0xB6
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVZBQ shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVL) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
emitrex(a, rhi(ft), rhi(p.to.reg), 0);
|
|
emitbyte(a, 137u8);
|
|
emitmodrmmem(a, rcode(ft), p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 0);
|
|
emitbyte(a, 139u8);
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(ft), rhi(tt), 0);
|
|
emitbyte(a, 137u8);
|
|
emitbyte(a, modrmbyte(3, rcode(ft), rcode(tt)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVL shape\n".ptr, 27u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSXD) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(p.from.reg), 1);
|
|
emitbyte(a, 99u8); // 0x63
|
|
emitmodrmmem(a, rcode(tt), p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), rhi(ft), 1);
|
|
emitbyte(a, 99u8); // 0x63
|
|
emitbyte(a, modrmbyte(3, rcode(tt), rcode(ft)));
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSXD shape\n".ptr, 29u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_MOVSD) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isxmm(ft)) { if (isxmm(tt)) {
|
|
sserr(a, 242u8, 16u8, tt, ft);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isxmm(tt)) {
|
|
ssemrload(a, 242u8, 16u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isxmm(ft)) { if (tt == D_INDIR) {
|
|
ssemrload(a, 242u8, 17u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSD shape\n".ptr, 28u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ADDSD) { sserr(a, 242u8, 88u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_SUBSD) { sserr(a, 242u8, 92u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_MULSD) { sserr(a, 242u8, 89u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_DIVSD) { sserr(a, 242u8, 94u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_UCOMISD) { sserr(a, 102u8, 46u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTTSD2SI) { sserrw(a, 242u8, 44u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSI2SD) { sserrw(a, 242u8, 42u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
|
|
if (op == A_MOVSS) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (isxmm(ft)) { if (isxmm(tt)) {
|
|
sserr(a, 243u8, 16u8, tt, ft); p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isxmm(tt)) {
|
|
ssemrload(a, 243u8, 16u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isxmm(ft)) { if (tt == D_INDIR) {
|
|
ssemrload(a, 243u8, 17u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
os.write(2, "w6a: unsupported MOVSS shape\n".ptr, 28u64);
|
|
a.errs += 1;
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ADDSS) { sserr(a, 243u8, 88u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_SUBSS) { sserr(a, 243u8, 92u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_MULSS) { sserr(a, 243u8, 89u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_DIVSS) { sserr(a, 243u8, 94u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_UCOMISS) { sserr(a, 0u8, 46u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTTSS2SI) { sserrw(a, 243u8, 44u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSI2SS) { sserrw(a, 243u8, 42u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSD2SS) { sserr(a, 242u8, 90u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
if (op == A_CVTSS2SD) { sserr(a, 243u8, 90u8, p.to.atype, p.from.atype); p = p.link; continue; };
|
|
|
|
if (op == A_ADDQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 0, tt, p.from.offset: i32); // 0x81
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 129u8);
|
|
emitmodrmmem(a, 0, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 1u8, ft, p.to.reg, p.to.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 3u8, tt, p.from.reg, p.from.offset);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 1u8, ft, tt);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_SUBQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 5, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_CONST) { if (tt == D_INDIR) {
|
|
emitrex(a, 0, rhi(p.to.reg), 1);
|
|
emitbyte(a, 129u8);
|
|
emitmodrmmem(a, 5, p.to.reg, p.to.offset);
|
|
emitu32(a, p.from.offset: u32);
|
|
p = p.link; continue;
|
|
};};
|
|
if (isgpr(ft)) { if (tt == D_INDIR) {
|
|
encoderm(a, 41u8, ft, p.to.reg, p.to.offset); // 0x29
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 43u8, tt, p.from.reg, p.from.offset); // 0x2B
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 41u8, ft, tt);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_ANDQ) {
|
|
// AND r/m64, imm32 — 0x81 /4 (REX.W). Without the
|
|
// D_CONST path encoderr would silently emit 0x21
|
|
// with garbage reg fields.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 4, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 33u8, ft, tt); // 0x21
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_ORQ) {
|
|
// OR r/m64, imm32 — 0x81 /1 (REX.W). Mirrors ANDQ.
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 1, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 9u8, ft, tt); // 0x09
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_XORQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 6, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 49u8, ft, tt); // 0x31
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_IMULQ) {
|
|
emitrex(a, rhi(p.to.atype), rhi(p.from.atype), 1);
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, 175u8); // 0xAF
|
|
emitbyte(a, modrmbyte(3, rcode(p.to.atype), rcode(p.from.atype)));
|
|
p = p.link; continue;
|
|
};
|
|
if (op == A_SHLQ) { encodeunary(a, 211u8, 4, p.to.atype); p = p.link; continue; }; // 0xD3
|
|
if (op == A_SHRQ) { encodeunary(a, 211u8, 5, p.to.atype); p = p.link; continue; };
|
|
// #136: SAR r/m64, CL — REX.W + D3 /7 (arithmetic right
|
|
// shift, sign-extends MSB; cstage twin cmd/w6a/asm.c).
|
|
if (op == A_SARQ) { encodeunary(a, 211u8, 7, p.to.atype); p = p.link; continue; };
|
|
if (op == A_CMPQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_CONST) { if (isgpr(tt)) {
|
|
encoderiimm32(a, 129u8, 7, tt, p.from.offset: i32);
|
|
p = p.link; continue;
|
|
};};
|
|
encoderr(a, 57u8, ft, tt); // 0x39
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_LEAQ) {
|
|
let ft: i32 = p.from.atype;
|
|
let tt: i32 = p.to.atype;
|
|
if (ft == D_INDIR) { if (isgpr(tt)) {
|
|
encodemr(a, 141u8, tt, p.from.reg, p.from.offset); // 0x8D
|
|
p = p.link; continue;
|
|
};};
|
|
if (ft == D_EXTERN) { if (isgpr(tt)) {
|
|
emitrex(a, rhi(tt), 0, 1);
|
|
emitbyte(a, 141u8);
|
|
emitbyte(a, modrmbyte(0, rcode(tt), 5));
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.from.asym);
|
|
addreloc(a, reloff, 2, s, -4i64);
|
|
p = p.link; continue;
|
|
};};
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_CALL) {
|
|
let tt: i32 = p.to.atype;
|
|
if (tt == D_EXTERN) {
|
|
emitbyte(a, 232u8); // 0xE8
|
|
let reloff: u64 = a.textlen;
|
|
emitu32(a, 0u32);
|
|
let s: *asym = intern(a, p.to.asym);
|
|
addreloc(a, reloff, 4, s, -4i64);
|
|
p = p.link; continue;
|
|
};
|
|
if (tt == D_BRANCH) {
|
|
emitbyte(a, 232u8);
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
if (isgpr(tt)) {
|
|
if (rhi(tt) != 0) { emitbyte(a, 65u8); };
|
|
emitbyte(a, 255u8); // 0xFF
|
|
emitbyte(a, modrmbyte(3, 2, rcode(tt)));
|
|
p = p.link; continue;
|
|
};
|
|
p = p.link; continue;
|
|
};
|
|
|
|
if (op == A_JMP) {
|
|
emitbyte(a, 233u8); // 0xE9
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
// Conditional jumps. 0x0F + cc + rel32.
|
|
let cc: u8 = 0u8;
|
|
let isjcc: bool = true;
|
|
switch (op) {
|
|
case A_JE: cc = 132u8; // 0x84
|
|
case A_JZ: cc = 132u8;
|
|
case A_JNE: cc = 133u8;
|
|
case A_JNZ: cc = 133u8;
|
|
case A_JL: cc = 140u8;
|
|
case A_JLE: cc = 142u8;
|
|
case A_JG: cc = 143u8;
|
|
case A_JGE: cc = 141u8;
|
|
case A_JB: cc = 130u8;
|
|
case A_JBE: cc = 134u8;
|
|
case A_JA: cc = 135u8;
|
|
case A_JAE: cc = 131u8;
|
|
case A_JP: cc = 138u8; // 0x8A, UCOMISD unordered (#97)
|
|
case: isjcc = false;
|
|
};
|
|
if (isjcc) {
|
|
emitbyte(a, 15u8);
|
|
emitbyte(a, cc);
|
|
addfixup(a, a.textlen, p.to.asym);
|
|
emitu32(a, 0u32);
|
|
p = p.link; continue;
|
|
};
|
|
|
|
os.write(2, "w6a: unsupported opcode\n".ptr, 23u64);
|
|
a.errs += 1;
|
|
p = p.link;
|
|
};
|
|
|
|
// Second pass: patch fixups (forward label refs).
|
|
let f: *afixup = a.fixups;
|
|
for (f != nil) {
|
|
if (!labeldefined(a, f.label)) {
|
|
os.write(2, "w6a: undefined label '".ptr, 21u64);
|
|
let lbl: str = f.label;
|
|
os.write(2, lbl.ptr, lbl.len: u64);
|
|
os.write(2, "'\n".ptr, 2u64);
|
|
a.errs += 1;
|
|
f = f.fnext;
|
|
continue;
|
|
};
|
|
let target: u64 = resolvelabel(a, f.label);
|
|
let rel: i64 = target: i64 - (f.off: i64 + 4i64);
|
|
let rel32: u32 = rel: u32;
|
|
a.text[f.off] = (rel32 & 255u32): u8;
|
|
a.text[f.off + 1u64] = ((rel32 >> 8u32) & 255u32): u8;
|
|
a.text[f.off + 2u64] = ((rel32 >> 16u32) & 255u32): u8;
|
|
a.text[f.off + 3u64] = ((rel32 >> 24u32) & 255u32): u8;
|
|
f = f.fnext;
|
|
};
|
|
return a.errs;
|
|
};
|
|
|
|
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
|
|
//
|
|
// Emit a tiny ELF64 relocatable object. Layout (in file order):
|
|
// [0] ELF header
|
|
// [1] Section .text (program bytes)
|
|
// [2] Section .rela.text (relocations)
|
|
// [3] Section .symtab
|
|
// [4] Section .strtab
|
|
// [5] Section .shstrtab
|
|
// [6] Section header table
|
|
//
|
|
// Symtab indices: 0 = STN_UNDEF, 1.. = our syms. Only GLOBAL symbols.
|
|
|
|
package w6a;
|
|
|
|
import os;
|
|
import opcodes;
|
|
|
|
// Local wrappers around os.writeall's tagged return — collapse the
|
|
// (i64 | oserror) back to a boolean / int sentinel for the
|
|
// length-checked / fire-and-forget write patterns below.
|
|
fn wrn(fd: i32, p: *u8, n: u64, want: i64) bool = {
|
|
let r: (i64 | os.oserror) = os.writeall(fd, p, n);
|
|
match (r) {
|
|
case let v: i64 => return v == want;
|
|
case let e: os.oserror => return false;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
fn wrdrop(fd: i32, p: *u8, n: u64) void = {
|
|
let r: (i64 | os.oserror) = os.writeall(fd, p, n);
|
|
match (r) {
|
|
case let v: i64 => { };
|
|
case let e: os.oserror => { };
|
|
};
|
|
};
|
|
|
|
// ---- ELF constants ----------------------------------------------------
|
|
def ELFCLASS64: u8 = 2u8;
|
|
def ELFDATA2LSB: u8 = 1u8;
|
|
def EV_CURRENT_W: u32 = 1u32;
|
|
def ET_REL_W: u16 = 1u16;
|
|
def EM_X86_64_W: u16 = 62u16;
|
|
|
|
def SHT_NULL_C: u32 = 0u32;
|
|
def SHT_PROGBITS_C: u32 = 1u32;
|
|
def SHT_SYMTAB_C: u32 = 2u32;
|
|
def SHT_STRTAB_C: u32 = 3u32;
|
|
def SHT_RELA_C: u32 = 4u32;
|
|
|
|
def SHF_WRITE: u64 = 1u64;
|
|
def SHF_ALLOC: u64 = 2u64;
|
|
def SHF_EXECINSTR: u64 = 4u64;
|
|
def SHF_INFO_LINK: u64 = 64u64; // 0x40
|
|
|
|
def STB_GLOBAL: u8 = 1u8;
|
|
def STT_NOTYPE: u8 = 0u8;
|
|
def STT_OBJECT: u8 = 1u8;
|
|
def STT_FUNC: u8 = 2u8;
|
|
|
|
// Sizes of fixed structures.
|
|
def EHDR_SZ: u64 = 64u64;
|
|
def SHDR_SZ: u64 = 64u64;
|
|
def SYM_SZ: u64 = 24u64;
|
|
def RELA_SZ: u64 = 24u64;
|
|
|
|
// ---- LE byte writers (own the bytes — write into a *u8 + offset) ----
|
|
|
|
fn wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
|
|
fn wru16(p: *u8, off: u64, v: u16) void = {
|
|
p[off] = (v & 255u16): u8;
|
|
p[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
|
};
|
|
fn wru32(p: *u8, off: u64, v: u32) void = {
|
|
p[off] = (v & 255u32): u8;
|
|
p[off + 1u64] = ((v >> 8u32) & 255u32): u8;
|
|
p[off + 2u64] = ((v >> 16u32) & 255u32): u8;
|
|
p[off + 3u64] = ((v >> 24u32) & 255u32): u8;
|
|
};
|
|
fn wru64(p: *u8, off: u64, v: u64) void = {
|
|
wru32(p, off, (v & 4294967295u64): u32);
|
|
wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
|
};
|
|
|
|
// ---- growable byte buffer ---------------------------------------------
|
|
|
|
type buf = struct {
|
|
p: *u8,
|
|
n: u64,
|
|
cap: u64,
|
|
};
|
|
|
|
fn bufinit(b: *buf) void = {
|
|
b.cap = 256u64;
|
|
b.n = 0u64;
|
|
let np: []u8 = alloc([], b.cap)!;
|
|
b.p = np.ptr;
|
|
};
|
|
|
|
fn bufgrow(b: *buf, need: u64) void = {
|
|
if (b.n + need <= b.cap) { return; };
|
|
let nc: u64 = b.cap;
|
|
for (nc < b.n + need) { nc = nc * 2u64; };
|
|
let np: []u8 = alloc([], nc)!;
|
|
let i: u64 = 0u64;
|
|
for (i < b.n) { np[i] = b.p[i]; i += 1u64; };
|
|
b.p = np.ptr;
|
|
b.cap = nc;
|
|
};
|
|
|
|
fn bufputb(b: *buf, src: *u8, n: u64) void = {
|
|
bufgrow(b, n);
|
|
let i: u64 = 0u64;
|
|
for (i < n) { b.p[b.n + i] = src[i]; i += 1u64; };
|
|
b.n += n;
|
|
};
|
|
|
|
// Write a NUL-terminated C-string copy of `s` into b. Returns offset
|
|
// where it started (suitable for st_name / sh_name fields).
|
|
fn bufputcstr(b: *buf, s: str) u32 = {
|
|
let off: u32 = b.n: u32;
|
|
bufgrow(b, s.len: u64 + 1u64);
|
|
let i: i32 = 0;
|
|
for (i < s.len) { b.p[b.n] = s[i]; b.n += 1u64; i += 1; };
|
|
b.p[b.n] = 0u8;
|
|
b.n += 1u64;
|
|
return off;
|
|
};
|
|
|
|
// ---- emitelf ---------------------------------------------------------
|
|
|
|
export fn emitelf(a: *asm_, fd: i32) i32 = {
|
|
let shstr: buf; bufinit(&shstr);
|
|
let str_: buf; bufinit(&str_);
|
|
let sym: buf; bufinit(&sym);
|
|
let rela: buf; bufinit(&rela);
|
|
let relad: buf; bufinit(&relad);
|
|
|
|
// Index 0 = empty.
|
|
let zero: u8 = 0u8;
|
|
bufputb(&shstr, &zero, 1u64);
|
|
bufputb(&str_, &zero, 1u64);
|
|
|
|
let hasdata: bool = a.datalen > 0u64;
|
|
let hasdatarelocs: bool = false;
|
|
let rscan: *areloc = a.relocs;
|
|
for (rscan != nil) {
|
|
if (rscan.section == 1) { hasdatarelocs = true; };
|
|
rscan = rscan.rnext;
|
|
};
|
|
|
|
// Section indices (mirror cmd/w6a/obj.c):
|
|
// without data, without data-relocs:
|
|
// 1=.text 2=.rela.text 3=.symtab 4=.strtab 5=.shstrtab
|
|
// with data, no data-relocs:
|
|
// 1=.text 2=.rela.text 3=.data 4=.symtab 5=.strtab 6=.shstrtab
|
|
// with data + data-relocs:
|
|
// 1=.text 2=.rela.text 3=.data 4=.rela.data 5=.symtab
|
|
// 6=.strtab 7=.shstrtab
|
|
let SH_TEXT: u16 = 1u16;
|
|
let SH_DATA: u16 = 0u16;
|
|
let SH_RELAD: u16 = 0u16;
|
|
let SH_SYMTAB: u16 = 3u16;
|
|
if (hasdata) {
|
|
SH_DATA = 3u16;
|
|
if (hasdatarelocs) {
|
|
SH_RELAD = 4u16;
|
|
SH_SYMTAB = 5u16;
|
|
} else {
|
|
SH_SYMTAB = 4u16;
|
|
};
|
|
};
|
|
let SH_STRTAB: u16 = SH_SYMTAB + 1u16;
|
|
let SH_SHSTR: u16 = SH_STRTAB + 1u16;
|
|
|
|
// Section name offsets. Append .data / .rela.data only when
|
|
// used so the .shstrtab buffer stays byte-identical for the
|
|
// no-DATAW case (test 991 byte-diff invariant).
|
|
let shntext: u32 = bufputcstr(&shstr, ".text");
|
|
let shnrela: u32 = bufputcstr(&shstr, ".rela.text");
|
|
let shndata: u32 = 0u32;
|
|
let shnrelad: u32 = 0u32;
|
|
if (hasdata) { shndata = bufputcstr(&shstr, ".data"); };
|
|
if (hasdata) { if (hasdatarelocs) {
|
|
shnrelad = bufputcstr(&shstr, ".rela.data");
|
|
};};
|
|
let shnsymtab: u32 = bufputcstr(&shstr, ".symtab");
|
|
let shnstrtab: u32 = bufputcstr(&shstr, ".strtab");
|
|
let shnshstrtab: u32 = bufputcstr(&shstr, ".shstrtab");
|
|
|
|
// Symbol 0 — STN_UNDEF (24 zero bytes).
|
|
let zsym: [24]u8;
|
|
let zi: i32 = 0;
|
|
for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
|
|
bufputb(&sym, zsym.ptr, 24u64);
|
|
|
|
// Build symbols.
|
|
let idx: i32 = 1;
|
|
let s: *asym = a.syms;
|
|
for (s != nil) {
|
|
let entry: [24]u8;
|
|
let ei: i32 = 0;
|
|
for (ei < 24) { entry[ei] = 0u8; ei += 1; };
|
|
let stname: u32 = bufputcstr(&str_, s.name);
|
|
wru32(entry.ptr, 0u64, stname);
|
|
if (s.defined != 0) {
|
|
if (s.isdata != 0) {
|
|
wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_OBJECT));
|
|
wru16(entry.ptr, 6u64, SH_DATA);
|
|
} else {
|
|
wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_FUNC));
|
|
wru16(entry.ptr, 6u64, SH_TEXT);
|
|
};
|
|
wru64(entry.ptr, 8u64, s.addr);
|
|
} else {
|
|
wru8(entry.ptr, 4u64, ((STB_GLOBAL << 4u8) | STT_NOTYPE));
|
|
wru16(entry.ptr, 6u64, 0u16);
|
|
};
|
|
bufputb(&sym, entry.ptr, 24u64);
|
|
s.idx = idx;
|
|
idx += 1;
|
|
s = s.snext;
|
|
};
|
|
|
|
// Build relocations — split into text vs data buffers.
|
|
let r: *areloc = a.relocs;
|
|
for (r != nil) {
|
|
let entry: [24]u8;
|
|
wru64(entry.ptr, 0u64, r.off);
|
|
let rinfo: u64 = (r.asy.idx: u64 << 32u64) | (r.kind: u64 & 4294967295u64);
|
|
wru64(entry.ptr, 8u64, rinfo);
|
|
wru64(entry.ptr, 16u64, r.addend: u64);
|
|
if (r.section == 1) {
|
|
bufputb(&relad, entry.ptr, 24u64);
|
|
} else {
|
|
bufputb(&rela, entry.ptr, 24u64);
|
|
};
|
|
r = r.rnext;
|
|
};
|
|
|
|
// File offsets.
|
|
let off: u64 = EHDR_SZ;
|
|
let offtext: u64 = off; off = off + a.textlen;
|
|
let offrela: u64 = off; off = off + rela.n;
|
|
let offdata: u64 = off; if (hasdata) { off = off + a.datalen; };
|
|
let offrelad: u64 = off; if (hasdata) { if (hasdatarelocs) {
|
|
off = off + relad.n;
|
|
};};
|
|
let offsym: u64 = off; off = off + sym.n;
|
|
let offstr: u64 = off; off = off + str_.n;
|
|
let offshstr: u64 = off; off = off + shstr.n;
|
|
for ((off & 7u64) != 0u64) { off += 1u64; };
|
|
let offshdr: u64 = off;
|
|
let NSECT: u16 = 6u16;
|
|
if (hasdata) {
|
|
if (hasdatarelocs) { NSECT = 8u16; }
|
|
else { NSECT = 7u16; };
|
|
};
|
|
|
|
// ---- Ehdr ----
|
|
let eh: [64]u8;
|
|
let i: i32 = 0;
|
|
for (i < 64) { eh[i] = 0u8; i += 1; };
|
|
eh[0] = 127u8; // 0x7f
|
|
eh[1] = 'E';
|
|
eh[2] = 'L';
|
|
eh[3] = 'F';
|
|
eh[4] = ELFCLASS64;
|
|
eh[5] = ELFDATA2LSB;
|
|
eh[6] = EV_CURRENT_W: u8;
|
|
wru16(eh.ptr, 16u64, ET_REL_W);
|
|
wru16(eh.ptr, 18u64, EM_X86_64_W);
|
|
wru32(eh.ptr, 20u64, EV_CURRENT_W);
|
|
wru64(eh.ptr, 24u64, 0u64); // e_entry
|
|
wru64(eh.ptr, 32u64, 0u64); // e_phoff
|
|
wru64(eh.ptr, 40u64, offshdr); // e_shoff
|
|
wru32(eh.ptr, 48u64, 0u32); // e_flags
|
|
wru16(eh.ptr, 52u64, 64u16); // e_ehsize
|
|
wru16(eh.ptr, 54u64, 0u16); // e_phentsize
|
|
wru16(eh.ptr, 56u64, 0u16); // e_phnum
|
|
wru16(eh.ptr, 58u64, 64u16); // e_shentsize
|
|
wru16(eh.ptr, 60u64, NSECT); // e_shnum
|
|
wru16(eh.ptr, 62u64, SH_SHSTR); // e_shstrndx
|
|
|
|
if (!wrn(fd, eh.ptr, 64u64, 64i64)) { return -1; };
|
|
if (a.textlen > 0u64) {
|
|
if (!wrn(fd, a.text, a.textlen, a.textlen: i64)) { return -1; };
|
|
};
|
|
if (rela.n > 0u64) {
|
|
if (!wrn(fd, rela.p, rela.n, rela.n: i64)) { return -1; };
|
|
};
|
|
if (hasdata) {
|
|
if (a.datalen > 0u64) {
|
|
if (!wrn(fd, a.data, a.datalen, a.datalen: i64)) { return -1; };
|
|
};
|
|
if (hasdatarelocs) {
|
|
if (relad.n > 0u64) {
|
|
if (!wrn(fd, relad.p, relad.n, relad.n: i64)) { return -1; };
|
|
};
|
|
};
|
|
};
|
|
if (sym.n > 0u64) {
|
|
if (!wrn(fd, sym.p, sym.n, sym.n: i64)) { return -1; };
|
|
};
|
|
if (str_.n > 0u64) {
|
|
if (!wrn(fd, str_.p, str_.n, str_.n: i64)) { return -1; };
|
|
};
|
|
if (shstr.n > 0u64) {
|
|
if (!wrn(fd, shstr.p, shstr.n, shstr.n: i64)) { return -1; };
|
|
};
|
|
|
|
// Pad to 8 before shdrs.
|
|
let written: u64 = EHDR_SZ + a.textlen + rela.n + sym.n + str_.n + shstr.n;
|
|
if (hasdata) {
|
|
written += a.datalen;
|
|
if (hasdatarelocs) { written += relad.n; };
|
|
};
|
|
for ((written & 7u64) != 0u64) {
|
|
wrdrop(fd, &zero, 1u64);
|
|
written += 1u64;
|
|
};
|
|
|
|
// Section header table — 6 headers of 64 bytes each = 384 bytes.
|
|
let shbuf: [64]u8;
|
|
// SHT_NULL
|
|
let sn: i32 = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
// .text
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shntext);
|
|
wru32(shbuf.ptr, 4u64, SHT_PROGBITS_C);
|
|
wru64(shbuf.ptr, 8u64, SHF_ALLOC | SHF_EXECINSTR);
|
|
wru64(shbuf.ptr, 24u64, offtext);
|
|
wru64(shbuf.ptr, 32u64, a.textlen);
|
|
wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
// .rela.text
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shnrela);
|
|
wru32(shbuf.ptr, 4u64, SHT_RELA_C);
|
|
wru64(shbuf.ptr, 8u64, SHF_INFO_LINK);
|
|
wru64(shbuf.ptr, 24u64, offrela);
|
|
wru64(shbuf.ptr, 32u64, rela.n);
|
|
wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32); // sh_link
|
|
wru32(shbuf.ptr, 44u64, 1u32); // sh_info = .text idx
|
|
wru64(shbuf.ptr, 48u64, 8u64);
|
|
wru64(shbuf.ptr, 56u64, RELA_SZ);
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
if (hasdata) {
|
|
// .data
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shndata);
|
|
wru32(shbuf.ptr, 4u64, SHT_PROGBITS_C);
|
|
wru64(shbuf.ptr, 8u64, SHF_ALLOC | SHF_WRITE);
|
|
wru64(shbuf.ptr, 24u64, offdata);
|
|
wru64(shbuf.ptr, 32u64, a.datalen);
|
|
wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
if (hasdatarelocs) {
|
|
// .rela.data
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shnrelad);
|
|
wru32(shbuf.ptr, 4u64, SHT_RELA_C);
|
|
wru64(shbuf.ptr, 8u64, SHF_INFO_LINK);
|
|
wru64(shbuf.ptr, 24u64, offrelad);
|
|
wru64(shbuf.ptr, 32u64, relad.n);
|
|
wru32(shbuf.ptr, 40u64, SH_SYMTAB: u32);
|
|
wru32(shbuf.ptr, 44u64, SH_DATA: u32); // applies to .data
|
|
wru64(shbuf.ptr, 48u64, 8u64);
|
|
wru64(shbuf.ptr, 56u64, RELA_SZ);
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
};
|
|
};
|
|
// .symtab
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shnsymtab);
|
|
wru32(shbuf.ptr, 4u64, SHT_SYMTAB_C);
|
|
wru64(shbuf.ptr, 24u64, offsym);
|
|
wru64(shbuf.ptr, 32u64, sym.n);
|
|
wru32(shbuf.ptr, 40u64, SH_STRTAB: u32); // sh_link
|
|
wru32(shbuf.ptr, 44u64, 1u32); // sh_info = one local (STN_UNDEF)
|
|
wru64(shbuf.ptr, 48u64, 8u64);
|
|
wru64(shbuf.ptr, 56u64, SYM_SZ);
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
// .strtab
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shnstrtab);
|
|
wru32(shbuf.ptr, 4u64, SHT_STRTAB_C);
|
|
wru64(shbuf.ptr, 24u64, offstr);
|
|
wru64(shbuf.ptr, 32u64, str_.n);
|
|
wru64(shbuf.ptr, 48u64, 1u64);
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
// .shstrtab
|
|
sn = 0;
|
|
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
|
|
wru32(shbuf.ptr, 0u64, shnshstrtab);
|
|
wru32(shbuf.ptr, 4u64, SHT_STRTAB_C);
|
|
wru64(shbuf.ptr, 24u64, offshstr);
|
|
wru64(shbuf.ptr, 32u64, shstr.n);
|
|
wru64(shbuf.ptr, 48u64, 1u64);
|
|
wrdrop(fd, shbuf.ptr, 64u64);
|
|
|
|
return 0;
|
|
};
|
|
|
|
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c.
|
|
//
|
|
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.
|
|
//
|
|
// w6a_ww -o file.o file.s
|
|
|
|
package main;
|
|
|
|
import os;
|
|
import rt;
|
|
import strings;
|
|
import opcodes;
|
|
import lex;
|
|
import parse;
|
|
import asm;
|
|
import obj;
|
|
|
|
fn cstreq(a: *u8, lit: str) bool = {
|
|
let n: u64 = lit.len: u64;
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
let li: i32 = i: i32;
|
|
if (a[i] != lit[li]) { return false; };
|
|
i += 1u64;
|
|
};
|
|
if (a[i] != 0u8) { return false; };
|
|
return true;
|
|
};
|
|
|
|
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).
|
|
fn pathstr(p: *u8) str = {
|
|
let r: str;
|
|
r.ptr = p;
|
|
r.len = cstrlen(p): i32;
|
|
return r;
|
|
};
|
|
|
|
// Slurp the whole file into a fresh buffer.
|
|
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 nz: u64 = n: u64;
|
|
let buf: []u8 = alloc([], nz + 1u64)!;
|
|
buf.len = (nz + 1u64): i32;
|
|
let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nz);
|
|
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; };
|
|
buf[nz] = 0u8;
|
|
return buf.ptr, nz;
|
|
};
|
|
|
|
export fn main(argc: i32, argv: **u8) i32 = {
|
|
let src: *u8 = nil;
|
|
let out: *u8 = nil;
|
|
|
|
let i: i32 = 1;
|
|
for (i < argc) {
|
|
let a: *u8 = argv[i];
|
|
if (cstreq(a, "-o")) {
|
|
i += 1;
|
|
if (i >= argc) {
|
|
os.write(2, "w6a: -o requires arg\n".ptr, 20u64);
|
|
return 2;
|
|
};
|
|
out = argv[i];
|
|
} else { if (a[0u64] == 45u8) {
|
|
os.write(2, "w6a: unknown flag\n".ptr, 17u64);
|
|
return 2;
|
|
} else {
|
|
if (src != nil) {
|
|
os.write(2, "w6a: only one input\n".ptr, 19u64);
|
|
return 2;
|
|
};
|
|
src = a;
|
|
}; };
|
|
i += 1;
|
|
};
|
|
|
|
if (src == nil) {
|
|
os.write(2, "usage: w6a_ww -o file.o file.s\n".ptr, 30u64);
|
|
return 2;
|
|
};
|
|
if (out == nil) {
|
|
os.write(2, "w6a: missing -o\n".ptr, 15u64);
|
|
return 2;
|
|
};
|
|
|
|
let buf: *u8;
|
|
let blen: u64;
|
|
buf, blen = slurp(src);
|
|
if (buf == nil) {
|
|
os.write(2, "w6a: cannot read input\n".ptr, 22u64);
|
|
return 1;
|
|
};
|
|
|
|
let s: asm_;
|
|
let nlen: u64 = cstrlen(src);
|
|
let view: str;
|
|
view.ptr = src;
|
|
view.len = nlen: i32;
|
|
let fname: str = strings.dup(view);
|
|
init(&s, fname, buf, blen);
|
|
|
|
if (parse(&s) != 0) { return 1; };
|
|
if (encode(&s) != 0) { return 1; };
|
|
|
|
// Open output for write.
|
|
let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
|
if (fd < 0) {
|
|
os.write(2, "w6a: cannot open output\n".ptr, 23u64);
|
|
return 1;
|
|
};
|
|
let rc: i32 = emitelf(&s, fd);
|
|
os.close(fd);
|
|
return rc;
|
|
};
|
|
|