Index: test/wasm/compress-relocs.ll =================================================================== --- /dev/null +++ test/wasm/compress-relocs.ll @@ -0,0 +1,22 @@ +; RUN: llc -filetype=obj %p/Inputs/call-indirect.ll -o %t2.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: wasm-ld --check-signatures -o %t.wasm %t2.o %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +; RUN: wasm-ld --check-signatures --compress-relocs -o %t-compressed.wasm %t2.o %t.o +; RUN: obj2yaml %t-compressed.wasm | FileCheck %s -check-prefix=COMPRESS + +target triple = "wasm32-unknown-unknown-wasm" + +define i32 @foo() { +entry: + ret i32 2 +} + +define void @_start() local_unnamed_addr { +entry: + ret void +} + +; CHECK: Body: 4100280284888080002100410028028088808000118080808000001A2000118180808000001A0B +; COMPRESS: Body: 41002802840821004100280280081100001A20001101001A0B Index: wasm/Config.h =================================================================== --- wasm/Config.h +++ wasm/Config.h @@ -20,6 +20,7 @@ struct Configuration { bool AllowUndefined; bool CheckSignatures; + bool CompressRelocTargets; bool Demangle; bool ExportTable; bool GcSections; Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -282,6 +282,8 @@ errorHandler().ErrorLimit = args::getInteger(Args, OPT_error_limit, 20); Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + Config->CompressRelocTargets = + Args.hasFlag(OPT_compress_relocs, OPT_no_compress_relocs, false); Config->CheckSignatures = Args.hasFlag(OPT_check_signatures, OPT_no_check_signatures, false); Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); @@ -308,6 +310,10 @@ Config->ZStackSize = args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize); + // We can't compress relocation targets when writing a relocatable file. + if (Config->Relocatable) + Config->CompressRelocTargets = false; + if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file)) readImportFile(Arg->getValue()); Index: wasm/InputChunks.h =================================================================== --- wasm/InputChunks.h +++ wasm/InputChunks.h @@ -48,11 +48,11 @@ Kind kind() const { return SectionKind; } - uint32_t getSize() const { return data().size(); } + virtual uint32_t getSize() const { return data().size(); } void copyRelocations(const WasmSection &Section); - void writeTo(uint8_t *SectionStart) const; + virtual void writeTo(uint8_t *SectionStart) const; ArrayRef getRelocations() const { return Relocations; } @@ -78,6 +78,7 @@ virtual ~InputChunk() = default; virtual ArrayRef data() const = 0; virtual uint32_t getInputSectionOffset() const = 0; + virtual uint32_t getInputSize() const { return getSize(); }; std::vector Relocations; Kind SectionKind; @@ -127,9 +128,17 @@ C->kind() == InputChunk::SyntheticFunction; } + void writeTo(uint8_t *SectionStart) const override; StringRef getName() const override { return Function->SymbolName; } StringRef getDebugName() const override { return Function->DebugName; } uint32_t getComdat() const override { return Function->Comdat; } + uint32_t getSize() const override { + if (Config->CompressRelocTargets && File) { + assert(CompressedSize); + return CompressedSize; + } + return data().size(); + } uint32_t getFunctionIndex() const { return FunctionIndex.getValue(); } bool hasFunctionIndex() const { return FunctionIndex.hasValue(); } void setFunctionIndex(uint32_t Index); @@ -137,13 +146,23 @@ bool hasTableIndex() const { return TableIndex.hasValue(); } void setTableIndex(uint32_t Index); + // The size of a given input function can depend on the values of the + // LEB relocations within it. This finalizeContents method is called after + // all the symbol values have be calcualted but before getSize() is ever + // called. + void calculateSize(); + const WasmSignature &Signature; protected: ArrayRef data() const override { + assert(!Config->CompressRelocTargets); return File->CodeSection->Content.slice(getInputSectionOffset(), Function->Size); } + + uint32_t getInputSize() const override { return Function->Size; } + uint32_t getInputSectionOffset() const override { return Function->CodeSectionOffset; } @@ -151,6 +170,8 @@ const WasmFunction *Function; llvm::Optional FunctionIndex; llvm::Optional TableIndex; + uint32_t CompressedFuncSize = 0; + uint32_t CompressedSize = 0; }; class SyntheticFunction : public InputFunction { Index: wasm/InputChunks.cpp =================================================================== --- wasm/InputChunks.cpp +++ wasm/InputChunks.cpp @@ -47,7 +47,7 @@ if (Section.Relocations.empty()) return; size_t Start = getInputSectionOffset(); - size_t Size = getSize(); + size_t Size = getInputSize(); for (const WasmRelocation &R : Section.Relocations) if (R.Offset >= Start && R.Offset < Start + Size) Relocations.push_back(R); @@ -143,3 +143,112 @@ assert(!hasTableIndex()); TableIndex = Index; } + +static unsigned getRelocWidthPadded(const WasmRelocation &Rel) { + switch (Rel.Type) { + case R_WEBASSEMBLY_TYPE_INDEX_LEB: + case R_WEBASSEMBLY_FUNCTION_INDEX_LEB: + case R_WEBASSEMBLY_GLOBAL_INDEX_LEB: + case R_WEBASSEMBLY_MEMORY_ADDR_LEB: + case R_WEBASSEMBLY_TABLE_INDEX_SLEB: + case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: + return 5; + case R_WEBASSEMBLY_TABLE_INDEX_I32: + case R_WEBASSEMBLY_MEMORY_ADDR_I32: + return 4; + default: + llvm_unreachable("unknown relocation type"); + } +} + +static unsigned writeCompressedReloc(const WasmRelocation &Rel, uint8_t *Buf, + uint32_t Value) { + switch (Rel.Type) { + case R_WEBASSEMBLY_TYPE_INDEX_LEB: + case R_WEBASSEMBLY_FUNCTION_INDEX_LEB: + case R_WEBASSEMBLY_GLOBAL_INDEX_LEB: + case R_WEBASSEMBLY_MEMORY_ADDR_LEB: + return encodeULEB128(Value, Buf); + case R_WEBASSEMBLY_TABLE_INDEX_SLEB: + case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: + return encodeSLEB128(static_cast(Value), Buf); + case R_WEBASSEMBLY_TABLE_INDEX_I32: + case R_WEBASSEMBLY_MEMORY_ADDR_I32: + return 4; + default: + llvm_unreachable("unknown relocation type"); + } +} + +static unsigned getRelocWidth(const WasmRelocation &Rel, uint32_t Value) { + uint8_t Target[5]; + return writeCompressedReloc(Rel, Target, Value); +} + +void InputFunction::calculateSize() { + if (!File || !Config->CompressRelocTargets) + return; + + DEBUG(dbgs() << "calculateSize: " << getName() << "\n"); + + const uint8_t *SecStart = File->CodeSection->Content.data(); + const uint8_t *FuncStart = SecStart + getInputSectionOffset(); + uint32_t FunctionSizeLength; + decodeULEB128(FuncStart, &FunctionSizeLength); + + uint32_t Start = getInputSectionOffset(); + uint32_t End = Start + Function->Size; + + uint32_t LastRelocEnd = Start + FunctionSizeLength; + for (WasmRelocation &Rel : Relocations) { + DEBUG(dbgs() << " region: " << (Rel.Offset - LastRelocEnd) << "\n"); + assert(Rel.Offset >= LastRelocEnd); + CompressedFuncSize += Rel.Offset - LastRelocEnd; + CompressedFuncSize += getRelocWidth(Rel, File->calcNewValue(Rel)); + LastRelocEnd = Rel.Offset + getRelocWidthPadded(Rel); + assert(LastRelocEnd <= End); + } + DEBUG(dbgs() << " final region: " << (End - LastRelocEnd) << "\n"); + CompressedFuncSize += End - LastRelocEnd; + + // Now we know how long the resulting function is we can add the encoding + // of its length + uint8_t Buf[5]; + CompressedSize = CompressedFuncSize + encodeULEB128(CompressedFuncSize, Buf); + + DEBUG(dbgs() << " calculateSize orig: " << Function->Size << "\n"); + DEBUG(dbgs() << " calculateSize new: " << CompressedSize << "\n"); +} + +void InputFunction::writeTo(uint8_t *Buf) const { + if (!File || !Config->CompressRelocTargets) + return InputChunk::writeTo(Buf); + + Buf += OutputOffset; + uint8_t *Orig = Buf; + + const uint8_t *SecStart = File->CodeSection->Content.data(); + const uint8_t *FuncStart = SecStart + getInputSectionOffset(); + const uint8_t *End = FuncStart + Function->Size; + uint32_t Count; + decodeULEB128(Buf, &Count); + FuncStart += Count; + + DEBUG(dbgs() << "write func: " << getName() << "\n"); + Buf += encodeULEB128(CompressedFuncSize, Buf); + const uint8_t *LastRelocEnd = FuncStart; + for (const WasmRelocation &Rel : Relocations) { + unsigned ChunkSize = (SecStart + Rel.Offset) - LastRelocEnd; + DEBUG(dbgs() << " write chunk: " << ChunkSize << "\n"); + memcpy(Buf, LastRelocEnd, ChunkSize); + Buf += ChunkSize; + Buf += writeCompressedReloc(Rel, Buf, File->calcNewValue(Rel)); + LastRelocEnd = SecStart + Rel.Offset + getRelocWidthPadded(Rel); + } + + assert(LastRelocEnd <= End); + unsigned ChunkSize = End - LastRelocEnd; + DEBUG(dbgs() << " write final chunk: " << ChunkSize << "\n"); + memcpy(Buf, LastRelocEnd, ChunkSize); + DEBUG(dbgs() << " total: " << (Buf + ChunkSize - Orig) << "\n"); +} Index: wasm/Options.td =================================================================== --- wasm/Options.td +++ wasm/Options.td @@ -23,6 +23,10 @@ def color_diagnostics_eq: J<"color-diagnostics=">, HelpText<"Use colors in diagnostics">; +defm compress_relocs: B<"compress-relocs", + "Compress relocation targets that contain padding", + "Do not compress relocation targets that contain padding">; + defm demangle: B<"demangle", "Demangle symbol names", "Do not demangle symbol names">; Index: wasm/OutputSections.cpp =================================================================== --- wasm/OutputSections.cpp +++ wasm/OutputSections.cpp @@ -85,8 +85,9 @@ OS.flush(); BodySize = CodeSectionHeader.size(); - for (InputChunk *Func : Functions) { + for (InputFunction *Func : Functions) { Func->OutputOffset = BodySize; + Func->calculateSize(); BodySize += Func->getSize(); }