Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -221,4 +221,4 @@ add_subdirectory(docs) add_subdirectory(COFF) add_subdirectory(ELF) - +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 @@ -28,6 +28,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/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" + +@.str = private 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]* @.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,6 @@ +define i32 @foo() #0 { +entry: + ret i32 0 +} + +@bar = weak alias i32 (), i32 ()* @foo Index: test/wasm/archive.ll =================================================================== --- /dev/null +++ test/wasm/archive.ll @@ -0,0 +1,22 @@ +; 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 + +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 +; CHECK: T main Index: test/wasm/call-indirect.ll =================================================================== --- /dev/null +++ test/wasm/call-indirect.ll @@ -0,0 +1,99 @@ +; 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 + +; Wasm module 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: FileHeader: +; CHECK: Version: 0x00000001 +; CHECK: Sections: +; CHECK: - Type: TYPE +; CHECK: Signatures: +; CHECK: - Index: 0 +; CHECK: ReturnType: I32 +; CHECK: ParamTypes: +; CHECK: - F32 +; CHECK: - Index: 1 +; CHECK: ReturnType: I32 +; CHECK: ParamTypes: +; CHECK: - Index: 2 +; CHECK: ReturnType: NORESULT +; CHECK: ParamTypes: +; CHECK: - Type: FUNCTION +; CHECK: FunctionTypes: [ 0, 1, 2 ] +; CHECK: - Type: TABLE +; CHECK: Tables: +; CHECK: - ElemType: ANYFUNC +; CHECK: Limits: +; CHECK: Flags: 0x00000001 +; CHECK: Initial: 0x00000001 +; CHECK: Maximum: 0x00000001 +; CHECK: - Type: MEMORY +; CHECK: Memories: +; CHECK: - Initial: 0x00000003 +; CHECK: - Type: GLOBAL +; CHECK: Globals: +; CHECK: - Type: I32 +; CHECK: Mutable: true +; CHECK: InitExpr: +; CHECK: Opcode: I32_CONST +; CHECK: Value: 132096 +; CHECK: - Type: EXPORT +; CHECK: Exports: +; CHECK: - Name: memory +; CHECK: Kind: MEMORY +; CHECK: Index: 0 +; CHECK: - Name: main +; CHECK: Kind: FUNCTION +; CHECK: Index: 2 +; CHECK: - Type: ELEM +; CHECK: Segments: +; CHECK: - Offset: +; CHECK: Opcode: I32_CONST +; CHECK: Value: 0 +; CHECK: Functions: [ 0 ] +; CHECK: - Type: CODE +; CHECK: Functions: +; CHECK: - Locals: +; CHECK: - Locals: +; CHECK: - Locals: +; CHECK: - Type: DATA +; CHECK: Segments: +; CHECK: - Index: 0 +; CHECK: Offset: +; CHECK: Opcode: I32_CONST +; CHECK: Value: 1024 +; CHECK: Content: '00000000' +; CHECK: - Type: CUSTOM +; CHECK: Name: name +; CHECK: FunctionNames: +; CHECK: - Index: 0 +; CHECK: Name: ret32 +; CHECK: - Index: 1 +; CHECK: Name: foo +; CHECK: - Index: 2 +; CHECK: Name: _start Index: test/wasm/conflict.test =================================================================== --- /dev/null +++ test/wasm/conflict.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 %t.ret32.o 2>&1 | FileCheck %s + +# CHECK: duplicate symbol: ret32 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: main +; 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,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: - Type: CUSTOM +; CHECK-NEXT: 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/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,100 @@ +; 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 + +; 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: MEMORY +; CHECK-NEXT: Memories: +; CHECK-NEXT: - Initial: 0x00000002 +; 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: 1024 +; 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: CODE +; CHECK-NEXT: Relocations: +; CHECK-NEXT: - Type: R_WEBASSEMBLY_GLOBAL_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: {{.*}} +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: {{.*}} +; CHECK-NEXT: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: 68656C6C6F0A00 +; 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,5 @@ +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-NOT: - Type: CUSTOM Index: test/wasm/undefined-entry.test =================================================================== --- /dev/null +++ test/wasm/undefined-entry.test @@ -0,0 +1,5 @@ +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: lld: error: undefined symbol: _start +CHECK: lld: error: link failed Index: test/wasm/undefined.ll =================================================================== --- /dev/null +++ test/wasm/undefined.ll @@ -0,0 +1,13 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: not lld -flavor wasm -o %t.wasm %t.o 2>&1 | FileCheck %s + +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 + +; CHECK: lld: error: {{.*}}.o: undefined symbol: foo +; CHECK: lld: error: link failed 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.ll =================================================================== --- /dev/null +++ test/wasm/weak-alias.ll @@ -0,0 +1,61 @@ +; 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: - Index: 1 +; CHECK-NEXT: ReturnType: NORESULT +; CHECK-NEXT: ParamTypes: +; CHECK-NEXT: - Type: FUNCTION +; CHECK-NEXT: FunctionTypes: [ 0, 0 ] +; 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: main +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1081808080000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 41000B +; 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 Index: test/wasm/weak-external.ll =================================================================== --- /dev/null +++ test/wasm/weak-external.ll @@ -0,0 +1,58 @@ +; 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: FileHeader: +; CHECK: Version: 0x00000001 +; CHECK: Sections: +; CHECK: - Type: TYPE +; CHECK: Signatures: +; CHECK: - Index: 0 +; CHECK: ReturnType: I32 +; CHECK: ParamTypes: +; CHECK: - Type: FUNCTION +; CHECK: FunctionTypes: [ 0, 0 ] +; CHECK: - Type: MEMORY +; CHECK: Memories: +; CHECK: - Initial: 0x00000002 +; CHECK: - Type: GLOBAL +; CHECK: Globals: +; CHECK: - Type: I32 +; CHECK: Mutable: true +; CHECK: InitExpr: +; CHECK: Opcode: I32_CONST +; CHECK: Value: 66560 +; CHECK: - Type: EXPORT +; CHECK: Exports: +; CHECK: - Name: memory +; CHECK: Kind: MEMORY +; CHECK: Index: 0 +; CHECK: - Name: main +; CHECK: Kind: FUNCTION +; CHECK: Index: 1 +; CHECK: - Type: CODE +; CHECK: Functions: +; CHECK: - Locals: +; CHECK: Body: 4180808080000B +; CHECK: - Locals: +; CHECK: Body: 4100280280808080000B +; CHECK: ... Index: tools/lld/CMakeLists.txt =================================================================== --- tools/lld/CMakeLists.txt +++ tools/lld/CMakeLists.txt @@ -10,13 +10,14 @@ lldDriver lldCOFF lldELF + 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("ld.wasm", "wasm", Wasm) .CaseLower("link", WinLink) .CaseLower("darwin", Darwin) .Default(Invalid); @@ -106,6 +108,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,26 @@ +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 + Strings.cpp + SymbolTable.cpp + Symbols.cpp + Writer.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 Relocatable = false; + bool Verbose = false; + bool StripDebug = false; + bool StripAll = false; + bool ImportMemory = 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; + llvm::StringRef ExportEntryAs = "main"; + + 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,58 @@ +//===- 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); + + // Used by ArchiveFile to add members. + void addArchiveBuffer(MemoryBufferRef MBRef, StringRef SymName, + StringRef ParentName); +private: + void addSyntheticUndefinedFunction(StringRef Name); + void addSyntheticGlobal(StringRef Name, int32_t Value); + void createFiles(llvm::opt::InputArgList &Args); + void addFile(StringRef Path); + void addLibrary(StringRef Name); + std::vector Files; +}; + +extern LinkerDriver *Driver; +void printHelp(const char *Argv0); +llvm::Optional searchLibrary(StringRef Name); + +} // namespace wasm +} // namespace lld + +#endif Index: wasm/Driver.cpp =================================================================== --- /dev/null +++ wasm/Driver.cpp @@ -0,0 +1,365 @@ +//===- 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/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +using namespace llvm; +using namespace llvm::sys; +using llvm::sys::Process; + +namespace lld { +namespace wasm { + +std::vector SpecificAllocBase::Instances; +Configuration *Config; +LinkerDriver *Driver; + +// 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 StringRef getString(opt::InputArgList &Args, unsigned Key, + StringRef Default = "") { + if (auto *Arg = Args.getLastArg(Key)) + return Arg->getValue(); + return Default; +} + +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 Result; + if (Value.getAsInteger(0, Result)) + error("invalid " + Key + ": " + Value); + return Result; + } + } + return Default; +} + +static bool parseUndefinedFile(StringRef Filename) { + Optional Buffer = readFile(Filename); + if (!Buffer.hasValue()) + return false; + StringRef Str = Buffer->getBuffer(); + size_t Pos = 0; + while (1) { + size_t NextLine = Str.find('\n', Pos); + StringRef SymbolName = Str.slice(Pos, NextLine); + if (!SymbolName.empty()) + Config->AllowUndefinedSymbols.insert(SymbolName); + if (NextLine == StringRef::npos) + break; + Pos = NextLine + 1; + } + + + return true; +} + +// Parse -color-diagnostics={auto,always,never} or -no-color-diagnostics. +static bool getColorDiagnostics(opt::InputArgList &Args) { + bool Default = (ErrorOS == &errs() && Process::StandardErrHasColors()); + + auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, + OPT_no_color_diagnostics); + if (!Arg) + return Default; + 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 Default; + if (S == "always") + return true; + if (S != "never") + error("unknown option: -color-diagnostics=" + S); + return 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; + + switch (identify_magic(MBRef.getBuffer())) { + case file_magic::archive: + Files.push_back(make(MBRef)); + break; + default: + Files.push_back(make(MBRef)); + break; + } +} + +void LinkerDriver::addArchiveBuffer(MemoryBufferRef MB, StringRef SymName, + StringRef ParentName) { + InputFile *Obj; + file_magic Magic = identify_magic(MB.getBuffer()); + if (Magic == file_magic::wasm_object) { + Obj = make(MB); + } else { + error("unknown file type: " + MB.getBufferIdentifier()); + return; + } + + Obj->ParentName = ParentName; + Symtab->addFile(Obj); + log("Including " + toString(Obj) + " for: " + SymName); +} + +// Add a given library by searching it from input search paths. +void LinkerDriver::addLibrary(StringRef Name) { + if (Optional Path = searchLibrary(Name)) + addFile(*Path); + else + error("unable to find library -l" + Name); +} + +void LinkerDriver::addSyntheticGlobal(StringRef Name, int32_t Value) { + log("injecting global: " + Twine(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); +} + +void LinkerDriver::addSyntheticUndefinedFunction(StringRef Name) { + log("injecting undefined func: " + Twine(Name)); + Symtab->addUndefinedFunction(Name); +} + +void LinkerDriver::createFiles(opt::InputArgList &Args) { + for (auto *Arg : Args) { + switch (Arg->getOption().getID()) { + case OPT_l: + addLibrary(Arg->getValue()); + break; + case OPT_INPUT: + addFile(Arg->getValue()); + } + } + + if (Files.empty()) + error("no input files"); +} + +void LinkerDriver::link(ArrayRef ArgsArr) { + SymbolTable Symtab; + wasm::Symtab = &Symtab; + + 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); + + // GNU linkers disagree here. Though both -version and -v are mentioned + // in help to print the version information, GNU ld just normally exits, + // while gold can continue linking. We are compatible with ld.bfd here. + if (Args.hasArg(OPT_version) || Args.hasArg(OPT_v)) + outs() << getLLDVersion() << "\n"; + if (Args.hasArg(OPT_version)) + return; + + Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + Config->EmitRelocs = Args.hasArg(OPT_emit_relocs); + Config->Entry = getString(Args, OPT_entry); + Config->ImportMemory = Args.hasArg(OPT_import_memory); + Config->OutputFile = getString(Args, 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 = getString(Args, OPT_sysroot); + Config->Verbose = Args.hasArg(OPT_verbose); + + 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 = getString(Args, OPT_allow_undefined_file); + if (!AllowUndefinedFilename.empty()) { + if (!parseUndefinedFile(AllowUndefinedFilename)) + return; + } + + // Default output filename is "a.out" by the Unix tradition. + if (Config->OutputFile.empty()) + Config->OutputFile = "a.out"; + + if (!Args.hasArgNoClaim(OPT_INPUT)) + fatal("no input files"); + + 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->AllowUndefined && !Config->Relocatable) + Symtab.reportRemainingUndefines(); + + if (!Config->Relocatable) { + Symbol* Sym = Symtab.find(Config->Entry); + if (!Sym->isFunction()) + fatal("entry point is not a function: " + Sym->getName()); + if (!Sym->isDefined()) + fatal("entry point undefined: " + Sym->getName()); + } + + // Write the result to the file. + writeResult(&Symtab); +} + +void printHelp(const char *Argv0) { + WasmOptTable Table; + Table.PrintHelp(outs(), Argv0, "LLVM Linker", false); +} + +bool link(ArrayRef Args, raw_ostream &Error) { + ErrorCount = 0; + Argv0 = Args[0]; + ErrorOS = &Error; + Config = make(); + Driver = make(); + Driver->link(Args); + return !ErrorCount; +} + +// Find a file by concatenating given paths. If a resulting path +// starts with "=", the character is replaced with a --sysroot value. +static Optional findFile(StringRef Path1, const Twine &Path2) { + SmallString<128> S; + if (Path1.startswith("=")) + path::append(S, Config->Sysroot, Path1.substr(1), Path2); + else + path::append(S, Path1, Path2); + + if (fs::exists(S)) + return S.str().str(); + return None; +} + +static Optional findFromSearchPaths(StringRef Path) { + for (StringRef Dir : Config->SearchPaths) + if (Optional S = findFile(Dir, Path)) + return S; + return None; +} + +// This is for -lfoo. We'll look for libfoo.so or libfoo.a from +// search paths. +Optional searchLibrary(StringRef Name) { + if (Name.startswith(":")) + return findFromSearchPaths(Name.substr(1)); + + for (StringRef Dir : Config->SearchPaths) { + if (Optional S = findFile(Dir, "lib" + Name + ".a")) + return S; + } + return None; +} + +} // 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,96 @@ +//===- 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,135 @@ +//===- 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/DenseSet.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/Wasm.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; +}; + +// .lib or .a file. +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. +class ObjectFile : public InputFile { + std::unique_ptr WasmObj; + + void initializeSymbols(); + +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 underying wasm file. + const WasmObjectFile *getWasmObj() { 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 relocateCodeOffset(uint32_t original) const; + uint32_t relocateDataLocation(uint32_t original) const; + + int32_t getGlobalAddress(uint32_t index) const; + + bool isImportedFunction(uint32_t index) const; + bool isImportedGlobal(uint32_t index) const; + bool isResolvedFunctionImport(uint32_t index) const; + bool isResolvedGlobalImport(uint32_t index) const; + + int32_t FunctionIndexOffset = 0; + int32_t GlobalIndexOffset = 0; + uint32_t TableIndexOffset = 0; + uint32_t CodeSectionOffset = 0; + uint32_t DataOffset = 0; + const WasmSection* CodeSection = nullptr; + const WasmSection* DataSection = nullptr; + + llvm::DenseMap TypeMap; + std::vector FunctionImports; + std::vector GlobalImports; +}; + +// 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,244 @@ +//===- 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 "SymbolTable.h" +#include "Config.h" +#include "Driver.h" +#include "Error.h" +#include "Memory.h" +#include "Strings.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()); + log(" FunctionIndexOffset : " + Twine(FunctionIndexOffset)); + log(" FunctionImports.size() : " + Twine(FunctionImports.size())); + log(" TableIndexOffset : " + Twine(TableIndexOffset)); + log(" GlobalIndexOffset : " + Twine(GlobalIndexOffset)); + log(" DataOffset : " + Twine(DataOffset)); + log(" GlobalImports.size() : " + Twine(GlobalImports.size())); +} + +bool ObjectFile::isImportedFunction(uint32_t index) const { + return index < FunctionImports.size(); +} + +bool ObjectFile::isImportedGlobal(uint32_t index) const { + return index < GlobalImports.size(); +} + +bool ObjectFile::isResolvedFunctionImport(uint32_t index) const { + if (!isImportedFunction(index)) + return false; + const WasmSymbol *WasmSym = FunctionImports[index]; + Symbol* Sym = Symtab->find(WasmSym->Name); + return Sym->isDefined(); +} + +bool ObjectFile::isResolvedGlobalImport(uint32_t index) const { + if (!isImportedGlobal(index)) + return false; + const WasmSymbol *WasmSym = GlobalImports[index]; + Symbol* Sym = Symtab->find(WasmSym->Name); + return Sym->isDefined(); +} + +int32_t ObjectFile::getGlobalAddress(uint32_t index) const { + if (isImportedGlobal(index)) { + return 0; + } else { + index -= GlobalImports.size(); + const WasmGlobal &Global = WasmObj->globals()[index]; + assert(Global.Type == WASM_TYPE_I32); + return Global.InitExpr.Value.Int32 + DataOffset; + } +} + +uint32_t ObjectFile::relocateFunctionIndex(uint32_t original) const { + if (isImportedFunction(original)) { + const WasmSymbol *WasmSym = FunctionImports[original]; + Symbol* Sym = Symtab->find(WasmSym->Name); + assert(Sym && "imported symbol not found in symbol table"); + return Sym->getOutputIndex(); + } else { + 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) const { + if (isImportedGlobal(original)) { + const WasmSymbol *WasmSym = GlobalImports[original]; + Symbol* Sym = Symtab->find(WasmSym->Name); + assert(Sym && "imported symbol not found in symbol table"); + return Sym->getOutputIndex(); + } else { + return original + GlobalIndexOffset; + } +} + +uint32_t ObjectFile::relocateCodeOffset(uint32_t original) const { + return original + CodeSectionOffset; +} + +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)); + + if (auto *Obj = dyn_cast(Bin.get())) { + Bin.release(); + WasmObj.reset(Obj); + } else { + fatal(toString(this) + " is not a wasm file"); + } + + 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() { + uint32_t NumSymbols = WasmObj->getNumberOfSymbols(); + Symbols.reserve(NumSymbols); + + for (const SymbolRef &Sym: WasmObj->symbols()) { + const WasmSymbol &WasmSym = WasmObj->getWasmSymbol(Sym.getRawDataRefImpl()); + switch (WasmSym.Type) { + case WasmSymbol::SymbolType::FUNCTION_IMPORT: + FunctionImports.emplace_back(&WasmSym); + createUndefined(WasmSym); + break; + case WasmSymbol::SymbolType::GLOBAL_IMPORT: + GlobalImports.emplace_back(&WasmSym); + 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); + if (S) + Symbols.push_back(S); + return S; +} + +Symbol *ObjectFile::createDefined(const WasmSymbol &Sym) { + Symbol *S = Symtab->addDefined(this, &Sym); + if (S) + 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 objects. + 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"); + //DEBUG(dbgs() << "loading symbol from object symbol: " << C.getName() << "\n"); + MemoryBufferRef MB = + check(C.getMemoryBufferRef(), + "could not get the buffer for the member defining symbol " + + Sym->getName()); + Driver->addArchiveBuffer(MB, Sym->getName(), ParentName); +} + +} // 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(); +} +} +} + +#endif Index: wasm/Options.td =================================================================== --- /dev/null +++ wasm/Options.td @@ -0,0 +1,84 @@ +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_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<"