diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 3882e4ca3b3f..feabcba887d6 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1728,7 +1728,6 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) { // possible to delegate the destructor body to the complete // destructor. Do so. if (DtorType == Dtor_Deleting) { - if(getTarget().isByteAddressable() || Dtor->getParent()->hasAttr()) { RunCleanupsScope DtorEpilogue(*this); EnterDtorCleanups(Dtor, Dtor_Deleting); if (HaveInsertPoint()) { @@ -1736,14 +1735,6 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) { EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false, /*Delegating=*/false, LoadCXXThisAddress(), ThisTy); } - } else { - // NOTE: Code is duplicated to avoid RAII cleanup - if (HaveInsertPoint()) { - QualType ThisTy = Dtor->getThisObjectType(); - EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false, - /*Delegating=*/false, LoadCXXThisAddress(), ThisTy); - } - } return; } diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp index 9e10558e8fc7..5c45c75bad40 100644 --- a/clang/lib/CodeGen/CGDeclCXX.cpp +++ b/clang/lib/CodeGen/CGDeclCXX.cpp @@ -143,8 +143,8 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D, CGF.needsEHCleanup(DtorKind), &D); Argument = llvm::Constant::getNullValue(CGF.Int8PtrTy); } - - if(CGM.getTarget().isByteAddressable()) + // We do not register global destructors if they are genericjs. + if (CGM.getTarget().isByteAddressable() || D.hasAttr()) CGM.getCXXABI().registerGlobalDtor(CGF, D, Func, Argument); } @@ -1154,6 +1154,8 @@ llvm::Function *CodeGenFunction::generateDestroyHelper( llvm::FunctionType *FTy = CGM.getTypes().GetFunctionType(FI); llvm::Function *fn = CGM.CreateGlobalInitOrCleanUpFunction( FTy, "__cxx_global_array_dtor", FI, VD->getLocation()); + if (getContext().getTargetInfo().getTriple().isCheerpWasm()) + fn->setSection("asmjs"); CurEHLocation = VD->getBeginLoc(); diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp index 643f0222bab8..c65b747ddd5e 100644 --- a/clang/lib/Driver/ToolChains/WebAssembly.cpp +++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp @@ -762,6 +762,7 @@ void cheerp::CheerpOptimizer::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-cheerp-keep-invokes"); addPass("function(simplifycfg)"); + addPass("LowerGlobalDestructors"); addPass("CallConstructors"); addPass("GlobalDepsAnalyzer"); addPass("TypeOptimizer"); diff --git a/compiler-rt/lib/asan/asan_cheerpwasm.cpp b/compiler-rt/lib/asan/asan_cheerpwasm.cpp index cd9f4de2de5f..dc1b47e0f470 100644 --- a/compiler-rt/lib/asan/asan_cheerpwasm.cpp +++ b/compiler-rt/lib/asan/asan_cheerpwasm.cpp @@ -39,8 +39,11 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) {} static void (*tsd_destructor)(void *tsd) = nullptr; +// Cheerp: for now we've removed the initializer for tsd_key's constructor. +// It caused a crash in ASAN because the constructor for the global is run after the asan module constructor has already used and set it. +// Originally the global is meant to be a thread_local, but thread_local constructors and destructors are not yet supported. struct tsd_key { - tsd_key() : key(nullptr) {} + tsd_key() {} ~tsd_key() { CHECK(tsd_destructor); if (key) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_cheerpwasm.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_cheerpwasm.cpp index 18320d222cd5..5155ba710193 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_cheerpwasm.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_cheerpwasm.cpp @@ -197,7 +197,6 @@ bool WriteToFile(fd_t, const void *, uptr, uptr *, error_t *) { bool FileExists(const char *) { UNIMPLEMENTED(); } bool IsAbsolutePath(const char *) { UNIMPLEMENTED(); } bool ReadFromFile(fd_t, void *, uptr, uptr *, error_t *) { UNIMPLEMENTED(); } -void UnsetAlternateSignalStack() { UNIMPLEMENTED(); } bool DontDumpShadowMemory(uptr addr, uptr length) { UNIMPLEMENTED(); } bool IsPathSeparator(const char) { UNIMPLEMENTED(); } bool DirExists(const char *) { UNIMPLEMENTED(); } @@ -213,6 +212,7 @@ void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, } void SetAlternateSignalStack() {} +void UnsetAlternateSignalStack() {} void internal__exit(int exitcode) { exit(exitcode); } diff --git a/llvm/include/llvm/Cheerp/GlobalDepsAnalyzer.h b/llvm/include/llvm/Cheerp/GlobalDepsAnalyzer.h index 14e532d1babd..0c6da04717d3 100644 --- a/llvm/include/llvm/Cheerp/GlobalDepsAnalyzer.h +++ b/llvm/include/llvm/Cheerp/GlobalDepsAnalyzer.h @@ -219,6 +219,8 @@ class GlobalDepsAnalyzer * \warning Even if this returns 0, it does *not* mean that the module has * not been modified. This function also *reorders* the variables inside the module. */ + void removeGlobalDestructors(llvm::Module& M); + int filterModule( const llvm::DenseSet&, llvm::Module & ); static bool isMathIntrinsic(const llvm::Function* F); diff --git a/llvm/include/llvm/Cheerp/LowerGlobalDestructors.h b/llvm/include/llvm/Cheerp/LowerGlobalDestructors.h new file mode 100644 index 000000000000..174a8c423dfe --- /dev/null +++ b/llvm/include/llvm/Cheerp/LowerGlobalDestructors.h @@ -0,0 +1,31 @@ +//===-- Cheerp/LowerGlobalDestructors.h - Cheerp helper -------------------===// +// +// Cheerp: The C++ compiler for the Web +// +// This file is distributed under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE.TXT for details. +// +// Copyright 2025 Leaning Technologies +// +//===----------------------------------------------------------------------===// + +#ifndef CHEERP_LOWER_GLOBAL_DESTRUCTORS_H +#define CHEERP_LOWER_GLOBAL_DESTRUCTORS_H + +#include "llvm/IR/PassManager.h" + +namespace cheerp{ + +using namespace llvm; + +class LowerGlobalDestructorsPass: public PassInfoMixin { +private: + void filterGenericJSDestructors(Module& M); +public: + PreservedAnalyses run(Module& M, ModuleAnalysisManager& MAM); + static bool isRequired() { return true;} +}; + +} + +#endif diff --git a/llvm/include/llvm/Cheerp/PassRegistry.h b/llvm/include/llvm/Cheerp/PassRegistry.h index 50a110c18e7c..6187539b3dbb 100644 --- a/llvm/include/llvm/Cheerp/PassRegistry.h +++ b/llvm/include/llvm/Cheerp/PassRegistry.h @@ -42,6 +42,7 @@ #include "llvm/Cheerp/CommandLine.h" #include "llvm/Cheerp/CheerpLowerAtomic.h" #include "llvm/Cheerp/ThreadLocalLowering.h" +#include "llvm/Cheerp/LowerGlobalDestructors.h" namespace cheerp { diff --git a/llvm/include/llvm/Cheerp/Utility.h b/llvm/include/llvm/Cheerp/Utility.h index e5e187e11b5a..2538ea951d40 100644 --- a/llvm/include/llvm/Cheerp/Utility.h +++ b/llvm/include/llvm/Cheerp/Utility.h @@ -308,6 +308,7 @@ struct GlobalConstructor }; std::vector getGlobalConstructors(llvm::Module& M); +void removeGlobalConstructorsGlobal(llvm::Module& M); uint32_t getIntFromValue(const llvm::Value* v); diff --git a/llvm/lib/CheerpUtils/CMakeLists.txt b/llvm/lib/CheerpUtils/CMakeLists.txt index 47506d52ecd0..deff325595da 100644 --- a/llvm/lib/CheerpUtils/CMakeLists.txt +++ b/llvm/lib/CheerpUtils/CMakeLists.txt @@ -36,6 +36,7 @@ add_llvm_component_library(LLVMCheerpUtils CheerpLowerAtomic.cpp JsExport.cpp ThreadLocalLowering.cpp + LowerGlobalDestructors.cpp ) add_dependencies(LLVMCheerpUtils intrinsics_gen) diff --git a/llvm/lib/CheerpUtils/CallConstructors.cpp b/llvm/lib/CheerpUtils/CallConstructors.cpp index 88366a668dc3..5653a100d8ae 100644 --- a/llvm/lib/CheerpUtils/CallConstructors.cpp +++ b/llvm/lib/CheerpUtils/CallConstructors.cpp @@ -63,9 +63,8 @@ PreservedAnalyses CallConstructorsPass::run(llvm::Module &M, llvm::ModuleAnalysi Builder.CreateCall(GetEnviron->getFunctionType(), GetEnviron); for (Constant* C: cheerp::getGlobalConstructors(M)) - { Builder.CreateCall(Ty, cast(C->getAggregateElement(1)->stripPointerCastsSafe())); - } + removeGlobalConstructorsGlobal(M); if (useUtilityThread) { @@ -139,9 +138,11 @@ PreservedAnalyses CallConstructorsPass::run(llvm::Module &M, llvm::ModuleAnalysi { ExitCode = Builder.CreateCall(Main->getFunctionType(), Main); } - if (Wasi) + if (!LowerAtomics || Wasi) { - Function* Exit = M.getFunction("__syscall_exit"); + // In WASI mode, or if -pthread has been passed, we call exit after main, which will run global destructors + Function* Exit = M.getFunction("exit"); + assert(Exit != nullptr); if (ExitCode->getType() != Builder.getInt32Ty()) ExitCode = ConstantInt::get(Builder.getInt32Ty(), 0); Builder.CreateCall(Exit->getFunctionType(), Exit, ExitCode); diff --git a/llvm/lib/CheerpUtils/GlobalDepsAnalyzer.cpp b/llvm/lib/CheerpUtils/GlobalDepsAnalyzer.cpp index 8251f3668962..3dd978a3a0f6 100644 --- a/llvm/lib/CheerpUtils/GlobalDepsAnalyzer.cpp +++ b/llvm/lib/CheerpUtils/GlobalDepsAnalyzer.cpp @@ -796,6 +796,11 @@ bool GlobalDepsAnalyzer::runOnModule( llvm::Module & module ) markAsReachableIfPresent(module.getFunction("__udivti3")); } + // If libc exit isn't called, remove global destructors. + Function* exitFunction = module.getFunction("exit"); + if (!llcPass && (exitFunction == nullptr || !isReachable(module.getFunction("exit")))) + removeGlobalDestructors(module); + NumRemovedGlobals = filterModule(droppedMathBuiltins, module); if(hasUndefinedSymbolErrors) @@ -1566,6 +1571,23 @@ bool GlobalDepsAnalyzer::isMathIntrinsic(const llvm::Function* F) (builtinID == BuiltinInstr::MOD_F); } +void GlobalDepsAnalyzer::removeGlobalDestructors(llvm::Module& M) +{ + // The goal is to empty the function cxa_atexit, and let optimization do the rest. + Function* cxaAtexit = M.getFunction("__cxa_atexit"); + if (cxaAtexit == nullptr) + return; + + GlobalValue::LinkageTypes linkage = cxaAtexit->getLinkage(); + cxaAtexit->deleteBody(); + BasicBlock* block = BasicBlock::Create(M.getContext(), "entry", cxaAtexit); + ConstantInt* ret = ConstantInt::get(Type::getInt32Ty(M.getContext()), 0); + IRBuilder<> Builder(M.getContext()); + Builder.SetInsertPoint(block); + Builder.CreateRet(ret); + cxaAtexit->setLinkage(linkage); +} + int GlobalDepsAnalyzer::filterModule( const DenseSet& droppedMathBuiltins, Module & module ) { std::vector< llvm::GlobalValue * > eraseQueue; @@ -1582,7 +1604,7 @@ int GlobalDepsAnalyzer::filterModule( const DenseSet& droppedMa bool isClient = TypeSupport::isClientGlobal(var); if( var->hasInitializer() ) { - if( !WasmSharedModule && !isClient && var->getName()!="llvm.global_ctors" ) + if( !WasmSharedModule && !isClient) var->setLinkage(GlobalValue::InternalLinkage); } else if( !isClient && var->getName() != "__cxa_cheerp_clause_table") diff --git a/llvm/lib/CheerpUtils/LowerGlobalDestructors.cpp b/llvm/lib/CheerpUtils/LowerGlobalDestructors.cpp new file mode 100644 index 000000000000..35293423234f --- /dev/null +++ b/llvm/lib/CheerpUtils/LowerGlobalDestructors.cpp @@ -0,0 +1,92 @@ +//===-- ThreadLocalLowering.h - Cheerp helper -------------------------===// +// +// Cheerp: The C++ compiler for the Web +// +// This file is distributed under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE.TXT for details. +// +// Copyright 2025 Leaning Technologies +// +//===----------------------------------------------------------------------===// + +#include +#include "llvm/Cheerp/LowerGlobalDestructors.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Module.h" +#include "llvm/Transforms/Utils/LowerGlobalDtors.h" + +using namespace llvm; + +namespace cheerp +{ + +void LowerGlobalDestructorsPass::filterGenericJSDestructors(Module& M) +{ + GlobalVariable* var = M.getGlobalVariable("llvm.global_dtors"); + if (!var || !var->hasInitializer()) + return; + ConstantArray* initList = dyn_cast(var->getInitializer()); + if (!initList) + return; + + GlobalValue::LinkageTypes linkageTypes = var->getLinkage(); + SmallVector validDestructors; + Type* elementType = nullptr; + + // Loop over the global destructors, put all the ones tagged asmjs into a new list. + for (Value* O: initList->operands()) + { + ConstantStruct* CS = dyn_cast(O); + if (!CS) + continue; + + Constant* destructor = CS->getOperand(1); + if (destructor->isNullValue()) + break; + + elementType = O->getType(); + Function* destructorFunc = dyn_cast(destructor); + assert(destructorFunc); + if (destructorFunc->getSection() == "asmjs") + validDestructors.push_back(cast(O)); + } + + var->eraseFromParent(); + uint32_t numElements = validDestructors.size(); + // If there are no valid destructors, don't create a new global. + if (numElements == 0) + return; + + ArrayType* arrayType = ArrayType::get(elementType, numElements); + Constant* newInitList = ConstantArray::get(arrayType, validDestructors); + new GlobalVariable(M, arrayType, true, linkageTypes, newInitList, "llvm.global_dtors"); +} + +PreservedAnalyses LowerGlobalDestructorsPass::run(Module& M, ModuleAnalysisManager& MAM) +{ + ModulePassManager MPM; + MPM.addPass(LowerGlobalDtorsPass()); + + // Collect all currently existing functions in a set. + std::unordered_set functionsBeforePass; + for (Function& F: M.getFunctionList()) + functionsBeforePass.insert(&F); + + // Remove the destructors that aren't tagged asmjs. + filterGenericJSDestructors(M); + + // Run the LowerGlobalDtorsPass. + PreservedAnalyses PA = MPM.run(M, MAM); + + // The functions that weren't in the list before are the new functions + // created by the LowerGlobalDtorsPass. Tag them asmjs. + for (Function& F: M.getFunctionList()) + { + if (!functionsBeforePass.count(&F)) + F.setSection("asmjs"); + } + + return PA; +} + +} diff --git a/llvm/lib/CheerpUtils/Utility.cpp b/llvm/lib/CheerpUtils/Utility.cpp index 06ed80b137a7..eafe5fce23bd 100644 --- a/llvm/lib/CheerpUtils/Utility.cpp +++ b/llvm/lib/CheerpUtils/Utility.cpp @@ -1038,6 +1038,14 @@ std::vector getGlobalConstructors(Module& module) return ret; } +void removeGlobalConstructorsGlobal(Module& M) +{ + GlobalVariable* var = M.getGlobalVariable("llvm.global_ctors"); + if (!var) + return; + var->eraseFromParent(); +} + const llvm::Loop* findCommonLoop(const llvm::LoopInfo* LI, const llvm::BasicBlock* first, const llvm::BasicBlock* second) { //Find the innermost common loop between two BB. diff --git a/llvm/lib/CheerpWriter/CheerpWriter.cpp b/llvm/lib/CheerpWriter/CheerpWriter.cpp index 206a71bcf612..2b0f371f87c8 100644 --- a/llvm/lib/CheerpWriter/CheerpWriter.cpp +++ b/llvm/lib/CheerpWriter/CheerpWriter.cpp @@ -6719,9 +6719,6 @@ void CheerpWriter::compileGenericJS() } for ( const GlobalVariable & GV : module.getGlobalList() ) { - // Skip global ctors array - if (GV.getName() == "llvm.global_ctors") - continue; if (GV.getSection() != StringRef("asmjs")) compileGlobal(GV); } diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index d5056de81816..79ed078e77da 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -140,6 +140,7 @@ MODULE_PASS("PreExecute", cheerp::PreExecutePass()) MODULE_PASS("FreeAndDeleteRemoval", cheerp::FreeAndDeleteRemovalPass()) MODULE_PASS("CallConstructors", cheerp::CallConstructorsPass()) MODULE_PASS("CheerpLowerAtomic", cheerp::CheerpLowerAtomicPass()) +MODULE_PASS("LowerGlobalDestructors", cheerp::LowerGlobalDestructorsPass()) #undef MODULE_PASS #ifndef MODULE_PASS_WITH_PARAMS