diff --git a/compiler/bin/main.ml b/compiler/bin/main.ml index 8b60a81..0ecd653 100644 --- a/compiler/bin/main.ml +++ b/compiler/bin/main.ml @@ -244,10 +244,12 @@ let merge_symbols (syms_list : Woc_lib.Types.symbols list) : Woc_lib.Types.symbo typedefs = SM.union keep_first acc.typedefs s.typedefs; unions = SM.union keep_first acc.unions s.unions; modules = acc.modules @ s.modules; + traced = Woc_lib.Types.StringSet.union acc.traced s.traced; }) Woc_lib.Types.{ classes = SM.empty; interfaces = SM.empty; free_fns = SM.empty; typedefs = SM.empty; unions = SM.empty; modules = []; + traced = Woc_lib.Types.StringSet.empty; } syms_list @@ -358,6 +360,14 @@ let typecheck_all (collector : Woc_lib.Diag.Collector.t) ~(root : string) (* haxe-parity Task 5: the predeclared `Error` record joins the merged table only — see Types.with_builtin_records for why not per-file. *) let syms = Woc_lib.Types.with_builtin_records (merge_symbols (List.map snd per_file_syms)) in + (* iteration 7b Phase 2: classify GC-ness once (structural SCC over the class + graph, union'd with surviving @gc annotations) and inject the traced set + into the merged table AND every module table, so Types.is_gc_class answers + from inference everywhere (field kinds, owner exemptions, the class flag). *) + let traced = Woc_lib.Gcinfer.traced_names (Woc_lib.Gcinfer.classify syms) in + let syms = { syms with Woc_lib.Types.traced } in + Hashtbl.fold (fun k v acc -> (k, { v with Woc_lib.Types.traced }) :: acc) module_syms [] + |> List.iter (fun (k, v) -> Hashtbl.replace module_syms k v); (* `~file_syms` (hotfix, multi-file double-report): `per_file_syms` and `parsed` are both `List.map`s over the same original file list, in the same order, so pairing them positionally is exact -- each diff --git a/compiler/src/emit.ml b/compiler/src/emit.ml index 5dcf173..c4c27f5 100644 --- a/compiler/src/emit.ml +++ b/compiler/src/emit.ml @@ -3255,7 +3255,7 @@ and call_window (p : pctx) (f : fstate) (v : views) (e : Ast.expr) ~(recv : Ast. | Some u -> u.Types.u_has_payload | None -> ( match Types.StringMap.find_opt n p.p_syms.Types.classes with - | Some (ci : Types.class_info) -> not ci.Types.is_gc + | Some (ci : Types.class_info) -> not (Types.is_gc_class p.p_syms ci.Types.name) | None -> false)) | _ -> false) | None -> false) @@ -4454,7 +4454,7 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string) cols) cfg.Ast.indexes | None -> ()); - { cr_name = c.name; cr_gc = c.is_gc; + { cr_name = c.name; cr_gc = Types.is_gc_class syms c.name; cr_fields = Array.of_list (List.filter_map diff --git a/compiler/src/gcinfer.ml b/compiler/src/gcinfer.ml index c30119f..8b8e0d1 100644 --- a/compiler/src/gcinfer.ml +++ b/compiler/src/gcinfer.ml @@ -75,6 +75,11 @@ type result = { order : string list; (* all class names, sorted — deterministic dump order *) } +(* the traced class names as a set, for injection into `Types.symbols.traced` + (what `Types.is_gc_class` consults). *) +let traced_names (r : result) : Types.StringSet.t = + SMap.fold (fun k _ acc -> Types.StringSet.add k acc) r.traced Types.StringSet.empty + let classify (syms : Types.symbols) : result = let classes = syms.Types.classes in let nodes = diff --git a/compiler/src/types.ml b/compiler/src/types.ml index 4b904ec..0c4f421 100644 --- a/compiler/src/types.ml +++ b/compiler/src/types.ml @@ -125,6 +125,9 @@ and symbols = { typedefs : typedef_info StringMap.t; unions : union_info StringMap.t; (* haxe-parity Task 4 *) modules : string list; + traced : StringSet.t; + (* iteration 7b: classes inferred `gc` (Gcinfer). Injected after + declaration collection; is_gc_class reads it. Empty until then. *) } (* Variant lookup by bare name, across every union in scope — how a @@ -327,11 +330,13 @@ and has_recursive_structure_type (ty : Ast.field_ty) (cls_name : string) : bool let has_unique_field (cls : class_info) : bool = List.exists (fun (_, _, _, anns) -> List.mem "unique" anns) cls.fields +(* iteration 7b: GC-ness is inference-first. A class is traced if the inference + pass put it in `syms.traced` (structural cycle, or a Phase-2 demand + promotion), OR — as a temporary bridge until demand promotion lands — it + still carries the `@gc` annotation for the acyclic-but-aliased case. *) let is_gc_class (syms : symbols) name = - try - let cls = StringMap.find name syms.classes in - cls.is_gc - with Not_found -> false + StringSet.mem name syms.traced + || (try (StringMap.find name syms.classes).is_gc with Not_found -> false) let gc_suggestion_code = Diag.warning_prefix ^ "201" (* WO-W201 *) @@ -604,7 +609,8 @@ let collect_declarations ~file (prog : program) (collector : Diag.Collector.t) : ) prog.decls; { classes = !classes; interfaces = !interfaces; free_fns = !free_fns; - typedefs = !typedefs; unions = !unions; modules = !modules } + typedefs = !typedefs; unions = !unions; modules = !modules; + traced = StringSet.empty } (* ============================================================ Pass 2: Body Typechecking @@ -2126,9 +2132,11 @@ let merge_syms_for_module (syms_list : symbols list) : symbols = typedefs = StringMap.union keep_first acc.typedefs s.typedefs; unions = StringMap.union keep_first acc.unions s.unions; modules = acc.modules @ s.modules; + traced = StringSet.union acc.traced s.traced; }) { classes = StringMap.empty; interfaces = StringMap.empty; free_fns = StringMap.empty; - typedefs = StringMap.empty; unions = StringMap.empty; modules = [] } + typedefs = StringMap.empty; unions = StringMap.empty; modules = []; + traced = StringSet.empty } syms_list (* Generic structural walk over every Ctor/Call site in a program's diff --git a/docs/examples/gc-cycle/README.md b/docs/examples/gc-cycle/README.md index dcee299..5e97499 100644 --- a/docs/examples/gc-cycle/README.md +++ b/docs/examples/gc-cycle/README.md @@ -183,23 +183,31 @@ inferred). The developer writes no memory annotations for either. ## Run status -**This is a target sample for iteration 7b, which is not yet implemented.** It -does **not** build on today's toolchain — and the compile error is precisely -the motivation. `woc --emit docs/examples/gc-cycle` today reports: +Iteration 7b is landing in phases (plan: +[`../../superpowers/plans/2026-08-18-inferred-gc-mark-sweep.md`](../../superpowers/plans/2026-08-18-inferred-gc-mark-sweep.md)). + +**Phase 1 (landed).** The inference pass classifies each class; `woc --dump-gc +docs/examples/gc-cycle` prints: ``` -main.wo: error WO-E301: use of `a` after it was moved - c.next = a; <- `a` moved here +Node gc (cycle Node -> Node) +Segment owned ``` -Under the current model a class instance is `owned` and single-owner, so storing -`b` into `a.next` **moves** it and closing the ring with `c.next = a` re-uses a -moved value. Iteration 7b classifies `Node` as **traced** — and *traced classes -alias freely* (spec §3 rule 5), so the ring becomes legal and the collector, -not ownership, reclaims it. The pieces still to build: the inference pass -(`gcinfer.ml`), `--dump-gc`, the `.wob` opcode-27/28 retirement, the sweep list, -and the incremental collector. Today's runtime still uses RC + a Bacon–Rajan -cycle collector behind an explicit `@gc` annotation (`runtime/src/gc.c`). +**Phase 2a (landed).** `Types.is_gc_class` is now inference-first, so `Node` is +traced with **no annotation** and *traced classes alias freely* — the ring +**compiles** (the old `WO-E301: use of \`a\` after it was moved` at `c.next = a` +is gone), and its bytecode is byte-identical to writing `@gc class Node`. + +**Not yet: the ring runs.** On today's runtime (RC + Bacon–Rajan, `gc.c`) a +**nullable single-reference gc field** (`next: ?Node`) store/read is +unimplemented — even a one-hop `a.next = b; print(a.next.label)` traps +`null receiver` (the existing gc corpus only exercises `multi` gcref fields, +which do work). Running the ring, and reclaiming it, is **Phase 3**: the +incremental mark-sweep collector + full gcref field paths, the `.wob` +opcode-27/28 retirement, and the sweep list. Remaining front-end work +(**Phase 2b**): demand-promotion for the acyclic-but-aliased case, then +`@gc`-in-source becomes an error and the annotation bridge is removed. When 7b lands, the acceptance is: - `woc --dump-gc docs/examples/gc-cycle` classifies `Node gc (cycle …)` /