Skip to content

Fix hybrid-func-to-func cast crash when casting cross-module tuple to interface - #256

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-hybrid-func-interface-cast-crash
Jul 19, 2026
Merged

Fix hybrid-func-to-func cast crash when casting cross-module tuple to interface#256
ASDAlexander77 merged 1 commit into
mainfrom
fix-hybrid-func-interface-cast-crash

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

  • Fixes one of the two pre-existing crashes documented in PR Add captures-cast-to-interface test; document imported-object cast bugs #255's "Test matrix follow-up" (docs/interface-vtable-simplification-design.md): casting a cross-module, structurally-typed object VALUE to a method-bearing interface inside the importing module hit llvm_unreachable("review usage") at CastLogicHelper.h:765.
  • Root cause: castTypeScriptTypes handled narrowing a BoundFunctionType value to a plain FunctionType (the "losing this reference" vtable-slot path) but not the equivalent HybridFunctionType case — which is the actual type of a method field read off a @dllimport-reconstructed tuple (!ts.hybrid_func<, !ts.void, false>!ts.func<!ts.opaque, !ts.void, false>). The value fell through to castLLVMTypes' dead "value to ref of value" branch.
  • Fix: add the missing HybridFunctionType branch mirroring the BoundFunctionType one exactly — extract the raw function pointer via GetMethodOp, emit the same "losing this reference" warning (the interface vtable call re-supplies its own thisVal, so dropping the value's own receiver here is the established convention).

Important caveat — crash fixed, scenario not yet fully working

Verifying end-to-end (real -shared compile+link+run, WinDbg breakpoints on the actual c.inc() call site with live register/memory inspection) surfaced a separate pre-existing bug: the object clone built when casting the cross-module tuple to an interface writes its fields in reversed order (inc funcptr at offset 0, count at offset 8 — backwards from the declared struct<(f64, ptr)> layout the vtable's field offsets are computed against). So the compile-time crash is gone, but at runtime c.inc() silently corrupts the funcptr slot instead of incrementing count. That clone-path bug (likely MLIRGenCast.cpp's castObjectToInterface, behind the "Cloned object is used" warning) is documented in the design doc as its own follow-up investigation.

No regression test added for this fix alone — an end-to-end test would still fail on the clone bug, not the fixed crash. The design doc records both the fix and the newly-found blocker.

Test plan

  • Repro (export var counterObj: { count: number; inc(): void } = {...} + <A.Counter>A.counterObj in the importer, -shared mode) now compiles and links where it previously crashed the compiler.
  • Full ctest suite: 720/720 passed (debug build, -j8).

🤖 Generated with Claude Code

… interface

castTypeScriptTypes handled narrowing a BoundFunctionType value to a plain
FunctionType (the "losing this reference" vtable-slot path) but not the
equivalent HybridFunctionType case - the actual type of a method field read
off a cross-module reconstructed tuple. That value fell through to
castLLVMTypes' dead "value to ref of value" branch and hit
llvm_unreachable("review usage") at CastLogicHelper.h:765, crashing the
compiler on `<Counter>importedObj` in the importing module.

Add the missing branch, mirroring the BoundFunctionType case: extract the
raw function pointer via GetMethodOp and warn about the dropped receiver
(the interface vtable call re-supplies its own thisVal).

Note: this fixes the compile-time crash only. Verifying end-to-end surfaced
a separate pre-existing bug - the object clone built for the interface cast
writes its fields in reversed order, so the vtable's field offsets read the
wrong slots at runtime. Recorded in
docs/interface-vtable-simplification-design.md for a follow-up
investigation; no regression test added since the full scenario still fails
on that second bug.

Full ctest suite: 720/720 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit c6e7a54 into main Jul 19, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-hybrid-func-interface-cast-crash branch July 19, 2026 16:40
ASDAlexander77 added a commit that referenced this pull request Jul 19, 2026
When a tuple/object value can't be cast to an interface as-is (field types
need coercion), castTupleToInterface / castObjectToInterface build a clone
- and both took the clone's field list from InterfaceInfo::getTupleTypeFields,
which orders members methods-first. For Counter {count; inc()} the clone
became [inc@0, count@8], reversed from the source layout [count@0, inc@8]
that both the vtable's field-offset slots and any already-compiled method
bodies (in particular in the exporting module) address by byte offset. At
runtime c.count read the funcptr slot and inc() incremented the funcptr
instead of count - silent corruption, no crash.

Factor the clone field-list construction into getInterfaceCloneFields: keep
the SOURCE tuple's field order, take only the field types from the
interface (the coercion purpose of the clone), append interface-only
members after the source fields. All downstream member lookups are
name-based, so only the byte layout changes.

Together with #256 this makes casting a cross-module structurally-typed
object VALUE to a method-bearing interface inside the importer work
end-to-end for the first time. Add the regression test pair
export/import_object_literal_structural_typed.ts (asserts count 0 -> 1 -> 2
through the interface) in both compile-shared and jit-shared modes.

Full ctest suite: 722/722 passed (720 + 2 new).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ASDAlexander77 added a commit that referenced this pull request Jul 19, 2026
…spatch (#259)

* Extend test coverage for object-literal/interface casts and method dispatch

Adds four new test cases exercising variations not covered by the recent
interface-vtable-cast arc (#256-258), which only ever tested zero-argument,
single-method interfaces:

- 00object_annotated_method_params.ts: type-literal-annotated object
  methods with PARAMETERS (that arc's fix only had zero-arg method
  coverage), including a same-module chained sibling-method call.
- 00interface_object_array.ts: several distinct object literals (each its
  own per-type constant vtable) collected into a single interface-typed
  array and dispatched through the same call site in a loop - would catch
  a shared/aliased vtable or wrong-`this` bug that a single-object test
  can't.
- export/import_object_literal_structural_typed_params.ts: extends
  export/import_object_literal_structural_typed.ts's cross-module coverage
  along the "zero-arg -> parameterized method" axis.

Along the way, found and documented (not fixed) two new, real gaps in
docs/interface-vtable-simplification-design.md:
- a cross-module cast to a MULTI-method interface breaks for every vtable
  slot past 0 (wrong value at slot 1, crash at slot 2+) - every prior test
  in this arc only used single-method interfaces, so this was never
  exercised before. The committed cross-module test deliberately stays
  single-method to avoid committing a failing case.
- (same-module only, unrelated) a value-returning method can't `return
  this.siblingMethod(...)` within the same type-literal-annotated object
  literal - calling the sibling as a bare statement works fine. Avoided in
  the committed tests.

Full ctest suite: 730/730 passed (722 + 8 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update CMake configuration for Visual Studio 18 and adjust ROOT_PATH in typescript.cmake

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant