From 5a4dc62a79af6135c7050e81a82dbfdfb0a7d4e0 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 22:28:06 +0330 Subject: uninit: track aggregate fills per part, sticky computed-index fills, &a[k] and method calls set the aggregate; fix test/example hits; docs --- README.md | 2 +- docs/LEARN.md | 2 +- docs/SPEC.md | 6 ++-- examples/bf.zc | 2 +- note | 1 + src/compiler/analyzer.zc | 35 +++++++++++++++++----- src/compiler/base.zc | 4 ++- src/compiler/expr.zc | 28 ++++++++++++----- src/lib/std.zc | 2 +- .../4-statements/uninit_array_elems_ok.zc | 11 +++++++ .../4-statements/uninit_array_fill_loop_ok.zc | 9 ++++++ .../4-statements/uninit_struct_fields_ok.zc | 13 ++++++++ tests/programs/bench_strings.zc | 2 +- tests/programs/bounds_body.zc | 2 +- 14 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 note create mode 100644 tests/conformance/4-statements/uninit_array_elems_ok.zc create mode 100644 tests/conformance/4-statements/uninit_array_fill_loop_ok.zc create mode 100644 tests/conformance/4-statements/uninit_struct_fields_ok.zc diff --git a/README.md b/README.md index 65f0955..ebea4b0 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ fn main() { } ``` -declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. a local must be assigned on every path before it is read (error: used before it is initialized); locals are not zeroed. passing `&x` counts as initializing `x`. an array stays unset until a full `{...}` initializer or `&agg`; a struct stays unset until `&agg` (structs have no brace initializer) — writing some fields or elements is not enough. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned; with a constant initializer it can be used in constant expressions (array lengths, `case` labels), which follow C precedence. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `defer`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `defer expr;` or `defer { ... }` registers code that runs when the enclosing block ends, on every way out of it: falling off the end, `return`, `break` and `continue`. several defers in a block run last registered first, the deferred code is compiled at each exit and so reads the variables as they are when it runs (a `return e;` computes `e` first, then runs the defers), a defer that was never reached registers nothing, and `return`, `break` and `continue` inside a deferred statement are errors. a deferred `rt.free(p);` is the transfer of `p` for the analyzer at the point it runs: `p` stays usable until the block ends, is not freed or reported a second time, and freeing an owner that was moved away meanwhile is the usual use-after-move error. defer also works in `unchecked` code. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `node*` (pointer types are postfix), not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels (`case _:` is a catch-all like `default:`, and a label repeated in one switch is an error), and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there; an embedded `\0` ends a string literal (`"ab\0cd"` is `ab`, `b"hi\n\0x"` has `len` 3). +declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. a local must be assigned on every path before it is read (error: used before it is initialized); locals are not zeroed. passing `&x` counts as initializing `x`. an array or struct is tracked part by part: reading a written field or constant-index element is fine, and writing every part, a `{...}` array initializer, `&agg`, or a computed-index fill (`a[i] = e`, taken as filling it all) sets the whole value; reading an unwritten part is the error. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned; with a constant initializer it can be used in constant expressions (array lengths, `case` labels), which follow C precedence. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `defer`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `defer expr;` or `defer { ... }` registers code that runs when the enclosing block ends, on every way out of it: falling off the end, `return`, `break` and `continue`. several defers in a block run last registered first, the deferred code is compiled at each exit and so reads the variables as they are when it runs (a `return e;` computes `e` first, then runs the defers), a defer that was never reached registers nothing, and `return`, `break` and `continue` inside a deferred statement are errors. a deferred `rt.free(p);` is the transfer of `p` for the analyzer at the point it runs: `p` stays usable until the block ends, is not freed or reported a second time, and freeing an owner that was moved away meanwhile is the usual use-after-move error. defer also works in `unchecked` code. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `node*` (pointer types are postfix), not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels (`case _:` is a catch-all like `default:`, and a label repeated in one switch is an error), and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there; an embedded `\0` ends a string literal (`"ab\0cd"` is `ab`, `b"hi\n\0x"` has `len` 3). `typedef T name;` gives a type another name, as in c, at the top level or inside a function (where it lasts to the end of its block, like a local): `typedef node* link;`, `typedef int[4] quad;`, `typedef int(int, int) binop;`, `typedef list ints;`. `type name = T;` says the same thing. a typedef is only a new spelling: `link` and `node*` are the very same type record, so they mix freely in declarations, parameters, results, casts, `sizeof`, struct fields, generic arguments and `typecheck on;` code, and they print as the target type. `T` can be any type the parser accepts, but not a tag (`.u`, `.m` and `.d` belong to a declaration). a typedef can't name a type that isn't declared yet, so a struct comes before the typedefs of its pointers; using a name that is already defined is an error, and a typedef of `void` is `void` (a variable of it is the usual error). `string` is built in as a typedef of `char*`, so `string s = "hi";` and `string f(string a)` work with no declaration; a function, variable or parameter of the same name shadows it. diff --git a/docs/LEARN.md b/docs/LEARN.md index e4fcdc6..6f3e0fc 100644 --- a/docs/LEARN.md +++ b/docs/LEARN.md @@ -63,7 +63,7 @@ hello, world! ### variables and while -`let` infers a variable's type from its initializer. an integer literal stays a plain word. `int i = 0;` writes the type explicitly. a local must be assigned on every path before it is read (locals are not zeroed); passing `&x` counts as initializing `x`, and an array or struct needs a full `{...}` initializer or `&agg`. the lifetime analyzer runs by default; the optional typechecker is enabled separately with `typecheck on;`. +`let` infers a variable's type from its initializer. an integer literal stays a plain word. `int i = 0;` writes the type explicitly. a local must be assigned on every path before it is read (locals are not zeroed); passing `&x` counts as initializing `x`, and an array or struct is set once every field or constant-index element is written, by a `{...}` initializer, by `&agg`, or by a computed-index fill such as `a[i] = e` in a loop; reading a part that was written is fine. the lifetime analyzer runs by default; the optional typechecker is enabled separately with `typecheck on;`. `while` tests before entering the body. `i++` increments the variable, and `break` leaves the loop. diff --git a/docs/SPEC.md b/docs/SPEC.md index b645742..7ba5ce0 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -553,12 +553,14 @@ let sq = { let t = 3 + 4; t * t }; // 49 4.1.4 `const k = e;` declares a local constant: no type is written, it takes the initializer's type (read-only, 2.10), needs an initializer (error: a const needs an initializer), and cannot be assigned (error: const storage cannot be modified). `const T k = e;` is a typed const. -4.1.5 definite assignment. a local with no initializer is unset until every path that reaches a read has assigned it (or taken its address). reading an unset local is error: X: used before it is initialized. a local is set by an initializer, by `x = e`, by passing `&x` (the out-parameter pattern), or by a full array initializer list `{...}` (structs have no brace initializer; use `&agg` or pass the array). writing some fields or elements does not set the aggregate: the whole value stays unset until a full array initializer, `&agg`, or using the aggregate as an address (an array decaying to a pointer). at a join (if/else, loop exit, switch, `?:`) a local is set only if every reaching path set it. a `while` or `for` that may run zero times does not count an assignment only inside the body; a `do`/`while` always runs once, so an assignment in its body does. parameters and globals are set from the start (a global with no initializer is 0). locals are not zeroed. +4.1.5 definite assignment. a local with no initializer is unset until every path that reaches a read has assigned it (or taken its address). reading an unset local is error: X: used before it is initialized. a local is set by an initializer, by `x = e`, by passing `&x` (the out-parameter pattern), or by a full array initializer list `{...}` (structs have no brace initializer; use `&agg` or pass the array). an array or struct is tracked part by part: writing every field (top-level) or every element by constant index sets the whole value, and reading a field or constant-index element that was written is fine even before the rest is; reading an unwritten part, or the whole value, before it is set is the error. a write through a computed index (`a[i] = e`, a fill loop) counts as setting the whole array, on every path (an approximation: the analyzer does not prove the loop covers it). a full `{...}` initializer, `&agg`, `&a[k]`, using the aggregate as an address (an array decaying to a pointer), or calling a method on it also set it. at a join (if/else, loop exit, switch, `?:`) a local is set only if every reaching path set it. a `while` or `for` that may run zero times does not count an assignment only inside the body; a `do`/`while` always runs once, so an assignment in its body does. parameters and globals are set from the start (a global with no initializer is 0). locals are not zeroed. ``` int b; pn(b); // error: b: used before it is initialized int x; x = 1; pn(x); // ok int y; if (c) y = 1; pn(y); // error unless every branch sets y -point p; p.x = 1; pn(p.y); // error: partial fill does not set p +point p; p.x = 1; pn(p.y); // error: p.y was never written +point r; r.x = 1; pn(r.x); // ok: that part was written +int[2] a; a[0] = 1; a[1] = 2; f(a); // ok: every element written point q; fill(&q); pn(q.x); // ok: &q sets q (structs have no brace initializer) int z; fill(&z); pn(z); // ok: &z sets z ``` diff --git a/examples/bf.zc b/examples/bf.zc index b3d3a41..5683482 100644 --- a/examples/bf.zc +++ b/examples/bf.zc @@ -14,7 +14,7 @@ int run(s, int i, int c) { int k = 0; while (s[i + k] == c) k++; return k; } fn translate(s) { int i = 0, k; n = 0; depth = 0; - emit("let t[30000], p = 0;\n"); + emit("let t[30000] = {}, p = 0;\n"); while (s[i]) { switch (s[i]) { case '+', '-': diff --git a/note b/note new file mode 100644 index 0000000..42fe16c --- /dev/null +++ b/note @@ -0,0 +1 @@ +zy \ No newline at end of file diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index 3802437..e68e165 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -440,7 +440,7 @@ int newloc(int n) { if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0; if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0; if (!local_shadow) local_shadow = chk_resize(0, 0, loccap); local_shadow[i] = 0; - if (!linited) linited = chk_resize(0, 0, loccap); linited[i] = 0; + if (!linited) linited = chk_resize(0, 0, loccap); linited[i] = 0; if (!lfill) lfill = chk_resize(0, 0, loccap); lfill[i] = 0; lpos[i] = 0; lreadpos[i] = 0; lowner[i] = 0; @@ -1027,13 +1027,32 @@ int luse(int i) { } // definite assignment (spec 4.1.5): a local must be written on every path // before it is read. linited[i] is 1 once that is true for the current path. +// linited[i] is -1 when the whole local is set; an array or struct being filled +// part by part holds a bit per field or constant element written so far (parts +// 0..61). a join ands the paths' words, so a part counts only when every path set it. int mark_inited(int i) { - if (i >= 0 && linited) linited[i] = 1; + if (i >= 0 && linited) linited[i] = -1; return 0; } int need_inited(int i) { if (!fchk || panic || dry || i < 0 || !linited) return 0; - if (!linited[i]) serrn(lname[i], "used before it is initialized"); + if (linited[i] != -1 && !(lfill && lfill[i])) serrn(lname[i], "used before it is initialized"); + return 0; +} +// part k (or -1: unknown) of aggregate local i was written; n is how many parts it has +int mark_part(int i, int k, int n) { + if (i < 0 || !linited || linited[i] == -1) return 0; + if (k < 0 || k > 61 || n > 62) { linited[i] = -1; if (lfill) lfill[i] = 1; return 0; } // a computed index: a fill loop, taken as setting it all (on every path: loops may not run, so this is sticky) + linited[i] = linited[i] | (1 << k); + if (n > 0 && linited[i] == (1 << n) - 1) linited[i] = -1; + return 0; +} +// part k (or -1) of aggregate local i is read +int need_part(int i, int k) { + if (!fchk || panic || dry || i < 0 || !linited || linited[i] == -1 || (lfill && lfill[i])) return 0; + if (k > 61) return 0; // past the parts tracked (or out of range: the bounds check reports it) + if (k >= 0 && k <= 61 && ((linited[i] >> k) & 1)) return 0; + serrn(lname[i], "used before it is initialized"); return 0; } @@ -1212,7 +1231,7 @@ int snap_push() { p = R_SNAP + b; p[0] = lbase; p[1] = nloc; p = p + 2; - while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; if (linited) p[3] = linited[i]; else p[3] = 1; p = p + 4; i++; } + while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; if (linited) p[3] = linited[i]; else p[3] = -1; p = p + 4; i++; } snaptop = p - R_SNAP; return b; } @@ -1295,7 +1314,7 @@ word jow(word p, int i) { return p[3 + 4 * (i - p[0])]; } word jhm(word p, int i) { return p[4 + 4 * (i - p[0])]; } // definite-assignment bit for local i on path p (1 if missing from the snapshot). word jin(word p, int i) { - if (i >= p[1]) return 1; + if (i >= p[1]) return -1; return p[5 + 4 * (i - p[0])]; } // the states at a break, with the jump to patch: [site] then a snapshot @@ -1304,7 +1323,7 @@ word exit_rec(int site) { reserve_R_EXIT(extop + 3 + 4 * (nloc - lbase)); p = R_EXIT + extop; p[0] = site; p[1] = lbase; p[2] = nloc; q = p + 3; - while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; if (linited) q[3] = linited[i]; else q[3] = 1; q = q + 4; i++; } + while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; if (linited) q[3] = linited[i]; else q[3] = -1; q = q + 4; i++; } extop = q - R_EXIT; return p; } @@ -1360,8 +1379,8 @@ int join(int n0, int what) { } // definite assignment: only initialized if every reaching path assigned it if (linited) { - j = 0; s = 1; - while (j < njps) { if (!jin(jps[j], i)) s = 0; j++; } + j = 0; s = -1; + while (j < njps) { s = s & jin(jps[j], i); j++; } linited[i] = s; } i++; diff --git a/src/compiler/base.zc b/src/compiler/base.zc index 03a575c..8eb107e 100644 --- a/src/compiler/base.zc +++ b/src/compiler/base.zc @@ -254,7 +254,7 @@ int fn_return_type; // a consistent return type inferred from the function body // effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative. const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64; word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base; -word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info, local_shadow, xshadow, effect_shadows, linited; int argowner_mask; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy = {}; +word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info, local_shadow, xshadow, effect_shadows, linited, lfill; int argowner_mask; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy = {}; // drops (docs/design/destructors.md): the code position just past a drop form (a fresh // value with a drop still on the stack there), the type `this` gets in the next closure, // a local whose drop is set, a mailbox send whose arguments are checked, and o3 block depth. @@ -264,6 +264,7 @@ int argorigin = -1, argtemporary, argowner = -1; word argowner_fact; int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints word xprojection; int af_mutation_tick; int xbor = -1; // local a pointer value is borrowed from, or -1 +int xpart = -1, xparts = 0; // an lvalue inside an aggregate local: which top-level part (-1 unknown) of how many int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on int lsp, loopbase, ctxsp, snaptop; // defer: the statements registered and not yet run, DFR_W words each (the block @@ -786,6 +787,7 @@ int reserve_locals(int need) { if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap); if (local_shadow) local_shadow = chk_resize(local_shadow, nloc, cap); if (linited) linited = chk_resize(linited, nloc, cap); + if (lfill) lfill = chk_resize(lfill, nloc, cap); lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap); if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); } o3cache = chk_resize(o3cache, nloc, cap); diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index ecbb24c..11ca8e1 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -36,7 +36,7 @@ int rval_do(int check) { af_warning_end(); } } - if (lv && check && lvk == LV_ELEM && lvi >= 0) need_inited(lvi); + if (lv && check && lvk == LV_ELEM && lvi >= 0) { if (larr[lvi]) need_part(lvi, xpart); else need_inited(lvi); } if (lv) { if (ispk(xty)) pkld(xty); else if (!isagg(xty)) { dbg_addr(); op(O_LD); } @@ -1212,7 +1212,7 @@ int incdec(int post, int dlt) { cerrn(lvi, "owns an object and can't be incremented"); } if (lvk == LV_LOCAL) need_inited(lvi); - else if (lvk == LV_ELEM && lvi >= 0) need_inited(lvi); + else if (lvk == LV_ELEM && lvi >= 0) { if (larr[lvi]) need_part(lvi, xpart); else need_inited(lvi); } tcneed(isnum(xty) || xty[0] == TY_PTR || xty == tword, "++ and -- need a number or a pointer", xty); if (lvk == LV_LOCAL) { ltag[lvi] = ltag[lvi] | LASG; @@ -1251,7 +1251,9 @@ word field(int c) { if_value = 0; int n, fieldoff, fieldpos, sv; word st; word borrowed = xaf, base_mask = effect_mask(c); af_use(xaf, tpack(), c); xaf = 0; xvk = 0; int i, b = xbor, k = 0, scanned = 0, ms, de, pmatch = 0, base_local = -1; - if (lv && lvk == LV_LOCAL) base_local = lvi; + int chained = 0; + if (lv && lvk == LV_LOCAL && larr[lvi]) base_local = lvi; + else if (lv && lvk == LV_ELEM && lvi >= 0 && larr[lvi]) { base_local = lvi; chained = 1; } de = toff + toklen; next(); // is the place asked about on this member name (or just after the dot, with none typed) @@ -1296,6 +1298,7 @@ word field(int c) { else if (st == tword || isnum(st)) { if (fieldscan(n) == 0) ms = methscan(n); } if (ms == 0 - 2) { serrn(n, "is a method of more than one struct"); ms = 0 - 1; } if (ms >= 0) { + if (base_local >= 0) mark_inited(base_local); // the method gets its address and may fill it return methcall(ms, st, borrowed, b, base_mask); } } @@ -1350,8 +1353,8 @@ word field(int c) { effect_value = base_mask; xprojection = af_project(borrowed, k, tsz(xty), tpack(), 0); lv = 1; xzero = 0; - if (b >= 0) { lvk = LV_ELEM; lvi = b; } - else if (base_local >= 0) { lvk = LV_ELEM; lvi = base_local; } + if (base_local >= 0) { lvk = LV_ELEM; lvi = base_local; if (!chained) { xpart = i; xparts = st[3]; } } + else if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; return C_PLAIN; } @@ -1467,6 +1470,9 @@ int postfix_tail(int c) { if (o3on && lv && lvk == LV_LOCAL && o3cache[lvi]) ci = o3cache[lvi]; ub = !fbnd || o3on || (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)); // unbounded, or o3 code bl = -1; if (lv && lvk == LV_LOCAL) bl = lvi; + int abase = -1, achain = 0; ic = 0; + if (lv && lvk == LV_LOCAL && larr[lvi]) abase = lvi; + else if (lv && lvk == LV_ELEM && lvi >= 0 && larr[lvi]) { abase = lvi; achain = 1; } next(); rval_do(0); // indexing forms an element address; reading the element checks later if (was) { @@ -1615,8 +1621,11 @@ int postfix_tail(int c) { lv = 1; xty = et; xzero = 0; xconst = 0; xvk = 0; xlenloc = -1; xstamp = 0; if (isref(xty)) { if (b >= 0) xbor = lroot(b); } else xbor = -1; - if (b >= 0) { lvk = LV_ELEM; lvi = b; } - else if (bl >= 0) { lvk = LV_ELEM; lvi = bl; } + if (abase >= 0) { + lvk = LV_ELEM; lvi = abase; + if (!achain) { xpart = -1; xparts = n; if (ic) { if (offset >= 0 && (n < 0 || offset < n)) xpart = offset; else xpart = 99; } } + } + else if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; c = C_PLAIN; xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0; @@ -1739,6 +1748,7 @@ int unary() { if (lv && ispk(xty)) serr("an element of a sized-integer array has no address; it is a byte inside a word"); if (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)) serr("an o3 variable has no address"); if (lv && lvk == LV_LOCAL) { ltag[lvi] = ltag[lvi] | LREAD | LASG; mark_inited(lvi); } + else if (lv && lvk == LV_ELEM && lvi >= 0 && larr[lvi]) mark_inited(lvi); // the pointer reaches every element if (c >= C_VAR) touch(c - C_VAR); if (lvk == LV_ELEM && lvi >= 0) xbor = lroot(lvi); lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0; @@ -1960,6 +1970,7 @@ int expr() { if (tv == s_eq) { if (!lv) die("this can't be assigned to"); k = lvk; i = lvi; lt = xty; require_mutable(lt); + int wpart = xpart, wparts = xparts; if (isagg(lt)) serr("a whole struct or array can't be assigned; assign its fields"); next(); a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i]; @@ -1989,6 +2000,7 @@ int expr() { if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see storekeep(lt); } + if (k == LV_ELEM && i >= 0 && larr[i]) mark_part(i, wpart, wparts); lv = 0; lvk = LV_NONE; xty = lt; xzero = 0; return assigncheck(k, i, rc); } @@ -1999,7 +2011,7 @@ int expr() { cerrn(lvi, "owns an object; compound assignment would lose it"); } if (lvk == LV_LOCAL) need_inited(lvi); - else if (lvk == LV_ELEM && lvi >= 0) need_inited(lvi); + else if (lvk == LV_ELEM && lvi >= 0) { if (larr[lvi]) need_part(lvi, xpart); else need_inited(lvi); } lt = xty; require_mutable(lt); fk = -1; lf = xfloat; if (lvk == LV_LOCAL) fk = lvi; diff --git a/src/lib/std.zc b/src/lib/std.zc index 5c86aa6..76c89f7 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -116,7 +116,7 @@ struct Text { // dst: where the text goes; 21 words is always enough (20 characters and the 0) // returns: how many characters were written, not counting the 0 static int itoa(int n, char* dst) { - int b[24], i = 0, j = 0, neg = 0; + int b[24] = {}, i = 0, j = 0, neg = 0; if (!n) { dst[0] = '0'; dst[1] = 0; return 1; } if (n < 0) { neg = 1; n = 0 - n; } if (n < 0) { rt.copy_w(dst, "-9223372036854775808", 21); return 20; } diff --git a/tests/conformance/4-statements/uninit_array_elems_ok.zc b/tests/conformance/4-statements/uninit_array_elems_ok.zc new file mode 100644 index 0000000..00f4d17 --- /dev/null +++ b/tests/conformance/4-statements/uninit_array_elems_ok.zc @@ -0,0 +1,11 @@ +// expect: 6 +// spec 4.1.5: writing every element by constant index sets the array +include rt; +fn main() { + int[3] a; + a[0] = 1; a[1] = 2; a[2] = 3; + int s = 0; + for (int i = 0; i < 3; i++) s = s + a[i]; + rt.put_n(s); + return 0; +} diff --git a/tests/conformance/4-statements/uninit_array_fill_loop_ok.zc b/tests/conformance/4-statements/uninit_array_fill_loop_ok.zc new file mode 100644 index 0000000..3b017b9 --- /dev/null +++ b/tests/conformance/4-statements/uninit_array_fill_loop_ok.zc @@ -0,0 +1,9 @@ +// expect: 30 +// spec 4.1.5: a computed-index fill counts as setting the whole array +include rt; +fn main() { + int[4] a; + for (int i = 0; i < 4; i++) a[i] = i * 5; + rt.put_n(a[0] + a[1] + a[2] + a[3]); + return 0; +} diff --git a/tests/conformance/4-statements/uninit_struct_fields_ok.zc b/tests/conformance/4-statements/uninit_struct_fields_ok.zc new file mode 100644 index 0000000..a12fa71 --- /dev/null +++ b/tests/conformance/4-statements/uninit_struct_fields_ok.zc @@ -0,0 +1,13 @@ +// expect: 3/7/ +// spec 4.1.5: a written field may be read before the rest; writing every field sets the struct +include rt; +struct point { int x; int y; } +fn sum(point* q) { return q.x + q.y; } +fn main() { + point p; + p.x = 3; + rt.put_n(p.x); rt.put_c(10); + p.y = 4; + rt.put_n(sum(&p)); rt.put_c(10); + return 0; +} diff --git a/tests/programs/bench_strings.zc b/tests/programs/bench_strings.zc index 6f224db..c9fcdee 100644 --- a/tests/programs/bench_strings.zc +++ b/tests/programs/bench_strings.zc @@ -5,7 +5,7 @@ fn work() { let s = b"abcdefghijklmnopqrstuvwxyz0123456789"; let i, acc, n, t, part, k, reps, ch; ch = rt.get_c(); - reps = 0; + reps = 0; acc = 0; while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = rt.get_c(); } while (reps > 0) { k = 0; acc = 0; n = rt.len_s(s); diff --git a/tests/programs/bounds_body.zc b/tests/programs/bounds_body.zc index ea838ac..d239802 100644 --- a/tests/programs/bounds_body.zc +++ b/tests/programs/bounds_body.zc @@ -4,7 +4,7 @@ include rt; // a loop proof only holds while the body leaves the index alone: here the body // moves i back below 0 once, so t[i] keeps its check and the access faults int main() { - int[4] t; int s = 0, once = 1; + int[4] t = {}; int s = 0, once = 1; for (int i = 0; i < 4; i++) { s = s + t[i]; if (once) { once = 0; i = -3000; } } rt.put_n(s); rt.put_c(10); return 0; -- cgit v1.2.3