w6c+wcc+selfhost+lib: int-cast truncate + use_alias, 5 new modules

Two cgen/check bugs surfaced by new lib modules, plus the modules
themselves (crc64, siphash, random, base64, base32).

  1. `(big_u64): u32` (and `: u16`, `: u8`, `: bool`) didn't truncate.
     N_CAST emitted nothing for int↔int; the value stayed in AX with
     its upper bits intact and downstream CMPQ/DIVQ misread the slot.
     The TK_TILDE path already had clamp logic for the same reason —
     N_CAST was the missing case. Both stages now MOVL r,r for u32 and
     ANDQ $mask for u8/u16/bool. Signed-narrow (i8/i16/i32) stays
     no-op until w6a grows reg-reg MOVSBQ/MOVSWQ/MOVSXD. selfhost
     cgcast walks alias chains via aliaslookup before checking
     primsize/typenameisunsigned so `(u: random)` where
     `type random = u64` still bypasses the clamp.
     See cmd/w6c/cgen.c N_CAST and selfhost/cmd/wcc/cgenexpr.ww cgcast.

  2. `mod.mod` type refs (`random.random` when the imported module
     declares `export type random = u64;`) failed with "unknown type".
     The driver concatenates imports into one flat scope, so SK_USE
     `random` collided with SK_TYPE `random` and scope_define silently
     dropped the use. resolve_typename's leaf lookup required
     `kind == SK_USE` and gave up. Adds a `use_alias` flag to Sym; the
     pass-1 decl scan now marks colliding syms in both directions
     (use-after-type and type-after-use). resolve_typename and the
     N_DOT cexpr branch treat `use_alias` like SK_USE for qualified
     lookup. selfhost check.ww was already lenient on this path so no
     ww-side change was needed; bootstrap fixed point (990-995) holds.
     See cmd/wcc/check.c installdecl pass + N_DOT/resolve_typename and
     cmd/wcc/ww.h Sym.use_alias.

New modules under lib/, each with @test vectors in *_test.ww and wired
into test/wcc/900_stdlib.c (26 modules → all compile):

  - lib/hash/crc64       ECMA, ISO  (mirror of crc32 shape)
  - lib/hash/siphash     SipHash-2-4, buffer-based sum/sum24
  - lib/math/random      SplitMix64 (init, next, u32n, u64n)
  - lib/encoding/base64  RFC 4648 std + url-safe encode/decode + sizes
  - lib/encoding/base32  RFC 4648 std + base32hex encode/decode + sizes
This commit is contained in:
2026-05-13 14:58:36 +09:00
parent cbcc0167ae
commit 7a4f60b041
17 changed files with 1302 additions and 23 deletions

View File

