Skip to content

Commit

Permalink
[WebAssembly] Implement @llvm.global_ctors and @llvm.global_dtors
Browse files Browse the repository at this point in the history
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](WebAssembly/tool-conventions#25) for more background.

Subscribers: jfb, dschuff, mgorny, jgravelle-google, aheejin, sunfish

Differential Revision: https://reviews.llvm.org/D41211

llvm-svn: 320774
  • Loading branch information
sbc100 committed Dec 15, 2017
1 parent 476a739 commit bafe690
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 45 deletions.
4 changes: 4 additions & 0 deletions llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
Expand Up @@ -182,6 +182,10 @@ class TargetLoweringObjectFileWasm : public TargetLoweringObjectFile {
const Function &F) const override;

void InitializeWasm();
MCSection *getStaticCtorSection(unsigned Priority,
const MCSymbol *KeySym) const override;
MCSection *getStaticDtorSection(unsigned Priority,
const MCSymbol *KeySym) const override;

const MCExpr *lowerRelativeReference(const GlobalValue *LHS,
const GlobalValue *RHS,
Expand Down
16 changes: 14 additions & 2 deletions llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
Expand Up @@ -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;
}
79 changes: 75 additions & 4 deletions llvm/lib/MC/WasmObjectWriter.cpp
Expand Up @@ -284,7 +284,8 @@ class WasmObjectWriter : public MCObjectWriter {
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,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/WebAssembly/CMakeLists.txt
Expand Up @@ -25,6 +25,7 @@ add_llvm_target(WebAssemblyCodeGen
WebAssemblyInstrInfo.cpp
WebAssemblyLowerBrUnless.cpp
WebAssemblyLowerEmscriptenEHSjLj.cpp
WebAssemblyLowerGlobalDtors.cpp
WebAssemblyMachineFunctionInfo.cpp
WebAssemblyMCInstLower.cpp
WebAssemblyOptimizeLiveIntervals.cpp
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/WebAssembly/WebAssembly.h
Expand Up @@ -28,6 +28,7 @@ class FunctionPass;
// LLVM IR passes.
ModulePass *createWebAssemblyLowerEmscriptenEHSjLj(bool DoEH, bool DoSjLj);
void initializeWebAssemblyLowerEmscriptenEHSjLjPass(PassRegistry &);
ModulePass *createWebAssemblyLowerGlobalDtors();
ModulePass *createWebAssemblyFixFunctionBitcasts();
FunctionPass *createWebAssemblyOptimizeReturned();

Expand Down
191 changes: 191 additions & 0 deletions llvm/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;
}
3 changes: 3 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
Expand Up @@ -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());
Expand Down

0 comments on commit bafe690

Please sign in to comment.