Skip to content

Commit d9eea66

Browse files
Remove redundant bound-method-field check in GlobalOpLowering (#250)
The #246 isBoundMethodTupleConstant special case in GlobalOpLowering decided whether a global's initializer needs the constructor path based on whether any ConstantOp in it had a bound-method-shaped tuple field. Since PR #249 boxes every method-bearing object literal, such a literal's initializer always contains a NewOp (the boxing recipe), which the walk's existing NewOp case already forces onto the constructor path -- making the bound-method check redundant. Verified via a diagnostic probe at that call site across every test file (zero hits) before removing it for real. Investigated but did NOT remove needsIdentityStorage/boundRefMaterializedCache: contrary to the original plan (docs/object-literal-boxing-design.md), these are still load-bearing. Whenever a boxed literal is unboxed back into a plain tuple to match a declared/inferred type (annotated const, parameter, interface-cast fallback, generic intersection/inference), the resulting tuple's method field is still bound-method-shaped even though the tuple itself is a plain value -- that machinery is what makes calling through it work correctly instead of crash. Root-caused via site-tagged diagnostic probes across the full test corpus; documented in the design doc so this isn't re-attempted without re-deriving the same finding. Full suite green: 353/353 JIT + 357/357 AOT. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 9f0d1a6 commit d9eea66

2 files changed

Lines changed: 38 additions & 34 deletions

File tree

tslang/docs/object-literal-boxing-design.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Object literals with methods as reference types (`ObjectType`): design
22

3-
Status: **PR A merged (#248, main@9a7bd71e); PR B implemented, full suite
4-
green (353/353 JIT + 357/357 AOT)** — generalizes the generator-wrapper
3+
Status: **PR A merged (#248, main@9a7bd71e); PR B merged (#249,
4+
main@9f0d1a6a); PR C implemented (narrower than planned — see §5), full
5+
suite green (353/353 JIT + 357/357 AOT)** — generalizes the generator-wrapper
56
boxing of PR #245 (`docs/generator-object-wrapper-design.md`) from "literals
67
carrying the `BoxAsObject` flag" to **every object literal that has at least
78
one method or accessor**. Pure-data literals (`{ x: 1, y: 2 }`) deliberately
@@ -191,13 +192,37 @@ Things that consume the literal's *value type* and must tolerate
191192
object, accessor mutating `this`, global `const` literal with mutating
192193
method (generalizes the #246 repro), conditional-expression merge of two
193194
same-shape literals. Full suite green: 353/353 JIT + 357/357 AOT.
194-
3. **PR C — cleanup** (only after B is green on main): remove
195-
`needsIdentityStorage` (`MLIRGenImpl.h:788-794`,
196-
`MLIRGenVariables.cpp:102-113`), `boundRefMaterializedCache`
197-
(`MLIRGenImpl.h:10797+`), the #246 `isBoundMethodTupleConstant` check in
198-
`GlobalOpLowering` (`LowerToLLVM.cpp:3696-3731`), and stale comments —
199-
each removal proven dead by the flip, verified by full suite. Keeping
200-
them through B preserves bisectability.
195+
3. **PR C — cleanup, narrower than originally planned** (implemented): the
196+
assumption that `needsIdentityStorage` and `boundRefMaterializedCache`
197+
become fully dead after the flip was **wrong** — verified empirically
198+
(not by reasoning) via temporary `report_fatal_error` probes at
199+
`hasBoundMethodField` and both `isBoundRef` call sites
200+
(`MLIRCodeLogic.h`'s `Tuple()`/`TupleNoError()`), full suite rerun: 8 of
201+
10 originally-regressed-then-fixed tests plus 2 more (`00question_question`,
202+
`00type_aliases_in_generics`) still hit them. Root cause: the **annotated
203+
tuple-type boundary** noted below in §6 is not just a documented
204+
value-semantics limitation — the code path that makes it *work correctly*
205+
(rather than crash) is exactly `isBoundReference`/`boundRefMaterializedCache`
206+
in `Tuple()`. Whenever a boxed `ObjectType` literal is unboxed back into a
207+
plain tuple to match a declared/inferred type (an annotated `const`, a
208+
function parameter, an interface-cast fallback, a generic
209+
intersection/parameter-inference result — anywhere PR A's gap-3 unboxing
210+
cast or an equivalent fires), the resulting tuple's method field is still
211+
*shaped* like a bound method (first input `ObjectType`) even though the
212+
tuple itself is now a plain value. `Tuple()`'s `isBoundRef` branch is what
213+
correctly materializes `this` for a call through that field. **Kept as
214+
permanent, still-necessary machinery, not scheduled for removal.**
215+
216+
The one piece confirmed genuinely dead: the #246
217+
`isBoundMethodTupleConstant` check in `GlobalOpLowering`
218+
(`LowerToLLVM.cpp`, formerly ~3696-3731) — removed. Every boxed literal's
219+
initializer region unconditionally contains a `NewOp` (the boxing
220+
recipe), which the walk's existing `isa<mlir_ts::NewOp>(op)` case already
221+
forces onto the global-constructor path; the bound-method-field check
222+
never changed the outcome. Verified by running every test file directly
223+
through the JIT with a diagnostic (non-fatal) probe at that specific call
224+
site — zero hits across the full ~369-file test corpus — before removing
225+
it for real and reconfirming 353/353 JIT + 357/357 AOT green.
201226

202227
## 6. Risks / open items
203228

@@ -206,7 +231,10 @@ Things that consume the literal's *value type* and must tolerate
206231
tuple at the annotation boundary, losing aliasing for that binding. The
207232
eventual fix is in *type resolution* (map method-bearing type literals to
208233
`ObjectType`), which is a bigger, separate change — out of scope; document
209-
the limitation in the test.
234+
the limitation in the test. **This is also why `needsIdentityStorage` /
235+
`boundRefMaterializedCache` can't be removed (§5 PR C)**: the unboxed
236+
tuple's method field still has a bound-method *shape*, and that machinery
237+
is what makes calling through it work correctly rather than crash.
210238
- **Function-expression / arrow fields** (`{ f: () => ... }`,
211239
`{ f: function () {...} }`) are plain fields, not `methodInfos` entries —
212240
they do not trigger boxing. Matches the "methods only" rule; TS's

tslang/lib/TypeScript/LowerToLLVM.cpp

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3681,23 +3681,6 @@ struct GlobalOpLowering : public TsLlvmPattern<mlir_ts::GlobalOp>
36813681

36823682
LLVMCodeHelper lch(globalOp, rewriter, getTypeConverter(), tsLlvmContext->compileOptions);
36833683

3684-
// A ConstantOp whose *TupleType/ConstTupleType result* has a bound-method
3685-
// field (e.g. a plain `const obj = { ..., method() {} }` at global scope)
3686-
// lowers its method field via LLVM::AddressOfOp + LLVM::InsertValueOp (see
3687-
// LLVMCodeHelper::getTupleFromArrayAttr) -- not valid inside a static LLVM
3688-
// global initializer region, only inside real code (a function), so such a
3689-
// global must go through the constructor path below. Deliberately narrower
3690-
// than "any ConstantOp whose attribute contains a FlatSymbolRefAttr": that
3691-
// broader check also caught MSVC RTTI/EH type-descriptor globals (e.g.
3692-
// `??_R0PEAD@8`, which reference the `type_info` vtable symbol) that are
3693-
// NOT mlir_ts tuple types and were already lowering correctly as static
3694-
// data -- forcing those through the constructor path broke JIT symbol
3695-
// materialization for the whole module.
3696-
MLIRTypeHelper mth(rewriter.getContext(), tsLlvmContext->compileOptions);
3697-
auto isBoundMethodTupleConstant = [&](mlir_ts::ConstantOp constantOp) {
3698-
return mth.hasBoundMethodField(constantOp.getType());
3699-
};
3700-
37013684
auto createAsGlobalConstructor = false;
37023685
// TODO: we need to write correct attributes to Ops and detect which ops should be in GlobalConstructor
37033686
auto visitorAllOps = [&](Operation *op) {
@@ -3722,13 +3705,6 @@ struct GlobalOpLowering : public TsLlvmPattern<mlir_ts::GlobalOp>
37223705
createAsGlobalConstructor = true;
37233706
}
37243707
}
3725-
else if (auto constantOp = dyn_cast<mlir_ts::ConstantOp>(op))
3726-
{
3727-
if (isBoundMethodTupleConstant(constantOp))
3728-
{
3729-
createAsGlobalConstructor = true;
3730-
}
3731-
}
37323708

37333709
// TODO: error in try-catch
37343710
// if it has memory side effect - can't be in global init.

0 commit comments

Comments
 (0)