diff --git a/lld/test/ELF/map-file.s b/lld/test/ELF/map-file.s --- a/lld/test/ELF/map-file.s +++ b/lld/test/ELF/map-file.s @@ -11,7 +11,7 @@ # RUN: ld.lld %t1.o %t2.o %t3.o %t4.a %t5.so -o %t -M | FileCheck --match-full-lines --strict-whitespace %s # RUN: ld.lld %t1.o %t2.o %t3.o %t4.a %t5.so -o %t --print-map | FileCheck --match-full-lines -strict-whitespace %s # RUN: ld.lld %t1.o %t2.o %t3.o %t4.a %t5.so -o %t -Map=%t.map -# RUN: FileCheck -strict-whitespace %s < %t.map +# RUN: FileCheck -match-full-lines -strict-whitespace %s < %t.map .global _start _start: diff --git a/lld/test/wasm/early-exit-for-bad-paths.s b/lld/test/wasm/early-exit-for-bad-paths.s --- a/lld/test/wasm/early-exit-for-bad-paths.s +++ b/lld/test/wasm/early-exit-for-bad-paths.s @@ -4,10 +4,16 @@ # RUN: FileCheck %s -check-prefixes=NO-DIR-OUTPUT,CHECK # RUN: not wasm-ld %t.o -o %s/dir_is_a_file 2>&1 | \ # RUN: FileCheck %s -check-prefixes=DIR-IS-OUTPUT,CHECK -# TODO(sbc): check similar check for -Map file once we add that option + +# RUN: not wasm-ld %t.o -o %t -Map=does_not_exist/output 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=NO-DIR-MAP,CHECK +# RUN: not wasm-ld %t.o -o %t -Map=%s/dir_is_a_file 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=DIR-IS-MAP,CHECK # NO-DIR-OUTPUT: error: cannot open output file does_not_exist/output: # DIR-IS-OUTPUT: error: cannot open output file {{.*}}/dir_is_a_file: +# NO-DIR-MAP: error: cannot open map file does_not_exist/output: +# DIR-IS-MAP: error: cannot open map file {{.*}}/dir_is_a_file: # We should exit before doing the actual link. If an undefined symbol error is # discovered we haven't bailed out early as expected. diff --git a/lld/test/wasm/map-file.s b/lld/test/wasm/map-file.s new file mode 100644 --- /dev/null +++ b/lld/test/wasm/map-file.s @@ -0,0 +1,47 @@ +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t1.o +# RUN: wasm-ld %t1.o -o %t -M | FileCheck --match-full-lines --strict-whitespace %s +# RUN: wasm-ld %t1.o -o %t -print-map | FileCheck --match-full-lines --strict-whitespace %s +# RUN: wasm-ld %t1.o -o %t -Map=%t.map +# RUN: FileCheck --match-full-lines --strict-whitespace %s < %t.map + +bar: + .functype bar () -> () + i32.const somedata + end_function + + .globl _start +_start: + .functype _start () -> () + call bar + end_function + +.section .data.somedata,"",@ +somedata: + .int32 123 +.size somedata, 4 + +.section .debug_info,"",@ + .int32 bar + +# CHECK: Addr Off Size Out In Symbol +# CHECK-NEXT: - 8 6 TYPE +# CHECK-NEXT: - e 5 FUNCTION +# CHECK-NEXT: - 13 7 TABLE +# CHECK-NEXT: - 1a 5 MEMORY +# CHECK-NEXT: - 1f a GLOBAL +# CHECK-NEXT: - 29 15 EXPORT +# CHECK-NEXT: - 3e 15 CODE +# CHECK-NEXT: - 3f 9 {{.*}}{{/|\\}}map-file.s.tmp1.o:(bar) +# CHECK-NEXT: - 3f 9 bar +# CHECK-NEXT: - 48 9 {{.*}}{{/|\\}}map-file.s.tmp1.o:(_start) +# CHECK-NEXT: - 48 9 _start +# CHECK-NEXT: - 53 d DATA +# CHECK-NEXT: 400 54 4 .data +# CHECK-NEXT: 400 5a 4 {{.*}}{{/|\\}}map-file.s.tmp1.o:(.data.somedata) +# CHECK-NEXT: 400 5a 4 somedata +# CHECK-NEXT: - 60 12 CUSTOM(.debug_info) +# CHECK-NEXT: - 72 17 CUSTOM(name) + +# RUN: not wasm-ld %t1.o -o /dev/null -Map=/ 2>&1 \ +# RUN: | FileCheck -check-prefix=FAIL %s +# FAIL: wasm-ld: error: cannot open map file / diff --git a/lld/wasm/CMakeLists.txt b/lld/wasm/CMakeLists.txt --- a/lld/wasm/CMakeLists.txt +++ b/lld/wasm/CMakeLists.txt @@ -11,6 +11,7 @@ InputChunks.cpp InputFiles.cpp LTO.cpp + MapFile.cpp MarkLive.cpp OutputSections.cpp Relocations.cpp diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h --- a/lld/wasm/Config.h +++ b/lld/wasm/Config.h @@ -56,6 +56,7 @@ llvm::StringRef thinLTOJobs; llvm::StringRef entry; + llvm::StringRef mapFile; llvm::StringRef outputFile; llvm::StringRef thinLTOCacheDir; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -343,6 +343,7 @@ config->importTable = args.hasArg(OPT_import_table); config->ltoo = args::getInteger(args, OPT_lto_O, 2); config->ltoPartitions = args::getInteger(args, OPT_lto_partitions, 1); + config->mapFile = args.getLastArgValue(OPT_Map); config->optimize = args::getInteger(args, OPT_O, 0); config->outputFile = args.getLastArgValue(OPT_o); config->relocatable = args.hasArg(OPT_relocatable); @@ -398,6 +399,9 @@ for (StringRef s : arg->getValues()) config->features->push_back(std::string(s)); } + + if (args.hasArg(OPT_print_map)) + config->mapFile = "-"; } // Some Config members do not directly correspond to any particular @@ -751,7 +755,8 @@ // find that it failed because there was a mistake in their command-line. if (auto e = tryCreateFile(config->outputFile)) error("cannot open output file " + config->outputFile + ": " + e.message()); - // TODO(sbc): add check for map file too once we add support for that. + if (auto e = tryCreateFile(config->mapFile)) + error("cannot open map file " + config->mapFile + ": " + e.message()); if (errorCount()) return; diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -57,6 +57,8 @@ void writeRelocations(llvm::raw_ostream &os) const; ObjFile *file; + OutputSection *outputSec = nullptr; + // Offset withing the output section int32_t outputOffset = 0; // Signals that the section is part of the output. The garbage collector, @@ -214,8 +216,6 @@ StringRef getDebugName() const override { return StringRef(); } uint32_t getComdat() const override { return UINT32_MAX; } - OutputSection *outputSec = nullptr; - protected: ArrayRef data() const override { return section.Content; } diff --git a/lld/wasm/MapFile.h b/lld/wasm/MapFile.h new file mode 100644 --- /dev/null +++ b/lld/wasm/MapFile.h @@ -0,0 +1,21 @@ +//===- MapFile.h ------------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_MAPFILE_H +#define LLD_WASM_MAPFILE_H + +#include "llvm/ADT/ArrayRef.h" + +namespace lld { +namespace wasm { +class OutputSection; +void writeMapFile(llvm::ArrayRef outputSections); +} // namespace wasm +} // namespace lld + +#endif diff --git a/lld/wasm/MapFile.cpp b/lld/wasm/MapFile.cpp new file mode 100644 --- /dev/null +++ b/lld/wasm/MapFile.cpp @@ -0,0 +1,152 @@ +//===- MapFile.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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the -Map option. It shows lists in order and +// hierarchically the output sections, input sections, input files and +// symbol: +// +// Addr Off Size Out In Symbol +// - 00000015 10 .text +// - 0000000e 10 test.o:(.text) +// - 00000000 5 local +// - 00000000 5 f(int) +// +//===----------------------------------------------------------------------===// + +#include "MapFile.h" +#include "InputFiles.h" +#include "OutputSections.h" +#include "OutputSegment.h" +#include "SymbolTable.h" +#include "Symbols.h" +#include "SyntheticSections.h" +#include "lld/Common/Strings.h" +#include "lld/Common/Threads.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::object; + +namespace lld { +namespace wasm { + +using SymbolMapTy = DenseMap>; + +// Print out the first three columns of a line. +static void writeHeader(raw_ostream &os, int64_t vma, uint64_t lma, + uint64_t size) { + // Not all entries in the map has a virtual memory address (e.g. functions) + if (vma == -1) + os << format(" - %8llx %8llx ", lma, size); + else + os << format("%8llx %8llx %8llx ", vma, lma, size); +} + +// Returns a list of all symbols that we want to print out. +static std::vector getSymbols() { + std::vector v; + for (InputFile *file : symtab->objectFiles) + for (Symbol *b : file->getSymbols()) + if (auto *dr = dyn_cast(b)) + if ((!isa(dr)) && dr->isLive() && + (dr->getFile() == file)) + v.push_back(dr); + return v; +} + +// Returns a map from sections to their symbols. +static SymbolMapTy getSectionSyms(ArrayRef syms) { + SymbolMapTy ret; + for (Symbol *dr : syms) + ret[dr->getChunk()].push_back(dr); + return ret; +} + +// Construct a map from symbols to their stringified representations. +// Demangling symbols (which is what toString() does) is slow, so +// we do that in batch using parallel-for. +static DenseMap +getSymbolStrings(ArrayRef syms) { + std::vector str(syms.size()); + parallelForEachN(0, syms.size(), [&](size_t i) { + raw_string_ostream os(str[i]); + auto &chunk = *syms[i]->getChunk(); + uint64_t fileOffset = chunk.outputSec->getOffset() + chunk.outputOffset; + uint64_t vma = -1; + uint64_t size = 0; + if (auto *DD = dyn_cast(syms[i])) { + vma = DD->getVirtualAddress(); + size = DD->getSize(); + fileOffset += DD->offset; + } + if (auto *DF = dyn_cast(syms[i])) { + size = DF->function->getSize(); + } + writeHeader(os, vma, fileOffset, size); + os.indent(16) << toString(*syms[i]); + }); + + DenseMap ret; + for (size_t i = 0, e = syms.size(); i < e; ++i) + ret[syms[i]] = std::move(str[i]); + return ret; +} + +void writeMapFile(ArrayRef outputSections) { + if (config->mapFile.empty()) + return; + + // Open a map file for writing. + std::error_code ec; + raw_fd_ostream os(config->mapFile, ec, sys::fs::OF_None); + if (ec) { + error("cannot open " + config->mapFile + ": " + ec.message()); + return; + } + + // Collect symbol info that we want to print out. + std::vector syms = getSymbols(); + SymbolMapTy sectionSyms = getSectionSyms(syms); + DenseMap symStr = getSymbolStrings(syms); + + // Print out the header line. + os << " Addr Off Size Out In Symbol\n"; + + for (OutputSection *osec : outputSections) { + writeHeader(os, -1, osec->getOffset(), osec->getSize()); + os << toString(*osec) << '\n'; + if (auto *code = dyn_cast(osec)) { + for (auto *chunk : code->functions) { + writeHeader(os, -1, chunk->outputSec->getOffset() + chunk->outputOffset, + chunk->getSize()); + os.indent(8) << toString(chunk) << '\n'; + for (Symbol *sym : sectionSyms[chunk]) + os << symStr[sym] << '\n'; + } + } else if (auto *data = dyn_cast(osec)) { + for (auto *oseg : data->segments) { + writeHeader(os, oseg->startVA, data->getOffset() + oseg->sectionOffset, + oseg->size); + os << oseg->name << '\n'; + for (auto *chunk : oseg->inputSegments) { + writeHeader(os, oseg->startVA + chunk->outputSegmentOffset, + chunk->outputSec->getOffset() + chunk->outputOffset, + chunk->getSize()); + os.indent(8) << toString(chunk) << '\n'; + for (Symbol *sym : sectionSyms[chunk]) + os << symStr[sym] << '\n'; + } + } + } + } +} + +} // namespace wasm +} // namespace lld diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -64,6 +64,8 @@ def mllvm: S<"mllvm">, HelpText<"Options to pass to LLVM">; +defm Map: Eq<"Map", "Print a link map to the specified file">; + def no_color_diagnostics: F<"no-color-diagnostics">, HelpText<"Do not use colors in diagnostics">; @@ -82,6 +84,9 @@ "List removed unused sections", "Do not list removed unused sections">; +def print_map: F<"print-map">, + HelpText<"Print a link map to the standard output">; + def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">; defm reproduce: Eq<"reproduce", "Dump linker invocation and input files for debugging">; @@ -180,6 +185,7 @@ def: Flag<["-"], "E">, Alias, HelpText<"Alias for --export-dynamic">; def: Flag<["-"], "i">, Alias; def: Flag<["-"], "m">, Alias; +def: Flag<["-"], "M">, Alias, HelpText<"Alias for --print-map">; def: Flag<["-"], "r">, Alias; def: Flag<["-"], "s">, Alias, HelpText<"Alias for --strip-all">; def: Flag<["-"], "S">, Alias, HelpText<"Alias for --strip-debug">; diff --git a/lld/wasm/OutputSections.h b/lld/wasm/OutputSections.h --- a/lld/wasm/OutputSections.h +++ b/lld/wasm/OutputSections.h @@ -40,6 +40,7 @@ void createHeader(size_t bodySize); virtual bool isNeeded() const { return true; } virtual size_t getSize() const = 0; + virtual size_t getOffset() { return offset; } virtual void writeTo(uint8_t *buf) = 0; virtual void finalizeContents() = 0; virtual uint32_t getNumRelocations() const { return 0; } @@ -60,6 +61,10 @@ explicit CodeSection(ArrayRef functions) : OutputSection(llvm::wasm::WASM_SEC_CODE), functions(functions) {} + static bool classof(const OutputSection *sec) { + return sec->type == llvm::wasm::WASM_SEC_CODE; + } + size_t getSize() const override { return header.size() + bodySize; } void writeTo(uint8_t *buf) override; uint32_t getNumRelocations() const override; @@ -67,8 +72,9 @@ bool isNeeded() const override { return functions.size() > 0; } void finalizeContents() override; -protected: ArrayRef functions; + +protected: std::string codeSectionHeader; size_t bodySize = 0; }; @@ -78,6 +84,10 @@ explicit DataSection(ArrayRef segments) : OutputSection(llvm::wasm::WASM_SEC_DATA), segments(segments) {} + static bool classof(const OutputSection *sec) { + return sec->type == llvm::wasm::WASM_SEC_DATA; + } + size_t getSize() const override { return header.size() + bodySize; } void writeTo(uint8_t *buf) override; uint32_t getNumRelocations() const override; @@ -85,8 +95,9 @@ bool isNeeded() const override; void finalizeContents() override; -protected: ArrayRef segments; + +protected: std::string dataSectionHeader; size_t bodySize = 0; }; @@ -103,6 +114,11 @@ CustomSection(std::string name, ArrayRef inputSections) : OutputSection(llvm::wasm::WASM_SEC_CUSTOM, name), inputSections(inputSections) {} + + static bool classof(const OutputSection *sec) { + return sec->type == llvm::wasm::WASM_SEC_CUSTOM; + } + size_t getSize() const override { return header.size() + nameData.size() + payloadSize; } diff --git a/lld/wasm/OutputSections.cpp b/lld/wasm/OutputSections.cpp --- a/lld/wasm/OutputSections.cpp +++ b/lld/wasm/OutputSections.cpp @@ -87,6 +87,7 @@ bodySize = codeSectionHeader.size(); for (InputFunction *func : functions) { + func->outputSec = this; func->outputOffset = bodySize; func->calculateSize(); bodySize += func->getSize(); @@ -165,9 +166,11 @@ log("Data segment: size=" + Twine(segment->size) + ", startVA=" + Twine::utohexstr(segment->startVA) + ", name=" + segment->name); - for (InputSegment *inputSeg : segment->inputSegments) + for (InputSegment *inputSeg : segment->inputSegments) { + inputSeg->outputSec = this; inputSeg->outputOffset = segment->sectionOffset + segment->header.size() + inputSeg->outputSegmentOffset; + } } createHeader(bodySize); @@ -226,8 +229,8 @@ os.flush(); for (InputSection *section : inputSections) { - section->outputOffset = payloadSize; section->outputSec = this; + section->outputOffset = payloadSize; payloadSize += section->getSize(); } diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -284,9 +284,9 @@ uint32_t getSize() const { return size; } InputSegment *segment = nullptr; + uint32_t offset = 0; protected: - uint32_t offset = 0; uint32_t size = 0; }; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -11,6 +11,7 @@ #include "InputChunks.h" #include "InputEvent.h" #include "InputGlobal.h" +#include "MapFile.h" #include "OutputSections.h" #include "OutputSegment.h" #include "Relocations.h" @@ -1070,6 +1071,9 @@ log("-- finalizeSections"); finalizeSections(); + log("-- writeMapFile"); + writeMapFile(outputSections); + log("-- openFile"); openFile(); if (errorCount())