diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 32db49015..13c50b3a8 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -239,7 +239,8 @@ class MLIRGenImpl int processStatements(NodeArray statements, const GenContext &genContext, - bool isRoot = false); + bool isRoot = false, + bool skipCode = false); bool hasGlobalCode(NodeArray statements); @@ -251,6 +252,10 @@ class MLIRGenImpl mlir::LogicalResult generateGlobalEntryCode(mlir::Location location, NodeArray statements, bool hasDeferredStatements, const GenContext &genContext); + // an imported module's top-level statements, run from a global constructor + mlir::LogicalResult generateModuleInitCode(mlir::Location location, NodeArray statements, + const GenContext &genContext); + mlir::LogicalResult outputDiagnostics(mlir::SmallVector> &postponedMessages, int notResolved); diff --git a/tslang/lib/TypeScript/MLIRGenModule.cpp b/tslang/lib/TypeScript/MLIRGenModule.cpp index 01721c051..9657b913e 100644 --- a/tslang/lib/TypeScript/MLIRGenModule.cpp +++ b/tslang/lib/TypeScript/MLIRGenModule.cpp @@ -627,7 +627,8 @@ namespace mlirgen int MLIRGenImpl::processStatements(NodeArray statements, const GenContext &genContext, - bool isRoot) + bool isRoot, + bool skipCode) { clearState(statements); @@ -654,6 +655,11 @@ namespace mlirgen continue; } + if (skipCode && isCodeStatment(statement)) + { + continue; + } + if (failed(mlirGen(statement, genContext))) { emitError(loc(statement), "failed statement"); @@ -749,7 +755,16 @@ namespace mlirgen std::string name = MAIN_ENTRY_NAME; auto fullGlobalFuncName = getFullNamespaceName(name); - if (theModule.lookupSymbol(fullGlobalFuncName)) + // A library - a module linked beside the program, or one the JIT cache compiles for an + // import - runs its top level from a global constructor: a `main` of its own was a second + // one at link time (#447). + if (!compileOptions.generateEntryPoint && hasDeferredStatements && !theModule.lookupSymbol(fullGlobalFuncName)) + { + name = MLIRHelper::getAnonymousName(location, "." MAIN_ENTRY_NAME, ""); + fullGlobalFuncName = getFullNamespaceName(name); + useGlobalCtor = true; + } + else if (theModule.lookupSymbol(fullGlobalFuncName)) { // a user-written `main` already is the entry point, so with nothing deferred to run // ahead of it there is nothing left to generate @@ -820,6 +835,39 @@ namespace mlirgen return mlir::success(); } + // An imported module's top-level statements - `print("module init")` beside its exports - run + // when the program starts, from a global constructor of their own, after the module's variables + // are initialized. They were generated at the importer's module level, outside any function, + // which LLVM rejected ("Global is referenced by parentless instruction", #447). + mlir::LogicalResult MLIRGenImpl::generateModuleInitCode(mlir::Location location, NodeArray statements, + const GenContext &genContext) + { + mlir::OpBuilder::InsertionGuard insertGuard(builder); + + auto name = MLIRHelper::getAnonymousName(location, ".init", ""); + auto fullName = getFullNamespaceName(name); + auto funcType = getFunctionType({}, {}, false); + if (mlir::failed(mlirGenFunctionBody(location, name, fullName, funcType, + [&](mlir::Location location, const GenContext &genContext) { + for (auto &statement : statements) + { + if (isCodeStatment(statement) && failed(mlirGen(statement, genContext))) + { + emitError(loc(statement), "failed statement"); + return mlir::failure(); + } + } + + return mlir::success(); + }, genContext, 0, true))) + { + return mlir::failure(); + } + + addGlobalConstructor(location, fullName); + return mlir::success(); + } + mlir::LogicalResult MLIRGenImpl::outputDiagnostics(mlir::SmallVector> &postponedMessages, int notResolved) { @@ -950,7 +998,16 @@ namespace mlirgen } auto anyGlobalCode = hasGlobalCode(module->statements); - auto notResolved = processStatements(module->statements, genContext, isMain && anyGlobalCode); + // An imported module's top-level code is never generated at this module's level: read as + // declarations it is its own object's to run, and included with its bodies it runs from a + // global constructor (generateModuleInitCode). + auto notResolved = processStatements(module->statements, genContext, isMain && anyGlobalCode, !isMain); + if (notResolved == 0 && !isMain && anyGlobalCode && !declarationMode && + mlir::failed(generateModuleInitCode(loc(module), module->statements, genContext))) + { + notResolved = 1; + } + if (failed(outputDiagnostics(postponedMessages, notResolved))) { return mlir::failure(); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 47627f105..3273d9527 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3043,6 +3043,11 @@ set_tests_properties(test-compile-reference-path-import-cycle-quiet PROPERTIES F set(import_source_dir "${PROJECT_SOURCE_DIR}/test/tester/import-source") tslang_add_test(NAME test-jit-import-source-new-imported-class COMMAND test-runner -jit "${import_source_dir}/new_imported_class.ts") tslang_add_import_tests(import-source-new-imported-class "${import_source_dir}" new_imported_class imported_class_module) +# A module with top-level statements, imported: they run before main, from a global constructor; +# generated outside any function, they failed with "Global is referenced by parentless +# instruction", and a module linked beside the program got a `main` of its own (#447). +tslang_add_test(NAME test-jit-import-source-toplevel COMMAND test-runner -jit "${import_source_dir}/import_toplevel.ts") +tslang_add_import_tests(import-source-toplevel "${import_source_dir}" import_toplevel toplevel_module) # The JIT cache (--emit=jit, on by default): each .ts module imported is an object of its own, # loaded beside the program's - imported through two others and initialized first; and a program diff --git a/tslang/test/tester/import-source/import_toplevel.ts b/tslang/test/tester/import-source/import_toplevel.ts new file mode 100644 index 000000000..31750f0d7 --- /dev/null +++ b/tslang/test/tester/import-source/import_toplevel.ts @@ -0,0 +1,9 @@ +// The importer of a module with top-level statements (#447): the module's statements have run, +// once, by the time main does. +import "./toplevel_module"; + +function main() { + assert(startCount() == 1, "the module's top level ran once, before main"); + assert(twice(21) == 42, "its function"); + print("done."); +} diff --git a/tslang/test/tester/import-source/toplevel_module.ts b/tslang/test/tester/import-source/toplevel_module.ts new file mode 100644 index 000000000..815b12257 --- /dev/null +++ b/tslang/test/tester/import-source/toplevel_module.ts @@ -0,0 +1,15 @@ +// A module with top-level statements, imported (#447): they run when the program starts, before +// the importer's main. Generated at the importer's module level, outside any function, they were +// invalid LLVM IR ("Global is referenced by parentless instruction"). +let started = 0; + +export function twice(n: number) { + return n * 2; +} + +export function startCount() { + return started; +} + +started = started + 1; +print("module init");