From 164b30309e6337b95ec2baacacd0f101fafd3d97 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 9 Jun 2026 01:09:05 +0300 Subject: [PATCH] v2: fix arm64 self-hosting (#27396) --- vlib/v2/ast/flat.v | 415 ++++--- vlib/v2/ast/flat_append_flat_test.v | 43 + vlib/v2/builder/builder.v | 11 +- vlib/v2/gen/arm64/linker.v | 4 +- .../gen/arm64/tests/flat_ast_import_edges.v | 29 + .../v2/gen/arm64/tests/mut_array_param_push.v | 18 + vlib/v2/gen/cleanc/cleanc.v | 222 +++- vlib/v2/gen/cleanc/cleanc_test.v | 3 +- vlib/v2/gen/cleanc/consts_and_globals.v | 16 +- vlib/v2/gen/cleanc/expr.v | 8 +- vlib/v2/gen/cleanc/flag_enum_codegen_test.v | 6 +- vlib/v2/gen/cleanc/fn.v | 217 +++- vlib/v2/gen/cleanc/interface.v | 5 +- .../gen/cleanc/result_option_codegen_test.v | 3 +- vlib/v2/gen/cleanc/struct.v | 1066 ++++++++++++++++- vlib/v2/gen/cleanc/types.v | 111 +- vlib/v2/markused/markused.v | 17 + vlib/v2/ssa/build_call_from_flat_test.v | 120 ++ vlib/v2/ssa/build_fn_bodies_from_flat_test.v | 10 +- vlib/v2/ssa/builder.v | 103 +- vlib/v2/transformer/flat_write.v | 338 +++++- vlib/v2/transformer/monomorphize.v | 47 +- vlib/v2/transformer/transformer.v | 27 +- 23 files changed, 2445 insertions(+), 394 deletions(-) create mode 100644 vlib/v2/gen/arm64/tests/flat_ast_import_edges.v create mode 100644 vlib/v2/gen/arm64/tests/mut_array_param_push.v diff --git a/vlib/v2/ast/flat.v b/vlib/v2/ast/flat.v index 51d001096..44ceea839 100644 --- a/vlib/v2/ast/flat.v +++ b/vlib/v2/ast/flat.v @@ -395,9 +395,9 @@ pub fn (mut b FlatBuilder) append_file(file File) FlatNodeId { // bit-equal to `add_file`'s output. pub fn (mut b FlatBuilder) append_file_with_stmt_ids(file File, stmt_ids []FlatNodeId) FlatNodeId { mut edges := []FlatEdge{} - b.push_edge(mut edges, b.make_list_attribute(file.attributes)) - b.push_edge(mut edges, b.make_list_imports(file.imports)) - b.push_edge(mut edges, b.make_list_from_stmt_ids(stmt_ids)) + edges = b.push_edge(edges, b.make_list_attribute(file.attributes)) + edges = b.push_edge(edges, b.make_list_imports(file.imports)) + edges = b.push_edge(edges, b.make_list_from_stmt_ids(stmt_ids)) file_id := b.emit(.file, token.Pos{}, b.intern(file.name), b.intern(file.mod), 0, 0, edges) b.flat.files << FlatFile{ file_id: file_id @@ -472,6 +472,42 @@ pub fn (mut b FlatBuilder) append_flat(src &FlatAst) int { return node_offset } +// copy_subtree_from copies the subtree rooted at `root` from `src` into this +// builder, re-interning strings and rebuilding child edges recursively. +pub fn (mut b FlatBuilder) copy_subtree_from(src &FlatAst, root FlatNodeId) FlatNodeId { + if root < 0 || root >= src.nodes.len { + return invalid_flat_node_id + } + n := src.nodes[root] + mut edges := []FlatEdge{cap: n.edge_count} + for i in 0 .. n.edge_count { + child := src.child_at(root, i) + copied_child := if child >= 0 { + b.copy_subtree_from(src, child) + } else { + child + } + edges << FlatEdge{ + child_id: copied_child + } + } + name_id := b.copy_string_id_from(src, n.name_id) + extra := if n.extra >= 0 + && (n.kind == .file || n.kind == .stmt_directive || n.kind == .stmt_import) { + b.copy_string_id_from(src, n.extra) + } else { + n.extra + } + return b.emit(n.kind, n.pos, name_id, extra, n.aux, n.flags, edges) +} + +fn (mut b FlatBuilder) copy_string_id_from(src &FlatAst, id int) int { + if id < 0 || id >= src.strings.len { + return id + } + return b.intern(src.strings[id]) +} + // emit_stmt is the public wrapper around the legacy stmt-to-flat conversion. // Future sessions of the transformer-writes-flat port reach for this when a // stmt variant still falls back to legacy emission; the long-term goal is for @@ -528,9 +564,9 @@ pub fn (mut b FlatBuilder) append_file_stmts(file_idx int, extra_stmt_ids []Flat } new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids) mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, old_attrs_list_id) - b.push_edge(mut edges, old_imports_list_id) - b.push_edge(mut edges, new_stmts_list_id) + edges = b.push_edge(edges, old_attrs_list_id) + edges = b.push_edge(edges, old_imports_list_id) + edges = b.push_edge(edges, new_stmts_list_id) new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges) b.flat.files[file_idx] = FlatFile{ file_id: new_file_id @@ -580,9 +616,9 @@ pub fn (mut b FlatBuilder) prepend_file_stmts(file_idx int, prepended_stmt_ids [ } new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids) mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, old_attrs_list_id) - b.push_edge(mut edges, old_imports_list_id) - b.push_edge(mut edges, new_stmts_list_id) + edges = b.push_edge(edges, old_attrs_list_id) + edges = b.push_edge(edges, old_imports_list_id) + edges = b.push_edge(edges, new_stmts_list_id) new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges) b.flat.files[file_idx] = FlatFile{ file_id: new_file_id @@ -638,9 +674,9 @@ pub fn (mut b FlatBuilder) replace_file_stmt(file_idx int, stmt_idx int, new_stm } new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids) mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, old_attrs_list_id) - b.push_edge(mut edges, old_imports_list_id) - b.push_edge(mut edges, new_stmts_list_id) + edges = b.push_edge(edges, old_attrs_list_id) + edges = b.push_edge(edges, old_imports_list_id) + edges = b.push_edge(edges, new_stmts_list_id) name_idx := b.flat.files[file_idx].name_idx mod_idx := b.flat.files[file_idx].mod_idx new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges) @@ -700,10 +736,10 @@ pub fn (mut b FlatBuilder) prepend_to_fn_body(fn_decl_id FlatNodeId, extra_stmt_ } new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids) mut edges := []FlatEdge{cap: 4} - b.push_edge(mut edges, old_receiver_id) - b.push_edge(mut edges, old_typ_id) - b.push_edge(mut edges, old_attrs_id) - b.push_edge(mut edges, new_stmts_list_id) + edges = b.push_edge(edges, old_receiver_id) + edges = b.push_edge(edges, old_typ_id) + edges = b.push_edge(edges, old_attrs_id) + edges = b.push_edge(edges, new_stmts_list_id) return b.emit(.stmt_fn_decl, old_node.pos, old_node.name_id, old_node.extra, old_node.aux, old_node.flags, edges) } @@ -785,10 +821,10 @@ pub fn (mut b FlatBuilder) replace_fn_body_stmts(fn_decl_id FlatNodeId, new_body old_attrs_id := b.flat.child_at(fn_decl_id, 2) new_stmts_list_id := b.make_list_from_stmt_ids(new_body_stmt_ids) mut edges := []FlatEdge{cap: 4} - b.push_edge(mut edges, old_receiver_id) - b.push_edge(mut edges, old_typ_id) - b.push_edge(mut edges, old_attrs_id) - b.push_edge(mut edges, new_stmts_list_id) + edges = b.push_edge(edges, old_receiver_id) + edges = b.push_edge(edges, old_typ_id) + edges = b.push_edge(edges, old_attrs_id) + edges = b.push_edge(edges, new_stmts_list_id) return b.emit(.stmt_fn_decl, old_node.pos, old_node.name_id, old_node.extra, old_node.aux, old_node.flags, edges) } @@ -818,7 +854,7 @@ pub fn (mut b FlatBuilder) emit_aux_list_from_ids(ids []FlatNodeId) FlatNodeId { } mut edges := []FlatEdge{cap: ids.len} for id in ids { - b.push_edge(mut edges, id) + edges = b.push_edge(edges, id) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -829,9 +865,9 @@ pub fn (mut b FlatBuilder) emit_aux_list_from_ids(ids []FlatNodeId) FlatNodeId { // have been emitted directly. Mirrors the add_field_decl encoding exactly. pub fn (mut b FlatBuilder) emit_field_decl_by_ids(field FieldDecl, typ_id FlatNodeId, value_id FlatNodeId, attrs_id FlatNodeId) FlatNodeId { mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, typ_id) - b.push_edge(mut edges, value_id) - b.push_edge(mut edges, attrs_id) + edges = b.push_edge(edges, typ_id) + edges = b.push_edge(edges, value_id) + edges = b.push_edge(edges, attrs_id) return b.emit(.aux_field_decl, token.Pos{}, b.intern(field.name), -1, 0, field_decl_flags(field), edges) } @@ -1404,10 +1440,10 @@ pub fn (mut b FlatBuilder) emit_fn_literal_by_ids(typ_id FlatNodeId, captured_va // resolved_fmt interns into name. pub fn (mut b FlatBuilder) emit_string_inter_by_ids(format StringInterFormat, width int, precision int, expr_id FlatNodeId, format_expr_id FlatNodeId, resolved_fmt string) FlatNodeId { mut edges := []FlatEdge{cap: 4} - b.push_edge(mut edges, expr_id) - b.push_edge(mut edges, format_expr_id) - b.push_edge(mut edges, b.emit_int(width)) - b.push_edge(mut edges, b.emit_int(precision)) + edges = b.push_edge(edges, expr_id) + edges = b.push_edge(edges, format_expr_id) + edges = b.push_edge(edges, b.emit_int(width)) + edges = b.push_edge(edges, b.emit_int(precision)) return b.emit(.aux_string_inter, token.Pos{}, b.intern(resolved_fmt), -1, u16(int(format)), 0, edges) } @@ -1419,8 +1455,8 @@ pub fn (mut b FlatBuilder) emit_string_inter_by_ids(format StringInterFormat, wi // supplied FlatNodeIds via the standard aux_list shape). pub fn (mut b FlatBuilder) emit_string_inter_literal_by_ids(kind StringLiteralKind, values []string, inter_ids []FlatNodeId, pos token.Pos) FlatNodeId { mut edges := []FlatEdge{cap: 2} - b.push_edge(mut edges, b.make_list_strings(values)) - b.push_edge(mut edges, b.emit_aux_list_from_ids(inter_ids)) + edges = b.push_edge(edges, b.make_list_strings(values)) + edges = b.push_edge(edges, b.emit_aux_list_from_ids(inter_ids)) return b.emit(.expr_string_inter, pos, -1, -1, u16(int(kind)), 0, edges) } @@ -1462,7 +1498,7 @@ fn (mut b FlatBuilder) make_list_from_stmt_ids(stmt_ids []FlatNodeId) FlatNodeId } mut edges := []FlatEdge{cap: stmt_ids.len} for id in stmt_ids { - b.push_edge(mut edges, id) + edges = b.push_edge(edges, id) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1506,22 +1542,24 @@ fn (mut b FlatBuilder) emit_simple(kind FlatNodeKind, pos token.Pos, edges []Fla return b.emit(kind, pos, -1, -1, 0, 0, edges) } -fn (mut b FlatBuilder) push_edge(mut edges []FlatEdge, child FlatNodeId) { - edges << FlatEdge{ +fn (mut b FlatBuilder) push_edge(edges []FlatEdge, child FlatNodeId) []FlatEdge { + mut updated := edges.clone() + updated << FlatEdge{ child_id: child } + return updated } -fn (mut b FlatBuilder) push_expr(mut edges []FlatEdge, expr Expr) { - b.push_edge(mut edges, b.add_expr(expr)) +fn (mut b FlatBuilder) push_expr(edges []FlatEdge, expr Expr) []FlatEdge { + return b.push_edge(edges, b.add_expr(expr)) } -fn (mut b FlatBuilder) push_stmt(mut edges []FlatEdge, stmt Stmt) { - b.push_edge(mut edges, b.add_stmt(stmt)) +fn (mut b FlatBuilder) push_stmt(edges []FlatEdge, stmt Stmt) []FlatEdge { + return b.push_edge(edges, b.add_stmt(stmt)) } -fn (mut b FlatBuilder) push_type(mut edges []FlatEdge, typ Type) { - b.push_edge(mut edges, b.add_type(typ)) +fn (mut b FlatBuilder) push_type(edges []FlatEdge, typ Type) []FlatEdge { + return b.push_edge(edges, b.add_type(typ)) } // get_empty_list returns a shared aux_list node id used as a canonical @@ -1541,7 +1579,7 @@ fn (mut b FlatBuilder) make_list_expr(items []Expr) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_expr(mut edges, it) + edges = b.push_expr(edges, it) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1552,7 +1590,7 @@ fn (mut b FlatBuilder) make_list_stmt(items []Stmt) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_stmt(mut edges, it) + edges = b.push_stmt(edges, it) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1563,7 +1601,7 @@ fn (mut b FlatBuilder) make_list_attribute(items []Attribute) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_attribute(it)) + edges = b.push_edge(edges, b.add_attribute(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1574,7 +1612,7 @@ fn (mut b FlatBuilder) make_list_field_init(items []FieldInit) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_field_init(it)) + edges = b.push_edge(edges, b.add_field_init(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1585,7 +1623,7 @@ fn (mut b FlatBuilder) make_list_field_decl(items []FieldDecl) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_field_decl(it)) + edges = b.push_edge(edges, b.add_field_decl(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1596,7 +1634,7 @@ fn (mut b FlatBuilder) make_list_parameter(items []Parameter) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_parameter(it)) + edges = b.push_edge(edges, b.add_parameter(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1607,7 +1645,7 @@ fn (mut b FlatBuilder) make_list_match_branch(items []MatchBranch) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_match_branch(it)) + edges = b.push_edge(edges, b.add_match_branch(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1618,7 +1656,7 @@ fn (mut b FlatBuilder) make_list_string_inter(items []StringInter) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_string_inter(it)) + edges = b.push_edge(edges, b.add_string_inter(it)) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1630,7 +1668,7 @@ fn (mut b FlatBuilder) make_list_strings(items []string) FlatNodeId { mut edges := []FlatEdge{cap: items.len} for s in items { id := b.emit(.aux_string, token.Pos{}, b.intern(s), -1, 0, 0, []FlatEdge{}) - b.push_edge(mut edges, id) + edges = b.push_edge(edges, id) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1645,7 +1683,7 @@ fn (mut b FlatBuilder) make_list_imports(items []ImportStmt) FlatNodeId { } mut edges := []FlatEdge{cap: items.len} for it in items { - b.push_edge(mut edges, b.add_stmt(Stmt(it))) + edges = b.push_edge(edges, b.add_stmt(Stmt(it))) } return b.emit_simple(.aux_list, token.Pos{}, edges) } @@ -1653,9 +1691,9 @@ fn (mut b FlatBuilder) make_list_imports(items []ImportStmt) FlatNodeId { // add_file emits the file root and returns its FlatNodeId. fn (mut b FlatBuilder) add_file(file File) FlatNodeId { mut edges := []FlatEdge{} - b.push_edge(mut edges, b.make_list_attribute(file.attributes)) - b.push_edge(mut edges, b.make_list_imports(file.imports)) - b.push_edge(mut edges, b.make_list_stmt(file.stmts)) + edges = b.push_edge(edges, b.make_list_attribute(file.attributes)) + edges = b.push_edge(edges, b.make_list_imports(file.imports)) + edges = b.push_edge(edges, b.make_list_stmt(file.stmts)) return b.emit(.file, token.Pos{}, b.intern(file.name), b.intern(file.mod), 0, 0, edges) } @@ -1673,24 +1711,24 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { } AssertStmt { mut edges := []FlatEdge{} - b.push_expr(mut edges, stmt.expr) - b.push_expr(mut edges, stmt.extra) + edges = b.push_expr(edges, stmt.expr) + edges = b.push_expr(edges, stmt.extra) return b.emit_simple(.stmt_assert, token.Pos{}, edges) } AssignStmt { mut edges := []FlatEdge{cap: stmt.lhs.len + stmt.rhs.len} for e in stmt.lhs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } for e in stmt.rhs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } return b.emit(.stmt_assign, stmt.pos, -1, stmt.lhs.len, u16(int(stmt.op)), 0, edges) } BlockStmt { mut edges := []FlatEdge{cap: stmt.stmts.len} for s in stmt.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.stmt_block, token.Pos{}, edges) } @@ -1719,7 +1757,7 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { } mut edges := []FlatEdge{cap: stmt.stmts.len} for s in stmt.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple_with_flags(.stmt_defer, token.Pos{}, flags, edges) } @@ -1729,7 +1767,7 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { if stmt.ct_cond.len > 0 { id := b.emit(.aux_string, token.Pos{}, b.intern(stmt.ct_cond), -1, 0, 0, []FlatEdge{}) - b.push_edge(mut edges, id) + edges = b.push_edge(edges, id) } return b.emit(.stmt_directive, token.Pos{}, b.intern(stmt.name), b.intern(stmt.value), 0, 0, edges) @@ -1749,9 +1787,9 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { flags |= flag_is_public } mut edges := []FlatEdge{} - b.push_expr(mut edges, stmt.as_type) - b.push_edge(mut edges, b.make_list_attribute(stmt.attributes)) - b.push_edge(mut edges, b.make_list_field_decl(stmt.fields)) + edges = b.push_expr(edges, stmt.as_type) + edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes)) + edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields)) return b.emit_simple_with_flags_name(.stmt_enum_decl, token.Pos{}, flags, b.intern(stmt.name), edges) } @@ -1779,7 +1817,7 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { } mut edges := []FlatEdge{} if stmt.is_method { - b.push_edge(mut edges, b.add_parameter(stmt.receiver)) + edges = b.push_edge(edges, b.add_parameter(stmt.receiver)) } else { // s251: a non-method FnDecl keeps the parser's zero `ast.Parameter{}` // receiver, whose `typ` is a zero-valued Expr (invalid sum-type tag, @@ -1788,30 +1826,30 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { // first arm (ArrayInitExpr) and derefs the null payload. The receiver // edge is only read for methods, so emit a clean empty receiver // (typ = empty_expr, a valid EmptyExpr) for non-methods. - b.push_edge(mut edges, b.add_parameter(Parameter{ + edges = b.push_edge(edges, b.add_parameter(Parameter{ typ: empty_expr })) } - b.push_edge(mut edges, b.add_type(Type(stmt.typ))) - b.push_edge(mut edges, b.make_list_attribute(stmt.attributes)) - b.push_edge(mut edges, b.make_list_stmt(stmt.stmts)) + edges = b.push_edge(edges, b.add_type(Type(stmt.typ))) + edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes)) + edges = b.push_edge(edges, b.make_list_stmt(stmt.stmts)) return b.emit(.stmt_fn_decl, stmt.pos, b.intern(stmt.name), -1, u16(int(stmt.language)), flags, edges) } ForInStmt { mut edges := []FlatEdge{cap: 3} - b.push_expr(mut edges, stmt.key) - b.push_expr(mut edges, stmt.value) - b.push_expr(mut edges, stmt.expr) + edges = b.push_expr(edges, stmt.key) + edges = b.push_expr(edges, stmt.value) + edges = b.push_expr(edges, stmt.expr) return b.emit_simple(.stmt_for_in, token.Pos{}, edges) } ForStmt { mut edges := []FlatEdge{cap: 3 + stmt.stmts.len} - b.push_stmt(mut edges, stmt.init) - b.push_expr(mut edges, stmt.cond) - b.push_stmt(mut edges, stmt.post) + edges = b.push_stmt(edges, stmt.init) + edges = b.push_expr(edges, stmt.cond) + edges = b.push_stmt(edges, stmt.post) for s in stmt.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.stmt_for, token.Pos{}, edges) } @@ -1821,8 +1859,8 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { flags |= flag_is_public } mut edges := []FlatEdge{} - b.push_edge(mut edges, b.make_list_attribute(stmt.attributes)) - b.push_edge(mut edges, b.make_list_field_decl(stmt.fields)) + edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes)) + edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields)) return b.emit_simple_with_flags(.stmt_global_decl, token.Pos{}, flags, edges) } ImportStmt { @@ -1832,7 +1870,7 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { } mut edges := []FlatEdge{cap: stmt.symbols.len} for sym in stmt.symbols { - b.push_expr(mut edges, sym) + edges = b.push_expr(edges, sym) } return b.emit(.stmt_import, token.Pos{}, b.intern(stmt.name), b.intern(stmt.alias), 0, flags, edges) @@ -1843,10 +1881,10 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { flags |= flag_is_public } mut edges := []FlatEdge{cap: 4} - b.push_edge(mut edges, b.make_list_attribute(stmt.attributes)) - b.push_edge(mut edges, b.make_list_expr(stmt.generic_params)) - b.push_edge(mut edges, b.make_list_expr(stmt.embedded)) - b.push_edge(mut edges, b.make_list_field_decl(stmt.fields)) + edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes)) + edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params)) + edges = b.push_edge(edges, b.make_list_expr(stmt.embedded)) + edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields)) return b.emit_simple_with_flags_name(.stmt_interface_decl, token.Pos{}, flags, b.intern(stmt.name), edges) } @@ -1863,7 +1901,7 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { ReturnStmt { mut edges := []FlatEdge{cap: stmt.exprs.len} for e in stmt.exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } return b.emit_simple(.stmt_return, token.Pos{}, edges) } @@ -1876,11 +1914,11 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { flags |= flag_is_union } mut edges := []FlatEdge{cap: 5} - b.push_edge(mut edges, b.make_list_attribute(stmt.attributes)) - b.push_edge(mut edges, b.make_list_expr(stmt.implements)) - b.push_edge(mut edges, b.make_list_expr(stmt.embedded)) - b.push_edge(mut edges, b.make_list_expr(stmt.generic_params)) - b.push_edge(mut edges, b.make_list_field_decl(stmt.fields)) + edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes)) + edges = b.push_edge(edges, b.make_list_expr(stmt.implements)) + edges = b.push_edge(edges, b.make_list_expr(stmt.embedded)) + edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params)) + edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields)) return b.emit(.stmt_struct_decl, stmt.pos, b.intern(stmt.name), -1, u16(int(stmt.language)), flags, edges) } @@ -1890,10 +1928,10 @@ fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId { flags |= flag_is_public } mut edges := []FlatEdge{cap: 4} - b.push_expr(mut edges, stmt.base_type) - b.push_edge(mut edges, b.make_list_attribute([]Attribute{})) - b.push_edge(mut edges, b.make_list_expr(stmt.generic_params)) - b.push_edge(mut edges, b.make_list_expr(stmt.variants)) + edges = b.push_expr(edges, stmt.base_type) + edges = b.push_edge(edges, b.make_list_attribute([]Attribute{})) + edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params)) + edges = b.push_edge(edges, b.make_list_expr(stmt.variants)) return b.emit(.stmt_type_decl, token.Pos{}, b.intern(stmt.name), -1, u16(int(stmt.language)), flags, edges) } @@ -1915,6 +1953,12 @@ fn (mut b FlatBuilder) emit_simple_with_flags_name(kind FlatNodeKind, pos token. } fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { + if expr_is_zero_value(expr) { + if b.empty_expr_id == invalid_flat_node_id { + b.empty_expr_id = b.emit(.expr_empty, token.Pos{}, -1, 0, 0, 0, []FlatEdge{}) + } + return b.empty_expr_id + } match expr { Type { return b.add_type(expr) @@ -1928,28 +1972,28 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { match expr { ArrayInitExpr { mut edges := []FlatEdge{cap: 5 + expr.exprs.len} - b.push_expr(mut edges, expr.typ) - b.push_expr(mut edges, expr.init) - b.push_expr(mut edges, expr.cap) - b.push_expr(mut edges, expr.len) - b.push_expr(mut edges, expr.update_expr) + edges = b.push_expr(edges, expr.typ) + edges = b.push_expr(edges, expr.init) + edges = b.push_expr(edges, expr.cap) + edges = b.push_expr(edges, expr.len) + edges = b.push_expr(edges, expr.update_expr) for e in expr.exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } return b.emit_simple(.expr_array_init, expr.pos, edges) } AsCastExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.expr) - b.push_expr(mut edges, expr.typ) + edges = b.push_expr(edges, expr.expr) + edges = b.push_expr(edges, expr.typ) return b.emit_simple(.expr_as_cast, expr.pos, edges) } AssocExpr { mut edges := []FlatEdge{cap: 2 + expr.fields.len} - b.push_expr(mut edges, expr.typ) - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.typ) + edges = b.push_expr(edges, expr.expr) for f in expr.fields { - b.push_edge(mut edges, b.add_field_init(f)) + edges = b.push_edge(edges, b.add_field_init(f)) } return b.emit_simple(.expr_assoc, expr.pos, edges) } @@ -1959,22 +2003,22 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } CallExpr { mut edges := []FlatEdge{cap: 1 + expr.args.len} - b.push_expr(mut edges, expr.lhs) + edges = b.push_expr(edges, expr.lhs) for a in expr.args { - b.push_expr(mut edges, a) + edges = b.push_expr(edges, a) } return b.emit_simple(.expr_call, expr.pos, edges) } CallOrCastExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.lhs) - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.lhs) + edges = b.push_expr(edges, expr.expr) return b.emit_simple(.expr_call_or_cast, expr.pos, edges) } CastExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.typ) - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.typ) + edges = b.push_expr(edges, expr.expr) return b.emit_simple(.expr_cast, expr.pos, edges) } ComptimeExpr { @@ -1997,12 +2041,12 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } FnLiteral { mut edges := []FlatEdge{} - b.push_edge(mut edges, b.add_type(Type(expr.typ))) + edges = b.push_edge(edges, b.add_type(Type(expr.typ))) for cv in expr.captured_vars { - b.push_expr(mut edges, cv) + edges = b.push_expr(edges, cv) } for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } // extra stores captured_vars.len so the boundary between captured // vars and stmts is recoverable. @@ -2010,15 +2054,15 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } GenericArgOrIndexExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.lhs) - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.lhs) + edges = b.push_expr(edges, expr.expr) return b.emit_simple(.expr_generic_arg_or_index, expr.pos, edges) } GenericArgs { mut edges := []FlatEdge{cap: 1 + expr.args.len} - b.push_expr(mut edges, expr.lhs) + edges = b.push_expr(edges, expr.lhs) for a in expr.args { - b.push_expr(mut edges, a) + edges = b.push_expr(edges, a) } return b.emit_simple(.expr_generic_args, expr.pos, edges) } @@ -2027,10 +2071,10 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } IfExpr { mut edges := []FlatEdge{cap: 2 + expr.stmts.len} - b.push_expr(mut edges, expr.cond) - b.push_expr(mut edges, expr.else_expr) + edges = b.push_expr(edges, expr.cond) + edges = b.push_expr(edges, expr.else_expr) for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.expr_if, expr.pos, edges) } @@ -2047,21 +2091,21 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { flags |= flag_is_gated } mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.lhs) - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.lhs) + edges = b.push_expr(edges, expr.expr) return b.emit(.expr_index, expr.pos, -1, -1, 0, flags, edges) } InfixExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.lhs) - b.push_expr(mut edges, expr.rhs) + edges = b.push_expr(edges, expr.lhs) + edges = b.push_expr(edges, expr.rhs) return b.emit(.expr_infix, expr.pos, -1, -1, u16(int(expr.op)), 0, edges) } InitExpr { mut edges := []FlatEdge{cap: 1 + expr.fields.len} - b.push_expr(mut edges, expr.typ) + edges = b.push_expr(edges, expr.typ) for f in expr.fields { - b.push_edge(mut edges, b.add_field_init(f)) + edges = b.push_edge(edges, b.add_field_init(f)) } return b.emit_simple(.expr_init, expr.pos, edges) } @@ -2071,15 +2115,15 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { KeywordOperator { mut edges := []FlatEdge{cap: expr.exprs.len} for e in expr.exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } return b.emit(.expr_keyword_operator, expr.pos, -1, -1, u16(int(expr.op)), 0, edges) } LambdaExpr { mut edges := []FlatEdge{cap: 1 + expr.args.len} - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.expr) for a in expr.args { - b.push_expr(mut edges, Expr(a)) + edges = b.push_expr(edges, Expr(a)) } return b.emit_simple(.expr_lambda, expr.pos, edges) } @@ -2089,13 +2133,13 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { LockExpr { mut edges := []FlatEdge{cap: expr.lock_exprs.len + expr.rlock_exprs.len + expr.stmts.len} for e in expr.lock_exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } for e in expr.rlock_exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } // Pack (lock.len, rlock.len) into extra; stmts.len = edge_count - lock - rlock. lock_len := u32(expr.lock_exprs.len) & 0xFFFF @@ -2105,33 +2149,33 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } MapInitExpr { mut edges := []FlatEdge{cap: 1 + expr.keys.len + expr.vals.len} - b.push_expr(mut edges, expr.typ) + edges = b.push_expr(edges, expr.typ) for k in expr.keys { - b.push_expr(mut edges, k) + edges = b.push_expr(edges, k) } for v in expr.vals { - b.push_expr(mut edges, v) + edges = b.push_expr(edges, v) } return b.emit(.expr_map_init, expr.pos, -1, expr.keys.len, 0, 0, edges) } MatchExpr { mut edges := []FlatEdge{cap: 1 + expr.branches.len} - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.expr) for br in expr.branches { - b.push_edge(mut edges, b.add_match_branch(br)) + edges = b.push_edge(edges, b.add_match_branch(br)) } return b.emit_simple(.expr_match, expr.pos, edges) } ModifierExpr { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.expr) return b.emit(.expr_modifier, expr.pos, -1, -1, u16(int(expr.kind)), 0, edges) } OrExpr { mut edges := []FlatEdge{cap: 1 + expr.stmts.len} - b.push_expr(mut edges, expr.expr) + edges = b.push_expr(edges, expr.expr) for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.expr_or, expr.pos, edges) } @@ -2158,23 +2202,23 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } RangeExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.start) - b.push_expr(mut edges, expr.end) + edges = b.push_expr(edges, expr.start) + edges = b.push_expr(edges, expr.end) return b.emit(.expr_range, expr.pos, -1, -1, u16(int(expr.op)), 0, edges) } SelectExpr { mut edges := []FlatEdge{cap: 2 + expr.stmts.len} - b.push_stmt(mut edges, expr.stmt) - b.push_expr(mut edges, expr.next) + edges = b.push_stmt(edges, expr.stmt) + edges = b.push_expr(edges, expr.next) for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.expr_select, expr.pos, edges) } SelectorExpr { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, expr.lhs) - b.push_expr(mut edges, Expr(expr.rhs)) + edges = b.push_expr(edges, expr.lhs) + edges = b.push_expr(edges, Expr(expr.rhs)) return b.emit_simple(.expr_selector, expr.pos, edges) } SqlExpr { @@ -2193,8 +2237,8 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } StringInterLiteral { mut edges := []FlatEdge{cap: 2} - b.push_edge(mut edges, b.make_list_strings(expr.values)) - b.push_edge(mut edges, b.make_list_string_inter(expr.inters)) + edges = b.push_edge(edges, b.make_list_strings(expr.values)) + edges = b.push_edge(edges, b.make_list_string_inter(expr.inters)) return b.emit(.expr_string_inter, expr.pos, -1, -1, u16(int(expr.kind)), 0, edges) } StringLiteral { @@ -2204,14 +2248,14 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { Tuple { mut edges := []FlatEdge{cap: expr.exprs.len} for e in expr.exprs { - b.push_expr(mut edges, e) + edges = b.push_expr(edges, e) } return b.emit_simple(.expr_tuple, expr.pos, edges) } UnsafeExpr { mut edges := []FlatEdge{cap: expr.stmts.len} for s in expr.stmts { - b.push_stmt(mut edges, s) + edges = b.push_stmt(edges, s) } return b.emit_simple(.expr_unsafe, expr.pos, edges) } @@ -2222,51 +2266,58 @@ fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId { } } +@[inline] +fn expr_is_zero_value(expr Expr) bool { + tag_word := unsafe { (&u64(&expr))[0] } + data_word := unsafe { (&u64(&expr))[1] } + return tag_word == 0 && data_word == 0 +} + fn (mut b FlatBuilder) add_type(typ Type) FlatNodeId { match typ { AnonStructType { mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, b.make_list_expr(typ.generic_params)) - b.push_edge(mut edges, b.make_list_expr(typ.embedded)) - b.push_edge(mut edges, b.make_list_field_decl(typ.fields)) + edges = b.push_edge(edges, b.make_list_expr(typ.generic_params)) + edges = b.push_edge(edges, b.make_list_expr(typ.embedded)) + edges = b.push_edge(edges, b.make_list_field_decl(typ.fields)) return b.emit_simple(.typ_anon_struct, token.Pos{}, edges) } ArrayFixedType { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, typ.len) - b.push_expr(mut edges, typ.elem_type) + edges = b.push_expr(edges, typ.len) + edges = b.push_expr(edges, typ.elem_type) return b.emit_simple(.typ_array_fixed, token.Pos{}, edges) } ArrayType { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, typ.elem_type) + edges = b.push_expr(edges, typ.elem_type) return b.emit_simple(.typ_array, token.Pos{}, edges) } ChannelType { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, typ.cap) - b.push_expr(mut edges, typ.elem_type) + edges = b.push_expr(edges, typ.cap) + edges = b.push_expr(edges, typ.elem_type) return b.emit_simple(.typ_channel, token.Pos{}, edges) } FnType { mut edges := []FlatEdge{cap: 3} - b.push_edge(mut edges, b.make_list_expr(typ.generic_params)) - b.push_edge(mut edges, b.make_list_parameter(typ.params)) - b.push_expr(mut edges, typ.return_type) + edges = b.push_edge(edges, b.make_list_expr(typ.generic_params)) + edges = b.push_edge(edges, b.make_list_parameter(typ.params)) + edges = b.push_expr(edges, typ.return_type) return b.emit_simple(.typ_fn, token.Pos{}, edges) } GenericType { mut edges := []FlatEdge{cap: 1 + typ.params.len} - b.push_expr(mut edges, typ.name) + edges = b.push_expr(edges, typ.name) for p in typ.params { - b.push_expr(mut edges, p) + edges = b.push_expr(edges, p) } return b.emit_simple(.typ_generic, token.Pos{}, edges) } MapType { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, typ.key_type) - b.push_expr(mut edges, typ.value_type) + edges = b.push_expr(edges, typ.key_type) + edges = b.push_expr(edges, typ.value_type) return b.emit_simple(.typ_map, token.Pos{}, edges) } NilType { @@ -2277,28 +2328,28 @@ fn (mut b FlatBuilder) add_type(typ Type) FlatNodeId { } OptionType { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, typ.base_type) + edges = b.push_expr(edges, typ.base_type) return b.emit_simple(.typ_option, token.Pos{}, edges) } PointerType { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, typ.base_type) + edges = b.push_expr(edges, typ.base_type) return b.emit(.typ_pointer, token.Pos{}, b.intern(typ.lifetime), -1, 0, 0, edges) } ResultType { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, typ.base_type) + edges = b.push_expr(edges, typ.base_type) return b.emit_simple(.typ_result, token.Pos{}, edges) } ThreadType { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, typ.elem_type) + edges = b.push_expr(edges, typ.elem_type) return b.emit_simple(.typ_thread, token.Pos{}, edges) } TupleType { mut edges := []FlatEdge{cap: typ.types.len} for t in typ.types { - b.push_expr(mut edges, t) + edges = b.push_expr(edges, t) } return b.emit_simple(.typ_tuple, token.Pos{}, edges) } @@ -2307,14 +2358,14 @@ fn (mut b FlatBuilder) add_type(typ Type) FlatNodeId { fn (mut b FlatBuilder) add_attribute(attr Attribute) FlatNodeId { mut edges := []FlatEdge{cap: 2} - b.push_expr(mut edges, attr.value) - b.push_expr(mut edges, attr.comptime_cond) + edges = b.push_expr(edges, attr.value) + edges = b.push_expr(edges, attr.comptime_cond) return b.emit(.aux_attribute, token.Pos{}, b.intern(attr.name), -1, 0, 0, edges) } fn (mut b FlatBuilder) add_field_init(field FieldInit) FlatNodeId { mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, field.value) + edges = b.push_expr(edges, field.value) return b.emit(.aux_field_init, token.Pos{}, b.intern(field.name), -1, 0, 0, edges) } @@ -2337,9 +2388,9 @@ fn field_decl_flags(field FieldDecl) u8 { fn (mut b FlatBuilder) add_field_decl(field FieldDecl) FlatNodeId { mut edges := []FlatEdge{cap: 3} - b.push_expr(mut edges, field.typ) - b.push_expr(mut edges, field.value) - b.push_edge(mut edges, b.make_list_attribute(field.attributes)) + edges = b.push_expr(edges, field.typ) + edges = b.push_expr(edges, field.value) + edges = b.push_edge(edges, b.make_list_attribute(field.attributes)) return b.emit(.aux_field_decl, token.Pos{}, b.intern(field.name), -1, 0, field_decl_flags(field), edges) } @@ -2350,23 +2401,23 @@ fn (mut b FlatBuilder) add_parameter(param Parameter) FlatNodeId { flags |= flag_is_mut } mut edges := []FlatEdge{cap: 1} - b.push_expr(mut edges, param.typ) + edges = b.push_expr(edges, param.typ) return b.emit(.aux_parameter, param.pos, b.intern(param.name), -1, 0, flags, edges) } fn (mut b FlatBuilder) add_match_branch(branch MatchBranch) FlatNodeId { mut edges := []FlatEdge{cap: 2} - b.push_edge(mut edges, b.make_list_expr(branch.cond)) - b.push_edge(mut edges, b.make_list_stmt(branch.stmts)) + edges = b.push_edge(edges, b.make_list_expr(branch.cond)) + edges = b.push_edge(edges, b.make_list_stmt(branch.stmts)) return b.emit_simple(.aux_match_branch, branch.pos, edges) } fn (mut b FlatBuilder) add_string_inter(inter StringInter) FlatNodeId { mut edges := []FlatEdge{cap: 4} - b.push_expr(mut edges, inter.expr) - b.push_expr(mut edges, inter.format_expr) - b.push_edge(mut edges, b.emit_int(inter.width)) - b.push_edge(mut edges, b.emit_int(inter.precision)) + edges = b.push_expr(edges, inter.expr) + edges = b.push_expr(edges, inter.format_expr) + edges = b.push_edge(edges, b.emit_int(inter.width)) + edges = b.push_edge(edges, b.emit_int(inter.precision)) return b.emit(.aux_string_inter, token.Pos{}, b.intern(inter.resolved_fmt), -1, u16(int(inter.format)), 0, edges) } diff --git a/vlib/v2/ast/flat_append_flat_test.v b/vlib/v2/ast/flat_append_flat_test.v index df791afd9..c78c440e8 100644 --- a/vlib/v2/ast/flat_append_flat_test.v +++ b/vlib/v2/ast/flat_append_flat_test.v @@ -165,3 +165,46 @@ fn test_append_flat_empty_source_is_noop() { assert dst.flat.edges.len == pre_edges assert dst.flat.files.len == pre_files } + +fn test_copy_subtree_from_preserves_stmt_shape_and_strings() { + mut src := new_flat_builder() + imp_id := src.emit_stmt(Stmt(ImportStmt{ + name: 'os' + alias: 'myos' + is_aliased: true + })) + dir_id := src.emit_stmt(Stmt(Directive{ + name: 'flag' + value: '-lm' + })) + for_id := src.emit_stmt(make_for_with_idents(['alpha', 'beta'])) + + mut dst := seeded_dst() + copied_imp := dst.copy_subtree_from(&src.flat, imp_id) + copied_dir := dst.copy_subtree_from(&src.flat, dir_id) + copied_for := dst.copy_subtree_from(&src.flat, for_id) + + assert dst.flat.subtree_signature(copied_for) == src.flat.subtree_signature(for_id) + + merged_imp := Cursor{ + flat: &dst.flat + id: copied_imp + }.stmt() as ImportStmt + assert merged_imp.name == 'os' + assert merged_imp.alias == 'myos' + assert merged_imp.is_aliased + + merged_dir := Cursor{ + flat: &dst.flat + id: copied_dir + }.stmt() as Directive + assert merged_dir.name == 'flag' + assert merged_dir.value == '-lm' +} + +fn test_copy_subtree_from_invalid_root_returns_invalid() { + mut src := new_flat_builder() + mut dst := new_flat_builder() + assert dst.copy_subtree_from(&src.flat, -1) == invalid_flat_node_id + assert dst.copy_subtree_from(&src.flat, 100) == invalid_flat_node_id +} diff --git a/vlib/v2/builder/builder.v b/vlib/v2/builder/builder.v index 5a75e6c1a..910938759 100644 --- a/vlib/v2/builder/builder.v +++ b/vlib/v2/builder/builder.v @@ -285,14 +285,9 @@ pub fn (mut b Builder) build(files []string) { } } else { // Parallel transform fans the per-file work across worker threads via - // the driver, then flattens. The sequential transform_flat_to_flat_direct - // is ~3x slower here because it cannot parallelize the per-file loop, and - // the flat AST has no thread-safe merge primitive to let workers append - // to one builder concurrently. For flat-codegen backends we still drop - // b.files immediately so codegen stays flat-only (the legacy files are - // live only transiently during the parallel transform). The memory- - // critical arm64 self-host runs --no-parallel, so it takes the sequential - // branch above and keeps the allocation-minimal flat-direct path. + // the driver. Flat-codegen backends use worker-local FlatBuilders merged + // by append_flat and keep b.files empty; legacy consumers request the + // compatibility []ast.File result. new_flat, files_out := b.transform_files_parallel_to_flat_via_driver(mut trans, !transform_flat_only) b.flat = new_flat diff --git a/vlib/v2/gen/arm64/linker.v b/vlib/v2/gen/arm64/linker.v index 25041ae76..b9152c6b5 100644 --- a/vlib/v2/gen/arm64/linker.v +++ b/vlib/v2/gen/arm64/linker.v @@ -882,8 +882,8 @@ fn (l Linker) generate_code_signature(ident string) []u8 { // Slot -2: Hash of requirements blob mut hash_buf := [32]u8{} sha256_hash(req_blob.data, req_blob.len, &hash_buf[0]) - for b in hash_buf { - sig << b + for i in 0 .. cs_hash_size { + sig << hash_buf[i] } // Slot -1: Info.plist (zeros = no Info.plist) diff --git a/vlib/v2/gen/arm64/tests/flat_ast_import_edges.v b/vlib/v2/gen/arm64/tests/flat_ast_import_edges.v new file mode 100644 index 000000000..a6667c65c --- /dev/null +++ b/vlib/v2/gen/arm64/tests/flat_ast_import_edges.v @@ -0,0 +1,29 @@ +import v2.ast + +fn main() { + file := ast.File{ + name: @FILE + mod: 'main' + imports: [ + ast.ImportStmt{ + name: 'v2.pref' + alias: 'pref' + }, + ] + stmts: [ + ast.Stmt(ast.ImportStmt{ + name: 'v2.pref' + alias: 'pref' + }), + ] + } + flat := ast.flatten_files([file]) + file_node := flat.nodes[flat.files[0].file_id] + imports := flat.read_file_imports(flat.files[0]) + println(file_node.edge_count) + println(imports.len) + if imports.len > 0 { + println(imports[0].name) + println(imports[0].alias) + } +} diff --git a/vlib/v2/gen/arm64/tests/mut_array_param_push.v b/vlib/v2/gen/arm64/tests/mut_array_param_push.v new file mode 100644 index 000000000..425b24125 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/mut_array_param_push.v @@ -0,0 +1,18 @@ +fn push_name(mut names []string, name string) { + names << name + println(names.len) +} + +fn wrap(mut names []string, name string) { + push_name(mut names, name) + println(names.len) +} + +fn main() { + mut names := []string{} + wrap(mut names, 'abc') + println(names.len) + if names.len > 0 { + println(names[0]) + } +} diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index c99118978..e7eabaf4a 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -1525,7 +1525,12 @@ fn (mut g Gen) emit_pass1_forward_decls() { fn (mut g Gen) emit_struct_forward_decl(decl ast.StructDecl) { name := g.get_struct_name(decl) - if decl.generic_params.len > 0 && name !in g.generic_struct_bindings + runtime_generic_params := if decl.generic_params.len > 0 { + g.generic_struct_runtime_param_names(name, name) + } else { + []string{} + } + if runtime_generic_params.len > 0 && name !in g.generic_struct_bindings && name !in g.generic_struct_instances { return } @@ -1833,7 +1838,7 @@ fn (mut g Gen) emit_fn_forward_decl_for_decl(decl ast.FnDecl, body_len int, file } if g.generic_fn_param_names(decl).len > 0 { gfn_name := g.get_fn_name(decl) - specs := g.generic_fn_specializations_for_emit_scope(decl) + specs := g.generic_fn_specializations_for_emit_scope_with_receiver_bindings(decl) if specs.len > 0 { prev_generic_types := g.active_generic_types.clone() for spec in specs { @@ -2358,8 +2363,8 @@ fn (mut g Gen) emit_forced_helpers_from_non_emit_files() { if stmt.kind() != .stmt_fn_decl || !stmt.name().starts_with('__sort_cmp_') { continue } - decl := stmt.fn_decl() - fn_name := g.get_fn_name(decl) + decl_sig := stmt.fn_decl_signature() + fn_name := g.get_fn_name(decl_sig) if fn_name == '' || fn_name !in g.force_emit_fn_names || fn_name in emitted { continue } @@ -2369,6 +2374,7 @@ fn (mut g Gen) emit_forced_helpers_from_non_emit_files() { if 'fn_${fn_name}' in g.fn_owner_file { continue } + decl := stmt.fn_decl() g.gen_fn_decl(decl) emitted[fn_name] = true } @@ -2490,10 +2496,18 @@ fn (mut g Gen) generic_types_from_specialized_fn_name(node ast.FnDecl, fn_name s base_name := g.get_fn_name(node) g.active_generic_types = prev_generic_types.move() prefix := '${base_name}_T_' - if !fn_name.starts_with(prefix) { - return none + mut suffix := '' + if fn_name.starts_with(prefix) { + suffix = fn_name[prefix.len..] + } else { + specialized_base := generic_call_base_name_for_specialization(fn_name) + specialized_prefix := '${specialized_base}_T_' + if specialized_fn_decl_base_name(fn_name) != base_name + || !fn_name.starts_with(specialized_prefix) { + return none + } + suffix = fn_name[specialized_prefix.len..] } - suffix := fn_name[prefix.len..] tokens := g.split_specialization_suffix(suffix, generic_params.len) or { return none } mut generic_types := map[string]types.Type{} for i, param_name in generic_params { @@ -2592,7 +2606,7 @@ fn (g &Gen) pass5_stmt_cost(file_idx int, stmt_idx int) int { if stmt_idx < 0 || stmt_idx >= stmts.len() { return 1 } - return cleanc_stmt_codegen_cost(stmts.at(stmt_idx).stmt()) + return cleanc_stmt_cursor_codegen_cost(stmts.at(stmt_idx)) } if file_idx < 0 || file_idx >= g.files.len || stmt_idx < 0 || stmt_idx >= g.files[file_idx].stmts.len { @@ -2761,7 +2775,7 @@ pub fn (g &Gen) pass5_file_cost(file_idx int) int { mut cost := 1 stmts := g.flat.file_cursor(file_idx).stmts() for i in 0 .. stmts.len() { - cost += cleanc_stmt_codegen_cost(stmts.at(i).stmt()) + cost += cleanc_stmt_cursor_codegen_cost(stmts.at(i)) } return cost } @@ -2783,6 +2797,86 @@ fn cleanc_stmts_codegen_cost(stmts []ast.Stmt) int { return cost } +fn cleanc_cursor_stmts_codegen_cost(stmts ast.CursorList) int { + mut cost := 0 + for i in 0 .. stmts.len() { + cost += cleanc_stmt_cursor_codegen_cost(stmts.at(i)) + } + return cost +} + +fn cleanc_cursor_children_expr_codegen_cost(c ast.Cursor, start int) int { + mut cost := 0 + for i in start .. c.edge_count() { + cost += cleanc_expr_cursor_codegen_cost(c.edge(i)) + } + return cost +} + +fn cleanc_cursor_children_stmt_codegen_cost(c ast.Cursor, start int) int { + mut cost := 0 + for i in start .. c.edge_count() { + cost += cleanc_stmt_cursor_codegen_cost(c.edge(i)) + } + return cost +} + +fn cleanc_stmt_cursor_codegen_cost(stmt ast.Cursor) int { + if !stmt.is_valid() { + return 1 + } + match stmt.kind() { + .stmt_fn_decl { + return 50 + cleanc_cursor_stmts_codegen_cost(stmt.list_at(3)) + } + .stmt_assign { + return 12 + cleanc_cursor_children_expr_codegen_cost(stmt, 0) + } + .stmt_expr { + return 4 + cleanc_expr_cursor_codegen_cost(stmt.edge(0)) + } + .stmt_return { + return 8 + cleanc_cursor_children_expr_codegen_cost(stmt, 0) + } + .stmt_for { + return 30 + cleanc_stmt_cursor_codegen_cost(stmt.edge(0)) + + cleanc_expr_cursor_codegen_cost(stmt.edge(1)) + + cleanc_stmt_cursor_codegen_cost(stmt.edge(2)) + + cleanc_cursor_children_stmt_codegen_cost(stmt, 3) + } + .stmt_for_in { + return 30 + cleanc_cursor_children_expr_codegen_cost(stmt, 0) + } + .stmt_block { + return 4 + cleanc_cursor_children_stmt_codegen_cost(stmt, 0) + } + .stmt_defer { + return 10 + cleanc_cursor_children_stmt_codegen_cost(stmt, 0) + } + .stmt_const_decl { + return 8 + cleanc_expr_cursor_codegen_cost(stmt.edge(0)) + } + .stmt_global_decl { + return 20 + stmt.list_at(1).len() * 8 + } + .stmt_struct_decl { + return 8 + stmt.list_at(4).len() + } + .stmt_interface_decl { + return 8 + stmt.list_at(3).len() + } + .stmt_type_decl { + return 8 + stmt.list_at(3).len() + } + .stmt_comptime, .stmt_label { + return 4 + cleanc_stmt_cursor_codegen_cost(stmt.edge(0)) + } + else { + return 1 + } + } +} + fn cleanc_stmt_codegen_cost(stmt ast.Stmt) int { match stmt { ast.FnDecl { @@ -2852,6 +2946,65 @@ fn cleanc_stmt_codegen_cost(stmt ast.Stmt) int { } } +fn cleanc_expr_cursor_codegen_cost(expr ast.Cursor) int { + if !expr.is_valid() { + return 1 + } + match expr.kind() { + .aux_list { + return cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .aux_field_init { + return 4 + cleanc_expr_cursor_codegen_cost(expr.edge(0)) + } + .expr_call { + return 18 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_call_or_cast { + return 14 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_init { + return 10 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_array_init { + return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_map_init { + return 10 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_if { + return 18 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_match { + return 20 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_infix { + return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_prefix, .expr_postfix, .expr_selector, .expr_keyword_operator, .expr_range { + return 4 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_index, .expr_cast, .expr_as_cast, .expr_tuple { + return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_paren, .expr_modifier { + return cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_or, .expr_unsafe { + return 12 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_lock, .expr_fn_literal, .expr_lambda, .expr_sql { + return 20 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + .expr_comptime { + return 8 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + else { + return 1 + cleanc_cursor_children_expr_codegen_cost(expr, 0) + } + } +} + fn cleanc_exprs_codegen_cost(exprs []ast.Expr) int { mut cost := 0 for expr in exprs { @@ -3180,13 +3333,28 @@ fn (g &Gen) has_live_reload_functions() bool { fn (g &Gen) cursor_stmts_have_live_reload_function(stmts ast.CursorList) bool { for i in 0 .. stmts.len() { - if g.stmt_has_live_reload_function(stmts.at(i).stmt()) { + if g.cursor_stmt_has_live_reload_function(stmts.at(i)) { return true } } return false } +fn (g &Gen) cursor_stmt_has_live_reload_function(stmt ast.Cursor) bool { + match stmt.kind() { + .stmt_fn_decl { + decl := stmt.fn_decl_signature() + return decl.attributes.has('live') && decl.name != 'main' + } + .stmt_expr { + return g.expr_cursor_has_live_reload_function(stmt.edge(0)) + } + else { + return false + } + } +} + fn (g &Gen) stmts_have_live_reload_function(stmts []ast.Stmt) bool { for stmt in stmts { if g.stmt_has_live_reload_function(stmt) { @@ -3206,6 +3374,17 @@ fn (g &Gen) stmt_has_live_reload_function(stmt ast.Stmt) bool { return false } +fn (g &Gen) expr_cursor_has_live_reload_function(expr ast.Cursor) bool { + if expr.kind() == .expr_comptime { + inner := expr.edge(0) + if inner.kind() == .expr_if { + return g.active_comptime_if_cursor_has_live_reload_function(inner) + } + return g.expr_cursor_has_live_reload_function(inner) + } + return false +} + fn (g &Gen) expr_has_live_reload_function(expr ast.Expr) bool { if expr is ast.ComptimeExpr { if expr.expr is ast.IfExpr { @@ -3216,6 +3395,29 @@ fn (g &Gen) expr_has_live_reload_function(expr ast.Expr) bool { return false } +fn (g &Gen) if_expr_cursor_body_has_live_reload_function(node ast.Cursor) bool { + for i in 2 .. node.edge_count() { + if g.cursor_stmt_has_live_reload_function(node.edge(i)) { + return true + } + } + return false +} + +fn (g &Gen) active_comptime_if_cursor_has_live_reload_function(node ast.Cursor) bool { + if g.eval_comptime_cond(node.edge(0).expr()) { + return g.if_expr_cursor_body_has_live_reload_function(node) + } + else_expr := node.edge(1) + if else_expr.kind() == .expr_if { + if else_expr.edge(0).kind() == .expr_empty { + return g.if_expr_cursor_body_has_live_reload_function(else_expr) + } + return g.active_comptime_if_cursor_has_live_reload_function(else_expr) + } + return false +} + fn (g &Gen) active_comptime_if_has_live_reload_function(node ast.IfExpr) bool { if g.eval_comptime_cond(node.cond) { return g.stmts_have_live_reload_function(node.stmts) diff --git a/vlib/v2/gen/cleanc/cleanc_test.v b/vlib/v2/gen/cleanc/cleanc_test.v index b74f85d9b..809b313e7 100644 --- a/vlib/v2/gen/cleanc/cleanc_test.v +++ b/vlib/v2/gen/cleanc/cleanc_test.v @@ -4114,7 +4114,8 @@ mut: g.type_modules['json2'] = true g.collect_generic_struct_bindings() - assert g.generic_struct_instances.len == 0 + assert g.generic_struct_instances['json2__LinkedList'].len == 1 + assert g.generic_struct_instances['json2__Node'].len == 1 mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs) gen.cache_bundle_name = 'imports' gen.emit_modules['json2'] = true diff --git a/vlib/v2/gen/cleanc/consts_and_globals.v b/vlib/v2/gen/cleanc/consts_and_globals.v index 6d1efaef8..16948dda5 100644 --- a/vlib/v2/gen/cleanc/consts_and_globals.v +++ b/vlib/v2/gen/cleanc/consts_and_globals.v @@ -238,23 +238,17 @@ fn (mut g Gen) collect_runtime_const_targets() { if body_stmt.kind() != .stmt_assign { continue } - assign_stmt := body_stmt.stmt() - if assign_stmt !is ast.AssignStmt { + if body_stmt.extra_int() != 1 { continue } - assign := assign_stmt as ast.AssignStmt - if assign.lhs.len != 1 { + lhs := body_stmt.edge(0) + if lhs.kind() != .expr_ident { continue } - lhs_expr := assign.lhs[0] - if lhs_expr !is ast.Ident { - continue - } - lhs_ident := lhs_expr as ast.Ident - if lhs_ident.name !in const_names { + if lhs.name() !in const_names { continue } - g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs_ident.name)] = true + g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs.name())] = true } } } diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index 1ebe447cc..45da98473 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -251,6 +251,7 @@ fn (mut g Gen) gen_bound_method_value_expr(node ast.SelectorExpr, expected_c_typ if lhs_base != recv_base && short_type_name(lhs_base) != short_type_name(recv_base) { return false } + g.mark_called_fn_name(method_name) callback_param_types := method_params[1..] mut ret_type := g.fn_return_types[method_name] or { 'void' } if ret_type == '' { @@ -288,7 +289,12 @@ fn (mut g Gen) gen_bound_method_value_expr(node ast.SelectorExpr, expected_c_typ g.anon_fn_defs << def.str() g.sb.write_string('(({ ${recv_store} = ') if receiver_type.ends_with('*') { - g.expr(node.lhs) + if lhs_type.ends_with('*') { + g.expr(node.lhs) + } else { + g.sb.write_u8(`&`) + g.expr(node.lhs) + } } else { if lhs_type.ends_with('*') { g.sb.write_string('(*') diff --git a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v index ee0472b58..3e0051bc5 100644 --- a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v +++ b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v @@ -2538,8 +2538,10 @@ fn main() { } ', ]) - assert csrc.contains('dep__Middleware_T_Context__use_T_Context(&app.Middleware_T_Context,') - assert csrc.contains('((void*)_bound_method_') + assert csrc.contains('dep__Middleware_T_Context__use(&app.Middleware_T_Context,') + || csrc.contains('dep__Middleware_T_Context__use_T_Context(&app.Middleware_T_Context,') + assert csrc.contains('((bool (*)(Context*))_bound_method_') + assert csrc.contains('_bound_recv_') assert !csrc.contains('app.use') assert !csrc.contains('app.before_request') } diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 5f0f20a7f..8ef486f2b 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -464,7 +464,7 @@ fn (mut g Gen) collect_fn_signatures() { if stmt.kind() != .stmt_fn_decl { continue } - g.collect_fn_signature_from_decl(stmt.fn_decl(), stmt.list_at(3).len()) + g.collect_fn_signature_from_cursor(stmt) } } return @@ -485,6 +485,30 @@ fn (mut g Gen) collect_fn_signatures() { } } +fn (mut g Gen) collect_fn_signature_from_cursor(stmt ast.Cursor) { + decl := stmt.fn_decl_signature() + body_len := stmt.list_at(3).len() + if body_len > 0 && g.fn_signature_collection_needs_body(decl) { + g.collect_fn_signature_from_decl(stmt.fn_decl(), body_len) + return + } + g.collect_fn_signature_from_decl(decl, body_len) +} + +fn (mut g Gen) fn_signature_collection_needs_body(decl ast.FnDecl) bool { + if !g.should_emit_fn_decl_cached(g.cur_module, decl) { + return false + } + if decl.language == .js || g.should_skip_backend_generic_fn(decl) { + return false + } + if g.generic_fn_param_names(decl).len > 0 || receiver_generic_param_names(decl).len > 0 { + return true + } + return decl.typ.return_type !is ast.EmptyExpr + && expr_has_generic_placeholder(decl.typ.return_type) +} + fn (mut g Gen) collect_fn_signature_from_decl(decl ast.FnDecl, body_len int) { if !g.should_emit_fn_decl_cached(g.cur_module, decl) { return @@ -504,7 +528,7 @@ fn (mut g Gen) collect_fn_signature_from_decl(decl ast.FnDecl, body_len int) { prev_runtime_decl_types := g.runtime_decl_types.clone() prev_not_local_var_cache := g.not_local_var_cache.clone() prev_cur_fn_generic_params := g.cur_fn_generic_params.clone() - for spec in g.generic_fn_specializations_for_emit_scope(decl) { + for spec in g.generic_fn_specializations_for_emit_scope_with_receiver_bindings(decl) { g.active_generic_types = spec.generic_types.clone() g.cur_fn_name = decl.name g.cur_fn_c_name = spec.name @@ -1216,7 +1240,8 @@ fn (mut g Gen) build_generic_fn_decl_index() { continue } decl := stmt.fn_decl_signature() - if g.generic_fn_param_names(decl).len == 0 { + if g.generic_fn_param_names(decl).len == 0 + && receiver_generic_param_names(decl).len == 0 { continue } info := GenericFnDeclInfo{ @@ -1248,7 +1273,8 @@ fn (mut g Gen) build_generic_fn_decl_index() { continue } if stmt is ast.FnDecl { - if g.generic_fn_param_names(stmt).len == 0 { + if g.generic_fn_param_names(stmt).len == 0 + && receiver_generic_param_names(stmt).len == 0 { continue } info := GenericFnDeclInfo{ @@ -2031,7 +2057,7 @@ fn (mut g Gen) find_generic_fn_decl_for_requirements(call_name string) ?ast.FnDe if stmt.kind() != .stmt_fn_decl || stmt.name() != short_name { continue } - decl := stmt.fn_decl() + decl := stmt.fn_decl_signature() if g.generic_fn_param_names(decl).len > 0 { return decl } @@ -2890,7 +2916,7 @@ fn (mut g Gen) discover_nested_generic_specs() { for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() == .stmt_fn_decl { - g.discover_nested_generic_specs_for_decl(stmt.fn_decl(), mut scanned_specs) + g.discover_nested_generic_specs_for_cursor(stmt, mut scanned_specs) } } } @@ -2911,6 +2937,20 @@ fn (mut g Gen) discover_nested_generic_specs() { g.build_generic_spec_index() } +fn (mut g Gen) discover_nested_generic_specs_for_cursor(stmt ast.Cursor, mut scanned_specs map[string]bool) { + decl := stmt.fn_decl_signature() + if !g.should_emit_fn_decl_cached(g.cur_module, decl) { + return + } + if g.generic_fn_param_names(decl).len == 0 { + return + } + if stmt.list_at(3).len() == 0 { + return + } + g.discover_nested_generic_specs_for_decl(stmt.fn_decl(), mut scanned_specs) +} + fn (mut g Gen) discover_nested_generic_specs_for_decl(decl ast.FnDecl, mut scanned_specs map[string]bool) { if !g.should_emit_fn_decl_cached(g.cur_module, decl) { return @@ -2994,7 +3034,7 @@ fn (mut g Gen) discover_direct_generic_call_specs() { for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() == .stmt_fn_decl { - g.discover_direct_generic_call_specs_for_decl(stmt.fn_decl()) + g.discover_direct_generic_call_specs_for_cursor(stmt) } } } @@ -3013,6 +3053,20 @@ fn (mut g Gen) discover_direct_generic_call_specs() { } } +fn (mut g Gen) discover_direct_generic_call_specs_for_cursor(stmt ast.Cursor) { + decl := stmt.fn_decl_signature() + if !g.should_emit_fn_decl_cached(g.cur_module, decl) { + return + } + if g.generic_fn_param_names(decl).len != 0 { + return + } + if stmt.list_at(3).len() == 0 { + return + } + g.discover_direct_generic_call_specs_for_decl(stmt.fn_decl()) +} + fn (mut g Gen) discover_direct_generic_call_specs_for_decl(decl ast.FnDecl) { if !g.should_emit_fn_decl_cached(g.cur_module, decl) { return @@ -3226,6 +3280,46 @@ fn (mut g Gen) generic_fn_specializations_for_emit_scope(node ast.FnDecl) []Gene return specs } +fn (mut g Gen) generic_fn_specializations_for_emit_scope_with_receiver_bindings(node ast.FnDecl) []GenericFnSpecialization { + specs := g.generic_fn_specializations_for_emit_scope(node) + recv_params := receiver_generic_param_names(node) + if recv_params.len == 0 { + return specs + } + mut receiver_bindings := g.get_all_receiver_generic_bindings(node) + if receiver_bindings.len == 0 { + if bindings := g.get_receiver_generic_bindings(node) { + receiver_bindings << bindings + } + } + if receiver_bindings.len == 0 { + return specs + } + mut out := []GenericFnSpecialization{} + mut seen := map[string]bool{} + for spec in specs { + for bindings in receiver_bindings { + mut generic_types := bindings.clone() + for param_name, concrete in spec.generic_types { + generic_types[param_name] = concrete + } + name := g.specialized_fn_name(node, generic_types) + if name == '' || name in seen { + continue + } + seen[name] = true + out << GenericFnSpecialization{ + name: name + generic_types: generic_types.clone() + } + } + } + if out.len == 0 { + return specs + } + return out +} + fn (mut g Gen) generic_fn_specializations_with_fallback(node ast.FnDecl, include_fallback bool) []GenericFnSpecialization { generic_params := g.generic_fn_param_names(node) if generic_params.len == 0 || g.env == unsafe { nil } { @@ -4676,9 +4770,9 @@ fn (mut g Gen) find_generic_fn_decl_by_spec_key(key string) ?ast.FnDecl { if stmt.kind() != .stmt_fn_decl || stmt.name() != short_name { continue } - decl := stmt.fn_decl() + decl := stmt.fn_decl_signature() if g.generic_fn_param_names(decl).len > 0 { - return decl + return stmt.fn_decl() } } } @@ -4939,7 +5033,7 @@ fn (mut g Gen) ensure_specialized_call_signature(name string) { if name == '' || !name.contains('_T_') || name in g.fn_param_is_ptr || name in g.fn_return_types { return } - base_name := name.all_before('_T_') + base_name := specialized_fn_decl_base_name(name) decl := g.find_generic_fn_decl_by_base_name(base_name) or { return } generic_types := g.generic_types_from_specialized_fn_name(decl, name) or { g.register_fn_signature(decl, name) @@ -4951,6 +5045,34 @@ fn (mut g Gen) ensure_specialized_call_signature(name string) { g.active_generic_types = prev_generic_types.clone() } +fn (g &Gen) receiver_only_specialized_call_name(name string) ?string { + if name == '' { + return none + } + base_name := generic_call_base_name_for_specialization(name) + if base_name == '' || base_name == name { + return none + } + if base_name in g.fn_param_types || base_name in g.fn_return_types + || base_name in g.fn_param_is_ptr { + return base_name + } + return none +} + +fn specialized_fn_decl_base_name(name string) string { + base_name := generic_call_base_name_for_specialization(name) + if base_name == '' || !base_name.contains('_T_') { + return base_name + } + method_sep := base_name.last_index('__') or { return base_name.all_before('_T_') } + receiver_name := base_name[..method_sep] + if !receiver_name.contains('_T_') { + return base_name + } + return receiver_name.all_before('_T_') + base_name[method_sep..] +} + fn (mut g Gen) record_late_single_generic_call_spec_from_c_type(fn_name string, param_name string, c_type string) { if fn_name == '' || param_name == '' { return @@ -5207,7 +5329,7 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } } if name.ends_with('_T') { - base_name := if name.contains('_T_') { name.all_before('_T_') } else { name[..name.len - 2] } + base_name := generic_call_base_name_for_specialization(name) if base_name != '' { mut has_arg_bindings := false if generic_decl := g.find_generic_fn_decl_by_base_name(base_name) { @@ -5464,17 +5586,19 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp return candidate } } - mut generic_base_name := if name.contains('_T_') { - name.all_before('_T_') - } else if name.ends_with('_T') { - name[..name.len - 2] - } else { - name - } + mut generic_base_name := generic_call_base_name_for_specialization(name) if qualified_name := g.qualified_generic_fn_base_name(generic_base_name) { generic_base_name = qualified_name } - decl := g.find_generic_fn_decl_by_base_name(generic_base_name) or { return none } + decl := g.find_generic_fn_decl_by_base_name(generic_base_name) or { + if generic_base_name.contains('__') { + g.find_generic_fn_decl_by_base_name(generic_base_name.all_after_last('__')) or { + return none + } + } else { + return none + } + } generic_params := g.generic_fn_param_names(decl) if generic_params.len == 0 || g.env == unsafe { nil } { return none @@ -5649,6 +5773,7 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } } mut bindings := map[string]types.Type{} + mut receiver_bindings := map[string]types.Type{} if decl.is_method && call_args.len > 0 && expr_has_generic_placeholder(decl.receiver.typ) { recv_arg := if call_args[0] is ast.ModifierExpr { (call_args[0] as ast.ModifierExpr).expr @@ -5658,7 +5783,8 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp if expr_has_valid_data(recv_arg) { if concrete := g.concrete_type_from_generic_call_arg(recv_arg) { g.infer_generic_type_bindings_from_param(decl.receiver.typ, - g.concrete_type_with_active_generics(concrete), generic_params, mut bindings) + g.concrete_type_with_active_generics(concrete), + receiver_generic_param_names(decl), mut receiver_bindings) } } } @@ -5764,7 +5890,11 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp return alt_candidate } } - candidate := g.specialized_fn_name(decl, bindings) + mut combined_bindings := receiver_bindings.clone() + for param_name, concrete in bindings { + combined_bindings[param_name] = concrete + } + candidate := g.specialized_fn_name(decl, combined_bindings) g.ensure_specialized_call_signature(candidate) if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { return candidate @@ -5777,6 +5907,21 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp return candidate } +fn (mut g Gen) specialize_direct_method_call_name(name string, receiver ast.Expr, args []ast.Expr) string { + if name == '' { + return name + } + mut call_args := []ast.Expr{cap: args.len + 1} + call_args << receiver + call_args << args + if specialized_name := g.try_specialize_generic_call_name(name, call_args) { + if g.generic_specialization_should_replace_call_name(name, specialized_name) { + return specialized_name + } + } + return name +} + // register_builder_methods ensures strings__Builder methods are known to // fn_param_is_ptr so call sites emit &sb for the receiver. The Builder // is a typedef for array and its methods take a mutable (pointer) receiver. @@ -5863,7 +6008,7 @@ fn (mut g Gen) gen_fn_decl_ptr(node &ast.FnDecl) { return } prev_generic_types := g.active_generic_types.clone() - specs := g.generic_fn_specializations_for_emit_scope(*node) + specs := g.generic_fn_specializations_for_emit_scope_with_receiver_bindings(*node) for spec in specs { g.active_generic_types = spec.generic_types.clone() g.gen_fn_decl_with_name_ptr(node, spec.name) @@ -10393,6 +10538,25 @@ fn explicit_generic_call_name(base_name string, tokens []string) string { return '${name}_T_${tokens.join('_')}' } +fn generic_call_base_name_for_specialization(name string) string { + if name == '' { + return '' + } + if name.ends_with('_T') { + return name[..name.len - 2] + } + if !name.contains('_T_') { + return name + } + method_sep := name.last_index('__') or { return name.all_before('_T_') } + method_start := method_sep + 2 + method_part := name[method_start..] + if idx := method_part.index('_T_') { + return name[..method_start + idx] + } + return name +} + fn (mut g Gen) explicit_generic_base_call_name(base_lhs ast.Expr, args []ast.Expr) ?string { match base_lhs { ast.Ident { @@ -10820,6 +10984,8 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) { method_name := lhs.rhs.name if concrete_method := g.resolve_method_on_concrete_type(base_receiver, method_name) { mut method_fn := concrete_method + call_args2 := g.interface_call_args_without_object(lhs.lhs, args) + method_fn = g.specialize_direct_method_call_name(method_fn, lhs.lhs, call_args2) g.mark_called_fn_name(method_fn) receiver_as_ptr := g.fn_param_wants_pointer(method_fn, 0) g.sb.write_string('${method_fn}(') @@ -10836,7 +11002,6 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) { } else { g.expr(lhs.lhs) } - call_args2 := g.interface_call_args_without_object(lhs.lhs, args) for i in 0 .. call_args2.len { g.sb.write_string(', ') g.gen_call_arg(method_fn, i + 1, call_args2[i]) @@ -11519,6 +11684,9 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) { } if name.contains('_T_') { g.ensure_specialized_call_signature(name) + if receiver_only_name := g.receiver_only_specialized_call_name(name) { + name = receiver_only_name + } } if name.contains('_T_') && name !in g.fn_return_types && name !in g.fn_param_is_ptr { base_name := name.all_before('_T_') @@ -11576,6 +11744,9 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) { } if name.contains('_T_') { g.ensure_specialized_call_signature(name) + if receiver_only_name := g.receiver_only_specialized_call_name(name) { + name = receiver_only_name + } } if call_args_have_field_init(call_args) { call_args = g.lower_field_init_call_args_for_codegen(name, call_args) diff --git a/vlib/v2/gen/cleanc/interface.v b/vlib/v2/gen/cleanc/interface.v index 1410c2e37..d8b036fdf 100644 --- a/vlib/v2/gen/cleanc/interface.v +++ b/vlib/v2/gen/cleanc/interface.v @@ -763,8 +763,9 @@ fn (mut g Gen) emit_fn_decl_by_c_name(fn_name string) { if stmt.kind() != .stmt_fn_decl { continue } - decl := stmt.fn_decl() - if g.get_fn_name(decl) == fn_name { + decl_sig := stmt.fn_decl_signature() + if g.get_fn_name(decl_sig) == fn_name { + decl := stmt.fn_decl() g.gen_fn_decl_with_name(decl, fn_name) return } diff --git a/vlib/v2/gen/cleanc/result_option_codegen_test.v b/vlib/v2/gen/cleanc/result_option_codegen_test.v index 2e22de5b1..cea0cd211 100644 --- a/vlib/v2/gen/cleanc/result_option_codegen_test.v +++ b/vlib/v2/gen/cleanc/result_option_codegen_test.v @@ -1759,9 +1759,10 @@ fn main() { outer := make() _ = outer.inner.value } -') + ') assert csrc.contains('struct Inner {\n\tint value;') assert csrc.contains('struct Outer {\n\tInner inner;') + || csrc.contains('struct Outer {\n\tInner_T_int inner;') assert csrc.contains('Outer make()') assert !csrc.contains('f64 make()') assert !csrc.contains('f64 outer = make()') diff --git a/vlib/v2/gen/cleanc/struct.v b/vlib/v2/gen/cleanc/struct.v index a4093fec9..b6486b224 100644 --- a/vlib/v2/gen/cleanc/struct.v +++ b/vlib/v2/gen/cleanc/struct.v @@ -156,7 +156,7 @@ fn (mut g Gen) collect_generic_struct_bindings_flat() { } } .stmt_fn_decl { - decl := stmt.fn_decl() + decl := stmt.fn_decl_signature() if decl.receiver.typ !is ast.EmptyExpr { g.scan_expr_for_generic_types(decl.receiver.typ) } @@ -166,7 +166,9 @@ fn (mut g Gen) collect_generic_struct_bindings_flat() { if decl.typ.return_type !is ast.EmptyExpr { g.scan_expr_for_generic_types(decl.typ.return_type) } - g.scan_fn_body_for_generic_types_with_clean_locals(decl, '') + if stmt.list_at(3).len() > 0 { + g.scan_fn_body_cursor_for_generic_types_with_clean_locals(stmt, decl, '') + } } else {} } @@ -315,7 +317,7 @@ fn (mut g Gen) scan_receiver_generic_struct_bindings_flat() { if stmt.kind() != .stmt_fn_decl { continue } - decl := stmt.fn_decl() + decl := stmt.fn_decl_signature() recv_params := receiver_generic_param_names(decl) if recv_params.len == 0 { continue @@ -333,7 +335,7 @@ fn (mut g Gen) scan_receiver_generic_struct_bindings_flat() { prev_generic_types := g.active_generic_types.clone() for bindings in binding_sets { g.active_generic_types = bindings.clone() - g.scan_fn_body_for_generic_types_with_clean_locals(decl, '') + g.scan_fn_body_cursor_for_generic_types_with_clean_locals(stmt, decl, '') } g.active_generic_types = prev_generic_types.clone() } @@ -564,6 +566,34 @@ fn (mut g Gen) scan_fn_body_for_generic_types_with_clean_locals(node ast.FnDecl, g.cur_fn_c_name = prev_fn_c_name } +fn (mut g Gen) scan_fn_body_cursor_for_generic_types_with_clean_locals(stmt ast.Cursor, decl ast.FnDecl, spec_name string) { + prev_runtime_local_types := g.runtime_local_types.clone() + prev_runtime_decl_types := g.runtime_decl_types.clone() + prev_not_local_var_cache := g.not_local_var_cache.clone() + prev_is_module_ident_cache := g.is_module_ident_cache.clone() + prev_resolved_module_names := g.resolved_module_names.clone() + prev_cur_fn_generic_params := g.cur_fn_generic_params.clone() + prev_fn_name := g.cur_fn_name + prev_fn_c_name := g.cur_fn_c_name + g.runtime_local_types = map[string]string{} + g.runtime_decl_types = map[string]string{} + g.not_local_var_cache = map[string]bool{} + g.is_module_ident_cache = map[string]bool{} + g.resolved_module_names = map[string]string{} + g.cur_fn_generic_params = map[string]string{} + g.cur_fn_name = '' + g.cur_fn_c_name = '' + g.scan_fn_body_cursor_for_generic_types(stmt, decl, spec_name) + g.runtime_local_types = prev_runtime_local_types.clone() + g.runtime_decl_types = prev_runtime_decl_types.clone() + g.not_local_var_cache = prev_not_local_var_cache.clone() + g.is_module_ident_cache = prev_is_module_ident_cache.clone() + g.resolved_module_names = prev_resolved_module_names.clone() + g.cur_fn_generic_params = prev_cur_fn_generic_params.clone() + g.cur_fn_name = prev_fn_name + g.cur_fn_c_name = prev_fn_c_name +} + fn clone_tuple_aliases(src map[string][]string) map[string][]string { mut out := map[string][]string{} for key, values in src { @@ -761,6 +791,7 @@ fn (mut g Gen) scan_expr_for_generic_types(e ast.Expr) { ast.Ident { if e.name.contains('_T_') || e.name.ends_with('_T') { g.scan_generic_fn_value_for_specs(e) + g.record_generic_struct_bindings_from_specialized_name(e.name) } } ast.Type { @@ -929,6 +960,62 @@ fn (mut g Gen) scan_expr_for_generic_types(e ast.Expr) { } } +fn (mut g Gen) record_generic_struct_bindings_from_specialized_name(raw_name string) { + if !raw_name.contains('_T_') { + return + } + struct_c_name := raw_name.all_before('_T_') + if struct_c_name == '' { + return + } + runtime_param_names := g.generic_struct_runtime_param_names(struct_c_name, struct_c_name) + if runtime_param_names.len == 0 { + return + } + arg_tokens := generic_call_embedded_type_arg_names_from_name(raw_name, runtime_param_names.len) + if arg_tokens.len != runtime_param_names.len { + return + } + mut bindings := map[string]types.Type{} + mut param_c_names := []string{cap: arg_tokens.len} + for i, param_name in runtime_param_names { + concrete_c_name := generic_token_to_c_type(arg_tokens[i]) + if concrete_c_name == '' { + return + } + concrete_type := g.concrete_type_from_call_arg_c_name(concrete_c_name) or { return } + if type_contains_generic_placeholder(concrete_type) + || !g.generic_concrete_type_is_runtime_specializable(concrete_type) { + return + } + bindings[param_name] = concrete_type + param_c_names << mangle_alias_component(concrete_c_name) + } + if bindings.len != runtime_param_names.len + || !g.generic_specialization_belongs_to_emit_modules(bindings) { + return + } + params_key := param_c_names.join('_') + bindings_key := g.generic_struct_bindings_key(runtime_param_names, bindings) + mut instances := g.generic_struct_instances[struct_c_name] + for inst in instances { + if inst.c_name == raw_name + || g.generic_struct_instance_matches(inst, params_key, bindings_key, runtime_param_names) + || g.generic_struct_instance_bindings_match(inst, bindings, runtime_param_names) { + return + } + } + instances << GenericStructInstance{ + params_key: params_key + bindings: bindings.clone() + c_name: raw_name + } + g.generic_struct_instances[struct_c_name] = instances + if struct_c_name !in g.generic_struct_bindings { + g.generic_struct_bindings[struct_c_name] = bindings.clone() + } +} + fn (mut g Gen) generic_call_decl_from_lhs(lhs ast.Expr) ?ast.FnDecl { mut call_name := match lhs { ast.Ident { @@ -969,7 +1056,7 @@ fn (mut g Gen) generic_call_decl_from_lhs(lhs ast.Expr) ?ast.FnDecl { } stmt := stmts.at(info.stmt_idx) if stmt.kind() == .stmt_fn_decl { - return stmt.fn_decl() + return stmt.fn_decl_signature() } } else if info.file_idx >= 0 && info.file_idx < g.files.len { file := g.files[info.file_idx] @@ -1004,7 +1091,7 @@ fn (mut g Gen) generic_call_decl_from_lhs(lhs ast.Expr) ?ast.FnDecl { if stmt.kind() != .stmt_fn_decl || stmt.name() != call_name { continue } - decl := stmt.fn_decl() + decl := stmt.fn_decl_signature() if g.generic_fn_param_names(decl).len > 0 { return decl } @@ -1069,6 +1156,30 @@ fn generic_call_short_name(lhs ast.Expr) string { return name } +fn generic_call_short_name_cursor(lhs ast.Cursor) string { + mut name := match lhs.kind() { + .expr_ident { + lhs.name() + } + .expr_selector { + lhs.edge(1).name() + } + .expr_generic_arg_or_index, .expr_generic_args { + generic_call_short_name_cursor(lhs.edge(0)) + } + else { + '' + } + } + + if name.contains('_T_') { + name = name.all_before('_T_') + } else if name.ends_with('_T') { + name = name[..name.len - 2] + } + return name +} + fn generic_call_raw_name(lhs ast.Expr) string { return match lhs { ast.Ident { @@ -1703,6 +1814,43 @@ fn (mut g Gen) scan_fn_body_for_generic_types(node ast.FnDecl, spec_name string) g.scan_stmts_for_generic_types(node.stmts) } +fn (mut g Gen) scan_fn_body_cursor_for_generic_types(stmt ast.Cursor, decl ast.FnDecl, spec_name string) { + prev_cur_fn_ret_type := g.cur_fn_ret_type + if ret_type := g.scan_fn_return_c_type(decl, spec_name) { + g.cur_fn_ret_type = ret_type + } + defer { + g.cur_fn_ret_type = prev_cur_fn_ret_type + } + body := stmt.list_at(3) + if decl.pos.id <= 0 { + if !g.cursor_stmts_may_need_generic_scan(body) { + return + } + g.scan_cursor_stmts_for_generic_types(body) + return + } + fn_key := decl.pos.id.str() + bindings_key := if spec_name.len > 0 { + spec_name + } else { + generic_body_scan_bindings_key(g.active_generic_types) + } + mode_key := if g.generic_call_spec_scan_only { 'calls' } else { 'all' } + cache_key := '${g.cur_module}:${fn_key}:${bindings_key}:${mode_key}' + if cache_key in g.generic_body_scan_cache { + if !g.collect_generic_scan_calls { + return + } + } else { + g.generic_body_scan_cache[cache_key] = true + } + if !g.cursor_stmts_may_need_generic_scan(body) { + return + } + g.scan_cursor_stmts_for_generic_types(body) +} + fn (mut g Gen) scan_fn_return_c_type(node ast.FnDecl, spec_name string) ?string { if spec_name != '' { if ret := g.fn_return_types[spec_name] { @@ -1771,6 +1919,360 @@ fn (g &Gen) stmts_may_need_generic_scan(stmts []ast.Stmt) bool { return false } +fn (g &Gen) cursor_stmts_may_need_generic_scan(stmts ast.CursorList) bool { + for i in 0 .. stmts.len() { + if g.cursor_stmt_may_need_generic_scan(stmts.at(i)) { + return true + } + } + return false +} + +fn (g &Gen) cursor_stmt_may_need_generic_scan(stmt ast.Cursor) bool { + if !stmt.is_valid() { + return false + } + match stmt.kind() { + .stmt_assign { + lhs_len := stmt.extra_int() + for i in 0 .. lhs_len { + if g.cursor_expr_may_need_generic_scan(stmt.edge(i)) { + return true + } + } + for i in lhs_len .. stmt.edge_count() { + if g.cursor_expr_may_need_generic_scan(stmt.edge(i)) { + return true + } + } + } + .stmt_return { + for i in 0 .. stmt.edge_count() { + if g.cursor_expr_may_need_generic_scan(stmt.edge(i)) { + return true + } + } + } + .stmt_expr { + return g.cursor_expr_may_need_generic_scan(stmt.edge(0)) + } + .stmt_comptime { + return g.cursor_stmt_may_need_generic_scan(stmt.edge(0)) + } + .stmt_for { + init := stmt.edge(0) + if init.kind() == .stmt_for_in && g.cursor_stmt_may_need_generic_scan(init) { + return true + } + if g.cursor_expr_may_need_generic_scan(stmt.edge(1)) { + return true + } + return g.cursor_stmts_may_need_generic_scan(stmt.for_body_list()) + } + .stmt_for_in { + return g.cursor_expr_may_need_generic_scan(stmt.edge(0)) + || g.cursor_expr_may_need_generic_scan(stmt.edge(1)) + || g.cursor_expr_may_need_generic_scan(stmt.edge(2)) + } + .stmt_block, .stmt_defer { + for i in 0 .. stmt.edge_count() { + if g.cursor_stmt_may_need_generic_scan(stmt.edge(i)) { + return true + } + } + } + else {} + } + + return false +} + +fn (g &Gen) cursor_call_lhs_may_need_generic_scan(lhs ast.Cursor) bool { + call_name := generic_call_short_name_cursor(lhs) + if call_name != '' { + for candidate in generic_call_decl_candidates(call_name) { + if candidate in g.generic_fn_decl_index { + return true + } + } + } + return g.cursor_expr_may_need_generic_scan(lhs) +} + +fn (g &Gen) cursor_expr_may_need_generic_scan(expr ast.Cursor) bool { + if !expr.is_valid() { + return false + } + match expr.kind() { + .expr_ident { + name := expr.name() + return name.contains('_T_') || name.ends_with('_T') + } + .typ_anon_struct, .typ_array_fixed, .typ_array, .typ_channel, .typ_fn, .typ_generic, + .typ_map, .typ_option, .typ_pointer, .typ_result, .typ_thread, .typ_tuple { + return g.type_cursor_may_need_generic_scan(expr) + } + .expr_generic_arg_or_index, .expr_generic_args, .expr_tuple { + return true + } + .expr_call { + if g.cursor_call_lhs_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_call_or_cast { + return g.cursor_call_lhs_may_need_generic_scan(expr.edge(0)) + || g.cursor_expr_may_need_generic_scan(expr.edge(1)) + } + .expr_infix { + return g.cursor_expr_may_need_generic_scan(expr.edge(0)) + || g.cursor_expr_may_need_generic_scan(expr.edge(1)) + } + .expr_postfix, .expr_prefix, .expr_paren, .expr_modifier, .expr_lambda, .expr_comptime { + return g.cursor_expr_may_need_generic_scan(expr.edge(0)) + } + .expr_assoc { + if g.type_cursor_may_need_generic_scan(expr.edge(0)) + || g.cursor_expr_may_need_generic_scan(expr.edge(1)) { + return true + } + for i in 2 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i).edge(0)) { + return true + } + } + } + .expr_if_guard { + return g.cursor_stmt_may_need_generic_scan(expr.edge(0)) + } + .expr_or { + if g.cursor_expr_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_unsafe { + for i in 0 .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_lock { + packed := u32(expr.extra_int()) + lock_len := int(packed & 0xFFFF) + rlock_len := int((packed >> 16) & 0xFFFF) + for i in 0 .. (lock_len + rlock_len) { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + for i in (lock_len + rlock_len) .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_if { + if g.cursor_expr_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 2 .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + return g.cursor_expr_may_need_generic_scan(expr.edge(1)) + } + .expr_match { + if g.cursor_expr_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + branch := expr.edge(i) + conds := branch.list_at(0) + for ci in 0 .. conds.len() { + if g.cursor_expr_may_need_generic_scan(conds.at(ci)) { + return true + } + } + if g.cursor_stmts_may_need_generic_scan(branch.list_at(1)) { + return true + } + } + } + .expr_select { + if g.cursor_stmt_may_need_generic_scan(expr.edge(0)) + || g.cursor_expr_may_need_generic_scan(expr.edge(1)) { + return true + } + for i in 2 .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_string_inter { + inters := expr.list_at(1) + for i in 0 .. inters.len() { + inter := inters.at(i) + if g.cursor_expr_may_need_generic_scan(inter.edge(0)) + || g.cursor_expr_may_need_generic_scan(inter.edge(1)) { + return true + } + } + } + .expr_init { + if g.type_cursor_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i).edge(0)) { + return true + } + } + } + .expr_array_init { + if g.type_cursor_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_map_init { + if g.type_cursor_may_need_generic_scan(expr.edge(0)) { + return true + } + for i in 1 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_index, .expr_cast, .expr_as_cast, .expr_range { + for i in 0 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_keyword_operator { + for i in 0 .. expr.edge_count() { + if g.cursor_expr_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_fn_literal { + captured_len := expr.extra_int() + for i in 0 .. captured_len { + if g.cursor_expr_may_need_generic_scan(expr.edge(1 + i)) { + return true + } + } + for i in (1 + captured_len) .. expr.edge_count() { + if g.cursor_stmt_may_need_generic_scan(expr.edge(i)) { + return true + } + } + } + .expr_sql, .expr_selector { + return g.cursor_expr_may_need_generic_scan(expr.edge(0)) + } + else {} + } + + return false +} + +fn (g &Gen) type_cursor_may_need_generic_scan(typ ast.Cursor) bool { + if !typ.is_valid() { + return false + } + match typ.kind() { + .typ_generic, .expr_generic_arg_or_index, .expr_generic_args { + return true + } + .expr_ident { + name := typ.name() + return name.contains('_T_') || name.ends_with('_T') + } + .expr_modifier, .expr_prefix, .expr_paren { + return g.type_cursor_may_need_generic_scan(typ.edge(0)) + } + .typ_anon_struct { + generic_params := typ.list_at(0) + for i in 0 .. generic_params.len() { + if g.cursor_expr_may_need_generic_scan(generic_params.at(i)) { + return true + } + } + embedded := typ.list_at(1) + for i in 0 .. embedded.len() { + if g.cursor_expr_may_need_generic_scan(embedded.at(i)) { + return true + } + } + fields := typ.list_at(2) + for i in 0 .. fields.len() { + field := fields.at(i) + if g.cursor_expr_may_need_generic_scan(field.edge(0)) + || g.cursor_expr_may_need_generic_scan(field.edge(1)) { + return true + } + } + } + .typ_array_fixed { + return g.type_cursor_may_need_generic_scan(typ.edge(0)) + || g.type_cursor_may_need_generic_scan(typ.edge(1)) + } + .typ_array, .typ_channel, .typ_option, .typ_pointer, .typ_result, .typ_thread { + return g.type_cursor_may_need_generic_scan(typ.edge(0)) + } + .typ_fn { + generic_params := typ.list_at(0) + for i in 0 .. generic_params.len() { + if g.cursor_expr_may_need_generic_scan(generic_params.at(i)) { + return true + } + } + params := typ.list_at(1) + for i in 0 .. params.len() { + if g.cursor_expr_may_need_generic_scan(params.at(i).edge(0)) { + return true + } + } + return g.type_cursor_may_need_generic_scan(typ.edge(2)) + } + .typ_map { + return g.type_cursor_may_need_generic_scan(typ.edge(0)) + || g.type_cursor_may_need_generic_scan(typ.edge(1)) + } + .typ_tuple { + for i in 0 .. typ.edge_count() { + if g.type_cursor_may_need_generic_scan(typ.edge(i)) { + return true + } + } + } + else {} + } + + return false +} + fn (g &Gen) call_lhs_may_need_generic_scan(lhs ast.Expr) bool { call_name := generic_call_short_name(lhs) if call_name != '' { @@ -2049,60 +2551,199 @@ fn (mut g Gen) scan_stmts_for_generic_types(stmts []ast.Stmt) { g.scan_expr_for_generic_types(rhs) g.scan_expr_stmts_for_generic_types(rhs) } - for i, lhs in stmt.lhs { - if i >= stmt.rhs.len { + for i, lhs in stmt.lhs { + if i >= stmt.rhs.len { + continue + } + lhs_name := generic_wrapped_ident_name(lhs) + if lhs_name == '' { + continue + } + + mut rhs_type := g.get_expr_type(stmt.rhs[i]).trim_space() + if generic_scan_type_is_unresolved(rhs_type) { + if raw := g.get_raw_type(stmt.rhs[i]) { + rhs_type = g.types_type_to_c(raw).trim_space() + } + } + if generic_scan_type_is_unresolved(rhs_type) { + rhs_type = (g.get_local_var_c_type(lhs_name) or { '' }).trim_space() + } + if generic_scan_type_is_unresolved(rhs_type) && stmt.rhs[i] is ast.ArrayInitExpr { + array_init := stmt.rhs[i] as ast.ArrayInitExpr + rhs_type = g.expr_type_to_c(array_init.typ).trim_space() + } + if !generic_scan_type_is_unresolved(rhs_type) { + g.remember_runtime_local_type(lhs_name, rhs_type) + } else if stmt.rhs[i] is ast.ArrayInitExpr { + g.remember_runtime_current_local_type(lhs_name, 'array') + } + } + } else if stmt is ast.ReturnStmt { + for expr in stmt.exprs { + g.scan_expr_for_generic_types(expr) + g.scan_expr_stmts_for_generic_types(expr) + } + } else if stmt is ast.ExprStmt { + g.scan_expr_for_generic_types(stmt.expr) + // Recurse into IfExpr/MatchExpr bodies + g.scan_expr_stmts_for_generic_types(stmt.expr) + } else if stmt is ast.ComptimeStmt { + if stmt.stmt is ast.ForStmt && g.scan_comptime_for_for_generic_types(stmt.stmt) { + continue + } + if stmt.stmt is ast.ExprStmt && stmt.stmt.expr is ast.ComptimeExpr + && (stmt.stmt.expr as ast.ComptimeExpr).expr is ast.IfExpr { + g.scan_comptime_if_for_generic_types((stmt.stmt.expr as ast.ComptimeExpr).expr as ast.IfExpr) + continue + } + // Recurse into comptime $if/$for bodies (wraps a single stmt) + g.scan_stmts_for_generic_types([stmt.stmt]) + } else if stmt is ast.ForStmt { + g.scan_stmts_for_generic_types(stmt.stmts) + } else if stmt is ast.BlockStmt { + g.scan_stmts_for_generic_types(stmt.stmts) + } + } +} + +fn (mut g Gen) scan_cursor_stmts_for_generic_types(stmts ast.CursorList) { + for i in 0 .. stmts.len() { + g.scan_cursor_stmt_for_generic_types(stmts.at(i)) + } +} + +fn (mut g Gen) scan_cursor_stmt_for_generic_types(stmt ast.Cursor) { + if !stmt.is_valid() { + return + } + match stmt.kind() { + .stmt_assign { + lhs_len := stmt.extra_int() + for i in lhs_len .. stmt.edge_count() { + g.scan_expr_cursor_for_generic_types(stmt.edge(i)) + } + for i in 0 .. lhs_len { + rhs_idx := lhs_len + i + if rhs_idx >= stmt.edge_count() { continue } + lhs := stmt.edge(i).expr() + rhs := stmt.edge(rhs_idx).expr() lhs_name := generic_wrapped_ident_name(lhs) if lhs_name == '' { continue } - mut rhs_type := g.get_expr_type(stmt.rhs[i]).trim_space() + mut rhs_type := g.get_expr_type(rhs).trim_space() if generic_scan_type_is_unresolved(rhs_type) { - if raw := g.get_raw_type(stmt.rhs[i]) { + if raw := g.get_raw_type(rhs) { rhs_type = g.types_type_to_c(raw).trim_space() } } if generic_scan_type_is_unresolved(rhs_type) { rhs_type = (g.get_local_var_c_type(lhs_name) or { '' }).trim_space() } - if generic_scan_type_is_unresolved(rhs_type) && stmt.rhs[i] is ast.ArrayInitExpr { - array_init := stmt.rhs[i] as ast.ArrayInitExpr + if generic_scan_type_is_unresolved(rhs_type) && rhs is ast.ArrayInitExpr { + array_init := rhs as ast.ArrayInitExpr rhs_type = g.expr_type_to_c(array_init.typ).trim_space() } if !generic_scan_type_is_unresolved(rhs_type) { g.remember_runtime_local_type(lhs_name, rhs_type) - } else if stmt.rhs[i] is ast.ArrayInitExpr { + } else if rhs is ast.ArrayInitExpr { g.remember_runtime_current_local_type(lhs_name, 'array') } } - } else if stmt is ast.ReturnStmt { - for expr in stmt.exprs { - g.scan_expr_for_generic_types(expr) - g.scan_expr_stmts_for_generic_types(expr) + } + .stmt_return { + for i in 0 .. stmt.edge_count() { + g.scan_expr_cursor_for_generic_types(stmt.edge(i)) } - } else if stmt is ast.ExprStmt { - g.scan_expr_for_generic_types(stmt.expr) - // Recurse into IfExpr/MatchExpr bodies - g.scan_expr_stmts_for_generic_types(stmt.expr) - } else if stmt is ast.ComptimeStmt { - if stmt.stmt is ast.ForStmt && g.scan_comptime_for_for_generic_types(stmt.stmt) { - continue + } + .stmt_expr { + g.scan_expr_cursor_for_generic_types(stmt.edge(0)) + } + .stmt_comptime { + inner := stmt.edge(0) + if inner.kind() == .stmt_for && g.scan_comptime_for_cursor_for_generic_types(inner) { + return } - if stmt.stmt is ast.ExprStmt && stmt.stmt.expr is ast.ComptimeExpr - && (stmt.stmt.expr as ast.ComptimeExpr).expr is ast.IfExpr { - g.scan_comptime_if_for_generic_types((stmt.stmt.expr as ast.ComptimeExpr).expr as ast.IfExpr) - continue + if inner.kind() == .stmt_expr { + expr := inner.edge(0) + if expr.kind() == .expr_comptime && expr.edge(0).kind() == .expr_if { + g.scan_comptime_if_cursor_for_generic_types(expr.edge(0)) + return + } } - // Recurse into comptime $if/$for bodies (wraps a single stmt) - g.scan_stmts_for_generic_types([stmt.stmt]) - } else if stmt is ast.ForStmt { - g.scan_stmts_for_generic_types(stmt.stmts) - } else if stmt is ast.BlockStmt { - g.scan_stmts_for_generic_types(stmt.stmts) + g.scan_cursor_stmt_for_generic_types(inner) } + .stmt_for { + g.scan_cursor_stmts_for_generic_types(stmt.for_body_list()) + } + .stmt_block, .stmt_defer { + for i in 0 .. stmt.edge_count() { + g.scan_cursor_stmt_for_generic_types(stmt.edge(i)) + } + } + else {} + } +} + +fn (mut g Gen) scan_expr_cursor_for_generic_types(expr ast.Cursor) { + if !expr.is_valid() || !g.cursor_expr_may_need_generic_scan(expr) { + return + } + if expr.kind() == .expr_comptime && expr.edge(0).kind() == .expr_if { + g.scan_comptime_if_cursor_for_generic_types(expr.edge(0)) + return + } + decoded := expr.expr() + g.scan_expr_for_generic_types(decoded) + g.scan_expr_stmts_for_generic_types(decoded) +} + +fn (mut g Gen) scan_comptime_for_cursor_for_generic_types(node ast.Cursor) bool { + if node.kind() != .stmt_for { + return false + } + for_in := node.edge(0) + if for_in.kind() != .stmt_for_in { + return false + } + iter := for_in.edge(2) + if iter.kind() != .expr_selector || iter.edge(1).name() != 'fields' { + return false + } + type_name := iter.edge(0).name() + concrete := g.active_generic_types[type_name] or { return false } + if concrete !is types.Struct { + return false + } + struct_type := g.comptime_for_struct_type(concrete, concrete as types.Struct) + prev_field_var := g.comptime_field_var + prev_field_name := g.comptime_field_name + prev_field_type := g.comptime_field_type + prev_field_raw_type := g.comptime_field_raw_type + prev_field_attrs := g.comptime_field_attrs + prev_field_idx := g.comptime_field_idx + defer { + g.comptime_field_var = prev_field_var + g.comptime_field_name = prev_field_name + g.comptime_field_type = prev_field_type + g.comptime_field_raw_type = prev_field_raw_type + g.comptime_field_attrs = prev_field_attrs + g.comptime_field_idx = prev_field_idx + } + g.comptime_field_var = for_in.edge(1).name() + for i, field in struct_type.fields { + g.comptime_field_name = field.name + g.comptime_field_type = g.types_type_to_c(field.typ) + g.comptime_field_raw_type = field.typ + g.comptime_field_attrs = g.comptime_field_attribute_strings(struct_type.name, field) + g.comptime_field_idx = i + g.scan_cursor_stmts_for_generic_types(node.for_body_list()) } + return true } // scan_expr_stmts_for_generic_types recurses into IfExpr/MatchExpr/ComptimeExpr bodies. @@ -2144,6 +2785,34 @@ fn (mut g Gen) scan_expr_stmts_for_generic_types(e ast.Expr) { } } +fn (mut g Gen) scan_comptime_if_cursor_for_generic_types(node ast.Cursor) { + if node.kind() != .expr_if { + return + } + if g.eval_comptime_cond(node.edge(0).expr()) { + g.scan_if_expr_cursor_body_for_generic_types(node) + return + } + else_expr := node.edge(1) + if else_expr.kind() == .expr_if { + if else_expr.edge(0).kind() == .expr_empty { + g.scan_if_expr_cursor_body_for_generic_types(else_expr) + } else { + g.scan_comptime_if_cursor_for_generic_types(else_expr) + } + return + } + if else_expr.is_valid() && else_expr.kind() != .expr_empty { + g.scan_expr_cursor_for_generic_types(else_expr) + } +} + +fn (mut g Gen) scan_if_expr_cursor_body_for_generic_types(node ast.Cursor) { + for i in 2 .. node.edge_count() { + g.scan_cursor_stmt_for_generic_types(node.edge(i)) + } +} + fn (mut g Gen) generic_scan_narrowing_from_cond(cond ast.Expr) (string, string) { if cond is ast.InfixExpr && cond.op == .key_is { name := cond.lhs.name() @@ -2634,11 +3303,17 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { if body_key in g.emitted_types || body_key in g.pending_late_body_keys { return } + if (name.contains('_T_') || name.contains('__T_')) + && g.struct_has_unresolved_generic_value_dependency(node, name) { + g.emit_late_concrete_struct_decl(node, name) + return + } // For generic structs with active bindings, verify that all field types // are already emitted. This prevents the last-resort pass from emitting // a generic struct body before its concrete field types are defined. if real_generic_params.len > 0 && g.active_generic_types.len > 0 { - if !g.struct_fields_resolved(node) { + if !g.struct_fields_resolved(node) + || g.struct_has_unresolved_generic_value_dependency(node, name) { return } } @@ -2647,7 +3322,7 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // Try to get the resolved struct type from the Environment env_struct := g.lookup_struct_type(node.name) for field in node.fields { - field_type := g.qualify_owner_local_field_type(name, g.expr_type_to_c(field.typ)) + field_type := g.struct_field_type_for_emit(name, field) if field_type.starts_with('Array_') { g.emit_array_alias_decl(field_type) } @@ -2757,7 +3432,10 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // Regular fields mut has_shared_fields := false for field in node.fields { - field_lookup_type := g.qualify_owner_local_field_type(name, g.expr_type_to_c(field.typ)) + mut field_lookup_type := g.struct_field_type_for_emit(name, field) + if fn_field_type := g.struct_fn_field_lookup_type_for_emit(field) { + field_lookup_type = fn_field_type + } field_v_name := if node.is_union && field_lookup_type.contains('__') && (field.name == '' || field.name.contains('__') || field.name == field_lookup_type @@ -2799,13 +3477,9 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { } if field.typ is ast.Type { if field.typ is ast.FnType { - if fn_type_has_generic_placeholder(field.typ) { - g.sb.writeln('\tvoid* ${field_name};') - continue + if decl := g.struct_fn_field_decl_for_emit(field, field_name) { + g.sb.writeln('\t${decl};') } - g.sb.write_string('\t') - g.gen_fn_type_param_decl(field.typ as ast.FnType, field_name) - g.sb.writeln(';') continue } } @@ -2880,7 +3554,8 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // Set active generic types to this instantiation's bindings prev_active := g.active_generic_types.clone() g.active_generic_types = inst.bindings.clone() - if !g.struct_fields_resolved(node) { + if !g.struct_fields_resolved(node) + || g.struct_has_unresolved_generic_value_dependency(node, inst.c_name) { g.active_generic_types = prev_active.clone() g.emit_late_generic_struct(name, inst) continue @@ -2894,7 +3569,7 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { g.sb.writeln('typedef struct ${inst.c_name} ${inst.c_name};') mut seen_field_typedef := map[string]bool{} for field in node.fields { - ft := g.expr_type_to_c(field.typ) + ft := g.struct_field_type_for_emit(inst.c_name, field) fbase := ft.trim_right('*') if ft.starts_with('Array_') { g.emit_array_alias_decl(ft) @@ -2910,8 +3585,15 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { } g.sb.writeln('${keyword} ${inst.c_name} {') for field in node.fields { - field_type := g.expr_type_to_c(field.typ) + field_type := g.struct_field_type_for_emit(inst.c_name, field) field_name := if field.name.len > 0 { field.name } else { 'value' } + if decl := g.struct_fn_field_decl_for_emit(field, field_name) { + g.sb.writeln('\t${decl};') + g.struct_field_types['${inst.c_name}.${field_name}'] = g.struct_fn_field_lookup_type_for_emit(field) or { + 'void*' + } + continue + } g.sb.writeln('\t${field_type} ${field_name};') // Register field types for this instantiation g.struct_field_types['${inst.c_name}.${field_name}'] = field_type @@ -2935,6 +3617,259 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { } } +fn (mut g Gen) emit_late_concrete_struct_decl(node ast.StructDecl, name string) { + body_key := 'body_${name}' + if body_key in g.emitted_types || body_key in g.pending_late_body_keys { + return + } + g.pending_late_body_keys[body_key] = true + keyword := if node.is_union { 'union' } else { 'struct' } + mut field_types := []string{cap: node.fields.len} + for field in node.fields { + field_type := g.struct_field_type_for_emit(name, field) + field_types << field_type + g.emit_late_generic_struct_dependency_for_type(field_type) + } + mut def := strings.new_builder(256) + def.writeln('typedef ${keyword} ${name} ${name};') + mut seen_field_typedef := map[string]bool{} + for field_type in field_types { + fbase := field_type.trim_right('*') + if fbase != name && fbase.contains('_T_') && !fbase.starts_with('Array_') + && !fbase.starts_with('Map_') && fbase !in seen_field_typedef { + seen_field_typedef[fbase] = true + def.writeln('typedef struct ${fbase} ${fbase};') + } + } + def.writeln('${keyword} ${name} {') + for i, field in node.fields { + field_type := if i < field_types.len { + field_types[i] + } else { + g.struct_field_type_for_emit(name, field) + } + field_name := if field.name.len > 0 { escape_c_keyword(field.name) } else { 'value' } + if decl := g.struct_fn_field_decl_for_emit(field, field_name) { + def.writeln('\t${decl};') + g.struct_field_types['${name}.${field.name}'] = g.struct_fn_field_lookup_type_for_emit(field) or { + 'void*' + } + continue + } + def.writeln('\t${field_type} ${field_name};') + g.struct_field_types['${name}.${field.name}'] = field_type + } + if node.fields.len == 0 { + def.writeln('\tu8 _dummy;') + } + def.writeln('};') + label := '${name}{}' + def.writeln('#define ${name}__str(v) ((string){.str = "${label}", .len = ${label.len}, .is_lit = 1})') + def.writeln('#define ${name}_str(v) ${name}__str(v)') + def.writeln('') + g.late_struct_defs << def.str() +} + +fn (mut g Gen) struct_has_unresolved_generic_value_dependency(node ast.StructDecl, owner_name string) bool { + for field in node.fields { + if g.is_pointer_type(field.typ) { + continue + } + field_type := g.struct_field_type_for_emit(owner_name, field) + if g.generic_struct_value_dependency_unresolved(field_type) { + return true + } + } + return false +} + +fn (mut g Gen) struct_fn_field_decl_for_emit(field ast.FieldDecl, field_name string) ?string { + if field.typ is ast.Type { + if field.typ is ast.FnType { + fn_type := field.typ as ast.FnType + if g.fn_type_has_unresolved_generic_placeholder(fn_type) { + return 'void* ${field_name}' + } + decl := c_fn_pointer_type_with_name(g.fn_type_c_type(fn_type), field_name) + if decl != '' { + return decl + } + } + } + return none +} + +fn (mut g Gen) struct_fn_field_lookup_type_for_emit(field ast.FieldDecl) ?string { + if field.typ is ast.Type { + if field.typ is ast.FnType { + fn_type := field.typ as ast.FnType + if g.fn_type_has_unresolved_generic_placeholder(fn_type) { + return 'void*' + } + return g.fn_type_c_type(fn_type) + } + } + return none +} + +fn (mut g Gen) fn_type_has_unresolved_generic_placeholder(fn_type ast.FnType) bool { + for param in fn_type.params { + if g.expr_has_unresolved_generic_placeholder(param.typ) { + return true + } + } + if fn_type.return_type !is ast.EmptyExpr { + return g.expr_has_unresolved_generic_placeholder(fn_type.return_type) + } + return false +} + +fn (mut g Gen) expr_has_unresolved_generic_placeholder(expr ast.Expr) bool { + match expr { + ast.Ident { + return is_generic_placeholder_type_name(expr.name) + && expr.name !in g.active_generic_types + } + ast.PrefixExpr { + return g.expr_has_unresolved_generic_placeholder(expr.expr) + } + ast.ModifierExpr { + return g.expr_has_unresolved_generic_placeholder(expr.expr) + } + ast.GenericArgOrIndexExpr { + return g.expr_has_unresolved_generic_placeholder(expr.lhs) + || g.expr_has_unresolved_generic_placeholder(expr.expr) + } + ast.GenericArgs { + if g.expr_has_unresolved_generic_placeholder(expr.lhs) { + return true + } + for arg in expr.args { + if g.expr_has_unresolved_generic_placeholder(arg) { + return true + } + } + return false + } + ast.Type { + match expr { + ast.ArrayType { + return g.expr_has_unresolved_generic_placeholder(expr.elem_type) + } + ast.ArrayFixedType { + return g.expr_has_unresolved_generic_placeholder(expr.elem_type) + || g.expr_has_unresolved_generic_placeholder(expr.len) + } + ast.MapType { + return g.expr_has_unresolved_generic_placeholder(expr.key_type) + || g.expr_has_unresolved_generic_placeholder(expr.value_type) + } + ast.OptionType { + return g.expr_has_unresolved_generic_placeholder(expr.base_type) + } + ast.ResultType { + return g.expr_has_unresolved_generic_placeholder(expr.base_type) + } + ast.PointerType { + return g.expr_has_unresolved_generic_placeholder(expr.base_type) + } + ast.GenericType { + if g.expr_has_unresolved_generic_placeholder(expr.name) { + return true + } + for param in expr.params { + if g.expr_has_unresolved_generic_placeholder(param) { + return true + } + } + } + ast.FnType { + return g.fn_type_has_unresolved_generic_placeholder(expr) + } + else {} + } + + return false + } + else { + return false + } + } +} + +fn (mut g Gen) struct_field_type_for_emit(owner_name string, field ast.FieldDecl) string { + first := g.qualify_owner_local_field_type(owner_name, g.expr_type_to_c(field.typ)) + second := g.qualify_owner_local_field_type(owner_name, g.expr_type_to_c(field.typ)) + if second != '' && second != first { + return g.canonical_generic_struct_type_for_emit(second) + } + return g.canonical_generic_struct_type_for_emit(first) +} + +fn (mut g Gen) canonical_generic_struct_type_for_emit(type_name string) string { + mut base := type_name.trim_space() + mut suffix := '' + for base.ends_with('*') { + base = base[..base.len - 1].trim_space() + suffix += '*' + } + if base == '' || !base.contains('__T_') { + return type_name + } + canonical := g.canonical_generic_struct_c_name_for_emit(base) + if canonical == base { + return type_name + } + return canonical + suffix +} + +fn (mut g Gen) canonical_generic_struct_c_name_for_emit(c_name string) string { + if c_name == '' || g.generic_struct_c_name_known_for_emit(c_name) { + return c_name + } + if !c_name.contains('__T_') { + return c_name + } + alt := c_name.replace('__T_', '_T_') + if alt != c_name && g.generic_struct_c_name_known_for_emit(alt) { + return alt + } + return c_name +} + +fn (mut g Gen) generic_struct_c_name_known_for_emit(c_name string) bool { + if c_name == '' { + return false + } + if c_name in g.emitted_types || 'body_${c_name}' in g.emitted_types + || 'body_${c_name}' in g.pending_late_body_keys { + return true + } + for _, instances in g.generic_struct_instances { + for inst in instances { + if inst.c_name == c_name { + return true + } + } + } + if _ := g.find_struct_node_by_c_name(c_name) { + return true + } + return false +} + +fn (mut g Gen) generic_struct_value_dependency_unresolved(type_name string) bool { + mut base := g.canonical_generic_struct_type_for_emit(type_name).trim_space() + for base.ends_with('*') { + base = base[..base.len - 1].trim_space() + } + if base == '' || !base.contains('_T_') || base.starts_with('Array_') || base.starts_with('Map_') + || base.starts_with('_option_') || base.starts_with('_result_') { + return false + } + return 'body_${base}' !in g.emitted_types +} + fn (mut g Gen) gen_sum_type_decl(node ast.TypeDecl) { name := g.get_type_decl_name(node) body_key := 'body_${name}' @@ -4126,6 +5061,9 @@ fn (mut g Gen) is_fn_pointer_alias_type(type_name string) bool { if type_name == '' { return false } + if type_name.contains('(*)') { + return true + } if raw_type := g.lookup_type_by_c_name(type_name) { if raw_type is types.Alias && raw_type.base_type is types.FnType { return true @@ -4687,6 +5625,9 @@ fn (mut g Gen) init_field_expected_type(type_name string, env_struct types.Struc if expected_field_type != '' { return expected_field_type } + if generic_field_type := g.generic_instance_field_type_for_init(type_name, field_name) { + return generic_field_type + } for field in env_struct.fields { if field.name == field_name { return g.types_type_to_c(field.typ) @@ -4707,6 +5648,39 @@ fn (mut g Gen) init_field_expected_type(type_name string, env_struct types.Struc return '' } +fn (mut g Gen) generic_instance_field_type_for_init(type_name string, field_name string) ?string { + if type_name == '' || field_name == '' { + return none + } + old_module := g.cur_module + old_file := g.cur_file_name + prev_active := g.active_generic_types.clone() + defer { + g.cur_module = old_module + g.cur_file_name = old_file + g.active_generic_types = prev_active.clone() + } + for struct_base, instances in g.generic_struct_instances { + for inst in instances { + if inst.c_name != type_name { + continue + } + node := g.find_generic_struct_node(struct_base) or { return none } + g.active_generic_types = inst.bindings.clone() + for field in node.fields { + if field.name != field_name { + continue + } + if fn_field_type := g.struct_fn_field_lookup_type_for_emit(field) { + return fn_field_type + } + return g.struct_field_type_for_emit(inst.c_name, field) + } + } + } + return none +} + fn (mut g Gen) gen_channel_init_expr(node ast.InitExpr) bool { mut is_channel := false mut elem_c_type := '' diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index 2b9843f89..e60b885da 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -1044,10 +1044,10 @@ fn (mut g Gen) collect_runtime_aliases() { // param / return types); decode the body-less signature // so cleanc's type-alias collection never rehydrates a fn // body on the default build path. - g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.fn_decl_signature())) + g.collect_decl_type_aliases_from_stmt_cursor(stmt) } .stmt_struct_decl, .stmt_interface_decl, .stmt_type_decl, .stmt_global_decl { - g.collect_decl_type_aliases_from_stmt(stmt.stmt()) + g.collect_decl_type_aliases_from_stmt_cursor(stmt) } else {} } @@ -1164,6 +1164,27 @@ fn (mut g Gen) collect_aliases_from_type(t types.Type) { } } +fn (mut g Gen) collect_decl_type_aliases_from_stmt_cursor(stmt ast.Cursor) { + match stmt.kind() { + .stmt_struct_decl { + g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.struct_decl())) + } + .stmt_interface_decl { + g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.interface_decl())) + } + .stmt_type_decl { + g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.type_decl())) + } + .stmt_fn_decl { + g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.fn_decl_signature())) + } + .stmt_global_decl { + g.collect_decl_type_aliases_from_stmt(ast.Stmt(stmt.global_decl(false))) + } + else {} + } +} + fn (mut g Gen) collect_decl_type_aliases_from_stmt(stmt ast.Stmt) { if !stmt_has_valid_data(stmt) { return @@ -1410,7 +1431,7 @@ fn (g &Gen) option_result_payload_is_unbound_generic_struct(val_type string) boo } typ := g.lookup_type_by_c_name_const(base) or { return false } if typ is types.Struct { - return typ.generic_params.len > 0 + return runtime_generic_param_names(typ.generic_params).len > 0 } return false } @@ -5463,6 +5484,10 @@ fn (mut g Gen) emit_late_generic_struct(base_name string, inst GenericStructInst prev_active := g.active_generic_types.clone() g.active_generic_types = inst.bindings.clone() mut def := strings.new_builder(256) + for field in struct_node.fields { + field_type := g.struct_field_type_for_emit(inst.c_name, field) + g.emit_late_generic_struct_dependency_for_type(field_type) + } // Emit typedef forward declarations so self-referential pointer fields // (e.g. linked-list `next` pointers) and other generic-instance field // types can be resolved without `struct` tag inside the body. @@ -5472,7 +5497,7 @@ fn (mut g Gen) emit_late_generic_struct(base_name string, inst GenericStructInst def.writeln('typedef ${keyword} ${inst.c_name} ${inst.c_name};') mut seen_field_typedef := map[string]bool{} for field in struct_node.fields { - ft := g.qualify_owner_local_field_type(inst.c_name, g.expr_type_to_c(field.typ)) + ft := g.struct_field_type_for_emit(inst.c_name, field) fbase := ft.trim_right('*') if fbase != inst.c_name && fbase.contains('_T_') && !fbase.starts_with('Array_') && !fbase.starts_with('Map_') && fbase !in seen_field_typedef { @@ -5482,8 +5507,15 @@ fn (mut g Gen) emit_late_generic_struct(base_name string, inst GenericStructInst } def.writeln('${keyword} ${inst.c_name} {') for field in struct_node.fields { - field_type := g.qualify_owner_local_field_type(inst.c_name, g.expr_type_to_c(field.typ)) + field_type := g.struct_field_type_for_emit(inst.c_name, field) field_name := if field.name.len > 0 { field.name } else { 'value' } + if decl := g.struct_fn_field_decl_for_emit(field, field_name) { + def.writeln('\t${decl};') + g.struct_field_types['${inst.c_name}.${field_name}'] = g.struct_fn_field_lookup_type_for_emit(field) or { + 'void*' + } + continue + } def.writeln('\t${field_type} ${field_name};') g.struct_field_types['${inst.c_name}.${field_name}'] = field_type } @@ -5502,6 +5534,75 @@ fn (mut g Gen) emit_late_generic_struct(base_name string, inst GenericStructInst g.late_struct_defs << def.str() } +fn (mut g Gen) emit_late_generic_struct_dependency_for_type(type_name string) { + mut base := g.canonical_generic_struct_type_for_emit(type_name).trim_space() + for base.ends_with('*') { + base = base[..base.len - 1].trim_space() + } + if base == '' || !base.contains('_T_') || base.starts_with('Array_') || base.starts_with('Map_') + || base.starts_with('_option_') || base.starts_with('_result_') { + return + } + body_key := 'body_${base}' + if body_key in g.emitted_types || body_key in g.pending_late_body_keys { + return + } + for struct_base, instances in g.generic_struct_instances { + for dep in instances { + if dep.c_name == base { + g.emit_late_generic_struct(struct_base, dep) + return + } + } + } + g.emit_late_concrete_struct_dependency_by_c_name(base) +} + +fn (mut g Gen) emit_late_concrete_struct_dependency_by_c_name(c_name string) { + body_key := 'body_${c_name}' + if body_key in g.emitted_types || body_key in g.pending_late_body_keys { + return + } + node := g.find_struct_node_by_c_name(c_name) or { return } + g.emit_late_concrete_struct_decl(node, c_name) +} + +fn (mut g Gen) find_struct_node_by_c_name(c_name string) ?ast.StructDecl { + prev_module := g.cur_module + prev_file_name := g.cur_file_name + defer { + g.cur_module = prev_module + g.cur_file_name = prev_file_name + } + if g.has_flat() { + for i in 0 .. g.flat.files.len { + fc := g.flat.file_cursor(i) + g.set_file_cursor_module(fc) + stmts := fc.stmts() + for j in 0 .. stmts.len() { + stmt := stmts.at(j) + if stmt.kind() != .stmt_struct_decl { + continue + } + decl := stmt.struct_decl() + if g.get_struct_name(decl) == c_name { + return decl + } + } + } + return none + } + for file in g.files { + g.set_file_module(file) + for stmt in file.stmts { + if stmt is ast.StructDecl && g.get_struct_name(stmt) == c_name { + return stmt + } + } + } + return none +} + // find_generic_struct_node finds the AST StructDecl for a given C struct name. fn (mut g Gen) find_generic_struct_node(c_name string) ?ast.StructDecl { if g.has_flat() { diff --git a/vlib/v2/markused/markused.v b/vlib/v2/markused/markused.v index b9b85877f..9f772597c 100644 --- a/vlib/v2/markused/markused.v +++ b/vlib/v2/markused/markused.v @@ -672,6 +672,16 @@ fn (mut w Walker) walk_expr_cursor(c ast.Cursor, mod_name string) { w.walk_expr_cursor(lhs_c, mod_name) for i in 1 .. c.edge_count() { arg_c := c.edge(i) + if arg_c.is_valid() && arg_c.kind() == .aux_field_init { + value_c := arg_c.edge(0) + if value_c.is_valid() && value_c.kind() == .expr_selector + && value_c.edge(0).kind() != .expr_empty { + w.mark_selector_fn_value_cursor_with_fallback(value_c, mod_name, true) + } + w.mark_fn_value_expr_cursor(value_c, mod_name) + w.walk_expr_cursor(value_c, mod_name) + continue + } w.mark_fn_value_expr_cursor(arg_c, mod_name) w.walk_expr_cursor(arg_c, mod_name) } @@ -4532,6 +4542,13 @@ fn (mut w Walker) walk_expr(expr ast.Expr, mod_name string) { w.mark_fn_name(expr.name, mod_name) } } + ast.FieldInit { + if expr.value is ast.SelectorExpr && expr.value.lhs !is ast.EmptyExpr { + w.mark_selector_fn_value_with_fallback(expr.value, mod_name, true) + } + w.mark_fn_value_expr(expr.value, mod_name) + w.walk_expr(expr.value, mod_name) + } ast.IfExpr { w.walk_expr(expr.cond, mod_name) w.walk_stmts(expr.stmts, mod_name) diff --git a/vlib/v2/ssa/build_call_from_flat_test.v b/vlib/v2/ssa/build_call_from_flat_test.v index 010dd5f2f..aca65258b 100644 --- a/vlib/v2/ssa/build_call_from_flat_test.v +++ b/vlib/v2/ssa/build_call_from_flat_test.v @@ -169,3 +169,123 @@ fn test_build_call_from_flat_matches_legacy() { assert mod_legacy.instrs.len == mod_flat.instrs.len assert mod_legacy.values.len == mod_flat.values.len } + +fn call_test_ident(name string) ast.Expr { + return ast.Expr(ast.Ident{ + name: name + }) +} + +fn call_test_param(name string, typ ast.Expr) ast.Parameter { + return ast.Parameter{ + name: name + typ: typ + } +} + +fn make_same_module_c_shadow_fixture() []ast.File { + int_type := call_test_ident('int') + return [ + ast.File{ + name: 'foo.v' + mod: 'foo' + stmts: [ + ast.Stmt(ast.ModuleStmt{ + name: 'foo' + }), + ast.Stmt(ast.FnDecl{ + name: 'callee' + language: .c + typ: ast.FnType{ + params: [ + call_test_param('x', int_type), + ] + return_type: int_type + } + }), + ast.Stmt(ast.FnDecl{ + name: 'callee' + typ: ast.FnType{ + params: [ + call_test_param('x', int_type), + ] + return_type: int_type + } + stmts: [ + ast.Stmt(ast.ReturnStmt{ + exprs: [ + call_test_ident('x'), + ] + }), + ] + }), + ast.Stmt(ast.FnDecl{ + name: 'caller' + typ: ast.FnType{ + return_type: int_type + } + stmts: [ + ast.Stmt(ast.ReturnStmt{ + exprs: [ + ast.Expr(ast.CallExpr{ + lhs: call_test_ident('callee') + args: [ + ast.Expr(ast.BasicLiteral{ + kind: .number + value: '1' + }), + ] + }), + ] + }), + ] + }), + ] + }, + ] +} + +fn call_test_func_value_ids(b &Builder, name string) []ValueID { + idx := b.fn_index[name] or { + assert false, 'missing SSA function ${name}' + return []ValueID{} + } + func := b.mod.funcs[idx] + mut ids := []ValueID{} + for block_id in func.blocks { + for value_id in b.mod.blocks[block_id].instrs { + ids << value_id + } + } + return ids +} + +fn call_test_callees(b &Builder, name string) []string { + mut callees := []string{} + for value_id in call_test_func_value_ids(b, name) { + value := b.mod.values[value_id] + if value.kind != .instruction { + continue + } + instr := b.mod.instrs[value.index] + if instr.op != .call || instr.operands.len == 0 { + continue + } + callee_id := instr.operands[0] + if callee_id > 0 && callee_id < b.mod.values.len { + callees << b.mod.values[callee_id].name + } + } + return callees +} + +fn test_build_all_from_flat_prefers_same_module_fn_over_bare_c_symbol() { + files := make_same_module_c_shadow_fixture() + flat := ast.flatten_files(files) + env := types.Environment.new() + mut mod := Module.new('same_module_c_shadow') + mut b := Builder.new_with_env(mod, env) + b.build_all_from_flat(&flat) + + assert call_test_callees(b, 'foo__caller') == ['foo__callee'] +} diff --git a/vlib/v2/ssa/build_fn_bodies_from_flat_test.v b/vlib/v2/ssa/build_fn_bodies_from_flat_test.v index 45efa88d5..05929b8cc 100644 --- a/vlib/v2/ssa/build_fn_bodies_from_flat_test.v +++ b/vlib/v2/ssa/build_fn_bodies_from_flat_test.v @@ -5,12 +5,10 @@ // // Bit-equality pin for s174: `build_fn_bodies_from_flat` (flat-cursor port) // must build the same SSA bodies as the legacy `build_fn_bodies`. The flat -// port walks one file's top-level stmts via FileCursor and only rehydrates -// `.stmt_fn_decl` nodes via `flat.decode_stmt`. Non-FnDecl stmts -// (ModuleStmt, StructDecl, EnumDecl, ConstDecl, etc.) are never decoded. -// The fn body itself is still fully rehydrated per-decl — future sessions -// port individual statement classes inside `build_fn` to consume flat -// cursors directly. +// port walks one file's top-level stmts via FileCursor and reads FnDecl +// signatures, filters, params, and body statements directly from cursors. +// Non-FnDecl stmts (ModuleStmt, StructDecl, EnumDecl, ConstDecl, etc.) are +// never decoded. module ssa import v2.ast diff --git a/vlib/v2/ssa/builder.v b/vlib/v2/ssa/builder.v index 2b94ae4bb..72067befc 100644 --- a/vlib/v2/ssa/builder.v +++ b/vlib/v2/ssa/builder.v @@ -496,10 +496,8 @@ pub fn (mut b Builder) build_all(files []ast.File) { // and only rehydrate the decls each phase actually consumes — non-decl stmts // (ModuleStmt, ImportStmt, attributes, etc.) are never decoded. // -// Phase 4 (`build_fn_bodies`) is still a rehydrate-then-call per-file wrapper: -// once a flat-native variant of that phase exists, the per-file rehydration -// below disappears and the post-transform `[]ast.File` allocation in the -// `_via_driver` wedges can be dropped. +// Phase 4 also consumes cursors directly: function signatures are read from +// `.stmt_fn_decl` nodes and function bodies are lowered from body stmt cursors. pub fn (mut b Builder) build_all_from_flat(flat &ast.FlatAst) { // Register builtin globals needed by all backends i32_t := b.mod.type_store.get_int(32) @@ -608,12 +606,8 @@ pub fn (mut b Builder) build_all_from_flat(flat &ast.FlatAst) { b.generate_fd_macro_stubs() } - // Phase 4: Build function bodies. Per-FileCursor walk that rehydrates - // only `.stmt_fn_decl` nodes via `flat.decode_stmt`. Non-FnDecl stmts are - // never decoded. The fn body itself is still rehydrated per-decl — - // future sessions port individual statement classes inside `build_fn` to - // consume flat cursors directly. This is the only remaining per-decl - // rehydration in the SSA builder. + // Phase 4: build function bodies from `.stmt_fn_decl` cursors. Signatures, + // filters, params, and body statements all stay on the flat path. if !b.skip_fn_bodies { for fi in 0 .. flat.files.len { fc := flat.file_cursor(fi) @@ -4379,10 +4373,13 @@ fn (mut b Builder) register_fn_sig_from_flat(c ast.Cursor) { fntyp_c := c.edge(1) ret_type := b.ast_type_to_ssa_from_flat(fntyp_c.edge(2)) + language := unsafe { ast.Language(int(c.aux())) } + if !c.flag(ast.flag_is_method) && language != .c && !is_generated_helper_fn_name(c.name()) { + b.module_fn_names[module_fn_key(b.cur_module, c.name())] = fn_name + } idx := b.mod.new_function(fn_name, ret_type, []TypeID{}) b.fn_index[fn_name] = idx b.mod.func_set_prototype(idx, true) - language := unsafe { ast.Language(int(c.aux())) } if language == .c { b.mod.func_set_c_extern(idx, true) } @@ -8810,6 +8807,11 @@ fn (mut b Builder) build_cast_from_flat(c ast.Cursor) ValueID { if wrapped := b.wrap_address_for_sumtype_target(addr, target_type) { return wrapped } + if b.is_mut_ptr_param_cursor(val_c) { + if ptr_cast := b.cast_forwarded_mut_param_addr_to_pointer(addr, target_type) { + return ptr_cast + } + } } val := b.build_expr_from_flat(val_c) return b.build_cast_value_to_type(val, target_type) @@ -9485,6 +9487,11 @@ fn (mut b Builder) build_cast_expr_to_type_id_from_flat(c ast.Cursor, target_typ if wrapped := b.wrap_address_for_sumtype_target(addr, target_type) { return wrapped } + if b.is_mut_ptr_param_cursor(c) { + if ptr_cast := b.cast_forwarded_mut_param_addr_to_pointer(addr, target_type) { + return ptr_cast + } + } } val := b.build_expr_from_flat(c) return b.build_cast_value_to_type(val, target_type) @@ -9777,6 +9784,15 @@ fn (mut b Builder) array_bound_or_zero(val ValueID, int_type TypeID) ValueID { if b.mod.values[val].typ == int_type { return val } + if b.mod.values[val].kind == .constant && b.mod.values[val].typ > 0 + && int(b.mod.values[val].typ) < b.mod.type_store.types.len + && int(int_type) < b.mod.type_store.types.len { + src := b.mod.type_store.types[b.mod.values[val].typ] + dst := b.mod.type_store.types[int_type] + if src.kind == .int_t && dst.kind == .int_t { + return b.mod.get_or_add_const(int_type, b.mod.values[val].name) + } + } return b.build_cast_value_to_type(val, int_type) } @@ -17113,12 +17129,67 @@ fn (mut b Builder) build_cast_expr_to_type(type_expr ast.Expr, value_expr ast.Ex return b.build_cast_expr_to_type_id(value_expr, target_type) } +fn (b &Builder) is_mut_ptr_param_expr(expr ast.Expr) bool { + match expr { + ast.Ident { + return expr.name in b.mut_ptr_params + } + ast.ParenExpr, ast.ModifierExpr { + return b.is_mut_ptr_param_expr(expr.expr) + } + else { + return false + } + } +} + +fn (b &Builder) is_mut_ptr_param_cursor(c ast.Cursor) bool { + match c.kind() { + .expr_ident { + return c.name() in b.mut_ptr_params + } + .expr_paren, .expr_modifier { + if c.edge_count() == 0 { + return false + } + return b.is_mut_ptr_param_cursor(c.edge(0)) + } + else { + return false + } + } +} + +fn (mut b Builder) cast_forwarded_mut_param_addr_to_pointer(addr ValueID, target_type TypeID) ?ValueID { + if !b.valid_value_id(addr) || !b.valid_type_id(target_type) { + return none + } + target_typ := b.mod.type_store.types[target_type] + if target_typ.kind != .ptr_t { + return none + } + addr_type := b.mod.values[addr].typ + if !b.valid_type_id(addr_type) { + return none + } + addr_typ := b.mod.type_store.types[addr_type] + if addr_typ.kind != .ptr_t && addr_typ.kind != .int_t { + return none + } + return b.build_cast_value_to_type(addr, target_type) +} + fn (mut b Builder) build_cast_expr_to_type_id(value_expr ast.Expr, target_type TypeID) ValueID { addr := b.build_addr(value_expr) if addr != 0 { if wrapped := b.wrap_address_for_sumtype_target(addr, target_type) { return wrapped } + if b.is_mut_ptr_param_expr(value_expr) { + if ptr_cast := b.cast_forwarded_mut_param_addr_to_pointer(addr, target_type) { + return ptr_cast + } + } } val := b.build_expr(value_expr) return b.build_cast_value_to_type(val, target_type) @@ -17144,6 +17215,11 @@ fn (mut b Builder) build_cast(expr ast.CastExpr) ValueID { if wrapped := b.wrap_address_for_sumtype_target(addr, target_type) { return wrapped } + if b.is_mut_ptr_param_expr(expr.expr) { + if ptr_cast := b.cast_forwarded_mut_param_addr_to_pointer(addr, target_type) { + return ptr_cast + } + } } val := b.build_expr(expr.expr) return b.build_cast_value_to_type(val, target_type) @@ -17191,6 +17267,11 @@ fn (mut b Builder) build_call_or_cast(expr ast.CallOrCastExpr) ValueID { if wrapped := b.wrap_address_for_sumtype_target(addr, target_type) { return wrapped } + if b.is_mut_ptr_param_expr(expr.expr) { + if ptr_cast := b.cast_forwarded_mut_param_addr_to_pointer(addr, target_type) { + return ptr_cast + } + } } val := b.build_expr(expr.expr) return b.build_cast_value_to_type(val, target_type) diff --git a/vlib/v2/transformer/flat_write.v b/vlib/v2/transformer/flat_write.v index 4d2f42687..d1be68755 100644 --- a/vlib/v2/transformer/flat_write.v +++ b/vlib/v2/transformer/flat_write.v @@ -1629,17 +1629,15 @@ import v2.types // continue to pass. // // Phase 5: post-pass port. -// `post_pass` (runtime const init injection, helper functions, str/clone -// generation, ...) still mutates `[]ast.File`. Port it to either emit -// directly into the builder or run as a separate flat-write pass after -// `transform_file_index_to_flat` is complete for all files. -// -// Phase 6: drop legacy materialisation. -// `transform_files_to_flat`'s second return value (`[]ast.File`) is the -// last consumer keeping legacy AST alive. Once the SSA builder consumes -// flat directly, the return shape changes to `ast.FlatAst` only and the -// boundary `flatten_files()` inside the wedge is removed. This is where -// the peak-memory win from project_v2_flat_migration.md materialises. +// The active flat-direct paths run post_pass_to_flat and the post-pass tail +// without materialising a transformed []ast.File. Compatibility entry points +// still maintain legacy files for the `.v`/eval backends. +// +// Phase 6: drop compatibility materialisation. +// `transform_files_to_flat` and `_via_driver` still return []ast.File for +// legacy consumers. Flat-codegen backends use transform_flat_to_flat_direct +// or the parallel flat-direct path and keep the transformed program in +// FlatAst only. // // ----- Rewrite Site Inventory (55 sites, audited 2026-05-26) ----- // @@ -1955,34 +1953,75 @@ fn (mut t Transformer) transform_stmt_list_item_to_flat(stmt ast.Stmt, mut ids [ // statement kind at a time (eliminating the whole-subtree `.stmt()` decode at // the `transform_cursor_stmts_to_flat_direct` loop). // -// First converted set: the true-passthrough top-level kinds that carry no -// `try_expand_*` guard and that `transform_stmt_to_flat` emits verbatim -// (flat_write.v:3890). They are routed through `append_transformed_stmt_to_flat` -// exactly as the fallback would after its guards fail to match — bit-equal, just -// skipping the (always-false for these kinds) guard checks. They still decode via -// `c.stmt()` for now (dropping that decode needs a flat-to-flat subtree copy and -// is a later stage); the value here is the dispatcher that subsequent stages -// (const/global, then the FnDecl body — the real decode win) extend arm by arm. +// First converted set: true-passthrough top-level kinds that carry no +// `try_expand_*` guard and that `transform_stmt_to_flat` emits verbatim. Those +// copy their flat subtrees directly, so they do not route through `c.stmt()`. fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) { match c.kind() { .stmt_import, .stmt_module, .stmt_directive, .stmt_empty, .stmt_enum_decl, - .stmt_interface_decl, .stmt_type_decl, .stmt_asm, .stmt_flow_control { - t.append_transformed_stmt_to_flat(mut ids, c.stmt(), mut out) + .stmt_interface_decl, .stmt_type_decl, .stmt_asm, .stmt_flow_control, .stmt_attributes { + id := out.copy_subtree_from(c.flat, c.id) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } .stmt_const_decl { id := t.transform_const_decl_cursor_to_flat(c, mut out) t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } + .stmt_block { + id := t.transform_block_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + .stmt_defer { + id := t.transform_defer_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + .stmt_label { + id := t.transform_label_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } .stmt_global_decl { id := t.transform_global_decl_cursor_to_flat(c, mut out) t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } + .stmt_struct_decl { + id := out.emit_stmt(t.transform_struct_decl(c.struct_decl())) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + .stmt_assert { + t.expand_assert_stmt_cursor_to_flat(c, mut ids, mut out) + } + .stmt_assign { + t.transform_stmt_list_item_to_flat(assign_stmt_from_cursor(c), mut ids, mut out) + } + .stmt_comptime { + id := t.transform_comptime_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + .stmt_expr { + if t.expr_stmt_cursor_needs_legacy_expand(c) { + t.transform_stmt_list_item_to_flat(expr_stmt_from_cursor(c), mut ids, mut out) + } else { + id := t.transform_expr_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + } + .stmt_return { + if t.return_stmt_cursor_needs_legacy_expand(c) { + t.transform_stmt_list_item_to_flat(return_stmt_from_cursor(c), mut ids, mut out) + } else { + id := t.transform_return_stmt_cursor_to_flat(c, mut out) + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + } + .stmt_for_in { + t.transform_stmt_list_item_to_flat(for_in_stmt_from_cursor(c), mut ids, mut out) + } .stmt_fn_decl { // Stream the body cursor-native when it has no defers (defer // lowering needs the whole body, so those take the legacy // whole-decl decode path). if flat_body_has_defer(c.list_at(3)) { - t.transform_stmt_list_item_to_flat(c.stmt(), mut ids, mut out) + t.transform_stmt_list_item_to_flat(c.fn_decl(), mut ids, mut out) } else { id := t.transform_fn_decl_streaming_to_flat(c, mut out) t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) @@ -1999,14 +2038,14 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut // `stmt.init is ast.ForInStmt` branch exactly. init_c := c.edge(0) if init_c.is_valid() && init_c.kind() == .stmt_for_in { - t.transform_stmt_list_item_to_flat(c.stmt(), mut ids, mut out) + t.transform_stmt_list_item_to_flat(for_stmt_from_cursor(c), mut ids, mut out) } else { id := t.transform_for_stmt_streaming_to_flat(c, mut out) t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } } else { - t.transform_stmt_list_item_to_flat(c.stmt(), mut ids, mut out) + panic('unexpected flat statement kind: ${c.kind()}') } } } @@ -2093,6 +2132,247 @@ fn (mut t Transformer) transform_global_decl_cursor_to_flat(c ast.Cursor, mut ou return out.emit_global_decl_by_ids(c.flag(ast.flag_is_public), decl_attrs_id, fields_list_id) } +fn (mut t Transformer) transform_block_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + body := ast.CursorList{ + flat: c.flat + parent_id: c.id + } + stmt_ids := t.transform_cursor_stmts_to_flat_direct(body, [], mut out) + return out.emit_block_stmt_by_ids(stmt_ids) +} + +fn (mut t Transformer) transform_defer_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + body := ast.CursorList{ + flat: c.flat + parent_id: c.id + } + mode := if c.flag(ast.flag_defer_func) { + ast.DeferMode.function + } else { + ast.DeferMode.scoped + } + stmt_ids := t.transform_cursor_stmts_to_flat_direct(body, [], mut out) + return out.emit_defer_stmt_by_ids(mode, stmt_ids) +} + +fn (mut t Transformer) transform_comptime_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + inner := c.edge(0) + if inner.kind() == .stmt_for { + stmt_ids := t.transform_cursor_stmts_to_flat_direct(inner.for_body_list(), [], mut out) + init_id := out.copy_subtree_from(inner.flat, inner.edge(0).id) + cond_id := out.copy_subtree_from(inner.flat, inner.edge(1).id) + post_id := out.copy_subtree_from(inner.flat, inner.edge(2).id) + for_id := out.emit_for_stmt_by_ids(init_id, cond_id, post_id, stmt_ids) + return out.emit_comptime_stmt_by_id(for_id) + } + return t.transform_stmt_cursor_to_flat(inner, mut out) +} + +fn (mut t Transformer) transform_label_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + inner_id := t.transform_stmt_cursor_to_flat(c.edge(0), mut out) + return out.emit_label_stmt_by_id(c.name(), inner_id) +} + +fn (mut t Transformer) expand_assert_stmt_cursor_to_flat(c ast.Cursor, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) { + t.expand_assert_stmt_to_flat(ast.AssertStmt{ + expr: c.edge(0).expr() + extra: c.edge(1).expr() + }, mut ids, mut out) +} + +fn assign_stmt_from_cursor(c ast.Cursor) ast.AssignStmt { + lhs_len := c.extra_int() + mut lhs := []ast.Expr{cap: lhs_len} + for i in 0 .. lhs_len { + lhs << c.edge(i).expr() + } + mut rhs := []ast.Expr{cap: c.edge_count() - lhs_len} + for i in lhs_len .. c.edge_count() { + rhs << c.edge(i).expr() + } + return ast.AssignStmt{ + op: unsafe { token.Token(int(c.aux())) } + lhs: lhs + rhs: rhs + pos: c.pos() + } +} + +fn for_in_stmt_from_cursor(c ast.Cursor) ast.ForInStmt { + return ast.ForInStmt{ + key: c.edge(0).expr() + value: c.edge(1).expr() + expr: c.edge(2).expr() + } +} + +fn for_stmt_from_cursor(c ast.Cursor) ast.ForStmt { + return ast.ForStmt{ + init: c.edge(0).stmt() + cond: c.edge(1).expr() + post: c.edge(2).stmt() + stmts: c.for_body_list().stmts() + } +} + +fn expr_stmt_from_cursor(c ast.Cursor) ast.ExprStmt { + return ast.ExprStmt{ + expr: c.edge(0).expr() + } +} + +fn return_stmt_from_cursor(c ast.Cursor) ast.ReturnStmt { + mut exprs := []ast.Expr{cap: c.edge_count()} + for i in 0 .. c.edge_count() { + exprs << c.edge(i).expr() + } + return ast.ReturnStmt{ + exprs: exprs + } +} + +fn (mut t Transformer) transform_expr_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + is_direct_if := c.edge(0).kind() == .expr_if + saved_skip := t.skip_if_value_lowering + if is_direct_if { + t.skip_if_value_lowering = true + } + expr_id := t.transform_expr_to_flat(c.edge(0).expr(), mut out) + t.skip_if_value_lowering = saved_skip + return out.emit_expr_stmt_by_id(expr_id) +} + +fn (t &Transformer) expr_stmt_cursor_needs_legacy_expand(c ast.Cursor) bool { + expr := c.edge(0) + if !expr.is_valid() { + return true + } + if t.cursor_subtree_has_or_expr(expr) { + return true + } + return expr.kind() in [.expr_comptime, .expr_if, .expr_lock, .expr_call, .expr_call_or_cast, + .expr_postfix, .expr_infix] +} + +fn (mut t Transformer) transform_return_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + mut exprs := []ast.Expr{cap: c.edge_count()} + for i in 0 .. c.edge_count() { + exprs << c.edge(i).expr() + } + transformed_exprs := t.transform_return_stmt_parts(ast.ReturnStmt{ + exprs: exprs + }) + mut expr_ids := []ast.FlatNodeId{cap: transformed_exprs.len} + for e in transformed_exprs { + expr_ids << out.emit_expr(e) + } + return out.emit_return_stmt_by_ids(expr_ids) +} + +fn (t &Transformer) return_stmt_cursor_needs_legacy_expand(c ast.Cursor) bool { + if c.edge_count() == 1 { + expr := c.edge(0) + if expr.kind() == .expr_match { + return true + } + if expr.kind() == .expr_if && !expr_cursor_is_empty(expr.edge(1)) { + return true + } + } + for i in 0 .. c.edge_count() { + if t.cursor_subtree_has_or_expr(c.edge(i)) { + return true + } + } + return false +} + +fn (t &Transformer) cursor_subtree_has_or_expr(c ast.Cursor) bool { + if !c.is_valid() { + return false + } + if c.kind() == .expr_or { + return true + } + if c.kind() == .expr_postfix { + op := unsafe { token.Token(int(c.aux())) } + if op in [.not, .question] { + return true + } + } + for i in 0 .. c.edge_count() { + if t.cursor_subtree_has_or_expr(c.edge(i)) { + return true + } + } + return false +} + +fn (mut t Transformer) transform_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + if !c.is_valid() { + return out.emit_stmt(ast.empty_stmt) + } + match c.kind() { + .stmt_import, .stmt_module, .stmt_directive, .stmt_empty, .stmt_enum_decl, + .stmt_interface_decl, .stmt_type_decl, .stmt_asm, .stmt_flow_control, .stmt_attributes { + return out.copy_subtree_from(c.flat, c.id) + } + .stmt_const_decl { + return t.transform_const_decl_cursor_to_flat(c, mut out) + } + .stmt_block { + return t.transform_block_stmt_cursor_to_flat(c, mut out) + } + .stmt_defer { + return t.transform_defer_stmt_cursor_to_flat(c, mut out) + } + .stmt_label { + return t.transform_label_stmt_cursor_to_flat(c, mut out) + } + .stmt_global_decl { + return t.transform_global_decl_cursor_to_flat(c, mut out) + } + .stmt_struct_decl { + return out.emit_stmt(t.transform_struct_decl(c.struct_decl())) + } + .stmt_assert { + expr_id := t.transform_expr_to_flat(c.edge(0).expr(), mut out) + return out.emit_assert_stmt_by_id(expr_id) + } + .stmt_assign { + return t.transform_stmt_to_flat(assign_stmt_from_cursor(c), mut out) + } + .stmt_comptime { + return t.transform_comptime_stmt_cursor_to_flat(c, mut out) + } + .stmt_expr { + return t.transform_expr_stmt_cursor_to_flat(c, mut out) + } + .stmt_return { + return t.transform_return_stmt_cursor_to_flat(c, mut out) + } + .stmt_for_in { + return t.transform_stmt_to_flat(for_in_stmt_from_cursor(c), mut out) + } + .stmt_fn_decl { + if flat_body_has_defer(c.list_at(3)) { + return t.transform_stmt_to_flat(c.fn_decl(), mut out) + } + return t.transform_fn_decl_streaming_to_flat(c, mut out) + } + .stmt_for { + init_c := c.edge(0) + if init_c.is_valid() && init_c.kind() == .stmt_for_in { + return t.transform_stmt_to_flat(for_stmt_from_cursor(c), mut out) + } + return t.transform_for_stmt_streaming_to_flat(c, mut out) + } + else { + panic('unexpected flat statement kind: ${c.kind()}') + } + } +} + // emit_fn_decl_flat assembles a stmt_fn_decl flat node from the immutable // header of `decl` plus the already-transformed (and flat-emitted) attribute // list and body stmt ids. Shared by the legacy `ast.FnDecl` arm of @@ -2207,9 +2487,9 @@ fn (mut t Transformer) transform_for_stmt_streaming_to_flat(c ast.Cursor, mut ou } // init/cond/post transform after the body (matches the struct-literal field // evaluation order in transform_for_stmt:701-706: init, cond, post). - init_id := out.emit_stmt(t.transform_stmt(c.edge(0).stmt())) - cond_id := out.emit_expr(t.transform_expr(cond)) - post_id := out.emit_stmt(t.transform_stmt(c.edge(2).stmt())) + init_id := t.transform_stmt_cursor_to_flat(c.edge(0), mut out) + cond_id := t.transform_expr_to_flat(cond, mut out) + post_id := t.transform_stmt_cursor_to_flat(c.edge(2), mut out) t.close_scope() return out.emit_for_stmt_by_ids(init_id, cond_id, post_id, body_ids) } @@ -2592,7 +2872,7 @@ pub fn (mut t Transformer) try_expand_or_expr_stmt_to_flat(stmt ast.ExprStmt, mu // site as the outer `pending_stmts` drain). // - Tuple destructuring branch (a, b := call()?): build temp ident + // decl_assign + per-arg selector assigns, all direct-emit. -// - Otherwise: build final AssignStmt via `transform_stmt` (so map +// - Otherwise: build final AssignStmt via `transform_stmt_to_flat` (so map // index lowering / string compound assignment fire), direct-emit // prefix_stmts then the final transformed assign. // @@ -2687,7 +2967,7 @@ pub fn (mut t Transformer) try_expand_or_expr_assign_stmts_to_flat(stmt &ast.Ass for ps in prefix_stmts { ids << out.emit_stmt(ps) } - ids << out.emit_stmt(t.transform_stmt(final_assign)) + ids << t.transform_stmt_to_flat(final_assign, mut out) return true } diff --git a/vlib/v2/transformer/monomorphize.v b/vlib/v2/transformer/monomorphize.v index 61e1754ae..28c94b45f 100644 --- a/vlib/v2/transformer/monomorphize.v +++ b/vlib/v2/transformer/monomorphize.v @@ -2733,39 +2733,6 @@ fn flat_file_struct_decls_with_extra(flat &ast.FlatAst, extra_stmts map[int][]as return decls } -fn flat_file_fn_decls_with_extra(flat &ast.FlatAst, extra_stmts map[int][]ast.Stmt, fi int) []ast.FnDecl { - if fi < 0 || fi >= flat.files.len { - return []ast.FnDecl{} - } - stmt_cursors := flat.file_cursor(fi).stmts() - extra := extra_stmts[fi] or { []ast.Stmt{} } - mut decls := []ast.FnDecl{cap: stmt_cursors.len() + extra.len} - for i in 0 .. stmt_cursors.len() { - stmt_c := stmt_cursors.at(i) - if stmt_c.kind() != .stmt_fn_decl { - continue - } - decls << stmt_c.fn_decl() - } - for stmt in extra { - if stmt is ast.FnDecl { - decls << stmt - } - } - return decls -} - -fn flat_file_stmts_with_extra(flat &ast.FlatAst, extra_stmts map[int][]ast.Stmt, fi int) []ast.Stmt { - if fi < 0 || fi >= flat.files.len { - return []ast.Stmt{} - } - stmt_cursors := flat.file_cursor(fi).stmts() - extra := extra_stmts[fi] or { []ast.Stmt{} } - mut stmts := stmt_cursors.stmts() - stmts << extra - return stmts -} - fn append_flat_extra_stmts(mut extra_stmts map[int][]ast.Stmt, fi int, stmts []ast.Stmt) { if stmts.len == 0 { return @@ -2982,7 +2949,7 @@ fn (mut t Transformer) collect_generic_call_specs_in_for_stmt_cursor(stmt ast.Cu t.collect_generic_call_specs_in_expr_cursor(stmt.edge(1)) t.collect_generic_call_specs_in_stmt_cursor(stmt.edge(2)) old_local_decl_types := t.local_decl_types.clone() - t.seed_generic_scan_for_in_var_types(for_in.stmt() as ast.ForInStmt) + t.seed_generic_scan_for_in_var_types_cursor(for_in) for i in 3 .. stmt.edge_count() { t.collect_generic_call_specs_in_stmt_cursor(stmt.edge(i)) } @@ -3001,6 +2968,18 @@ fn (mut t Transformer) collect_generic_call_specs_in_for_stmt(stmt ast.ForStmt, t.local_decl_types = old_local_decl_types.clone() } +fn (mut t Transformer) seed_generic_scan_for_in_var_types_cursor(for_in ast.Cursor) { + iter_type := t.for_in_iter_expr_type(for_in.edge(2).expr()) or { return } + value_name := t.get_var_name(for_in.edge(1).expr()) + if value_name != '' && value_name != '_' { + t.remember_local_decl_type(value_name, t.for_in_value_type(iter_type)) + } + key_name := t.get_var_name(for_in.edge(0).expr()) + if key_name != '' && key_name != '_' { + t.remember_local_decl_type(key_name, t.for_in_key_type_for_generic_scan(iter_type)) + } +} + fn (mut t Transformer) seed_generic_scan_for_in_var_types(for_in ast.ForInStmt) { iter_type := t.for_in_iter_expr_type(for_in.expr) or { return } value_name := t.get_var_name(for_in.value) diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index 0f4ab026f..eb1e44f54 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -3129,20 +3129,10 @@ fn (mut t Transformer) transform_files_from_flat_no_post_pass(flat &ast.FlatAst, return result } -// transform_files_to_flat is the transformer's flat-output entry point. -// -// Today it wraps transform_files_from_flat + ast.flatten_files: the -// internal pipeline still produces []ast.File and the conversion happens -// at the boundary. Externally this exposes the API shape the future -// "transformer writes directly into a FlatBuilder" port will keep — so -// callers can switch to this entry now and get the eventual peak-memory -// win without further changes. -// -// Callers that only need flat output should -// route through here. The returned []ast.File is kept alive only for the -// downstream consumers that still need legacy (SSA builder). Once the -// SSA builder consumes flat as well, this entry point will drop the -// legacy []ast.File entirely and the peak-memory win materializes. +// transform_files_to_flat is the legacy-compatible flat-output entry point. +// It wraps transform_files_from_flat + ast.flatten_files and returns both the +// FlatAst and transformed files for callers that still need []ast.File. Flat +// codegen paths should use transform_flat_to_flat_direct instead. pub fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) { result := t.transform_files_from_flat(flat, files) return ast.flatten_files(result), result @@ -3175,12 +3165,9 @@ pub fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []as // strings at small indices). Compare via per-file `subtree_signature` of // the stmts list (file root edge 2) to assert structural parity. // -// Memory: zero saving over `transform_files_to_flat` while the SSA -// builder still consumes the returned `[]ast.File` (the alloc lives until -// SSA migration). Value: once SSA migrates to flat, this entry can drop -// the `[]ast.File` return and the post-transform `[]ast.File` allocation -// disappears — first measurable peak-memory win. Until then this is the -// migration scaffolding, pinned by per-file subtree parity tests. +// Memory: this avoids the legacy post_pass + flatten_files boundary but still +// returns []ast.File for compatibility consumers. Flat-codegen backends bypass +// this with transform_flat_to_flat_direct or the parallel flat-direct path. pub fn (mut t Transformer) transform_files_to_flat_via_driver(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) { timing := os.getenv('V2_TTIME') != '' mut sw := time.new_stopwatch() -- 2.39.5