Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use IO.Std; | |
| 3 | ||
| 4 | use Logging; | |
| 5 | ||
| 6 | use Semantic.Symbols.LOCAL_VARIABLE; | |
| 7 | ||
| 8 | // Pre-`compile-expressions` analysis pass: walks the AST, | |
| 9 | // resolves every IDENTIFIER reference and ASSIGNMENT LHS, | |
| 10 | // marks each `LOCAL_VARIABLE` it sees as captured (if | |
| 11 | // referenced from inside an enclosing function — i.e. from | |
| 12 | // a closure body) and/or reassigned (if it appears on the | |
| 13 | // LHS of an assignment). At end of apply, sweeps the set of | |
| 14 | // touched locals and sets `is_boxed = is_captured /\ | |
| 15 | // is_reassigned`, so that subsequent `symbol_loader`-driven | |
| 16 | // IR emission switches to `Ghul.BOX[T]`-backed storage. | |
| 17 | // | |
| 18 | // Runs only on IL-bound builds: boxing is a code-generation | |
| 19 | // strategy, and assignability of a captured local is keyed | |
| 20 | // on its mut marker alone, so analysis-only paths never need | |
| 21 | // the flags this pass derives. | |
| 22 | class MARK_BOXED_LOCALS: ScopedVisitor is | |
| 23 | _logger: Logger; | |
| 24 | _touched: Collections.SET[LOCAL_VARIABLE]; | |
| 25 | ||
| 26 | init( | |
| 27 | logger: Logger, | |
| 28 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 29 | namespaces: Semantic.NAMESPACES | |
| 30 | ) | |
| 31 | is | |
| 32 | super.init(logger, symbol_table, namespaces); | |
| 33 | ||
| 34 | _logger = logger; | |
| 35 | _touched = Collections.SET[LOCAL_VARIABLE](); | |
| 36 | si | |
| 37 | ||
| 38 | apply(root: Trees.Node) is | |
| 39 | _touched.clear(); | |
| 40 | ||
| 41 | root.walk(self); | |
| 42 | ||
| 43 | // Sweep: a local is boxed iff (a) it was declared | |
| 44 | // with the `mut` modifier, (b) it's captured from | |
| 45 | // inside an enclosing closure body, and (c) it's | |
| 46 | // assigned anywhere. Plain captured immutable | |
| 47 | // locals stay value-captured (and any rogue | |
| 48 | // assignment hits the captured-assignment diagnostic | |
| 49 | // upstream). Plain mutable-but-not-captured locals | |
| 50 | // stay on the stack. | |
| 51 | for local in _touched do | |
| 52 | if | |
| 53 | local.is_mutable_marked /\ | |
| 54 | local.is_captured /\ | |
| 55 | local.is_reassigned | |
| 56 | then | |
| 57 | local.is_boxed = true; | |
| 58 | fi | |
| 59 | od | |
| 60 | ||
| 61 | _touched.clear(); | |
| 62 | si | |
| 63 | ||
| 64 | // Identifier-as-expression reference. If it resolves to | |
| 65 | // a LOCAL_VARIABLE owned by a function outside our | |
| 66 | // current function context, this is a capture. | |
| 67 | visit(identifier: Trees.Expressions.IDENTIFIER) is | |
| 68 | ||
| 69 | let symbol = try_find(identifier.identifier); | |
| 70 | ||
| 71 | if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then | |
| 72 | return; | |
| 73 | fi | |
| 74 | ||
| 75 | let local = cast LOCAL_VARIABLE(symbol); | |
| 76 | let function = current_function; | |
| 77 | ||
| 78 | if !function? then | |
| 79 | return; | |
| 80 | fi | |
| 81 | ||
| 82 | _touched.add(local); | |
| 83 | ||
| 84 | if local.owner != function then | |
| 85 | local.is_captured = true; | |
| 86 | fi | |
| 87 | si | |
| 88 | ||
| 89 | // Reassignment of a local: LHS resolves to a | |
| 90 | // LOCAL_VARIABLE. Field assignments (`obj.field = v`) | |
| 91 | // and the like get filtered by the LOCAL_VARIABLE cast. | |
| 92 | visit(assignment: Trees.Statements.ASSIGNMENT) is | |
| 93 | let targets = Collections.LIST[Trees.Expressions.Expression](); | |
| 94 | assignment.left.get_names_into(targets); | |
| 95 | ||
| 96 | for target in targets do | |
| 97 | if !isa Trees.Expressions.IDENTIFIER(target) then | |
| 98 | continue; | |
| 99 | fi | |
| 100 | ||
| 101 | let symbol = try_find(target.identifier); | |
| 102 | ||
| 103 | if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then | |
| 104 | continue; | |
| 105 | fi | |
| 106 | ||
| 107 | _touched.add(symbol); | |
| 108 | symbol.is_reassigned = true; | |
| 109 | od | |
| 110 | si | |
| 111 | si | |
| 112 | si |