mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-28 16:11:29 +00:00
ea38ac5ba3
The current PIC model for WebAssembly is more like ELF in that it allows symbol interposition. This means that more functions end up being addressed via the GOT and fewer directly added to the wasm table. One effect is a reduction in the number of wasm table entries similar to the previous attempt in https://reviews.llvm.org/D61539 which was reverted. Differential Revision: https://reviews.llvm.org/D61772 llvm-svn: 360402
1503 lines
48 KiB
C++
1503 lines
48 KiB
C++
//===- Writer.cpp ---------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Writer.h"
|
|
#include "Config.h"
|
|
#include "InputChunks.h"
|
|
#include "InputEvent.h"
|
|
#include "InputGlobal.h"
|
|
#include "OutputSections.h"
|
|
#include "OutputSegment.h"
|
|
#include "SymbolTable.h"
|
|
#include "WriterUtils.h"
|
|
#include "lld/Common/ErrorHandler.h"
|
|
#include "lld/Common/Memory.h"
|
|
#include "lld/Common/Strings.h"
|
|
#include "lld/Common/Threads.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/BinaryFormat/Wasm.h"
|
|
#include "llvm/Object/WasmTraits.h"
|
|
#include "llvm/Support/FileOutputBuffer.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/LEB128.h"
|
|
#include "llvm/Support/Path.h"
|
|
|
|
#include <cstdarg>
|
|
#include <map>
|
|
|
|
#define DEBUG_TYPE "lld"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::wasm;
|
|
using namespace lld;
|
|
using namespace lld::wasm;
|
|
|
|
static constexpr int StackAlignment = 16;
|
|
static constexpr const char *FunctionTableName = "__indirect_function_table";
|
|
const char *lld::wasm::DefaultModule = "env";
|
|
|
|
namespace {
|
|
|
|
// An init entry to be written to either the synthetic init func or the
|
|
// linking metadata.
|
|
struct WasmInitEntry {
|
|
const FunctionSymbol *Sym;
|
|
uint32_t Priority;
|
|
};
|
|
|
|
// The writer writes a SymbolTable result to a file.
|
|
class Writer {
|
|
public:
|
|
void run();
|
|
|
|
private:
|
|
void openFile();
|
|
|
|
uint32_t lookupType(const WasmSignature &Sig);
|
|
uint32_t registerType(const WasmSignature &Sig);
|
|
|
|
void createApplyRelocationsFunction();
|
|
void createCallCtorsFunction();
|
|
|
|
void calculateInitFunctions();
|
|
void processRelocations(InputChunk *Chunk);
|
|
void assignIndexes();
|
|
void calculateTargetFeatures();
|
|
void calculateImports();
|
|
void calculateExports();
|
|
void calculateCustomSections();
|
|
void assignSymtab();
|
|
void calculateTypes();
|
|
void createOutputSegments();
|
|
void layoutMemory();
|
|
void createHeader();
|
|
void createSections();
|
|
SyntheticSection *createSyntheticSection(uint32_t Type, StringRef Name = "");
|
|
|
|
// Builtin sections
|
|
void createTypeSection();
|
|
void createFunctionSection();
|
|
void createTableSection();
|
|
void createGlobalSection();
|
|
void createEventSection();
|
|
void createExportSection();
|
|
void createImportSection();
|
|
void createMemorySection();
|
|
void createElemSection();
|
|
void createDataCountSection();
|
|
void createCodeSection();
|
|
void createDataSection();
|
|
void createCustomSections();
|
|
|
|
// Custom sections
|
|
void createDylinkSection();
|
|
void createRelocSections();
|
|
void createLinkingSection();
|
|
void createNameSection();
|
|
void createProducersSection();
|
|
void createTargetFeaturesSection();
|
|
|
|
void writeHeader();
|
|
void writeSections();
|
|
|
|
uint64_t FileSize = 0;
|
|
uint32_t TableBase = 0;
|
|
uint32_t NumMemoryPages = 0;
|
|
uint32_t MaxMemoryPages = 0;
|
|
// Memory size and aligment. Written to the "dylink" section
|
|
// when build with -shared or -pie.
|
|
uint32_t MemAlign = 0;
|
|
uint32_t MemSize = 0;
|
|
|
|
std::vector<const WasmSignature *> Types;
|
|
DenseMap<WasmSignature, int32_t> TypeIndices;
|
|
std::vector<const Symbol *> ImportedSymbols;
|
|
std::vector<const Symbol *> GOTSymbols;
|
|
unsigned NumImportedFunctions = 0;
|
|
unsigned NumImportedGlobals = 0;
|
|
unsigned NumImportedEvents = 0;
|
|
std::vector<WasmExport> Exports;
|
|
std::vector<const DefinedData *> DefinedFakeGlobals;
|
|
std::vector<InputGlobal *> InputGlobals;
|
|
std::vector<InputFunction *> InputFunctions;
|
|
std::vector<InputEvent *> InputEvents;
|
|
std::vector<const FunctionSymbol *> IndirectFunctions;
|
|
std::vector<const Symbol *> SymtabEntries;
|
|
std::vector<WasmInitEntry> InitFunctions;
|
|
|
|
llvm::StringMap<std::vector<InputSection *>> CustomSectionMapping;
|
|
llvm::StringMap<SectionSymbol *> CustomSectionSymbols;
|
|
llvm::SmallSet<std::string, 8> TargetFeatures;
|
|
|
|
// Elements that are used to construct the final output
|
|
std::string Header;
|
|
std::vector<OutputSection *> OutputSections;
|
|
|
|
std::unique_ptr<FileOutputBuffer> Buffer;
|
|
|
|
std::vector<OutputSegment *> Segments;
|
|
llvm::SmallDenseMap<StringRef, OutputSegment *> SegmentMap;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void Writer::createImportSection() {
|
|
uint32_t NumImports = ImportedSymbols.size() + GOTSymbols.size();
|
|
if (Config->ImportMemory)
|
|
++NumImports;
|
|
if (Config->ImportTable)
|
|
++NumImports;
|
|
|
|
if (NumImports == 0)
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_IMPORT);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, NumImports, "import count");
|
|
|
|
if (Config->ImportMemory) {
|
|
WasmImport Import;
|
|
Import.Module = DefaultModule;
|
|
Import.Field = "memory";
|
|
Import.Kind = WASM_EXTERNAL_MEMORY;
|
|
Import.Memory.Flags = 0;
|
|
Import.Memory.Initial = NumMemoryPages;
|
|
if (MaxMemoryPages != 0 || Config->SharedMemory) {
|
|
Import.Memory.Flags |= WASM_LIMITS_FLAG_HAS_MAX;
|
|
Import.Memory.Maximum = MaxMemoryPages;
|
|
}
|
|
if (Config->SharedMemory)
|
|
Import.Memory.Flags |= WASM_LIMITS_FLAG_IS_SHARED;
|
|
writeImport(OS, Import);
|
|
}
|
|
|
|
if (Config->ImportTable) {
|
|
uint32_t TableSize = TableBase + IndirectFunctions.size();
|
|
WasmImport Import;
|
|
Import.Module = DefaultModule;
|
|
Import.Field = FunctionTableName;
|
|
Import.Kind = WASM_EXTERNAL_TABLE;
|
|
Import.Table.ElemType = WASM_TYPE_FUNCREF;
|
|
Import.Table.Limits = {0, TableSize, 0};
|
|
writeImport(OS, Import);
|
|
}
|
|
|
|
for (const Symbol *Sym : ImportedSymbols) {
|
|
WasmImport Import;
|
|
if (auto *F = dyn_cast<UndefinedFunction>(Sym)) {
|
|
Import.Field = F->ImportName;
|
|
Import.Module = F->ImportModule;
|
|
} else if (auto *G = dyn_cast<UndefinedGlobal>(Sym)) {
|
|
Import.Field = G->ImportName;
|
|
Import.Module = G->ImportModule;
|
|
} else {
|
|
Import.Field = Sym->getName();
|
|
Import.Module = DefaultModule;
|
|
}
|
|
|
|
if (auto *FunctionSym = dyn_cast<FunctionSymbol>(Sym)) {
|
|
Import.Kind = WASM_EXTERNAL_FUNCTION;
|
|
Import.SigIndex = lookupType(*FunctionSym->Signature);
|
|
} else if (auto *GlobalSym = dyn_cast<GlobalSymbol>(Sym)) {
|
|
Import.Kind = WASM_EXTERNAL_GLOBAL;
|
|
Import.Global = *GlobalSym->getGlobalType();
|
|
} else {
|
|
auto *EventSym = cast<EventSymbol>(Sym);
|
|
Import.Kind = WASM_EXTERNAL_EVENT;
|
|
Import.Event.Attribute = EventSym->getEventType()->Attribute;
|
|
Import.Event.SigIndex = lookupType(*EventSym->Signature);
|
|
}
|
|
writeImport(OS, Import);
|
|
}
|
|
|
|
for (const Symbol *Sym : GOTSymbols) {
|
|
WasmImport Import;
|
|
Import.Kind = WASM_EXTERNAL_GLOBAL;
|
|
Import.Global = {WASM_TYPE_I32, true};
|
|
if (isa<DataSymbol>(Sym))
|
|
Import.Module = "GOT.mem";
|
|
else
|
|
Import.Module = "GOT.func";
|
|
Import.Field = Sym->getName();
|
|
writeImport(OS, Import);
|
|
}
|
|
}
|
|
|
|
void Writer::createTypeSection() {
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_TYPE);
|
|
raw_ostream &OS = Section->getStream();
|
|
writeUleb128(OS, Types.size(), "type count");
|
|
for (const WasmSignature *Sig : Types)
|
|
writeSig(OS, *Sig);
|
|
}
|
|
|
|
void Writer::createFunctionSection() {
|
|
if (InputFunctions.empty())
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_FUNCTION);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, InputFunctions.size(), "function count");
|
|
for (const InputFunction *Func : InputFunctions)
|
|
writeUleb128(OS, lookupType(Func->Signature), "sig index");
|
|
}
|
|
|
|
void Writer::createMemorySection() {
|
|
if (Config->ImportMemory)
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_MEMORY);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
bool HasMax = MaxMemoryPages != 0 || Config->SharedMemory;
|
|
writeUleb128(OS, 1, "memory count");
|
|
unsigned Flags = 0;
|
|
if (HasMax)
|
|
Flags |= WASM_LIMITS_FLAG_HAS_MAX;
|
|
if (Config->SharedMemory)
|
|
Flags |= WASM_LIMITS_FLAG_IS_SHARED;
|
|
writeUleb128(OS, Flags, "memory limits flags");
|
|
writeUleb128(OS, NumMemoryPages, "initial pages");
|
|
if (HasMax)
|
|
writeUleb128(OS, MaxMemoryPages, "max pages");
|
|
}
|
|
|
|
void Writer::createGlobalSection() {
|
|
unsigned NumGlobals = InputGlobals.size() + DefinedFakeGlobals.size();
|
|
if (NumGlobals == 0)
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_GLOBAL);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, NumGlobals, "global count");
|
|
for (const InputGlobal *G : InputGlobals)
|
|
writeGlobal(OS, G->Global);
|
|
for (const DefinedData *Sym : DefinedFakeGlobals) {
|
|
WasmGlobal Global;
|
|
Global.Type = {WASM_TYPE_I32, false};
|
|
Global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
|
|
Global.InitExpr.Value.Int32 = Sym->getVirtualAddress();
|
|
writeGlobal(OS, Global);
|
|
}
|
|
}
|
|
|
|
// The event section contains a list of declared wasm events associated with the
|
|
// module. Currently the only supported event kind is exceptions. A single event
|
|
// entry represents a single event with an event tag. All C++ exceptions are
|
|
// represented by a single event. An event entry in this section contains
|
|
// information on what kind of event it is (e.g. exception) and the type of
|
|
// values contained in a single event object. (In wasm, an event can contain
|
|
// multiple values of primitive types. But for C++ exceptions, we just throw a
|
|
// pointer which is an i32 value (for wasm32 architecture), so the signature of
|
|
// C++ exception is (i32)->(void), because all event types are assumed to have
|
|
// void return type to share WasmSignature with functions.)
|
|
void Writer::createEventSection() {
|
|
unsigned NumEvents = InputEvents.size();
|
|
if (NumEvents == 0)
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_EVENT);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, NumEvents, "event count");
|
|
for (InputEvent *E : InputEvents) {
|
|
E->Event.Type.SigIndex = lookupType(E->Signature);
|
|
writeEvent(OS, E->Event);
|
|
}
|
|
}
|
|
|
|
void Writer::createTableSection() {
|
|
if (Config->ImportTable)
|
|
return;
|
|
|
|
// Always output a table section (or table import), even if there are no
|
|
// indirect calls. There are two reasons for this:
|
|
// 1. For executables it is useful to have an empty table slot at 0
|
|
// which can be filled with a null function call handler.
|
|
// 2. If we don't do this, any program that contains a call_indirect but
|
|
// no address-taken function will fail at validation time since it is
|
|
// a validation error to include a call_indirect instruction if there
|
|
// is not table.
|
|
uint32_t TableSize = TableBase + IndirectFunctions.size();
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_TABLE);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, 1, "table count");
|
|
WasmLimits Limits = {WASM_LIMITS_FLAG_HAS_MAX, TableSize, TableSize};
|
|
writeTableType(OS, WasmTable{WASM_TYPE_FUNCREF, Limits});
|
|
}
|
|
|
|
void Writer::createExportSection() {
|
|
if (!Exports.size())
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_EXPORT);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, Exports.size(), "export count");
|
|
for (const WasmExport &Export : Exports)
|
|
writeExport(OS, Export);
|
|
}
|
|
|
|
void Writer::calculateCustomSections() {
|
|
log("calculateCustomSections");
|
|
bool StripDebug = Config->StripDebug || Config->StripAll;
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
for (InputSection *Section : File->CustomSections) {
|
|
StringRef Name = Section->getName();
|
|
// These custom sections are known the linker and synthesized rather than
|
|
// blindly copied
|
|
if (Name == "linking" || Name == "name" || Name == "producers" ||
|
|
Name == "target_features" || Name.startswith("reloc."))
|
|
continue;
|
|
// .. or it is a debug section
|
|
if (StripDebug && Name.startswith(".debug_"))
|
|
continue;
|
|
CustomSectionMapping[Name].push_back(Section);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Writer::createCustomSections() {
|
|
log("createCustomSections");
|
|
for (auto &Pair : CustomSectionMapping) {
|
|
StringRef Name = Pair.first();
|
|
|
|
auto P = CustomSectionSymbols.find(Name);
|
|
if (P != CustomSectionSymbols.end()) {
|
|
uint32_t SectionIndex = OutputSections.size();
|
|
P->second->setOutputSectionIndex(SectionIndex);
|
|
}
|
|
|
|
LLVM_DEBUG(dbgs() << "createCustomSection: " << Name << "\n");
|
|
OutputSections.push_back(make<CustomSection>(Name, Pair.second));
|
|
}
|
|
}
|
|
|
|
void Writer::createElemSection() {
|
|
if (IndirectFunctions.empty())
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_ELEM);
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, 1, "segment count");
|
|
writeUleb128(OS, 0, "table index");
|
|
WasmInitExpr InitExpr;
|
|
if (Config->Pic) {
|
|
InitExpr.Opcode = WASM_OPCODE_GLOBAL_GET;
|
|
InitExpr.Value.Global = WasmSym::TableBase->getGlobalIndex();
|
|
} else {
|
|
InitExpr.Opcode = WASM_OPCODE_I32_CONST;
|
|
InitExpr.Value.Int32 = TableBase;
|
|
}
|
|
writeInitExpr(OS, InitExpr);
|
|
writeUleb128(OS, IndirectFunctions.size(), "elem count");
|
|
|
|
uint32_t TableIndex = TableBase;
|
|
for (const FunctionSymbol *Sym : IndirectFunctions) {
|
|
assert(Sym->getTableIndex() == TableIndex);
|
|
writeUleb128(OS, Sym->getFunctionIndex(), "function index");
|
|
++TableIndex;
|
|
}
|
|
}
|
|
|
|
void Writer::createDataCountSection() {
|
|
if (!Segments.size() || !TargetFeatures.count("bulk-memory"))
|
|
return;
|
|
|
|
log("createDataCountSection");
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_DATACOUNT);
|
|
raw_ostream &OS = Section->getStream();
|
|
writeUleb128(OS, Segments.size(), "data count");
|
|
}
|
|
|
|
void Writer::createCodeSection() {
|
|
if (InputFunctions.empty())
|
|
return;
|
|
|
|
log("createCodeSection");
|
|
|
|
auto Section = make<CodeSection>(InputFunctions);
|
|
OutputSections.push_back(Section);
|
|
}
|
|
|
|
void Writer::createDataSection() {
|
|
if (!Segments.size())
|
|
return;
|
|
|
|
log("createDataSection");
|
|
auto Section = make<DataSection>(Segments);
|
|
OutputSections.push_back(Section);
|
|
}
|
|
|
|
// Create relocations sections in the final output.
|
|
// These are only created when relocatable output is requested.
|
|
void Writer::createRelocSections() {
|
|
log("createRelocSections");
|
|
// Don't use iterator here since we are adding to OutputSection
|
|
size_t OrigSize = OutputSections.size();
|
|
for (size_t I = 0; I < OrigSize; I++) {
|
|
OutputSection *OSec = OutputSections[I];
|
|
uint32_t Count = OSec->numRelocations();
|
|
if (!Count)
|
|
continue;
|
|
|
|
StringRef Name;
|
|
if (OSec->Type == WASM_SEC_DATA)
|
|
Name = "reloc.DATA";
|
|
else if (OSec->Type == WASM_SEC_CODE)
|
|
Name = "reloc.CODE";
|
|
else if (OSec->Type == WASM_SEC_CUSTOM)
|
|
Name = Saver.save("reloc." + OSec->Name);
|
|
else
|
|
llvm_unreachable(
|
|
"relocations only supported for code, data, or custom sections");
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, Name);
|
|
raw_ostream &OS = Section->getStream();
|
|
writeUleb128(OS, I, "reloc section");
|
|
writeUleb128(OS, Count, "reloc count");
|
|
OSec->writeRelocations(OS);
|
|
}
|
|
}
|
|
|
|
static uint32_t getWasmFlags(const Symbol *Sym) {
|
|
uint32_t Flags = 0;
|
|
if (Sym->isLocal())
|
|
Flags |= WASM_SYMBOL_BINDING_LOCAL;
|
|
if (Sym->isWeak())
|
|
Flags |= WASM_SYMBOL_BINDING_WEAK;
|
|
if (Sym->isHidden())
|
|
Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN;
|
|
if (Sym->isUndefined())
|
|
Flags |= WASM_SYMBOL_UNDEFINED;
|
|
if (auto *F = dyn_cast<UndefinedFunction>(Sym)) {
|
|
if (F->getName() != F->ImportName)
|
|
Flags |= WASM_SYMBOL_EXPLICIT_NAME;
|
|
} else if (auto *G = dyn_cast<UndefinedGlobal>(Sym)) {
|
|
if (G->getName() != G->ImportName)
|
|
Flags |= WASM_SYMBOL_EXPLICIT_NAME;
|
|
}
|
|
return Flags;
|
|
}
|
|
|
|
// Some synthetic sections (e.g. "name" and "linking") have subsections.
|
|
// Just like the synthetic sections themselves these need to be created before
|
|
// they can be written out (since they are preceded by their length). This
|
|
// class is used to create subsections and then write them into the stream
|
|
// of the parent section.
|
|
class SubSection {
|
|
public:
|
|
explicit SubSection(uint32_t Type) : Type(Type) {}
|
|
|
|
void writeTo(raw_ostream &To) {
|
|
OS.flush();
|
|
writeUleb128(To, Type, "subsection type");
|
|
writeUleb128(To, Body.size(), "subsection size");
|
|
To.write(Body.data(), Body.size());
|
|
}
|
|
|
|
private:
|
|
uint32_t Type;
|
|
std::string Body;
|
|
|
|
public:
|
|
raw_string_ostream OS{Body};
|
|
};
|
|
|
|
// Create the custom "dylink" section containing information for the dynamic
|
|
// linker.
|
|
// See
|
|
// https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
|
|
void Writer::createDylinkSection() {
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, "dylink");
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, MemSize, "MemSize");
|
|
writeUleb128(OS, MemAlign, "MemAlign");
|
|
writeUleb128(OS, IndirectFunctions.size(), "TableSize");
|
|
writeUleb128(OS, 0, "TableAlign");
|
|
writeUleb128(OS, Symtab->SharedFiles.size(), "Needed");
|
|
for (auto *SO : Symtab->SharedFiles)
|
|
writeStr(OS, llvm::sys::path::filename(SO->getName()), "so name");
|
|
}
|
|
|
|
// Create the custom "linking" section containing linker metadata.
|
|
// This is only created when relocatable output is requested.
|
|
void Writer::createLinkingSection() {
|
|
SyntheticSection *Section =
|
|
createSyntheticSection(WASM_SEC_CUSTOM, "linking");
|
|
raw_ostream &OS = Section->getStream();
|
|
|
|
writeUleb128(OS, WasmMetadataVersion, "Version");
|
|
|
|
if (!SymtabEntries.empty()) {
|
|
SubSection Sub(WASM_SYMBOL_TABLE);
|
|
writeUleb128(Sub.OS, SymtabEntries.size(), "num symbols");
|
|
|
|
for (const Symbol *Sym : SymtabEntries) {
|
|
assert(Sym->isDefined() || Sym->isUndefined());
|
|
WasmSymbolType Kind = Sym->getWasmType();
|
|
uint32_t Flags = getWasmFlags(Sym);
|
|
|
|
writeU8(Sub.OS, Kind, "sym kind");
|
|
writeUleb128(Sub.OS, Flags, "sym flags");
|
|
|
|
if (auto *F = dyn_cast<FunctionSymbol>(Sym)) {
|
|
writeUleb128(Sub.OS, F->getFunctionIndex(), "index");
|
|
if (Sym->isDefined() ||
|
|
(Flags & WASM_SYMBOL_EXPLICIT_NAME) != 0)
|
|
writeStr(Sub.OS, Sym->getName(), "sym name");
|
|
} else if (auto *G = dyn_cast<GlobalSymbol>(Sym)) {
|
|
writeUleb128(Sub.OS, G->getGlobalIndex(), "index");
|
|
if (Sym->isDefined() ||
|
|
(Flags & WASM_SYMBOL_EXPLICIT_NAME) != 0)
|
|
writeStr(Sub.OS, Sym->getName(), "sym name");
|
|
} else if (auto *E = dyn_cast<EventSymbol>(Sym)) {
|
|
writeUleb128(Sub.OS, E->getEventIndex(), "index");
|
|
if (Sym->isDefined() ||
|
|
(Flags & WASM_SYMBOL_EXPLICIT_NAME) != 0)
|
|
writeStr(Sub.OS, Sym->getName(), "sym name");
|
|
} else if (isa<DataSymbol>(Sym)) {
|
|
writeStr(Sub.OS, Sym->getName(), "sym name");
|
|
if (auto *DataSym = dyn_cast<DefinedData>(Sym)) {
|
|
writeUleb128(Sub.OS, DataSym->getOutputSegmentIndex(), "index");
|
|
writeUleb128(Sub.OS, DataSym->getOutputSegmentOffset(),
|
|
"data offset");
|
|
writeUleb128(Sub.OS, DataSym->getSize(), "data size");
|
|
}
|
|
} else {
|
|
auto *S = cast<SectionSymbol>(Sym);
|
|
writeUleb128(Sub.OS, S->getOutputSectionIndex(), "sym section index");
|
|
}
|
|
}
|
|
|
|
Sub.writeTo(OS);
|
|
}
|
|
|
|
if (Segments.size()) {
|
|
SubSection Sub(WASM_SEGMENT_INFO);
|
|
writeUleb128(Sub.OS, Segments.size(), "num data segments");
|
|
for (const OutputSegment *S : Segments) {
|
|
writeStr(Sub.OS, S->Name, "segment name");
|
|
writeUleb128(Sub.OS, S->Alignment, "alignment");
|
|
writeUleb128(Sub.OS, 0, "flags");
|
|
}
|
|
Sub.writeTo(OS);
|
|
}
|
|
|
|
if (!InitFunctions.empty()) {
|
|
SubSection Sub(WASM_INIT_FUNCS);
|
|
writeUleb128(Sub.OS, InitFunctions.size(), "num init functions");
|
|
for (const WasmInitEntry &F : InitFunctions) {
|
|
writeUleb128(Sub.OS, F.Priority, "priority");
|
|
writeUleb128(Sub.OS, F.Sym->getOutputSymbolIndex(), "function index");
|
|
}
|
|
Sub.writeTo(OS);
|
|
}
|
|
|
|
struct ComdatEntry {
|
|
unsigned Kind;
|
|
uint32_t Index;
|
|
};
|
|
std::map<StringRef, std::vector<ComdatEntry>> Comdats;
|
|
|
|
for (const InputFunction *F : InputFunctions) {
|
|
StringRef Comdat = F->getComdatName();
|
|
if (!Comdat.empty())
|
|
Comdats[Comdat].emplace_back(
|
|
ComdatEntry{WASM_COMDAT_FUNCTION, F->getFunctionIndex()});
|
|
}
|
|
for (uint32_t I = 0; I < Segments.size(); ++I) {
|
|
const auto &InputSegments = Segments[I]->InputSegments;
|
|
if (InputSegments.empty())
|
|
continue;
|
|
StringRef Comdat = InputSegments[0]->getComdatName();
|
|
#ifndef NDEBUG
|
|
for (const InputSegment *IS : InputSegments)
|
|
assert(IS->getComdatName() == Comdat);
|
|
#endif
|
|
if (!Comdat.empty())
|
|
Comdats[Comdat].emplace_back(ComdatEntry{WASM_COMDAT_DATA, I});
|
|
}
|
|
|
|
if (!Comdats.empty()) {
|
|
SubSection Sub(WASM_COMDAT_INFO);
|
|
writeUleb128(Sub.OS, Comdats.size(), "num comdats");
|
|
for (const auto &C : Comdats) {
|
|
writeStr(Sub.OS, C.first, "comdat name");
|
|
writeUleb128(Sub.OS, 0, "comdat flags"); // flags for future use
|
|
writeUleb128(Sub.OS, C.second.size(), "num entries");
|
|
for (const ComdatEntry &Entry : C.second) {
|
|
writeU8(Sub.OS, Entry.Kind, "entry kind");
|
|
writeUleb128(Sub.OS, Entry.Index, "entry index");
|
|
}
|
|
}
|
|
Sub.writeTo(OS);
|
|
}
|
|
}
|
|
|
|
// Create the custom "name" section containing debug symbol names.
|
|
void Writer::createNameSection() {
|
|
unsigned NumNames = NumImportedFunctions;
|
|
for (const InputFunction *F : InputFunctions)
|
|
if (!F->getName().empty() || !F->getDebugName().empty())
|
|
++NumNames;
|
|
|
|
if (NumNames == 0)
|
|
return;
|
|
|
|
SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, "name");
|
|
|
|
SubSection Sub(WASM_NAMES_FUNCTION);
|
|
writeUleb128(Sub.OS, NumNames, "name count");
|
|
|
|
// Names must appear in function index order. As it happens ImportedSymbols
|
|
// and InputFunctions are numbered in order with imported functions coming
|
|
// first.
|
|
for (const Symbol *S : ImportedSymbols) {
|
|
if (auto *F = dyn_cast<FunctionSymbol>(S)) {
|
|
writeUleb128(Sub.OS, F->getFunctionIndex(), "func index");
|
|
writeStr(Sub.OS, toString(*S), "symbol name");
|
|
}
|
|
}
|
|
for (const InputFunction *F : InputFunctions) {
|
|
if (!F->getName().empty()) {
|
|
writeUleb128(Sub.OS, F->getFunctionIndex(), "func index");
|
|
if (!F->getDebugName().empty()) {
|
|
writeStr(Sub.OS, F->getDebugName(), "symbol name");
|
|
} else {
|
|
writeStr(Sub.OS, maybeDemangleSymbol(F->getName()), "symbol name");
|
|
}
|
|
}
|
|
}
|
|
|
|
Sub.writeTo(Section->getStream());
|
|
}
|
|
|
|
void Writer::createProducersSection() {
|
|
SmallVector<std::pair<std::string, std::string>, 8> Languages;
|
|
SmallVector<std::pair<std::string, std::string>, 8> Tools;
|
|
SmallVector<std::pair<std::string, std::string>, 8> SDKs;
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
const WasmProducerInfo &Info = File->getWasmObj()->getProducerInfo();
|
|
for (auto &Producers : {std::make_pair(&Info.Languages, &Languages),
|
|
std::make_pair(&Info.Tools, &Tools),
|
|
std::make_pair(&Info.SDKs, &SDKs)})
|
|
for (auto &Producer : *Producers.first)
|
|
if (Producers.second->end() ==
|
|
llvm::find_if(*Producers.second,
|
|
[&](std::pair<std::string, std::string> Seen) {
|
|
return Seen.first == Producer.first;
|
|
}))
|
|
Producers.second->push_back(Producer);
|
|
}
|
|
int FieldCount =
|
|
int(!Languages.empty()) + int(!Tools.empty()) + int(!SDKs.empty());
|
|
if (FieldCount == 0)
|
|
return;
|
|
SyntheticSection *Section =
|
|
createSyntheticSection(WASM_SEC_CUSTOM, "producers");
|
|
auto &OS = Section->getStream();
|
|
writeUleb128(OS, FieldCount, "field count");
|
|
for (auto &Field :
|
|
{std::make_pair("language", Languages),
|
|
std::make_pair("processed-by", Tools), std::make_pair("sdk", SDKs)}) {
|
|
if (Field.second.empty())
|
|
continue;
|
|
writeStr(OS, Field.first, "field name");
|
|
writeUleb128(OS, Field.second.size(), "number of entries");
|
|
for (auto &Entry : Field.second) {
|
|
writeStr(OS, Entry.first, "producer name");
|
|
writeStr(OS, Entry.second, "producer version");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Writer::createTargetFeaturesSection() {
|
|
if (TargetFeatures.empty())
|
|
return;
|
|
|
|
SmallVector<std::string, 8> Emitted(TargetFeatures.begin(),
|
|
TargetFeatures.end());
|
|
llvm::sort(Emitted);
|
|
SyntheticSection *Section =
|
|
createSyntheticSection(WASM_SEC_CUSTOM, "target_features");
|
|
auto &OS = Section->getStream();
|
|
writeUleb128(OS, Emitted.size(), "feature count");
|
|
for (auto &Feature : Emitted) {
|
|
writeU8(OS, WASM_FEATURE_PREFIX_USED, "feature used prefix");
|
|
writeStr(OS, Feature, "feature name");
|
|
}
|
|
}
|
|
|
|
void Writer::writeHeader() {
|
|
memcpy(Buffer->getBufferStart(), Header.data(), Header.size());
|
|
}
|
|
|
|
void Writer::writeSections() {
|
|
uint8_t *Buf = Buffer->getBufferStart();
|
|
parallelForEach(OutputSections, [Buf](OutputSection *S) { S->writeTo(Buf); });
|
|
}
|
|
|
|
// Fix the memory layout of the output binary. This assigns memory offsets
|
|
// to each of the input data sections as well as the explicit stack region.
|
|
// The default memory layout is as follows, from low to high.
|
|
//
|
|
// - initialized data (starting at Config->GlobalBase)
|
|
// - BSS data (not currently implemented in llvm)
|
|
// - explicit stack (Config->ZStackSize)
|
|
// - heap start / unallocated
|
|
//
|
|
// The --stack-first option means that stack is placed before any static data.
|
|
// This can be useful since it means that stack overflow traps immediately
|
|
// rather than overwriting global data, but also increases code size since all
|
|
// static data loads and stores requires larger offsets.
|
|
void Writer::layoutMemory() {
|
|
createOutputSegments();
|
|
|
|
uint32_t MemoryPtr = 0;
|
|
|
|
auto PlaceStack = [&]() {
|
|
if (Config->Relocatable || Config->Shared)
|
|
return;
|
|
MemoryPtr = alignTo(MemoryPtr, StackAlignment);
|
|
if (Config->ZStackSize != alignTo(Config->ZStackSize, StackAlignment))
|
|
error("stack size must be " + Twine(StackAlignment) + "-byte aligned");
|
|
log("mem: stack size = " + Twine(Config->ZStackSize));
|
|
log("mem: stack base = " + Twine(MemoryPtr));
|
|
MemoryPtr += Config->ZStackSize;
|
|
auto *SP = cast<DefinedGlobal>(WasmSym::StackPointer);
|
|
SP->Global->Global.InitExpr.Value.Int32 = MemoryPtr;
|
|
log("mem: stack top = " + Twine(MemoryPtr));
|
|
};
|
|
|
|
if (Config->StackFirst) {
|
|
PlaceStack();
|
|
} else {
|
|
MemoryPtr = Config->GlobalBase;
|
|
log("mem: global base = " + Twine(Config->GlobalBase));
|
|
}
|
|
|
|
uint32_t DataStart = MemoryPtr;
|
|
|
|
// Arbitrarily set __dso_handle handle to point to the start of the data
|
|
// segments.
|
|
if (WasmSym::DsoHandle)
|
|
WasmSym::DsoHandle->setVirtualAddress(DataStart);
|
|
|
|
MemAlign = 0;
|
|
for (OutputSegment *Seg : Segments) {
|
|
MemAlign = std::max(MemAlign, Seg->Alignment);
|
|
MemoryPtr = alignTo(MemoryPtr, 1ULL << Seg->Alignment);
|
|
Seg->StartVA = MemoryPtr;
|
|
log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", Seg->Name,
|
|
MemoryPtr, Seg->Size, Seg->Alignment));
|
|
MemoryPtr += Seg->Size;
|
|
}
|
|
|
|
// TODO: Add .bss space here.
|
|
if (WasmSym::DataEnd)
|
|
WasmSym::DataEnd->setVirtualAddress(MemoryPtr);
|
|
|
|
log("mem: static data = " + Twine(MemoryPtr - DataStart));
|
|
|
|
if (Config->Shared) {
|
|
MemSize = MemoryPtr;
|
|
return;
|
|
}
|
|
|
|
if (!Config->StackFirst)
|
|
PlaceStack();
|
|
|
|
// Set `__heap_base` to directly follow the end of the stack or global data.
|
|
// The fact that this comes last means that a malloc/brk implementation
|
|
// can grow the heap at runtime.
|
|
if (!Config->Relocatable) {
|
|
WasmSym::HeapBase->setVirtualAddress(MemoryPtr);
|
|
log("mem: heap base = " + Twine(MemoryPtr));
|
|
}
|
|
|
|
if (Config->InitialMemory != 0) {
|
|
if (Config->InitialMemory != alignTo(Config->InitialMemory, WasmPageSize))
|
|
error("initial memory must be " + Twine(WasmPageSize) + "-byte aligned");
|
|
if (MemoryPtr > Config->InitialMemory)
|
|
error("initial memory too small, " + Twine(MemoryPtr) + " bytes needed");
|
|
else
|
|
MemoryPtr = Config->InitialMemory;
|
|
}
|
|
MemSize = MemoryPtr;
|
|
NumMemoryPages = alignTo(MemoryPtr, WasmPageSize) / WasmPageSize;
|
|
log("mem: total pages = " + Twine(NumMemoryPages));
|
|
|
|
// Check max if explicitly supplied or required by shared memory
|
|
if (Config->MaxMemory != 0 || Config->SharedMemory) {
|
|
if (Config->MaxMemory != alignTo(Config->MaxMemory, WasmPageSize))
|
|
error("maximum memory must be " + Twine(WasmPageSize) + "-byte aligned");
|
|
if (MemoryPtr > Config->MaxMemory)
|
|
error("maximum memory too small, " + Twine(MemoryPtr) + " bytes needed");
|
|
MaxMemoryPages = Config->MaxMemory / WasmPageSize;
|
|
log("mem: max pages = " + Twine(MaxMemoryPages));
|
|
}
|
|
}
|
|
|
|
SyntheticSection *Writer::createSyntheticSection(uint32_t Type,
|
|
StringRef Name) {
|
|
auto Sec = make<SyntheticSection>(Type, Name);
|
|
log("createSection: " + toString(*Sec));
|
|
OutputSections.push_back(Sec);
|
|
return Sec;
|
|
}
|
|
|
|
void Writer::createSections() {
|
|
// Known sections
|
|
if (Config->Pic)
|
|
createDylinkSection();
|
|
createTypeSection();
|
|
createImportSection();
|
|
createFunctionSection();
|
|
createTableSection();
|
|
createMemorySection();
|
|
createGlobalSection();
|
|
createEventSection();
|
|
createExportSection();
|
|
createElemSection();
|
|
createDataCountSection();
|
|
createCodeSection();
|
|
createDataSection();
|
|
createCustomSections();
|
|
|
|
// Custom sections
|
|
if (Config->Relocatable) {
|
|
createLinkingSection();
|
|
createRelocSections();
|
|
}
|
|
|
|
if (!Config->StripDebug && !Config->StripAll)
|
|
createNameSection();
|
|
|
|
if (!Config->StripAll) {
|
|
createProducersSection();
|
|
createTargetFeaturesSection();
|
|
}
|
|
|
|
for (OutputSection *S : OutputSections) {
|
|
S->setOffset(FileSize);
|
|
S->finalizeContents();
|
|
FileSize += S->getSize();
|
|
}
|
|
}
|
|
|
|
void Writer::calculateTargetFeatures() {
|
|
SmallSet<std::string, 8> Used;
|
|
SmallSet<std::string, 8> Required;
|
|
SmallSet<std::string, 8> Disallowed;
|
|
|
|
// Only infer used features if user did not specify features
|
|
bool InferFeatures = !Config->Features.hasValue();
|
|
|
|
if (!InferFeatures) {
|
|
for (auto &Feature : Config->Features.getValue())
|
|
TargetFeatures.insert(Feature);
|
|
// No need to read or check features
|
|
if (!Config->CheckFeatures)
|
|
return;
|
|
}
|
|
|
|
// Find the sets of used, required, and disallowed features
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
for (auto &Feature : File->getWasmObj()->getTargetFeatures()) {
|
|
switch (Feature.Prefix) {
|
|
case WASM_FEATURE_PREFIX_USED:
|
|
Used.insert(Feature.Name);
|
|
break;
|
|
case WASM_FEATURE_PREFIX_REQUIRED:
|
|
Used.insert(Feature.Name);
|
|
Required.insert(Feature.Name);
|
|
break;
|
|
case WASM_FEATURE_PREFIX_DISALLOWED:
|
|
Disallowed.insert(Feature.Name);
|
|
break;
|
|
default:
|
|
error("Unrecognized feature policy prefix " +
|
|
std::to_string(Feature.Prefix));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (InferFeatures)
|
|
TargetFeatures.insert(Used.begin(), Used.end());
|
|
|
|
if (TargetFeatures.count("atomics") && !Config->SharedMemory)
|
|
error("'atomics' feature is used, so --shared-memory must be used");
|
|
|
|
if (!Config->CheckFeatures)
|
|
return;
|
|
|
|
if (Disallowed.count("atomics") && Config->SharedMemory)
|
|
error(
|
|
"'atomics' feature is disallowed, so --shared-memory must not be used");
|
|
|
|
// Validate that used features are allowed in output
|
|
if (!InferFeatures) {
|
|
for (auto &Feature : Used) {
|
|
if (!TargetFeatures.count(Feature))
|
|
error(Twine("Target feature '") + Feature + "' is not allowed.");
|
|
}
|
|
}
|
|
|
|
// Validate the required and disallowed constraints for each file
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
SmallSet<std::string, 8> ObjectFeatures;
|
|
for (auto &Feature : File->getWasmObj()->getTargetFeatures()) {
|
|
if (Feature.Prefix == WASM_FEATURE_PREFIX_DISALLOWED)
|
|
continue;
|
|
ObjectFeatures.insert(Feature.Name);
|
|
if (Disallowed.count(Feature.Name))
|
|
error(Twine("Target feature '") + Feature.Name +
|
|
"' is disallowed. Use --no-check-features to suppress.");
|
|
}
|
|
for (auto &Feature : Required) {
|
|
if (!ObjectFeatures.count(Feature))
|
|
error(Twine("Missing required target feature '") + Feature +
|
|
"'. Use --no-check-features to suppress.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Writer::calculateImports() {
|
|
for (Symbol *Sym : Symtab->getSymbols()) {
|
|
if (!Sym->isUndefined())
|
|
continue;
|
|
if (Sym->isWeak() && !Config->Relocatable)
|
|
continue;
|
|
if (!Sym->isLive())
|
|
continue;
|
|
if (!Sym->IsUsedInRegularObj)
|
|
continue;
|
|
// We don't generate imports for data symbols. They however can be imported
|
|
// as GOT entries.
|
|
if (isa<DataSymbol>(Sym))
|
|
continue;
|
|
|
|
LLVM_DEBUG(dbgs() << "import: " << Sym->getName() << "\n");
|
|
ImportedSymbols.emplace_back(Sym);
|
|
if (auto *F = dyn_cast<FunctionSymbol>(Sym))
|
|
F->setFunctionIndex(NumImportedFunctions++);
|
|
else if (auto *G = dyn_cast<GlobalSymbol>(Sym))
|
|
G->setGlobalIndex(NumImportedGlobals++);
|
|
else
|
|
cast<EventSymbol>(Sym)->setEventIndex(NumImportedEvents++);
|
|
}
|
|
}
|
|
|
|
void Writer::calculateExports() {
|
|
if (Config->Relocatable)
|
|
return;
|
|
|
|
if (!Config->Relocatable && !Config->ImportMemory)
|
|
Exports.push_back(WasmExport{"memory", WASM_EXTERNAL_MEMORY, 0});
|
|
|
|
if (!Config->Relocatable && Config->ExportTable)
|
|
Exports.push_back(WasmExport{FunctionTableName, WASM_EXTERNAL_TABLE, 0});
|
|
|
|
unsigned FakeGlobalIndex = NumImportedGlobals + InputGlobals.size();
|
|
|
|
for (Symbol *Sym : Symtab->getSymbols()) {
|
|
if (!Sym->isExported())
|
|
continue;
|
|
if (!Sym->isLive())
|
|
continue;
|
|
|
|
StringRef Name = Sym->getName();
|
|
WasmExport Export;
|
|
if (auto *F = dyn_cast<DefinedFunction>(Sym)) {
|
|
Export = {Name, WASM_EXTERNAL_FUNCTION, F->getFunctionIndex()};
|
|
} else if (auto *G = dyn_cast<DefinedGlobal>(Sym)) {
|
|
// TODO(sbc): Remove this check once to mutable global proposal is
|
|
// implement in all major browsers.
|
|
// See: https://github.com/WebAssembly/mutable-global
|
|
if (G->getGlobalType()->Mutable) {
|
|
// Only the __stack_pointer should ever be create as mutable.
|
|
assert(G == WasmSym::StackPointer);
|
|
continue;
|
|
}
|
|
Export = {Name, WASM_EXTERNAL_GLOBAL, G->getGlobalIndex()};
|
|
} else if (auto *E = dyn_cast<DefinedEvent>(Sym)) {
|
|
Export = {Name, WASM_EXTERNAL_EVENT, E->getEventIndex()};
|
|
} else {
|
|
auto *D = cast<DefinedData>(Sym);
|
|
DefinedFakeGlobals.emplace_back(D);
|
|
Export = {Name, WASM_EXTERNAL_GLOBAL, FakeGlobalIndex++};
|
|
}
|
|
|
|
LLVM_DEBUG(dbgs() << "Export: " << Name << "\n");
|
|
Exports.push_back(Export);
|
|
}
|
|
}
|
|
|
|
void Writer::assignSymtab() {
|
|
if (!Config->Relocatable)
|
|
return;
|
|
|
|
StringMap<uint32_t> SectionSymbolIndices;
|
|
|
|
unsigned SymbolIndex = SymtabEntries.size();
|
|
|
|
auto AddSymbol = [&](Symbol *Sym) {
|
|
if (auto *S = dyn_cast<SectionSymbol>(Sym)) {
|
|
StringRef Name = S->getName();
|
|
if (CustomSectionMapping.count(Name) == 0)
|
|
return;
|
|
|
|
auto SSI = SectionSymbolIndices.find(Name);
|
|
if (SSI != SectionSymbolIndices.end()) {
|
|
Sym->setOutputSymbolIndex(SSI->second);
|
|
return;
|
|
}
|
|
|
|
SectionSymbolIndices[Name] = SymbolIndex;
|
|
CustomSectionSymbols[Name] = cast<SectionSymbol>(Sym);
|
|
|
|
Sym->markLive();
|
|
}
|
|
|
|
// (Since this is relocatable output, GC is not performed so symbols must
|
|
// be live.)
|
|
assert(Sym->isLive());
|
|
Sym->setOutputSymbolIndex(SymbolIndex++);
|
|
SymtabEntries.emplace_back(Sym);
|
|
};
|
|
|
|
for (Symbol *Sym : Symtab->getSymbols())
|
|
if (Sym->IsUsedInRegularObj)
|
|
AddSymbol(Sym);
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
LLVM_DEBUG(dbgs() << "Local symtab entries: " << File->getName() << "\n");
|
|
for (Symbol *Sym : File->getSymbols())
|
|
if (Sym->isLocal())
|
|
AddSymbol(Sym);
|
|
}
|
|
}
|
|
|
|
uint32_t Writer::lookupType(const WasmSignature &Sig) {
|
|
auto It = TypeIndices.find(Sig);
|
|
if (It == TypeIndices.end()) {
|
|
error("type not found: " + toString(Sig));
|
|
return 0;
|
|
}
|
|
return It->second;
|
|
}
|
|
|
|
uint32_t Writer::registerType(const WasmSignature &Sig) {
|
|
auto Pair = TypeIndices.insert(std::make_pair(Sig, Types.size()));
|
|
if (Pair.second) {
|
|
LLVM_DEBUG(dbgs() << "type " << toString(Sig) << "\n");
|
|
Types.push_back(&Sig);
|
|
}
|
|
return Pair.first->second;
|
|
}
|
|
|
|
void Writer::calculateTypes() {
|
|
// The output type section is the union of the following sets:
|
|
// 1. Any signature used in the TYPE relocation
|
|
// 2. The signatures of all imported functions
|
|
// 3. The signatures of all defined functions
|
|
// 4. The signatures of all imported events
|
|
// 5. The signatures of all defined events
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
ArrayRef<WasmSignature> Types = File->getWasmObj()->types();
|
|
for (uint32_t I = 0; I < Types.size(); I++)
|
|
if (File->TypeIsUsed[I])
|
|
File->TypeMap[I] = registerType(Types[I]);
|
|
}
|
|
|
|
for (const Symbol *Sym : ImportedSymbols) {
|
|
if (auto *F = dyn_cast<FunctionSymbol>(Sym))
|
|
registerType(*F->Signature);
|
|
else if (auto *E = dyn_cast<EventSymbol>(Sym))
|
|
registerType(*E->Signature);
|
|
}
|
|
|
|
for (const InputFunction *F : InputFunctions)
|
|
registerType(F->Signature);
|
|
|
|
for (const InputEvent *E : InputEvents)
|
|
registerType(E->Signature);
|
|
}
|
|
|
|
static bool requiresGOTAccess(const Symbol* Sym) {
|
|
return Config->Pic && !Sym->isHidden() && !Sym->isLocal();
|
|
}
|
|
|
|
void Writer::processRelocations(InputChunk *Chunk) {
|
|
if (!Chunk->Live)
|
|
return;
|
|
ObjFile *File = Chunk->File;
|
|
ArrayRef<WasmSignature> Types = File->getWasmObj()->types();
|
|
for (const WasmRelocation &Reloc : Chunk->getRelocations()) {
|
|
switch (Reloc.Type) {
|
|
case R_WASM_TABLE_INDEX_I32:
|
|
case R_WASM_TABLE_INDEX_SLEB:
|
|
case R_WASM_TABLE_INDEX_REL_SLEB: {
|
|
FunctionSymbol *Sym = File->getFunctionSymbol(Reloc.Index);
|
|
if (Sym->hasTableIndex() || !Sym->hasFunctionIndex() || requiresGOTAccess(Sym))
|
|
break;
|
|
Sym->setTableIndex(TableBase + IndirectFunctions.size());
|
|
IndirectFunctions.emplace_back(Sym);
|
|
break;
|
|
}
|
|
case R_WASM_TYPE_INDEX_LEB:
|
|
// Mark target type as live
|
|
File->TypeMap[Reloc.Index] = registerType(Types[Reloc.Index]);
|
|
File->TypeIsUsed[Reloc.Index] = true;
|
|
break;
|
|
case R_WASM_GLOBAL_INDEX_LEB: {
|
|
auto* Sym = File->getSymbols()[Reloc.Index];
|
|
if (!isa<GlobalSymbol>(Sym) && !Sym->isInGOT()) {
|
|
Sym->setGOTIndex(NumImportedGlobals++);
|
|
GOTSymbols.push_back(Sym);
|
|
}
|
|
break;
|
|
}
|
|
case R_WASM_MEMORY_ADDR_SLEB:
|
|
case R_WASM_MEMORY_ADDR_LEB:
|
|
case R_WASM_MEMORY_ADDR_REL_SLEB: {
|
|
if (!Config->Relocatable) {
|
|
auto* Sym = File->getSymbols()[Reloc.Index];
|
|
if (Sym->isUndefined() && !Sym->isWeak()) {
|
|
error(toString(File) + ": cannot resolve relocation of type " +
|
|
relocTypeToString(Reloc.Type) +
|
|
" against undefined (non-weak) data symbol: " + toString(*Sym));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Config->Pic) {
|
|
switch (Reloc.Type) {
|
|
case R_WASM_TABLE_INDEX_SLEB:
|
|
case R_WASM_MEMORY_ADDR_SLEB:
|
|
case R_WASM_MEMORY_ADDR_LEB: {
|
|
// Certain relocation types can't be used when building PIC output, since
|
|
// they would require absolute symbol addresses at link time.
|
|
Symbol *Sym = File->getSymbols()[Reloc.Index];
|
|
error(toString(File) + ": relocation " +
|
|
relocTypeToString(Reloc.Type) + " cannot be used againt symbol " +
|
|
toString(*Sym) + "; recompile with -fPIC");
|
|
break;
|
|
}
|
|
case R_WASM_TABLE_INDEX_I32:
|
|
case R_WASM_MEMORY_ADDR_I32: {
|
|
// These relocation types are only present in the data section and
|
|
// will be converted into code by `generateRelocationCode`. This code
|
|
// requires the symbols to have GOT entires.
|
|
auto* Sym = File->getSymbols()[Reloc.Index];
|
|
if (requiresGOTAccess(Sym) && !Sym->isInGOT()) {
|
|
Sym->setGOTIndex(NumImportedGlobals++);
|
|
GOTSymbols.push_back(Sym);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Writer::assignIndexes() {
|
|
assert(InputFunctions.empty());
|
|
uint32_t FunctionIndex = NumImportedFunctions;
|
|
auto AddDefinedFunction = [&](InputFunction *Func) {
|
|
if (!Func->Live)
|
|
return;
|
|
InputFunctions.emplace_back(Func);
|
|
Func->setFunctionIndex(FunctionIndex++);
|
|
};
|
|
|
|
for (InputFunction *Func : Symtab->SyntheticFunctions)
|
|
AddDefinedFunction(Func);
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
LLVM_DEBUG(dbgs() << "Functions: " << File->getName() << "\n");
|
|
for (InputFunction *Func : File->Functions)
|
|
AddDefinedFunction(Func);
|
|
}
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
LLVM_DEBUG(dbgs() << "Handle relocs: " << File->getName() << "\n");
|
|
for (InputChunk *Chunk : File->Functions)
|
|
processRelocations(Chunk);
|
|
for (InputChunk *Chunk : File->Segments)
|
|
processRelocations(Chunk);
|
|
for (auto &P : File->CustomSections)
|
|
processRelocations(P);
|
|
}
|
|
|
|
assert(InputGlobals.empty());
|
|
uint32_t GlobalIndex = NumImportedGlobals;
|
|
auto AddDefinedGlobal = [&](InputGlobal *Global) {
|
|
if (Global->Live) {
|
|
LLVM_DEBUG(dbgs() << "AddDefinedGlobal: " << GlobalIndex << "\n");
|
|
Global->setGlobalIndex(GlobalIndex++);
|
|
InputGlobals.push_back(Global);
|
|
}
|
|
};
|
|
|
|
for (InputGlobal *Global : Symtab->SyntheticGlobals)
|
|
AddDefinedGlobal(Global);
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
LLVM_DEBUG(dbgs() << "Globals: " << File->getName() << "\n");
|
|
for (InputGlobal *Global : File->Globals)
|
|
AddDefinedGlobal(Global);
|
|
}
|
|
|
|
assert(InputEvents.empty());
|
|
uint32_t EventIndex = NumImportedEvents;
|
|
auto AddDefinedEvent = [&](InputEvent *Event) {
|
|
if (Event->Live) {
|
|
LLVM_DEBUG(dbgs() << "AddDefinedEvent: " << EventIndex << "\n");
|
|
Event->setEventIndex(EventIndex++);
|
|
InputEvents.push_back(Event);
|
|
}
|
|
};
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
LLVM_DEBUG(dbgs() << "Events: " << File->getName() << "\n");
|
|
for (InputEvent *Event : File->Events)
|
|
AddDefinedEvent(Event);
|
|
}
|
|
}
|
|
|
|
static StringRef getOutputDataSegmentName(StringRef Name) {
|
|
// With PIC code we currently only support a single data segment since
|
|
// we only have a single __memory_base to use as our base address.
|
|
if (Config->Pic)
|
|
return "data";
|
|
if (!Config->MergeDataSegments)
|
|
return Name;
|
|
if (Name.startswith(".text."))
|
|
return ".text";
|
|
if (Name.startswith(".data."))
|
|
return ".data";
|
|
if (Name.startswith(".bss."))
|
|
return ".bss";
|
|
if (Name.startswith(".rodata."))
|
|
return ".rodata";
|
|
return Name;
|
|
}
|
|
|
|
void Writer::createOutputSegments() {
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
for (InputSegment *Segment : File->Segments) {
|
|
if (!Segment->Live)
|
|
continue;
|
|
StringRef Name = getOutputDataSegmentName(Segment->getName());
|
|
OutputSegment *&S = SegmentMap[Name];
|
|
if (S == nullptr) {
|
|
LLVM_DEBUG(dbgs() << "new segment: " << Name << "\n");
|
|
S = make<OutputSegment>(Name, Segments.size());
|
|
Segments.push_back(S);
|
|
}
|
|
S->addInputSegment(Segment);
|
|
LLVM_DEBUG(dbgs() << "added data: " << Name << ": " << S->Size << "\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// For -shared (PIC) output, we create create a synthetic function which will
|
|
// apply any relocations to the data segments on startup. This function is
|
|
// called __wasm_apply_relocs and is added at the very beginning of
|
|
// __wasm_call_ctors before any of the constructors run.
|
|
void Writer::createApplyRelocationsFunction() {
|
|
LLVM_DEBUG(dbgs() << "createApplyRelocationsFunction\n");
|
|
// First write the body's contents to a string.
|
|
std::string BodyContent;
|
|
{
|
|
raw_string_ostream OS(BodyContent);
|
|
writeUleb128(OS, 0, "num locals");
|
|
for (const OutputSegment *Seg : Segments)
|
|
for (const InputSegment *InSeg : Seg->InputSegments)
|
|
InSeg->generateRelocationCode(OS);
|
|
writeU8(OS, WASM_OPCODE_END, "END");
|
|
}
|
|
|
|
// Once we know the size of the body we can create the final function body
|
|
std::string FunctionBody;
|
|
{
|
|
raw_string_ostream OS(FunctionBody);
|
|
writeUleb128(OS, BodyContent.size(), "function size");
|
|
OS << BodyContent;
|
|
}
|
|
|
|
ArrayRef<uint8_t> Body = arrayRefFromStringRef(Saver.save(FunctionBody));
|
|
cast<SyntheticFunction>(WasmSym::ApplyRelocs->Function)->setBody(Body);
|
|
}
|
|
|
|
// Create synthetic "__wasm_call_ctors" function based on ctor functions
|
|
// in input object.
|
|
void Writer::createCallCtorsFunction() {
|
|
if (!WasmSym::CallCtors->isLive())
|
|
return;
|
|
|
|
// First write the body's contents to a string.
|
|
std::string BodyContent;
|
|
{
|
|
raw_string_ostream OS(BodyContent);
|
|
writeUleb128(OS, 0, "num locals");
|
|
if (Config->Pic) {
|
|
writeU8(OS, WASM_OPCODE_CALL, "CALL");
|
|
writeUleb128(OS, WasmSym::ApplyRelocs->getFunctionIndex(),
|
|
"function index");
|
|
}
|
|
for (const WasmInitEntry &F : InitFunctions) {
|
|
writeU8(OS, WASM_OPCODE_CALL, "CALL");
|
|
writeUleb128(OS, F.Sym->getFunctionIndex(), "function index");
|
|
}
|
|
writeU8(OS, WASM_OPCODE_END, "END");
|
|
}
|
|
|
|
// Once we know the size of the body we can create the final function body
|
|
std::string FunctionBody;
|
|
{
|
|
raw_string_ostream OS(FunctionBody);
|
|
writeUleb128(OS, BodyContent.size(), "function size");
|
|
OS << BodyContent;
|
|
}
|
|
|
|
ArrayRef<uint8_t> Body = arrayRefFromStringRef(Saver.save(FunctionBody));
|
|
cast<SyntheticFunction>(WasmSym::CallCtors->Function)->setBody(Body);
|
|
}
|
|
|
|
// Populate InitFunctions vector with init functions from all input objects.
|
|
// This is then used either when creating the output linking section or to
|
|
// synthesize the "__wasm_call_ctors" function.
|
|
void Writer::calculateInitFunctions() {
|
|
if (!Config->Relocatable && !WasmSym::CallCtors->isLive())
|
|
return;
|
|
|
|
for (ObjFile *File : Symtab->ObjectFiles) {
|
|
const WasmLinkingData &L = File->getWasmObj()->linkingData();
|
|
for (const WasmInitFunc &F : L.InitFunctions) {
|
|
FunctionSymbol *Sym = File->getFunctionSymbol(F.Symbol);
|
|
assert(Sym->isLive());
|
|
if (*Sym->Signature != WasmSignature{{}, {}})
|
|
error("invalid signature for init func: " + toString(*Sym));
|
|
InitFunctions.emplace_back(WasmInitEntry{Sym, F.Priority});
|
|
}
|
|
}
|
|
|
|
// Sort in order of priority (lowest first) so that they are called
|
|
// in the correct order.
|
|
llvm::stable_sort(InitFunctions,
|
|
[](const WasmInitEntry &L, const WasmInitEntry &R) {
|
|
return L.Priority < R.Priority;
|
|
});
|
|
}
|
|
|
|
void Writer::run() {
|
|
if (Config->Relocatable || Config->Pic)
|
|
Config->GlobalBase = 0;
|
|
|
|
// For PIC code the table base is assigned dynamically by the loader.
|
|
// For non-PIC, we start at 1 so that accessing table index 0 always traps.
|
|
if (!Config->Pic)
|
|
TableBase = 1;
|
|
|
|
log("-- calculateTargetFeatures");
|
|
calculateTargetFeatures();
|
|
log("-- calculateImports");
|
|
calculateImports();
|
|
log("-- assignIndexes");
|
|
assignIndexes();
|
|
log("-- calculateInitFunctions");
|
|
calculateInitFunctions();
|
|
log("-- calculateTypes");
|
|
calculateTypes();
|
|
log("-- layoutMemory");
|
|
layoutMemory();
|
|
if (!Config->Relocatable) {
|
|
if (Config->Pic)
|
|
createApplyRelocationsFunction();
|
|
createCallCtorsFunction();
|
|
}
|
|
log("-- calculateExports");
|
|
calculateExports();
|
|
log("-- calculateCustomSections");
|
|
calculateCustomSections();
|
|
log("-- assignSymtab");
|
|
assignSymtab();
|
|
|
|
if (errorHandler().Verbose) {
|
|
log("Defined Functions: " + Twine(InputFunctions.size()));
|
|
log("Defined Globals : " + Twine(InputGlobals.size()));
|
|
log("Defined Events : " + Twine(InputEvents.size()));
|
|
log("Function Imports : " + Twine(NumImportedFunctions));
|
|
log("Global Imports : " + Twine(NumImportedGlobals));
|
|
log("Event Imports : " + Twine(NumImportedEvents));
|
|
for (ObjFile *File : Symtab->ObjectFiles)
|
|
File->dumpInfo();
|
|
}
|
|
|
|
createHeader();
|
|
log("-- createSections");
|
|
createSections();
|
|
|
|
log("-- openFile");
|
|
openFile();
|
|
if (errorCount())
|
|
return;
|
|
|
|
writeHeader();
|
|
|
|
log("-- writeSections");
|
|
writeSections();
|
|
if (errorCount())
|
|
return;
|
|
|
|
if (Error E = Buffer->commit())
|
|
fatal("failed to write the output file: " + toString(std::move(E)));
|
|
}
|
|
|
|
// Open a result file.
|
|
void Writer::openFile() {
|
|
log("writing: " + Config->OutputFile);
|
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
|
FileOutputBuffer::create(Config->OutputFile, FileSize,
|
|
FileOutputBuffer::F_executable);
|
|
|
|
if (!BufferOrErr)
|
|
error("failed to open " + Config->OutputFile + ": " +
|
|
toString(BufferOrErr.takeError()));
|
|
else
|
|
Buffer = std::move(*BufferOrErr);
|
|
}
|
|
|
|
void Writer::createHeader() {
|
|
raw_string_ostream OS(Header);
|
|
writeBytes(OS, WasmMagic, sizeof(WasmMagic), "wasm magic");
|
|
writeU32(OS, WasmVersion, "wasm version");
|
|
OS.flush();
|
|
FileSize += Header.size();
|
|
}
|
|
|
|
void lld::wasm::writeResult() { Writer().run(); }
|