diff options
author | Sam Clegg <sbc@chromium.org> | 2017-12-15 00:17:10 +0000 |
---|---|---|
committer | Sam Clegg <sbc@chromium.org> | 2017-12-15 00:17:10 +0000 |
commit | 5334180cf2fafa3564ed803026f8c2ae7f76b0ce (patch) | |
tree | b3bda965562f5cfb5fdfb61378f3f1b8e62b4192 /lib | |
parent | d7cf6c74e2ebd4bfa9e02f8ffebeeced38d28d12 (diff) |
[WebAssembly] Implement @llvm.global_ctors and @llvm.global_dtors
Summary:
- lowers @llvm.global_dtors by adding @llvm.global_ctors
functions which register the destructors with `__cxa_atexit`.
- impements @llvm.global_ctors with wasm start functions and linker metadata
See [here](https://github.com/WebAssembly/tool-conventions/issues/25) for more background.
Subscribers: jfb, dschuff, mgorny, jgravelle-google, aheejin, sunfish
Differential Revision: https://reviews.llvm.org/D41211
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@320774 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib')
-rw-r--r-- | lib/CodeGen/TargetLoweringObjectFileImpl.cpp | 16 | ||||
-rw-r--r-- | lib/MC/WasmObjectWriter.cpp | 79 | ||||
-rw-r--r-- | lib/Target/WebAssembly/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/Target/WebAssembly/WebAssembly.h | 1 | ||||
-rw-r--r-- | lib/Target/WebAssembly/WebAssemblyLowerGlobalDtors.cpp | 191 | ||||
-rw-r--r-- | lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp | 3 |
6 files changed, 285 insertions, 6 deletions
diff --git a/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/lib/CodeGen/TargetLoweringObjectFileImpl.cpp index 925e67b01f6..24d4baa31e1 100644 --- a/lib/CodeGen/TargetLoweringObjectFileImpl.cpp +++ b/lib/CodeGen/TargetLoweringObjectFileImpl.cpp @@ -1359,6 +1359,18 @@ const MCExpr *TargetLoweringObjectFileWasm::lowerRelativeReference( void TargetLoweringObjectFileWasm::InitializeWasm() { StaticCtorSection = getContext().getWasmSection(".init_array", SectionKind::getData()); - StaticDtorSection = - getContext().getWasmSection(".fini_array", SectionKind::getData()); +} + +MCSection *TargetLoweringObjectFileWasm::getStaticCtorSection( + unsigned Priority, const MCSymbol *KeySym) const { + return Priority == UINT16_MAX ? + StaticCtorSection : + getContext().getWasmSection(".init_array." + utostr(Priority), + SectionKind::getData()); +} + +MCSection *TargetLoweringObjectFileWasm::getStaticDtorSection( + unsigned Priority, const MCSymbol *KeySym) const { + llvm_unreachable("@llvm.global_dtors should have been lowered already"); + return nullptr; } diff --git a/lib/MC/WasmObjectWriter.cpp b/lib/MC/WasmObjectWriter.cpp index b2c5b14bca5..6e76c5fac35 100644 --- a/lib/MC/WasmObjectWriter.cpp +++ b/lib/MC/WasmObjectWriter.cpp @@ -284,7 +284,8 @@ private: void writeDataRelocSection(); void writeLinkingMetaDataSection( ArrayRef<WasmDataSegment> Segments, uint32_t DataSize, - SmallVector<std::pair<StringRef, uint32_t>, 4> SymbolFlags); + const SmallVector<std::pair<StringRef, uint32_t>, 4> &SymbolFlags, + const SmallVector<std::pair<uint16_t, uint32_t>, 2> &InitFuncs); uint32_t getProvisionalValue(const WasmRelocationEntry &RelEntry); void applyRelocations(ArrayRef<WasmRelocationEntry> Relocations, @@ -366,6 +367,10 @@ void WasmObjectWriter::recordRelocation(MCAssembler &Asm, uint64_t FixupOffset = Layout.getFragmentOffset(Fragment) + Fixup.getOffset(); MCContext &Ctx = Asm.getContext(); + // The .init_array isn't translated as data, so don't do relocations in it. + if (FixupSection.getSectionName().startswith(".init_array")) + return; + if (const MCSymbolRefExpr *RefB = Target.getSymB()) { assert(RefB->getKind() == MCSymbolRefExpr::VK_None && "Should not have constructed this"); @@ -905,7 +910,8 @@ void WasmObjectWriter::writeDataRelocSection() { void WasmObjectWriter::writeLinkingMetaDataSection( ArrayRef<WasmDataSegment> Segments, uint32_t DataSize, - SmallVector<std::pair<StringRef, uint32_t>, 4> SymbolFlags) { + const SmallVector<std::pair<StringRef, uint32_t>, 4> &SymbolFlags, + const SmallVector<std::pair<uint16_t, uint32_t>, 2> &InitFuncs) { SectionBookkeeping Section; startSection(Section, wasm::WASM_SEC_CUSTOM, "linking"); SectionBookkeeping SubSection; @@ -937,6 +943,16 @@ void WasmObjectWriter::writeLinkingMetaDataSection( endSection(SubSection); } + if (!InitFuncs.empty()) { + startSection(SubSection, wasm::WASM_INIT_FUNCS); + encodeULEB128(InitFuncs.size(), getStream()); + for (auto &StartFunc : InitFuncs) { + encodeULEB128(StartFunc.first, getStream()); // priority + encodeULEB128(StartFunc.second, getStream()); // function index + } + endSection(SubSection); + } + endSection(Section); } @@ -977,6 +993,7 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, SmallVector<WasmImport, 4> Imports; SmallVector<WasmExport, 4> Exports; SmallVector<std::pair<StringRef, uint32_t>, 4> SymbolFlags; + SmallVector<std::pair<uint16_t, uint32_t>, 2> InitFuncs; SmallPtrSet<const MCSymbolWasm *, 4> IsAddressTaken; unsigned NumFuncImports = 0; SmallVector<WasmDataSegment, 4> DataSegments; @@ -1132,6 +1149,10 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, if (!Section.isWasmData()) continue; + // .init_array sections are handled specially elsewhere. + if (cast<MCSectionWasm>(Sec).getSectionName().startswith(".init_array")) + continue; + DataSize = alignTo(DataSize, Section.getAlignment()); DataSegments.emplace_back(); WasmDataSegment &Segment = DataSegments.back(); @@ -1291,6 +1312,56 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, registerFunctionType(*Fixup.Symbol); } + // Translate .init_array section contents into start functions. + for (const MCSection &S : Asm) { + const auto &WS = static_cast<const MCSectionWasm &>(S); + if (WS.getSectionName().startswith(".fini_array")) + report_fatal_error(".fini_array sections are unsupported"); + if (!WS.getSectionName().startswith(".init_array")) + continue; + if (WS.getFragmentList().empty()) + continue; + if (WS.getFragmentList().size() != 2) + report_fatal_error("only one .init_array section fragment supported"); + const MCFragment &AlignFrag = *WS.begin(); + if (AlignFrag.getKind() != MCFragment::FT_Align) + report_fatal_error(".init_array section should be aligned"); + if (cast<MCAlignFragment>(AlignFrag).getAlignment() != (is64Bit() ? 8 : 4)) + report_fatal_error(".init_array section should be aligned for pointers"); + const MCFragment &Frag = *std::next(WS.begin()); + if (Frag.hasInstructions() || Frag.getKind() != MCFragment::FT_Data) + report_fatal_error("only data supported in .init_array section"); + uint16_t Priority = UINT16_MAX; + if (WS.getSectionName().size() != 11) { + if (WS.getSectionName()[11] != '.') + report_fatal_error(".init_array section priority should start with '.'"); + if (WS.getSectionName().substr(12).getAsInteger(10, Priority)) + report_fatal_error("invalid .init_array section priority"); + } + const auto &DataFrag = cast<MCDataFragment>(Frag); + const SmallVectorImpl<char> &Contents = DataFrag.getContents(); + for (const uint8_t *p = (const uint8_t *)Contents.data(), + *end = (const uint8_t *)Contents.data() + Contents.size(); + p != end; ++p) { + if (*p != 0) + report_fatal_error("non-symbolic data in .init_array section"); + } + for (const MCFixup &Fixup : DataFrag.getFixups()) { + assert(Fixup.getKind() == MCFixup::getKindForSize(is64Bit() ? 8 : 4, false)); + const MCExpr *Expr = Fixup.getValue(); + auto *Sym = dyn_cast<MCSymbolRefExpr>(Expr); + if (!Sym) + report_fatal_error("fixups in .init_array should be symbol references"); + if (Sym->getKind() != MCSymbolRefExpr::VK_WebAssembly_FUNCTION) + report_fatal_error("symbols in .init_array should be for functions"); + auto I = SymbolIndices.find(cast<MCSymbolWasm>(&Sym->getSymbol())); + if (I == SymbolIndices.end()) + report_fatal_error("symbols in .init_array should be defined"); + uint32_t Index = I->second; + InitFuncs.push_back(std::make_pair(Priority, Index)); + } + } + // Write out the Wasm header. writeHeader(Asm); @@ -1301,14 +1372,14 @@ void WasmObjectWriter::writeObject(MCAssembler &Asm, // Skip the "memory" section; we import the memory instead. writeGlobalSection(); writeExportSection(Exports); - // TODO: Start Section writeElemSection(TableElems); writeCodeSection(Asm, Layout, Functions); writeDataSection(DataSegments); writeNameSection(Functions, Imports, NumFuncImports); writeCodeRelocSection(); writeDataRelocSection(); - writeLinkingMetaDataSection(DataSegments, DataSize, SymbolFlags); + writeLinkingMetaDataSection(DataSegments, DataSize, SymbolFlags, + InitFuncs); // TODO: Translate the .comment section to the output. // TODO: Translate debug sections to the output. diff --git a/lib/Target/WebAssembly/CMakeLists.txt b/lib/Target/WebAssembly/CMakeLists.txt index 78b2cdb61b7..68b68bd797b 100644 --- a/lib/Target/WebAssembly/CMakeLists.txt +++ b/lib/Target/WebAssembly/CMakeLists.txt @@ -25,6 +25,7 @@ add_llvm_target(WebAssemblyCodeGen WebAssemblyInstrInfo.cpp WebAssemblyLowerBrUnless.cpp WebAssemblyLowerEmscriptenEHSjLj.cpp + WebAssemblyLowerGlobalDtors.cpp WebAssemblyMachineFunctionInfo.cpp WebAssemblyMCInstLower.cpp WebAssemblyOptimizeLiveIntervals.cpp diff --git a/lib/Target/WebAssembly/WebAssembly.h b/lib/Target/WebAssembly/WebAssembly.h index e04c4db19c8..7ac6c399153 100644 --- a/lib/Target/WebAssembly/WebAssembly.h +++ b/lib/Target/WebAssembly/WebAssembly.h @@ -28,6 +28,7 @@ class FunctionPass; // LLVM IR passes. ModulePass *createWebAssemblyLowerEmscriptenEHSjLj(bool DoEH, bool DoSjLj); void initializeWebAssemblyLowerEmscriptenEHSjLjPass(PassRegistry &); +ModulePass *createWebAssemblyLowerGlobalDtors(); ModulePass *createWebAssemblyFixFunctionBitcasts(); FunctionPass *createWebAssemblyOptimizeReturned(); diff --git a/lib/Target/WebAssembly/WebAssemblyLowerGlobalDtors.cpp b/lib/Target/WebAssembly/WebAssemblyLowerGlobalDtors.cpp new file mode 100644 index 00000000000..0020817aee4 --- /dev/null +++ b/lib/Target/WebAssembly/WebAssemblyLowerGlobalDtors.cpp @@ -0,0 +1,191 @@ +//===-- WebAssemblyLowerGlobalDtors.cpp - Lower @llvm.global_dtors --------===// +// +// 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/ADT/MapVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +using namespace llvm; + +#define DEBUG_TYPE "wasm-lower-global-dtors" + +namespace { +class LowerGlobalDtors final : public ModulePass { + StringRef getPassName() const override { + return "WebAssembly Lower @llvm.global_dtors"; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + ModulePass::getAnalysisUsage(AU); + } + + bool runOnModule(Module &M) override; + +public: + static char ID; + LowerGlobalDtors() : ModulePass(ID) {} +}; +} // End anonymous namespace + +char LowerGlobalDtors::ID = 0; +ModulePass *llvm::createWebAssemblyLowerGlobalDtors() { + return new LowerGlobalDtors(); +} + +bool LowerGlobalDtors::runOnModule(Module &M) { + GlobalVariable *GV = M.getGlobalVariable("llvm.global_dtors"); + if (!GV) + return false; + + const ConstantArray *InitList = dyn_cast<ConstantArray>(GV->getInitializer()); + if (!InitList) + return false; + + // Sanity-check @llvm.global_dtor's type. + StructType *ETy = dyn_cast<StructType>(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). + + // Collect the contents of @llvm.global_dtors, collated by priority and + // associated symbol. + std::map<uint16_t, MapVector<Constant *, std::vector<Constant *> > > DtorFuncs; + for (Value *O : InitList->operands()) { + ConstantStruct *CS = dyn_cast<ConstantStruct>(O); + if (!CS) continue; // Malformed. + + ConstantInt *Priority = dyn_cast<ConstantInt>(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. + + Constant *Associated = CS->getOperand(2); + Associated = cast<Constant>(Associated->stripPointerCastsNoFollowAliases()); + + DtorFuncs[PriorityValue][Associated].push_back(DtorFunc); + } + if (DtorFuncs.empty()) + return false; + + // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); + LLVMContext &C = M.getContext(); + PointerType *VoidStar = Type::getInt8PtrTy(C); + Type *AtExitFuncArgs[] = { VoidStar }; + FunctionType *AtExitFuncTy = FunctionType::get( + Type::getVoidTy(C), + AtExitFuncArgs, + /*isVarArg=*/false); + + Type *AtExitArgs[] = { + PointerType::get(AtExitFuncTy, 0), + VoidStar, + VoidStar + }; + FunctionType *AtExitTy = FunctionType::get( + Type::getInt32Ty(C), + AtExitArgs, + /*isVarArg=*/false); + Constant *AtExit = M.getOrInsertFunction("__cxa_atexit", AtExitTy); + + // Declare __dso_local. + Constant *DsoHandle = M.getNamedValue("__dso_handle"); + if (!DsoHandle) { + Type *DsoHandleTy = Type::getInt8Ty(C); + GlobalVariable *Handle = + new GlobalVariable(M, DsoHandleTy, /*isConstant=*/true, + GlobalVariable::ExternalWeakLinkage, + nullptr, "__dso_handle"); + Handle->setVisibility(GlobalVariable::HiddenVisibility); + DsoHandle = Handle; + } + + // For each unique priority level and associated symbol, generate a function + // to call all the destructors at that level, and a function to register the + // first function with __cxa_atexit. + for (auto &PriorityAndMore : DtorFuncs) { + uint16_t Priority = PriorityAndMore.first; + for (auto &AssociatedAndMore : PriorityAndMore.second) { + Constant *Associated = AssociatedAndMore.first; + + Function *CallDtors = Function::Create( + AtExitFuncTy, Function::PrivateLinkage, + "call_dtors" + + (Priority != UINT16_MAX ? + (Twine(".") + Twine(Priority)) : Twine()) + + (!Associated->isNullValue() ? + (Twine(".") + Associated->getName()) : Twine()), + &M); + BasicBlock *BB = BasicBlock::Create(C, "body", CallDtors); + + for (auto Dtor : AssociatedAndMore.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" + + (Priority != UINT16_MAX ? + (Twine(".") + Twine(Priority)) : Twine()) + + (!Associated->isNullValue() ? + (Twine(".") + Associated->getName()) : Twine()), + &M); + BasicBlock *EntryBB = BasicBlock::Create(C, "entry", RegisterCallDtors); + BasicBlock *FailBB = BasicBlock::Create(C, "fail", RegisterCallDtors); + BasicBlock *RetBB = BasicBlock::Create(C, "return", RegisterCallDtors); + + Value *Null = ConstantPointerNull::get(VoidStar); + Value *Args[] = { CallDtors, Null, DsoHandle }; + Value *Res = CallInst::Create(AtExit, Args, "call", 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, Priority, Associated); + } + } + + // Now that we've lowered everything, remove @llvm.global_dtors. + GV->eraseFromParent(); + + return true; +} diff --git a/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp index 8ad74d9db7b..3cc19ef5fba 100644 --- a/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -175,6 +175,9 @@ void WebAssemblyPassConfig::addIRPasses() { // control specifically what gets lowered. addPass(createAtomicExpandPass()); + // Lower .llvm.global_dtors into .llvm_global_ctors with __cxa_atexit calls. + addPass(createWebAssemblyLowerGlobalDtors()); + // Fix function bitcasts, as WebAssembly requires caller and callee signatures // to match. addPass(createWebAssemblyFixFunctionBitcasts()); |