Skip to content

Commit ef5ed32

Browse files
committed
Run global destructors on exit
This commit enables global destructors to run after main. Destructors that are in genericjs are not run. The new wrapper pass is run to lower functions with the destructor attribute into calls to __cxa_atexit. If the libc exit function is not reachable from the code, the destructors are removed. Finally, exit is now called automatically after the main function has finished running in WASI mode, or when -pthread is passed.
1 parent cfa5499 commit ef5ed32

File tree

11 files changed

+159
-13
lines changed

11 files changed

+159
-13
lines changed

clang/lib/CodeGen/CGClass.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,22 +1728,13 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
17281728
// possible to delegate the destructor body to the complete
17291729
// destructor. Do so.
17301730
if (DtorType == Dtor_Deleting) {
1731-
if(getTarget().isByteAddressable() || Dtor->getParent()->hasAttr<AsmJSAttr>()) {
17321731
RunCleanupsScope DtorEpilogue(*this);
17331732
EnterDtorCleanups(Dtor, Dtor_Deleting);
17341733
if (HaveInsertPoint()) {
17351734
QualType ThisTy = Dtor->getThisObjectType();
17361735
EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false,
17371736
/*Delegating=*/false, LoadCXXThisAddress(), ThisTy);
17381737
}
1739-
} else {
1740-
// NOTE: Code is duplicated to avoid RAII cleanup
1741-
if (HaveInsertPoint()) {
1742-
QualType ThisTy = Dtor->getThisObjectType();
1743-
EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false,
1744-
/*Delegating=*/false, LoadCXXThisAddress(), ThisTy);
1745-
}
1746-
}
17471738
return;
17481739
}
17491740

clang/lib/CodeGen/CGDeclCXX.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D,
143143
CGF.needsEHCleanup(DtorKind), &D);
144144
Argument = llvm::Constant::getNullValue(CGF.Int8PtrTy);
145145
}
146-
147-
if(CGM.getTarget().isByteAddressable())
146+
// We do not register global destructors if they are genericjs.
147+
if (CGM.getTarget().isByteAddressable() || D.hasAttr<AsmJSAttr>())
148148
CGM.getCXXABI().registerGlobalDtor(CGF, D, Func, Argument);
149149
}
150150

