From 3717c87642197a981fee0a45efdcd212bdeda909 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 14 Sep 2026 00:02:17 +0100 Subject: [PATCH] Add support for @dllname attribute in TypeScript exports and imports - Introduced DLL_NAME attribute for functions, methods, variables, and accessors. - Updated relevant classes and methods to handle DLL_NAME during export and import. - Added tests to ensure correct handling of @dllname in declarations. --- tslang/include/TypeScript/Defines.h | 1 + .../MLIRLogic/MLIRDeclarationPrinter.h | 6 +-- .../TypeScript/MLIRLogic/MLIRGenStore.h | 6 +++ tslang/lib/TypeScript/DeclarationPrinter.cpp | 38 ++++++++++++-- tslang/lib/TypeScript/LowerToLLVM.cpp | 30 ++++++++++- tslang/lib/TypeScript/MLIRGenAccessCall.cpp | 4 +- tslang/lib/TypeScript/MLIRGenClasses.cpp | 11 ++-- tslang/lib/TypeScript/MLIRGenFunctions.cpp | 13 +++-- tslang/lib/TypeScript/MLIRGenImpl.h | 52 +++++++++++++------ tslang/lib/TypeScript/MLIRGenVariables.cpp | 10 +++- .../TypeScriptExportPass/ExportFixPass.cpp | 10 +++- tslang/test/tester/CMakeLists.txt | 1 + tslang/test/tester/tests/export_dllname.ts | 48 +++++++++++++++++ tslang/test/tester/tests/import_dllname.ts | 20 +++++++ .../unittests/MLIRGen/DeclarationPrinter.cpp | 50 ++++++++++++++++++ 15 files changed, 264 insertions(+), 36 deletions(-) create mode 100644 tslang/test/tester/tests/export_dllname.ts create mode 100644 tslang/test/tester/tests/import_dllname.ts diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index c460e079e..9aae092b3 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -122,6 +122,7 @@ #define SHARED_LIB_MEMORY_MODEL "__tsmm_" #define DLL_EXPORT "dllexport" #define DLL_IMPORT "dllimport" +#define DLL_NAME "dllname" #if __LP64__ #define TRAMPOLINE_SIZE 48 diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h index 7bdef2c86..5a4cf46b7 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h @@ -18,8 +18,8 @@ namespace typescript void printTypeDeclaration(StringRef, NamespaceInfo::TypePtr, mlir::Type); void printEnum(StringRef, NamespaceInfo::TypePtr, mlir::DictionaryAttr); - void printVariableDeclaration(StringRef, NamespaceInfo::TypePtr, mlir::Type, bool); - void print(StringRef, NamespaceInfo::TypePtr, mlir_ts::FunctionType); + void printVariableDeclaration(StringRef, NamespaceInfo::TypePtr, mlir::Type, bool, StringRef = StringRef()); + void print(StringRef, NamespaceInfo::TypePtr, mlir_ts::FunctionType, StringRef = StringRef()); void print(ClassInfo::TypePtr); void print(InterfaceInfo::TypePtr); void printGenericClass(NamespaceInfo::TypePtr, StringRef); @@ -35,7 +35,7 @@ namespace typescript void printParams(ArrayRef, mlir::Type); void printFunction(StringRef, ArrayRef, mlir::Type); void printMethod(bool, StringRef, ArrayRef, mlir::Type, mlir::Type); - void printAccessor(bool, StringRef, StringRef, mlir_ts::AccessLevel, ArrayRef, mlir::Type, mlir::Type); + void printAccessor(bool, StringRef, StringRef, mlir_ts::AccessLevel, ArrayRef, mlir::Type, mlir::Type, StringRef); void printIndexer(mlir::Type, mlir::Type); void printNamespaceBegin(NamespaceInfo::TypePtr); void printNamespaceEnd(NamespaceInfo::TypePtr); diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index b250c8257..495ea2baa 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -102,6 +102,8 @@ struct VariableClass // DeclarationPrinter.cpp's printVariableDeclaration and // MLIRGenVariables.cpp's isDynamicImport load branch. bool isBoxed; + // @dllname: exported symbol name, empty when the variable keeps its own name + StringRef dllName; inline VariableClass& operator=(VariableType type_) { type = type_; return *this; } @@ -124,6 +126,8 @@ struct FunctionEntry { std::string name; mlir_ts::FunctionType funcType; + // @dllname: exported symbol name, empty when the function keeps name + std::string dllName; explicit operator bool() const { @@ -144,6 +148,8 @@ struct MethodInfo int virtualIndex; int orderWeight; mlir_ts::AccessLevel accessLevel; + // @dllname: exported symbol name, empty when the method keeps funcName + std::string dllName; }; struct GenericMethodInfo diff --git a/tslang/lib/TypeScript/DeclarationPrinter.cpp b/tslang/lib/TypeScript/DeclarationPrinter.cpp index 800a9ac56..8c8f7b760 100644 --- a/tslang/lib/TypeScript/DeclarationPrinter.cpp +++ b/tslang/lib/TypeScript/DeclarationPrinter.cpp @@ -258,8 +258,15 @@ namespace typescript } void MLIRDeclarationPrinter::printAccessor(bool isStatic, StringRef keyword, StringRef name, mlir_ts::AccessLevel accessLevel, - ArrayRef params, mlir::Type returnType, mlir::Type thisType) + ArrayRef params, mlir::Type returnType, mlir::Type thisType, StringRef dllName) { + if (!dllName.empty()) + { + os.indent(4); + os << "@dllname(\"" << dllName << "\")"; + newline(); + } + os.indent(4); if (accessLevel == mlir_ts::AccessLevel::Protected) @@ -347,12 +354,18 @@ namespace typescript printNamespaceEnd(elementNamespace); } - void MLIRDeclarationPrinter::printVariableDeclaration(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type, bool isConst) + void MLIRDeclarationPrinter::printVariableDeclaration(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type, bool isConst, StringRef dllName) { printNamespaceBegin(elementNamespace); printBeforeDeclaration(); + if (!dllName.empty()) + { + os << "@dllname(\"" << dllName << "\")"; + newline(); + } + // no TS source syntax expresses "this symbol's storage is a single // boxed pointer to the real data, not the data inline" - an inferred // (untyped) object-literal export is boxed as ObjectType, but its @@ -387,12 +400,18 @@ namespace typescript printNamespaceEnd(elementNamespace); } - void MLIRDeclarationPrinter::print(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir_ts::FunctionType funcType) + void MLIRDeclarationPrinter::print(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir_ts::FunctionType funcType, StringRef dllName) { printNamespaceBegin(elementNamespace); printBeforeDeclaration(); + if (!dllName.empty()) + { + os << "@dllname(\"" << dllName << "\")"; + newline(); + } + printFunction( name, funcType.getParams(), @@ -561,6 +580,13 @@ namespace typescript continue; } + if (!method.dllName.empty()) + { + os.indent(4); + os << "@dllname(\"" << method.dllName << "\")"; + newline(); + } + os.indent(4); if (method.accessLevel == mlir_ts::AccessLevel::Protected) @@ -599,7 +625,8 @@ namespace typescript accessor.isStatic, "get", accessor.name, accessor.getAccessLevel, accessor.get.funcType.getParams(), accessor.get.funcType.getNumResults() > 0 ? accessor.get.funcType.getResult(0) : mlir::Type(), - classType->classType); + classType->classType, + accessor.get.dllName); } if (accessor.set) @@ -608,7 +635,8 @@ namespace typescript accessor.isStatic, "set", accessor.name, accessor.setAccessLevel, accessor.set.funcType.getParams(), mlir::Type(), - classType->classType); + classType->classType, + accessor.set.dllName); } } diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index e5c9a3ab8..09bef6739 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -1568,9 +1568,11 @@ struct FuncOpLowering : public TsLlvmPattern continue; } - auto addAttr = + auto addAttr = std::find(skipAttrs.begin(), skipAttrs.end(), name) == skipAttrs.end(); - if (addAttr) + if (addAttr && name == DLL_NAME) + funcAttrs.push_back(ArrayAttr::get(rewriter.getContext(), {name, attr.getValue()})); + else if (addAttr) funcAttrs.push_back(name); } @@ -7216,6 +7218,30 @@ void TypeScriptToLLVMLoweringPass::runOnOperation() { auto m = getOperation(); + // @dllname on a global: unlike a function, an LLVM global carries no string attribute + // ExportFixPass could rename it by, so rename the symbol and its uses here instead + SmallVector renamedGlobals; + m.walk([&](mlir_ts::GlobalOp globalOp) { + if (globalOp->hasAttr(DLL_NAME)) + { + renamedGlobals.push_back(globalOp); + } + }); + + for (auto globalOp : renamedGlobals) + { + auto dllName = globalOp->getAttrOfType(DLL_NAME); + globalOp->removeAttr(DLL_NAME); + if (failed(SymbolTable::replaceAllSymbolUses(globalOp, dllName, m))) + { + globalOp.emitError("can't rename global to @dllname '") << dllName.getValue() << "'"; + signalPassFailure(); + return; + } + + SymbolTable::setSymbolName(globalOp, dllName); + } + // The first thing to define is the conversion target. This will define the // final target for this lowering. For this lowering, we are only targeting // the LLVM dialect. diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index 6e6a4a4dc..1c1fb52fd 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -716,7 +716,7 @@ namespace mlirgen auto globalFuncVar = resolveFullNameIdentifier(location, funcName, false, genContext); if (!globalFuncVar) { - auto symbolNameValue = V(mlirGenStringValue(location, funcName.str(), true)); + auto symbolNameValue = V(mlirGenStringValue(location, funcEntry.dllName.empty() ? funcName.str() : funcEntry.dllName, true)); auto referenceToFuncOpaque = builder.create( location, getOpaqueType(), symbolNameValue); auto castResult = cast(location, effectiveFuncType, referenceToFuncOpaque, genContext); @@ -838,7 +838,7 @@ namespace mlirgen auto globalFuncVar = resolveFullNameIdentifier(location, funcName, false, genContext); if (!globalFuncVar) { - auto symbolNameValue = V(mlirGenStringValue(location, funcName.str(), true)); + auto symbolNameValue = V(mlirGenStringValue(location, funcEntry.dllName.empty() ? funcName.str() : funcEntry.dllName, true)); auto referenceToFuncOpaque = builder.create( location, getOpaqueType(), symbolNameValue); auto castResult = cast(location, effectiveFuncType, referenceToFuncOpaque, genContext); diff --git a/tslang/lib/TypeScript/MLIRGenClasses.cpp b/tslang/lib/TypeScript/MLIRGenClasses.cpp index 0371abee2..41266f2c3 100644 --- a/tslang/lib/TypeScript/MLIRGenClasses.cpp +++ b/tslang/lib/TypeScript/MLIRGenClasses.cpp @@ -1776,7 +1776,8 @@ genContext); // initializer into a synthesized __cctor function via // GlobalOpLowering, and a "live" (main-scoped) location on any op // living there would attach the wrong DISubprogram to it. - auto symbolNameValue = V(mlirGenStringValue(location, methodOrField.methodInfo.funcName, true)); + auto symbolNameValue = V(mlirGenStringValue(location, + methodOrField.methodInfo.dllName.empty() ? methodOrField.methodInfo.funcName : methodOrField.methodInfo.dllName, true)); auto referenceToSymbolOpaque = builder.create( location, getOpaqueType(), symbolNameValue); auto castResult = cast(location, methodOrField.methodInfo.funcType, referenceToSymbolOpaque, genContext); @@ -2001,7 +2002,10 @@ genContext); if (isOwnedByDynamicImport(symbolName, vtRecord.isStaticField)) { - auto symbolNameValue = V(mlirGenStringValue(location, symbolName.str(), true)); + auto dllSymbolName = !vtRecord.isStaticField && !vtRecord.methodInfo.dllName.empty() + ? StringRef(vtRecord.methodInfo.dllName) + : symbolName; + auto symbolNameValue = V(mlirGenStringValue(location, dllSymbolName.str(), true)); auto referenceToSymbolOpaque = builder.create( location, getOpaqueType(), symbolNameValue); auto castResult = cast(location, slotType, referenceToSymbolOpaque, genContext); @@ -2280,8 +2284,9 @@ genContext); classMethodMemberInfo.setFuncOp(funcOp); auto location = loc(funcLikeDeclaration); + auto dllNameAttr = funcOp->getAttrOfType(DLL_NAME); if (mlir::succeeded(mlirGenFunctionLikeDeclarationDynamicImport( - location, funcOp.getName(), funcOp.getFunctionType(), funcOp.getName(), genContext))) + location, funcOp.getName(), funcOp.getFunctionType(), dllNameAttr ? dllNameAttr.getValue() : funcOp.getName(), genContext))) { // no need to generate method in code funcLikeDeclaration->processed = true; diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index 28f11cba7..a2231d5be 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -392,7 +392,8 @@ namespace mlirgen if (functionLikeDeclarationBaseAST == SyntaxKind::FunctionDeclaration || functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction) { - addFunctionDeclarationToExport(funcProto, currentNamespace); + auto dllNameAttr = funcOp->getAttrOfType(DLL_NAME); + addFunctionDeclarationToExport(funcProto, currentNamespace, dllNameAttr ? dllNameAttr.getValue() : StringRef()); } } } @@ -844,19 +845,25 @@ namespace mlirgen // check decorator for class auto dynamicImport = false; + StringRef dllFuncName = funcProto->getName(); iterateDecorators(functionLikeDeclarationBaseAST, genContext, [&](StringRef name, SmallVector args) { if (name == DLL_IMPORT && args.size() > 0) { dynamicImport = true; } + + if (name == DLL_NAME && args.size() > 0) + { + dllFuncName = args.front(); + } }); if (dynamicImport) { // TODO: we do not need to register funcOp as we need to reference global variables auto result = mlirGenFunctionLikeDeclarationDynamicImport( - location, funcProto->getNameWithoutNamespace(), funcOp.getFunctionType(), - funcProto->getName(), funcDeclGenContext, false); + location, funcProto->getNameWithoutNamespace(), funcOp.getFunctionType(), + dllFuncName, funcDeclGenContext, false); return {result, funcOp, funcProto->getName().str(), false}; } diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index b51705180..ec2ad56b8 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1917,7 +1917,14 @@ class MLIRGenImpl if (variableDeclarationInfo.isImport) { attrs.push_back({mlir::StringAttr::get(builder.getContext(), "import"), mlir::UnitAttr::get(builder.getContext())}); - } + } + + // a dynamic import only holds a local copy resolved by name at runtime + if (!variableDeclarationInfo.varClass.dllName.empty() && !variableDeclarationInfo.varClass.isDynamicImport) + { + attrs.push_back({mlir::StringAttr::get(builder.getContext(), DLL_NAME), + mlir::StringAttr::get(builder.getContext(), variableDeclarationInfo.varClass.dllName)}); + } if (this->compileOptions.generateDebugInfo) { @@ -2544,6 +2551,11 @@ class MLIRGenImpl if (name == "used") { builder.create(location, fullName); } + + if (name == DLL_NAME && args.size() > 0) + { + attrs.push_back({mlir::StringAttr::get(builder.getContext(), DLL_NAME), mlir::StringAttr::get(builder.getContext(), args.front())}); + } }); // add modifiers @@ -2683,7 +2695,9 @@ class MLIRGenImpl static FunctionEntry makeFunctionEntry(mlir_ts::FuncOp funcOp) { - return FunctionEntry{funcOp.getName().str(), mlir::cast(funcOp.getFunctionType())}; + auto dllNameAttr = funcOp->getAttrOfType(DLL_NAME); + return FunctionEntry{funcOp.getName().str(), mlir::cast(funcOp.getFunctionType()), + dllNameAttr ? dllNameAttr.getValue().str() : std::string()}; } bool registerFunctionOp(FunctionPrototypeDOM::TypePtr funcProto, mlir_ts::FuncOp funcOp); @@ -6443,7 +6457,8 @@ class MLIRGenImpl // 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 symbolNameValue = V(mlirGenStringValue(location, + methodInfo.dllName.empty() ? funcName.str() : methodInfo.dllName, true)); auto referenceToFuncOpaque = builder.create( location, getOpaqueType(), symbolNameValue); auto castResult = cast(location, effectiveFuncType, referenceToFuncOpaque, genContext); @@ -10064,6 +10079,8 @@ class MLIRGenImpl auto &methodInfos = newClassPtr->methods; auto methodIndex = newClassPtr->getMethodIndex(methodName); + auto dllNameAttr = funcOp ? funcOp->getAttrOfType(DLL_NAME) : mlir::StringAttr(); + auto dllName = dllNameAttr ? dllNameAttr.getValue().str() : std::string(); if (methodIndex < 0) { methodInfos.push_back( @@ -10072,17 +10089,22 @@ class MLIRGenImpl getFuncType(), getFuncName().str(), isStatic, - isAbstract || isVirtual, - isAbstract, - -1, - orderWeight, - accessLevel + isAbstract || isVirtual, + isAbstract, + -1, + orderWeight, + accessLevel, + dllName }); } else { methodInfos[methodIndex].orderWeight = orderWeight; methodInfos[methodIndex].accessLevel = accessLevel; + if (!dllName.empty()) + { + methodInfos[methodIndex].dllName = dllName; + } } if (propertyName.size() > 0) @@ -11344,22 +11366,22 @@ class MLIRGenImpl declExports << ss.str().str(); } - void addVariableDeclarationToExport(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type, bool isConst) - { + void addVariableDeclarationToExport(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type, bool isConst, StringRef dllName) + { // TODO: add distinct declaration // we need to add it anyway as it is varaible declaration addDependancyTypesToExport(type); SmallVector out; - llvm::raw_svector_ostream ss(out); + llvm::raw_svector_ostream ss(out); MLIRDeclarationPrinter dp(ss); - dp.printVariableDeclaration(name, elementNamespace, type, isConst); + dp.printVariableDeclaration(name, elementNamespace, type, isConst, dllName); declExports << ss.str().str(); } - void addFunctionDeclarationToExport(FunctionPrototypeDOM::TypePtr funcProto, NamespaceInfo::TypePtr elementNamespace) + void addFunctionDeclarationToExport(FunctionPrototypeDOM::TypePtr funcProto, NamespaceInfo::TypePtr elementNamespace, StringRef dllName) { // TODO: add distinct declaration @@ -11367,9 +11389,9 @@ class MLIRGenImpl addDependancyTypesToExport(funcProto->getFuncType()); SmallVector out; - llvm::raw_svector_ostream ss(out); + llvm::raw_svector_ostream ss(out); MLIRDeclarationPrinter dp(ss); - dp.print(funcProto->getNameWithoutNamespace(), elementNamespace, funcProto->getFuncType()); + dp.print(funcProto->getNameWithoutNamespace(), elementNamespace, funcProto->getFuncType(), dllName); declExports << ss.str().str(); } diff --git a/tslang/lib/TypeScript/MLIRGenVariables.cpp b/tslang/lib/TypeScript/MLIRGenVariables.cpp index 20d765054..0f264f2ca 100644 --- a/tslang/lib/TypeScript/MLIRGenVariables.cpp +++ b/tslang/lib/TypeScript/MLIRGenVariables.cpp @@ -723,7 +723,7 @@ namespace mlirgen if (varClass.isExport) { auto isConst = varClass.type == VariableType::Const || varClass.type == VariableType::ConstRef; - addVariableDeclarationToExport(nameStr, currentNamespace, varType, isConst); + addVariableDeclarationToExport(nameStr, currentNamespace, varType, isConst, varClass.dllName); } return mlir::success(); @@ -782,7 +782,9 @@ namespace mlirgen if (varClass.isDynamicImport) { - auto nameStr = concatFullNamespaceName(MLIRHelper::getName(item->name)); + auto nameStr = varClass.dllName.empty() + ? concatFullNamespaceName(MLIRHelper::getName(item->name)) + : varClass.dllName.str(); auto fieldType = std::get<0>(typeAndInit); if (fieldType) { @@ -900,6 +902,10 @@ namespace mlirgen varClass.isBoxed = true; } + if (name == DLL_NAME && args.size() > 0) { + varClass.dllName = args.front(); + } + if (name == "used") { varClass.isUsed = true; } diff --git a/tslang/lib/TypeScriptExportPass/ExportFixPass.cpp b/tslang/lib/TypeScriptExportPass/ExportFixPass.cpp index 488f428f3..da9bc2e17 100644 --- a/tslang/lib/TypeScriptExportPass/ExportFixPass.cpp +++ b/tslang/lib/TypeScriptExportPass/ExportFixPass.cpp @@ -25,7 +25,15 @@ struct ExportFixPassCode LLVM_DEBUG(llvm::dbgs() << "\nEXPORT Function: " << F.getName()); LLVM_DEBUG(llvm::dbgs() << "\nEXPORT Dump Before: ...\n" << F << "\n";); - + + if (F.hasFnAttribute(DLL_NAME)) + { + auto dllName = F.getFnAttribute(DLL_NAME).getValueAsString().str(); + F.removeFnAttr(DLL_NAME); + F.setName(dllName); + MadeChange = true; + } + if (F.hasFnAttribute("export")) { F.removeFnAttr("export"); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 232165bf6..43fec5ee6 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -1108,6 +1108,7 @@ add_test(NAME test-compile-shared-export-import-class-generic COMMAND test-runne 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") add_test(NAME test-compile-shared-export-import-type-alias-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_type_alias_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_type_alias_generic.ts") add_test(NAME test-compile-shared-export-import-function COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function.ts") +add_test(NAME test-compile-shared-export-import-dllname COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_dllname.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_dllname.ts") add_test(NAME test-compile-shared-export-import-type-alias COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_type_alias.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_type_alias.ts") add_test(NAME test-compile-shared-export-import-interface-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_generic.ts") # FIXED (2026-07-22): the -shared declaration-reconstruction path never printed real diff --git a/tslang/test/tester/tests/export_dllname.ts b/tslang/test/tester/tests/export_dllname.ts new file mode 100644 index 000000000..10aebcc6a --- /dev/null +++ b/tslang/test/tester/tests/export_dllname.ts @@ -0,0 +1,48 @@ +namespace M { + + // @dllname replaces the symbol a function, method, accessor or variable is + // exported under; the importer only sees the re-printed declaration, so it must + // resolve the custom name instead of the mangled M.xxx one. + + @dllname("custom_add") + export function add(a: number, b: number): number { + return a + b; + } + + export function addTwice(a: number, b: number): number { + return add(a, b) + add(a, b); + } + + @dllname("custom_counter") + export let counter = 40; + + export function bump(): number { + counter = counter + 2; + return counter; + } + + export class Calc { + constructor(public factor: number) { + } + + @dllname("calc_twice") + static twice(x: number): number { + return x * 2; + } + + @dllname("calc_scale") + scale(x: number): number { + return x * this.factor; + } + + @dllname("calc_get_double") + get double(): number { + return this.factor * 2; + } + + @dllname("calc_set_double") + set double(value: number) { + this.factor = value / 2; + } + } +} diff --git a/tslang/test/tester/tests/import_dllname.ts b/tslang/test/tester/tests/import_dllname.ts new file mode 100644 index 000000000..7771c2949 --- /dev/null +++ b/tslang/test/tester/tests/import_dllname.ts @@ -0,0 +1,20 @@ +import './export_dllname' + +function main() { + assert(M.add(2, 3) == 5); + assert(M.addTwice(2, 3) == 10); + + assert(M.counter == 40); + assert(M.bump() == 42); + + assert(M.Calc.twice(4) == 8); + + const calc = new M.Calc(3); + assert(calc.scale(5) == 15); + + assert(calc.double == 6); + calc.double = 10; + assert(calc.factor == 5); + + print("done."); +} diff --git a/tslang/unittests/MLIRGen/DeclarationPrinter.cpp b/tslang/unittests/MLIRGen/DeclarationPrinter.cpp index 106c78160..0525e011e 100644 --- a/tslang/unittests/MLIRGen/DeclarationPrinter.cpp +++ b/tslang/unittests/MLIRGen/DeclarationPrinter.cpp @@ -299,6 +299,56 @@ TEST_F(DeclarationPrinterTest, function_declaration_has_dllimport_marker) EXPECT_THAT(text, testing::HasSubstr("@dllimport")); } +// @dllname must survive the __decls round trip, or the importer looks up the +// mangled name the exporting library no longer exports. +TEST_F(DeclarationPrinterTest, function_declaration_prints_dllname_decorator) +{ + auto text = printed([&](MLIRDeclarationPrinter &dp) { + dp.print("foo", noNamespace(), getF({}, {}), "custom_foo"); + }); + EXPECT_THAT(text, testing::HasSubstr("@dllname(\"custom_foo\")\nfunction foo")); +} + +TEST_F(DeclarationPrinterTest, function_declaration_without_dllname_has_no_decorator) +{ + auto text = printed([&](MLIRDeclarationPrinter &dp) { + dp.print("foo", noNamespace(), getF({}, {})); + }); + EXPECT_THAT(text, testing::Not(testing::HasSubstr("@dllname"))); +} + +TEST_F(DeclarationPrinterTest, variable_declaration_prints_dllname_decorator) +{ + auto text = printed([&](MLIRDeclarationPrinter &dp) { + dp.printVariableDeclaration("x", noNamespace(), get(), /*isConst*/ false, "custom_x"); + }); + EXPECT_THAT(text, testing::HasSubstr("@dllname(\"custom_x\")\nlet x")); +} + +TEST_F(DeclarationPrinterTest, class_accessor_prints_dllname_decorator) +{ + auto ci = makeClass("Foo"); + auto getter = entry("Foo.get_x", getF({}, {get()})); + getter.dllName = "custom_get_x"; + ci->accessors.push_back(accessor("x", getter, noEntry())); + + auto text = printClass(ci); + EXPECT_THAT(text, testing::HasSubstr(" @dllname(\"custom_get_x\")\n get x(")); +} + +TEST_F(DeclarationPrinterTest, class_method_prints_dllname_decorator) +{ + auto ci = makeClass("Foo"); + auto m = method("bar", getF({}, {})); + m.dllName = "custom_bar"; + ci->methods.push_back(m); + ci->methods.push_back(method("baz", getF({}, {}))); + + auto text = printClass(ci); + EXPECT_THAT(text, testing::HasSubstr(" @dllname(\"custom_bar\")\n bar(")); + EXPECT_THAT(text, testing::Not(testing::HasSubstr("@dllname(\"custom_bar\")\n baz("))); +} + TEST_F(DeclarationPrinterTest, class_declaration_has_dllimport_marker) { auto text = printClass(makeClass("Foo"));