Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -222,4 +222,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: include/lld/Driver/Driver.h =================================================================== --- include/lld/Driver/Driver.h +++ include/lld/Driver/Driver.h @@ -33,6 +33,11 @@ bool link(llvm::ArrayRef Args, llvm::raw_ostream &Diag = llvm::errs()); } + +namespace wasm { +bool link(llvm::ArrayRef Args, + llvm::raw_ostream &Diag = llvm::errs()); +} } #endif Index: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/lit.cfg @@ -162,6 +162,7 @@ r"\bnot\b", NoPreJunk + r"\blld\b" + NoPostJunk, r"\bld.lld\b", + r"\bllc\b", r"\blld-link\b", r"\bllvm-as\b", r"\bllvm-mc\b", 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,13 @@ +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,117 @@ +; 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: '01000000' +; CHECK-NEXT: - SectionOffset: 17 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1028 +; CHECK-NEXT: Content: '02000000' +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 8 +; CHECK-NEXT: DataAlignment: 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: 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,77 @@ +; 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: 0x0000001B +; 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: '01000000' +; CHECK-NEXT: - SectionOffset: 17 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1040 +; CHECK-NEXT: Content: '03000000' +; CHECK-NEXT: - SectionOffset: 27 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1048 +; CHECK-NEXT: Content: 1C040000 +; CHECK-NEXT: - SectionOffset: 37 +; 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 +; CHECK-NEXT: DataAlignment: 0 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,34 @@ +; 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 Index: test/wasm/function-imports.ll =================================================================== --- /dev/null +++ test/wasm/function-imports.ll @@ -0,0 +1,39 @@ +; 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 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 @@ +config.suffixes = ['.test', '.yaml', '.ll'] Index: test/wasm/relocatable.ll =================================================================== --- /dev/null +++ test/wasm/relocatable.ll @@ -0,0 +1,148 @@ +; 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 #0 { +entry: + %call = tail call i32 @foo_import() #2 + ret i32 1 +} + +declare i32 @foo_import() local_unnamed_addr #1 + +@func_addr1 = hidden global i32()* @my_func, align 4 +@func_addr2 = hidden global i32()* @foo_import, align 4 + +; 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: - 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: 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: 0 +; 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: 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: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 16 +; CHECK-NEXT: DataAlignment: 4 +; 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,93 @@ +; 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: DataAlignment: 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,83 @@ +; 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: DataAlignment: 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,78 @@ +; 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 @_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 ] +; 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: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; 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: 4100280280808080000B +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: DataSize: 0 +; CHECK-NEXT: DataAlignment: 0 +; CHECK-NEXT: ... Index: tools/lld/CMakeLists.txt =================================================================== --- tools/lld/CMakeLists.txt +++ tools/lld/CMakeLists.txt @@ -11,13 +11,14 @@ lldCOFF lldELF lldMinGW + lldWasm ) install(TARGETS lld RUNTIME DESTINATION bin) if(NOT LLD_SYMLINKS_TO_CREATE) - set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld) + set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld ld-wasm) 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) .CaseLower("darwin", Darwin) .Default(Invalid); @@ -118,6 +120,8 @@ return !coff::link(Args); case Darwin: return !mach_o::link(Args); + case Wasm: + return !wasm::link(Args); 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 + Error.cpp + InputFiles.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 + lldConfig + lldCore + ) Index: wasm/Config.h =================================================================== --- /dev/null +++ wasm/Config.h @@ -0,0 +1,56 @@ +//===- 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 ColorDiagnostics = false; + bool Demangle = true; + bool EmitRelocs = false; + bool ImportMemory = false; + bool Relocatable = false; + bool Verbose = false; + bool StripDebug = false; + bool StripAll = false; + bool Threads = false; + uint32_t ZStackSize = 0; + uint32_t MaxMemory = 0; + uint32_t GlobalBase = 0; + uint32_t InitialMemory = 0; + uint64_t ErrorLimit = 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.h =================================================================== --- /dev/null +++ wasm/Driver.h @@ -0,0 +1,51 @@ +//===- Driver.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_DRIVER_H +#define LLD_WASM_DRIVER_H + +#include "InputFiles.h" +#include "lld/Core/LLVM.h" +#include "llvm/Option/ArgList.h" + +namespace lld { +namespace wasm { + +// 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; +}; + +extern LinkerDriver *Driver; + +} // namespace wasm +} // namespace lld + +#endif Index: wasm/Driver.cpp =================================================================== --- /dev/null +++ wasm/Driver.cpp @@ -0,0 +1,318 @@ +//===- Driver.cpp ---------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Driver.h" + +#include "Config.h" +#include "Error.h" +#include "Memory.h" +#include "SymbolTable.h" +#include "Writer.h" +#include "lld/Config/Version.h" +#include "lld/Driver/Driver.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Object/Wasm.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; + +namespace lld { +namespace wasm { + +std::vector SpecificAllocBase::Instances; +Configuration *Config; +LinkerDriver *Driver; + +bool link(ArrayRef Args, raw_ostream &Error) { + ErrorCount = 0; + Argv0 = Args[0]; + ErrorOS = &Error; + Config = make(); + Driver = make(); + Symtab = make(); + + Driver->link(Args); + 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 const 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()) + Ret.push_back(S); + } + return Ret; +} + +// Parse -color-diagnostics={auto,always,never} or -no-color-diagnostics. +static bool getColorDiagnostics(opt::InputArgList &Args) { + auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, + OPT_no_color_diagnostics); + if (!Arg) + return ErrorOS->has_colors(); + if (Arg->getOption().getID() == OPT_color_diagnostics) + return true; + if (Arg->getOption().getID() == OPT_no_color_diagnostics) + return false; + + StringRef S = Arg->getValue(); + if (S == "auto") + return ErrorOS->has_colors(); + if (S == "always") + return true; + if (S != "never") + error("unknown option: -color-diagnostics=" + S); + return false; +} + +// 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); + + for (auto *Arg : Args.filtered(OPT_UNKNOWN)) + fatal(Twine("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()); + + Config->ColorDiagnostics = getColorDiagnostics(Args); + + 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); + Config->Verbose = Args.hasArg(OPT_verbose); + Config->Threads = !Args.hasArg(OPT_no_threads); + + Config->InitialMemory = getInteger(Args, OPT_initial_memory, 0); + Config->ErrorLimit = getInteger(Args, OPT_error_limit, 20); + Config->GlobalBase = getInteger(Args, OPT_global_base, 1024); + Config->MaxMemory = getInteger(Args, OPT_max_memory, 0); + Config->ZStackSize = getZOptionValue(Args, "stack-size", WasmPageSize); + + StringRef AllowUndefinedFilename = + Args.getLastArgValue(OPT_allow_undefined_file); + if (!AllowUndefinedFilename.empty()) + if (Optional Buf = readFile(AllowUndefinedFilename)) + for (StringRef Sym : getLines(*Buf)) + Config->AllowUndefinedSymbols.insert(Sym); + + if (Config->OutputFile.empty()) + fatal("no output file specified"); + + if (!Args.hasArgNoClaim(OPT_INPUT)) + fatal("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(); +} + +} // namespace wasm +} // namespace lld Index: wasm/Error.h =================================================================== --- /dev/null +++ wasm/Error.h @@ -0,0 +1,57 @@ +//===- Error.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_ERROR_H +#define LLD_WASM_ERROR_H + +#include "lld/Core/LLVM.h" +#include "llvm/Support/Error.h" + +namespace lld { +namespace wasm { + +extern uint64_t ErrorCount; +extern llvm::raw_ostream *ErrorOS; +extern llvm::StringRef Argv0; + +void log(const Twine &Msg); +void warn(const Twine &Msg); +void error(const Twine &Msg); +LLVM_ATTRIBUTE_NORETURN void fatal(const Twine &Msg); + +// check() functions are convenient functions to strip errors +// from error-or-value objects. +template T check(ErrorOr E) { + if (auto EC = E.getError()) + fatal(EC.message()); + return std::move(*E); +} + +template T check(Expected E) { + if (!E) + fatal(llvm::toString(E.takeError())); + return std::move(*E); +} + +template T check(ErrorOr E, const Twine &Prefix) { + if (auto EC = E.getError()) + fatal(Prefix + ": " + EC.message()); + return std::move(*E); +} + +template T check(Expected E, const Twine &Prefix) { + if (!E) + fatal(Prefix + ": " + llvm::toString(E.takeError())); + return std::move(*E); +} + +} // namespace wasm +} // namespace lld + +#endif Index: wasm/Error.cpp =================================================================== --- /dev/null +++ wasm/Error.cpp @@ -0,0 +1,94 @@ +//===- Error.cpp ----------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Error.h" + +#include "Config.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/raw_ostream.h" + +#include + +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include +#endif + +using namespace llvm; + +namespace lld { +namespace wasm { + +uint64_t ErrorCount; +raw_ostream *ErrorOS; +StringRef Argv0; + +// The functions defined in this file can be called from multiple threads, +// but outs() or errs() are not thread-safe. We protect them using a mutex. +static std::mutex Mu; + +static void print(StringRef S, raw_ostream::Colors C) { + *ErrorOS << Argv0 + ": "; + if (Config->ColorDiagnostics) { + ErrorOS->changeColor(C, true); + *ErrorOS << S; + ErrorOS->resetColor(); + } else { + *ErrorOS << S; + } +} + +LLVM_ATTRIBUTE_NORETURN static void exitLld(int Val) { + // Dealloc/destroy ManagedStatic variables before calling + // _exit(). In a non-LTO build, this is a nop. In an LTO + // build allows us to get the output of -time-passes. + llvm_shutdown(); + + outs().flush(); + errs().flush(); + _exit(Val); +} + +void log(const Twine &Msg) { + std::lock_guard Lock(Mu); + if (Config->Verbose) + errs() << "lld: " << Msg << "\n"; +} + +void fatal(const Twine &Msg) { + std::lock_guard Lock(Mu); + print("error: ", raw_ostream::RED); + *ErrorOS << Msg << "\n"; + ErrorOS->flush(); + exitLld(1); +} + +void error(const Twine &Msg) { + std::lock_guard Lock(Mu); + + print("error: ", raw_ostream::RED); + *ErrorOS << Msg << "\n"; + + if (Config->ErrorLimit != 0 && ++ErrorCount >= Config->ErrorLimit) { + print("error: ", raw_ostream::RED); + *ErrorOS << "too many errors emitted, stopping now" + << " (use -error-limit=0 to see all errors)\n"; + exitLld(1); + } +} + +void warn(const Twine &Msg) { + std::lock_guard Lock(Mu); + print("warning: ", raw_ostream::MAGENTA); + *ErrorOS << Msg << "\n"; +} + +} // namespace wasm +} // namespace lld Index: wasm/InputFiles.h =================================================================== --- /dev/null +++ wasm/InputFiles.h @@ -0,0 +1,159 @@ +//===- 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/Core/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; + +namespace lld { +namespace wasm { + +class Symbol; + +class InputFile { +public: + enum Kind { + ObjectKind, + ArchiveKind, + }; + + virtual ~InputFile() {} + + // Returns the filename. + StringRef getName() const { return MB.getBufferIdentifier(); } + + std::vector &getSymbols() { return Symbols; } + + // 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; + + // List of all symbols referenced or defined by this file. + std::vector Symbols; + +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 ObjectFile : public InputFile { +public: + explicit ObjectFile(MemoryBufferRef M) : InputFile(ObjectKind, M) {} + static bool classof(const InputFile *F) { return F->kind() == ObjectKind; } + + Symbol *createDefined(const WasmSymbol &Sym); + Symbol *createUndefined(const WasmSymbol &Sym); + + 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); + uint32_t relocateGlobalIndex(uint32_t Original); + uint32_t relocateTableIndex(uint32_t Original) const; + uint32_t relocateDataLocation(uint32_t Original) const; + + int32_t getGlobalAddress(uint32_t Index); + + // Returns true if the given function index is an imported function, + // as opposed to the locally defined function. + bool isImportedFunction(uint32_t Index) const; + + // Returns true if the given global index is an imported global, + // as opposed to the locally defined (exported) global. + bool isImportedGlobal(uint32_t Index) const; + + // Return true if the given imported (undefined) function has been resolved + // in the output binary (i.e. defined by another object). + bool isResolvedFunctionImport(uint32_t Index); + + // Return true if the given imported (undefined) global has been resolved + // in the output binary (i.e. defined by another object). + bool isResolvedGlobalImport(uint32_t Index); + + size_t NumFunctionImports() const { return FunctionImports.size(); } + size_t NumGlobalImports() const { return GlobalImports.size(); } + + int32_t FunctionIndexOffset = 0; + int32_t GlobalIndexOffset = 0; + int32_t TableIndexOffset = 0; + uint32_t DataOffset = 0; + const WasmSection *CodeSection = nullptr; + const WasmSection *DataSection = nullptr; + + llvm::DenseMap TypeMap; + +private: + std::vector FunctionImports; + std::vector GlobalImports; + + const Symbol *getFunctionSymbol(uint32_t Index); + const Symbol *getGlobalSymbol(uint32_t Index); + + std::unique_ptr WasmObj; + + // 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; + + void initializeSymbols(); +}; + +// 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,269 @@ +//===- 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 "Driver.h" +#include "Error.h" +#include "Memory.h" +#include "Strings.h" +#include "SymbolTable.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; + +namespace lld { +namespace wasm { + +Optional 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 ObjectFile::dumpInfo() const { + log("reloc info for: " + getName() + "\n" + + " FunctionIndexOffset : " + Twine(FunctionIndexOffset) + "\n" + + " NumFunctionImports : " + Twine(NumFunctionImports()) + "\n" + + " TableIndexOffset : " + Twine(TableIndexOffset) + "\n" + + " GlobalIndexOffset : " + Twine(GlobalIndexOffset) + "\n" + + " DataOffset : " + Twine(DataOffset) + "\n" + + " NumGlobalImports : " + Twine(NumGlobalImports()) + "\n"); +} + +bool ObjectFile::isImportedFunction(uint32_t Index) const { + return Index < NumFunctionImports(); +} + +bool ObjectFile::isImportedGlobal(uint32_t Index) const { + return Index < NumGlobalImports(); +} + +const Symbol *ObjectFile::getFunctionSymbol(uint32_t Index) { + assert(isImportedFunction(Index)); + if (FunctionSymbols[Index] == nullptr) { + StringRef Name = FunctionImports[Index]; + FunctionSymbols[Index] = Symtab->find(Name); + assert(FunctionSymbols[Index]); + } + return FunctionSymbols[Index]; +} + +const Symbol *ObjectFile::getGlobalSymbol(uint32_t Index) { + assert(isImportedGlobal(Index)); + if (GlobalSymbols[Index] == nullptr) { + StringRef Name = GlobalImports[Index]; + GlobalSymbols[Index] = Symtab->find(Name); + assert(GlobalSymbols[Index]); + } + return GlobalSymbols[Index]; +} + +bool ObjectFile::isResolvedFunctionImport(uint32_t Index) { + if (!isImportedFunction(Index)) + return false; + return getFunctionSymbol(Index)->isDefined(); +} + +bool ObjectFile::isResolvedGlobalImport(uint32_t Index) { + if (!isImportedGlobal(Index)) + return false; + return getGlobalSymbol(Index)->isDefined(); +} + +int32_t ObjectFile::getGlobalAddress(uint32_t Index) { + if (isImportedGlobal(Index)) + return getGlobalSymbol(Index)->getMemoryAddress(); + + const WasmGlobal &Global = WasmObj->globals()[Index - GlobalImports.size()]; + assert(Global.Type == WASM_TYPE_I32); + return Global.InitExpr.Value.Int32 + DataOffset; +} + +uint32_t ObjectFile::relocateFunctionIndex(uint32_t Original) { + DEBUG(dbgs() << "relocateFunctionIndex: " << Original << "\n"); + if (isImportedFunction(Original)) + return getFunctionSymbol(Original)->getOutputIndex(); + + DEBUG(dbgs() << " -> " << FunctionIndexOffset << " " + << (Original + FunctionIndexOffset) << "\n"); + return Original + FunctionIndexOffset; +} + +uint32_t ObjectFile::relocateTypeIndex(uint32_t Original) const { + assert(TypeMap.count(Original) > 0); + return TypeMap.find(Original)->second; +} + +uint32_t ObjectFile::relocateTableIndex(uint32_t Original) const { + return Original + TableIndexOffset; +} + +uint32_t ObjectFile::relocateGlobalIndex(uint32_t Original) { + if (isImportedGlobal(Original)) + return getGlobalSymbol(Original)->getOutputIndex(); + + return Original + GlobalIndexOffset; +} + +void ObjectFile::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(); +} + +void ObjectFile::initializeSymbols() { + Symbols.reserve(WasmObj->getNumberOfSymbols()); + + for (const WasmImport &Import : WasmObj->imports()) { + switch (Import.Kind) { + case WASM_EXTERNAL_FUNCTION: + FunctionImports.emplace_back(Import.Field); + break; + case WASM_EXTERNAL_GLOBAL: + GlobalImports.emplace_back(Import.Field); + break; + } + } + + FunctionSymbols.resize(FunctionImports.size()); + GlobalSymbols.resize(GlobalImports.size()); + + 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: + createUndefined(WasmSym); + break; + case WasmSymbol::SymbolType::FUNCTION_EXPORT: + case WasmSymbol::SymbolType::GLOBAL_EXPORT: + createDefined(WasmSym); + break; + case WasmSymbol::SymbolType::DEBUG_FUNCTION_NAME: + // These are internal only, no need to create linker symbols for them + break; + } + } +} + +Symbol *ObjectFile::createUndefined(const WasmSymbol &Sym) { + Symbol *S = Symtab->addUndefined(this, &Sym); + Symbols.push_back(S); + return S; +} + +Symbol *ObjectFile::createDefined(const WasmSymbol &Sym) { + Symbol *S = Symtab->addDefined(this, &Sym); + Symbols.push_back(S); + return S; +} + +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. + for (const Archive::Symbol &Sym : File->symbols()) + Symtab->addLazy(this, &Sym); +} + +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); +} + +} // namespace wasm +} // namespace lld + +// Returns the last element of a path, which is supposed to be a filename. +static StringRef getBasename(StringRef Path) { + size_t Pos = Path.find_last_of("\\/"); + if (Pos == StringRef::npos) + return Path; + return Path.substr(Pos + 1); +} + +// Returns a string in the format of "foo.obj" or "foo.obj(bar.lib)". +std::string lld::toString(wasm::InputFile *File) { + if (!File) + return "(internal)"; + + if (File->ParentName.empty()) + return File->getName(); + + std::string Res = + (getBasename(File->ParentName) + "(" + getBasename(File->getName()) + ")") + .str(); + return StringRef(Res); +} 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,87 @@ +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 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<"