@@ -1154,6 +1154,8 @@ llvm::Function *CodeGenFunction::generateDestroyHelper(
11541154
llvm::FunctionType *FTy = CGM.getTypes().GetFunctionType(FI);
11551155
llvm::Function *fn = CGM.CreateGlobalInitOrCleanUpFunction(
11561156
FTy, "__cxx_global_array_dtor", FI, VD->getLocation());
1157+
if (getContext().getTargetInfo().getTriple().isCheerpWasm())
1158+
fn->setSection("asmjs");
11571159

11581160
CurEHLocation = VD->getBeginLoc();
11591161

clang/lib/Driver/ToolChains/WebAssembly.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ void cheerp::CheerpOptimizer::ConstructJob(Compilation &C, const JobAction &JA,
762762
CmdArgs.push_back("-cheerp-keep-invokes");
763763
addPass("function(simplifycfg)");
764764

765+
addPass("LowerGlobalDestructors");
765766
addPass("CallConstructors");
766767
addPass("GlobalDepsAnalyzer");
767768
addPass("TypeOptimizer");

llvm/include/llvm/Cheerp/GlobalDepsAnalyzer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ class GlobalDepsAnalyzer
219219
* \warning Even if this returns 0, it does *not* mean that the module has
220220
* not been modified. This function also *reorders* the variables inside the module.
221221
*/
222+
void removeGlobalDestructors(llvm::Module& M);
223+
222224
int filterModule( const llvm::DenseSet<const llvm::Function*>&, llvm::Module & );
223225

224226
static bool isMathIntrinsic(const llvm::Function* F);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===-- Cheerp/LowerGlobalDestructors.h - Cheerp helper -------------------===//
2+
//
3+
// Cheerp: The C++ compiler for the Web
4+
//
5+
// This file is distributed under the Apache License v2.0 with LLVM Exceptions.
6+
// See LICENSE.TXT for details.
7+
//
8+
// Copyright 2025 Leaning Technologies
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef CHEERP_LOWER_GLOBAL_DESTRUCTORS_H
13+
#define CHEERP_LOWER_GLOBAL_DESTRUCTORS_H
14+
15+
#include "llvm/IR/PassManager.h"
16+
17+
namespace cheerp{
18+
19+
using namespace llvm;
20+
21+
class LowerGlobalDestructorsPass: public PassInfoMixin<LowerGlobalDestructorsPass> {
22+
private:
23+
void filterGenericJSDestructors(Module& M);
24+
public:
25+
PreservedAnalyses run(Module& M, ModuleAnalysisManager& MAM);
26+
static bool isRequired() { return true;}
27+
};
28+
29+
}
30+
31+
#endif

llvm/include/llvm/Cheerp/PassRegistry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "llvm/Cheerp/CommandLine.h"
4343
#include "llvm/Cheerp/CheerpLowerAtomic.h"
4444
#include "llvm/Cheerp/ThreadLocalLowering.h"
45+
#include "llvm/Cheerp/LowerGlobalDestructors.h"
4546

4647
namespace cheerp {
4748

llvm/lib/CheerpUtils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_llvm_component_library(LLVMCheerpUtils
3636
CheerpLowerAtomic.cpp
3737
JsExport.cpp
3838
ThreadLocalLowering.cpp
39+
LowerGlobalDestructors.cpp
3940
)
4041

4142
add_dependencies(LLVMCheerpUtils intrinsics_gen)

llvm/lib/CheerpUtils/CallConstructors.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,11 @@ PreservedAnalyses CallConstructorsPass::run(llvm::Module &M, llvm::ModuleAnalysi
138138
{
139139
ExitCode = Builder.CreateCall(Main->getFunctionType(), Main);
140140
}
141-
if (Wasi)
141+
if (!LowerAtomics || Wasi)
142142
{
143-
Function* Exit = M.getFunction("__syscall_exit");
143+
// In WASI mode, or if -pthread has been passed, we call exit after main, which will run global destructors
144+
Function* Exit = M.getFunction("exit");
145+
assert(Exit != nullptr);
144146
if (ExitCode->getType() != Builder.getInt32Ty())
145147
ExitCode = ConstantInt::get(Builder.getInt32Ty(), 0);
146148
Builder.CreateCall(Exit->getFunctionType(), Exit, ExitCode);

llvm/lib/CheerpUtils/GlobalDepsAnalyzer.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,11 @@ bool GlobalDepsAnalyzer::runOnModule( llvm::Module & module )
796796
markAsReachableIfPresent(module.getFunction("__udivti3"));
797797
}
798798

799+
// If libc exit isn't called, remove global destructors.
800+
Function* exitFunction = module.getFunction("exit");
801+
if (!llcPass && (exitFunction == nullptr || !isReachable(module.getFunction("exit"))))
802+
removeGlobalDestructors(module);
803+
799804
NumRemovedGlobals = filterModule(droppedMathBuiltins, module);
800805

801806
if(hasUndefinedSymbolErrors)
@@ -1566,6 +1571,23 @@ bool GlobalDepsAnalyzer::isMathIntrinsic(const llvm::Function* F)
15661571
(builtinID == BuiltinInstr::MOD_F);
15671572
}
15681573

1574+
void GlobalDepsAnalyzer::removeGlobalDestructors(llvm::Module& M)
1575+
{
1576+
// The goal is to empty the function cxa_atexit, and let optimization do the rest.
1577+
Function* cxaAtexit = M.getFunction("__cxa_atexit");
1578+
if (cxaAtexit == nullptr)
1579+
return;
1580+
1581+
GlobalValue::LinkageTypes linkage = cxaAtexit->getLinkage();
1582+
cxaAtexit->deleteBody();
1583+
BasicBlock* block = BasicBlock::Create(M.getContext(), "entry", cxaAtexit);
1584+
ConstantInt* ret = ConstantInt::get(Type::getInt32Ty(M.getContext()), 0);
1585+
IRBuilder<> Builder(M.getContext());
1586+
Builder.SetInsertPoint(block);
1587+
Builder.CreateRet(ret);
1588+
cxaAtexit->setLinkage(linkage);
1589+
}
1590+
15691591
int GlobalDepsAnalyzer::filterModule( const DenseSet<const Function*>& droppedMathBuiltins, Module & module )
15701592
{
15711593
std::vector< llvm::GlobalValue * > eraseQueue;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//===-- ThreadLocalLowering.h - Cheerp helper -------------------------===//
2+
//
3+
// Cheerp: The C++ compiler for the Web
4+
//
5+
// This file is distributed under the Apache License v2.0 with LLVM Exceptions.
6+
// See LICENSE.TXT for details.
7+
//
8+
// Copyright 2025 Leaning Technologies
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#include <unordered_set>
13+
#include "llvm/Cheerp/LowerGlobalDestructors.h"
14+
#include "llvm/IR/Constants.h"
15+
#include "llvm/IR/Module.h"
16+
#include "llvm/Transforms/Utils/LowerGlobalDtors.h"
17+
18+
using namespace llvm;
19+
20+
namespace cheerp
21+
{
22+
23+
void LowerGlobalDestructorsPass::filterGenericJSDestructors(Module& M)
24+
{
25+
GlobalVariable* var = M.getGlobalVariable("llvm.global_dtors");
26+
if (!var || !var->hasInitializer())
27+
return;
28+
ConstantArray* initList = dyn_cast<ConstantArray>(var->getInitializer());
29+
if (!initList)
30+
return;
31+
32+
GlobalValue::LinkageTypes linkageTypes = var->getLinkage();
33+
SmallVector<Constant*, 8> validDestructors;
34+
Type* elementType = nullptr;
35+
36+
// Loop over the global destructors, put all the ones tagged asmjs into a new list.
37+
for (Value* O: initList->operands())
38+
{
39+
ConstantStruct* CS = dyn_cast<ConstantStruct>(O);
40+
if (!CS)
41+
continue;
42+
43+
Constant* destructor = CS->getOperand(1);
44+
if (destructor->isNullValue())
45+
break;
46+
47+
elementType = O->getType();
48+
Function* destructorFunc = dyn_cast<Function>(destructor);
49+
assert(destructorFunc);
50+
if (destructorFunc->getSection() == "asmjs")
51+
validDestructors.push_back(cast<Constant>(O));
52+
}
53+
54+
var->eraseFromParent();
55+
uint32_t numElements = validDestructors.size();
56+
// If there are no valid destructors, don't create a new global.
57+
if (numElements == 0)
58+
return;
59+
60+
ArrayType* arrayType = ArrayType::get(elementType, numElements);
61+
Constant* newInitList = ConstantArray::get(arrayType, validDestructors);
62+
new GlobalVariable(M, arrayType, true, linkageTypes, newInitList, "llvm.global_dtors");
63+
}
64+
65+
PreservedAnalyses LowerGlobalDestructorsPass::run(Module& M, ModuleAnalysisManager& MAM)
66+
{
67+
ModulePassManager MPM;
68+
MPM.addPass(LowerGlobalDtorsPass());
69+
70+
// Collect all currently existing functions in a set.
71+
std::unordered_set<Function*> functionsBeforePass;
72+
for (Function& F: M.getFunctionList())
73+
functionsBeforePass.insert(&F);
74+
75+
// Remove the destructors that aren't tagged asmjs.
76+
filterGenericJSDestructors(M);
77+
78+
// Run the LowerGlobalDtorsPass.
79+
PreservedAnalyses PA = MPM.run(M, MAM);
80+
81+
// The functions that weren't in the list before are the new functions
82+
// created by the LowerGlobalDtorsPass. Tag them asmjs.
83+
for (Function& F: M.getFunctionList())
84+
{
85+
if (!functionsBeforePass.count(&F))
86+
F.setSection("asmjs");
87+
}
88+
89+
return PA;
90+
}
91+
92+
}

0 commit comments

Comments
 (0)