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,46 @@ +// RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t1.o +// RUN: wasm-ld %t1.o -o %t -M | FileCheck -strict-whitespace %s +// RUN: wasm-ld %t1.o -o %t -print-map | FileCheck -strict-whitespace %s +// RUN: wasm-ld %t1.o -o %t -Map=%t.map +// RUN: FileCheck -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: 0 8 6 TYPE +// CHECK: 0 e 5 FUNCTION +// CHECK: 0 13 7 TABLE +// CHECK: 0 1a 5 MEMORY +// CHECK: 0 1f a GLOBAL +// CHECK: 0 29 15 EXPORT +// CHECK: 0 3e 15 CODE +// CHECK: 0 0 9 /usr/local/google/home/sbc/dev/wasm/llvm-build/tools/lld/test/wasm/Output/map-file.s.tmp1.o:(bar) +// CHECK: 0 0 9 bar +// CHECK: 0 0 9 /usr/local/google/home/sbc/dev/wasm/llvm-build/tools/lld/test/wasm/Output/map-file.s.tmp1.o:(_start) +// CHECK: 1 0 9 _start +// CHECK: 0 53 d DATA +// CHECK: 400 0 4 /usr/local/google/home/sbc/dev/wasm/llvm-build/tools/lld/test/wasm/Output/map-file.s.tmp1.o:(.data.somedata) +// CHECK: 400 0 4 somedata +// CHECK: 0 60 12 CUSTOM(.debug_info) +// CHECK: 0 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 / 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 @@ -340,6 +340,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); @@ -395,6 +396,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 @@ -726,7 +730,6 @@ cl::ParseCommandLineOptions(v.size(), v.data()); errorHandler().errorLimit = args::getInteger(args, OPT_error_limit, 20); - readConfigs(args); setConfigs(); checkOptions(args); 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,155 @@ +//===- 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 +// 00201000 00000015 10 .text +// 00201000 0000000e 10 test.o:(.text) +// 0020100e 00000000 5 local +// 0020100f 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>; + +static constexpr char indent8[] = " "; // 8 spaces +static constexpr char indent16[] = " "; // 16 spaces + +// Print out the first three columns of a line. +static void writeHeader(raw_ostream &os, uint64_t vma, uint64_t lma, + uint64_t size) { + 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]); + uint64_t vma = 0; + uint64_t size = 0; + if (auto *DD = dyn_cast(syms[i])) { + vma = DD->getVirtualAddress(); + size = DD->getSize(); + } + if (auto *DF = dyn_cast(syms[i])) { + vma = DF->getFunctionIndex(); + size = DF->function->getSize(); + } + writeHeader(os, vma, 0, size); + os << indent16 << 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. + int w = 8; + os << right_justify("Addr", w) << ' ' << right_justify("Off", w) + << " Size Out In Symbol\n"; + + for (OutputSection *osec : outputSections) { + writeHeader(os, 0, osec->getOffset(), osec->getSize()); + os << toString(*osec) << '\n'; + if (auto *code = dyn_cast(osec)) { + for (auto *chunk : code->functions) { + writeHeader(os, 0, 0, chunk->getSize()); + os << indent8 << 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, oseg->sectionOffset, oseg->size); + os << oseg->name << '\n'; + for (auto *chunk : oseg->inputSegments) { + writeHeader(os, oseg->startVA + chunk->outputSegmentOffset, 0, + chunk->getSize()); + os << indent8 << toString(chunk) << '\n'; + for (Symbol *sym : sectionSyms[chunk]) + os << symStr[sym] << '\n'; + } + } + } + /* + for (InputSection *isec : osec->inputSections) { + for (Symbol *sym : sectionSyms[isec]) + 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/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" @@ -1058,6 +1059,9 @@ log("-- finalizeSections"); finalizeSections(); + log("-- writeMapFile"); + writeMapFile(outputSections); + log("-- openFile"); openFile(); if (errorCount())