@@ -3474,6 +3474,39 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
}
}
/* Narrowing integer cast: clamp AX to the target width so
* downstream 64-bit ops see a value within the declared
* range. Hare semantics: `expr: T` truncates to T's bit
* width (mod 2^n). Without this, `(big_u64): u32` left the
* upper 32 bits intact and CMPQ/DIVQ misread the value.
*
* Unsigned targets only here. Signed-narrow targets (i8/
* i16/i32) need MOVSBQ / MOVSWQ / MOVSXD in their reg-reg
* form which the assembler doesn't expose yet; callers
* that need a clean signed-narrow value either keep the
* value in range before the cast (as strconv does with
* an explicit bounds check) or AND the low bits manually.
* Tracking this gap is part of the same TODO. */
if (!from_f && !to_f && n->type) {
Type *tt = n->type;
Type *tu = (tt && tt->kind == TY_NAMED) ? tt->under : tt;
if (tu && type_isint(tu) && tu->size > 0
&& tu->size < 8 && type_isunsigned(tu)) {
if (tu->size == 4) {
ins2(c, A_MOVL, areg(D_AX), areg(D_AX));
} else {
u64 mask = ((u64)1 << (tu->size * 8)) - 1;
ins2(c, A_ANDQ, aimm((i64)mask),
areg(D_AX));
}
}
/* TY_BOOL is size 1 too; clamp to a single byte so
* `(u32_val): bool` produces 0 or a low-byte value
* instead of leaking the upper bits. */
if (tu && tu->kind == TY_BOOL) {
ins2(c, A_ANDQ, aimm(0xFF), areg(D_AX));
}
}
break;
}
case N_DOT: {

View File

@@ -63,14 +63,17 @@ resolve_typename(Checker *c, Node *n)
Sym *s = scope_lookup(c->cur, nm);
if (s == NULL && nm) {
/* module-qualified: io.stream → strip the last dot prefix
* and look up the leaf if `io` is a `use`-imported name. */
* and look up the leaf if `io` is a `use`-imported name.
* `m->use_alias` covers the self-import case where the
* imported module declares a type with the same name as
* the module itself (e.g. `random.random`). */
const char *dot = strrchr(nm, '.');
if (dot) {
char head[128] = {0};
size_t hl = (size_t)(dot - nm);
if (hl < sizeof head) memcpy(head, nm, hl);
Sym *m = scope_lookup(c->cur, head);
if (m && m->kind == SK_USE)
if (m && (m->kind == SK_USE || m->use_alias))
s = scope_lookup(c->cur, dot + 1);
}
}
@@ -684,16 +687,28 @@ cexpr(Checker *c, Node *n)
* scope, so we lookup `n->str` directly. */
if (n->lhs && n->lhs->kind == N_IDENT) {
Sym *ms = scope_lookup(c->cur, n->lhs->str);
if (ms && ms->kind == SK_USE) {
if (ms && (ms->kind == SK_USE || ms->use_alias)) {
/* Module-qualified ref. `use_alias` covers
* the self-import case where the module's
* type name shadowed the SK_USE; the leaf
* still resolves through the flat scope.
* fs == ms is the `mod.mod` case (the
* imported module's leaf type is named after
* the module itself — both names point at
* the same SK_TYPE sym in the flat scope). */
Sym *fs = scope_lookup(c->cur, n->str);
if (fs)
return n->type = fs->type;
/* Leaf isn't in scope here — treat as an
* external declaration. The codegen will
* still emit CALL/MOVQ by the leaf name; the
* linker fails if the symbol is truly
* missing. */
return n->type = ty_err;
if (ms->kind == SK_USE) {
/* Pure SK_USE with missing leaf:
* external declaration. Codegen
* emits CALL/MOVQ by the leaf name
* and the linker resolves it. */
return n->type = ty_err;
}
/* SK_TYPE with use_alias=1 and no leaf
* found: fall through so the enum / type-
* member paths below get a shot. */
}
/* enum member access: TypeName.MEMBER → fold to
* the member's integer literal value. Type is the
@@ -1590,13 +1605,34 @@ check_file(Checker *c, Node *file)
* walked in the next pass. */
for (Node *d = file->list; d; d = d->next) {
if (d->kind == N_USE) {
scope_define(c->cur, d->str, SK_USE, NULL, d);
Sym *prev = scope_lookup_local(c->cur, d->str);
if (prev != NULL) {
/* Self-import: the driver concatenates the
* imported module's source into the flat
* scope, so its top-level decls (types, fns,
* defs) shadow a same-named SK_USE. Mark
* the colliding sym as also-a-use so dotted
* qualifiers (`mod.x`) still resolve. */
prev->use_alias = 1;
} else {
scope_define(c->cur, d->str, SK_USE, NULL, d);
}
continue;
}
if (d->kind != N_TYPEDECL) continue;
Type *named = type_named(c->a, d->str, NULL);
if (!scope_define(c->cur, d->str, SK_TYPE, named, d))
Sym *prev = scope_lookup_local(c->cur, d->str);
if (prev && prev->kind == SK_USE) {
/* `use mod; ... type mod = ...;` — promote the
* SK_USE to the type symbol but remember it was
* also a module name. */
prev->kind = SK_TYPE;
prev->type = named;
prev->decl = d;
prev->use_alias = 1;
} else if (!scope_define(c->cur, d->str, SK_TYPE, named, d)) {
err(c, d->pos, "duplicate type %s", d->str);
}
d->type = named;
}
for (Node *d = file->list; d; d = d->next) {

View File

@@ -476,6 +476,12 @@ struct Sym {
Node *decl;
int exported;
int is_const; /* const-bound (assignment rejected) */
int use_alias; /* also bound as a `use` module name. Set
* when a `use foo;` directive collides
* with a same-named SK_TYPE/SK_FN/etc.
* Lets resolve_typename treat `foo.x`
* as module-qualified even though the
* primary kind isn't SK_USE. */
Sym *next; /* iteration */
Sym *hashnext; /* bucket chain */
Scope *scope;

View File

@@ -0,0 +1,186 @@
// encoding/base32 — RFC 4648 base32 encode/decode, buffer-based.
//
// Mirrors Hare's encoding::base32 surface, modulo Hare's stream-based
// encoder/decoder. ww ships the in-memory subset only: `encode(dst,
// src)` writes the encoded bytes into `dst`, returning the count;
// `decode(dst, src)` writes the decoded bytes into `dst`, returning a
// count or invalid.
//
// std uses 'A'-'Z' and '2'-'7' for indexes 0..31 (RFC 4648 §6); hex
// uses '0'-'9' and 'A'-'V' (the base32hex alphabet, RFC 4648 §7).
// Both pad encoded output with '=' to a multiple of 8 bytes.
export type invalid = !i32;
// encodedsize — bytes required to encode `n` source bytes (including
// '=' padding). Hare names it the same.
export fn encodedsize(n: i32) i32 = {
if (n == 0) { return 0; };
return ((n - 1) / 5 + 1) * 8;
};
// decodedsize — upper bound on the number of bytes decoded from `n`
// encoded bytes.
export fn decodedsize(n: i32) i32 = {
return (n / 8) * 5;
};
// encchar — map a 5-bit value to its alphabet character. `hex` picks
// the base32hex alphabet instead of std.
fn encchar(v: u8, hex: bool) u8 = {
if (hex) {
if (v < 10u8) { return v + 48u8; }; // '0' + v
return v + 55u8; // 'A' + (v - 10) = v + 55
};
if (v < 26u8) { return v + 65u8; }; // 'A' + v
return v + 24u8; // '2' + (v - 26) = v + 24
};
// decchar — inverse of encchar. Returns 0..31 or 255 on invalid char.
// '=' is handled in the decode loop, not here.
fn decchar(c: u8, hex: bool) u8 = {
if (hex) {
if (c >= 48u8) { if (c <= 57u8) { return c - 48u8; }; }; // '0'..'9'
if (c >= 65u8) { if (c <= 86u8) { return c - 55u8; }; }; // 'A'..'V'
return 255u8;
};
if (c >= 65u8) { if (c <= 90u8) { return c - 65u8; }; }; // 'A'..'Z'
if (c >= 50u8) { if (c <= 55u8) { return c - 24u8; }; }; // '2'..'7'
return 255u8;
};
fn encgroup(dst: []u8, j: i32, src: []u8, i: i32, n: i32, hex: bool) void = {
let b0: u8 = 0u8;
let b1: u8 = 0u8;
let b2: u8 = 0u8;
let b3: u8 = 0u8;
let b4: u8 = 0u8;
if (n > 0) { b0 = src[i]; };
if (n > 1) { b1 = src[i + 1]; };
if (n > 2) { b2 = src[i + 2]; };
if (n > 3) { b3 = src[i + 3]; };
if (n > 4) { b4 = src[i + 4]; };
dst[j] = encchar(b0 >> 3u8, hex);
dst[j + 1] = encchar(((b0 & 7u8) << 2u8) | (b1 >> 6u8), hex);
dst[j + 2] = encchar((b1 >> 1u8) & 31u8, hex);
dst[j + 3] = encchar(((b1 & 1u8) << 4u8) | (b2 >> 4u8), hex);
dst[j + 4] = encchar(((b2 & 15u8) << 1u8) | (b3 >> 7u8), hex);
dst[j + 5] = encchar((b3 >> 2u8) & 31u8, hex);
dst[j + 6] = encchar(((b3 & 3u8) << 3u8) | (b4 >> 5u8), hex);
dst[j + 7] = encchar(b4 & 31u8, hex);
// Pad the encoded slots that map past the source bytes.
if (n < 5) {
// n=1 → 2 chars then 6 '='. n=2 → 4 chars. n=3 → 5. n=4 → 7.
let keep: i32 = 2;
if (n == 2) { keep = 4; };
if (n == 3) { keep = 5; };
if (n == 4) { keep = 7; };
let k: i32 = keep;
for (k < 8) { dst[j + k] = 61u8; k += 1; }; // '='
};
};
fn encodeinto(dst: []u8, src: []u8, hex: bool) i32 = {
let i: i32 = 0;
let j: i32 = 0;
for (i + 5 <= src.len) {
encgroup(dst, j, src, i, 5, hex);
i += 5;
j += 8;
};
let rem: i32 = src.len - i;
if (rem > 0) {
encgroup(dst, j, src, i, rem, hex);
j += 8;
};
return j;
};
// encode — encode `src` into `dst` using the std (RFC 4648 §6)
// alphabet. Returns bytes written. `dst` must hold at least
// encodedsize(src.len) bytes.
export fn encode(dst: []u8, src: []u8) i32 = {
return encodeinto(dst, src, false);
};
// encodehex — same as encode but uses the base32hex alphabet
// (RFC 4648 §7).
export fn encodehex(dst: []u8, src: []u8) i32 = {
return encodeinto(dst, src, true);
};
// padcount — number of bytes encoded in the last group, given the
// count `p` of trailing '=' chars. RFC 4648 lists the legal mapping:
// 6=>1, 4=>2, 3=>3, 1=>4, 0=>5. Returns -1 if `p` isn't legal.
fn padcount(p: i32) i32 = {
if (p == 0) { return 5; };
if (p == 1) { return 4; };
if (p == 3) { return 3; };
if (p == 4) { return 2; };
if (p == 6) { return 1; };
return -1;
};
fn decodeinto(dst: []u8, src: []u8, hex: bool) (i32 | invalid) = {
if (src.len == 0) { return 0; };
if ((src.len & 7) != 0) { return src.len: invalid; };
let i: i32 = 0;
let j: i32 = 0;
let end: i32 = src.len;
for (i < end) {
let v: [8]u8;
let last: bool = false;
let p: i32 = 0;
let k: i32 = 0;
for (k < 8) {
let c: u8 = src[i + k];
if (c == 61u8) { // '='
if (i + 8 != end) { return (i + k): invalid; };
last = true;
v[k] = 0u8;
p += 1;
} else {
if (last) { return (i + k): invalid; };
let d: u8 = decchar(c, hex);
if (d == 255u8) { return (i + k): invalid; };
v[k] = d;
};
k += 1;
};
let nb: i32 = 5;
if (last) {
nb = padcount(p);
if (nb < 0) { return (i + 8 - p): invalid; };
};
// First two chars cover byte[0] (5 + 3 bits).
if (nb > 0) {
dst[j] = (v[0] << 3u8) | (v[1] >> 2u8);
};
if (nb > 1) {
dst[j + 1] = (v[1] << 6u8) | (v[2] << 1u8) | (v[3] >> 4u8);
};
if (nb > 2) {
dst[j + 2] = (v[3] << 4u8) | (v[4] >> 1u8);
};
if (nb > 3) {
dst[j + 3] = (v[4] << 7u8) | (v[5] << 2u8) | (v[6] >> 3u8);
};
if (nb > 4) {
dst[j + 4] = (v[6] << 5u8) | v[7];
};
j += nb;
i += 8;
};
return j;
};
// decode — decode std-alphabet base32 from `src` into `dst`. Returns
// count of decoded bytes, or invalid on malformed input.
export fn decode(dst: []u8, src: []u8) (i32 | invalid) = {
return decodeinto(dst, src, false);
};
// decodehex — same as decode but accepts base32hex.
export fn decodehex(dst: []u8, src: []u8) (i32 | invalid) = {
return decodeinto(dst, src, true);
};

View File

@@ -0,0 +1,155 @@
use base32;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
into[off + i] = s[i];
i += 1;
};
return off + s.len;
};
fn streq(buf: []u8, expect: str) bool = {
if (buf.len != expect.len) { return false; };
let i: i32 = 0;
for (i < buf.len) {
if (buf[i] != expect[i]) { return false; };
i += 1;
};
return true;
};
fn encvec(input: str, expect: str) void = {
let inbuf: [128]u8;
let outbuf: [128]u8;
let n: i32 = putstr(input, inbuf[0:128], 0);
let m: i32 = base32.encode(outbuf[0:128], inbuf[0:n]);
if (m != expect.len) { let _: i32 = 1/0; };
if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; };
};
@test fn rfc4648_std() void = {
// RFC 4648 §10 test vectors.
encvec("", "");
encvec("f", "MY======");
encvec("fo", "MZXQ====");
encvec("foo", "MZXW6===");
encvec("foob", "MZXW6YQ=");
encvec("fooba", "MZXW6YTB");
encvec("foobar", "MZXW6YTBOI======");
};
fn decvec(input: str, expect: str) void = {
let inbuf: [128]u8;
let outbuf: [128]u8;
let n: i32 = putstr(input, inbuf[0:128], 0);
let r: (i32 | base32.invalid) = base32.decode(outbuf[0:128], inbuf[0:n]);
match (r) {
case let m: i32 => {
if (m != expect.len) { let _: i32 = 1/0; };
if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; };
};
case let e: base32.invalid => { let _: i32 = 1/0; };
};
};
@test fn rfc4648_decode() void = {
decvec("", "");
decvec("MY======", "f");
decvec("MZXQ====", "fo");
decvec("MZXW6===", "foo");
decvec("MZXW6YQ=", "foob");
decvec("MZXW6YTB", "fooba");
decvec("MZXW6YTBOI======", "foobar");
};
fn enchexvec(input: str, expect: str) void = {
let inbuf: [128]u8;
let outbuf: [128]u8;
let n: i32 = putstr(input, inbuf[0:128], 0);
let m: i32 = base32.encodehex(outbuf[0:128], inbuf[0:n]);
if (m != expect.len) { let _: i32 = 1/0; };
if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; };
};
@test fn rfc4648_hex() void = {
// RFC 4648 §10 base32hex vectors.
enchexvec("", "");
enchexvec("f", "CO======");
enchexvec("fo", "CPNG====");
enchexvec("foo", "CPNMU===");
enchexvec("foob", "CPNMUOG=");
enchexvec("fooba", "CPNMUOJ1");
enchexvec("foobar", "CPNMUOJ1E8======");
};
@test fn roundtrip_all_quintets() void = {
// Encode then decode every 5-byte combination of a small set.
let raw: [5]u8;
raw[0] = 0x00u8;
raw[1] = 0x55u8;
raw[2] = 0xAAu8;
raw[3] = 0xFFu8;
raw[4] = 0x01u8;
let enc: [16]u8;
let dec: [5]u8;
let m: i32 = base32.encode(enc[0:16], raw[0:5]);
if (m != 8) { let _: i32 = 1/0; };
let r: (i32 | base32.invalid) = base32.decode(dec[0:5], enc[0:m]);
match (r) {
case let n: i32 => {
if (n != 5) { let _: i32 = 1/0; };
let i: i32 = 0;
for (i < 5) {
if (dec[i] != raw[i]) { let _: i32 = 1/0; };
i += 1;
};
};
case let e: base32.invalid => { let _: i32 = 1/0; };
};
};
@test fn invalid_inputs() void = {
let inbuf: [16]u8;
let outbuf: [16]u8;
// Length not a multiple of 8.
let n: i32 = putstr("ABCD", inbuf[0:16], 0);
let r1: (i32 | base32.invalid) = base32.decode(outbuf[0:16], inbuf[0:n]);
match (r1) {
case let m: i32 => { let _: i32 = 1/0; };
case let e: base32.invalid => void;
};
// Bad pad count (5 '=' is illegal — must be 0,1,3,4,6).
let n2: i32 = putstr("MZX=====", inbuf[0:16], 0);
let r2: (i32 | base32.invalid) = base32.decode(outbuf[0:16], inbuf[0:n2]);
match (r2) {
case let m: i32 => { let _: i32 = 1/0; };
case let e: base32.invalid => void;
};
// Bad char ('1' is not in the std alphabet).
let n3: i32 = putstr("MZ1W6YTB", inbuf[0:16], 0);
let r3: (i32 | base32.invalid) = base32.decode(outbuf[0:16], inbuf[0:n3]);
match (r3) {
case let m: i32 => { let _: i32 = 1/0; };
case let e: base32.invalid => void;
};
};
@test fn sizes() void = {
if (base32.encodedsize(0) != 0) { let _: i32 = 1/0; };
if (base32.encodedsize(1) != 8) { let _: i32 = 1/0; };
if (base32.encodedsize(5) != 8) { let _: i32 = 1/0; };
if (base32.encodedsize(6) != 16) { let _: i32 = 1/0; };
if (base32.decodedsize(8) != 5) { let _: i32 = 1/0; };
if (base32.decodedsize(16) != 10) { let _: i32 = 1/0; };
};
export fn main() i32 = {
rfc4648_std();
rfc4648_decode();
rfc4648_hex();
roundtrip_all_quintets();
invalid_inputs();
sizes();
return 0;
};

