Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 320 additions & 0 deletions tslang/docs/cross-module-dynamic-import-instanceof-design.md

Large diffs are not rendered by default.

19 changes: 17 additions & 2 deletions tslang/lib/TypeScript/DeclarationPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,12 @@ namespace typescript
auto any = false;
for (auto baseClass : classType->baseClasses)
{
if (any)
if (any)
{
os << ", ";
}

os << classType->fullName;
os << baseClass->fullName;
any = true;
}
}
Expand Down Expand Up @@ -426,6 +426,21 @@ namespace typescript
if (filterField(field.id))
continue;

// a derived class's storage embeds each base class's storage as a synthetic
// first field whose id is the base's full name (mlirGenClassHeritageClause);
// that is memory layout, not a source member - printing it would make the
// importer parse it as a real extra field, shifting every subsequent field's
// offset and silently corrupting cross-module field access (the `extends`
// clause printed above already carries the inheritance).
if (auto strId = dyn_cast<mlir::StringAttr>(field.id))
{
if (llvm::any_of(classType->baseClasses,
[&](auto &baseClass) { return strId.getValue() == baseClass->fullName; }))
{
continue;
}
}

os.indent(4);

if (field.accessLevel == mlir_ts::AccessLevel::Protected)
Expand Down
70 changes: 62 additions & 8 deletions tslang/lib/TypeScript/MLIRGenClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1734,26 +1734,80 @@ genContext);
}
else
{
// The vtable is extends-recursive, so a derived class's vtable can
// contain slots whose symbols (inherited virtual methods, and - with
// ADD_STATIC_MEMBERS_TO_VTABLE - inherited static fields like the
// RTTI `.rtti`/`.size`) are owned by a base class that lives in a
// dynamically imported module. Those cannot be constant SymbolRefOp
// references: with no import library, the address is not a link-time
// constant. Resolve them at runtime instead (SearchForAddressOfSymbolOp
// + cast) - GlobalOpLowering already routes any initializer containing
// a SearchForAddressOfSymbolOp through the __cctor global-constructor
// path, and the module-load ctor is emitted before all per-symbol
// ctors, so the DLL is loaded by the time this resolves.
auto isOwnedByDynamicImport = [&](mlir::StringRef symbolName, bool isStaticField) {
std::function<ClassInfo::TypePtr(ClassInfo::TypePtr)> findOwner =
[&](ClassInfo::TypePtr cls) -> ClassInfo::TypePtr {
if (isStaticField
? llvm::any_of(cls->staticFields, [&](auto &f) { return f.globalVariableName == symbolName; })
: llvm::any_of(cls->methods, [&](auto &m) { return m.funcName == symbolName; }))
{
return cls;
}

for (auto &base : cls->baseClasses)
{
if (auto owner = findOwner(base))
{
return owner;
}
}

return ClassInfo::TypePtr();
};

auto owner = findOwner(newClassPtr);
return owner && owner->isDynamicImport;
};

mlir::Value methodOrFieldNameRef;
mlir::StringRef symbolName;
mlir::Type slotType;
if (!vtRecord.isStaticField)
{
if (vtRecord.methodInfo.isAbstract)
{
emitError(location) << "Abstract method '" << vtRecord.methodInfo.name << "' is not implemented in '" << newClassPtr->name << "'";
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

methodOrFieldNameRef = builder.create<mlir_ts::SymbolRefOp>(
location, vtRecord.methodInfo.funcType,
mlir::FlatSymbolRefAttr::get(builder.getContext(),
vtRecord.methodInfo.funcName));
symbolName = vtRecord.methodInfo.funcName;
slotType = vtRecord.methodInfo.funcType;
}
else
{
symbolName = vtRecord.staticFieldInfo.globalVariableName;
slotType = mlir_ts::RefType::get(vtRecord.staticFieldInfo.type);
}

if (isOwnedByDynamicImport(symbolName, vtRecord.isStaticField))
{
auto symbolNameValue = V(mlirGenStringValue(location, symbolName.str(), true));
auto referenceToSymbolOpaque = builder.create<mlir_ts::SearchForAddressOfSymbolOp>(
location, getOpaqueType(), symbolNameValue);
auto castResult = cast(location, slotType, referenceToSymbolOpaque, genContext);
if (castResult.failed_or_no_value())
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

methodOrFieldNameRef = V(castResult);
}
else
{
methodOrFieldNameRef = builder.create<mlir_ts::SymbolRefOp>(
location, mlir_ts::RefType::get(vtRecord.staticFieldInfo.type),
mlir::FlatSymbolRefAttr::get(builder.getContext(),
vtRecord.staticFieldInfo.globalVariableName));
location, slotType,
mlir::FlatSymbolRefAttr::get(builder.getContext(), symbolName));
}

vtableValue = builder.create<mlir_ts::InsertPropertyOp>(
Expand Down
64 changes: 46 additions & 18 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5446,32 +5446,60 @@ class MLIRGenImpl

if (classInfo->isDynamicImport)
{
// need to resolve global variable
// Direct (non-virtual) access to a dynamic-import class member - e.g. a
// cross-module `super.method(...)` call, or a non-virtual inherited method.
// Resolution order:
//
// Not every method of an isDynamicImport class is actually
// registered as a dlsym-style global variable - a
// compiler-synthesized method (e.g. .instanceOf, ForceVirtual,
// see mlirGenClassInstanceOfMethod) never carries its own
// @dllimport decorator (that's only ever attached to
// source-declared methods reprinted under `@dllimport class
// ... { ... }`), so mlirGenFunctionLikeDeclaration's decorator
// check never routes it through
// mlirGenFunctionLikeDeclarationDynamicImport - it gets a real
// (bodyless-for-a-declaration) FuncOp registered directly
// instead, just like a same-module method. Try that first.
// 1. A FuncOp WITH a body: the method is actually defined in this module
// (compiler-synthesized methods like .instanceOf get real FuncOps even
// for isDynamicImport classes - see mlirGenClassInstanceOfMethod). Only
// a defined body qualifies: a bodyless declaration FuncOp would lower
// to a plain external symbol reference, which the dynamic import mode
// (-shared without an import .lib) cannot link.
if (auto funcOp = theModule.lookupSymbol<mlir_ts::FuncOp>(funcName))
{
auto thisSymbOp = builder.create<mlir_ts::ThisSymbolRefOp>(
location, getBoundFunctionType(effectiveFuncType), effectiveThisValue,
mlir::FlatSymbolRefAttr::get(builder.getContext(), funcName));
return thisSymbOp;
if (!funcOp.getBody().empty())
{
auto thisSymbOp = builder.create<mlir_ts::ThisSymbolRefOp>(
location, getBoundFunctionType(effectiveFuncType), effectiveThisValue,
mlir::FlatSymbolRefAttr::get(builder.getContext(), funcName));
return thisSymbOp;
}
}

// 2. The dlsym-style global variable mlirGenClassMethodMemberDynamicImport /
// mlirGenFunctionLikeDeclarationDynamicImport registered for @dllimport
// members (statics/constructors/.new today).
auto globalFuncVar = resolveFullNameIdentifier(location, funcName, false, genContext);
if (!globalFuncVar)
{
emitError(location, "Class member '") << funcName << "' can't be resolved (dynamic import)";
return mlir::Value();
// 3. Inline dlsym. Compiler-synthesized methods (.instanceOf) and plain
// instance methods of an imported class have neither of the above: no
// per-member @dllimport decorator ever routes them through the
// registration path, and their FuncOp (when one exists at all) is a
// bodyless declaration. Registering a global lazily from HERE is not
// an option either - this can run inside a transient discovery scope
// ("simulate scope"), where a fullNameGlobalsMap registration is torn
// down with the scope, or worse trips its LIFO assert; see
// docs/cross-module-dynamic-import-instanceof-design.md §5-§7 for the
// two reverted attempts. So resolve the symbol in place, exactly like
// the registered variant's initializer does
// (mlirGenFunctionLikeDeclarationDynamicImport): the DLL is already
// loaded by the import's LoadLibraryPermanentlyOp global ctor by the
// time any method body runs. Self-contained: no global state, valid
// in both discovery (ops land in the throwaway module) and real
// passes, at the cost of one symbol lookup per call site.
auto symbolNameValue = V(mlirGenStringValue(location, funcName.str(), true));
auto referenceToFuncOpaque = builder.create<mlir_ts::SearchForAddressOfSymbolOp>(
location, getOpaqueType(), symbolNameValue);
auto castResult = cast(location, effectiveFuncType, referenceToFuncOpaque, genContext);
if (castResult.failed_or_no_value())
{
emitError(location, "Class member '") << funcName << "' can't be resolved (dynamic import)";
return mlir::Value();
}

globalFuncVar = V(castResult);
}

CAST_A(opaqueThisValue, location, getOpaqueType(), effectiveThisValue, genContext);
Expand Down
Loading
Loading