Index: lib/Target/WebAssembly/CMakeLists.txt =================================================================== --- lib/Target/WebAssembly/CMakeLists.txt +++ lib/Target/WebAssembly/CMakeLists.txt @@ -24,6 +24,7 @@ WebAssemblyISelLowering.cpp WebAssemblyInstrInfo.cpp WebAssemblyLowerBrUnless.cpp + WebAssemblyLowerDestructors.cpp WebAssemblyLowerEmscriptenEHSjLj.cpp WebAssemblyMachineFunctionInfo.cpp WebAssemblyMCInstLower.cpp Index: lib/Target/WebAssembly/WebAssembly.h =================================================================== --- lib/Target/WebAssembly/WebAssembly.h +++ lib/Target/WebAssembly/WebAssembly.h @@ -28,6 +28,7 @@ // LLVM IR passes. ModulePass *createWebAssemblyLowerEmscriptenEHSjLj(bool DoEH, bool DoSjLj); void initializeWebAssemblyLowerEmscriptenEHSjLjPass(PassRegistry &); +ModulePass *createWebAssemblyLowerDestructors(); ModulePass *createWebAssemblyFixFunctionBitcasts(); FunctionPass *createWebAssemblyOptimizeReturned(); Index: lib/Target/WebAssembly/WebAssemblyLowerDestructors.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyLowerDestructors.cpp +++ lib/Target/WebAssembly/WebAssemblyLowerDestructors.cpp @@ -0,0 +1,160 @@ +//===-- WebAssemblyLowerDestructors.cpp - Lower Destructors ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Lower @llvm.global_dtors. +/// +/// WebAssembly doesn't have a builtin way to invoke static destructors. +/// Implement @llvm.global_dtors by creating wrapper functions that are +/// registered in @llvm.global_ctors and which contain a call to +/// `__cxa_atexit` to register their destructor functions. +/// +//===----------------------------------------------------------------------===// + +#include "WebAssembly.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/Module.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" +#include "llvm/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +using namespace llvm; + +#define DEBUG_TYPE "wasm-lower-destructors" + +namespace { +class LowerDestructors final : public ModulePass { + StringRef getPassName() const override { + return "WebAssembly Lower Destructors"; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + ModulePass::getAnalysisUsage(AU); + } + + bool runOnModule(Module &M) override; + +public: + static char ID; + LowerDestructors() : ModulePass(ID) {} +}; +} // End anonymous namespace + +char LowerDestructors::ID = 0; +ModulePass *llvm::createWebAssemblyLowerDestructors() { + return new LowerDestructors(); +} + +bool LowerDestructors::runOnModule(Module &M) { + GlobalVariable *GV = M.getGlobalVariable("llvm.global_dtors"); + if (!GV) + return false; + + const ConstantArray *InitList = dyn_cast(GV->getInitializer()); + if (!InitList) + return false; + + // Sanity-check @llvm.global_dtor's type. + StructType *ETy = dyn_cast(InitList->getType()->getElementType()); + if (!ETy || ETy->getNumElements() != 3 || + !ETy->getTypeAtIndex(0U)->isIntegerTy() || + !ETy->getTypeAtIndex(1U)->isPointerTy() || + !ETy->getTypeAtIndex(2U)->isPointerTy()) + return false; // Not (int, ptr, ptr). + + // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); + LLVMContext &C = M.getContext(); + PointerType *VoidStar = PointerType::get(Type::getInt8Ty(C), 0); + Type *AtExitFuncArgs[] = { VoidStar }; + FunctionType *AtExitFuncType = FunctionType::get( + Type::getVoidTy(C), + AtExitFuncArgs, + /*isVarArg=*/false); + + Type *AtExitArgs[] = { + PointerType::get(AtExitFuncType, 0), + VoidStar, + VoidStar + }; + FunctionType *AtExitType = FunctionType::get( + Type::getInt32PtrTy(C), + AtExitArgs, + /*isVarArg=*/false); + Constant *AtExit = M.getOrInsertFunction("__cxa_atexit", AtExitType); + + // Collect the contents of @llvm.global_dtors, collated by priority. + std::map > DtorFuncs; + for (Value *O : InitList->operands()) { + ConstantStruct *CS = dyn_cast(O); + if (!CS) continue; // Malformed. + + ConstantInt *Priority = dyn_cast(CS->getOperand(0)); + if (!Priority) continue; // Malformed. + uint16_t PriorityValue = Priority->getLimitedValue(UINT16_MAX); + + Constant *DtorFunc = CS->getOperand(1); + if (DtorFunc->isNullValue()) + break; // Found a null terminator, skip the rest. + + // Ignore the third operand. Wasm doesn't support unloading code, so + // it isn't significant. + + DtorFuncs[PriorityValue].push_back(DtorFunc); + } + + // For each unique priority level, generate a function to call all the + // destructors at that level, and a function to register the first function + // with __cxa_atexit. + for (auto &Pair : DtorFuncs) { + Function *CallDtors = + Function::Create(AtExitFuncType, Function::PrivateLinkage, + "call_dtors", &M); + BasicBlock *BB = BasicBlock::Create(C, "body", CallDtors); + + for (auto Dtor : Pair.second) + CallInst::Create(Dtor, "", BB); + ReturnInst::Create(C, BB); + + FunctionType *VoidVoid = FunctionType::get(Type::getVoidTy(C), + /*isVarArg=*/false); + Function *RegisterCallDtors = + Function::Create(VoidVoid, Function::PrivateLinkage, + "register_call_dtors", &M); + BasicBlock *EntryBB = BasicBlock::Create(C, "body", RegisterCallDtors); + BasicBlock *FailBB = BasicBlock::Create(C, "fail", RegisterCallDtors); + BasicBlock *RetBB = BasicBlock::Create(C, "return", RegisterCallDtors); + + Value *Null = ConstantPointerNull::get(VoidStar); + Value *Args[] = { CallDtors, Null, Null }; + Value *Res = CallInst::Create(AtExit, Args, "call_cxa_atexit", EntryBB); + Value *Cmp = new ICmpInst(*EntryBB, ICmpInst::ICMP_NE, Res, + Constant::getNullValue(Res->getType())); + BranchInst::Create(FailBB, RetBB, Cmp, EntryBB); + + // If `__cxa_atexit` hits out of memory, trap, so that we don't misbehave. + // This should be very rare, because if the process is running out of memory + // before main has even started, something is wrong. + CallInst::Create(Intrinsic::getDeclaration(&M, Intrinsic::trap), + "", FailBB); + new UnreachableInst(C, FailBB); + + ReturnInst::Create(C, RetBB); + + // Now register the registration function with @llvm.global_ctors. + appendToGlobalCtors(M, RegisterCallDtors, Pair.first); + } + + // Now that we've lowered everything, remove @llvm.global_dtors. + GV->eraseFromParent(); + + return true; +} Index: lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -175,6 +175,9 @@ // control specifically what gets lowered. addPass(createAtomicExpandPass()); + // Lower .llvm.global_dtors into .llvm_global_ctors with __cxa_atexit calls. + addPass(createWebAssemblyLowerDestructors()); + // Fix function bitcasts, as WebAssembly requires caller and callee signatures // to match. addPass(createWebAssemblyFixFunctionBitcasts()); Index: test/CodeGen/WebAssembly/lower-dtors.ll =================================================================== --- test/CodeGen/WebAssembly/lower-dtors.ll +++ test/CodeGen/WebAssembly/lower-dtors.ll @@ -0,0 +1,27 @@ +; RUN: llc < %s -asm-verbose=false | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +declare void @orig_ctor() +declare void @orig_dtor0() +declare void @orig_dtor1a() +declare void @orig_dtor1b() +declare void @orig_dtor65536() + +@llvm.global_ctors = appending global +[1 x { i32, void ()*, i8* }] +[ + { i32, void ()*, i8* } { i32 200, void ()* @orig_ctor, i8* null } +] + +@llvm.global_dtors = appending global +[4 x { i32, void ()*, i8* }] +[ + { i32, void ()*, i8* } { i32 0, void ()* @orig_dtor0, i8* null }, + { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1a, i8* null }, + { i32, void ()*, i8* } { i32 1, void ()* @orig_dtor1b, i8* null }, + { i32, void ()*, i8* } { i32 65535, void ()* @orig_dtor65536, i8* null } +] + +; CHECK-NOT: .fini_array