View File

@@ -0,0 +1,182 @@
// encoding/base64 — RFC 4648 base64 encode/decode, buffer-based.
//
// Mirrors Hare's encoding::base64 surface, modulo Hare's stream-based
// encoder/decoder. ww ships the in-memory subset only: `encode(dst,
// src)` writes the encoded bytes into `dst`, returning the count;
// `decode(dst, src)` writes the decoded bytes into `dst`, returning a
// count or invalid.
//
// std uses '+' and '/' for indexes 62 and 63 (the RFC 4648 §4
// alphabet); url uses '-' and '_' (the §5 url-safe alphabet). Both
// pad encoded output with '=' to a multiple of 4 bytes.
// invalid — input was not well-formed base64 (bad char, wrong length,
// padding error). Payload is the byte index of the first offending
// position. Matches Hare's errors::invalid pairing with strconv.
export type invalid = !i32;
// encodedsize — bytes required to encode `n` source bytes (including
// '=' padding). Hare names it the same.
export fn encodedsize(n: i32) i32 = {
if (n == 0) { return 0; };
return ((n - 1) / 3 + 1) * 4;
};
// decodedsize — upper bound on the number of bytes decoded from `n`
// encoded bytes. The exact count depends on padding; callers consult
// the i32 returned by `decode`.
export fn decodedsize(n: i32) i32 = {
return (n / 4) * 3;
};
// encchar — map a 6-bit value to its alphabet character. `urlsafe`
// chooses '-'/'_' instead of '+'/'/' for 62/63.
fn encchar(v: u8, urlsafe: bool) u8 = {
if (v < 26u8) { return v + 65u8; }; // 'A' + v
if (v < 52u8) { return v + 71u8; }; // 'a' + (v - 26) = v + 71
if (v < 62u8) { return v - 4u8; }; // '0' + (v - 52) = v - 4
if (v == 62u8) {
if (urlsafe) { return 45u8; }; // '-'
return 43u8; // '+'
};
if (urlsafe) { return 95u8; }; // '_'
return 47u8; // '/'
};
// decchar — inverse of encchar. Returns 0..63 on success or 255 on
// invalid char. '=' is handled in the decode loop, not here.
fn decchar(c: u8, urlsafe: bool) u8 = {
if (c >= 65u8) { if (c <= 90u8) { return c - 65u8; }; }; // 'A'..'Z'
if (c >= 97u8) { if (c <= 122u8) { return c - 71u8; }; }; // 'a'..'z'
if (c >= 48u8) { if (c <= 57u8) { return c + 4u8; }; }; // '0'..'9'
if (urlsafe) {
if (c == 45u8) { return 62u8; }; // '-'
if (c == 95u8) { return 63u8; }; // '_'
} else {
if (c == 43u8) { return 62u8; }; // '+'
if (c == 47u8) { return 63u8; }; // '/'
};
return 255u8;
};
// encodeinto — encode `src` into `dst` using the std (`urlsafe=false`)
// or url-safe (`urlsafe=true`) alphabet. `dst` must hold at least
// encodedsize(src.len) bytes. Returns the number of bytes written.
fn encodeinto(dst: []u8, src: []u8, urlsafe: bool) i32 = {
let i: i32 = 0;
let j: i32 = 0;
for (i + 2 < src.len) {
let b0: u8 = src[i];
let b1: u8 = src[i + 1];
let b2: u8 = src[i + 2];
dst[j] = encchar(b0 >> 2u8, urlsafe);
dst[j + 1] = encchar(((b0 & 3u8) << 4u8) | (b1 >> 4u8), urlsafe);
dst[j + 2] = encchar(((b1 & 15u8) << 2u8) | (b2 >> 6u8), urlsafe);
dst[j + 3] = encchar(b2 & 63u8, urlsafe);
i += 3;
j += 4;
};
let rem: i32 = src.len - i;
if (rem == 1) {
let b0: u8 = src[i];
dst[j] = encchar(b0 >> 2u8, urlsafe);
dst[j + 1] = encchar((b0 & 3u8) << 4u8, urlsafe);
dst[j + 2] = 61u8; // '='
dst[j + 3] = 61u8; // '='
j += 4;
};
if (rem == 2) {
let b0: u8 = src[i];
let b1: u8 = src[i + 1];
dst[j] = encchar(b0 >> 2u8, urlsafe);
dst[j + 1] = encchar(((b0 & 3u8) << 4u8) | (b1 >> 4u8), urlsafe);
dst[j + 2] = encchar((b1 & 15u8) << 2u8, urlsafe);
dst[j + 3] = 61u8; // '='
j += 4;
};
return j;
};
// encode — encode `src` into `dst` using the std alphabet. Returns
// the number of bytes written. `dst` must hold at least
// encodedsize(src.len) bytes.
export fn encode(dst: []u8, src: []u8) i32 = {
return encodeinto(dst, src, false);
};
// encodeurl — same as encode but uses the url-safe alphabet ('-'/'_'
// for 62/63).
export fn encodeurl(dst: []u8, src: []u8) i32 = {
return encodeinto(dst, src, true);
};
// decodeinto — decode base64 `src` into `dst`. `dst` must hold at
// least decodedsize(src.len) bytes. Returns the number of bytes
// written, or invalid with the offending source index.
fn decodeinto(dst: []u8, src: []u8, urlsafe: bool) (i32 | invalid) = {
if (src.len == 0) { return 0; };
if ((src.len & 3) != 0) { return src.len: invalid; };
let i: i32 = 0;
let j: i32 = 0;
let end: i32 = src.len;
for (i < end) {
let c0: u8 = src[i];
let c1: u8 = src[i + 1];
let c2: u8 = src[i + 2];
let c3: u8 = src[i + 3];
let v0: u8 = decchar(c0, urlsafe);
let v1: u8 = decchar(c1, urlsafe);
if (v0 == 255u8) { return i: invalid; };
if (v1 == 255u8) { return (i + 1): invalid; };
// Last quad may carry '=' padding.
if (i + 4 == end) {
if (c2 == 61u8) {
// "XX=="
if (c3 != 61u8) { return (i + 3): invalid; };
dst[j] = (v0 << 2u8) | (v1 >> 4u8);
j += 1;
i += 4;
return j;
};
let v2: u8 = decchar(c2, urlsafe);
if (v2 == 255u8) { return (i + 2): invalid; };
if (c3 == 61u8) {
// "XXX="
dst[j] = (v0 << 2u8) | (v1 >> 4u8);
dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8);
j += 2;
i += 4;
return j;
};
let v3: u8 = decchar(c3, urlsafe);
if (v3 == 255u8) { return (i + 3): invalid; };
dst[j] = (v0 << 2u8) | (v1 >> 4u8);
dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8);
dst[j + 2] = (v2 << 6u8) | v3;
j += 3;
i += 4;
return j;
};
let v2: u8 = decchar(c2, urlsafe);
let v3: u8 = decchar(c3, urlsafe);
if (v2 == 255u8) { return (i + 2): invalid; };
if (v3 == 255u8) { return (i + 3): invalid; };
dst[j] = (v0 << 2u8) | (v1 >> 4u8);
dst[j + 1] = (v1 << 4u8) | (v2 >> 2u8);
dst[j + 2] = (v2 << 6u8) | v3;
i += 4;
j += 3;
};
return j;
};
// decode — decode std-alphabet base64 from `src` into `dst`. Returns
// the count of decoded bytes, or invalid on a malformed input.
export fn decode(dst: []u8, src: []u8) (i32 | invalid) = {
return decodeinto(dst, src, false);
};
// decodeurl — same as decode but accepts the url-safe alphabet.
export fn decodeurl(dst: []u8, src: []u8) (i32 | invalid) = {
return decodeinto(dst, src, true);
};

