[lld] cc2da55 - [lld][WebAssembly] Add initial support for -Map/--print-map

Sam Clegg via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 16:12:57 PDT 2020


Author: Sam Clegg
Date: 2020-09-12T16:10:51-07:00
New Revision: cc2da5554b5ee5d5939222af263699a9d0bf2049

URL: https://github.com/llvm/llvm-project/commit/cc2da5554b5ee5d5939222af263699a9d0bf2049
DIFF: https://github.com/llvm/llvm-project/commit/cc2da5554b5ee5d5939222af263699a9d0bf2049.diff

LOG: [lld][WebAssembly] Add initial support for -Map/--print-map

Differential Revision: https://reviews.llvm.org/D77187

Added: 
    lld/test/wasm/map-file.s
    lld/wasm/MapFile.cpp
    lld/wasm/MapFile.h

Modified: 
    lld/test/ELF/map-file.s
    lld/test/wasm/early-exit-for-bad-paths.s
    lld/wasm/CMakeLists.txt
    lld/wasm/Config.h
    lld/wasm/Driver.cpp
    lld/wasm/InputChunks.h
    lld/wasm/Options.td
    lld/wasm/OutputSections.cpp
    lld/wasm/OutputSections.h
    lld/wasm/Symbols.h
    lld/wasm/Writer.cpp

Removed: 
    


################################################################################
diff  --git a/lld/test/ELF/map-file.s b/lld/test/ELF/map-file.s
index 1cd3b9087cbe..55b6b9e67281 100644
--- 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
index 2866bfa62f86..21cec318e449 100644
--- 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
index 000000000000..c2ec089ccb13
--- /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
index cd46f0a826ac..37902ededa0c 100644
--- a/lld/wasm/CMakeLists.txt
+++ b/lld/wasm/CMakeLists.txt
@@ -7,6 +7,7 @@ add_lld_library(lldWasm
   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
index e8d018f09bf6..cd6d57333a21 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -58,6 +58,7 @@ struct Configuration {
   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
index 7307aaa3f7be..09318421574c 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -344,6 +344,7 @@ static void readConfigs(opt::InputArgList &args) {
   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);
@@ -410,6 +411,9 @@ static void readConfigs(opt::InputArgList &args) {
     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
@@ -795,7 +799,8 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
   // 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
index cadff6883fa4..be91b19ed452 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -57,6 +57,8 @@ class InputChunk {
   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 @@ class InputSection : public InputChunk {
   StringRef getDebugName() const override { return StringRef(); }
   uint32_t getComdat() const override { return UINT32_MAX; }
 
-  OutputSection *outputSec = nullptr;
-
 protected:
   ArrayRef<uint8_t> data() const override { return section.Content; }
 

diff  --git a/lld/wasm/MapFile.cpp b/lld/wasm/MapFile.cpp
new file mode 100644
index 000000000000..a08d2a97d74a
--- /dev/null
+++ b/lld/wasm/MapFile.cpp
@@ -0,0 +1,148 @@
+//===- 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 "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/Support/Parallel.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace llvm::object;
+using namespace lld;
+using namespace lld::wasm;
+
+using SymbolMapTy = DenseMap<const InputChunk *, SmallVector<Symbol *, 4>>;
+
+// 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<Symbol *> getSymbols() {
+  std::vector<Symbol *> v;
+  for (InputFile *file : symtab->objectFiles)
+    for (Symbol *b : file->getSymbols())
+      if (auto *dr = dyn_cast<Symbol>(b))
+        if ((!isa<SectionSymbol>(dr)) && dr->isLive() &&
+            (dr->getFile() == file))
+          v.push_back(dr);
+  return v;
+}
+
+// Returns a map from sections to their symbols.
+static SymbolMapTy getSectionSyms(ArrayRef<Symbol *> 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<Symbol *, std::string>
+getSymbolStrings(ArrayRef<Symbol *> syms) {
+  std::vector<std::string> 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<DefinedData>(syms[i])) {
+      vma = DD->getVirtualAddress();
+      size = DD->getSize();
+      fileOffset += DD->offset;
+    }
+    if (auto *DF = dyn_cast<DefinedFunction>(syms[i])) {
+      size = DF->function->getSize();
+    }
+    writeHeader(os, vma, fileOffset, size);
+    os.indent(16) << toString(*syms[i]);
+  });
+
+  DenseMap<Symbol *, std::string> ret;
+  for (size_t i = 0, e = syms.size(); i < e; ++i)
+    ret[syms[i]] = std::move(str[i]);
+  return ret;
+}
+
+void lld::wasm::writeMapFile(ArrayRef<OutputSection *> 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<Symbol *> syms = getSymbols();
+  SymbolMapTy sectionSyms = getSectionSyms(syms);
+  DenseMap<Symbol *, std::string> 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<CodeSection>(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<DataSection>(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';
+        }
+      }
+    }
+  }
+}

