Index: wasm/InputChunks.h =================================================================== --- wasm/InputChunks.h +++ wasm/InputChunks.h @@ -152,6 +152,8 @@ bool hasTableIndex() const { return TableIndex.hasValue(); } void setTableIndex(uint32_t Index); + uint32_t translateCompressedPC(uint32_t PC) const; + // 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 @@ -178,6 +180,7 @@ llvm::Optional TableIndex; uint32_t CompressedFuncSize = 0; uint32_t CompressedSize = 0; + std::vector> CompressTransform; }; class SyntheticFunction : public InputFunction { Index: wasm/InputChunks.cpp =================================================================== --- wasm/InputChunks.cpp +++ wasm/InputChunks.cpp @@ -14,12 +14,14 @@ #include "lld/Common/ErrorHandler.h" #include "lld/Common/LLVM.h" #include "llvm/Support/LEB128.h" +#include #define DEBUG_TYPE "lld" using namespace llvm; using namespace llvm::wasm; using namespace llvm::support::endian; +using namespace llvm::dwarf; using namespace lld; using namespace lld::wasm; @@ -90,6 +92,177 @@ } } +static void updateDebugLine(ObjFile *File, const ArrayRef &data, + const std::vector &Relocations, + uint8_t *OutputBuf) { + LLVM_DEBUG(dbgs() << "Processing .debug_line\n"); + + // R_WEBASSEMBLY_FUNCTION_OFFSET_I32 entries will be found during .debug_info + // units parsing. Save these entries to access them during DW_LNE_set_address + // operation below. + llvm::DenseMap FunctionRelocations; + for (const WasmRelocation &Rel : Relocations) { + if (Rel.Type == R_WEBASSEMBLY_FUNCTION_OFFSET_I32) + FunctionRelocations[Rel.Offset] = &Rel; + } + + // Scan all CUs to find only related to the current data region. + DWARFContext *DW = File->getDWARFContext(); + for (const auto &CU : DW->compile_units()) { + const DWARFSection &S = CU->getLineSection(); + // Skip .debug_line section if is not contained in the data region. + if (data.data() > (const unsigned char *)S.Data.data() || + (const unsigned char *)S.Data.data() + S.Data.size() > + data.data() + data.size()) + continue; + + // Processing .debug_line section unit + DWARFDataExtractor d(DW->getDWARFObj(), S, /* isLittleEndian = */ true, + CU->getFormParams().AddrSize); + size_t CUOffset = (const unsigned char *)S.Data.data() - data.data(); + + // Parsing .debug_line prolog for DWARF format versions 2-4. + // TODO add v5 support? + uint32_t Offset = 0; + uint64_t TotalLength = d.getU32(&Offset); + assert(TotalLength < 0xffffff00); + uint16_t Version = d.getU16(&Offset); + assert(Version >= 2 && Version <= 4); + const uint64_t SizeOfPrologueLength = 4; + uint64_t PrologueLength = d.getUnsigned(&Offset, SizeOfPrologueLength); + const uint64_t EndPrologueOffset = PrologueLength + Offset; + const uint64_t SizeOfTotalLength = 4; + const uint64_t EndOffset = TotalLength + SizeOfTotalLength; + + /* uint8_t MinInstLength = */ d.getU8(&Offset); + uint8_t MaxOpsPerInst = Version >= 4 ? d.getU8(&Offset) : 1; + assert(MaxOpsPerInst == 1); + /* uint8_t DefaultIsStmt = */ d.getU8(&Offset); + int8_t LineBase = (int8_t)d.getU8(&Offset); + uint8_t LineRange = d.getU8(&Offset); + uint8_t OpcodeBase = d.getU8(&Offset); + + std::vector StandardOpcodeLengths; + StandardOpcodeLengths.reserve(OpcodeBase - 1); + for (uint32_t I = 1; I < OpcodeBase; ++I) { + uint8_t OpLen = d.getU8(&Offset); + StandardOpcodeLengths.push_back(OpLen); + } + + assert(Offset < EndPrologueOffset); + // Skip directory entries + Offset = EndPrologueOffset; + + // TODO remove tracking of the Address (added for debugging) + uint64_t Address = 0; + std::unique_ptr PCTr; + while (Offset < EndOffset) { + const uint8_t OpcodeOffset = Offset; + uint8_t Opcode = d.getU8(&Offset); + if (Opcode == 0) { + // Parse Extended Opcodes, mosly DW_LNE_set_address (see below) + uint64_t Len = d.getULEB128(&Offset); + assert(Len > 0); + const uint64_t EndExtOp = Offset + Len; + uint8_t SubOpcode = d.getU8(&Offset); + switch (SubOpcode) { + case DW_LNE_set_address: { + const uint32_t RelocValueOffset = (uint32_t)CUOffset + Offset; + Address = d.getRelocatedAddress(&Offset); + LLVM_DEBUG(dbgs() + << "DW_LNE_set_address " << (void *)Address << "\n"); + // Get relocation entry to get get right InputFunction and start PC. + const WasmRelocation *SecRelPtr = + FunctionRelocations[RelocValueOffset]; + assert(SecRelPtr); + PCTr = File->getPCTransformIterator(*SecRelPtr); + break; + } + case DW_LNE_end_sequence: { + LLVM_DEBUG(dbgs() << " - end_seq " << (void *)Address << "\n"); + break; + } + } + Offset = EndExtOp; + } else if (Opcode < OpcodeBase) { + // Parse Standard Opcodes + switch (Opcode) { + case DW_LNS_copy: { + LLVM_DEBUG(dbgs() << " - copy " << (void *)Address << "\n"); + break; + } + case DW_LNS_advance_pc: { + // Check if PC advance value for DW_LNS_advance_pc was changed, + // and change its argument LEB value to fit the smaller value + // if needed + const uint32_t LEBStart = Offset; + uint64_t AddrOffset = d.getULEB128(&Offset); + Address += AddrOffset; + LLVM_DEBUG(dbgs() << "DW_LNE_advance_pc " << (void *)Address << "\n"); + const uint32_t Delta = PCTr->advance(AddrOffset); + assert(Delta <= AddrOffset); + if (Delta < AddrOffset) { + encodeULEB128(Delta, OutputBuf + CUOffset + LEBStart, + Offset - LEBStart); + } + break; + } + case DW_LNS_const_add_pc: { + // This one tricky, adding extra table line here by replacing + // standard opcode with special one, with shorter PC advance value, + // if needed + // TODO Is this the best we can do here? + uint8_t AdjustOpcode = 255 - OpcodeBase; + uint64_t AddrOffset = AdjustOpcode / LineRange; + Address += AddrOffset; + LLVM_DEBUG(dbgs() + << "DW_LNS_const_add_pc " << (void *)Address << "\n"); + const uint32_t Delta = PCTr->advance(AddrOffset); + assert(Delta <= AddrOffset); + if (Delta < AddrOffset) { + uint8_t NewOpcode = OpcodeBase - LineBase + Delta * LineRange; + OutputBuf[CUOffset + OpcodeOffset] = NewOpcode; + } + break; + } + case DW_LNS_fixed_advance_pc: { + // Check if PC advance value for DW_LNS_fixed_advance_pc was changed, + // and change its uint16_t argument if needed. + uint16_t PCOffset = d.getU16(&Offset); + Address += PCOffset; + LLVM_DEBUG(dbgs() + << "DW_LNS_fixed_advance_pc " << (void *)Address << "\n"); + const uint32_t Delta = PCTr->advance(PCOffset); + assert(Delta <= PCOffset); + if (Delta < PCOffset) + write16le(OutputBuf + CUOffset + OpcodeOffset + 1, Delta); + break; + } + default: { + uint8_t Skip = StandardOpcodeLengths[Opcode - 1U]; + for (uint8_t I = 0; I < Skip; ++I) + d.getULEB128(&Offset); + break; + } + } + } else { + // Parsing Special Opcode and updating only PC advance value + // part if needed. + uint8_t AdjustOpcode = Opcode - OpcodeBase; + uint64_t AddrOffset = AdjustOpcode / LineRange; + Address += AddrOffset; + LLVM_DEBUG(dbgs() << " - special " << (void *)Address << "\n"); + const uint32_t Delta = PCTr->advance(AddrOffset); + assert(Delta <= AddrOffset); + if (Delta < AddrOffset) { + uint8_t NewOpcode = Opcode - (AddrOffset - Delta) * LineRange; + OutputBuf[CUOffset + OpcodeOffset] = NewOpcode; + } + } + } + } +} + // Copy this input chunk to an mmap'ed output file and apply relocations. void InputChunk::writeTo(uint8_t *Buf) const { // Copy contents @@ -103,6 +276,9 @@ verifyRelocTargets(); #endif + if (Config->CompressRelocTargets && getName() == ".debug_line") + updateDebugLine(File, data(), Relocations, Buf + OutputOffset); + LLVM_DEBUG(dbgs() << "applying relocations: " << getName() << " count=" << Relocations.size() << "\n"); int32_t Off = OutputOffset - getInputSectionOffset(); @@ -247,10 +423,16 @@ CompressedFuncSize += Rel.Offset - LastRelocEnd; CompressedFuncSize += getRelocWidth(Rel, File->calcNewValue(Rel)); LastRelocEnd = Rel.Offset + getRelocWidthPadded(Rel); + + CompressTransform.emplace_back(LastRelocEnd - Start - FunctionSizeLength, + CompressedFuncSize); } LLVM_DEBUG(dbgs() << " final region: " << (End - LastRelocEnd) << "\n"); CompressedFuncSize += End - LastRelocEnd; + CompressTransform.emplace_back(Function->Size - FunctionSizeLength, + CompressedFuncSize); + // Now we know how long the resulting function is we can add the encoding // of its length uint8_t Buf[5]; @@ -293,3 +475,27 @@ memcpy(Buf, LastRelocEnd, ChunkSize); LLVM_DEBUG(dbgs() << " total: " << (Buf + ChunkSize - Orig) << "\n"); } + +uint32_t InputFunction::translateCompressedPC(uint32_t PC) const { + if (CompressTransform.size() == 0) + return PC; + + // Find address range: a pair of when the range starts and its new start. + auto I = + std::lower_bound(CompressTransform.rbegin(), CompressTransform.rend(), PC, + [](const std::pair &El, + uint32_t Value) { return El.first > Value; }); + + if (I == CompressTransform.rend()) { + // If a range is not found, the PC was not transformed yet, + // clamping result to the new start of the first range. + return std::min(PC, CompressTransform[0].second); + } + + uint32_t NewPC = I->second + (PC - I->first); + // If a range is not last one, the result needs to be clamped to the new + // start of the following range. + if (I == CompressTransform.rbegin()) + return NewPC; + return std::min(NewPC, std::prev(I)->second); +} Index: wasm/InputFiles.h =================================================================== --- wasm/InputFiles.h +++ wasm/InputFiles.h @@ -14,11 +14,13 @@ #include "lld/Common/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Wasm.h" #include "llvm/Support/MemoryBuffer.h" #include +using llvm::DWARFContext; using llvm::object::Archive; using llvm::object::WasmObjectFile; using llvm::object::WasmSection; @@ -80,6 +82,25 @@ llvm::DenseSet Seen; }; +class PCTransformIterator { +public: + PCTransformIterator(const InputFunction &Function, uint32_t PC) + : Function(Function), OldPC(PC) { + NewPC = translate(PC); + } + + uint32_t getOldPC() const { return OldPC; } + uint32_t getNewPC() const { return NewPC; } + uint32_t advance(uint32_t Delta); + +private: + const InputFunction &Function; + uint32_t OldPC; + uint32_t NewPC; + + uint32_t translate(uint32_t PC) const; +}; + // .o file (wasm object file) class ObjFile : public InputFile { public: @@ -90,6 +111,7 @@ // Returns the underlying wasm file. const WasmObjectFile *getWasmObj() const { return WasmObj.get(); } + DWARFContext *getDWARFContext() const { return DWContext.get(); } void dumpInfo() const; @@ -98,6 +120,9 @@ uint32_t calcNewAddend(const WasmRelocation &Reloc) const; uint32_t calcExpectedValue(const WasmRelocation &Reloc) const; + std::unique_ptr + getPCTransformIterator(const WasmRelocation &Reloc) const; + const WasmSection *CodeSection = nullptr; const WasmSection *DataSection = nullptr; @@ -130,6 +155,8 @@ std::vector Symbols; std::unique_ptr WasmObj; + + std::unique_ptr DWContext; }; // Opens a given file. Index: wasm/InputFiles.cpp =================================================================== --- wasm/InputFiles.cpp +++ wasm/InputFiles.cpp @@ -42,6 +42,19 @@ return MBRef; } +uint32_t PCTransformIterator::advance(uint32_t Delta) { + const uint32_t PastNewPC = NewPC; + OldPC += Delta; + NewPC = translate(OldPC); + assert(NewPC >= PastNewPC); + return NewPC - PastNewPC; +} + +uint32_t PCTransformIterator::translate(uint32_t PC) const { + auto i = Function.translateCompressedPC(PC); + return i; +} + void ObjFile::dumpInfo() const { log("info for: " + getName() + "\n Symbols : " + Twine(Symbols.size()) + @@ -148,6 +161,13 @@ } } +std::unique_ptr +ObjFile::getPCTransformIterator(const WasmRelocation &Reloc) const { + if (auto *Sym = dyn_cast(getFunctionSymbol(Reloc.Index))) + return make_unique(*Sym->Function, Reloc.Addend); + llvm_unreachable("InputFunction is not found"); +} + void ObjFile::parse() { // Parse a memory buffer as a wasm file. LLVM_DEBUG(dbgs() << "Parsing object: " << toString(this) << "\n"); @@ -162,6 +182,8 @@ Bin.release(); WasmObj.reset(Obj); + DWContext = DWARFContext::create(*Obj); + // Build up a map of function indices to table indices for use when // verifying the existing table index relocations uint32_t TotalFunctions =