aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
commitbe43babc1886fc5efee898b2a3d0285012e511fb (patch)
treeb7ae73e7a6d4c896e2c49b7090168ef589b574af
parentmerge work/known-bugs: constant-expression precedence, local const in constan... (diff)
definite assignment: reading a local before it is initialized is a compile error (spec 4.1.5, b.1.9)
track linited per local alongside the ownership state; snapshots, exit records and joins carry it, and a join keeps a local set only when every reaching path set it. x = e, a declaration initializer, &x (out-parameter), a full array initializer, or using an aggregate as an address set a local; field and element writes do not set the aggregate. while/for bodies that may run zero times do not count; do/while does. compiler, runtime, std, actor and lsp sources initialize the locals the rule flagged; zc0 accepts = {} brace lists (zeroed). tests: tests/errors/uninit_*.zc, tests/conformance/4-statements/uninit_*.zc
-rw-r--r--README.md2
-rw-r--r--docs/LEARN.md2
-rw-r--r--docs/SPEC.md11
-rw-r--r--src/compiler/analyzer.zc46
-rw-r--r--src/compiler/base.zc25
-rw-r--r--src/compiler/builtins.zc6
-rw-r--r--src/compiler/decl.zc18
-rw-r--r--src/compiler/expr.zc61
-rw-r--r--src/compiler/lex.zc2
-rw-r--r--src/compiler/ops.zc2
-rw-r--r--src/compiler/stmt.zc16
-rw-r--r--src/compiler/types.zc10
-rw-r--r--src/lib/actor.zc2
-rw-r--r--src/lsp/server.zc2
-rw-r--r--src/runtime/rt.zc8
-rw-r--r--src/tools/zc0.c25
-rw-r--r--tests/conformance/4-statements/uninit_array_init_ok.zc10
-rw-r--r--tests/conformance/4-statements/uninit_assigned_ok.zc11
-rw-r--r--tests/conformance/4-statements/uninit_both_branches_ok.zc11
-rw-r--r--tests/conformance/4-statements/uninit_branch_rejected.zc10
-rw-r--r--tests/conformance/4-statements/uninit_do_loop_ok.zc12
-rw-r--r--tests/conformance/4-statements/uninit_loop_rejected.zc11
-rw-r--r--tests/conformance/4-statements/uninit_outparam_agg_ok.zc13
-rw-r--r--tests/conformance/4-statements/uninit_outparam_ok.zc12
-rw-r--r--tests/conformance/4-statements/uninit_partial_struct_rejected.zc11
-rw-r--r--tests/conformance/4-statements/uninit_read_rejected.zc5
-rw-r--r--tests/conformance/4-statements/uninit_struct_init_ok.zc11
-rw-r--r--tests/errors/uninit_branch.zc10
-rw-r--r--tests/errors/uninit_loop.zc11
-rw-r--r--tests/errors/uninit_partial_array.zc10
-rw-r--r--tests/errors/uninit_partial_struct.zc12
-rw-r--r--tests/errors/uninit_read.zc9
32 files changed, 321 insertions, 86 deletions
diff --git a/README.md b/README.md
index a717d8a..65f0955 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;`. `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 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).
`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 081498d..e4fcdc6 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. 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 needs a full `{...}` initializer or `&agg`. 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 f655395..b645742 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -553,9 +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 a local with no initializer is uninitialised: its value is whatever the frame held. a global with no initializer is 0. an array or struct local has no zeroing.
+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.
```
-int b; pn(b); // unspecified value
+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 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
```
4.1.6 a local may shadow a local of an outer block, a parameter, a global or a function name. redeclaring a name in the same block is accepted and the new declaration hides the old one from that point.
@@ -1152,7 +1157,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the
6. fixed (README says it now): README/LEARN did not say that an embedded `\0` ends a string literal: `b"hi\n\0x"` has `len` 3 and `"ab\0cd"` is `ab`.
7. fixed (3.14.2): README ("zc, the language") says `const k = 5;` in a function is a local constant. it is not a constant expression: `const n = 5; int[n] a;` in a function => `expected a constant`.
8. fixed (README now gives 0/1/2): README (reflection) described `run.tparamtag` as "the u/m/d tag of a parameter"; the code returns 0 untagged, 1 `.m`, 2 `.d` (there is no u at a parameter).
-9. README/LEARN do not say that local variables and arrays are not zeroed (4.1.5).
+9. fixed (4.1.5): reading an uninitialized local is a compile error (definite assignment), not an unspecified frame value. README/LEARN describe the rule; locals are still not zeroed.
10. zc0 vs compiler, the case that needed a referee: with `||` and `&&` now at c precedence, zc0 and compiler.zc agree on `1 || 0 && 0` => 1, `0 && 0 || 1` => 1, `1 | 2 && 0` => 0, `1 + 2 << 1` => 6, `7 & 3 ^ 1 | 8` => 10, `6 & 3 == 3` => 0 (checked on both).
11. zc0 vs compiler, integer literals. zc0 reads them with `strtoll` base 0: `rt.put_n(010)` prints 8 in zc0 and 10 in the compiler; `99999999999999999999` and `0xFFFFFFFFFFFFFFFF` saturate to 9223372036854775807 in zc0 and wrap to 7766279631452241919 and -1 in the compiler. this spec follows the compiler.
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc
index 55a96bb..3802437 100644
--- a/src/compiler/analyzer.zc
+++ b/src/compiler/analyzer.zc
@@ -440,6 +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;
lpos[i] = 0;
lreadpos[i] = 0;
lowner[i] = 0;
@@ -1024,6 +1025,18 @@ int luse(int i) {
if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1;
return 0;
}
+// 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.
+int mark_inited(int i) {
+ 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");
+ return 0;
+}
+
// touch records a use of a tracked local and rejects dead objects. a d object
// read in an inner block moves there, where the block owns it as m.
int touch(int i) {
@@ -1190,16 +1203,16 @@ int alias_end(int dep, int save) {
}
return 0;
}
-// snapshots: [lbase] [nloc] then state, owner, home per local
+// snapshots: [lbase] [nloc] then state, owner, home, inited per local
// save the states of all current locals; returns the snapshot offset
int snap_push() {
int b = snaptop, i = lbase; word p;
af_forget();
- reserve_R_SNAP(b + 2 + 3 * (nloc - lbase));
+ reserve_R_SNAP(b + 2 + 4 * (nloc - lbase));
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]; p = p + 3; 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;
}
@@ -1211,7 +1224,7 @@ int snap_restore(int b) {
af_forget();
if (e > nloc) e = nloc;
p = p + 2;
- while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; }
+ while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; if (linited) linited[i] = p[3]; p = p + 4; i++; }
return 0;
}
// true for a plain or dead local state, which merge to dead.
@@ -1226,7 +1239,7 @@ int snap_cmp(int b) {
s = lstate[i];
if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; }
else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i;
- p = p + 3; i++;
+ p = p + 4; i++;
}
return -1;
}
@@ -1258,7 +1271,7 @@ int merge_err(int i, int sa, int what) {
// to analyze an if, the compiler snapshots the locals' states before the
// branch, compiles the then path, restores the snapshot for the else path, and
// then compares or merges the two. a snapshot is [lbase, nloc] followed by
-// state, owner and home for each local; the stack of them is at R_SNAP.
+// state, owner, home and inited for each local; the stack of them is at R_SNAP.
// ---- joins ----
// where control flow comes back together (after an if, a loop, a switch or
@@ -1274,19 +1287,24 @@ int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JO
// read as plain.
word jst(word p, int i) {
if (i >= p[1]) return S_PLAIN;
- return p[2 + 3 * (i - p[0])];
+ return p[2 + 4 * (i - p[0])];
}
// the owner recorded in a jump's state for local i.
-word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; }
+word jow(word p, int i) { return p[3 + 4 * (i - p[0])]; }
// the jump's marker for local i, in the same record as jow.
-word jhm(word p, int i) { return p[4 + 3 * (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;
+ return p[5 + 4 * (i - p[0])];
+}
// the states at a break, with the jump to patch: [site] then a snapshot
word exit_rec(int site) {
word p; int i = lbase; word q;
- reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop;
+ 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]; q = q + 3; 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;
}
@@ -1340,6 +1358,12 @@ int join(int n0, int what) {
rt.put_s(")");
errend();
}
+ // 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++; }
+ linited[i] = s;
+ }
i++;
}
return 0;
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index 3135b39..03a575c 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -240,7 +240,7 @@ word lt_marks; int lt_nmarks, lt_markcap, lt_next_mark; char* lt_mark_file; int
word lt_buf, lt_head, lt_file_at; int lt_top, lt_cap, lt_base = 0 - 1, lt_file_img; char* lt_file;
char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect;
char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300;
-int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead
+int pk_tk, pk_tv, pq_n; word[4] pq_tk = {}; word[4] pq_tv = {}; word[4] pq_gl = {}; word[4] pq_ln = {}; // up to 4 tokens of lookahead
int here, hdelta, hlimit, ptop;
int code_chunk = rt.CODE_END;
word image_buffer, image_words; int image_active, image_org, image_capacity, image_size;
@@ -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; 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; 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.
@@ -308,7 +308,7 @@ int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word i
word if_edges, if_value, if_local;
int ifrcap = NIF, ifxcap = IFN, iftcap = IFT;
int fldst, fldi; // the one struct a bare address's field name resolved to
-char*[8] tpn; int ntp; // type parameters in scope
+char*[8] tpn = {}; int ntp; // type parameters in scope
// reading a return type: unknown names are type parameters declared after the function's name
int tvauto;
// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the
@@ -328,8 +328,8 @@ int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32;
// table of shared variables, hold_count the blocks so far. a shared variable's symbol has
// F_SHARED and its block's rank above it in sflag.
const int F_SHARED = 131072, F_SHARED_RANK = 262144;
-int hold_cur, fhold, hold_count, hold_desc, hold_nshared; word[128] hold_tab;
-int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0;
+int hold_cur, fhold, hold_count, hold_desc, hold_nshared; word[128] hold_tab = {};
+int o3on, o3nxt, o3one; word[32] o3sl = {}; word[32] o3rg = {}; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc = {}; int o3fs, o3na, o3fn0;
// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten;
// operator names used by numeric type checks.
@@ -348,18 +348,18 @@ int sx_push, sx_pop, sx_resize, sx_defer, sx_asm, sx_quote, sx_new, sx_int, sx_c
// local lower bound / len(array); one proof record per live loop
int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial;
int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s;
-int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled
+int mdg; word[8] mdn = {}; // the type parameters of the generic struct whose methods are being compiled
word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...)
// generic functions. the template (type parameters stay variables) is compiled once, as before,
// and its tokens are kept; a call whose type arguments are all known gets an instance, the same
// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
// inside it mean the real type. instances are compiled after the item that asked for them.
int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read
-int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined
+int gnew, gnew_np; word[8] gnew_nm = {}; // a generic function was just defined
int fn_method_compile, fnpatchseq;
word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function
int gi_n; word gi_t, gi_sym, gi_ar, gi_done;
-int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for
+int ginst; word[8] gnm = {}; word[8] gib = {}; int gng, ginsn; // compiling an instance: the names and what they stand for
int s_this;
const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */
const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */
@@ -376,7 +376,7 @@ word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial;
word[NCLO] clo_n_initial; word clo_n = clo_n_initial;
word[NCLO] clo_env_initial; word clo_env = clo_env_initial;
word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial;
-char*[256] clo_name_initial; char** clo_name = clo_name_initial;
+char*[256] clo_name_initial = {}; char** clo_name = clo_name_initial;
word[256] clo_loc_initial; word clo_loc = clo_loc_initial;
word[256] clo_via_initial; word clo_via = clo_via_initial;
@@ -704,7 +704,7 @@ word sdoff, sdsrc;
// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor
// sent (a host with no files of its own), incres the path an include was found at
char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done;
-word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff;
+word rsrec, rstmp; word[32] pnoffs = {}; word[32] pnplaces = {}; int rsn, rscap, rsbase, nameoff;
// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when
// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from.
// transfers are logged as events, EV_W words each: the local's declaration offset, what
@@ -785,6 +785,7 @@ int reserve_locals(int need) {
if (local_ended) local_ended = chk_resize(local_ended, nloc, cap);
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);
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);
@@ -1506,8 +1507,8 @@ int o3promote(int i) {
int o3mkcache(int i) {
int k;
if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0;
- k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k);
- newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1);
+ k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k); mark_inited(k);
+ newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1); mark_inited(k + 1);
o3cache[i] = k;
return 0;
}
diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc
index f40e223..56895e9 100644
--- a/src/compiler/builtins.zc
+++ b/src/compiler/builtins.zc
@@ -464,7 +464,7 @@ int embed_bind(int n, int off, word t, int a, int kind) {
return 0;
}
i = newloc(n); lrsoff[i] = off; rs_note(RS_LOCAL, off);
- ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t;
+ ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t; mark_inited(i);
if (kind == 1) {
larr[i] = 1; fslots = fslots + tsz(t); capture_cell(i, 0);
vaddr(i); lit(a); lit(tsz(t)); callname(s_copy_w); op(O_DROP);
@@ -1094,7 +1094,7 @@ int ckind(int skipname) {
int ckindfloat(int k) { return k == 2 || k == 5; }
// parse an extern declaration: a c function with its signature, and a zc stub for it.
int kw_extern() {
- int rk; word[16] ks; word[16] kst; char*[16] ksn; int np = 0, k; char* fname; char* cname; int i, a, j, n, w, sig, bad = 0; char* buf; word oldimports = module_imports; int pos, len, g; word st, rst; int rn, rsd, sret = 0;
+ int rk; word[16] ks = {}; word[16] kst = {}; char*[16] ksn = {}; int np = 0, k; char* fname; char* cname; int i, a, j, n, w, sig, bad = 0; char* buf; word oldimports = module_imports; int pos, len, g; word st, rst = 0; int rn = 0, rsd, sret = 0;
stmtonly();
next();
rk = ckind(0);
@@ -1544,7 +1544,7 @@ word targ(word t, int i) { return t[6][1 + i]; }
// ---- function types by address ----
word sigof(int f) {
- int i = nsym, s = -1; word[24] ws; int n, k;
+ int i = nsym, s = -1; word[24] ws = {}; int n, k;
while (i) { i--; if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) { s = i; break; } }
if (s < 0) return 0;
if (stype[s]) return stype[s];
diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc
index 313d1ab..b6669b1 100644
--- a/src/compiler/decl.zc
+++ b/src/compiler/decl.zc
@@ -94,7 +94,7 @@ int prescan_maker() {
// the guess only makes the function compile as if declared .u; its body is then
// checked against that, so a wrong guess is an error, never a silent leak.
int prescan_owned(int self) {
- int depth = 0, n, k, found = 0, nown = 0, decl = 0; let owned[16];
+ int depth = 0, n, k, found = 0, nown = 0, decl = 0; let owned[16] = {};
while (tk != T_EOF) {
// a declaration's name follows a type name, a *, let or a comma; a plain
// assignment (to a global, say) follows ; { or }
@@ -385,7 +385,7 @@ int borrows_check(int s, int np, char** pn, word pty) {
return 0;
}
int fndef(int chk, int rty, int ret, int n) {
- int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk, lt, lend, lfl = tline, redef, amb = 0, nonm = 0;
+ int s, np = 0, tags = 0, t, sig, i; char*[24] pn = {}; word[24] pt = {}; word[24] pty = {}; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk, lt, lend, lfl = tline, redef, amb = 0, nonm = 0;
o3nxt = 0;
if (rty || ntp) typed = 1;
if (!dnm) dnm = n;
@@ -500,7 +500,7 @@ int fndef(int chk, int rty, int ret, int n) {
prologue(); borrow_prologue(); hold_gate(57);
i = 0;
while (i < np) {
- t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]);
+ t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); mark_inited(t);
ltype[t] = pty[i]; lptr[t] = 0;
if (ifcur && if_local && i < IFP) if_local[t] = ifcur + 8 + 3 * i;
lt_note(pnoffs[i], pty[i]);
@@ -645,7 +645,7 @@ int gconst() {
// lays out the descriptor the install opcode reads, and wires it in right
// away (an image wires its own in when it boots).
int enhdef() {
- int n, np = 0, nout = 1, dsc, na, pay, len = 0, kind = 0, i; word p; int s, slot, r; char*[8] pn;
+ int n, np = 0, nout = 1, dsc, na, pay = 0, len = 0, kind = 0, i; word p; int s, slot, r; char*[8] pn = {};
next();
// enhance 2 divmod(a, b)
if (tk == T_NUM) {
@@ -711,7 +711,7 @@ int topsync() {
// after a function's name: <T, U> declares its type parameters. any the return
// type used were taken on trust; now they get their real numbers
int genparams() {
- int i, j, n = 0; char*[8] ps; word[8] bs;
+ int i, j, n = 0; char*[8] ps = {}; word[8] bs = {};
if (!is_op(s_lt)) { if (ntp) serrn(tpn[0], "isn't a type"); ntp = 0; return 0; }
next();
while (1) {
@@ -1012,7 +1012,7 @@ int fn_remember(int s, int name) {
// their original globals and entry addresses.
int syntax_host_function(int symbol) {
word meta = fnmeta[symbol], source, full; int clone, count, name;
- char[40] generated; char[24] reversed; int sequence = fnpatchseq, digits = 0, i = 0;
+ char[40] generated = {}; char[24] reversed = {}; int sequence = fnpatchseq, digits = 0, i = 0;
if (syntax_host_symbols[symbol]) return syntax_host_symbols[symbol] - 1;
if (!meta || (sflag[symbol] & F_GEN)) { serr("an emitted syntax dependency requires retained nongeneric source"); return -1; }
fnpatchseq++;
@@ -1049,7 +1049,7 @@ int syntax_resident(int symbol) {
// queued to be compiled once the template's tokens are in. a type parameter
// that nothing bound is a plain word.
word ginstance(int s, word bs) {
- int k = 0, t = 0 - 1, np, i, j, ns; char* nm; char[24] dg; int nd; char* a;
+ int k = 0, t = 0 - 1, np, i, j, ns; char* nm; char[24] dg = {}; int nd; char* a;
if (dry || !gtab) return s;
while (k < gt_n) {
if (gt_sym[k] == s) { t = k; break; }
@@ -1159,7 +1159,7 @@ int unit_top() {
}
// the edit distance between two names (levenshtein), or 99 when one is too long
word editdist(word a, word b) {
- int la = rt.len_w(a), lb = rt.len_w(b), i, j, x, y, z, last, row[65];
+ int la = rt.len_w(a), lb = rt.len_w(b), i, j, x, y, z, last, row[65] = {};
if (la > 60 || lb > 60) return 99;
j = 0; while (j <= lb) { row[j] = j; j++; }
i = 1;
@@ -1519,7 +1519,7 @@ word*.u function_tokens(int addr) {
int replace_function(int addr, word* body_tokens) {
int s = 0, open, close, namepos, olderr = nerr, oldpanic = panic, oldabort = chkabort;
int errors, generated_id, new_symbol, count, i = 0, out = 0, cap, diag, seq, digits = 0, published;
- char[40] generated; char[24] reversed; word meta, source, full, imports; char* module;
+ char[40] generated = {}; char[24] reversed = {}; word meta, source, full, imports; char* module;
while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++;
if (s >= nsym || !body_tokens || body_tokens[0] < 0) return 0;
meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; namepos = meta[3]; module = meta[1]; imports = meta[2];
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index e437d2f..ecbb24c 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -20,14 +20,23 @@ int dbg_addr() {
// variable, field or element that has not been loaded; rval loads it, unless
// the thing is an array or struct, whose address is its value. every place
// that needs a value calls this after parsing an expression.
-int rval() {
+// load or finish an lvalue as a value. check is 1 when the value is read
+// (definite assignment); 0 when forming a field or element address to write.
+int rval_do(int check) {
if (lv && lvk == LV_LOCAL) {
ltag[lvi] = ltag[lvi] | LREAD;
+ if (check) {
+ // an aggregate's value is its address (like &x), so using it initializes it;
+ // a scalar must already be set
+ if (isagg(xty) || larr[lvi]) mark_inited(lvi);
+ else need_inited(lvi);
+ }
if (rt.eq_w(lname[lvi], "_") && af_warning(lreadpos[lvi])) {
rt.put_s("discard placeholder `_` is read\nhelp: give the parameter or pattern binding a name when its value is needed");
af_warning_end();
}
}
+ if (lv && check && lvk == LV_ELEM && lvi >= 0) need_inited(lvi);
if (lv) {
if (ispk(xty)) pkld(xty);
else if (!isagg(xty)) { dbg_addr(); op(O_LD); }
@@ -35,6 +44,8 @@ int rval() {
}
return 0;
}
+int rval() { return rval_do(1); }
+
// run a keyword's handler (kw_len, kw_struct, a parse word...) from the spot
// where the parser met it. e is 1 in expression position, where statement-only
// keywords refuse to run. the handler reports the class of what it produced
@@ -441,7 +452,7 @@ int printcall(int line) {
return C_PLAIN;
}
int call(int s) {
- int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact;
+ int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs = {}; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact;
word requirements = effect_requirements, edges = effect_edges;
if (rt.eq_w(sname[s], "std.Io.print")) return printcall(0);
if (rt.eq_w(sname[s], "std.Io.println")) return printcall(1);
@@ -702,7 +713,7 @@ int shared_use(int s) {
// compiler state around the body.
int closure() {
char* xcf;
- int np = 0; char*[24] pn; word[24] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag, lt, lfl = tline; word[24] ptags;
+ int np = 0; char*[24] pn = {}; word[24] pty = {}; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag, lt, lfl = tline; word[24] ptags = {};
word expected = closure_expected, this_type = closure_this; closure_expected = 0; closure_this = 0;
reserve_closures(nclo + 1);
if (o3on) serr("closures can't be made inside an o3 region");
@@ -744,14 +755,14 @@ int closure() {
prologue(); borrow_prologue();
i = 0;
while (i < np) {
- t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]);
+ t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); mark_inited(t);
ltype[t] = pty[i];
if (fchk && ptags[i] == 1) lstate[t] = S_BORROW;
if (fchk && ptags[i] == 2) { lstate[t] = S_M; lown[t] = 1; }
if (isfl(pty[i])) setflt(t, 1);
i++;
}
- envl = newloc(intern("<env>")); fslots++;
+ envl = newloc(intern("<env>")); fslots++; mark_inited(envl);
clo_env[nclo - 1] = envl;
i = np + 1;
while (i) { i--; laddr(i); op(O_ST); }
@@ -776,7 +787,7 @@ int closure() {
while (i < ncap) {
t = clo_loc[c * capture_width + i];
if (clo_via[c * capture_width + i] < 0) {
- touch(t); effect_note_mask(effect_mask(C_VAR + t), EF_RETAIN); ev_put(t, EV_CAPTURE, -1);
+ need_inited(t); touch(t); effect_note_mask(effect_mask(C_VAR + t), EF_RETAIN); ev_put(t, EV_CAPTURE, -1);
if (cmode && !dry) { lvescaped[t] = 1; lvkind[t] = 0; }
if (!isalias(t)) { vaddr(t); op(O_LD); callname(intern("share")); op(O_DROP); }
vaddr(t);
@@ -963,7 +974,7 @@ int array_literal_tokens(word q, word t) {
}
int i = newloc(intern("<array literal>")), place = tpack();
t = array_measure(q, t); ltype[i] = t; larr[i] = 1; fslots = fslots + tsz(t);
- array_initialize(q, t, lslot[i], 1);
+ array_initialize(q, t, lslot[i], 1); mark_inited(i);
lafact[i] = af_new(t[3], 4, place); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i;
laddr(lslot[i]); xty = t; lv = 0; lvk = LV_NONE; xbor = i; xaf = lafact[i];
xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; rt.free(q);
@@ -1018,7 +1029,7 @@ int brace_primary() {
// variable the value is.
int primary() {
effect_value = 0; xprojection = 0; if_value = 0;
- int n, i, s, c, k, npos, sv; word[8] bs0; int ft, mn, ms;
+ int n, i, s, c, k, npos, sv; word[8] bs0 = {}; int ft, mn, ms;
lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1;
xconst = 0; xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0;
if (tk == T_FLT) { op(OP_FLIT); emit(tv); xvk = 2; xvv = tv; xfloat = 1; xty = tfloat; next(); return C_PLAIN; }
@@ -1067,7 +1078,7 @@ int primary() {
}
vaddr(i);
xty = ltype[i];
- if (larr[i]) return C_PLAIN;
+ if (larr[i]) { lv = 1; lvk = LV_LOCAL; lvi = i; return C_PLAIN; }
xfloat = getflt(i);
if (isfl(ltype[i])) xfloat = 1;
xstr = getstr(i); if (xty == tbstr) xstr = 1;
@@ -1200,6 +1211,8 @@ int incdec(int post, int dlt) {
if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
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);
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;
@@ -1237,7 +1250,8 @@ word field(int c) {
int readonly = treadonly(xty);
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;
+ 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;
de = toff + toklen;
next();
// is the place asked about on this member name (or just after the dot, with none typed)
@@ -1270,7 +1284,7 @@ word field(int c) {
}
}
}
- rval();
+ rval_do(0);
if (c == C_FRESH) cerr("use a field of a new object only after binding it to a name");
else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; }
st = xty;
@@ -1336,7 +1350,9 @@ 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 lvk = LV_OTHER;
+ if (b >= 0) { lvk = LV_ELEM; lvi = b; }
+ else if (base_local >= 0) { lvk = LV_ELEM; lvi = base_local; }
+ else lvk = LV_OTHER;
return C_PLAIN;
}
// array indexing, address arithmetic. mulsz scales an index by the element
@@ -1407,7 +1423,7 @@ int fixed_chk(int n) {
// and returns at once for any other pointer. sz is the element size in words, or
// minus the element size in bytes for packed elements.
int ptr_chk(int sz) {
- int k = lookup(s_bnd), j;
+ int k = lookup(s_bnd), j = 0;
if (k >= 0 && skind[k] == K_ENH) { // the test is one enhancement; the call only runs to report
lit(1); op(O_PICK); lit(1); op(O_PICK);
if (sz > 0) { lit(sz); lit(1); } else { lit(0 - sz); lit(8); }
@@ -1430,7 +1446,7 @@ int ptr_chk(int sz) {
// through rt.bchk, which only knows the size of a heap block's first word.
int postfix() { return postfix_tail(primary()); }
int postfix_tail(int c) {
- int b, n, t, et; word ft; word[8] bs; int k, ci, was, al, ix, enh, cz, cj, place, offset, temporary, call_owner, readonly, ub, bl, ic, inr, hg, pn, iu; word storage, storage_mask, call_origins;
+ int b, n, t, et; word ft; word[8] bs = {}; int k, ci, was, al, ix, enh, cz, cj, place, offset, temporary, call_owner, readonly, ub, bl, ic, inr, hg, pn, iu; word storage, storage_mask, call_origins;
while (1) {
if (tk == T_ID && tv == s_as) { // e as T: same bits, another type
next(); t = ptype(); rval(); if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 0);
@@ -1452,7 +1468,7 @@ int postfix_tail(int c) {
ub = !fbnd || o3on || (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)); // unbounded, or o3 code
bl = -1; if (lv && lvk == LV_LOCAL) bl = lvi;
next();
- rval();
+ rval_do(0); // indexing forms an element address; reading the element checks later
if (was) {
c = expr(); rval(); plainuse(c);
if (accept(s_colon)) {
@@ -1599,7 +1615,9 @@ 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 lvk = LV_OTHER;
+ if (b >= 0) { lvk = LV_ELEM; lvi = b; }
+ else if (bl >= 0) { lvk = LV_ELEM; lvi = bl; }
+ else lvk = LV_OTHER;
c = C_PLAIN;
xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0;
continue;
@@ -1622,6 +1640,7 @@ int postfix_tail(int c) {
ifcall = -1; effect_argument_base = 0;
int call_tags = 0; if (ft) call_tags = ft[5] >> 6;
n = args(call_tags, ft, bs, o3on); temporary = argtemporary; call_owner = argowner; call_origins = argorigin_mask;
+ place = tpack();
// raw code can live in a heap segment; a closure has an environment block
lit(n); op(O_PICK); callname(intern("callable_code"));
cz = hole(); op(O_JZ);
@@ -1719,7 +1738,7 @@ int unary() {
if (!lv && !isagg(xty)) die("& needs something addressable");
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;
+ if (lv && lvk == LV_LOCAL) { ltag[lvi] = ltag[lvi] | LREAD | LASG; mark_inited(lvi); }
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;
@@ -1799,7 +1818,7 @@ int bin(int minp) {
next(); rval(); plainuse(c);
lt = xty;
if (isfl(lt)) lf = 1;
- src = -1;
+ src = -1; rf = 0; rs = 0; right_mask = 0;
if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1
af_forget();
ftest();
@@ -1892,7 +1911,7 @@ int storekeep(int t) {
// restored for the second side) and then joined, so a variable can't be given
// away on one side only. both sides are fresh objects, or neither is.
int ternary(int c) {
- int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb, conv, skip, back; word temp_a, temp_b, mask_a, mask_b;
+ int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb, conv, skip, back = 0; word temp_a, temp_b, mask_a, mask_b;
rval(); plainuse(c);
ftest();
next();
@@ -1950,7 +1969,7 @@ int expr() {
if (k != LV_LOCAL) af_escape(xaf);
fcoerce(lt);
if (k == LV_LOCAL) {
- ltag[i] = ltag[i] | LASG;
+ ltag[i] = ltag[i] | LASG; mark_inited(i);
setflt(i, xfloat);
setstr(i, xstr);
note_store(i); af_store(i); vf_store(i);
@@ -1979,6 +1998,8 @@ int expr() {
if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
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);
lt = xty; require_mutable(lt);
fk = -1; lf = xfloat;
if (lvk == LV_LOCAL) fk = lvi;
diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc
index 95415a6..3b810ba 100644
--- a/src/compiler/lex.zc
+++ b/src/compiler/lex.zc
@@ -377,7 +377,7 @@ int fieldscan(int n) {
// before a program it joins to the runtime. the rest of the line is read here and the marker kept: the
// source is read once to find the signatures and again to compile, and it counts only when compiling reaches it.
int mark_line() {
- char[300] b; int n = 0, i, base; word name;
+ char[300] b = {}; int n = 0, i, base; word name;
while (ch >= 0 && ch != 10) { if (n < 298) { b[n] = ch; n++; } nextch(); }
while (n && (b[n - 1] == 13 || b[n - 1] == 32)) n--;
b[n] = 0;
diff --git a/src/compiler/ops.zc b/src/compiler/ops.zc
index a396f72..0129e75 100644
--- a/src/compiler/ops.zc
+++ b/src/compiler/ops.zc
@@ -141,7 +141,7 @@ int add_method(char* member, char* function_name) {
return s;
}
int receiver_method(word receiver_type, int member) {
- word link = receiver_methods, ft, actual; word[8] bs; int found = -1, s;
+ word link = receiver_methods, ft, actual; word[8] bs = {}; int found = -1, s;
while (link) {
s = link[2];
if (s < nsym && link[1] == member) {
diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc
index 79e5fb8..2e0cfe3 100644
--- a/src/compiler/stmt.zc
+++ b/src/compiler/stmt.zc
@@ -138,7 +138,7 @@ int localdecl(int typed) {
if (larr[i] && ltype[i][0] == TY_ARR && (is_op(s_lb) || tk == T_STR || tk == T_BSTR)) {
iq = array_initializer_tokens(ltype[i]); it = array_measure(iq, ltype[i]);
fslots = fslots + tsz(it) - tsz(ltype[i]);
- ltype[i] = it; array_initialize(iq, it, lslot[i], 1); rt.free(iq);
+ ltype[i] = it; array_initialize(iq, it, lslot[i], 1); rt.free(iq); mark_inited(i);
} else {
if (larr[i]) serr("an array or a struct needs an initializer list");
newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1;
@@ -159,7 +159,7 @@ int localdecl(int typed) {
else if (xty[0] == TY_STRUCT) ltype[i] = tptr(xty);
else ltype[i] = xty;
}
- assigncheck(LV_LOCAL, i, c);
+ assigncheck(LV_LOCAL, i, c); mark_inited(i);
if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); }
if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; }
}
@@ -271,7 +271,7 @@ int loop_end(int a, int sc) {
njps = 0;
if (sc >= 0) jadd(R_SNAP + sc);
p = R_EXIT + f[6]; e = R_EXIT + extop;
- while (p < e) { jadd(p + 1); p = p + 3 + 3 * (p[2] - p[1]); }
+ while (p < e) { jadd(p + 1); p = p + 3 + 4 * (p[2] - p[1]); }
join(n0, "leaves the loop in different states on different paths");
live = njps > 0;
p = R_EXIT + f[6];
@@ -283,7 +283,7 @@ int loop_end(int a, int sc) {
op(O_JMP);
}
else outs = fixnew(p[0], outs);
- p = p + 3 + 3 * (p[2] - p[1]);
+ p = p + 3 + 4 * (p[2] - p[1]);
}
if (a >= 0) setlit(a, here);
if (sc >= 0) fixups(R_SNAP + sc, n0, 1);
@@ -578,14 +578,14 @@ int case_seen(word seen, int n, int v) {
int kw_switch() {
int sl = tline;
int save = nloc, tmp, c, s0, live0, defent = -1, skip, nxt, bj, v, a = -1, st, tg = 0, cov = 0, gi, nb = 0, j, arm, bi;
- word[8] pbn; word[8] pbl; word[256] seen; int nseen = 0;
+ word[8] pbn = {}; word[8] pbl = {}; word[256] seen = {}; int nseen = 0;
stmtonly();
next();
expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp);
tg = ptagged(st);
fdepth++;
tmp = newloc(0); fslots++; // a hidden local holds the value
- laddr(lslot[tmp]); op(O_ST);
+ laddr(lslot[tmp]); op(O_ST); mark_inited(tmp);
live0 = live;
s0 = snap_push();
loop_push(-1, s0);
@@ -634,7 +634,7 @@ int kw_switch() {
for (j = 0; j < nb && j < pm_nf[gi]; j++) {
capture_cell(pbl[j], 0);
laddr(lslot[tmp]); op(O_LD); lit(pm_off[gi * 8 + j]); op(O_ADD); op(O_LD);
- vaddr(pbl[j]); op(O_ST);
+ vaddr(pbl[j]); op(O_ST); mark_inited(pbl[j]);
}
casebody(s0, live0);
block_close(arm);
@@ -848,7 +848,7 @@ int asmblock() {
if (tk != T_ID) die("bad token in asm");
i = findloc(tv);
if (i >= 0 && (ltag[i] & 16) && !larr[i]) serr("asm can't reach an o3 variable");
- if (i >= 0) { luse(i); ltag[i] = ltag[i] | LREAD | LASG; vaddr(i); next(); continue; }
+ if (i >= 0) { need_inited(i); luse(i); ltag[i] = ltag[i] | LREAD | LASG; mark_inited(i); vaddr(i); next(); continue; }
if (nclo) {
k = clocap(tv);
if (k >= 0) { vaddr(clo_env[nclo - 1]); op(O_LD); lit(k); op(O_ADD); op(O_LD); next(); continue; }
diff --git a/src/compiler/types.zc b/src/compiler/types.zc
index 322d0fd..615af93 100644
--- a/src/compiler/types.zc
+++ b/src/compiler/types.zc
@@ -181,7 +181,7 @@ word tinst(word g, word args, int n) {
}
// lay out a generic instance on first use
word tresolve(word t) {
- word g, f; int n, i; word fs; int off = 0, ft; word[8] bs;
+ word g, f; int n, i; word fs; int off = 0, ft; word[8] bs = {};
if (t[0] != TY_STRUCT || t[3] >= 0) return t;
if (t[3] == -2) { serr("a struct can't contain itself by value"); t[3] = 0; return t; }
t[3] = -2;
@@ -206,7 +206,7 @@ word tsz(word t) {
// replace type parameters with what they were bound to (unbound ones become word)
word subst(word t, word bs) {
if (treadonly(t)) return tconst(subst(tunconst(t), bs));
- int k = t[0], i, n; word[24] v; word vec;
+ int k = t[0], i, n; word[24] v = {}; word vec;
if (k == TY_VAR) { if (bs[t[3]]) return bs[t[3]]; return tword; }
if (k == TY_PTR) return tptr(subst(t[4], bs));
if (k == TY_ARR) return tarr(subst(t[4], bs), t[3]);
@@ -399,7 +399,7 @@ int closeangle() {
// the first thing in ptype's comment applies here too: fn(int) is a function
// returning a plain word.
int ptype() {
- int start = toff, t = ptype0(), np, tags, tag; word[24] ps;
+ int start = toff, t = ptype0(), np, tags, tag; word[24] ps = {};
while (is_op(s_lp)) {
next(); np = 0; tags = 0;
if (!is_op(s_rp)) {
@@ -445,7 +445,7 @@ int ptype0() {
}
// parse one type with no trailing function parts.
word ptype1() {
- int n, s; word t; word[8] args; int na = 0, i;
+ int n, s; word t; word[8] args = {}; int na = 0, i;
if (is_op(s_lk)) { die("array types are postfix: T[N], T[] or T[?]"); return tword; }
if (is_id(s_const)) { next(); return tconst(ptype0()); }
if (is_id(s_fixed)) { // fixed T[?] can't grow; fixed T[n] says what T[n] already means
@@ -880,7 +880,7 @@ int methdef(char* sn, char* mn, word q, int stat, int typed, int rty, int rtag)
// has been compiled, and its result class is fresh if the method returns .u.
int methcall(int s, word rst, word receiver_fact, int receiver, word receiver_mask) {
af_forget();
- int np, tags = 0, ret = 0, temporary, owner, aliases, i; word ft = stype[s]; word[8] bs; word owner_fact, origins;
+ int np, tags = 0, ret = 0, temporary, owner, aliases, i; word ft = stype[s]; word[8] bs = {}; word owner_fact, origins;
word requirements = effect_requirements, edges = effect_edges; int original = s;
effect_args_mask(s, 0, receiver_mask);
if (receiver_fact && (receiver_fact[2] == 4 || receiver_fact[2] == 5)) effect_require(s, 0, callpos);
diff --git a/src/lib/actor.zc b/src/lib/actor.zc
index c013441..d0c0f10 100644
--- a/src/lib/actor.zc
+++ b/src/lib/actor.zc
@@ -121,7 +121,7 @@ unchecked int write_message(int fd, word address, word type) {
rt.free(refs); rt.free(data); return result;
}
unchecked int read_message(int fd, word address, word type) {
- int count = 0; word data, refs, frame[2];
+ int count = 0; word data, refs, frame[2] = {};
if (rt.syscall(45, fd, &count, 1) < 0 || count < 0 || count > 1048576) return -1;
data = rt.dnew(1, count, 0); refs = vector(); frame[0] = data; frame[1] = 0;
if (rt.syscall(45, fd, data[2], count) < 0) { rt.free(refs); rt.free(data); return -1; }
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 536d71c..79afb36 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -67,7 +67,7 @@ int ws(char* s) {
}
// writes n in decimal, with a minus sign when it is negative.
int wn(int n) {
- word[24] b; int i = 0;
+ word[24] b = {}; int i = 0;
if (n < 0) { wc('-'); n = 0 - n; }
while (1) { b[i] = '0' + n % 10; i++; n = n / 10; if (!n) break; }
while (i) { i--; wc(b[i]); }
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index 4f96320..a79387f 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -182,7 +182,7 @@ int put_s(char* s) {
// write the decimal form of a number, with a minus sign for a negative one.
// the smallest integer is handled as its own case.
int put_n(int n) {
- char[24] b; int i = 0;
+ char[24] b = {}; int i = 0;
if (n < 0) { put_c('-'); n = 0 - n; }
if (n < 0) { put_s("9223372036854775808"); return 0; }
while (1) { b[i++] = '0' + n % 10; n = n / 10; if (!n) break; }
@@ -340,7 +340,7 @@ int s_to_n(bstr s) {
}
// a new packed byte string holding the decimal form of n.
bstr n_to_s(int n) {
- word[24] b; int i = 0, j = 0, neg = 0; bstr p; char* t;
+ word[24] b = {}; int i = 0, j = 0, neg = 0; bstr p; char* t;
if (!n) { p = new_s(1); sput(p, 0, '0'); sput(p, 1, 0); return p; }
if (n < 0) { neg = 1; n = 0 - n; }
if (n < 0) {
@@ -431,7 +431,7 @@ const int F_HASDROP = 16, F_DROPPED = 32, F_FINAL = 64, F_CYCLE = 128; // F_CYCL
int o3_depth, gc_pending, gc_cyc; // gc_cyc: the last sweep found a cycle with drops to run
// flags live in these bits; the size class is above them.
const int F_LOW = 65535;
-int hbump; word[32] freel; int gc_runs, gc_freed, gc_auto = 1;
+int hbump; word[32] freel = {}; int gc_runs, gc_freed, gc_auto = 1;
// block walks and new chunks stop here.
int hstop;
// while nonzero, balloc will not return a block overlapping this range.
@@ -1201,7 +1201,7 @@ int line_of(word b, int a) {
}
// write the hexadecimal form of a number
int put_hex(int n) {
- char[20] b; int i = 0, d;
+ char[20] b = {}; int i = 0, d;
put_s("0x");
if (!n) { put_c('0'); return 0; }
while (n && i < 16) { d = n & 15; if (d < 10) b[i++] = '0' + d; else b[i++] = 'a' + d - 10; n = n >> 4; }
diff --git a/src/tools/zc0.c b/src/tools/zc0.c
index 229734c..07a5933 100644
--- a/src/tools/zc0.c
+++ b/src/tools/zc0.c
@@ -824,14 +824,24 @@ static void stmt(void)
if (nw >= 0 || accept("let") || accept("var")) {
if (nw < 0) nw = 1;
do {
- Loc *l = &lo[nlo++]; strcpy(l->name, ident()); l->arr = 0;
- if (nw > 1) { l->slot = nslot; nslot += nw; l->arr = 1; }
+ Loc *l = &lo[nlo++]; strcpy(l->name, ident()); l->arr = 0; int asz = 1;
+ if (nw > 1) { l->slot = nslot; nslot += nw; l->arr = 1; asz = nw; }
else {
- if (accept("[")) { n = cexpr(); expect("]"); nslot += n; l->slot = nslot - n; l->arr = 1; }
+ if (accept("[")) { n = cexpr(); expect("]"); nslot += n; l->slot = nslot - n; l->arr = 1; asz = n; }
else l->slot = nslot++;
}
l->flt = 0; l->dstr = tstr && !l->arr; l->str = l->dstr;
- if (accept("=")) { expr(); rval(); laddr(l->slot); op(O_ST); l->flt = xf; l->str = xs || l->dstr; }
+ if (accept("=")) {
+ if (is("{")) { /* brace initializer: skip the list and zero the slots */
+ int depth = 0, zi;
+ do {
+ if (is("{")) { depth++; next(); }
+ else if (is("}")) { depth--; next(); }
+ else next();
+ } while (depth > 0 && tk);
+ for (zi = 0; zi < asz; zi++) { lit(0); laddr(l->slot + zi); op(O_ST); }
+ } else { expr(); rval(); laddr(l->slot); op(O_ST); l->flt = xf; l->str = xs || l->dstr; }
+ }
} while (accept(","));
expect(";"); return;
}
@@ -1106,10 +1116,15 @@ static void top1(void)
else if (accept("[")) { k = (int)cexpr(); expect("]"); }
if (k > 1) {
a = here; while (k--) emit(0); add(n, K_GARR, a);
+ if (accept("=")) { /* = {} or = {...}: already zeroed */
+ if (!is("{")) die("a global array initializer is a brace list");
+ { int depth = 0; do { if (is("{")) { depth++; next(); } else if (is("}")) { depth--; next(); } else next(); } while (depth > 0 && tk); }
+ }
} else {
a = here; gs = add(n, K_GVAR, a); gs->str = gstr; emit(0);
if (accept("=")) {
- if (tk == T_STR) { int i, l = (int)strlen(ts); img[a - GORG] = here; for (i = 0; i < l; i++) emit((unsigned char)ts[i]); emit(0); next(); }
+ if (is("{")) { int depth = 0; do { if (is("{")) { depth++; next(); } else if (is("}")) { depth--; next(); } else next(); } while (depth > 0 && tk); }
+ else if (tk == T_STR) { int i, l = (int)strlen(ts); img[a - GORG] = here; for (i = 0; i < l; i++) emit((unsigned char)ts[i]); emit(0); next(); }
else if (tk == T_BSTR) { int d = bstrwords((const unsigned char *)ts, (long)strlen(ts)); img[a - GORG] = d; gs->str = 1; next(); }
else img[a - GORG] = cexpr();
}
diff --git a/tests/conformance/4-statements/uninit_array_init_ok.zc b/tests/conformance/4-statements/uninit_array_init_ok.zc
new file mode 100644
index 0000000..d291448
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_array_init_ok.zc
@@ -0,0 +1,10 @@
+// expect: 10/
+// spec 4.1.5: a full array initializer list initializes the array
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ int[4] a = { 1, 2, 3, 4 };
+ pn(a[0] + a[1] + a[2] + a[3]);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_assigned_ok.zc b/tests/conformance/4-statements/uninit_assigned_ok.zc
new file mode 100644
index 0000000..6c831a4
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_assigned_ok.zc
@@ -0,0 +1,11 @@
+// expect: 3/
+// spec 4.1.5: a local assigned before use is fine
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ int x;
+ x = 3;
+ pn(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_both_branches_ok.zc b/tests/conformance/4-statements/uninit_both_branches_ok.zc
new file mode 100644
index 0000000..205acdf
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_both_branches_ok.zc
@@ -0,0 +1,11 @@
+// expect: 1/
+// spec 4.1.5: every branch assigns before the join
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ int x;
+ if (1) x = 1; else x = 2;
+ pn(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_branch_rejected.zc b/tests/conformance/4-statements/uninit_branch_rejected.zc
new file mode 100644
index 0000000..ca93c39
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_branch_rejected.zc
@@ -0,0 +1,10 @@
+// expect error: used before it is initialized
+// spec 4.1.5: a local set on only some branches is unset at the join
+include rt;
+fn main() {
+ int x;
+ if (0) x = 1;
+ rt.put_n(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_do_loop_ok.zc b/tests/conformance/4-statements/uninit_do_loop_ok.zc
new file mode 100644
index 0000000..4f09302
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_do_loop_ok.zc
@@ -0,0 +1,12 @@
+// expect: 1/
+// spec 4.1.5: do/while always runs once, so an assignment in the body initializes
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ int x;
+ int n = 0;
+ do { x = 1; n = 0; } while (n);
+ pn(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_loop_rejected.zc b/tests/conformance/4-statements/uninit_loop_rejected.zc
new file mode 100644
index 0000000..67d8120
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_loop_rejected.zc
@@ -0,0 +1,11 @@
+// expect error: used before it is initialized
+// spec 4.1.5: a loop that may run zero times does not initialize
+include rt;
+fn main() {
+ int x;
+ int n = 0;
+ while (n) x = 1;
+ rt.put_n(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_outparam_agg_ok.zc b/tests/conformance/4-statements/uninit_outparam_agg_ok.zc
new file mode 100644
index 0000000..75ba07d
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_outparam_agg_ok.zc
@@ -0,0 +1,13 @@
+// expect: 5/
+// spec 4.1.5: &aggregate counts as initializing the whole aggregate
+include rt;
+struct point { int x; int y; }
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+void fill(point* p) { p->x = 2; p->y = 3; }
+fn main() {
+ point p;
+ fill(&p);
+ pn(p.x + p.y);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_outparam_ok.zc b/tests/conformance/4-statements/uninit_outparam_ok.zc
new file mode 100644
index 0000000..e97f54b
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_outparam_ok.zc
@@ -0,0 +1,12 @@
+// expect: 7/
+// spec 4.1.5: passing &x counts as initializing x
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+void fill(int* p) { *p = 7; }
+fn main() {
+ int x;
+ fill(&x);
+ pn(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_partial_struct_rejected.zc b/tests/conformance/4-statements/uninit_partial_struct_rejected.zc
new file mode 100644
index 0000000..9c71f92
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_partial_struct_rejected.zc
@@ -0,0 +1,11 @@
+// expect error: used before it is initialized
+// spec 4.1.5: writing some fields does not initialize the aggregate
+include rt;
+struct point { int x; int y; }
+fn main() {
+ point p;
+ p.x = 1;
+ rt.put_n(p.y);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_read_rejected.zc b/tests/conformance/4-statements/uninit_read_rejected.zc
new file mode 100644
index 0000000..3ef407a
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_read_rejected.zc
@@ -0,0 +1,5 @@
+// expect error: used before it is initialized
+// spec 4.1.5: reading a local before any assignment is an error
+include rt;
+fn main() { int x; rt.put_n(x); return 0; }
+// zc0: skip
diff --git a/tests/conformance/4-statements/uninit_struct_init_ok.zc b/tests/conformance/4-statements/uninit_struct_init_ok.zc
new file mode 100644
index 0000000..ab033ae
--- /dev/null
+++ b/tests/conformance/4-statements/uninit_struct_init_ok.zc
@@ -0,0 +1,11 @@
+// expect: 9/
+// spec 4.1.5: while(1) always enters, so an assignment before break initializes
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ int x;
+ while (1) { x = 9; break; }
+ pn(x);
+ return 0;
+}
+// zc0: skip
diff --git a/tests/errors/uninit_branch.zc b/tests/errors/uninit_branch.zc
new file mode 100644
index 0000000..e4d253b
--- /dev/null
+++ b/tests/errors/uninit_branch.zc
@@ -0,0 +1,10 @@
+namespace;
+include rt;
+
+// expect: x: used before it is initialized
+fn main() {
+ int x;
+ if (rt.get_c()) x = 1;
+ rt.put_n(x);
+ return 0;
+}
diff --git a/tests/errors/uninit_loop.zc b/tests/errors/uninit_loop.zc
new file mode 100644
index 0000000..d345296
--- /dev/null
+++ b/tests/errors/uninit_loop.zc
@@ -0,0 +1,11 @@
+namespace;
+include rt;
+
+// expect: x: used before it is initialized
+fn main() {
+ int x;
+ int n = 0;
+ while (n) { x = 1; n = 0; }
+ rt.put_n(x);
+ return 0;
+}
diff --git a/tests/errors/uninit_partial_array.zc b/tests/errors/uninit_partial_array.zc
new file mode 100644
index 0000000..d30310f
--- /dev/null
+++ b/tests/errors/uninit_partial_array.zc
@@ -0,0 +1,10 @@
+namespace;
+include rt;
+
+// expect: a: used before it is initialized
+fn main() {
+ int[4] a;
+ a[0] = 1;
+ rt.put_n(a[1]);
+ return 0;
+}
diff --git a/tests/errors/uninit_partial_struct.zc b/tests/errors/uninit_partial_struct.zc
new file mode 100644
index 0000000..7b65481
--- /dev/null
+++ b/tests/errors/uninit_partial_struct.zc
@@ -0,0 +1,12 @@
+namespace;
+include rt;
+
+struct point { int x; int y; }
+
+// expect: p: used before it is initialized
+fn main() {
+ point p;
+ p.x = 1;
+ rt.put_n(p.y);
+ return 0;
+}
diff --git a/tests/errors/uninit_read.zc b/tests/errors/uninit_read.zc
new file mode 100644
index 0000000..3a18a95
--- /dev/null
+++ b/tests/errors/uninit_read.zc
@@ -0,0 +1,9 @@
+namespace;
+include rt;
+
+// expect: x: used before it is initialized
+fn main() {
+ int x;
+ rt.put_n(x);
+ return 0;
+}