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
27 changes: 25 additions & 2 deletions tslang/lib/TypeScript/MLIRGenFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,32 @@ namespace mlirgen
funcProto->setHasExtraFields(existLocalVarsInThisContextMap(funcProto->getName()));
}

// a concrete instantiation of a generic function (e.g. identity<number>) reprocesses
// the SAME AST node as the bare template (`export function identity<T>(...)`), so
// processFunctionAttributes's own getExportModifier check below would otherwise mark
// this LOCAL, per-instantiation specialization as exported too - wrong for the same
// two reasons the class-generic fix (class-generic-declaration-export-fix) suppresses
// isExport on a specialized ClassInfo: (1) a specialization materialized by whichever
// module instantiates it is local to that module, not a re-export of it (the bare
// template's own export is handled separately via registerGenericFunctionLike /
// addGenericFunctionDeclarationToExport); (2) a multi-type-param instantiation's
// mangled name contains a raw comma (e.g. M.pair<!ts.number,!ts.string>), a
// metacharacter in the linker's `/EXPORT:name[,option]` directive syntax - lld rejects
// it outright ("invalid /export:"). NOTE: can't key this off
// genContext.instantiateSpecializedFunction - mlirGenFunctionLikeDeclaration
// deliberately clears that flag on funcDeclGenContext before calling
// mlirGenFunctionPrototype (to stop nested generics being instantiated by mistake),
// so by the time we get here it always reads false. typeParamsWithArgs is the
// signal that actually survives - same one the class fix uses. Must be passed INTO
// processFunctionAttributes (not applied to its return value afterward) because it
// pushes the "export" attribute into `attrs` itself before returning.
auto suppressExportForGenericInstantiation =
functionLikeDeclarationBaseAST->typeParameters.size() > 0 && !genContext.typeParamsWithArgs.empty();

SmallVector<mlir::NamedAttribute> attrs;
auto dllExport = processFunctionAttributes(location, fullName, functionLikeDeclarationBaseAST, attrs, funcProtoGenContext);

auto dllExport = processFunctionAttributes(location, fullName, functionLikeDeclarationBaseAST, attrs, funcProtoGenContext,
suppressExportForGenericInstantiation);

if (funcType)
{
auto it = getCaptureVarsMap().find(funcProto->getName());
Expand Down
77 changes: 74 additions & 3 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1778,7 +1778,8 @@ class MLIRGenImpl
}

bool processFunctionAttributes(mlir::Location location, StringRef fullName,
FunctionLikeDeclarationBase functionLikeDeclarationBaseAST, SmallVector<mlir::NamedAttribute> &attrs, const GenContext &genContext)
FunctionLikeDeclarationBase functionLikeDeclarationBaseAST, SmallVector<mlir::NamedAttribute> &attrs, const GenContext &genContext,
bool suppressExport = false)
{
#ifdef ADD_GC_ATTRIBUTE
attrs.push_back({builder.getIdentifier(TS_GC_ATTRIBUTE), mlir::UnitAttr::get(builder.getContext())});
Expand All @@ -1802,8 +1803,9 @@ class MLIRGenImpl
});

// add modifiers
auto dllExport = getExportModifier(functionLikeDeclarationBaseAST)
|| ((functionLikeDeclarationBaseAST->internalFlags & InternalFlags::DllExport) == InternalFlags::DllExport);
auto dllExport = !suppressExport
&& (getExportModifier(functionLikeDeclarationBaseAST)
|| ((functionLikeDeclarationBaseAST->internalFlags & InternalFlags::DllExport) == InternalFlags::DllExport));
if (dllExport)
{
attrs.push_back({mlir::StringAttr::get(builder.getContext(), "export"), mlir::UnitAttr::get(builder.getContext())});
Expand Down Expand Up @@ -1861,6 +1863,17 @@ class MLIRGenImpl

if (existGenericFunctionMap(name))
{
// already registered - but the registration itself typically happens during
// Stages::Discovering (before addGenericFunctionDeclarationToExport's
// isAddedToExport gate, which only actually emits once stage ==
// Stages::SourceGeneration, will do anything) - retry the export step alone
// using the existing GenericFunctionInfo rather than skipping it entirely.
// Mirrors registerGenericClass's identical gotcha-3 fix.
if (getExportModifier(functionLikeDeclarationBaseAST))
{
addGenericFunctionDeclarationToExport(lookupGenericFunctionMap(name));
}

return {mlir::success(), name};
}

Expand Down Expand Up @@ -1909,6 +1922,18 @@ class MLIRGenImpl
getGenericFunctionMap().insert({namePtr, newGenericFunctionPtr});
fullNameGenericFunctionsMap.insert(fullNamePtr, newGenericFunctionPtr);

// support dynamic loading: a generic function is never instantiated in this
// module if nothing here uses it concretely, so mlirGenFunctionPrototype's own
// addFunctionDeclarationToExport call never runs for the bare template (it only
// fires for a SPECIALIZED instantiation) - the bare template needs to be
// exported here instead, the one place every generic function declaration
// passes through regardless of whether it is ever instantiated locally. Mirrors
// registerGenericClass.
if (getExportModifier(functionLikeDeclarationBaseAST))
{
addGenericFunctionDeclarationToExport(newGenericFunctionPtr);
}

return {mlir::success(), name};
}