View File

@@ -0,0 +1,172 @@
use base64;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
into[off + i] = s[i];
i += 1;
};
return off + s.len;
};
fn streq(buf: []u8, expect: str) bool = {
if (buf.len != expect.len) { return false; };
let i: i32 = 0;
for (i < buf.len) {
if (buf[i] != expect[i]) { return false; };
i += 1;
};
return true;
};
fn encodevec(input: str, expect: str) void = {
let inbuf: [128]u8;
let outbuf: [128]u8;
let n: i32 = putstr(input, inbuf[0:128], 0);
let m: i32 = base64.encode(outbuf[0:128], inbuf[0:n]);
if (m != expect.len) { let _: i32 = 1/0; };
if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; };
};
@test fn rfc4648_vectors() void = {
encodevec("", "");
encodevec("f", "Zg==");
encodevec("fo", "Zm8=");
encodevec("foo", "Zm9v");
encodevec("foob", "Zm9vYg==");
encodevec("fooba", "Zm9vYmE=");
encodevec("foobar", "Zm9vYmFy");
};
fn decodevec(input: str, expect: str) void = {
let inbuf: [128]u8;
let outbuf: [128]u8;
let n: i32 = putstr(input, inbuf[0:128], 0);
let r: (i32 | base64.invalid) = base64.decode(outbuf[0:128], inbuf[0:n]);
match (r) {
case let m: i32 => {
if (m != expect.len) { let _: i32 = 1/0; };
if (!streq(outbuf[0:m], expect)) { let _: i32 = 1/0; };
};
case let e: base64.invalid => { let _: i32 = 1/0; };
};
};
@test fn rfc4648_decode() void = {
decodevec("", "");
decodevec("Zg==", "f");
decodevec("Zm8=", "fo");
decodevec("Zm9v", "foo");
decodevec("Zm9vYg==", "foob");
decodevec("Zm9vYmE=", "fooba");
decodevec("Zm9vYmFy", "foobar");
};
@test fn alphabet_full() void = {
// Round-trip every 6-bit value (0..63) by encoding three bytes that
// expose b0=0x00, b1=AA, b2=FF — the encoded chars depend on all
// four positions including the >>2 path.
let i: i32 = 0;
for (i < 64) {
let bits: u8 = i: u8;
// Construct a triple [bits<<2, 0, 0] so the first encoded
// char encodes `bits`. The other three chars are derivable
// from the remaining bytes; we only check the first here.
let inbuf: [3]u8;
inbuf[0] = bits << 2u8;
inbuf[1] = 0u8;
inbuf[2] = 0u8;
let outbuf: [4]u8;
let m: i32 = base64.encode(outbuf[0:4], inbuf[0:3]);
if (m != 4) { let _: i32 = 1/0; };
// Decoding back must give us `bits` in the high 6 bits of [0].
let r: (i32 | base64.invalid) = base64.decode(inbuf[0:3], outbuf[0:4]);
match (r) {
case let n: i32 => {
if (n != 3) { let _: i32 = 1/0; };
if ((inbuf[0] >> 2u8) != bits) { let _: i32 = 1/0; };
};
case let e: base64.invalid => { let _: i32 = 1/0; };
};
i += 1;
};
};
@test fn invalid_inputs() void = {
let inbuf: [16]u8;
let outbuf: [16]u8;
// Length not a multiple of 4.
let n: i32 = putstr("abc", inbuf[0:16], 0);
let r1: (i32 | base64.invalid) = base64.decode(outbuf[0:16], inbuf[0:n]);
match (r1) {
case let m: i32 => { let _: i32 = 1/0; };
case let e: base64.invalid => void;
};
// Bad char ('@' is not in the std alphabet).
let n2: i32 = putstr("Z@==", inbuf[0:16], 0);
let r2: (i32 | base64.invalid) = base64.decode(outbuf[0:16], inbuf[0:n2]);
match (r2) {
case let m: i32 => { let _: i32 = 1/0; };
case let e: base64.invalid => void;
};
};
@test fn urlsafe_roundtrip() void = {
// Byte sequence chosen so the std alphabet would use '+' and '/',
// while url-safe replaces them with '-' and '_'. 0xFB = 11111011
// hits index 62 in some quad, and 0xFF hits 63.
let raw: [3]u8;
raw[0] = 0xFBu8;
raw[1] = 0xFFu8;
raw[2] = 0xBFu8;
let std: [8]u8;
let url: [8]u8;
let dec: [3]u8;
let m1: i32 = base64.encode(std[0:8], raw[0:3]);
let m2: i32 = base64.encodeurl(url[0:8], raw[0:3]);
if (m1 != 4) { let _: i32 = 1/0; };
if (m2 != 4) { let _: i32 = 1/0; };
// Round-trip both ways.
let r1: (i32 | base64.invalid) = base64.decode(dec[0:3], std[0:m1]);
match (r1) {
case let n: i32 => {
if (n != 3) { let _: i32 = 1/0; };
if (dec[0] != raw[0]) { let _: i32 = 1/0; };
if (dec[1] != raw[1]) { let _: i32 = 1/0; };
if (dec[2] != raw[2]) { let _: i32 = 1/0; };
};
case let e: base64.invalid => { let _: i32 = 1/0; };
};
let r2: (i32 | base64.invalid) = base64.decodeurl(dec[0:3], url[0:m2]);
match (r2) {
case let n: i32 => {
if (n != 3) { let _: i32 = 1/0; };
if (dec[0] != raw[0]) { let _: i32 = 1/0; };
if (dec[1] != raw[1]) { let _: i32 = 1/0; };
if (dec[2] != raw[2]) { let _: i32 = 1/0; };
};
case let e: base64.invalid => { let _: i32 = 1/0; };
};
};
@test fn sizes() void = {
if (base64.encodedsize(0) != 0) { let _: i32 = 1/0; };
if (base64.encodedsize(1) != 4) { let _: i32 = 1/0; };
if (base64.encodedsize(2) != 4) { let _: i32 = 1/0; };
if (base64.encodedsize(3) != 4) { let _: i32 = 1/0; };
if (base64.encodedsize(4) != 8) { let _: i32 = 1/0; };
if (base64.encodedsize(6) != 8) { let _: i32 = 1/0; };
if (base64.encodedsize(7) != 12) { let _: i32 = 1/0; };
if (base64.decodedsize(4) != 3) { let _: i32 = 1/0; };
if (base64.decodedsize(8) != 6) { let _: i32 = 1/0; };
};
export fn main() i32 = {
rfc4648_vectors();
rfc4648_decode();
alphabet_full();
invalid_inputs();
urlsafe_roundtrip();
sizes();
return 0;
};

