Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -223,4 +223,4 @@ add_subdirectory(COFF) add_subdirectory(ELF) add_subdirectory(MinGW) - +add_subdirectory(wasm) Index: CODE_OWNERS.TXT =================================================================== --- CODE_OWNERS.TXT +++ CODE_OWNERS.TXT @@ -17,3 +17,6 @@ E: lhames@gmail.com, kledzik@apple.com D: Mach-O backend +N: Sam Clegg +E: sbc@chromium.org +D: WebAssembly backend (wasm/*) Index: docs/NewLLD.rst =================================================================== --- docs/NewLLD.rst +++ docs/NewLLD.rst @@ -1,5 +1,5 @@ -The ELF and COFF Linkers -======================== +The ELF, COFF and Wasm Linkers +============================== The ELF Linker as a Library --------------------------- @@ -33,11 +33,12 @@ We implemented the linkers as native linkers for each file format. - The two linkers share the same design but do not share code. + The linkers share the same design but share very little code. Sharing code makes sense if the benefit is worth its cost. - In our case, ELF and COFF are different enough that we thought the layer to - abstract the differences wouldn't worth its complexity and run-time cost. - Elimination of the abstract layer has greatly simplified the implementation. + In our case, the object formats are different enough that we thought the layer + to abstract the differences wouldn't be worth its complexity and run-time + cost. Elimination of the abstract layer has greatly simplified the + implementation. * Speed by design Index: docs/WebAssembly.rst =================================================================== --- /dev/null +++ docs/WebAssembly.rst @@ -0,0 +1,36 @@ +WebAssembly lld port +==================== + +Note: The WebAssembly port is still a work in progress and is be lacking +certain features. + +The WebAssembly version of lld takes WebAssembly binaries as inputs and produces +a WebAssembly binary as its output. For the most part this port tried to mimik +the behaviour of traditional ELF linkers and specifically the ELF lld port. +Where possible that command line flags and the semantics should be the same. + + +Object file format +------------------ + +The format the input object files that lld expects is specified as part of the +the WebAssembly tool conventions +https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md. + +This is object format that the llvm will produce when run with the +`wasm32-unknown-unknown-wasm` target. To build llvm with WebAssembly support +currently requires enabling the experimental backed using +``-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly``. + + +Missing features +---------------- + +There are several key features that are not yet implement in the WebAssembly +ports: + +- COMDAT support. This means that support for C++ is still very limited. +- Function stripping. Essentially there is no GC of sections so functions and + data from a given object will linked be in as a unit. +- Section start/end symbols. The synthetic symbols that mark the start and + of data regions are not yet created in the output file. Index: docs/index.rst =================================================================== --- docs/index.rst +++ docs/index.rst @@ -5,13 +5,14 @@ for system linkers and runs much faster than them. It also provides features that are useful for toolchain developers. -The linker supports ELF (Unix), PE/COFF (Windows) and Mach-O (macOS) -in descending order of completeness. Internally, LLD consists of three -different linkers. The ELF port is the one that will be described in -this document. The PE/COFF port is almost complete except the lack of -the Windows debug info (PDB) support. The Mach-O port is built based -on a different architecture than the ELF or COFF ports. For the -details about Mach-O, please read :doc:`AtomLLD`. +The linker supports ELF (Unix), PE/COFF (Windows), Mach-O (macOS) and +WebAssembly in descending order of completeness. Internally, LLD consists of +several different linkers. The ELF port is the one that will be described in +this document. The PE/COFF port is almost complete except the lack of the +Windows debug info (PDB) support. The WebAssembly port is still a work in +progress (See :doc:`WebAssembly`). The Mach-O port is built based on a +different architecture than the others. For the details about Mach-O, please +read :doc:`AtomLLD`. Features -------- @@ -172,5 +173,6 @@ NewLLD AtomLLD + WebAssembly windows_support ReleaseNotes Index: include/lld/Common/Driver.h =================================================================== --- include/lld/Common/Driver.h +++ include/lld/Common/Driver.h @@ -33,6 +33,11 @@ bool link(llvm::ArrayRef Args, llvm::raw_ostream &Diag = llvm::errs()); } + +namespace wasm { +bool link(llvm::ArrayRef Args, bool CanExitEarly, + llvm::raw_ostream &Diag = llvm::errs()); +} } #endif Index: test/lit.cfg.py =================================================================== --- test/lit.cfg.py +++ test/lit.cfg.py @@ -39,7 +39,7 @@ llvm_config.use_lld() tool_patterns = [ - 'llvm-as', 'llvm-mc', 'llvm-nm', + 'llc', 'llvm-as', 'llvm-mc', 'llvm-nm', 'llvm-objdump', 'llvm-pdbutil', 'llvm-readobj', 'obj2yaml', 'yaml2obj'] llvm_config.add_tool_substitutions(tool_patterns) @@ -67,6 +67,7 @@ 'Mips': 'mips', 'PowerPC': 'ppc', 'Sparc': 'sparc', + 'WebAssembly': 'wasm', 'X86': 'x86'}) ]) Index: test/wasm/Inputs/archive1.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/archive1.ll @@ -0,0 +1,7 @@ +declare i32 @bar() local_unnamed_addr #1 + +define i32 @foo() local_unnamed_addr #0 { +entry: + %call = tail call i32 @bar() #2 + ret i32 %call +} Index: test/wasm/Inputs/archive2.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/archive2.ll @@ -0,0 +1,7 @@ +declare i32 @foo() local_unnamed_addr #1 + +define i32 @bar() local_unnamed_addr #0 { +entry: + %call = tail call i32 @foo() #2 + ret i32 %call +} Index: test/wasm/Inputs/call-indirect.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/call-indirect.ll @@ -0,0 +1,18 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@indirect_bar = hidden local_unnamed_addr global i32 ()* @bar, align 4 + +; Function Attrs: norecurse nounwind readnone +define hidden i32 @bar() #0 { +entry: + ret i32 1 +} + +; Function Attrs: nounwind +define hidden void @call_bar_indirect() local_unnamed_addr #1 { +entry: + %0 = load i32 ()*, i32 ()** @indirect_bar, align 4 + %call = tail call i32 %0() #2 + ret void +} Index: test/wasm/Inputs/hello.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/hello.ll @@ -0,0 +1,18 @@ +; Wasm module generated from the following C code: +; void puts(const char*); +; void hello() { puts("hello\n"); } + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@hello_str = unnamed_addr constant [7 x i8] c"hello\0A\00", align 1 + +; Function Attrs: nounwind +define hidden void @hello() local_unnamed_addr #0 { +entry: + tail call void @puts(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @hello_str, i32 0, i32 0)) + ret void +} + +; Function Attrs: nounwind +declare void @puts(i8* nocapture readonly) local_unnamed_addr #1 Index: test/wasm/Inputs/ret32.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/ret32.ll @@ -0,0 +1,9 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Function Attrs: norecurse nounwind readnone +define hidden i32 @ret32(float %arg) #0 { +entry: + ret i32 0 + ; ptrtoint (i32 (float)* @ret32 to i32) +} Index: test/wasm/Inputs/ret64.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/ret64.ll @@ -0,0 +1,7 @@ +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define hidden i64 @ret64(double %arg) local_unnamed_addr #0 { +entry: + ret i64 1 +} Index: test/wasm/Inputs/weak-alias.ll =================================================================== --- /dev/null +++ test/wasm/Inputs/weak-alias.ll @@ -0,0 +1,12 @@ +define i32 @foo() #0 { +entry: + ret i32 0 +} + +@bar = weak alias i32 (), i32 ()* @foo + +define hidden i32 @call_bar() #0 { +entry: + %call = call i32 @bar() + ret i32 %call +} Index: test/wasm/archive.ll =================================================================== --- /dev/null +++ test/wasm/archive.ll @@ -0,0 +1,24 @@ +; Verify that multually dependant object files in an archive is handled +; correctly. +; +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %s -o %t.o +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %S/Inputs/archive1.ll -o %t2.o +; RUN: llc -filetype=obj -mtriple=wasm32-unknown-uknown-wasm %S/Inputs/archive2.ll -o %t3.o +; RUN: llvm-ar rcs %t.a %t2.o %t3.o +; RUN: lld -flavor wasm %t.a %t.o -o %t.wasm +; RUN: llvm-nm -a %t.wasm | FileCheck %s + +; Specifying the same archive twice is allowed. +; RUN: lld -flavor wasm %t.a %t.a %t.o -o %t.wasm + +declare i32 @foo() local_unnamed_addr #1 + +define i32 @_start() local_unnamed_addr #0 { +entry: + %call = tail call i32 @foo() #2 + ret i32 %call +} + +; CHECK: T _start +; CHECK: T bar +; CHECK: T foo Index: test/wasm/call-indirect.ll =================================================================== --- /dev/null +++ test/wasm/call-indirect.ll @@ -0,0 +1,110 @@ +; RUN: llc -filetype=obj %p/Inputs/call-indirect.ll -o %t2.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t2.o %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +; bitcode generated from the following C code: +; int foo(void) { return 1; } +; int (*indirect_func)(void) = &foo; +; void _start(void) { indirect_func(); } + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@indirect_func = hidden local_unnamed_addr global i32 ()* @foo, align 4 + +; Function Attrs: norecurse nounwind readnone +define hidden i32 @foo() #0 { +entry: + ret i32 1 +} + +; Function Attrs: nounwind +define hidden void @_start() local_unnamed_addr #1 { +entry: + %0 = load i32 ()*, i32 ()** @indirect_func, align 4 + %call = tail call i32 %0() #2 + ret void +} + +; CHECK: !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 1, 0, 1 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000003 +; CHECK-NEXT: Maximum: 0x00000003 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66576 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: call_bar_indirect +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: foo +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 3 +; CHECK: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 0, 2 ] +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK: - Locals: +; CHECK: - Locals: +; CHECK: - Locals: +; CHECK: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 7 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: '0100000002000000' +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 8 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: bar +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: call_bar_indirect +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: foo +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: _start Index: test/wasm/conflict.test =================================================================== --- /dev/null +++ test/wasm/conflict.test @@ -0,0 +1,6 @@ +# RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +# RUN: not lld -flavor wasm -o %t.wasm %t.ret32.o %t.ret32.o 2>&1 | FileCheck %s + +# CHECK: duplicate symbol: ret32 +# CHECK-NEXT: >>> defined in {{.*}}conflict.test.tmp.ret32.o +# CHECK-NEXT: >>> defined in {{.*}}conflict.test.tmp.ret32.o Index: test/wasm/data-layout.ll =================================================================== --- /dev/null +++ test/wasm/data-layout.ll @@ -0,0 +1,64 @@ +; RUN: llc -filetype=obj %p/Inputs/hello.ll -o %t.hello.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm --emit-relocs --allow-undefined -o %t.wasm %t.o %t.hello.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@foo = hidden global i32 1, align 4 +@aligned_bar = hidden global i32 3, align 16 + +@hello_str = external global i8* +@external_ref = global i8** @hello_str, align 8 + +; CHECK: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66608 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1040 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1048 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1052 + +; CHECK: - Type: DATA +; CHECK-NEXT: Relocations: +; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_I32 +; CHECK-NEXT: Index: 4 +; CHECK-NEXT: Offset: 0x0000001F +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 7 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: 0100000000000000000000000000000003000000000000001C040000 +; CHECK-NEXT: - SectionOffset: 41 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1052 +; CHECK-NEXT: Content: 68656C6C6F0A00 + +; CHECK: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 35 Index: test/wasm/entry.ll =================================================================== --- /dev/null +++ test/wasm/entry.ll @@ -0,0 +1,22 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -e entry -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s +; RUN: lld -flavor wasm --entry=entry -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define hidden void @entry() local_unnamed_addr #0 { +entry: + ret void +} + +; CHECK: - Type: EXPORT +; CHECK: Exports: +; CHECK: - Name: memory +; CHECK: Kind: MEMORY +; CHECK: Index: 0 +; CHECK: - Name: entry +; CHECK: Kind: FUNCTION +; CHECK: Index: 0 Index: test/wasm/function-imports-first.ll =================================================================== --- /dev/null +++ test/wasm/function-imports-first.ll @@ -0,0 +1,45 @@ +; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o %t.ret32.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Function Attrs: nounwind +define hidden void @_start() local_unnamed_addr #0 { +entry: + %call = tail call i32 @ret32(float 0.000000e+00) #2 + ret void +} + +declare i32 @ret32(float) local_unnamed_addr #1 + +; CHECK: - Type: TYPE +; CHECK: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - F32 +; CHECK: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 1 ] +; CHECK: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 43000000001081808080001A0B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41000B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 0 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: _start +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: ret32 +; CHECK-NEXT: ... Index: test/wasm/function-imports.ll =================================================================== --- /dev/null +++ test/wasm/function-imports.ll @@ -0,0 +1,40 @@ +; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.ret32.o %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Function Attrs: nounwind +define hidden void @_start() local_unnamed_addr #0 { +entry: + %call = tail call i32 @ret32(float 0.000000e+00) #2 + ret void +} + +declare i32 @ret32(float) local_unnamed_addr #1 + +; CHECK: Sections: +; CHECK: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - F32 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 1 ] +; CHECK: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK: - Locals: +; CHECK: - Locals: +; CHECK: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: ret32 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: _start +; CHECK-NEXT: ... Index: test/wasm/function-index.test =================================================================== --- /dev/null +++ test/wasm/function-index.test @@ -0,0 +1,18 @@ +# RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +# RUN: llc -filetype=obj %p/Inputs/ret64.ll -o %t.ret64.o +# RUN: lld -flavor wasm -r -o %t.wasm %t.ret32.o %t.ret64.o +# RUN: obj2yaml %t.wasm | FileCheck %s + +CHECK: Sections: +CHECK: - Type: TYPE +CHECK: Signatures: +CHECK: - Index: 0 +CHECK: ReturnType: I32 +CHECK: ParamTypes: +CHECK: - F32 +CHECK: - Index: 1 +CHECK: ReturnType: I64 +CHECK: ParamTypes: +CHECK: - F64 +CHECK: - Type: FUNCTION +CHECK: FunctionTypes: [ 0, 1 ] Index: test/wasm/import-memory.test =================================================================== --- /dev/null +++ test/wasm/import-memory.test @@ -0,0 +1,13 @@ +# RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +# RUN: lld -flavor wasm -entry ret32 --import-memory -o %t.wasm %t.ret32.o +# RUN: obj2yaml %t.wasm | FileCheck %s + +# Verify the --import-memory flag creates a memory import + +# CHECK: - Type: IMPORT +# CHECK-NEXT: Imports: +# CHECK-NEXT: - Module: env +# CHECK-NEXT: Field: memory +# CHECK-NEXT: Kind: MEMORY +# CHECK-NEXT: Memory: +# CHECK-NEXT: Initial: 0x00000002 Index: test/wasm/invalid-stack-size.test =================================================================== --- /dev/null +++ test/wasm/invalid-stack-size.test @@ -0,0 +1,9 @@ +; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj %s -o %t.o +; RUN: not lld -flavor wasm -o %t.wasm -z stack-size=1 %t.o 2>&1 | FileCheck %s + +define i32 @_start() local_unnamed_addr #1 { +entry: + ret i32 0 +} + +; CHECK: error: stack size must be 16-byte aligned Index: test/wasm/lit.local.cfg =================================================================== --- /dev/null +++ test/wasm/lit.local.cfg @@ -0,0 +1,4 @@ +if 'wasm' not in config.available_features: + config.unsupported = True + +config.suffixes = ['.test', '.yaml', '.ll'] Index: test/wasm/local-symbols.ll =================================================================== --- /dev/null +++ test/wasm/local-symbols.ll @@ -0,0 +1,81 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@foo = default global i32 1, align 4 +@bar = internal default global i32 3, align 4 + +define internal i32 @baz() local_unnamed_addr { +entry: + ret i32 2 +} + +define i32 @_start() local_unnamed_addr { +entry: + ret i32 1 +} + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 0 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000001 +; CHECK-NEXT: Maximum: 0x00000001 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66576 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41020B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41010B +; CHECK-NEXT: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 7 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: '0100000003000000' +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 8 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: baz +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: _start +; CHECK-NEXT: ... Index: test/wasm/relocatable.ll =================================================================== --- /dev/null +++ test/wasm/relocatable.ll @@ -0,0 +1,185 @@ +; RUN: llc -filetype=obj %p/Inputs/hello.ll -o %t.hello.o +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -r -o %t.wasm %t.hello.o %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Function Attrs: nounwind +define hidden i32 @my_func() local_unnamed_addr { +entry: + %call = tail call i32 @foo_import() + ret i32 1 +} + +declare i32 @foo_import() local_unnamed_addr +@data_import = external global i64 + +@func_addr1 = hidden global i32()* @my_func, align 4 +@func_addr2 = hidden global i32()* @foo_import, align 4 +@data_addr1 = hidden global i64* @data_import, align 8 + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - I32 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: IMPORT +; CHECK-NEXT: Imports: +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: puts +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: SigIndex: 1 +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: foo_import +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: SigIndex: 2 +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: data_import +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: GlobalType: I32 +; CHECK-NEXT: GlobalMutable: false +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 2 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000002 +; CHECK-NEXT: Maximum: 0x00000002 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000001 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 0 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 8 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 12 +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: false +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 16 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: hello +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: my_func +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 3 +; CHECK-NEXT: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 0 +; CHECK-NEXT: Functions: [ 3, 1 ] +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Relocations: +; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_SLEB +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: Offset: 0x00000004 +; CHECK-NEXT: - Type: R_WEBASSEMBLY_FUNCTION_INDEX_LEB +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: Offset: 0x0000000A +; CHECK-NEXT: - Type: R_WEBASSEMBLY_FUNCTION_INDEX_LEB +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: Offset: 0x00000013 +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 4180808080001080808080000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1081808080001A41010B +; CHECK-NEXT: - Type: DATA +; CHECK-NEXT: Relocations: +; CHECK-NEXT: - Type: R_WEBASSEMBLY_TABLE_INDEX_I32 +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: Offset: 0x00000012 +; CHECK-NEXT: - Type: R_WEBASSEMBLY_TABLE_INDEX_I32 +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: Offset: 0x0000001B +; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_I32 +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: Offset: 0x00000024 +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 6 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 0 +; CHECK-NEXT: Content: 68656C6C6F0A00 +; CHECK-NEXT: - SectionOffset: 18 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 8 +; CHECK-NEXT: Content: '00000000' +; CHECK-NEXT: - SectionOffset: 27 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 12 +; CHECK-NEXT: Content: '01000000' +; CHECK-NEXT: - SectionOffset: 36 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 16 +; CHECK-NEXT: Content: FFFFFFFF +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 20 +; CHECK-NEXT: SegmentInfo: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: .rodata.hello_str +; CHECK-NEXT: Alignment: 1 +; CHECK-NEXT: Flags: 0 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: .data.func_addr1 +; CHECK-NEXT: Alignment: 4 +; CHECK-NEXT: Flags: 0 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: .data.func_addr2 +; CHECK-NEXT: Alignment: 4 +; CHECK-NEXT: Flags: 0 +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: .data.data_addr1 +; CHECK-NEXT: Alignment: 8 +; CHECK-NEXT: Flags: 0 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: puts +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: foo_import +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: hello +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: my_func +; CHECK-NEXT: ... Index: test/wasm/strip-debug.test =================================================================== --- /dev/null +++ test/wasm/strip-debug.test @@ -0,0 +1,6 @@ +RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +RUN: lld -flavor wasm --strip-debug --entry=ret32 -o %t.wasm %t.ret32.o +RUN: obj2yaml %t.wasm | FileCheck %s + +# Check that there is no name section +CHECK-NOT: Name: name Index: test/wasm/symtol-type-mismatch.ll =================================================================== --- /dev/null +++ test/wasm/symtol-type-mismatch.ll @@ -0,0 +1,12 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +; RUN: not lld -flavor wasm -o %t.wasm %t.o %t.ret32.o 2>&1 | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@ret32 = extern_weak global i32, align 4 + +; CHECK: error: symbol type mismatch: ret32 +; CHECK: >>> defined as Global in {{.*}}symtol-type-mismatch.ll.tmp.o +; CHECK: >>> defined as Function in {{.*}}.ret32.o Index: test/wasm/undefined-entry.test =================================================================== --- /dev/null +++ test/wasm/undefined-entry.test @@ -0,0 +1,4 @@ +RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o +RUN: not lld -flavor wasm -o %t.wasm %t.ret32.o 2>&1 | FileCheck %s + +CHECK: error: undefined symbol: _start Index: test/wasm/undefined.ll =================================================================== --- /dev/null +++ test/wasm/undefined.ll @@ -0,0 +1,23 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm --allow-undefined -o %t.wasm %t.o + +; Fails due to undefined 'foo' +; RUN: not lld -flavor wasm -o %t.wasm %t.o 2>&1 | FileCheck %s +; CHECK: error: {{.*}}.o: undefined symbol: foo + +; But succeeds if we pass a file containing 'foo' as --allow-undefined-file. +; RUN: echo 'foo' > %t.txt +; RUN: lld -flavor wasm --allow-undefined-file=%t.txt -o %t.wasm %t.o + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +; Takes the address of the external foo() resulting in undefined external +@bar = hidden local_unnamed_addr global i8* bitcast (i32 ()* @foo to i8*), align 4 + +declare i32 @foo() #0 + +define hidden void @_start() local_unnamed_addr #0 { +entry: + ret void +} Index: test/wasm/version.ll =================================================================== --- /dev/null +++ test/wasm/version.ll @@ -0,0 +1,16 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o +; RUN: llvm-readobj -file-headers %t.wasm | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +define hidden void @_start() local_unnamed_addr #0 { +entry: + ret void +} + +; CHECK: Format: WASM +; CHECK: Arch: wasm32 +; CHECK: AddressSize: 32bit +; CHECK: Version: 0x1 Index: test/wasm/weak-alias-overide.ll =================================================================== --- /dev/null +++ test/wasm/weak-alias-overide.ll @@ -0,0 +1,92 @@ +; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s +; RUN: llc -mtriple=wasm32-unknown-unknown-wasm -filetype=obj %S/Inputs/weak-alias.ll -o %t2.o +; RUN: lld -flavor wasm %t.o %t2.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; Test that the strongly defined bar is used correctly despite the existence +; of the weak alias + +define i32 @bar() local_unnamed_addr #1 { + ret i32 1 +} + +; Function Attrs: nounwind uwtable +define void @_start() local_unnamed_addr #1 { +entry: + %call = tail call i32 @bar() #2 + ret void +} + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 1, 0, 0 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000001 +; CHECK-NEXT: Maximum: 0x00000001 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66560 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: foo +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: call_bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 3 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41010B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1080808080001A0B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1080808080000B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 0 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: bar +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: _start +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: foo +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: call_bar +; CHECK-NEXT: ... Index: test/wasm/weak-alias.ll =================================================================== --- /dev/null +++ test/wasm/weak-alias.ll @@ -0,0 +1,82 @@ +; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s +; RUN: llc -mtriple=wasm32-unknown-unknown-wasm -filetype=obj %S/Inputs/weak-alias.ll -o %t2.o +; RUN: lld -flavor wasm %t.o %t2.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; Test that weak aliases (bar is a weak alias of foo) are linked correctly + +declare i32 @bar() local_unnamed_addr #1 + +; Function Attrs: nounwind uwtable +define i32 @_start() local_unnamed_addr #1 { +entry: + %call = tail call i32 @bar() #2 + ret i32 %call +} + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 0, 0 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000001 +; CHECK-NEXT: Maximum: 0x00000001 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66560 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: foo +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: call_bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1081808080000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1081808080000B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 0 +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: _start +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: foo +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: call_bar +; CHECK-NEXT: ... Index: test/wasm/weak-external.ll =================================================================== --- /dev/null +++ test/wasm/weak-external.ll @@ -0,0 +1,86 @@ +; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s +; RUN: lld -flavor wasm -strip-debug %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; Test that undefined weak externals (global_var) and (foo) don't cause +; link failures and resolve to zero. + +@global_var = extern_weak global i32, align 4 + +declare extern_weak i32 @foo() + +define hidden i8* @get_address_of_foo() #0 { +entry: + ret i8* bitcast (i32 ()* @foo to i8*) +} + +define hidden i32* @get_address_of_global_var() #0 { + ret i32* @global_var +} + +define hidden i32 @_start() #0 { +entry: + %0 = load i32, i32* @global_var, align 4 + ret i32 %0 +} + +; CHECK: --- !WASM +; CHECK-NEXT: FileHeader: +; CHECK-NEXT: Version: 0x00000001 +; CHECK-NEXT: Sections: +; CHECK-NEXT: - Type: TYPE +; CHECK-NEXT: Signatures: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: ReturnType: I32 +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 0, 0 ] +; CHECK-NEXT: - Type: TABLE +; CHECK-NEXT: Tables: +; CHECK-NEXT: - ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: 0x00000001 +; CHECK-NEXT: Initial: 0x00000002 +; CHECK-NEXT: Maximum: 0x00000002 +; CHECK-NEXT: - Type: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; CHECK-NEXT: - Type: GLOBAL +; CHECK-NEXT: Globals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Mutable: true +; CHECK-NEXT: InitExpr: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 66560 +; CHECK-NEXT: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: get_address_of_foo +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: get_address_of_global_var +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 0 ] +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 4181808080000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41FFFFFFFF7F0B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41002802FFFFFFFF0F0B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 0 +; CHECK-NEXT: ... Index: tools/lld/CMakeLists.txt =================================================================== --- tools/lld/CMakeLists.txt +++ tools/lld/CMakeLists.txt @@ -11,13 +11,14 @@ lldDriver lldELF lldMinGW + lldWasm ) install(TARGETS lld RUNTIME DESTINATION bin) if(NOT LLD_SYMLINKS_TO_CREATE) - set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld ld64.lld) + set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld ld64.lld wasm-ld) endif() foreach(link ${LLD_SYMLINKS_TO_CREATE}) Index: tools/lld/lld.cpp =================================================================== --- tools/lld/lld.cpp +++ tools/lld/lld.cpp @@ -34,6 +34,7 @@ Gnu, // -flavor gnu WinLink, // -flavor link Darwin, // -flavor darwin + Wasm, // -flavor wasm }; LLVM_ATTRIBUTE_NORETURN static void die(const Twine &S) { @@ -44,6 +45,7 @@ static Flavor getFlavor(StringRef S) { return StringSwitch(S) .CasesLower("ld", "ld.lld", "gnu", Gnu) + .CasesLower("wasm", "ld-wasm", Wasm) .CaseLower("link", WinLink) .CasesLower("ld64", "ld64.lld", "darwin", Darwin) .Default(Invalid); @@ -118,6 +120,8 @@ return !coff::link(Args, true); case Darwin: return !mach_o::link(Args); + case Wasm: + return !wasm::link(Args, true); default: die("lld is a generic driver.\n" "Invoke ld.lld (Unix), ld (macOS) or lld-link (Windows) instead."); Index: wasm/CMakeLists.txt =================================================================== --- /dev/null +++ wasm/CMakeLists.txt @@ -0,0 +1,28 @@ +set(LLVM_TARGET_DEFINITIONS Options.td) +tablegen(LLVM Options.inc -gen-opt-parser-defs) +add_public_tablegen_target(WasmOptionsTableGen) + +add_lld_library(lldWasm + Driver.cpp + InputFiles.cpp + InputSegment.cpp + OutputSections.cpp + Strings.cpp + SymbolTable.cpp + Symbols.cpp + Writer.cpp + WriterUtils.cpp + + LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + BinaryFormat + Core + Demangle + Object + Option + Support + + LINK_LIBS + lldCommon + lldCore + ) Index: wasm/Config.h =================================================================== --- /dev/null +++ wasm/Config.h @@ -0,0 +1,52 @@ +//===- Config.h -------------------------------------------------*- C++ -*-===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_CONFIG_H +#define LLD_WASM_CONFIG_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/Wasm.h" + +#include "Symbols.h" + +using llvm::wasm::WasmGlobal; + +#include + +namespace lld { +namespace wasm { + +struct Configuration { + bool AllowUndefined = false; + bool Demangle = true; + bool EmitRelocs = false; + bool ImportMemory = false; + bool Relocatable = false; + bool StripDebug = false; + bool StripAll = false; + uint32_t ZStackSize = 0; + uint32_t MaxMemory = 0; + uint32_t GlobalBase = 0; + uint32_t InitialMemory = 0; + llvm::StringRef Entry; + llvm::StringRef Sysroot; + llvm::StringRef OutputFile; + + std::vector SearchPaths; + std::set AllowUndefinedSymbols; + std::vector> SyntheticGlobals; +}; + +// The only instance of Configuration struct. +extern Configuration *Config; + +} // namespace wasm +} // namespace lld + +#endif Index: wasm/Driver.cpp =================================================================== --- /dev/null +++ wasm/Driver.cpp @@ -0,0 +1,360 @@ +//===- Driver.cpp ---------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lld/Common/Driver.h" +#include "Config.h" +#include "Memory.h" +#include "SymbolTable.h" +#include "Writer.h" +#include "lld/Common/ErrorHandler.h" +#include "lld/Common/Threads.h" +#include "lld/Common/Version.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +using namespace llvm; +using namespace llvm::sys; +using namespace llvm::wasm; +using llvm::sys::Process; + +using namespace lld; +using namespace lld::wasm; + +namespace { + +// Parses command line options. +class WasmOptTable : public llvm::opt::OptTable { +public: + WasmOptTable(); + llvm::opt::InputArgList parse(ArrayRef Argv); +}; + +// Create enum with OPT_xxx values for each option in Options.td +enum { + OPT_INVALID = 0, +#define OPTION(_1, _2, ID, _4, _5, _6, _7, _8, _9, _10, _11, _12) OPT_##ID, +#include "Options.inc" +#undef OPTION +}; + +class LinkerDriver { +public: + void link(ArrayRef ArgsArr); + +private: + void createFiles(llvm::opt::InputArgList &Args); + void addFile(StringRef Path); + void addLibrary(StringRef Name); + std::vector Files; +}; + +LinkerDriver *Driver; + +} // anonymous namespace + +std::vector lld::wasm::SpecificAllocBase::Instances; +Configuration *lld::wasm::Config; +BumpPtrAllocator lld::wasm::BAlloc; + +bool lld::wasm::link(ArrayRef Args, bool CanExitEarly, + raw_ostream &Error) { + errorHandler().LogName = Args[0]; + errorHandler().ErrorOS = &Error; + errorHandler().ColorDiagnostics = Error.has_colors(); + errorHandler().ErrorLimitExceededMsg = + "too many errors emitted, stopping now (use " + "-error-limit=0 to see all errors)"; + + Config = make(); + Driver = make(); + Symtab = make(); + + Driver->link(Args); + + // Exit immediately if we don't need to return to the caller. + // This saves time because the overhead of calling destructors + // for all globally-allocated objects is not negligible. + if (CanExitEarly) + exitLld(errorCount() ? 1 : 0); + + freeArena(); + return !errorCount(); +} + +// Create OptTable + +// Create prefix string literals used in Options.td +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "Options.inc" +#undef PREFIX + +// Create table mapping all options defined in Options.td +static constexpr opt::OptTable::Info OptInfo[] = { +#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \ + {X1, X2, X10, X11, OPT_##ID, opt::Option::KIND##Class, \ + X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12}, +#include "Options.inc" +#undef OPTION +}; + +static std::vector getArgs(opt::InputArgList &Args, int Id) { + std::vector V; + for (auto *Arg : Args.filtered(Id)) + V.push_back(Arg->getValue()); + return V; +} + +static int getInteger(opt::InputArgList &Args, unsigned Key, int Default) { + int V = Default; + if (auto *Arg = Args.getLastArg(Key)) { + StringRef S = Arg->getValue(); + if (S.getAsInteger(10, V)) + error(Arg->getSpelling() + ": number expected, but got " + S); + } + return V; +} + +static uint64_t getZOptionValue(opt::InputArgList &Args, StringRef Key, + uint64_t Default) { + for (auto *Arg : Args.filtered(OPT_z)) { + StringRef Value = Arg->getValue(); + size_t Pos = Value.find("="); + if (Pos != StringRef::npos && Key == Value.substr(0, Pos)) { + Value = Value.substr(Pos + 1); + uint64_t Res; + if (Value.getAsInteger(0, Res)) + error("invalid " + Key + ": " + Value); + return Res; + } + } + return Default; +} + +static std::vector getLines(MemoryBufferRef MB) { + SmallVector Arr; + MB.getBuffer().split(Arr, '\n'); + + std::vector Ret; + for (StringRef S : Arr) { + S = S.trim(); + if (!S.empty() && S[0] != '#') + Ret.push_back(S); + } + return Ret; +} + +// Set color diagnostics according to -color-diagnostics={auto,always,never} +// or -no-color-diagnostics flags. +static void handleColorDiagnostics(opt::InputArgList &Args) { + auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, + OPT_no_color_diagnostics); + if (!Arg) + return; + + if (Arg->getOption().getID() == OPT_color_diagnostics) + errorHandler().ColorDiagnostics = true; + else if (Arg->getOption().getID() == OPT_no_color_diagnostics) + errorHandler().ColorDiagnostics = false; + else { + StringRef S = Arg->getValue(); + if (S == "always") + errorHandler().ColorDiagnostics = true; + if (S == "never") + errorHandler().ColorDiagnostics = false; + if (S != "auto") + error("unknown option: -color-diagnostics=" + S); + } +} + +// Find a file by concatenating given paths. +static Optional findFile(StringRef Path1, const Twine &Path2) { + SmallString<128> S; + path::append(S, Path1, Path2); + if (fs::exists(S)) + return S.str(); + return None; +} + +// Inject a new wasm global into the output binary with the given value. +// Wasm global are used in relocatable object files to model symbol imports +// and exports. In the final exectuable the only use of wasm globals is the +// for the exlicit stack pointer (__stack_pointer). +static void addSyntheticGlobal(StringRef Name, int32_t Value) { + log("injecting global: " + Name); + Symbol *S = Symtab->addDefinedGlobal(Name); + S->setOutputIndex(Config->SyntheticGlobals.size()); + + WasmGlobal Global; + Global.Mutable = true; + Global.Type = WASM_TYPE_I32; + Global.InitExpr.Opcode = WASM_OPCODE_I32_CONST; + Global.InitExpr.Value.Int32 = Value; + Config->SyntheticGlobals.emplace_back(S, Global); +} + +// Inject a new undefined symbol into the link. This will cause the link to +// fail unless this symbol can be found. +static void addSyntheticUndefinedFunction(StringRef Name) { + log("injecting undefined func: " + Name); + Symtab->addUndefinedFunction(Name); +} + +static void printHelp(const char *Argv0) { + WasmOptTable Table; + Table.PrintHelp(outs(), Argv0, "LLVM Linker", false); +} + +WasmOptTable::WasmOptTable() : OptTable(OptInfo) {} + +opt::InputArgList WasmOptTable::parse(ArrayRef Argv) { + SmallVector Vec(Argv.data(), Argv.data() + Argv.size()); + + unsigned MissingIndex; + unsigned MissingCount; + opt::InputArgList Args = this->ParseArgs(Vec, MissingIndex, MissingCount); + + handleColorDiagnostics(Args); + for (auto *Arg : Args.filtered(OPT_UNKNOWN)) + error("unknown argument: " + Arg->getSpelling()); + return Args; +} + +void LinkerDriver::addFile(StringRef Path) { + Optional Buffer = readFile(Path); + if (!Buffer.hasValue()) + return; + MemoryBufferRef MBRef = *Buffer; + + if (identify_magic(MBRef.getBuffer()) == file_magic::archive) + Files.push_back(make(MBRef)); + else + Files.push_back(make(MBRef)); +} + +// Add a given library by searching it from input search paths. +void LinkerDriver::addLibrary(StringRef Name) { + for (StringRef Dir : Config->SearchPaths) { + if (Optional S = findFile(Dir, "lib" + Name + ".a")) { + addFile(*S); + return; + } + } + + error("unable to find library -l" + Name); +} + +void LinkerDriver::createFiles(opt::InputArgList &Args) { + for (auto *Arg : Args) { + switch (Arg->getOption().getUnaliasedOption().getID()) { + case OPT_l: + addLibrary(Arg->getValue()); + break; + case OPT_INPUT: + addFile(Arg->getValue()); + break; + } + } + + if (Files.empty()) + error("no input files"); +} + +void LinkerDriver::link(ArrayRef ArgsArr) { + WasmOptTable Parser; + opt::InputArgList Args = Parser.parse(ArgsArr.slice(1)); + + // Handle --help + if (Args.hasArg(OPT_help)) { + printHelp(ArgsArr[0]); + return; + } + + // Parse and evaluate -mllvm options. + std::vector V; + V.push_back("lld-link (LLVM option parsing)"); + for (auto *Arg : Args.filtered(OPT_mllvm)) + V.push_back(Arg->getValue()); + cl::ParseCommandLineOptions(V.size(), V.data()); + + errorHandler().ErrorLimit = getInteger(Args, OPT_error_limit, 20); + + if (Args.hasArg(OPT_version) || Args.hasArg(OPT_v)) { + outs() << getLLDVersion() << "\n"; + return; + } + + Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + Config->EmitRelocs = Args.hasArg(OPT_emit_relocs); + Config->Entry = Args.getLastArgValue(OPT_entry); + Config->ImportMemory = Args.hasArg(OPT_import_memory); + Config->OutputFile = Args.getLastArgValue(OPT_o); + Config->Relocatable = Args.hasArg(OPT_relocatable); + Config->SearchPaths = getArgs(Args, OPT_L); + Config->StripAll = Args.hasArg(OPT_strip_all); + Config->StripDebug = Args.hasArg(OPT_strip_debug); + Config->Sysroot = Args.getLastArgValue(OPT_sysroot); + errorHandler().Verbose = Args.hasArg(OPT_verbose); + ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true); + + Config->InitialMemory = getInteger(Args, OPT_initial_memory, 0); + Config->GlobalBase = getInteger(Args, OPT_global_base, 1024); + Config->MaxMemory = getInteger(Args, OPT_max_memory, 0); + Config->ZStackSize = getZOptionValue(Args, "stack-size", WasmPageSize); + + if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file)) + if (Optional Buf = readFile(Arg->getValue())) + for (StringRef Sym : getLines(*Buf)) + Config->AllowUndefinedSymbols.insert(Sym); + + if (Config->OutputFile.empty()) + error("no output file specified"); + + if (!Args.hasArg(OPT_INPUT)) + error("no input files"); + + if (Config->Relocatable && !Config->Entry.empty()) + error("entry point specified for relocatable output file"); + + if (!Config->Relocatable) { + if (Config->Entry.empty()) + Config->Entry = "_start"; + addSyntheticUndefinedFunction(Config->Entry); + + addSyntheticGlobal("__stack_pointer", 0); + } + + createFiles(Args); + if (errorCount()) + return; + + // Add all files to the symbol table. This will add almost all + // symbols that we need to the symbol table. + for (InputFile *F : Files) + Symtab->addFile(F); + + // Make sure we have resolved all symbols. + if (!Config->Relocatable && !Config->AllowUndefined) { + Symtab->reportRemainingUndefines(); + if (errorCount()) + return; + } + + if (!Config->Entry.empty()) { + Symbol *Sym = Symtab->find(Config->Entry); + if (!Sym->isFunction()) + fatal("entry point is not a function: " + Sym->getName()); + } + + // Write the result to the file. + writeResult(); +} Index: wasm/InputFiles.h =================================================================== --- /dev/null +++ wasm/InputFiles.h @@ -0,0 +1,146 @@ +//===- InputFiles.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_INPUT_FILES_H +#define LLD_WASM_INPUT_FILES_H + +#include "lld/Common/LLVM.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Support/MemoryBuffer.h" + +#include + +using llvm::object::WasmObjectFile; +using llvm::object::WasmSection; +using llvm::object::WasmSymbol; +using llvm::object::Archive; +using llvm::wasm::WasmImport; + +namespace lld { +namespace wasm { + +class Symbol; +class InputSegment; + +class InputFile { +public: + enum Kind { + ObjectKind, + ArchiveKind, + }; + + virtual ~InputFile() {} + + // Returns the filename. + StringRef getName() const { return MB.getBufferIdentifier(); } + + // Reads a file (the constructor doesn't do that). + virtual void parse() = 0; + + Kind kind() const { return FileKind; } + + // An archive file name if this file is created from an archive. + StringRef ParentName; + +protected: + InputFile(Kind K, MemoryBufferRef M) : MB(M), FileKind(K) {} + MemoryBufferRef MB; + +private: + const Kind FileKind; +}; + +// .a file (ar archive) +class ArchiveFile : public InputFile { +public: + explicit ArchiveFile(MemoryBufferRef M) : InputFile(ArchiveKind, M) {} + static bool classof(const InputFile *F) { return F->kind() == ArchiveKind; } + + void addMember(const Archive::Symbol *Sym); + + void parse() override; + +private: + std::unique_ptr File; + llvm::DenseSet Seen; +}; + +// .o file (wasm object file) +class ObjFile : public InputFile { +public: + explicit ObjFile(MemoryBufferRef M) : InputFile(ObjectKind, M) {} + static bool classof(const InputFile *F) { return F->kind() == ObjectKind; } + + void parse() override; + + // Returns the underlying wasm file. + const WasmObjectFile *getWasmObj() const { return WasmObj.get(); } + + void dumpInfo() const; + + uint32_t relocateTypeIndex(uint32_t Original) const; + uint32_t relocateFunctionIndex(uint32_t Original) const; + uint32_t relocateGlobalIndex(uint32_t Original) const; + uint32_t relocateTableIndex(uint32_t Original) const; + uint32_t getRelocatedAddress(uint32_t Index) const; + + // Returns true if the given function index is an imported function, + // as opposed to the locally defined function. + bool isImportedFunction(uint32_t Index) const; + + size_t NumFunctionImports() const { return FunctionImports; } + size_t NumGlobalImports() const { return GlobalImports; } + + int32_t FunctionIndexOffset = 0; + int32_t GlobalIndexOffset = 0; + int32_t TableIndexOffset = 0; + const WasmSection *CodeSection = nullptr; + const WasmSection *DataSection = nullptr; + + std::vector TypeMap; + std::vector Segments; + + const std::vector &getSymbols() { return Symbols; } + +private: + Symbol *createDefined(const WasmSymbol &Sym, + const InputSegment *Segment = nullptr); + Symbol *createUndefined(const WasmSymbol &Sym); + void initializeSymbols(); + InputSegment* getSegment(const WasmSymbol &WasmSym); + const Symbol *getFunctionSymbol(uint32_t Index) const; + const Symbol *getGlobalSymbol(uint32_t Index) const; + + // List of all symbols referenced or defined by this file. + std::vector Symbols; + + // List of all function symbols indexed by the function index space + std::vector FunctionSymbols; + + // List of all global symbols indexed by the global index space + std::vector GlobalSymbols; + + uint32_t GlobalImports = 0; + uint32_t FunctionImports = 0; + std::unique_ptr WasmObj; +}; + +// Opens a given file. +llvm::Optional readFile(StringRef Path); + +} // namespace wasm + +std::string toString(wasm::InputFile *File); + +} // namespace lld + +#endif Index: wasm/InputFiles.cpp =================================================================== --- /dev/null +++ wasm/InputFiles.cpp @@ -0,0 +1,273 @@ +//===- InputFiles.cpp -----------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InputFiles.h" + +#include "Config.h" +#include "InputSegment.h" +#include "Memory.h" +#include "Strings.h" +#include "SymbolTable.h" +#include "lld/Common/ErrorHandler.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Support/raw_ostream.h" + +#define DEBUG_TYPE "lld" + +using namespace lld; +using namespace lld::wasm; + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::wasm; + +Optional lld::wasm::readFile(StringRef Path) { + log("Loading: " + Path); + + auto MBOrErr = MemoryBuffer::getFile(Path); + if (auto EC = MBOrErr.getError()) { + error("cannot open " + Path + ": " + EC.message()); + return None; + } + std::unique_ptr &MB = *MBOrErr; + MemoryBufferRef MBRef = MB->getMemBufferRef(); + make>(std::move(MB)); // take MB ownership + + return MBRef; +} + +void ObjFile::dumpInfo() const { + log("reloc info for: " + getName() + "\n" + + " FunctionIndexOffset : " + Twine(FunctionIndexOffset) + "\n" + + " NumFunctionImports : " + Twine(NumFunctionImports()) + "\n" + + " TableIndexOffset : " + Twine(TableIndexOffset) + "\n" + + " GlobalIndexOffset : " + Twine(GlobalIndexOffset) + "\n" + + " NumGlobalImports : " + Twine(NumGlobalImports()) + "\n"); +} + +bool ObjFile::isImportedFunction(uint32_t Index) const { + return Index < NumFunctionImports(); +} + +const Symbol *ObjFile::getFunctionSymbol(uint32_t Index) const { + return FunctionSymbols[Index]; +} + +const Symbol *ObjFile::getGlobalSymbol(uint32_t Index) const { + return GlobalSymbols[Index]; +} + +uint32_t ObjFile::getRelocatedAddress(uint32_t Index) const { + return getGlobalSymbol(Index)->getVirtualAddress(); +} + +uint32_t ObjFile::relocateFunctionIndex(uint32_t Original) const { + DEBUG(dbgs() << "relocateFunctionIndex: " << Original); + const Symbol* Sym = getFunctionSymbol(Original); + uint32_t Index; + if (Sym) + Index = Sym->getOutputIndex(); + else + Index = Original + FunctionIndexOffset; + + DEBUG(dbgs() << " -> " << Index << "\n"); + return Index; +} + +uint32_t ObjFile::relocateTypeIndex(uint32_t Original) const { + return TypeMap[Original]; +} + +uint32_t ObjFile::relocateTableIndex(uint32_t Original) const { + return Original + TableIndexOffset; +} + +uint32_t ObjFile::relocateGlobalIndex(uint32_t Original) const { + DEBUG(dbgs() << "relocateGlobalIndex: " << Original); + uint32_t Index; + const Symbol *Sym = getGlobalSymbol(Original); + if (Sym) + Index = Sym->getOutputIndex(); + else + Index = Original + GlobalIndexOffset; + + DEBUG(dbgs() << " -> " << Index << "\n"); + return Index; +} + +void ObjFile::parse() { + // Parse a memory buffer as a wasm file. + DEBUG(dbgs() << "Parsing object: " << toString(this) << "\n"); + std::unique_ptr Bin = check(createBinary(MB), toString(this)); + + auto *Obj = dyn_cast(Bin.get()); + if (!Obj) + fatal(toString(this) + ": not a wasm file"); + if (!Obj->isRelocatableObject()) + fatal(toString(this) + ": not a relocatable wasm file"); + + Bin.release(); + WasmObj.reset(Obj); + + // Find the code and data sections. Wasm objects can have at most one code + // and one data section. + for (const SectionRef &Sec : WasmObj->sections()) { + const WasmSection &Section = WasmObj->getWasmSection(Sec); + if (Section.Type == WASM_SEC_CODE) + CodeSection = &Section; + else if (Section.Type == WASM_SEC_DATA) + DataSection = &Section; + } + + initializeSymbols(); +} + +// Return the InputSegment in which a given symbol is defined. +InputSegment* ObjFile::getSegment(const WasmSymbol &WasmSym) { + uint32_t Address = WasmObj->getWasmSymbolValue(WasmSym); + for (InputSegment* Segment : Segments) { + if (Address >= Segment->startVA() && Address < Segment->endVA()) { + DEBUG(dbgs() << "Found symbol in segment: " << WasmSym.Name << " -> " + << Segment->getName() << "\n"); + + return Segment; + } + } + error("Symbol not found in any segment: " + WasmSym.Name); + return nullptr; +} + +void ObjFile::initializeSymbols() { + Symbols.reserve(WasmObj->getNumberOfSymbols()); + + for (const WasmImport &Import : WasmObj->imports()) { + switch (Import.Kind) { + case WASM_EXTERNAL_FUNCTION: + ++FunctionImports; + break; + case WASM_EXTERNAL_GLOBAL: + ++GlobalImports; + break; + } + } + + FunctionSymbols.resize(FunctionImports + WasmObj->functions().size()); + GlobalSymbols.resize(GlobalImports + WasmObj->globals().size()); + + for (const WasmSegment &Seg : WasmObj->dataSegments()) + Segments.emplace_back(make(&Seg, this)); + + Symbol* S; + for (const SymbolRef &Sym : WasmObj->symbols()) { + const WasmSymbol &WasmSym = WasmObj->getWasmSymbol(Sym.getRawDataRefImpl()); + switch (WasmSym.Type) { + case WasmSymbol::SymbolType::FUNCTION_IMPORT: + case WasmSymbol::SymbolType::GLOBAL_IMPORT: + S = createUndefined(WasmSym); + break; + case WasmSymbol::SymbolType::GLOBAL_EXPORT: + S = createDefined(WasmSym, getSegment(WasmSym)); + break; + case WasmSymbol::SymbolType::FUNCTION_EXPORT: + S = createDefined(WasmSym); + break; + case WasmSymbol::SymbolType::DEBUG_FUNCTION_NAME: + // These are for debugging only, no need to create linker symbols for them + continue; + } + + Symbols.push_back(S); + if (WasmSym.isFunction()) { + DEBUG(dbgs() << "Function: " << WasmSym.ElementIndex << " -> " << toString(*S) << "\n"); + FunctionSymbols[WasmSym.ElementIndex] = S; + } else { + DEBUG(dbgs() << "Global: " << WasmSym.ElementIndex << " -> " << toString(*S) << "\n"); + GlobalSymbols[WasmSym.ElementIndex] = S; + } + } + + DEBUG(dbgs() << "Functions: " << FunctionSymbols.size() << "\n"); + DEBUG(dbgs() << "Globals : " << GlobalSymbols.size() << "\n"); +} + +Symbol *ObjFile::createUndefined(const WasmSymbol &Sym) { + return Symtab->addUndefined(this, &Sym); +} + +Symbol *ObjFile::createDefined(const WasmSymbol &Sym, + const InputSegment *Segment) { + Symbol* S; + if (Sym.isLocal()) { + S = make(Sym.Name, true); + Symbol::Kind Kind; + if (Sym.Type == WasmSymbol::SymbolType::FUNCTION_EXPORT) + Kind = Symbol::Kind::DefinedFunctionKind; + else if (Sym.Type == WasmSymbol::SymbolType::GLOBAL_EXPORT) + Kind = Symbol::Kind::DefinedGlobalKind; + else + llvm_unreachable("invalid local symbol type"); + S->update(Kind, this, &Sym, Segment); + return S; + } + return Symtab->addDefined(this, &Sym, Segment); +} + +void ArchiveFile::parse() { + // Parse a MemoryBufferRef as an archive file. + DEBUG(dbgs() << "Parsing library: " << toString(this) << "\n"); + File = check(Archive::create(MB), toString(this)); + + // Read the symbol table to construct Lazy symbols. + int Count = 0; + for (const Archive::Symbol &Sym : File->symbols()) { + Symtab->addLazy(this, &Sym); + ++Count; + } + DEBUG(dbgs() << "Read " << Count << " symbols\n"); +} + +void ArchiveFile::addMember(const Archive::Symbol *Sym) { + const Archive::Child &C = + check(Sym->getMember(), + "could not get the member for symbol " + Sym->getName()); + + // Don't try to load the same member twice (this can happen when members + // mutually reference each other). + if (!Seen.insert(C.getChildOffset()).second) + return; + + DEBUG(dbgs() << "loading lazy: " << displayName(Sym->getName()) << "\n"); + DEBUG(dbgs() << "from archive: " << toString(this) << "\n"); + + MemoryBufferRef MB = + check(C.getMemoryBufferRef(), + "could not get the buffer for the member defining symbol " + + Sym->getName()); + + if (identify_magic(MB.getBuffer()) != file_magic::wasm_object) { + error("unknown file type: " + MB.getBufferIdentifier()); + return; + } + + InputFile *Obj = make(MB); + Obj->ParentName = ParentName; + Symtab->addFile(Obj); +} + +// Returns a string in the format of "foo.o" or "foo.a(bar.o)". +std::string lld::toString(wasm::InputFile *File) { + if (!File) + return ""; + + if (File->ParentName.empty()) + return File->getName(); + + return (File->ParentName + "(" + File->getName() + ")").str(); +} Index: wasm/InputSegment.h =================================================================== --- /dev/null +++ wasm/InputSegment.h @@ -0,0 +1,80 @@ +//===- InputSegment.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Represents a WebAssembly data segment which can be included as part of +// an output data segments. Note that in WebAssembly, unlike ELF and other +// formats, used the term "data segment" to refer to the continous regions of +// memory that make on the data section. See: +// https://webassembly.github.io/spec/syntax/modules.html#syntax-data +// +// For example, by default, clang will produce a separate data section for +// each global variable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_INPUT_SEGMENT_H +#define LLD_WASM_INPUT_SEGMENT_H + +#include "lld/Common/ErrorHandler.h" +#include "llvm/Object/Wasm.h" + +using llvm::object::WasmSegment; +using llvm::wasm::WasmRelocation; + +namespace lld { +namespace wasm { + +class ObjFile; +class OutputSegment; + +class InputSegment { +public: + InputSegment(const WasmSegment *Seg, const ObjFile *F) + : Segment(Seg), File(F) {} + + // Translate an offset in the input segment to an offset in the output + // segment. + uint32_t translateVA(uint32_t Address) const; + + const OutputSegment *getOutputSegment() const { + return OutputSeg; + } + + uint32_t getOutputSegmentOffset() const { + return OutputSegmentOffset; + } + + uint32_t getInputSectionOffset() const { + return Segment->SectionOffset; + } + + void setOutputSegment(const OutputSegment* Segment, uint32_t Offset) { + OutputSeg = Segment; + OutputSegmentOffset = Offset; + } + + uint32_t getSize() const { return Segment->Data.Content.size(); } + uint32_t getAlignment() const { return Segment->Data.Alignment; } + uint32_t startVA() const { return Segment->Data.Offset.Value.Int32; } + uint32_t endVA() const { return startVA() + getSize(); } + StringRef getName() const { return Segment->Data.Name; } + + const WasmSegment *Segment; + const ObjFile *File; + std::vector Relocations; + +protected: + const OutputSegment *OutputSeg = nullptr; + uint32_t OutputSegmentOffset = 0; +}; + +} // namespace wasm +} // namespace lld + +#endif // LLD_WASM_INPUT_SEGMENT_H Index: wasm/InputSegment.cpp =================================================================== --- /dev/null +++ wasm/InputSegment.cpp @@ -0,0 +1,26 @@ +//===- InputSegment.cpp ---------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InputSegment.h" +#include "OutputSegment.h" +#include "lld/Common/LLVM.h" + +#define DEBUG_TYPE "lld" + +using namespace llvm; +using namespace lld::wasm; + +uint32_t InputSegment::translateVA(uint32_t Address) const { + assert(Address >= startVA() && Address < endVA()); + int32_t Delta = OutputSeg->StartVA + OutputSegmentOffset - + startVA(); + DEBUG(dbgs() << "translateVA: " << getName() << " Delta=" << Delta + << " Address=" << Address << "\n"); + return Address + Delta; +} Index: wasm/Memory.h =================================================================== --- /dev/null +++ wasm/Memory.h @@ -0,0 +1,52 @@ +//===- Memory.h -------------------------------------------------*- C++ -*-===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// See WASM/Memory.h +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_WASM_MEMORY_H +#define LLD_WASM_MEMORY_H + +#include "llvm/Support/Allocator.h" +#include "llvm/Support/StringSaver.h" +#include + +namespace lld { +namespace wasm { + +extern llvm::BumpPtrAllocator BAlloc; +extern llvm::StringSaver Saver; + +struct SpecificAllocBase { + SpecificAllocBase() { Instances.push_back(this); } + virtual ~SpecificAllocBase() = default; + virtual void reset() = 0; + static std::vector Instances; +}; + +template struct SpecificAlloc : public SpecificAllocBase { + void reset() override { Alloc.DestroyAll(); } + llvm::SpecificBumpPtrAllocator Alloc; +}; + +template T *make(U &&... Args) { + static SpecificAlloc Alloc; + return new (Alloc.Alloc.Allocate()) T(std::forward(Args)...); +} + +inline void freeArena() { + for (SpecificAllocBase *Alloc : SpecificAllocBase::Instances) + Alloc->reset(); + BAlloc.Reset(); +} +} // namespace wasm +} // namespace lld + +#endif Index: wasm/Options.td =================================================================== --- /dev/null +++ wasm/Options.td @@ -0,0 +1,89 @@ +include "llvm/Option/OptParser.td" + +// For options whose names are multiple letters, either one dash or +// two can precede the option name except those that start with 'o'. +class F: Flag<["--", "-"], name>; +class J: Joined<["--", "-"], name>; +class S: Separate<["--", "-"], name>; + +def L: JoinedOrSeparate<["-"], "L">, MetaVarName<"">, + HelpText<"Add a directory to the library search path">; + +def color_diagnostics: F<"color-diagnostics">, + HelpText<"Use colors in diagnostics">; + +def color_diagnostics_eq: J<"color-diagnostics=">, + HelpText<"Use colors in diagnostics">; + +// The follow flags are shared with the ELF linker +def help: F<"help">, HelpText<"Print option help">; + +def l: JoinedOrSeparate<["-"], "l">, MetaVarName<"">, + HelpText<"Root name of library to use">; + +def mllvm: S<"mllvm">, HelpText<"Options to pass to LLVM">; + +def no_threads: F<"no-threads">, + HelpText<"Do not run the linker multi-threaded">; + +def no_color_diagnostics: F<"no-color-diagnostics">, + HelpText<"Do not use colors in diagnostics">; + +def o: JoinedOrSeparate<["-"], "o">, MetaVarName<"">, + HelpText<"Path to file to write output">; + +def threads: F<"threads">, HelpText<"Run the linker multi-threaded">; + +def v: Flag<["-"], "v">, HelpText<"Display the version number">; + +def version: F<"version">, HelpText<"Display the version number and exit">; + +def verbose: F<"verbose">, HelpText<"Verbose mode">; + +def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">; + +def emit_relocs: F<"emit-relocs">, HelpText<"Generate relocations in output">; + +def strip_all: F<"strip-all">, HelpText<"Strip all symbols">; + +def strip_debug: F<"strip-debug">, HelpText<"Strip debugging information">; + +def sysroot: J<"sysroot=">, HelpText<"Set the system root">; + +def z: JoinedOrSeparate<["-"], "z">, MetaVarName<"