Expand Down Expand Up @@ -10322,6 +10347,52 @@ class MLIRGenImpl
declExports << ss.str().str();
}

void addGenericFunctionDeclarationToExport(GenericFunctionInfo::TypePtr genericFunctionInfo)
{
// funcType can be null when registered with ignoreFunctionArgsDetection=true
// (see registerGenericFunctionLike) - nothing to key isAddedToExport's dedup on
// in that case, so just fall through and re-print (matches
// addFunctionDeclarationToExport's own "TODO: add distinct declaration" gap for
// the non-generic case).
if (genericFunctionInfo->funcType && isAddedToExport(genericFunctionInfo->funcType))
{
// already added
return;
}

// same bounds-check as addGenericClassDeclarationToExport - a generic function
// re-declared while re-importing another module's embedded declarations still
// carries its own `export` keyword verbatim, but at that point `sourceFile` is
// the ambient/outer file, not the "partial" buffer parsePartialStatements
// actually parsed this declaration from.
auto declEnd = static_cast<size_t>(genericFunctionInfo->functionDeclaration->_end);
if (declEnd > genericFunctionInfo->sourceFile->text.length())
{
return;
}

if (genericFunctionInfo->funcType)
{
exportedTypes.insert(genericFunctionInfo->funcType);
}

// like a generic class, a generic function has no compiled body for any given
// instantiation: each importing module instantiates it locally, on demand,
// exactly like a same-file usage would. So the FULL original source - type
// parameters and body intact, no @dllimport marker - must be re-exported
// verbatim for parsePartialStatements to recompile per instantiation in the
// importer.
auto declText = convertWideToUTF8(getTextOfNodeFromSourceText(
genericFunctionInfo->sourceFile->text, genericFunctionInfo->functionDeclaration.as<Node>(), true));

SmallVector<char> out;
llvm::raw_svector_ostream ss(out);
MLIRDeclarationPrinter dp(ss);
dp.printGenericClass(genericFunctionInfo->elementNamespace, declText);

genericDeclExports << ss.str().str();
}

void addGenericClassDeclarationToExport(GenericClassInfo::TypePtr genericClassInfo)
{
if (isAddedToExport(genericClassInfo->classType))
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ add_test(NAME test-compile-export-import-class-static COMMAND test-runner "${PRO
# declaration's own re-export attempt hit a stale source-file/AST-position mismatch).
# See class-generic-declaration-export-fix memory for the full account.
add_test(NAME test-compile-export-import-class-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts")
add_test(NAME test-compile-export-import-function-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts")
add_test(NAME test-compile-export-import-class-accessor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_accessor.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_accessor.ts")
add_test(NAME test-compile-export-import-class-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_indexer.ts")
add_test(NAME test-compile-export-import-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_indexer.ts")
Expand Down Expand Up @@ -955,6 +956,7 @@ add_test(NAME test-compile-shared-export-import-class-structural-interface COMMA
# add_test(NAME test-compile-shared-export-import-class-implements-interface-abstract COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_abstract.ts")
# FIXED: see the matching test-compile-export-import-class-generic comment above (2026-07-22).
add_test(NAME test-compile-shared-export-import-class-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts")
add_test(NAME test-compile-shared-export-import-function-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts")
# FIXED (2026-07-22): the -shared declaration-reconstruction path never printed real
# get/set syntax for a class's accessors, only the underlying get_x/set_x funcOps as
# ordinary methods - so property-style access ("Class member 'celsius' can't be found")
Expand Down Expand Up @@ -1001,6 +1003,7 @@ add_test(NAME test-jit-shared-export-import-class-structural-interface COMMAND t
# add_test(NAME test-jit-shared-export-import-class-implements-interface-abstract COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_abstract.ts")
# FIXED: see the matching test-compile-export-import-class-generic comment above (2026-07-22).
add_test(NAME test-jit-shared-export-import-class-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts")
add_test(NAME test-jit-shared-export-import-function-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts")
# FIXED: see the matching test-compile-shared-export-import-class-accessor comment above (2026-07-22).
add_test(NAME test-jit-shared-export-import-class-accessor COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_accessor.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_accessor.ts")
add_test(NAME test-jit-shared-export-import-class-indexer COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_indexer.ts")
Expand Down
18 changes: 18 additions & 0 deletions tslang/test/tester/tests/export_function_generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace M {

// Cross-module counterpart to 00funcs_generic.ts. GenericFunctionInfo
// (MLIRGenStore.h) is structurally analogous to GenericClassInfo, whose
// cross-module export gap took 4 fixes to close (PR #280,
// class-generic-declaration-export-fix) - per
// decls-cross-module-declaration-mechanism memory, generic
// functions/interfaces/type-aliases were flagged as plausible,
// unverified instances of the exact same gap.

export function identity<T>(x: T): T {
return x;
}

export function pair<A, B>(a: A, b: B): string {
return `${a}-${b}`;
}
}
9 changes: 9 additions & 0 deletions tslang/test/tester/tests/import_function_generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import './export_function_generic'

function main() {
assert(M.identity<number>(42) == 42);
assert(M.identity<string>("hi") == "hi");
assert(M.pair<number, string>(1, "one") == "1-one");

print("done.");
}
Loading