36
lib/hash/crc64/crc64.ww Normal file
View File

@@ -0,0 +1,36 @@
// hash/crc64 — CRC-64 checksum. Pure ww.
//
// Same shape as lib/hash/crc32: per-byte inline polynomial shift, no
// precomputed table. Slower than Hare's table-driven path by ~8x per
// byte but produces identical answers for the documented polynomials.
//
// Polynomials are given in reversed form, matching Hare.
def ECMA: u64 = 0xC96C5795D7870F42u64; // ECMA-182, xz-utils
def ISO: u64 = 0xD800000000000000u64; // ISO 3309 HDLC
// sum64 — fold `buf` under `poly` (reversed form). Initial cval is
// ~0u64; per byte we mix in the low byte via 8 polynomial shifts and
// XOR with the high seven bytes shifted down.
export fn sum64(buf: []u8, poly: u64) u64 = {
let c: u64 = 0xFFFFFFFFFFFFFFFFu64;
let i: i32 = 0;
for (i < buf.len) {
let t: u64 = (c & 0xFFu64) ^ (buf[i]: u64);
let z: i32 = 0;
for (z < 8) {
if ((t & 1u64) == 1u64) {
t = (t >> 1u64) ^ poly;
} else {
t = t >> 1u64;
};
z += 1;
};
c = t ^ (c >> 8u64);
i += 1;
};
return ~c;
};
export fn sum64ecma(buf: []u8) u64 = { return sum64(buf, ECMA); };
export fn sum64iso(buf: []u8) u64 = { return sum64(buf, ISO); };

