Skip to content

A -shared library's class is imported once, with the library's vtable - #391

Merged
ASDAlexander77 merged 1 commit into
mainfrom
shared-import-cycle-redeclare
Sep 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
shared-import-cycle-redeclare

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

This fixes two bugs in importing a class from a -shared library. I found them through the class import cycle (reference-path/import_cycle_class), whose -shared variant was left unregistered in #381. Neither bug needs a cycle.

1. The class was declared twice: redefinition of symbol named 'Base..new'.
A module whose export depends on a class of another module declares that class in its own __decls too. A derived class's base is the typical case, and that re-export is deliberate: a library holding only the derived module still has to describe the base. So an importer of a library holding both modules read the base twice. mlirGen(ClassLikeDeclaration) now skips a second, different declaration of an imported class that is already fully processed in this module. The new ClassInfo::processingDeclaration separates that case from the same declaration being generated again on a later pass.

2. The importer built the class's vtable in a different order from the library.
MLIRDeclarationPrinter::print(ClassInfo) printed all methods, then all accessors. The importer builds the vtable in the order it reads the members. So with get textContent() written before addText() in the source, the two slots were swapped, and the importer's n.addText("a") ran the getter: nothing was pushed, and reading childNodes[0] faulted. Each accessor half (get or set) is now printed where its function sits in classType->methods, which is the order the library built its vtable in. An accessor with no entry in the method list is still printed after it, as before.

Tests:

  • reference-path-import-cycle-class is now registered through tslang_add_import_tests, so it runs static, -shared and -jit -shared.
  • New shared-class-members/, all three tiers. A base class has an accessor before a method, and a setter between methods. A derived class in another module of the same library extends it. It hit both bugs before this change.

Results:

  • The suite passes locally, 3016/3016, and the MLIRGenTests unit tests (which include the declaration printer) pass 144/144.
  • I rebuilt DefaultLib (release, gc) with this compiler, and its tests pass 156/156 in both jit and compile modes. The local DefaultLib build was restored afterwards.

Not changed: with --export=all, a getter's local variables end up in __decls as @dllimport globals in a .f_Node.get_textContent namespace. The test runner doesn't pass that flag, and it's a separate issue.

🤖 Generated with Claude Code

Two bugs in importing a class from a -shared library, found through the
class import cycle whose -shared test was left disabled; neither needs a
cycle.

- A module whose export depends on a class of another module declares
  that class in its __decls too (a derived class's base), so an importer
  of a library holding both read it twice: "redefinition of symbol named
  'Base..new'". A second declaration of an imported class already
  processed in this module is now skipped (ClassInfo::processingDeclaration
  tells it from the same declaration generated again).
- The declaration printer wrote accessors after all methods. The importer
  builds the vtable in the order it reads the members, so with
  `get textContent()` written before `addText()` their slots were swapped
  and the importer's `addText` call ran the getter. Accessors are now
  printed where their functions sit among the methods.

Co-Authored-By: Claude Opus 5.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