From d9cfb91cb97e52d7998a6da8dc18db26d7190da4 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 26 May 2026 08:31:41 +0900 Subject: [PATCH] types: add INT/UINT limit constants, derived from size(int) (#114) ww's int/uint are machine words (8B on amd64, type.c:58), not the 4B Hare gives them on amd64 (arch+x86_64.ha maps INT_MAX->I32_MAX). So the limits can't alias a per-arch literal; they DERIVE from size(int) the Go way (cf math.MaxInt), staying correct on any word width: INT_MAX: int = (1 << (size(int)*8 - 1)) - 1 INT_MIN: int = -1 << (size(int)*8 - 1) UINT_MIN: uint = 0 UINT_MAX: uint = ~(0: uint) All four const-fold in def-init; on amd64 they evaluate to I64_MAX, I64_MIN, 0, U64_MAX. UINT_MAX uses the all-ones complement to dodge the 1<<64 overflow. Per the user ruling (2026-05-26): derived, not literal. Probe 959_types_intlim_run asserts each value vs both the literal and the i64/u64 limit const, plus wrap-through-i32 arithmetic usability. combined.ww regenerated for all 5 selfhost tools + smoke (all embed lib/types). --- Makefile | 5 + lib/types/types.ww | 8 ++ selfhost/cmd/w6a/main.combined.ww | 8 ++ selfhost/cmd/w6c/main.combined.ww | 8 ++ selfhost/cmd/w6l/main.combined.ww | 8 ++ selfhost/cmd/ww/main.combined.ww | 8 ++ selfhost/cmd/wwdump/main.combined.ww | 8 ++ selfhost/test/smoke.combined.ww | 8 ++ test/wcc/959_types_intlim_run.c | 145 +++++++++++++++++++++++++++ 9 files changed, 206 insertions(+) create mode 100644 test/wcc/959_types_intlim_run.c diff --git a/Makefile b/Makefile index 23b56280..efac1088 100644 --- a/Makefile +++ b/Makefile @@ -336,6 +336,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_floats_run \ $(BIN)/test_size_type_run \ $(BIN)/test_types_sizelim_run \ + $(BIN)/test_types_intlim_run \ $(BIN)/test_bufio_run $(BIN)/test_random_run $(BIN)/test_smoke: test/wcc/000_smoke.c $(LIB)/libwcc.a | $(BIN) @@ -1091,6 +1092,10 @@ $(BIN)/test_types_sizelim_run: test/wcc/958_types_sizelim_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_types_intlim_run: test/wcc/959_types_intlim_run.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_f64crossmod_run: test/wcc/953_f64crossmod_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/lib/types/types.ww b/lib/types/types.ww index b38caf87..4fa18452 100644 --- a/lib/types/types.ww +++ b/lib/types/types.ww @@ -24,6 +24,14 @@ 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; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index e340f5f0..12bfd7c3 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -776,6 +776,14 @@ 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; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e82cebad..6660e3fa 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -776,6 +776,14 @@ 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; diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index ec497b7d..d5617568 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -885,6 +885,14 @@ 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; diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index 259f141e..a24f473c 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -776,6 +776,14 @@ 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; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 7e78d5f2..46527bd2 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -753,6 +753,14 @@ 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; diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 731d0df6..7d977e3e 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -753,6 +753,14 @@ 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; diff --git a/test/wcc/959_types_intlim_run.c b/test/wcc/959_types_intlim_run.c new file mode 100644 index 00000000..858d4c47 --- /dev/null +++ b/test/wcc/959_types_intlim_run.c @@ -0,0 +1,145 @@ +/* + * 959_types_intlim_run — runtime proof of the lib/types INT_* and UINT_* + * limit constants. ww's int/uint are machine-word (8B on amd64, type.c:58), + * so the limits are DERIVED from size(int) (Go-style, cf math.MaxInt) rather + * than aliased to a per-arch literal the way Hare does (arch+x86_64.ha maps + * INT_MAX→I32_MAX). On amd64 the derived forms must therefore equal the 64-bit + * limits: INT_MAX==I64_MAX, INT_MIN==I64_MIN, UINT_MAX==U64_MAX, UINT_MIN==0 + * (#114, user ruling). + * + * Each row asserts both the value (vs the literal AND vs the i64/u64 limit + * const) and usability — the derived def survives const-fold, carries the + * right machine-word width, and is usable in arithmetic that wraps through an + * i32 cast (INT_MAX+43 and UINT_MAX+43 each wrap to 42 in the low 32 bits). + * + * Table-driven like 958_types_sizelim_run: each row is a self-contained ww + * program; the cstage `ww build -I lib` compiles it, we run the binary and + * assert the exit code. cstage-only by design (mirrors 951/952/957/958): + * per-program wwstage byte-id is the 990-997 gates' job. + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 1; +} + +struct row { const char *src; int want_exit; }; + +static const struct row rows[] = { + /* int limits: INT_MIN is I64_MIN, INT_MAX is I64_MAX — both by + * direct literal compare and against the i64 limit const. */ + { "package main;\n" + "import types;\n" + "export fn main() i32 = {\n" + " let lo: int = types.INT_MIN;\n" + " if (lo != -9223372036854775808) { return 1; };\n" + " if (lo: i64 != types.I64_MIN) { return 2; };\n" + " let hi: int = types.INT_MAX;\n" + " if (hi != 9223372036854775807) { return 3; };\n" + " if (hi: i64 != types.I64_MAX) { return 4; };\n" + " return 0;\n" + "};\n", 0 }, + /* INT_MAX usable in int arithmetic, propagated through an i32 cast: + * INT_MAX + 43 wraps past INT_MIN; low 32 bits are 42. */ + { "package main;\n" + "import types;\n" + "export fn main() i32 = {\n" + " let hi: int = types.INT_MAX;\n" + " let v: int = hi + 43;\n" + " return v: i32;\n" + "};\n", 42 }, + /* uint limits: UINT_MIN is zero; UINT_MAX is U64_MAX — both by + * compare and by the all-ones wrap (MAX + 1 == 0 on 8B). */ + { "package main;\n" + "import types;\n" + "export fn main() i32 = {\n" + " let lo: uint = types.UINT_MIN;\n" + " if (lo != 0) { return 1; };\n" + " let hi: uint = types.UINT_MAX;\n" + " if (hi != 18446744073709551615) { return 2; };\n" + " if (hi: u64 != types.U64_MAX) { return 3; };\n" + " if (hi + 1 != 0) { return 4; };\n" + " return 0;\n" + "};\n", 0 }, + /* UINT_MAX usable in uint arithmetic, propagated through an i32 + * cast: (2^64-1) + 43 wraps to 42. */ + { "package main;\n" + "import types;\n" + "export fn main() i32 = {\n" + " let hi: uint = types.UINT_MAX;\n" + " let v: uint = hi + 43;\n" + " return v: i32;\n" + "};\n", 42 }, + { NULL, 0 } +}; + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + char absbin[1024]; + if (bin[0] != '/') { + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + int n = 0, fail = 0; + for (int i = 0; rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwilim_%d_%d.ww", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwilim_%d_d_%d", getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && %s/ww build -I %s/lib %s", + tmpdir, bin, cwd, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row %d: build failed\n src: %s\n", + i, rows[i].src); + fail++; + unlink(src); rmdir(tmpdir); + continue; + } + + char outbin[128]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + + int got = runwait(outbin); + if (got != rows[i].want_exit) { + fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n", + i, got, rows[i].want_exit, rows[i].src); + fail++; + } + unlink(src); unlink(outbin); rmdir(tmpdir); + } + if (fail) { + fprintf(stderr, "%d/%d types_intlim tests failed\n", fail, n); + return 1; + } + printf("types_intlim: %d/%d ok\n", n, n); + return 0; +}