diff  --git a/lld/wasm/MapFile.h b/lld/wasm/MapFile.h
new file mode 100644
index 000000000000..ef2cc783a6c2
--- /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<OutputSection *> outputSections);
+} // namespace wasm
+} // namespace lld
+
+#endif

diff  --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 16c784f74828..27d54c5cdc64 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -66,6 +66,8 @@ def m: JoinedOrSeparate<["-"], "m">, HelpText<"Set target emulation">;
 
 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">;
 
@@ -84,6 +86,9 @@ defm print_gc_sections: B<"print-gc-sections",
     "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">;
@@ -181,6 +186,7 @@ def: JoinedOrSeparate<["-"], "e">, Alias<entry>;
 def: J<"entry=">, Alias<entry>;
 def: Flag<["-"], "E">, Alias<export_dynamic>, HelpText<"Alias for --export-dynamic">;
 def: Flag<["-"], "i">, Alias<initial_memory>;
+def: Flag<["-"], "M">, Alias<print_map>, HelpText<"Alias for --print-map">;
 def: Flag<["-"], "r">, Alias<relocatable>;
 def: Flag<["-"], "s">, Alias<strip_all>, HelpText<"Alias for --strip-all">;
 def: Flag<["-"], "S">, Alias<strip_debug>, HelpText<"Alias for --strip-debug">;

diff  --git a/lld/wasm/OutputSections.cpp b/lld/wasm/OutputSections.cpp
index a936562992dd..dbdabddb9320 100644
--- a/lld/wasm/OutputSections.cpp
+++ b/lld/wasm/OutputSections.cpp
@@ -87,6 +87,7 @@ void CodeSection::finalizeContents() {
   bodySize = codeSectionHeader.size();
 
   for (InputFunction *func : functions) {
+    func->outputSec = this;
     func->outputOffset = bodySize;
     func->calculateSize();
     bodySize += func->getSize();
@@ -166,9 +167,11 @@ void DataSection::finalizeContents() {
     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);
@@ -227,8 +230,8 @@ void CustomSection::finalizeContents() {
   os.flush();
 
   for (InputSection *section : inputSections) {
-    section->outputOffset = payloadSize;
     section->outputSec = this;
+    section->outputOffset = payloadSize;
     payloadSize += section->getSize();
   }
 

diff  --git a/lld/wasm/OutputSections.h b/lld/wasm/OutputSections.h
index 1fcb5723df98..444116dac7d8 100644
--- a/lld/wasm/OutputSections.h
+++ b/lld/wasm/OutputSections.h
@@ -40,6 +40,7 @@ class OutputSection {
   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 @@ class CodeSection : public OutputSection {
   explicit CodeSection(ArrayRef<InputFunction *> 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 @@ class CodeSection : public OutputSection {
   bool isNeeded() const override { return functions.size() > 0; }
   void finalizeContents() override;
 
-protected:
   ArrayRef<InputFunction *> functions;
+
+protected:
   std::string codeSectionHeader;
   size_t bodySize = 0;
 };
@@ -78,6 +84,10 @@ class DataSection : public OutputSection {
   explicit DataSection(ArrayRef<OutputSegment *> 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 @@ class DataSection : public OutputSection {
   bool isNeeded() const override;
   void finalizeContents() override;
 
-protected:
   ArrayRef<OutputSegment *> segments;
+
+protected:
   std::string dataSectionHeader;
   size_t bodySize = 0;
 };
@@ -103,6 +114,11 @@ class CustomSection : public OutputSection {
   CustomSection(std::string name, ArrayRef<InputSection *> 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/Symbols.h b/lld/wasm/Symbols.h
index 73f555217f26..eed481a0b44d 100644
--- a/lld/wasm/Symbols.h
+++ b/lld/wasm/Symbols.h
@@ -284,9 +284,9 @@ class DefinedData : public DataSymbol {
   uint64_t getSize() const { return size; }
 
   InputSegment *segment = nullptr;
+  uint32_t offset = 0;
 
 protected:
-  uint64_t offset = 0;
   uint64_t size = 0;
 };
 

diff  --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index fb4b79c5f634..82b1aec8d1e9 100644
--- 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"
@@ -1137,6 +1138,9 @@ void Writer::run() {
   log("-- finalizeSections");
   finalizeSections();
 
+  log("-- writeMapFile");
+  writeMapFile(outputSections);
+
   log("-- openFile");
   openFile();
   if (errorCount())


        


More information about the llvm-commits mailing list