aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 22:28:06 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 22:28:06 +0330
commit5a4dc62a79af6135c7050e81a82dbfdfb0a7d4e0 (patch)
tree805283b2d30b2cf0f53af42e5a3fc119af5e839f
parentdefinite assignment: reading a local before it is initialized is a compile er... (diff)
uninit: track aggregate fills per part, sticky computed-index fills, &a[k] and method calls set the aggregate; fix test/example hits; docs
-rw-r--r--README.md2
-rw-r--r--docs/LEARN.md2
-rw-r--r--docs/SPEC.md6
-rw-r--r--examples/bf.zc2
-rw-r--r--note1
-rw-r--r--src/compiler/analyzer.zc35
-rw-r--r--src/compiler/base.zc4
-rw-r--r--src/compiler/expr.zc28
-rw-r--r--src/lib/std.zc2
-rw-r--r--tests/conformance/4-statements/uninit_array_elems_ok.zc11
-rw-r--r--tests/conformance/4-statements/uninit_array_fill_loop_ok.zc9
-rw-r--r--tests/conformance/4-statements/uninit_struct_fields_ok.zc13
-rw-r--r--tests/programs/bench_strings.zc2
-rw-r--r--tests/programs/bounds_body.zc2
14 files changed, 94 insertions, 25 deletions
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<int> 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;