From 08173d7f88f8551caca81594ed8af79d82d3f71f Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 18:25:36 +0330 Subject: constant expressions: c precedence and / % ^ (b.1.3); local const usable in constant expressions (b.1.7, b.2.16); zc0 matches --- tests/conformance/3-expressions/const_expr_array_length.zc | 2 +- tests/conformance/3-expressions/const_expr_case_label.zc | 6 +++--- tests/conformance/3-expressions/const_expr_division.zc | 7 +++++++ .../conformance/3-expressions/const_expr_division_by_zero.zc | 4 ++++ .../conformance/3-expressions/const_expr_division_rejected.zc | 4 ---- tests/conformance/3-expressions/const_expr_left_to_right.zc | 8 -------- tests/conformance/3-expressions/const_expr_local_const.zc | 11 +++++++++++ .../3-expressions/const_expr_local_const_rejected.zc | 7 ------- tests/conformance/3-expressions/const_expr_modulo.zc | 6 ++++++ tests/conformance/3-expressions/const_expr_modulo_rejected.zc | 4 ---- tests/conformance/3-expressions/const_expr_neg_not_parens.zc | 2 +- tests/conformance/3-expressions/const_expr_precedence.zc | 9 +++++++++ tests/conformance/3-expressions/const_expr_xor.zc | 6 ++++++ tests/conformance/3-expressions/const_expr_xor_rejected.zc | 4 ---- tests/conformance/4-statements/switch_label_forms.zc | 4 ++-- tests/conformance/5-declarations/global_const_expr_init.zc | 4 ++-- .../9-appendix-b/b2_16_local_const_in_const_expr.zc | 2 +- tests/conformance/known.txt | 1 - 18 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 tests/conformance/3-expressions/const_expr_division.zc create mode 100644 tests/conformance/3-expressions/const_expr_division_by_zero.zc delete mode 100644 tests/conformance/3-expressions/const_expr_division_rejected.zc delete mode 100644 tests/conformance/3-expressions/const_expr_left_to_right.zc create mode 100644 tests/conformance/3-expressions/const_expr_local_const.zc delete mode 100644 tests/conformance/3-expressions/const_expr_local_const_rejected.zc create mode 100644 tests/conformance/3-expressions/const_expr_modulo.zc delete mode 100644 tests/conformance/3-expressions/const_expr_modulo_rejected.zc create mode 100644 tests/conformance/3-expressions/const_expr_precedence.zc create mode 100644 tests/conformance/3-expressions/const_expr_xor.zc delete mode 100644 tests/conformance/3-expressions/const_expr_xor_rejected.zc (limited to 'tests/conformance') diff --git a/tests/conformance/3-expressions/const_expr_array_length.zc b/tests/conformance/3-expressions/const_expr_array_length.zc index 07434a9..c6f11e3 100644 --- a/tests/conformance/3-expressions/const_expr_array_length.zc +++ b/tests/conformance/3-expressions/const_expr_array_length.zc @@ -1,4 +1,4 @@ -// expect: 10/ +// expect: 8/ include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } fn main() { diff --git a/tests/conformance/3-expressions/const_expr_case_label.zc b/tests/conformance/3-expressions/const_expr_case_label.zc index 3f1e781..0c76d4b 100644 --- a/tests/conformance/3-expressions/const_expr_case_label.zc +++ b/tests/conformance/3-expressions/const_expr_case_label.zc @@ -1,9 +1,9 @@ -// expect: nine/ +// expect: seven/ include rt; fn main() { - int k = 9; + int k = 7; switch (k) { - case 1 + 2 * 3: rt.put_s("nine"); + case 1 + 2 * 3: rt.put_s("seven"); default: rt.put_s("other"); } rt.put_c(10); diff --git a/tests/conformance/3-expressions/const_expr_division.zc b/tests/conformance/3-expressions/const_expr_division.zc new file mode 100644 index 0000000..90db20c --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_division.zc @@ -0,0 +1,7 @@ +// expect: 3/-3/2/ +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +const Z = 6 / 2; +const N = -7 / 2; +const P = 1 + 6 / 3 * 2 - 3; +fn main() { pn(Z); pn(N + 0); pn(P); return 0; } diff --git a/tests/conformance/3-expressions/const_expr_division_by_zero.zc b/tests/conformance/3-expressions/const_expr_division_by_zero.zc new file mode 100644 index 0000000..f1011ff --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_division_by_zero.zc @@ -0,0 +1,4 @@ +// expect error: division by zero in a constant expression +include rt; +const Z = 1 / (2 - 2); +fn main() { return 0; } diff --git a/tests/conformance/3-expressions/const_expr_division_rejected.zc b/tests/conformance/3-expressions/const_expr_division_rejected.zc deleted file mode 100644 index f095247..0000000 --- a/tests/conformance/3-expressions/const_expr_division_rejected.zc +++ /dev/null @@ -1,4 +0,0 @@ -// expect error: expected ';', found '/' -include rt; -const Z = 6 / 2; -fn main() { return 0; } diff --git a/tests/conformance/3-expressions/const_expr_left_to_right.zc b/tests/conformance/3-expressions/const_expr_left_to_right.zc deleted file mode 100644 index 68e26ab..0000000 --- a/tests/conformance/3-expressions/const_expr_left_to_right.zc +++ /dev/null @@ -1,8 +0,0 @@ -// expect: 9/5/7/255/ -include rt; -void pn(int x) { rt.put_n(x); rt.put_c(10); } -const X = 1 + 2 * 3; -const Y = 10 - 2 - 3; -const V = 1 | 2 + 4; -const W = ~0 & 255; -fn main() { pn(X); pn(Y); pn(V); pn(W); return 0; } diff --git a/tests/conformance/3-expressions/const_expr_local_const.zc b/tests/conformance/3-expressions/const_expr_local_const.zc new file mode 100644 index 0000000..46a8d55 --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_local_const.zc @@ -0,0 +1,11 @@ +// expect: 5/11/7/ +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +fn main() { + const n = 5; + const m = n * 2 + 1; + int[m] a; + const int k = 7; + pn(n); pn(len(a)); switch (7) { case k: pn(k); } + return 0; +} diff --git a/tests/conformance/3-expressions/const_expr_local_const_rejected.zc b/tests/conformance/3-expressions/const_expr_local_const_rejected.zc deleted file mode 100644 index edab64e..0000000 --- a/tests/conformance/3-expressions/const_expr_local_const_rejected.zc +++ /dev/null @@ -1,7 +0,0 @@ -// expect error: expected a constant -include rt; -fn main() { - const n = 5; - int[n] a; - return 0; -} diff --git a/tests/conformance/3-expressions/const_expr_modulo.zc b/tests/conformance/3-expressions/const_expr_modulo.zc new file mode 100644 index 0000000..3f32f46 --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_modulo.zc @@ -0,0 +1,6 @@ +// expect: 1/10/ +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +const Z = 7 % 2; +const Q = 1 + 20 % 7 * 3; +fn main() { pn(Z); pn(Q - 9); return 0; } diff --git a/tests/conformance/3-expressions/const_expr_modulo_rejected.zc b/tests/conformance/3-expressions/const_expr_modulo_rejected.zc deleted file mode 100644 index 4c54ffe..0000000 --- a/tests/conformance/3-expressions/const_expr_modulo_rejected.zc +++ /dev/null @@ -1,4 +0,0 @@ -// expect error: zc: line 3: -include rt; -const Z = 7 % 2; -fn main() { return 0; } diff --git a/tests/conformance/3-expressions/const_expr_neg_not_parens.zc b/tests/conformance/3-expressions/const_expr_neg_not_parens.zc index 6e96bdc..714990a 100644 --- a/tests/conformance/3-expressions/const_expr_neg_not_parens.zc +++ b/tests/conformance/3-expressions/const_expr_neg_not_parens.zc @@ -1,4 +1,4 @@ -// expect: -3/-6/-1/24/ +// expect: -3/-4/-1/24/ include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } const A = -3; diff --git a/tests/conformance/3-expressions/const_expr_precedence.zc b/tests/conformance/3-expressions/const_expr_precedence.zc new file mode 100644 index 0000000..c173959 --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_precedence.zc @@ -0,0 +1,9 @@ +// expect: 7/5/7/255/16/ +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +const X = 1 + 2 * 3; +const Y = 10 - 2 - 3; +const V = 1 | 2 + 4; +const W = ~0 & 255; +const S = 1 + 3 << 1 + 1 >> 1 - 1 | 6 & 2; +fn main() { pn(X); pn(Y); pn(V); pn(W); pn(S - 2); return 0; } diff --git a/tests/conformance/3-expressions/const_expr_xor.zc b/tests/conformance/3-expressions/const_expr_xor.zc new file mode 100644 index 0000000..30ca0a7 --- /dev/null +++ b/tests/conformance/3-expressions/const_expr_xor.zc @@ -0,0 +1,6 @@ +// expect: 5/7/ +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +const Z = 7 ^ 2; +const Q = 1 ^ 2 | 4 & 4; +fn main() { pn(Z); pn(Q); return 0; } diff --git a/tests/conformance/3-expressions/const_expr_xor_rejected.zc b/tests/conformance/3-expressions/const_expr_xor_rejected.zc deleted file mode 100644 index eb1ad25..0000000 --- a/tests/conformance/3-expressions/const_expr_xor_rejected.zc +++ /dev/null @@ -1,4 +0,0 @@ -// expect error: zc: line 3: -include rt; -const Z = 7 ^ 2; -fn main() { return 0; } diff --git a/tests/conformance/4-statements/switch_label_forms.zc b/tests/conformance/4-statements/switch_label_forms.zc index cfe2712..ef5e33b 100644 --- a/tests/conformance/4-statements/switch_label_forms.zc +++ b/tests/conformance/4-statements/switch_label_forms.zc @@ -1,5 +1,5 @@ // expect: neg/chr/sum/enum/ -// spec 4.7.1 and 3.14: labels are constant expressions (no precedence: 1 + 1 * 3 is 6) +// spec 4.7.1 and 3.14: labels are constant expressions (c precedence: 1 + 1 * 3 is 4) include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } enum color { RED, GREEN }; @@ -12,5 +12,5 @@ void t(int k) { } rt.put_c(10); } -fn main() { t(-1); t('a'); t(6); t(1); return 0; } +fn main() { t(-1); t('a'); t(4); t(1); return 0; } // zc0: skip diff --git a/tests/conformance/5-declarations/global_const_expr_init.zc b/tests/conformance/5-declarations/global_const_expr_init.zc index 8a9d30b..64a4d06 100644 --- a/tests/conformance/5-declarations/global_const_expr_init.zc +++ b/tests/conformance/5-declarations/global_const_expr_init.zc @@ -1,5 +1,5 @@ -// expect: 4/ -// spec 5.3.2 and 3.14.1 (left to right: ((1+2)*2)-2) +// expect: 3/ +// spec 5.3.2 and 3.14.1 (c precedence: 1+(2*2)-2) include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } int g = 1 + 2 * 2 - 2; diff --git a/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc b/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc index 70093c9..649321f 100644 --- a/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc +++ b/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc @@ -1,5 +1,5 @@ // expect: 5/ -// spec appendix b.2.16 and 3.14.2: a local const is not a constant expression +// spec 3.14.2 (was appendix b.2.16): a local const is a constant expression include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } fn main() { const n = 5; int[n] a; pn(5); return 0; } diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 8eeb0d3..25bc3b7 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -4,7 +4,6 @@ 4-statements/o3_outside_function_ordinary_block.zc zy c top-level o3 { } is rejected; spec 4.10.2 says it is an ordinary block 9-appendix-b/b2_10_recovery_hides_errors.zc zy c appendix b.2.10 confirmed; by design per 8.2 9-appendix-b/b2_15_duplicate_case.zc zy c appendix b.2.15 confirmed; 4.7.2 says duplicates are allowed -9-appendix-b/b2_16_local_const_in_const_expr.zc zy c appendix b.2.16 confirmed; 3.14.2 documents it 2-types/conv_argument_and_return.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) 2-types/conv_assignment_to_float.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) 2-types/conv_f32_rounds.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) -- cgit v1.2.3 From e7fe081efaae893ed46548fdbc3bfa39b2879878 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 18:29:55 +0330 Subject: duplicate case labels are an error (b.2.15); unterminated string literal reported where it starts (b.2.11); an unreachable droppable cycle drops whole in one collection (6.15.3) --- TODO.md | 1 + docs/SPEC.md | 10 ++++----- docs/design/destructors.md | 2 +- src/compiler/lex.zc | 6 ++++-- src/compiler/stmt.zc | 12 ++++++++++- src/runtime/rt.zc | 14 ++++++++----- tests/conformance/1-source/string_unterminated.zc | 5 +++++ .../switch_duplicate_label_rejected.zc | 6 ++++++ .../switch_duplicate_labels_first_wins.zc | 11 ---------- .../4-statements/switch_same_label_other_switch.zc | 5 +++++ tests/conformance/6-ownership/drop_gc_cycle.zc | 4 ++-- .../conformance/6-ownership/drop_gc_cycle_long.zc | 24 ++++++++++++++++++++++ .../9-appendix-b/b2_15_duplicate_case.zc | 2 +- tests/conformance/known.txt | 1 - 14 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 tests/conformance/1-source/string_unterminated.zc create mode 100644 tests/conformance/4-statements/switch_duplicate_label_rejected.zc delete mode 100644 tests/conformance/4-statements/switch_duplicate_labels_first_wins.zc create mode 100644 tests/conformance/4-statements/switch_same_label_other_switch.zc create mode 100644 tests/conformance/6-ownership/drop_gc_cycle_long.zc (limited to 'tests/conformance') diff --git a/TODO.md b/TODO.md index fe6baf9..ace4665 100644 --- a/TODO.md +++ b/TODO.md @@ -522,6 +522,7 @@ brief someone without re-reading the code. - [x] remaining zc0 gaps, one commit each on work/xref: string literals have no length limit (the token text grows; `1-source/lit_string_over_4095_bytes.zc`); `.u` on a typed parameter is rejected with the compiler's message, "a parameter tag is .m or .d" (`5-declarations/fn_param_tag_u_rejected.zc`); `x as T` reads a whole type, so `x as int*` and `x as char**` work (`3-expressions/cast_as_pointer_type.zc`); `embed` of an array type inside a function puts the elements in the code and the local holds their address (`tests/programs/embed_local_array.zc`, a parity program); integer literals as the compiler reads them, so `010` is 10, `0x` is 0 and values wrap modulo 2^64 (the eleven class b literal entries left `known.txt`). `docs/history/43-stdlib/notes.md` has a forward note that zc0 no longer reserves u/m/d. the genome is byte-identical to main's. - [x] verify both compilers against `docs/SPEC.md`: `tests/conformance/
/` (sections 1 to 8 plus `9-appendix-b`, 657 programs), runner `tests/conformance/run.sh` (`--zc0`, `--zym`, `--zy`, `-w`), understood failures in `tests/conformance/known.txt` (class a self-hosted bug, b zc0, c spec issue), wired into the interp section of `tests/test.sh`. zy 626 ok, 31 known; zc0 221 ok, 38 known, 398 skipped (outside appendix a). appendix b.2 items 1 to 16 confirmed except b.2.8, which is documented behavior (5.3.2, 3.14.2; the spec wins). top-level `o3 { }` is rejected (4.10.2, class c, left alone). the seven class a bugs are fixed, one commit each: a bad literal in a function's first statement (capture_scan left panic set), `u*d` with visible `u`/`d`, the bstr flag surviving a cast, the 24-parameter limit, `return e;` in a void function (now dropped, yields 0), `memdebug scrub;`, and `case member():` on a member with fields. the conformance run is in `tests/test.sh` but not in `-q`. open: `rt.memreport()` reports buddy capacity (`rt.new(2)` is 3 words), whether that is the spec's W; whether top-level `unchecked;` is in the zc0 subset. +- [x] (work/known-bugs) constant-expression precedence (b.1.3), local const in constant expressions (b.1.7/b.2.16), duplicate case labels (b.2.15), unterminated string literals (b.2.11), droppable cycles drop whole in one collection (6.15.3). still open from the line below: b.2.10 (by design), top-level `o3 { }` (4.10.2). - [ ] fix the implementation bugs still open in `docs/SPEC.md` appendix b: unterminated string literals are only reported as a later syntax error (b.2.11 fixed the `/*` half); duplicate `case` labels are not diagnosed (b.2.15); a local `const` cannot be used in a constant expression (b.2.16, b.1.7); constant expressions have no precedence, so `1+2*3` is 9 (b.1.3); syntax-error recovery hides errors in the skipped statements (b.2.10, by design; decide whether to keep it). fixed and merged from work/spec: enum members naming earlier members (b.2.2), function redefinition (b.2.3), prototype after the definition (b.2.4), `?:` with int and float branches (b.2.5), `bool` normalisation (b.2.6), float global initializers (b.2.7, integer constants too via 46825f2), `include std;` in a function body (b.2.9), unterminated `/*` (b.2.11), `x as int * y` settled as a syntax error (b.2.12, doc only), unnamed prototype parameters (b.2.13), `len(char*)` with typecheck off (b.2.14). fix the prose items appendix b.1 lists too (verify which are still open). - [ ] optional, low priority: plain enums and `?:` in zc0 (both are small and produce ordinary code), so parity covers more programs. no structs in zc0 (decided). - [ ] group the compiler's parallel per-local arrays (`lname`, `ltype`, `lstate`, `lhome`, `lown`, `lslot`, `ltag`, `lpos`, `lnsz`, `lleaf`, ...) without structs: declare each family together in one place, with one comment block stating each array's meaning and invariant (for example when `lhome` is meaningful), route snapshot and restore of the family through one pair of functions, and use the cross-reference report to confirm nothing else writes them. then symbols and types the same way. fixpoint and analyzer behavior unchanged. diff --git a/docs/SPEC.md b/docs/SPEC.md index 9d860fe..a58b59a 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -91,7 +91,7 @@ a keyword marked only S used inside an expression is the error `NAME: can't be u 1.4.3 float literals are ieee-754 binary64 values stored in one word. forms: `1.5`, `1.` , `.5`, `1e3`, `1.5e-3`, `1E+3`. the lexer reads the digits, optional `.digits`, and an optional exponent `e|E [+-] digits`; an exponent letter without digits after it is error: bad float. a dot after digits is a fraction when a digit, `e` or `E` follows it, is a field access when a letter or `_` (other than `e`/`E`) follows (`1.field`), and otherwise ends the literal (`1.` is a float). the type is `float`. -1.4.4 word strings. `"..."` is a string of type `char*`: the bytes are stored one per word in the code area, followed by a 0 word. escapes are as for character literals. a `\0` (or any byte 0) inside the literal ends the string there (`"ab\0cd"` is `"ab"`). a literal may contain a raw line feed. an unterminated string runs to the end of the unit and then error: expected ')' (or the like) at the end of the file. adjacent literals are not concatenated. the literal is static storage that is not a heap object; it is read-only by convention (writing through it is unspecified). +1.4.4 word strings. `"..."` is a string of type `char*`: the bytes are stored one per word in the code area, followed by a 0 word. escapes are as for character literals. a `\0` (or any byte 0) inside the literal ends the string there (`"ab\0cd"` is `"ab"`). a literal may contain a raw line feed. a string with no closing quote is error: unterminated string literal, reported on the line where it starts. adjacent literals are not concatenated. the literal is static storage that is not a heap object; it is read-only by convention (writing through it is unspecified). 1.4.5 packed byte strings. `b"..."`, where `b` touches the opening quote, has type `bstr`: a pointer to a block whose word 0 is the length in bytes, followed by the bytes eight to a word, little-endian, followed by a 0 byte. escapes and the `\0` truncation rule are as for word strings. `b "x"` (with a space) is the name `b` then a string. ``` @@ -595,7 +595,7 @@ pn(n); // 13 switch (k) { case 1, 2: ps("one-two"); case 3: ps("three"); break; default: ps("d"); } // k = 2: prints one-two only. no fallthrough into case 3. ``` -4.7.2 duplicate labels are not an error: the first matching case in source order runs. a non-constant label is error: expected a constant. the statements of a case form their own block. +4.7.2 a label value that appears twice in one switch (two equal constants, or a tagged member twice) is error: duplicate case label. a non-constant label is error: expected a constant. the statements of a case form their own block. 4.7.3 tagged enums. when `e` has type pointer-to-tagged-enum, `case member(a, b):` matches the member and binds each payload field, in order, to a new local scoped to that case; the name count must equal the member's field count (error: a case binds every field of its member). `case member:` or `case member():` with no names matches by tag. a switch on a tagged enum with no `default` that omits a member prints a warning `zc: line N: warning: this switch doesn't cover M1, M2` (not an error; compile continues). ``` @@ -1028,7 +1028,7 @@ rt.put_n(*heap(15).drop(fn() { rt.put_s(" bye "); rt.put_n(*this); })); // 15 let guard = drop(heap(0), fn() { unlock(); }); // unlocks at block end ``` 6.15.2 a value dies, and its drop runs, where 6.5 frees it: at the end of its owner's block or an early exit, at `rt.free`, when its owner is reassigned (6.8.5), or with its container, whose drop runs before its children's. a new value assigned to the owner can have its own drop set on the next statement, like a declaration's. a value with a drop that nothing holds (a temporary) dies at the end of its full statement, with hint: value with drop is never bound; it drops at the end of this statement. an owned local with a drop is not "never used" (6.5.2): holding it is its use. -6.15.3 the collector runs the drop of an unreachable value after its sweep, never during it, and at most once: the value is kept until a later collection finds it unreachable again. a drop that stores `this` somewhere reachable keeps the value alive, and its drop does not run again. among unreachable values, one reached by another waits until that one has dropped (parent first); values that reach each other (a cycle, or a drop that captures its own value) have nothing outside to drop first, so one of them drops per collection, in an unspecified order, each once. there is no collection at exit, so collector-owned values are not dropped then. a freed block forgets its drop, so a block reused at the same address has none. +6.15.3 the collector runs the drop of an unreachable value after its sweep, never during it, and at most once: the value is kept until a later collection finds it unreachable again. a drop that stores `this` somewhere reachable keeps the value alive, and its drop does not run again. among unreachable values, one reached by another waits until that one has dropped (parent first); values that reach each other (a cycle, or a drop that captures its own value) have nothing outside to drop first, so they all drop in the same collection, in an unspecified order, each once. there is no collection at exit, so collector-owned values are not dropped then. a freed block forgets its drop, so a block reused at the same address has none. 6.15.4 copying a value with a drop into an actor message is error: x: has drop and can't be copied into a message. `spawn` copies the whole vm into the worker process, so both processes hold an owned value that is live at the call and both run its drop: hint: x: has drop and is live at spawn; the worker's copy drops it too. the hint is not given for structs declared with `actor`: one that declares no `drop` gets one that waits for the replies its receiver is owed, closes the endpoint, and does nothing in the worker (docs/ACTOR.md). 6.15.5 nothing moves out of a value with a drop, whose drop may need what it holds. a field or element of it is a borrowed projection and can't be given away (error: a borrowed projection cannot transfer ownership; transfer its owner), and `pop` on an array that has a drop, or on an array field of a value with a drop, is error: a: can't move out of a value with drop. there is no `take`. borrowing a child is fine, and an array without a drop hands out its elements with `pop` as before, drops and all. @@ -1168,11 +1168,11 @@ each item gives a minimal program, the expected behavior (per this spec) and the 8. not a bug: a global initializer that names another global uses the other global's address (5.3.2, 3.14.2): `int a = 5; int b = a + 1;` gives `b` the value `&a + 1`. 9. fixed (5.12.7): `include std;` inside a function body gave `return outside a function`; it is now error: include is top level only. 10. a syntax error hides errors in the statements skipped by recovery: `int a = ; int b = 3 +;` reports only the first error (8.2). by design, but easy to miss. -11. fixed (1.2.2): an unterminated `/* ...` comment was accepted silently; it is error: unterminated comment. (an unterminated string literal is still only reported as a later syntax error.) +11. fixed (1.2.2): an unterminated `/* ...` comment was accepted silently; it is error: unterminated comment. an unterminated string literal is error: unterminated string literal (1.4.4), fixed too. 12. settled (3.2.3): `x as int * y` parses `int*` as the type. this is the rule (the type is read greedily, as zc0 does too), so the program is a syntax error. 13. fixed (5.2.1): a prototype's parameters may be unnamed (`int f(int, int*);`). a lone type name counts as a type only in a prototype; with a body it is still the parameter's name, and an unnamed typed parameter there is `a parameter needs a name`. 14. fixed (3.13.5): `len(s)` on a `char*` used to return word 0 of the string when `typecheck` was off; it is now a compile error. -15. duplicate `case` labels are not diagnosed (4.7.2). +15. fixed (4.7.2): duplicate `case` labels were not diagnosed; they are error: duplicate case label. 16. fixed (3.14.2): a local `const` could not be used in a constant expression (b.1.7). 17. fixed: a bad float or character literal in a function's first statement was not reported (1.4). 18. fixed: `u*d` with a visible variable `u` (local or global) was read as a recast; it is a product (1.6.3). diff --git a/docs/design/destructors.md b/docs/design/destructors.md index c828cbd..189c9c6 100644 --- a/docs/design/destructors.md +++ b/docs/design/destructors.md @@ -179,5 +179,5 @@ untyped `rt.new(n)` blocks take a drop like any other value, so there is no sepa - `gc_drops`: drops of marked values are marked (to a fixpoint); each unmarked value whose drop hasn't run gets `F_FINAL` and is kept with what it reaches; a candidate reached by another candidate waits for a later collection, so parents drop before children. - o3: `rt.o3_enter`/`rt.o3_leave` around `o3 { }` blocks and calls from normal code into o3 functions; `gc` inside sets `gc_pending`, the outermost leave runs it. - compiler: `heap(e)`, the three drop forms (a closure there gets `this`), the struct `drop` method as type drop (`set_tdrop` after `new(T)` and `heap`), set-once and set-early on locals, temporaries held in a hidden slot until the statement ends with a hint, the actor `send` check, the o3 errors. -- since: reassigning an `m` owner frees the old value with a hint (6.8.5; `reassign_free` in analyzer.zc, which frees without freevar's clearing store, since a captured local's slot holds its cell); `pop` on an array with a drop, or an array field of one, is refused (6.15.5; fields were already borrowed projections); the spawn hint on `x.spawn()` of a mailbox struct (`spawn_hint`); memreport's `(drop)` marker; `break`/`continue` out of an `o3 { }` block call `o3_leave` for each block they leave (`lpo3`, the depth each loop began at); an o3 receiver method called from normal code enters and leaves the region (`methcall`); a candidate that reaches itself in `gc_drops` (`F_CYCLE`, a cycle or a drop capturing its value) drops now, one per collection. +- since: reassigning an `m` owner frees the old value with a hint (6.8.5; `reassign_free` in analyzer.zc, which frees without freevar's clearing store, since a captured local's slot holds its cell); `pop` on an array with a drop, or an array field of one, is refused (6.15.5; fields were already borrowed projections); the spawn hint on `x.spawn()` of a mailbox struct (`spawn_hint`); memreport's `(drop)` marker; `break`/`continue` out of an `o3 { }` block call `o3_leave` for each block they leave (`lpo3`, the depth each loop began at); an o3 receiver method called from normal code enters and leaves the region (`methcall`); a candidate that reaches itself in `gc_drops` (`F_CYCLE`, a cycle or a drop capturing its value) drops now, one per sweep; `gc` sweeps again while a cycle member dropped, so the whole cycle drops in one collection. - first users: `std.File` (`Host.open` returns a `File*.u` whose drop closes the fd; `writefile` is built on it), and every declared actor struct without its own `drop` gets a generated one that waits for the replies its receiver is owed and closes the endpoint (the generated handler reaches the struct through `endpoint.state` rather than capturing `this`, since a captured value is shared and a shared value's free is the collector's; the generated drop returns at once in a worker, whose snapshot owns nothing). the spawn hint skips mailbox structs. diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc index 712eff8..95415a6 100644 --- a/src/compiler/lex.zc +++ b/src/compiler/lex.zc @@ -533,8 +533,9 @@ int lex1() { while (isidc(ch)) { n = token_char(n, ch); nextch(); } tbuf[n] = 0; if (n == 1 && tbuf[0] == 'b' && ch == 34) { - n = 0; nextch(); + n = 0; dg = line; nextch(); while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } + if (ch < 0) { tline = dg; serr("unterminated string literal"); } // spec 1.4.4 nextch(); tbuf[n] = 0; tk = T_BSTR; tv = intern(tbuf); return 0; @@ -556,8 +557,9 @@ int lex1() { return 0; } if (ch == 34) { - nextch(); + dg = line; nextch(); while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); } + if (ch < 0) { tline = dg; serr("unterminated string literal"); } // spec 1.4.4 nextch(); tbuf[n] = 0; tk = T_STR; tv = intern(tbuf); return 0; diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc index e98cb27..79e5fb8 100644 --- a/src/compiler/stmt.zc +++ b/src/compiler/stmt.zc @@ -566,11 +566,19 @@ int casebody(int s0, int live0) { live = 0; return 0; } +// a case label seen twice in one switch is an error (spec 4.7.2): the second +// arm could never run. the first 256 labels of a switch are checked. +int case_seen(word seen, int n, int v) { + int i = 0; + while (i < n) { if (seen[i] == v) { serr("duplicate case label"); return n; } i++; } + if (n < 256) { seen[n] = v; n++; } + return n; +} // compile a switch statement. 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[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); @@ -609,6 +617,7 @@ int kw_switch() { expect(s_rp); if (nb && nb != pm_nf[gi]) serr("a case binds every field of its member"); // member() with no names matches by tag (spec 4.7.3) v = pm_tag[gi]; + nseen = case_seen(seen, nseen, v); if (v < 63) cov = cov | (1 << v); laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB); bj = fixnew(hole(), bj); op(O_JZ); @@ -633,6 +642,7 @@ int kw_switch() { } else { while (1) { v = cexpr(); + nseen = case_seen(seen, nseen, v); if (tg && v >= 0 && v < 63) cov = cov | (1 << v); laddr(lslot[tmp]); op(O_LD); if (tg) op(O_LD); diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc index 149138b..4f96320 100644 --- a/src/runtime/rt.zc +++ b/src/runtime/rt.zc @@ -428,7 +428,7 @@ const int F_SHARE = 8; // table (dtab), its drop has run (it never runs again), and the collector found it // unreachable and runs its drop once the sweep is over. const int F_HASDROP = 16, F_DROPPED = 32, F_FINAL = 64, F_CYCLE = 128; // F_CYCLE: during gc_drops only, a candidate its own contents reach -int o3_depth, gc_pending; +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; @@ -1456,7 +1456,7 @@ int o3_leave() { // an unreachable value with a drop that hasn't run is kept for one more cycle and // flagged F_FINAL, unless another such value reaches it (its parent drops first and // it waits for a later collection) and it doesn't reach itself (a cycle drops one -// value per collection). nothing runs user code here. +// value per sweep, and gc() repeats the sweep until the cycle is done). nothing runs user code here. int gc_drops() { int i, again = 1; word k; while (again) { @@ -1486,9 +1486,9 @@ int gc_drops() { k = dtab[i * 3]; if (k > 1 && (k[-4] & F_FINAL)) { // reached from another candidate: later. one in a cycle has nothing outside it - // to drop first, so it drops now (it is marked, so kept), and the rest of the - // cycle in later collections, one each, never twice - if ((k[-4] & 7) == 1) mark(k); else if (!(k[-4] & F_CYCLE)) k[-4] = k[-4] & ~F_FINAL; + // to drop first, so it drops now (it is marked, so kept); gc() then sweeps again + // for the rest of the cycle, one each, never twice + if ((k[-4] & 7) == 1) mark(k); else if (!(k[-4] & F_CYCLE)) k[-4] = k[-4] & ~F_FINAL; else gc_cyc = 1; k[-4] = k[-4] & ~F_CYCLE; } i++; @@ -1549,8 +1549,12 @@ unchecked int gc_sweep() { unchecked int gc() { int n; if (o3_depth) { gc_pending = 1; return 0; } + gc_cyc = 0; n = gc_sweep(); if (dcount) gc_finalize(); + // an unreachable cycle with drops finishes in this collection: each sweep drops one + // member (nothing outside the cycle has to go first), and every value drops once + while (gc_cyc) { gc_cyc = 0; n = n + gc_sweep(); if (dcount) gc_finalize(); } return n; } // the number of live heap objects, for tests and leak checks. diff --git a/tests/conformance/1-source/string_unterminated.zc b/tests/conformance/1-source/string_unterminated.zc new file mode 100644 index 0000000..aca63d3 --- /dev/null +++ b/tests/conformance/1-source/string_unterminated.zc @@ -0,0 +1,5 @@ +// expect error: zc: line 4: unterminated string literal +// spec 1.4.4 (was appendix b.2.11): a string with no closing quote is reported where it starts +include rt; +fn main() { rt.put_s("never closed); return 0; } +// zc0: skip diff --git a/tests/conformance/4-statements/switch_duplicate_label_rejected.zc b/tests/conformance/4-statements/switch_duplicate_label_rejected.zc new file mode 100644 index 0000000..54f3b56 --- /dev/null +++ b/tests/conformance/4-statements/switch_duplicate_label_rejected.zc @@ -0,0 +1,6 @@ +// expect error: duplicate case label +// spec 4.7.2: a label seen twice in one switch is an error +include rt; +enum color { RED, GREEN }; +fn main() { int k = 1; switch (k) { case RED, GREEN: k = 2; case 2 - 1: k = 3; } return 0; } +// zc0: skip diff --git a/tests/conformance/4-statements/switch_duplicate_labels_first_wins.zc b/tests/conformance/4-statements/switch_duplicate_labels_first_wins.zc deleted file mode 100644 index 67a0204..0000000 --- a/tests/conformance/4-statements/switch_duplicate_labels_first_wins.zc +++ /dev/null @@ -1,11 +0,0 @@ -// expect: first/ -// spec 4.7.2: duplicate labels are not an error, the first matching case in source order runs -include rt; -void pn(int x) { rt.put_n(x); rt.put_c(10); } -void ps(char* s) { rt.put_s(s); rt.put_c(10); } -fn main() { - int k = 4; - switch (k) { case 4: ps("first"); case 4: ps("second"); } - return 0; -} -// zc0: skip diff --git a/tests/conformance/4-statements/switch_same_label_other_switch.zc b/tests/conformance/4-statements/switch_same_label_other_switch.zc new file mode 100644 index 0000000..bb52a94 --- /dev/null +++ b/tests/conformance/4-statements/switch_same_label_other_switch.zc @@ -0,0 +1,5 @@ +// expect: 2/ +// spec 4.7.2: equal labels in two different switches are fine +include rt; +fn main() { int k = 1; switch (k) { case 1: k = 2; } switch (k) { case 1: k = 9; case 2: rt.put_n(k); } rt.put_c(10); return 0; } +// zc0: skip diff --git a/tests/conformance/6-ownership/drop_gc_cycle.zc b/tests/conformance/6-ownership/drop_gc_cycle.zc index 01db6b8..d31dc6b 100644 --- a/tests/conformance/6-ownership/drop_gc_cycle.zc +++ b/tests/conformance/6-ownership/drop_gc_cycle.zc @@ -1,5 +1,5 @@ -// expect: 0 1 2 2 0/ -// destructors 6.15.3: two unreachable values with drops that reach each other are finalized one per collection (each is in a cycle, so nothing outside it drops first), each exactly once, then freed +// expect: 0 2 2 2 0/ +// destructors 6.15.3: two unreachable values with drops that reach each other are both finalized in one collection (each is in a cycle, so nothing outside it drops first), each exactly once, then freed include rt; int n; word g; diff --git a/tests/conformance/6-ownership/drop_gc_cycle_long.zc b/tests/conformance/6-ownership/drop_gc_cycle_long.zc new file mode 100644 index 0000000..3de3f1e --- /dev/null +++ b/tests/conformance/6-ownership/drop_gc_cycle_long.zc @@ -0,0 +1,24 @@ +// expect: 0 5 15 5 0/ +// destructors 6.15.3: an unreachable cycle of five values with drops is finalized whole by one collection, each value once, then freed +include rt; +int n, sum; +word g; +struct node { node* next; int v; void drop() { n++; sum = sum + this.v; } } +unchecked fn make() { + node* a = rt.new(node); node* p = a; int i; + a.v = 1; + for (i = 2; i <= 5; i++) { node* q = rt.new(node); q.v = i; p.next = q; p = q; } + p.next = a; + g = a; + return 0; +} +fn main() { + make(); + rt.gc(); rt.put_n(n); rt.put_c(32); + g = 0; + rt.gc(); rt.put_n(n); rt.put_c(32); rt.put_n(sum); rt.put_c(32); + rt.gc(); rt.put_n(n); rt.put_c(32); + rt.put_n(rt.dcount); rt.put_c(10); + return 0; +} +// zc0: skip diff --git a/tests/conformance/9-appendix-b/b2_15_duplicate_case.zc b/tests/conformance/9-appendix-b/b2_15_duplicate_case.zc index 63c30ec..e724c63 100644 --- a/tests/conformance/9-appendix-b/b2_15_duplicate_case.zc +++ b/tests/conformance/9-appendix-b/b2_15_duplicate_case.zc @@ -1,5 +1,5 @@ // expect error: duplicate -// spec appendix b.2.15: duplicate case labels are not diagnosed (4.7.2 says they are allowed) +// spec 4.7.2 (was appendix b.2.15): duplicate case labels are an error include rt; void pn(int x) { rt.put_n(x); rt.put_c(10); } fn main() { int k = 1; switch (k) { case 1: pn(1); case 1: pn(2); } return 0; } diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 25bc3b7..685b089 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -3,7 +3,6 @@ # appendix b.2.8 (a global named in an initializer is its address) is documented behavior per 5.3.2 and 3.14.2: no entry 4-statements/o3_outside_function_ordinary_block.zc zy c top-level o3 { } is rejected; spec 4.10.2 says it is an ordinary block 9-appendix-b/b2_10_recovery_hides_errors.zc zy c appendix b.2.10 confirmed; by design per 8.2 -9-appendix-b/b2_15_duplicate_case.zc zy c appendix b.2.15 confirmed; 4.7.2 says duplicates are allowed 2-types/conv_argument_and_return.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) 2-types/conv_assignment_to_float.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) 2-types/conv_f32_rounds.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a) -- cgit v1.2.3