View File

@@ -0,0 +1,66 @@
use crc64;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
into[off + i] = s[i];
i += 1;
};
return off + s.len;
};
fn check(s: str, ecma: u64, iso: u64) void = {
let arr: [128]u8;
let n: i32 = putstr(s, arr[0:128], 0);
let buf: []u8 = arr[0:n];
if (crc64.sum64ecma(buf) != ecma) { let _: i32 = 1/0; };
if (crc64.sum64iso(buf) != iso) { let _: i32 = 1/0; };
};
@test fn vec_empty() void = {
let arr: [1]u8;
let buf: []u8 = arr[0:0];
if (crc64.sum64ecma(buf) != 0u64) { let _: i32 = 1/0; };
if (crc64.sum64iso(buf) != 0u64) { let _: i32 = 1/0; };
};
@test fn vec_oscar() void = {
check("Man can believe the impossible, but can never believe the improbable. -- Oscar Wilde",
0x17F71EF3BB851DC7u64, 0xD0E8ED57865D6AC6u64);
};
@test fn vec_hegel() void = {
check("We learn from history that we do not learn from history. -- Georg Hegel",
0xAA3C335BB49ABE9Du64, 0x5FD192CC516BBEC3u64);
};
@test fn vec_chapelain() void = {
check("The final delusion is the belief that one has lost all delusions. -- Maurice Chapelain",
0x7DFC4F7CE9552D23u64, 0xF71B429C925D99AEu64);
};
@test fn vec_unix() void = {
check("UNIX is simple and coherent",
0x9C1938E72C1D8619u64, 0x372ABFFD392FF27Du64);
};
@test fn vec_gnu() void = {
check("GNU's not UNIX",
0x2C0C97B1CB17FCBAu64, 0x9BD1FA95A419A43Du64);
};
@test fn vec_blm() void = {
check("Black lives matter",
0x159BB7B6086BF47Eu64, 0xC218CBB390CF44EBu64);
};
export fn main() i32 = {
vec_empty();
vec_oscar();
vec_hegel();
vec_chapelain();
vec_unix();
vec_gnu();
vec_blm();
return 0;
};

View File

@@ -0,0 +1,96 @@
// hash/siphash — SipHash-2-4 keyed hash, buffer-based.
//
// Mirrors Hare's hash::siphash for the one-shot path: take a 16-byte
// key and a buffer, return the 64-bit hash. Hare ships a streaming
// io::stream-backed type; ww's subset doesn't, matching the rest of
// lib/hash/*. The (c, d) parameter pair is exposed as `sum`; `sum24`
// fixes c=2, d=4 (the recommendation in the SipHash paper).
//
// Constants and round structure follow Aumasson & Bernstein, "SipHash:
// a fast short-input PRF" (CHES 2012).
use endian;
fn rotl64(x: u64, n: u64) u64 = {
return (x << n) | (x >> (64u64 - n));
};
fn round(v: *[4]u64) void = {
let v0: u64 = v[0];
let v1: u64 = v[1];
let v2: u64 = v[2];
let v3: u64 = v[3];
v0 = v0 + v1;
v1 = rotl64(v1, 13u64);
v1 = v1 ^ v0;
v0 = rotl64(v0, 32u64);
v2 = v2 + v3;
v3 = rotl64(v3, 16u64);
v3 = v3 ^ v2;
v0 = v0 + v3;
v3 = rotl64(v3, 21u64);
v3 = v3 ^ v0;
v2 = v2 + v1;
v1 = rotl64(v1, 17u64);
v1 = v1 ^ v2;
v2 = rotl64(v2, 32u64);
v[0] = v0;
v[1] = v1;
v[2] = v2;
v[3] = v3;
};
// sum — compute SipHash-c-d of `buf` under `key`. `key` must be a
// 16-byte slice. (c, d) are the compression and finalization round
// counts. Mirrors Hare's siphash::sum (one-shot form).
export fn sum(key: []u8, buf: []u8, c: i32, d: i32) u64 = {
let k0: u64 = endian.legetu64(key[0:8]);
let k1: u64 = endian.legetu64(key[8:16]);
let v: [4]u64;
v[0] = 0x736F6D6570736575u64 ^ k0;
v[1] = 0x646F72616E646F6Du64 ^ k1;
v[2] = 0x6C7967656E657261u64 ^ k0;
v[3] = 0x7465646279746573u64 ^ k1;
let i: i32 = 0;
let n: i32 = buf.len;
let last: i32 = n - (n & 7); // last whole-block boundary
for (i < last) {
let m: u64 = endian.legetu64(buf[i:i + 8]);
v[3] = v[3] ^ m;
let r: i32 = 0;
for (r < c) { round(&v); r += 1; };
v[0] = v[0] ^ m;
i += 8;
};
// Pack the trailing 0..7 bytes plus length-byte into the final
// 8-byte word.
let tail: u64 = 0u64;
let shift: u64 = 0u64;
let k: i32 = i;
for (k < n) {
let b: u64 = (buf[k]: u64) & 0xFFu64;
tail = tail | (b << shift);
shift = shift + 8u64;
k += 1;
};
let lenbyte: u64 = (n: u64) & 0xFFu64;
tail = tail | (lenbyte << 56u64);
v[3] = v[3] ^ tail;
let r2: i32 = 0;
for (r2 < c) { round(&v); r2 += 1; };
v[0] = v[0] ^ tail;
v[2] = v[2] ^ 0xFFu64;
let r3: i32 = 0;
for (r3 < d) { round(&v); r3 += 1; };
return v[0] ^ v[1] ^ v[2] ^ v[3];
};
// sum24 — SipHash-2-4 of `buf` under `key`. The standard recommended
// variant. Equivalent to sum(key, buf, 2, 4).
export fn sum24(key: []u8, buf: []u8) u64 = {
return sum(key, buf, 2, 4);
};

View File

