From a73b27ab5e30b44016ba21f43456323d11efb189 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Thu, 11 Jun 2026 08:01:12 +0200 Subject: [PATCH] update logic for improve speed --- src/analyzer.cpp | 20 ++++++++++++++++++++ src/codegen.cpp | 25 ++++++++++++++++++++++--- src/codegen.h | 16 ++++++++++++++++ src/jam_llvm.cpp | 27 +++++++++++++++++++++++++++ src/jam_llvm.h | 6 ++++++ src/jir_codegen.cpp | 4 ++++ src/main.cpp | 27 +++++++++++++++++++++------ 7 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/analyzer.cpp b/src/analyzer.cpp index f97e4ef..5d5c272 100644 --- a/src/analyzer.cpp +++ b/src/analyzer.cpp @@ -122,10 +122,26 @@ DeclValue Analyzer::ensureDeclAnalyzed(DeclIndex idx) { return again.value; } +// Resolve a decl's member types in its OWNING module's scope: the +// demand-driven fill can run while any other module's body is current, +// and a private member type must not trip the handle-privacy gate of +// whichever module happened to trigger the fill. +namespace { +struct BodyModuleGuard { + JamCodegenContext &ctx; + BodyModuleGuard(JamCodegenContext &c, const std::string &m) : ctx(c) { + ctx.pushBodyModule(m); + } + ~BodyModuleGuard() { ctx.popBodyModule(); } +}; +} // namespace + bool Analyzer::resolveTypeFieldsStruct(DeclIndex idx) { if (idx == kNoDecl) return false; Decl &d = decls_.get(idx); if (d.kind != DeclKind::Struct) return false; + BodyModuleGuard bmg( + ctx_, d.structAst ? d.structAst->modulePath : std::string()); switch (d.structStatus) { case StructStatus::HaveFieldTypes: @@ -266,6 +282,8 @@ bool Analyzer::resolveTypeFieldsEnum(DeclIndex idx) { if (idx == kNoDecl) return false; Decl &d = decls_.get(idx); if (d.kind != DeclKind::Enum) return false; + BodyModuleGuard bmg(ctx_, + d.enumAst ? d.enumAst->modulePath : std::string()); switch (d.enumStatus) { case EnumStatus::HaveBody: @@ -420,6 +438,8 @@ bool Analyzer::resolveTypeFieldsUnion(DeclIndex idx) { if (idx == kNoDecl) return false; Decl &d = decls_.get(idx); if (d.kind != DeclKind::Union) return false; + BodyModuleGuard bmg(ctx_, + d.unionAst ? d.unionAst->modulePath : std::string()); switch (d.unionStatus) { case UnionStatus::HaveBody: diff --git a/src/codegen.cpp b/src/codegen.cpp index f494de5..31c5eb9 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -182,11 +182,14 @@ JamTypeRef JamCodegenContext::getLLVMType(TypeIdx ty) const { result = getLLVMType(substTarget); break; } - if (const auto *sinfo = getStruct(name)) { + bool privacyBlocked = handlePrivacyBlocked(name); + if (const auto *sinfo = privacyBlocked ? nullptr : getStruct(name)) { result = sinfo->type; - } else if (const auto *uinfo = getUnion(name)) { + } else if (const auto *uinfo = + privacyBlocked ? nullptr : getUnion(name)) { result = uinfo->type; - } else if (const auto *einfo = getEnum(name)) { + } else if (const auto *einfo = + privacyBlocked ? nullptr : getEnum(name)) { // Unit-only enums lower to i8. Payloaded enums lower // to {i8, [N x i8]} via the named struct type set during // declaration. @@ -304,6 +307,11 @@ JamCodegenContext::lookupStruct(TypeIdx ty) const { // resolved per-instantiation during method body codegen). TypeIdx substTarget = lookupCurrentSubst(name); if (substTarget != kNoType) { return lookupStruct(substTarget); } + // A handle-spelled reference to another module's PRIVATE type must + // miss (the caller's error path then reports `is not exported`), + // even when the handle name coincides with the module identity the + // registry keys by. + if (handlePrivacyBlocked(name)) { return nullptr; } if (const StructInfo *direct = getStruct(name)) { return direct; } // try the type alias table — `const BoxI32 = Box(i32);` // maps `BoxI32` to the instantiated struct's TypeIdx. @@ -510,6 +518,7 @@ JamCodegenContext::lookupUnion(TypeIdx ty) const { return nullptr; } const std::string &name = stringPool.get(static_cast(k.a)); + if (handlePrivacyBlocked(name)) return nullptr; if (const UnionInfo *direct = getUnion(name)) return direct; if (name.find('.') != std::string::npos) { TypeIdx chained = resolveChainedType(name); @@ -584,6 +593,7 @@ JamCodegenContext::lookupEnum(TypeIdx ty) const { return nullptr; } const std::string &name = stringPool.get(static_cast(k.a)); + if (handlePrivacyBlocked(name)) return nullptr; if (const EnumInfo *direct = getEnum(name)) return direct; // try the type alias table — `const OptI32 = // Option(i32);` maps `OptI32` to the instantiated enum's TypeIdx. @@ -1648,6 +1658,14 @@ TypeIdx JamCodegenContext::instantiateStructExpr( JamLLVMStructCreateNamed(getContext(), instName.c_str()); registerStruct(instName, llvmStruct, instFields); + // Field-type lowering and Pass 1 signature declaration resolve in + // the generic's DEFINING module scope — a field naming the owner's + // private sibling type must not trip the trigger-site module's + // handle-privacy gate. (Pass 2 bodies push the same module + // per-method below.) + const_cast(*this).pushBodyModule( + definingModulePath_); + std::vector fieldLLVM; fieldLLVM.reserve(instFields.size()); for (const auto &f : instFields) { @@ -1851,6 +1869,7 @@ TypeIdx JamCodegenContext::instantiateStructExpr( if (savedBB) { JamLLVMPositionBuilderAtEnd(getBuilder(), savedBB); } } + const_cast(*this).popBodyModule(); return typePool.internNamed(stringPool.intern(instName)); } diff --git a/src/codegen.h b/src/codegen.h index f985364..7e7786b 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -478,6 +478,22 @@ class JamCodegenContext { // Resolves `handle` against the CURRENT body module's imports (the "" // scope for the entry module). No cross-module fallback -- see the .cpp. const ImportHandleInfo *getImportHandle(const std::string &handle) const; + + // Privacy gate for handle-spelled dotted type names. Private types + // register under their qualified identity (`lib.Private`) so their + // own module resolves them — but when an import HANDLE shares the + // module's name, the source spelling `lib.Private` coincides with + // that registry key. A spelling whose first segment is one of the + // current module's import handles and whose member is private to + // that module must miss, so the `is not exported` diagnostic fires + // instead of leaking the type. + bool handlePrivacyBlocked(const std::string &dotted) const { + size_t dot = dotted.find('.'); + if (dot == std::string::npos) return false; + const ImportHandleInfo *ih = getImportHandle(dotted.substr(0, dot)); + if (ih == nullptr) return false; + return ih->privateNames.count(dotted.substr(dot + 1)) != 0; + } std::string formatNamespaceLookupError(const std::string &kind, const std::string &qualified) const; diff --git a/src/jam_llvm.cpp b/src/jam_llvm.cpp index 66e49f7..b599845 100644 --- a/src/jam_llvm.cpp +++ b/src/jam_llvm.cpp @@ -37,6 +37,9 @@ #include "llvm/Target/TargetOptions.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" +#include "llvm/Transforms/IPO/GlobalDCE.h" +#include "llvm/Transforms/IPO/Internalize.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" #include @@ -510,6 +513,11 @@ void JamLLVMSetFunctionNoReturn(JamFunctionRef func) { UNWRAP_FUNCTION(func)->addFnAttr(llvm::Attribute::NoReturn); } +void JamLLVMAppendToUsed(JamModuleRef mod, JamFunctionRef func) { + llvm::GlobalValue *gv = UNWRAP_FUNCTION(func); + llvm::appendToUsed(*UNWRAP_MODULE(mod), {gv}); +} + void JamLLVMAddParamAttrSret(JamFunctionRef func, unsigned argIdx, JamTypeRef pointeeType, unsigned align) { llvm::Function *F = UNWRAP_FUNCTION(func); @@ -1258,6 +1266,25 @@ bool JamLLVMEmitObjectFile(JamModuleRef mod, JamTargetMachineRef tm, break; } + // Internalize + strip dead code BEFORE the optimization pipeline. + // Every loaded module's every function is emitted eagerly, so a + // typical program carries unused std (and project) functions that + // the O2/O3 pipeline would otherwise fully optimize — and ISel / + // register allocation would lower — only for the linker's + // dead-strip to discard the result. `main` is preserved by the + // predicate; `export` fns sit in llvm.used (see jirDeclarePrototype + // via JamLLVMAppendToUsed), which InternalizePass always respects; + // declarations are untouched. Skipped under LTO, where the link-time + // pipeline owns whole-program internalization. + if (level != llvm::OptimizationLevel::O0 && lto == JAM_LTO_OFF) { + llvm::ModulePassManager pre; + pre.addPass(llvm::InternalizePass([](const llvm::GlobalValue &gv) { + return gv.getName() == "main"; + })); + pre.addPass(llvm::GlobalDCEPass()); + pre.run(*M, mam); + } + // LTO mode swaps in the LTO pre-link pipeline. The actual cross-module // optimization happens at link time inside lld/ld's LTO plugin once it // sees this module's bitcode plus any other LTO inputs. diff --git a/src/jam_llvm.h b/src/jam_llvm.h index 7c3ef07..b1b2d97 100644 --- a/src/jam_llvm.h +++ b/src/jam_llvm.h @@ -192,6 +192,12 @@ JAM_EXTERN_C void JamLLVMAddRetAttrZeroExt(JamFunctionRef func); JAM_EXTERN_C void JamLLVMSetFunctionNoReturn(JamFunctionRef func); +// Add `func` to llvm.used so whole-module internalization (the +// pre-pipeline InternalizePass in JamLLVMEmitObjectFile) never strips +// or hides it. Used for `export fn` — symbols that exist for external +// C callers the optimizer can't see. +JAM_EXTERN_C void JamLLVMAppendToUsed(JamModuleRef mod, JamFunctionRef func); + JAM_EXTERN_C void JamLLVMApplyDefaultFnAttrs(JamFunctionRef func, bool isExtern); diff --git a/src/jir_codegen.cpp b/src/jir_codegen.cpp index d84ced4..fce1a62 100644 --- a/src/jir_codegen.cpp +++ b/src/jir_codegen.cpp @@ -1103,6 +1103,10 @@ void jirDeclarePrototype(const JirFunction &jfn, JamCodegenContext &ctx) { if (externalLinkage) { JamLLVMSetLinkage(reinterpret_cast(f), JAM_LINKAGE_EXTERNAL); + // `export` symbols exist for C callers the optimizer can't see + // — pin them in llvm.used so the pre-pipeline internalize / + // global-DCE pass in JamLLVMEmitObjectFile never touches them. + if (jfn.isExport) { JamLLVMAppendToUsed(ctx.getModule(), f); } JamLLVMSetFunctionCallConv(f, JAM_CALLCONV_C); // C ABI requires bool args / returns to be zero-extended to // the underlying register width. Internal-linkage callers use diff --git a/src/main.cpp b/src/main.cpp index c289702..cd52f73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -417,8 +417,15 @@ static int compileAndRun(const std::string &filename, // name, resolves the right one). The qualified name matches the // key used by the struct/enum/union registries below and by the // requalified field/body TypeIdxs that reference them. + // + // PRIVATE types register too — a module's own bodies and pub-fn + // signatures reference them (`fn freshCpu() Cpu` where `Cpu` is + // module-private), and those references requalify to the + // qualified key. Privacy is enforced at NAME RESOLUTION: other + // modules' bare spellings can't reach a qualified key (their + // ownership maps only carry pub destructured names, and handle + // aliases register pub-only). for (auto &s : m->Structs) { - if (publicOnly && !s->isPub) continue; jam::DeclIndex idx = codegenCtx.declTable().create( jam::DeclKind::Struct, qualifyTypeName(s->modulePath, s->Name)); auto &d = codegenCtx.declTable().get(idx); @@ -426,7 +433,6 @@ static int compileAndRun(const std::string &filename, setSrc(d, s->Name); } for (auto &e : m->Enums) { - if (publicOnly && !e->isPub) continue; jam::DeclIndex idx = codegenCtx.declTable().create( jam::DeclKind::Enum, qualifyTypeName(e->modulePath, e->Name)); auto &d = codegenCtx.declTable().get(idx); @@ -434,7 +440,6 @@ static int compileAndRun(const std::string &filename, setSrc(d, e->Name); } for (auto &u : m->Unions) { - if (publicOnly && !u->isPub) continue; jam::DeclIndex idx = codegenCtx.declTable().create( jam::DeclKind::Union, qualifyTypeName(u->modulePath, u->Name)); auto &d = codegenCtx.declTable().get(idx); @@ -475,8 +480,8 @@ static int compileAndRun(const std::string &filename, return out; }; auto declareStructs = [&](ModuleAST *m, bool publicOnly) { + (void)publicOnly; // see registerTopLevelDecls: private types register too for (auto &s : m->Structs) { - if (publicOnly && !s->isPub) continue; std::string q = qualifyTypeName(s->modulePath, s->Name); JamTypeRef structType = JamLLVMStructCreateNamed(codegenCtx.getContext(), q.c_str()); @@ -485,8 +490,8 @@ static int compileAndRun(const std::string &filename, } }; auto declareUnions = [&](ModuleAST *m, bool publicOnly) { + (void)publicOnly; for (auto &u : m->Unions) { - if (publicOnly && !u->isPub) continue; std::string q = qualifyTypeName(u->modulePath, u->Name); JamTypeRef unionType = JamLLVMStructCreateNamed(codegenCtx.getContext(), q.c_str()); @@ -495,8 +500,8 @@ static int compileAndRun(const std::string &filename, } }; auto declareEnums = [&](ModuleAST *m, bool publicOnly) { + (void)publicOnly; for (auto &e : m->Enums) { - if (publicOnly && !e->isPub) continue; std::vector variants; variants.reserve(e->Variants.size()); for (auto &v : e->Variants) { @@ -633,6 +638,11 @@ static int compileAndRun(const std::string &filename, // already-populated global registry — or, via getFunctionAST's // fallback, against the generic's defining-module namespace. for (const auto &[path, importedModule] : resolver.getLoadedModules()) { + // Resolve each module's signature types in ITS OWN scope — a + // signature naming the module's private type must not trip the + // handle-privacy gate of whichever module the iteration would + // otherwise run under. + codegenCtx.pushBodyModule(path); for (auto &func : importedModule->Functions) { if (func->isGeneric()) continue; // Pub fns need prototypes so callers can call them. Private @@ -644,6 +654,7 @@ static int compileAndRun(const std::string &filename, codegenCtx.getStringPool()); jirDeclarePrototype(jfn, codegenCtx); } + codegenCtx.popBodyModule(); } // Register every flat `handle.X` mapping for a given (handle name, // resolved module). Shared by direct-import bindings and module- @@ -1175,11 +1186,15 @@ static int compileAndRun(const std::string &filename, } } { + // Method signatures resolve in the struct's own + // module scope (see the Pass B comment). + codegenCtx.pushBodyModule(meth->modulePath); JirFunction jfn = astgenMetadata(*meth, codegenCtx); jfn.name = mangledFunctionName(*meth, codegenCtx.getTypePool(), codegenCtx.getStringPool()); jirDeclarePrototype(jfn, codegenCtx); + codegenCtx.popBodyModule(); } codegenCtx.registerFunctionAST(qself + "." + meth->Name, meth.get()); -- 2.51.2