Skip to content

A -shared library's class can hold a specialization of its own generic - #390

Merged
ASDAlexander77 merged 2 commits into
mainfrom
shared-generic-class-import
Sep 28, 2026
Merged

ASDAlexander77 merged 2 commits into
mainfrom
shared-generic-class-import

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Suppose a -shared library exports a class with a field typed by a specialization of a generic from the same module:

export class Box<T> { items: T[] = []; /* ... */ }
export class Tree { name: string; children: Box<Tree>; /* ... */ }

Any importer of that library failed with generic type Box can't be found. This was the -shared gap left open in #380, whose test was registered static-only.

The library's __decls showed the causes:

class Tree { children: Box<!ts.class<@Tree, !ts.class_storage<@Tree>>>; ... }
@dllimport
class Box<!ts.class<@Tree, !ts.class_storage<@Tree>>> { ... }

Fix, in two parts:

  1. A specialization is no longer declared on its own, and it is written as Box<Tree>. addClassDeclarationToExport recognizes a specialization (getClassSpecializationOf). Instead of printing it, it exports the generic and the type arguments. The generic is exported only when it is this module's own, and then even when it isn't marked export. The importer builds Box<Tree> from the generic, as a use in its own source would. So the declaration printer can name a specialization the way it is written, MLIRPrinter gains an optional getClassSpecialization hook, supplied by MLIRGen, which has the generic registry. Diagnostics leave it unset. All ten MLIRDeclarationPrinter sites pass it.
  2. Generics are imported first. The library's symbols come back sorted, and __decls_<module> sorts before __decls_generic_<module>. So Tree's field was read before Box existed. mlirGenImportSharedLib now puts the generic declarations first. Registering a generic only records it, so a generic that names a class declared later still works.

The library's __decls are now:

@dllimport
class Tree { name: string; children: Box<Tree>; constructor(p1 : string) }

and the generic Box<T> is in __decls_generic_box_module_….

Tests: import-specialization is now registered through tslang_add_import_tests, so it runs static, -shared and -jit -shared.

Second commit (Linux CI): a specialization's name has no @. On Linux both new -shared tests failed to link the library:

/usr/bin/ld: libbox_module.so: version node not found for symbol Box<!ts.class<@Tree, !ts.class_storage<@Tree>>>..vtbl

A specialization's name embeds its type arguments as MLIR prints them, and MLIR writes a class as a symbol reference, @Tree. In an ELF symbol name, @ separates the symbol version, so ld can't export such a name. Windows accepts it.

appendSpecializedTypeNames (both overloads) now goes through appendTypeToSymbolName:

  • The @ before a symbol reference is left out, which gives Box<!ts.class<Tree, !ts.class_storage<Tree>>>.
  • An @ inside a quoted literal type is written as MLIR's escape \40, so two different literals can't end up with the same name.

These two overloads are the only places a specialization name is built, so the IR, the exported symbols and every SearchForAddressOfSymbol string stay consistent.

ABI note: every specialization with a class or interface type argument changes its symbol name on Windows as well. A prebuilt DefaultLib must be rebuilt with this compiler.

Results (after rebasing onto main, with #389/#391/#392):

  • Windows suite: 3038/3038.
  • Linux suite (WSL): 3025/3025, including test-compile-shared-import-specialization and test-jit-shared-import-specialization.
  • 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.

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits September 28, 2026 15:47
An importer of a -shared library whose exported class has a field typed
by a specialization of a generic from the same module (`children:
Box<Tree>`) failed: "generic type Box can't be found". Two things:

- The specialization was declared on its own in __decls, as
  `class Box<!ts.class<@Tree, ...>>`, and fields named it that way; neither
  parses. It is no longer declared: the importer makes it from the generic,
  as a use in its own source would. The declaration printer names it
  `Box<Tree>` (MLIRPrinter::getClassSpecialization, supplied by MLIRGen,
  which has the generic registry), and the generic - this module's own,
  even when not marked `export` - and the type arguments are exported in
  its place.
- __decls_generic_<module> sorted after __decls_<module>, so the field was
  read before the generic existed. The importer loads generics first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class type argument printed as a symbol reference, so a
specialization was named Box<!ts.class<@Tree, ...>>, and so were its
symbols. In an ELF symbol name `@` separates the version, and ld failed
to link a -shared library that exports one ("version node not found for
symbol Box<...>..vtbl"). The `@` before a symbol reference is now left
out of the name, and one inside a quoted literal type is written as
MLIR's escape \40. Windows linked such names; they change there too, so
a prebuilt DefaultLib must be rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 force-pushed the shared-generic-class-import branch from 83d5e03 to 3cbff1e Compare September 28, 2026 15:06
@ASDAlexander77
ASDAlexander77 merged commit d30cd53 into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the shared-generic-class-import branch September 28, 2026 16:30
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