@@ -0,0 +1,75 @@
use siphash;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
into[off + i] = s[i];
i += 1;
};
return off + s.len;
};
@test fn refkey_msgN() void = {
// Reference SipHash-2-4 vectors: key = 0x00..0x0F, message =
// 0x00..0x{n-1}. The reference test suite ships 64 vectors;
// we verify a representative sample (n in {0,1,4,7,8,9,15,16,
// 23,32}) which exercises both the partial-block and whole-
// block paths.
let key: [16]u8;
let i: i32 = 0;
for (i < 16) { key[i] = i: u8; i += 1; };
let msg: [40]u8;
let j: i32 = 0;
for (j < 40) { msg[j] = j: u8; j += 1; };
if (siphash.sum24(key[0:16], msg[0:0]) != 0x726FDB47DD0E0E31u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:1]) != 0x74F839C593DC67FDu64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:4]) != 0xCF2794E0277187B7u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:7]) != 0xAB0200F58B01D137u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:8]) != 0x93F5F5799A932462u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:9]) != 0x9E0082DF0BA9E4B0u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:15]) != 0xA129CA6149BE45E5u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:16]) != 0x3F2ACC7F57C29BDBu64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:23]) != 0xA80C038CCD5CCEC8u64) { let _: i32 = 1/0; };
if (siphash.sum24(key[0:16], msg[0:32]) != 0x7127512F72F27CCEu64) { let _: i32 = 1/0; };
};
@test fn ascii() void = {
let key: [16]u8;
let kstr: str = "sixteen-bytkey!?";
let i: i32 = 0;
for (i < 16) { key[i] = kstr[i]; i += 1; };
let buf: [128]u8;
let n: i32 = 0;
n = putstr("a", buf[0:128], 0);
if (siphash.sum24(key[0:16], buf[0:n]) != 0xFA7E197B2F4427D4u64) { let _: i32 = 1/0; };
n = putstr("abc", buf[0:128], 0);
if (siphash.sum24(key[0:16], buf[0:n]) != 0x133D3A92BB9CA86Bu64) { let _: i32 = 1/0; };
n = putstr("foobar", buf[0:128], 0);
if (siphash.sum24(key[0:16], buf[0:n]) != 0x84D2B5169DB6D3BFu64) { let _: i32 = 1/0; };
n = putstr("hello world", buf[0:128], 0);
if (siphash.sum24(key[0:16], buf[0:n]) != 0xB0417FA523CF7B36u64) { let _: i32 = 1/0; };
n = putstr("The quick brown fox jumps over the lazy dog", buf[0:128], 0);
if (siphash.sum24(key[0:16], buf[0:n]) != 0xA4CBBF0BDB704790u64) { let _: i32 = 1/0; };
};
@test fn empty() void = {
let key: [16]u8;
let kstr: str = "sixteen-bytkey!?";
let i: i32 = 0;
for (i < 16) { key[i] = kstr[i]; i += 1; };
let empty: [1]u8;
if (siphash.sum24(key[0:16], empty[0:0]) != 0x2A51AE0682925836u64) { let _: i32 = 1/0; };
};
export fn main() i32 = {
refkey_msgN();
ascii();
empty();
return 0;
};

56
lib/math/random/random.ww Normal file
View File

@@ -0,0 +1,56 @@
// math/random — SplitMix64 PRNG.
//
// Mirrors Hare's math::random surface. The state is a single u64;
// Hare types it as `random = u64`. Callers thread a pointer through
// next/u32n/u64n so each call advances the state in place.
// Deterministic — same seed reproduces the same sequence.
export type random = u64;
// init — initialize a generator with `seed`. Same seed reproduces the
// same sequence on every run. Mirrors Hare's random::init.
export fn init(seed: u64) random = { return seed: random; };
// next — return a pseudo-random 64-bit value and advance the state.
// SplitMix64, per Hare's random::next.
export fn next(r: *random) u64 = {
let s: u64 = (*r): u64 + 0x9E3779B97F4A7C15u64;
*r = s: random;
let a: u64 = s;
a = (a ^ (a >> 30u64)) * 0xBF58476D1CE4E5B9u64;
a = (a ^ (a >> 27u64)) * 0x94D049BB133111EBu64;
return a ^ (a >> 31u64);
};
// u32n — pseudo-random u32 in [0, n). n must be > 0. Uses Lemire's
// fast unbiased mapping (mulhi-then-leftover-reject). Mirrors Hare's
// random::u32n.
export fn u32n(r: *random, n: u32) u32 = {
let x: u32 = next(r): u32;
let prod: u64 = (x: u64) * (n: u64);
let leftover: u32 = prod: u32;
if (leftover < n) {
// thresh = -n mod n (two's-complement on u32).
let neg: u32 = 0u32 - n;
let thresh: u32 = neg % n;
for (leftover < thresh) {
x = next(r): u32;
prod = (x: u64) * (n: u64);
leftover = prod: u32;
};
};
return (prod >> 32u64): u32;
};
// u64n — pseudo-random u64 in [0, n). n must be > 0. Power-of-2 fast
// path; otherwise rejection-sample to avoid modulo bias. Mirrors
// Hare's random::u64n.
export fn u64n(r: *random, n: u64) u64 = {
if ((n & (n - 1u64)) == 0u64) { return next(r) & (n - 1u64); };
// max = U64_MAX - (U64_MAX+1) % n = -1 - (-n % n)
let neg: u64 = (0u64 - n);
let max: u64 = 0xFFFFFFFFFFFFFFFFu64 - (neg % n);
let out: u64 = next(r);
for (out > max) { out = next(r); };
return out % n;
};

View File

@@ -0,0 +1,61 @@
use random;
@test fn seq() void = {
let r: random.random = random.init(1234567u64);
if (random.next(&r) != 6457827717110365317u64) { let _: i32 = 1/0; };
if (random.next(&r) != 3203168211198807973u64) { let _: i32 = 1/0; };
if (random.next(&r) != 9817491932198370423u64) { let _: i32 = 1/0; };
if (random.next(&r) != 4593380528125082431u64) { let _: i32 = 1/0; };
if (random.next(&r) != 16408922859458223821u64) { let _: i32 = 1/0; };
};
@test fn deterministic() void = {
let a: random.random = random.init(42u64);
let b: random.random = random.init(42u64);
let i: i32 = 0;
for (i < 32) {
if (random.next(&a) != random.next(&b)) { let _: i32 = 1/0; };
i += 1;
};
};
@test fn u32n_inrange() void = {
let r: random.random = random.init(7u64);
let i: i32 = 0;
for (i < 200) {
let v: u32 = random.u32n(&r, 17u32);
// v stored as u64 with possible high bits — mask before compare.
let vv: u64 = (v: u64) & 0xFFFFFFFFu64;
if (vv >= 17u64) { let _: i32 = 1/0; };
i += 1;
};
};
@test fn u64n_pow2() void = {
let r: random.random = random.init(99u64);
let i: i32 = 0;
for (i < 200) {
let v: u64 = random.u64n(&r, 16u64);
if (v >= 16u64) { let _: i32 = 1/0; };
i += 1;
};
};
@test fn u64n_nonpow2() void = {
let r: random.random = random.init(123u64);
let i: i32 = 0;
for (i < 200) {
let v: u64 = random.u64n(&r, 100u64);
if (v >= 100u64) { let _: i32 = 1/0; };
i += 1;
};
};
export fn main() i32 = {
seq();
deterministic();
u32n_inrange();
u64n_pow2();
u64n_nonpow2();
return 0;
};

View File

@@ -8140,10 +8140,48 @@ fn cgcast(c: *cgen, n: *node) void = {
if (srcstr) { emitline("\tMOVQ\tBX, CX\n"); };
};
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
// same-kind casts (int↔int with widening differences,
// f64→f64 etc.) stay no-ops at the asm level, matching the
// pre-port behaviour for integer casts.
if (srcfk == 0 && dstfk == 0) { return; };
// int↔int casts narrow via an explicit clamp before the early
// return so `(big_u64): u32` doesn't leak the upper 32 bits.
// Hare semantics: `expr: T` truncates to T's bit width (mod 2^n).
// Mirrors cmd/w6c/cgen.c's N_CAST clamp; signed-narrow targets
// (i8/i16/i32) stay no-ops until the assembler grows MOVSBQ /
// MOVSWQ / MOVSXD reg-reg forms.
if (srcfk == 0 && dstfk == 0) {
let tn: *node = n.rhs;
// Walk through alias chains (`type random = u64`).
for (tn != nil) {
if (tn.kind != nkind.N_TNAME) { tn = nil; }
else {
let nm: str = tn.str;
if (primsize(nm) > 0) { break; };
let alias: *node = aliaslookup(c, nm);
if (alias == nil) { tn = nil; }
else { tn = alias; };
};
};
if (tn != nil) {
let nm: str = tn.str;
let sz: i32 = primsize(nm);
let is_unsigned: bool = typenameisunsigned(nm);
let is_bool: bool = streq(nm, "bool");
if (sz > 0) { if (sz < 8) {
if (is_unsigned) {
if (sz == 4) {
emitline("\tMOVL\tAX, AX\n");
} else {
let mask: i64 = 0xFFi64;
if (sz == 2) { mask = 0xFFFFi64; };
emitline("\tANDQ\t$");
emitint(mask);
emitline(", AX\n");
};
} else { if (is_bool) {
emitline("\tANDQ\t$255, AX\n");
}; };
}; };
};
return;
};
if (srcfk == 0 && dstfk == 2) {
emitline("\tCVTSI2SD\tAX, X0\n");
return;

View File

@@ -375,10 +375,48 @@ fn cgcast(c: *cgen, n: *node) void = {
if (srcstr) { emitline("\tMOVQ\tBX, CX\n"); };
};
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
// same-kind casts (int↔int with widening differences,
// f64→f64 etc.) stay no-ops at the asm level, matching the
// pre-port behaviour for integer casts.
if (srcfk == 0 && dstfk == 0) { return; };
// int↔int casts narrow via an explicit clamp before the early
// return so `(big_u64): u32` doesn't leak the upper 32 bits.
// Hare semantics: `expr: T` truncates to T's bit width (mod 2^n).
// Mirrors cmd/w6c/cgen.c's N_CAST clamp; signed-narrow targets
// (i8/i16/i32) stay no-ops until the assembler grows MOVSBQ /
// MOVSWQ / MOVSXD reg-reg forms.
if (srcfk == 0 && dstfk == 0) {
let tn: *node = n.rhs;
// Walk through alias chains (`type random = u64`).
for (tn != nil) {
if (tn.kind != nkind.N_TNAME) { tn = nil; }
else {
let nm: str = tn.str;
if (primsize(nm) > 0) { break; };
let alias: *node = aliaslookup(c, nm);
if (alias == nil) { tn = nil; }
else { tn = alias; };
};
};
if (tn != nil) {
let nm: str = tn.str;
let sz: i32 = primsize(nm);
let is_unsigned: bool = typenameisunsigned(nm);
let is_bool: bool = streq(nm, "bool");
if (sz > 0) { if (sz < 8) {
if (is_unsigned) {
if (sz == 4) {
emitline("\tMOVL\tAX, AX\n");
} else {
let mask: i64 = 0xFFi64;
if (sz == 2) { mask = 0xFFFFi64; };
emitline("\tANDQ\t$");
emitint(mask);
emitline(", AX\n");
};
} else { if (is_bool) {
emitline("\tANDQ\t$255, AX\n");
}; };
}; };
};
return;
};
if (srcfk == 0 && dstfk == 2) {
emitline("\tCVTSI2SD\tAX, X0\n");
return;

View File

@@ -8140,10 +8140,48 @@ fn cgcast(c: *cgen, n: *node) void = {
if (srcstr) { emitline("\tMOVQ\tBX, CX\n"); };
};
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
// same-kind casts (int↔int with widening differences,
// f64→f64 etc.) stay no-ops at the asm level, matching the
// pre-port behaviour for integer casts.
if (srcfk == 0 && dstfk == 0) { return; };
// int↔int casts narrow via an explicit clamp before the early
// return so `(big_u64): u32` doesn't leak the upper 32 bits.
// Hare semantics: `expr: T` truncates to T's bit width (mod 2^n).
// Mirrors cmd/w6c/cgen.c's N_CAST clamp; signed-narrow targets
// (i8/i16/i32) stay no-ops until the assembler grows MOVSBQ /
// MOVSWQ / MOVSXD reg-reg forms.
if (srcfk == 0 && dstfk == 0) {
let tn: *node = n.rhs;
// Walk through alias chains (`type random = u64`).
for (tn != nil) {
if (tn.kind != nkind.N_TNAME) { tn = nil; }
else {
let nm: str = tn.str;
if (primsize(nm) > 0) { break; };
let alias: *node = aliaslookup(c, nm);
if (alias == nil) { tn = nil; }
else { tn = alias; };
};
};
if (tn != nil) {
let nm: str = tn.str;
let sz: i32 = primsize(nm);
let is_unsigned: bool = typenameisunsigned(nm);
let is_bool: bool = streq(nm, "bool");
if (sz > 0) { if (sz < 8) {
if (is_unsigned) {
if (sz == 4) {
emitline("\tMOVL\tAX, AX\n");
} else {
let mask: i64 = 0xFFi64;
if (sz == 2) { mask = 0xFFFFi64; };
emitline("\tANDQ\t$");
emitint(mask);
emitline(", AX\n");
};
} else { if (is_bool) {
emitline("\tANDQ\t$255, AX\n");
}; };
}; };
};
return;
};
if (srcfk == 0 && dstfk == 2) {
emitline("\tCVTSI2SD\tAX, X0\n");
return;

View File

@@ -22,10 +22,15 @@ static const char *modules[] = {
"lib/path/path.ww",
"lib/encoding/utf8/utf8.ww",
"lib/encoding/hex/hex.ww",
"lib/encoding/base32/base32.ww",
"lib/encoding/base64/base64.ww",
"lib/hash/fnv/fnv.ww",
"lib/hash/adler32/adler32.ww",
"lib/hash/crc16/crc16.ww",
"lib/hash/crc32/crc32.ww",
"lib/hash/crc64/crc64.ww",
"lib/hash/siphash/siphash.ww",
"lib/math/random/random.ww",
"lib/time/time.ww",
"lib/c/libc/libc.ww",
"lib/bufio/bufio.ww",