| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| define i32 @foo() #0 { | ||
| entry: | ||
| ret i32 0 | ||
| } | ||
|
|
||
| @bar = weak alias i32 (), i32 ()* @foo | ||
|
|
||
| define hidden i32 @call_bar() #0 { | ||
| entry: | ||
| %call = call i32 @bar() | ||
| ret i32 %call | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| ; RUN: llc -filetype=obj %p/Inputs/call-indirect.ll -o %t2.o | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm -o %t.wasm %t2.o %t.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| ; bitcode generated from the following C code: | ||
| ; int foo(void) { return 1; } | ||
| ; int (*indirect_func)(void) = &foo; | ||
| ; void _start(void) { indirect_func(); } | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| @indirect_func = hidden local_unnamed_addr global i32 ()* @foo, align 4 | ||
|
|
||
| ; Function Attrs: norecurse nounwind readnone | ||
| define hidden i32 @foo() #0 { | ||
| entry: | ||
| ret i32 1 | ||
| } | ||
|
|
||
| ; Function Attrs: nounwind | ||
| define hidden void @_start() local_unnamed_addr #1 { | ||
| entry: | ||
| %0 = load i32 ()*, i32 ()** @indirect_func, align 4 | ||
| %call = tail call i32 %0() #2 | ||
| ret void | ||
| } | ||
|
|
||
| ; CHECK: !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 1, 0, 1 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000003 | ||
| ; CHECK-NEXT: Maximum: 0x00000003 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000002 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66576 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: memory | ||
| ; CHECK-NEXT: Kind: MEMORY | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: bar | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: call_bar_indirect | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Name: foo | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 2 | ||
| ; CHECK-NEXT: - Name: _start | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 3 | ||
| ; CHECK: - Type: ELEM | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1 | ||
| ; CHECK-NEXT: Functions: [ 0, 2 ] | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK: - Locals: | ||
| ; CHECK: - Locals: | ||
| ; CHECK: - Locals: | ||
| ; CHECK: - Type: DATA | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - SectionOffset: 7 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1024 | ||
| ; CHECK-NEXT: Content: '0100000002000000' | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 8 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: bar | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: call_bar_indirect | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: Name: foo | ||
| ; CHECK-NEXT: - Index: 3 | ||
| ; CHECK-NEXT: Name: _start |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| ; RUN: llc -filetype=obj %p/Inputs/hello.ll -o %t.hello.o | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm --emit-relocs --allow-undefined -o %t.wasm %t.o %t.hello.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| @foo = hidden global i32 1, align 4 | ||
| @aligned_bar = hidden global i32 3, align 16 | ||
|
|
||
| @hello_str = external global i8* | ||
| @external_ref = global i8** @hello_str, align 8 | ||
|
|
||
| ; CHECK: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66608 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1024 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1040 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1048 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1052 | ||
|
|
||
| ; CHECK: - Type: DATA | ||
| ; CHECK-NEXT: Relocations: | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_I32 | ||
| ; CHECK-NEXT: Index: 4 | ||
| ; CHECK-NEXT: Offset: 0x0000001F | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - SectionOffset: 7 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1024 | ||
| ; CHECK-NEXT: Content: 0100000000000000000000000000000003000000000000001C040000 | ||
| ; CHECK-NEXT: - SectionOffset: 41 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1052 | ||
| ; CHECK-NEXT: Content: 68656C6C6F0A00 | ||
|
|
||
| ; CHECK: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 35 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| ; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm -o %t.wasm %t.o %t.ret32.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| ; Function Attrs: nounwind | ||
| define hidden void @_start() local_unnamed_addr #0 { | ||
| entry: | ||
| %call = tail call i32 @ret32(float 0.000000e+00) #2 | ||
| ret void | ||
| } | ||
|
|
||
| declare i32 @ret32(float) local_unnamed_addr #1 | ||
|
|
||
| ; CHECK: - Type: TYPE | ||
| ; CHECK: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - F32 | ||
| ; CHECK: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 1 ] | ||
| ; CHECK: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 43000000001081808080001A0B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41000B | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 0 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: _start | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: ret32 | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| ; RUN: llc -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm -o %t.wasm %t.ret32.o %t.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| ; Function Attrs: nounwind | ||
| define hidden void @_start() local_unnamed_addr #0 { | ||
| entry: | ||
| %call = tail call i32 @ret32(float 0.000000e+00) #2 | ||
| ret void | ||
| } | ||
|
|
||
| declare i32 @ret32(float) local_unnamed_addr #1 | ||
|
|
||
| ; CHECK: Sections: | ||
| ; CHECK: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - F32 | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 1 ] | ||
| ; CHECK: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK: - Locals: | ||
| ; CHECK: - Locals: | ||
| ; CHECK: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: ret32 | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: _start | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| if 'wasm' not in config.available_features: | ||
| config.unsupported = True | ||
|
|
||
| config.suffixes = ['.test', '.yaml', '.ll'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm -o %t.wasm %t.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| @foo = default global i32 1, align 4 | ||
| @bar = internal default global i32 3, align 4 | ||
|
|
||
| define internal i32 @baz() local_unnamed_addr { | ||
| entry: | ||
| ret i32 2 | ||
| } | ||
|
|
||
| define i32 @_start() local_unnamed_addr { | ||
| entry: | ||
| ret i32 1 | ||
| } | ||
|
|
||
| ; CHECK: --- !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 0 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000001 | ||
| ; CHECK-NEXT: Maximum: 0x00000001 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000002 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66576 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: memory | ||
| ; CHECK-NEXT: Kind: MEMORY | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: _start | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41020B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41010B | ||
| ; CHECK-NEXT: - Type: DATA | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - SectionOffset: 7 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1024 | ||
| ; CHECK-NEXT: Content: '0100000003000000' | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 8 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: baz | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: _start | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| ; RUN: llc -filetype=obj %p/Inputs/hello.ll -o %t.hello.o | ||
| ; RUN: llc -filetype=obj %s -o %t.o | ||
| ; RUN: lld -flavor wasm -r -o %t.wasm %t.hello.o %t.o | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown-wasm" | ||
|
|
||
| ; Function Attrs: nounwind | ||
| define hidden i32 @my_func() local_unnamed_addr { | ||
| entry: | ||
| %call = tail call i32 @foo_import() | ||
| ret i32 1 | ||
| } | ||
|
|
||
| declare i32 @foo_import() local_unnamed_addr | ||
| @data_import = external global i64 | ||
|
|
||
| @func_addr1 = hidden global i32()* @my_func, align 4 | ||
| @func_addr2 = hidden global i32()* @foo_import, align 4 | ||
| @data_addr1 = hidden global i64* @data_import, align 8 | ||
|
|
||
| ; CHECK: --- !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - I32 | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: IMPORT | ||
| ; CHECK-NEXT: Imports: | ||
| ; CHECK-NEXT: - Module: env | ||
| ; CHECK-NEXT: Field: puts | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: SigIndex: 1 | ||
| ; CHECK-NEXT: - Module: env | ||
| ; CHECK-NEXT: Field: foo_import | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: SigIndex: 2 | ||
| ; CHECK-NEXT: - Module: env | ||
| ; CHECK-NEXT: Field: data_import | ||
| ; CHECK-NEXT: Kind: GLOBAL | ||
| ; CHECK-NEXT: GlobalType: I32 | ||
| ; CHECK-NEXT: GlobalMutable: false | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 2 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000002 | ||
| ; CHECK-NEXT: Maximum: 0x00000002 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000001 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 0 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 8 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 12 | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: false | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 16 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: hello | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 2 | ||
| ; CHECK-NEXT: - Name: my_func | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 3 | ||
| ; CHECK-NEXT: - Type: ELEM | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 0 | ||
| ; CHECK-NEXT: Functions: [ 3, 1 ] | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Relocations: | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_SLEB | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: Offset: 0x00000004 | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_FUNCTION_INDEX_LEB | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: Offset: 0x0000000A | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_FUNCTION_INDEX_LEB | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: Offset: 0x00000013 | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 4180808080001080808080000B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 1081808080001A41010B | ||
| ; CHECK-NEXT: - Type: DATA | ||
| ; CHECK-NEXT: Relocations: | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_TABLE_INDEX_I32 | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: Offset: 0x00000012 | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_TABLE_INDEX_I32 | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: Offset: 0x0000001B | ||
| ; CHECK-NEXT: - Type: R_WEBASSEMBLY_MEMORY_ADDR_I32 | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: Offset: 0x00000024 | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - SectionOffset: 6 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 0 | ||
| ; CHECK-NEXT: Content: 68656C6C6F0A00 | ||
| ; CHECK-NEXT: - SectionOffset: 18 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 8 | ||
| ; CHECK-NEXT: Content: '00000000' | ||
| ; CHECK-NEXT: - SectionOffset: 27 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 12 | ||
| ; CHECK-NEXT: Content: '01000000' | ||
| ; CHECK-NEXT: - SectionOffset: 36 | ||
| ; CHECK-NEXT: MemoryIndex: 0 | ||
| ; CHECK-NEXT: Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 16 | ||
| ; CHECK-NEXT: Content: FFFFFFFF | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 20 | ||
| ; CHECK-NEXT: SegmentInfo: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: .rodata.hello_str | ||
| ; CHECK-NEXT: Alignment: 1 | ||
| ; CHECK-NEXT: Flags: 0 | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: .data.func_addr1 | ||
| ; CHECK-NEXT: Alignment: 4 | ||
| ; CHECK-NEXT: Flags: 0 | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: Name: .data.func_addr2 | ||
| ; CHECK-NEXT: Alignment: 4 | ||
| ; CHECK-NEXT: Flags: 0 | ||
| ; CHECK-NEXT: - Index: 3 | ||
| ; CHECK-NEXT: Name: .data.data_addr1 | ||
| ; CHECK-NEXT: Alignment: 8 | ||
| ; CHECK-NEXT: Flags: 0 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: puts | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: foo_import | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: Name: hello | ||
| ; CHECK-NEXT: - Index: 3 | ||
| ; CHECK-NEXT: Name: my_func | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| ; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s | ||
| ; RUN: llc -mtriple=wasm32-unknown-unknown-wasm -filetype=obj %S/Inputs/weak-alias.ll -o %t2.o | ||
| ; RUN: lld -flavor wasm %t.o %t2.o -o %t.wasm | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| ; Test that the strongly defined bar is used correctly despite the existence | ||
| ; of the weak alias | ||
|
|
||
| define i32 @bar() local_unnamed_addr #1 { | ||
| ret i32 1 | ||
| } | ||
|
|
||
| ; Function Attrs: nounwind uwtable | ||
| define void @_start() local_unnamed_addr #1 { | ||
| entry: | ||
| %call = tail call i32 @bar() #2 | ||
| ret void | ||
| } | ||
|
|
||
| ; CHECK: --- !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: ReturnType: NORESULT | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 1, 0, 0 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000001 | ||
| ; CHECK-NEXT: Maximum: 0x00000001 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000002 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66560 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: memory | ||
| ; CHECK-NEXT: Kind: MEMORY | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: bar | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: _start | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Name: foo | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 2 | ||
| ; CHECK-NEXT: - Name: call_bar | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 3 | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41010B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 1080808080001A0B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41000B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 1080808080000B | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 0 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: bar | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: _start | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: Name: foo | ||
| ; CHECK-NEXT: - Index: 3 | ||
| ; CHECK-NEXT: Name: call_bar | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| ; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s | ||
| ; RUN: llc -mtriple=wasm32-unknown-unknown-wasm -filetype=obj %S/Inputs/weak-alias.ll -o %t2.o | ||
| ; RUN: lld -flavor wasm %t.o %t2.o -o %t.wasm | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| ; Test that weak aliases (bar is a weak alias of foo) are linked correctly | ||
|
|
||
| declare i32 @bar() local_unnamed_addr #1 | ||
|
|
||
| ; Function Attrs: nounwind uwtable | ||
| define i32 @_start() local_unnamed_addr #1 { | ||
| entry: | ||
| %call = tail call i32 @bar() #2 | ||
| ret i32 %call | ||
| } | ||
|
|
||
| ; CHECK: --- !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 0, 0 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000001 | ||
| ; CHECK-NEXT: Maximum: 0x00000001 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000002 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66560 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: memory | ||
| ; CHECK-NEXT: Kind: MEMORY | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: bar | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Name: _start | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: foo | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Name: call_bar | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 2 | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 1081808080000B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41000B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 1081808080000B | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 0 | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: name | ||
| ; CHECK-NEXT: FunctionNames: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: Name: _start | ||
| ; CHECK-NEXT: - Index: 1 | ||
| ; CHECK-NEXT: Name: foo | ||
| ; CHECK-NEXT: - Index: 2 | ||
| ; CHECK-NEXT: Name: call_bar | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| ; RUN: llc -mtriple wasm32-unknown-unknown-wasm -filetype=obj -o %t.o %s | ||
| ; RUN: lld -flavor wasm -strip-debug %t.o -o %t.wasm | ||
| ; RUN: obj2yaml %t.wasm | FileCheck %s | ||
|
|
||
| ; Test that undefined weak externals (global_var) and (foo) don't cause | ||
| ; link failures and resolve to zero. | ||
|
|
||
| @global_var = extern_weak global i32, align 4 | ||
|
|
||
| declare extern_weak i32 @foo() | ||
|
|
||
| define hidden i8* @get_address_of_foo() #0 { | ||
| entry: | ||
| ret i8* bitcast (i32 ()* @foo to i8*) | ||
| } | ||
|
|
||
| define hidden i32* @get_address_of_global_var() #0 { | ||
| ret i32* @global_var | ||
| } | ||
|
|
||
| define hidden i32 @_start() #0 { | ||
| entry: | ||
| %0 = load i32, i32* @global_var, align 4 | ||
| ret i32 %0 | ||
| } | ||
|
|
||
| ; CHECK: --- !WASM | ||
| ; CHECK-NEXT: FileHeader: | ||
| ; CHECK-NEXT: Version: 0x00000001 | ||
| ; CHECK-NEXT: Sections: | ||
| ; CHECK-NEXT: - Type: TYPE | ||
| ; CHECK-NEXT: Signatures: | ||
| ; CHECK-NEXT: - Index: 0 | ||
| ; CHECK-NEXT: ReturnType: I32 | ||
| ; CHECK-NEXT: ParamTypes: | ||
| ; CHECK-NEXT: - Type: FUNCTION | ||
| ; CHECK-NEXT: FunctionTypes: [ 0, 0, 0 ] | ||
| ; CHECK-NEXT: - Type: TABLE | ||
| ; CHECK-NEXT: Tables: | ||
| ; CHECK-NEXT: - ElemType: ANYFUNC | ||
| ; CHECK-NEXT: Limits: | ||
| ; CHECK-NEXT: Flags: 0x00000001 | ||
| ; CHECK-NEXT: Initial: 0x00000002 | ||
| ; CHECK-NEXT: Maximum: 0x00000002 | ||
| ; CHECK-NEXT: - Type: MEMORY | ||
| ; CHECK-NEXT: Memories: | ||
| ; CHECK-NEXT: - Initial: 0x00000002 | ||
| ; CHECK-NEXT: - Type: GLOBAL | ||
| ; CHECK-NEXT: Globals: | ||
| ; CHECK-NEXT: - Type: I32 | ||
| ; CHECK-NEXT: Mutable: true | ||
| ; CHECK-NEXT: InitExpr: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 66560 | ||
| ; CHECK-NEXT: - Type: EXPORT | ||
| ; CHECK-NEXT: Exports: | ||
| ; CHECK-NEXT: - Name: memory | ||
| ; CHECK-NEXT: Kind: MEMORY | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: get_address_of_foo | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 0 | ||
| ; CHECK-NEXT: - Name: get_address_of_global_var | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 1 | ||
| ; CHECK-NEXT: - Name: _start | ||
| ; CHECK-NEXT: Kind: FUNCTION | ||
| ; CHECK-NEXT: Index: 2 | ||
| ; CHECK-NEXT: - Type: ELEM | ||
| ; CHECK-NEXT: Segments: | ||
| ; CHECK-NEXT: - Offset: | ||
| ; CHECK-NEXT: Opcode: I32_CONST | ||
| ; CHECK-NEXT: Value: 1 | ||
| ; CHECK-NEXT: Functions: [ 0 ] | ||
| ; CHECK-NEXT: - Type: CODE | ||
| ; CHECK-NEXT: Functions: | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 4181808080000B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41FFFFFFFF7F0B | ||
| ; CHECK-NEXT: - Locals: | ||
| ; CHECK-NEXT: Body: 41002802FFFFFFFF0F0B | ||
| ; CHECK-NEXT: - Type: CUSTOM | ||
| ; CHECK-NEXT: Name: linking | ||
| ; CHECK-NEXT: DataSize: 0 | ||
| ; CHECK-NEXT: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| set(LLVM_TARGET_DEFINITIONS Options.td) | ||
| tablegen(LLVM Options.inc -gen-opt-parser-defs) | ||
| add_public_tablegen_target(WasmOptionsTableGen) | ||
|
|
||
| add_lld_library(lldWasm | ||
| Driver.cpp | ||
| InputFiles.cpp | ||
| InputSegment.cpp | ||
| OutputSections.cpp | ||
| Strings.cpp | ||
| SymbolTable.cpp | ||
| Symbols.cpp | ||
| Writer.cpp | ||
| WriterUtils.cpp | ||
|
|
||
| LINK_COMPONENTS | ||
| ${LLVM_TARGETS_TO_BUILD} | ||
| BinaryFormat | ||
| Core | ||
| Demangle | ||
| Object | ||
| Option | ||
| Support | ||
|
|
||
| LINK_LIBS | ||
| lldCommon | ||
| lldCore | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| //===- Config.h -------------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLD_WASM_CONFIG_H | ||
| #define LLD_WASM_CONFIG_H | ||
|
|
||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/BinaryFormat/Wasm.h" | ||
|
|
||
| #include "Symbols.h" | ||
|
|
||
| using llvm::wasm::WasmGlobal; | ||
|
|
||
| #include <set> | ||
|
|
||
| namespace lld { | ||
| namespace wasm { | ||
|
|
||
| struct Configuration { | ||
| bool AllowUndefined = false; | ||
| bool Demangle = true; | ||
| bool EmitRelocs = false; | ||
| bool ImportMemory = false; | ||
| bool Relocatable = false; | ||
| bool StripDebug = false; | ||
| bool StripAll = false; | ||
| uint32_t ZStackSize = 0; | ||
| uint32_t MaxMemory = 0; | ||
| uint32_t GlobalBase = 0; | ||
| uint32_t InitialMemory = 0; | ||
| llvm::StringRef Entry; | ||
| llvm::StringRef Sysroot; | ||
| llvm::StringRef OutputFile; | ||
|
|
||
| std::vector<llvm::StringRef> SearchPaths; | ||
| std::set<llvm::StringRef> AllowUndefinedSymbols; | ||
| std::vector<std::pair<Symbol *, WasmGlobal>> SyntheticGlobals; | ||
| }; | ||
|
|
||
| // The only instance of Configuration struct. | ||
| extern Configuration *Config; | ||
|
|
||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,357 @@ | ||
| //===- Driver.cpp ---------------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "lld/Common/Driver.h" | ||
| #include "Config.h" | ||
| #include "Memory.h" | ||
| #include "SymbolTable.h" | ||
| #include "Writer.h" | ||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "lld/Common/Threads.h" | ||
| #include "lld/Common/Version.h" | ||
| #include "llvm/ADT/Twine.h" | ||
| #include "llvm/Object/Wasm.h" | ||
| #include "llvm/Option/ArgList.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include "llvm/Support/Path.h" | ||
| #include "llvm/Support/Process.h" | ||
|
|
||
| using namespace llvm; | ||
| using namespace llvm::sys; | ||
| using namespace llvm::wasm; | ||
| using llvm::sys::Process; | ||
|
|
||
| using namespace lld; | ||
| using namespace lld::wasm; | ||
|
|
||
| namespace { | ||
|
|
||
| // Parses command line options. | ||
| class WasmOptTable : public llvm::opt::OptTable { | ||
| public: | ||
| WasmOptTable(); | ||
| llvm::opt::InputArgList parse(ArrayRef<const char *> 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<const char *> ArgsArr); | ||
|
|
||
| private: | ||
| void createFiles(llvm::opt::InputArgList &Args); | ||
| void addFile(StringRef Path); | ||
| void addLibrary(StringRef Name); | ||
| std::vector<InputFile *> Files; | ||
| }; | ||
|
|
||
| } // anonymous namespace | ||
|
|
||
| std::vector<SpecificAllocBase *> lld::wasm::SpecificAllocBase::Instances; | ||
| Configuration *lld::wasm::Config; | ||
| BumpPtrAllocator lld::wasm::BAlloc; | ||
|
|
||
| bool lld::wasm::link(ArrayRef<const char *> Args, bool CanExitEarly, | ||
| raw_ostream &Error) { | ||
| errorHandler().LogName = Args[0]; | ||
| errorHandler().ErrorOS = &Error; | ||
| errorHandler().ColorDiagnostics = Error.has_colors(); | ||
| errorHandler().ErrorLimitExceededMsg = | ||
| "too many errors emitted, stopping now (use " | ||
| "-error-limit=0 to see all errors)"; | ||
|
|
||
| Config = make<Configuration>(); | ||
| Symtab = make<SymbolTable>(); | ||
|
|
||
| LinkerDriver().link(Args); | ||
|
|
||
| // Exit immediately if we don't need to return to the caller. | ||
| // This saves time because the overhead of calling destructors | ||
| // for all globally-allocated objects is not negligible. | ||
| if (CanExitEarly) | ||
| exitLld(errorCount() ? 1 : 0); | ||
|
|
||
| freeArena(); | ||
| return !errorCount(); | ||
| } | ||
|
|
||
| // Create OptTable | ||
|
|
||
| // Create prefix string literals used in Options.td | ||
| #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; | ||
| #include "Options.inc" | ||
| #undef PREFIX | ||
|
|
||
| // Create table mapping all options defined in Options.td | ||
| static 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<StringRef> getArgs(opt::InputArgList &Args, int Id) { | ||
| std::vector<StringRef> 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<StringRef> getLines(MemoryBufferRef MB) { | ||
| SmallVector<StringRef, 0> Arr; | ||
| MB.getBuffer().split(Arr, '\n'); | ||
|
|
||
| std::vector<StringRef> Ret; | ||
| for (StringRef S : Arr) { | ||
| S = S.trim(); | ||
| if (!S.empty() && S[0] != '#') | ||
| Ret.push_back(S); | ||
| } | ||
| return Ret; | ||
| } | ||
|
|
||
| // Set color diagnostics according to -color-diagnostics={auto,always,never} | ||
| // or -no-color-diagnostics flags. | ||
| static void handleColorDiagnostics(opt::InputArgList &Args) { | ||
| auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, | ||
| OPT_no_color_diagnostics); | ||
| if (!Arg) | ||
| return; | ||
|
|
||
| if (Arg->getOption().getID() == OPT_color_diagnostics) | ||
| errorHandler().ColorDiagnostics = true; | ||
| else if (Arg->getOption().getID() == OPT_no_color_diagnostics) | ||
| errorHandler().ColorDiagnostics = false; | ||
| else { | ||
| StringRef S = Arg->getValue(); | ||
| if (S == "always") | ||
| errorHandler().ColorDiagnostics = true; | ||
| if (S == "never") | ||
| errorHandler().ColorDiagnostics = false; | ||
| if (S != "auto") | ||
| error("unknown option: -color-diagnostics=" + S); | ||
| } | ||
| } | ||
|
|
||
| // Find a file by concatenating given paths. | ||
| static Optional<std::string> findFile(StringRef Path1, const Twine &Path2) { | ||
| SmallString<128> S; | ||
| path::append(S, Path1, Path2); | ||
| if (fs::exists(S)) | ||
| return S.str().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<const char *> Argv) { | ||
| SmallVector<const char *, 256> Vec(Argv.data(), Argv.data() + Argv.size()); | ||
|
|
||
| unsigned MissingIndex; | ||
| unsigned MissingCount; | ||
| opt::InputArgList Args = this->ParseArgs(Vec, MissingIndex, MissingCount); | ||
|
|
||
| handleColorDiagnostics(Args); | ||
| for (auto *Arg : Args.filtered(OPT_UNKNOWN)) | ||
| error("unknown argument: " + Arg->getSpelling()); | ||
| return Args; | ||
| } | ||
|
|
||
| void LinkerDriver::addFile(StringRef Path) { | ||
| Optional<MemoryBufferRef> Buffer = readFile(Path); | ||
| if (!Buffer.hasValue()) | ||
| return; | ||
| MemoryBufferRef MBRef = *Buffer; | ||
|
|
||
| if (identify_magic(MBRef.getBuffer()) == file_magic::archive) | ||
| Files.push_back(make<ArchiveFile>(MBRef)); | ||
| else | ||
| Files.push_back(make<ObjFile>(MBRef)); | ||
| } | ||
|
|
||
| // Add a given library by searching it from input search paths. | ||
| void LinkerDriver::addLibrary(StringRef Name) { | ||
| for (StringRef Dir : Config->SearchPaths) { | ||
| if (Optional<std::string> 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<const char *> 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<const char *> V; | ||
| V.push_back("lld-link (LLVM option parsing)"); | ||
| for (auto *Arg : Args.filtered(OPT_mllvm)) | ||
| V.push_back(Arg->getValue()); | ||
| cl::ParseCommandLineOptions(V.size(), V.data()); | ||
|
|
||
| errorHandler().ErrorLimit = getInteger(Args, OPT_error_limit, 20); | ||
|
|
||
| if (Args.hasArg(OPT_version) || Args.hasArg(OPT_v)) { | ||
| outs() << getLLDVersion() << "\n"; | ||
| return; | ||
| } | ||
|
|
||
| Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); | ||
| Config->EmitRelocs = Args.hasArg(OPT_emit_relocs); | ||
| Config->Entry = Args.getLastArgValue(OPT_entry); | ||
| Config->ImportMemory = Args.hasArg(OPT_import_memory); | ||
| Config->OutputFile = Args.getLastArgValue(OPT_o); | ||
| Config->Relocatable = Args.hasArg(OPT_relocatable); | ||
| Config->SearchPaths = getArgs(Args, OPT_L); | ||
| Config->StripAll = Args.hasArg(OPT_strip_all); | ||
| Config->StripDebug = Args.hasArg(OPT_strip_debug); | ||
| Config->Sysroot = Args.getLastArgValue(OPT_sysroot); | ||
| errorHandler().Verbose = Args.hasArg(OPT_verbose); | ||
| ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true); | ||
|
|
||
| Config->InitialMemory = getInteger(Args, OPT_initial_memory, 0); | ||
| Config->GlobalBase = getInteger(Args, OPT_global_base, 1024); | ||
| Config->MaxMemory = getInteger(Args, OPT_max_memory, 0); | ||
| Config->ZStackSize = getZOptionValue(Args, "stack-size", WasmPageSize); | ||
|
|
||
| if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file)) | ||
| if (Optional<MemoryBufferRef> Buf = readFile(Arg->getValue())) | ||
| for (StringRef Sym : getLines(*Buf)) | ||
| Config->AllowUndefinedSymbols.insert(Sym); | ||
|
|
||
| if (Config->OutputFile.empty()) | ||
| error("no output file specified"); | ||
|
|
||
| if (!Args.hasArg(OPT_INPUT)) | ||
| error("no input files"); | ||
|
|
||
| if (Config->Relocatable && !Config->Entry.empty()) | ||
| error("entry point specified for relocatable output file"); | ||
|
|
||
| if (!Config->Relocatable) { | ||
| if (Config->Entry.empty()) | ||
| Config->Entry = "_start"; | ||
| addSyntheticUndefinedFunction(Config->Entry); | ||
|
|
||
| addSyntheticGlobal("__stack_pointer", 0); | ||
| } | ||
|
|
||
| createFiles(Args); | ||
| if (errorCount()) | ||
| return; | ||
|
|
||
| // Add all files to the symbol table. This will add almost all | ||
| // symbols that we need to the symbol table. | ||
| for (InputFile *F : Files) | ||
| Symtab->addFile(F); | ||
|
|
||
| // Make sure we have resolved all symbols. | ||
| if (!Config->Relocatable && !Config->AllowUndefined) { | ||
| Symtab->reportRemainingUndefines(); | ||
| if (errorCount()) | ||
| return; | ||
| } | ||
|
|
||
| if (!Config->Entry.empty()) { | ||
| Symbol *Sym = Symtab->find(Config->Entry); | ||
| if (!Sym->isFunction()) | ||
| fatal("entry point is not a function: " + Sym->getName()); | ||
| } | ||
|
|
||
| // Write the result to the file. | ||
| writeResult(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,275 @@ | ||
| //===- InputFiles.cpp -----------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InputFiles.h" | ||
|
|
||
| #include "Config.h" | ||
| #include "InputSegment.h" | ||
| #include "Memory.h" | ||
| #include "Strings.h" | ||
| #include "SymbolTable.h" | ||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "llvm/Object/Binary.h" | ||
| #include "llvm/Object/Wasm.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
|
|
||
| #define DEBUG_TYPE "lld" | ||
|
|
||
| using namespace lld; | ||
| using namespace lld::wasm; | ||
|
|
||
| using namespace llvm; | ||
| using namespace llvm::object; | ||
| using namespace llvm::wasm; | ||
|
|
||
| Optional<MemoryBufferRef> lld::wasm::readFile(StringRef Path) { | ||
| log("Loading: " + Path); | ||
|
|
||
| auto MBOrErr = MemoryBuffer::getFile(Path); | ||
| if (auto EC = MBOrErr.getError()) { | ||
| error("cannot open " + Path + ": " + EC.message()); | ||
| return None; | ||
| } | ||
| std::unique_ptr<MemoryBuffer> &MB = *MBOrErr; | ||
| MemoryBufferRef MBRef = MB->getMemBufferRef(); | ||
| make<std::unique_ptr<MemoryBuffer>>(std::move(MB)); // take MB ownership | ||
|
|
||
| return MBRef; | ||
| } | ||
|
|
||
| void ObjFile::dumpInfo() const { | ||
| log("reloc info for: " + getName() + "\n" + | ||
| " FunctionIndexOffset : " + Twine(FunctionIndexOffset) + "\n" + | ||
| " NumFunctionImports : " + Twine(NumFunctionImports()) + "\n" + | ||
| " TableIndexOffset : " + Twine(TableIndexOffset) + "\n" + | ||
| " GlobalIndexOffset : " + Twine(GlobalIndexOffset) + "\n" + | ||
| " NumGlobalImports : " + Twine(NumGlobalImports()) + "\n"); | ||
| } | ||
|
|
||
| bool ObjFile::isImportedFunction(uint32_t Index) const { | ||
| return Index < NumFunctionImports(); | ||
| } | ||
|
|
||
| const Symbol *ObjFile::getFunctionSymbol(uint32_t Index) const { | ||
| return FunctionSymbols[Index]; | ||
| } | ||
|
|
||
| const Symbol *ObjFile::getGlobalSymbol(uint32_t Index) const { | ||
| return GlobalSymbols[Index]; | ||
| } | ||
|
|
||
| uint32_t ObjFile::getRelocatedAddress(uint32_t Index) const { | ||
| return getGlobalSymbol(Index)->getVirtualAddress(); | ||
| } | ||
|
|
||
| uint32_t ObjFile::relocateFunctionIndex(uint32_t Original) const { | ||
| DEBUG(dbgs() << "relocateFunctionIndex: " << Original); | ||
| const Symbol *Sym = getFunctionSymbol(Original); | ||
| uint32_t Index; | ||
| if (Sym) | ||
| Index = Sym->getOutputIndex(); | ||
| else | ||
| Index = Original + FunctionIndexOffset; | ||
|
|
||
| DEBUG(dbgs() << " -> " << Index << "\n"); | ||
| return Index; | ||
| } | ||
|
|
||
| uint32_t ObjFile::relocateTypeIndex(uint32_t Original) const { | ||
| return TypeMap[Original]; | ||
| } | ||
|
|
||
| uint32_t ObjFile::relocateTableIndex(uint32_t Original) const { | ||
| return Original + TableIndexOffset; | ||
| } | ||
|
|
||
| uint32_t ObjFile::relocateGlobalIndex(uint32_t Original) const { | ||
| DEBUG(dbgs() << "relocateGlobalIndex: " << Original); | ||
| uint32_t Index; | ||
| const Symbol *Sym = getGlobalSymbol(Original); | ||
| if (Sym) | ||
| Index = Sym->getOutputIndex(); | ||
| else | ||
| Index = Original + GlobalIndexOffset; | ||
|
|
||
| DEBUG(dbgs() << " -> " << Index << "\n"); | ||
| return Index; | ||
| } | ||
|
|
||
| void ObjFile::parse() { | ||
| // Parse a memory buffer as a wasm file. | ||
| DEBUG(dbgs() << "Parsing object: " << toString(this) << "\n"); | ||
| std::unique_ptr<Binary> Bin = check(createBinary(MB), toString(this)); | ||
|
|
||
| auto *Obj = dyn_cast<WasmObjectFile>(Bin.get()); | ||
| if (!Obj) | ||
| fatal(toString(this) + ": not a wasm file"); | ||
| if (!Obj->isRelocatableObject()) | ||
| fatal(toString(this) + ": not a relocatable wasm file"); | ||
|
|
||
| Bin.release(); | ||
| WasmObj.reset(Obj); | ||
|
|
||
| // Find the code and data sections. Wasm objects can have at most one code | ||
| // and one data section. | ||
| for (const SectionRef &Sec : WasmObj->sections()) { | ||
| const WasmSection &Section = WasmObj->getWasmSection(Sec); | ||
| if (Section.Type == WASM_SEC_CODE) | ||
| CodeSection = &Section; | ||
| else if (Section.Type == WASM_SEC_DATA) | ||
| DataSection = &Section; | ||
| } | ||
|
|
||
| initializeSymbols(); | ||
| } | ||
|
|
||
| // Return the InputSegment in which a given symbol is defined. | ||
| InputSegment *ObjFile::getSegment(const WasmSymbol &WasmSym) { | ||
| uint32_t Address = WasmObj->getWasmSymbolValue(WasmSym); | ||
| for (InputSegment *Segment : Segments) { | ||
| if (Address >= Segment->startVA() && Address < Segment->endVA()) { | ||
| DEBUG(dbgs() << "Found symbol in segment: " << WasmSym.Name << " -> " | ||
| << Segment->getName() << "\n"); | ||
|
|
||
| return Segment; | ||
| } | ||
| } | ||
| error("Symbol not found in any segment: " + WasmSym.Name); | ||
| return nullptr; | ||
| } | ||
|
|
||
| void ObjFile::initializeSymbols() { | ||
| Symbols.reserve(WasmObj->getNumberOfSymbols()); | ||
|
|
||
| for (const WasmImport &Import : WasmObj->imports()) { | ||
| switch (Import.Kind) { | ||
| case WASM_EXTERNAL_FUNCTION: | ||
| ++FunctionImports; | ||
| break; | ||
| case WASM_EXTERNAL_GLOBAL: | ||
| ++GlobalImports; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| FunctionSymbols.resize(FunctionImports + WasmObj->functions().size()); | ||
| GlobalSymbols.resize(GlobalImports + WasmObj->globals().size()); | ||
|
|
||
| for (const WasmSegment &Seg : WasmObj->dataSegments()) | ||
| Segments.emplace_back(make<InputSegment>(&Seg, this)); | ||
|
|
||
| Symbol *S; | ||
| for (const SymbolRef &Sym : WasmObj->symbols()) { | ||
| const WasmSymbol &WasmSym = WasmObj->getWasmSymbol(Sym.getRawDataRefImpl()); | ||
| switch (WasmSym.Type) { | ||
| case WasmSymbol::SymbolType::FUNCTION_IMPORT: | ||
| case WasmSymbol::SymbolType::GLOBAL_IMPORT: | ||
| S = createUndefined(WasmSym); | ||
| break; | ||
| case WasmSymbol::SymbolType::GLOBAL_EXPORT: | ||
| S = createDefined(WasmSym, getSegment(WasmSym)); | ||
| break; | ||
| case WasmSymbol::SymbolType::FUNCTION_EXPORT: | ||
| S = createDefined(WasmSym); | ||
| break; | ||
| case WasmSymbol::SymbolType::DEBUG_FUNCTION_NAME: | ||
| // These are for debugging only, no need to create linker symbols for them | ||
| continue; | ||
| } | ||
|
|
||
| Symbols.push_back(S); | ||
| if (WasmSym.isFunction()) { | ||
| DEBUG(dbgs() << "Function: " << WasmSym.ElementIndex << " -> " | ||
| << toString(*S) << "\n"); | ||
| FunctionSymbols[WasmSym.ElementIndex] = S; | ||
| } else { | ||
| DEBUG(dbgs() << "Global: " << WasmSym.ElementIndex << " -> " | ||
| << toString(*S) << "\n"); | ||
| GlobalSymbols[WasmSym.ElementIndex] = S; | ||
| } | ||
| } | ||
|
|
||
| DEBUG(dbgs() << "Functions: " << FunctionSymbols.size() << "\n"); | ||
| DEBUG(dbgs() << "Globals : " << GlobalSymbols.size() << "\n"); | ||
| } | ||
|
|
||
| Symbol *ObjFile::createUndefined(const WasmSymbol &Sym) { | ||
| return Symtab->addUndefined(this, &Sym); | ||
| } | ||
|
|
||
| Symbol *ObjFile::createDefined(const WasmSymbol &Sym, | ||
| const InputSegment *Segment) { | ||
| Symbol *S; | ||
| if (Sym.isLocal()) { | ||
| S = make<Symbol>(Sym.Name, true); | ||
| Symbol::Kind Kind; | ||
| if (Sym.Type == WasmSymbol::SymbolType::FUNCTION_EXPORT) | ||
| Kind = Symbol::Kind::DefinedFunctionKind; | ||
| else if (Sym.Type == WasmSymbol::SymbolType::GLOBAL_EXPORT) | ||
| Kind = Symbol::Kind::DefinedGlobalKind; | ||
| else | ||
| llvm_unreachable("invalid local symbol type"); | ||
| S->update(Kind, this, &Sym, Segment); | ||
| return S; | ||
| } | ||
| return Symtab->addDefined(this, &Sym, Segment); | ||
| } | ||
|
|
||
| void ArchiveFile::parse() { | ||
| // Parse a MemoryBufferRef as an archive file. | ||
| DEBUG(dbgs() << "Parsing library: " << toString(this) << "\n"); | ||
| File = check(Archive::create(MB), toString(this)); | ||
|
|
||
| // Read the symbol table to construct Lazy symbols. | ||
| int Count = 0; | ||
| for (const Archive::Symbol &Sym : File->symbols()) { | ||
| Symtab->addLazy(this, &Sym); | ||
| ++Count; | ||
| } | ||
| DEBUG(dbgs() << "Read " << Count << " symbols\n"); | ||
| } | ||
|
|
||
| void ArchiveFile::addMember(const Archive::Symbol *Sym) { | ||
| const Archive::Child &C = | ||
| check(Sym->getMember(), | ||
| "could not get the member for symbol " + Sym->getName()); | ||
|
|
||
| // Don't try to load the same member twice (this can happen when members | ||
| // mutually reference each other). | ||
| if (!Seen.insert(C.getChildOffset()).second) | ||
| return; | ||
|
|
||
| DEBUG(dbgs() << "loading lazy: " << displayName(Sym->getName()) << "\n"); | ||
| DEBUG(dbgs() << "from archive: " << toString(this) << "\n"); | ||
|
|
||
| MemoryBufferRef MB = | ||
| check(C.getMemoryBufferRef(), | ||
| "could not get the buffer for the member defining symbol " + | ||
| Sym->getName()); | ||
|
|
||
| if (identify_magic(MB.getBuffer()) != file_magic::wasm_object) { | ||
| error("unknown file type: " + MB.getBufferIdentifier()); | ||
| return; | ||
| } | ||
|
|
||
| InputFile *Obj = make<ObjFile>(MB); | ||
| Obj->ParentName = ParentName; | ||
| Symtab->addFile(Obj); | ||
| } | ||
|
|
||
| // Returns a string in the format of "foo.o" or "foo.a(bar.o)". | ||
| std::string lld::toString(wasm::InputFile *File) { | ||
| if (!File) | ||
| return "<internal>"; | ||
|
|
||
| if (File->ParentName.empty()) | ||
| return File->getName(); | ||
|
|
||
| return (File->ParentName + "(" + File->getName() + ")").str(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| //===- InputFiles.h ---------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLD_WASM_INPUT_FILES_H | ||
| #define LLD_WASM_INPUT_FILES_H | ||
|
|
||
| #include "lld/Common/LLVM.h" | ||
| #include "llvm/ADT/DenseMap.h" | ||
| #include "llvm/ADT/DenseSet.h" | ||
| #include "llvm/Object/Archive.h" | ||
| #include "llvm/Object/Wasm.h" | ||
| #include "llvm/Support/MemoryBuffer.h" | ||
|
|
||
| #include "WriterUtils.h" | ||
|
|
||
| #include <vector> | ||
|
|
||
| using llvm::object::WasmObjectFile; | ||
| using llvm::object::WasmSection; | ||
| using llvm::object::WasmSymbol; | ||
| using llvm::object::Archive; | ||
| using llvm::wasm::WasmImport; | ||
|
|
||
| namespace lld { | ||
| namespace wasm { | ||
|
|
||
| class Symbol; | ||
| class InputSegment; | ||
|
|
||
| class InputFile { | ||
| public: | ||
| enum Kind { | ||
| ObjectKind, | ||
| ArchiveKind, | ||
| }; | ||
|
|
||
| virtual ~InputFile() {} | ||
|
|
||
| // Returns the filename. | ||
| StringRef getName() const { return MB.getBufferIdentifier(); } | ||
|
|
||
| // Reads a file (the constructor doesn't do that). | ||
| virtual void parse() = 0; | ||
|
|
||
| Kind kind() const { return FileKind; } | ||
|
|
||
| // An archive file name if this file is created from an archive. | ||
| StringRef ParentName; | ||
|
|
||
| protected: | ||
| InputFile(Kind K, MemoryBufferRef M) : MB(M), FileKind(K) {} | ||
| MemoryBufferRef MB; | ||
|
|
||
| private: | ||
| const Kind FileKind; | ||
| }; | ||
|
|
||
| // .a file (ar archive) | ||
| class ArchiveFile : public InputFile { | ||
| public: | ||
| explicit ArchiveFile(MemoryBufferRef M) : InputFile(ArchiveKind, M) {} | ||
| static bool classof(const InputFile *F) { return F->kind() == ArchiveKind; } | ||
|
|
||
| void addMember(const Archive::Symbol *Sym); | ||
|
|
||
| void parse() override; | ||
|
|
||
| private: | ||
| std::unique_ptr<Archive> File; | ||
| llvm::DenseSet<uint64_t> Seen; | ||
| }; | ||
|
|
||
| // .o file (wasm object file) | ||
| class ObjFile : public InputFile { | ||
| public: | ||
| explicit ObjFile(MemoryBufferRef M) : InputFile(ObjectKind, M) {} | ||
| static bool classof(const InputFile *F) { return F->kind() == ObjectKind; } | ||
|
|
||
| void parse() override; | ||
|
|
||
| // Returns the underlying wasm file. | ||
| const WasmObjectFile *getWasmObj() const { return WasmObj.get(); } | ||
|
|
||
| void dumpInfo() const; | ||
|
|
||
| uint32_t relocateTypeIndex(uint32_t Original) const; | ||
| uint32_t relocateFunctionIndex(uint32_t Original) const; | ||
| uint32_t relocateGlobalIndex(uint32_t Original) const; | ||
| uint32_t relocateTableIndex(uint32_t Original) const; | ||
| uint32_t getRelocatedAddress(uint32_t Index) const; | ||
|
|
||
| // Returns true if the given function index is an imported function, | ||
| // as opposed to the locally defined function. | ||
| bool isImportedFunction(uint32_t Index) const; | ||
|
|
||
| size_t NumFunctionImports() const { return FunctionImports; } | ||
| size_t NumGlobalImports() const { return GlobalImports; } | ||
|
|
||
| int32_t FunctionIndexOffset = 0; | ||
| int32_t GlobalIndexOffset = 0; | ||
| int32_t TableIndexOffset = 0; | ||
| const WasmSection *CodeSection = nullptr; | ||
| std::vector<OutputRelocation> CodeRelocations; | ||
| int32_t CodeOffset = 0; | ||
| const WasmSection *DataSection = nullptr; | ||
|
|
||
| std::vector<uint32_t> TypeMap; | ||
| std::vector<InputSegment *> Segments; | ||
|
|
||
| const std::vector<Symbol *> &getSymbols() { return Symbols; } | ||
|
|
||
| private: | ||
| Symbol *createDefined(const WasmSymbol &Sym, | ||
| const InputSegment *Segment = nullptr); | ||
| Symbol *createUndefined(const WasmSymbol &Sym); | ||
| void initializeSymbols(); | ||
| InputSegment *getSegment(const WasmSymbol &WasmSym); | ||
| const Symbol *getFunctionSymbol(uint32_t Index) const; | ||
| const Symbol *getGlobalSymbol(uint32_t Index) const; | ||
|
|
||
| // List of all symbols referenced or defined by this file. | ||
| std::vector<Symbol *> Symbols; | ||
|
|
||
| // List of all function symbols indexed by the function index space | ||
| std::vector<const Symbol *> FunctionSymbols; | ||
|
|
||
| // List of all global symbols indexed by the global index space | ||
| std::vector<const Symbol *> GlobalSymbols; | ||
|
|
||
| uint32_t GlobalImports = 0; | ||
| uint32_t FunctionImports = 0; | ||
| std::unique_ptr<WasmObjectFile> WasmObj; | ||
| }; | ||
|
|
||
| // Opens a given file. | ||
| llvm::Optional<MemoryBufferRef> readFile(StringRef Path); | ||
|
|
||
| } // namespace wasm | ||
|
|
||
| std::string toString(wasm::InputFile *File); | ||
|
|
||
| } // namespace lld | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| //===- InputSegment.cpp ---------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InputSegment.h" | ||
| #include "OutputSegment.h" | ||
| #include "lld/Common/LLVM.h" | ||
|
|
||
| #define DEBUG_TYPE "lld" | ||
|
|
||
| using namespace llvm; | ||
| using namespace lld::wasm; | ||
|
|
||
| uint32_t InputSegment::translateVA(uint32_t Address) const { | ||
| assert(Address >= startVA() && Address < endVA()); | ||
| int32_t Delta = OutputSeg->StartVA + OutputSegmentOffset - startVA(); | ||
| DEBUG(dbgs() << "translateVA: " << getName() << " Delta=" << Delta | ||
| << " Address=" << Address << "\n"); | ||
| return Address + Delta; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| //===- InputSegment.h -------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // Represents a WebAssembly data segment which can be included as part of | ||
| // an output data segments. Note that in WebAssembly, unlike ELF and other | ||
| // formats, used the term "data segment" to refer to the continous regions of | ||
| // memory that make on the data section. See: | ||
| // https://webassembly.github.io/spec/syntax/modules.html#syntax-data | ||
| // | ||
| // For example, by default, clang will produce a separate data section for | ||
| // each global variable. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLD_WASM_INPUT_SEGMENT_H | ||
| #define LLD_WASM_INPUT_SEGMENT_H | ||
|
|
||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "llvm/Object/Wasm.h" | ||
|
|
||
| using llvm::object::WasmSegment; | ||
| using llvm::wasm::WasmRelocation; | ||
|
|
||
| namespace lld { | ||
| namespace wasm { | ||
|
|
||
| class ObjFile; | ||
| class OutputSegment; | ||
|
|
||
| class InputSegment { | ||
| public: | ||
| InputSegment(const WasmSegment *Seg, const ObjFile *F) | ||
| : Segment(Seg), File(F) {} | ||
|
|
||
| // Translate an offset in the input segment to an offset in the output | ||
| // segment. | ||
| uint32_t translateVA(uint32_t Address) const; | ||
|
|
||
| const OutputSegment *getOutputSegment() const { return OutputSeg; } | ||
|
|
||
| uint32_t getOutputSegmentOffset() const { return OutputSegmentOffset; } | ||
|
|
||
| uint32_t getInputSectionOffset() const { return Segment->SectionOffset; } | ||
|
|
||
| void setOutputSegment(const OutputSegment *Segment, uint32_t Offset) { | ||
| OutputSeg = Segment; | ||
| OutputSegmentOffset = Offset; | ||
| } | ||
|
|
||
| uint32_t getSize() const { return Segment->Data.Content.size(); } | ||
| uint32_t getAlignment() const { return Segment->Data.Alignment; } | ||
| uint32_t startVA() const { return Segment->Data.Offset.Value.Int32; } | ||
| uint32_t endVA() const { return startVA() + getSize(); } | ||
| StringRef getName() const { return Segment->Data.Name; } | ||
|
|
||
| const WasmSegment *Segment; | ||
| const ObjFile *File; | ||
| std::vector<WasmRelocation> Relocations; | ||
|
|
||
| protected: | ||
| const OutputSegment *OutputSeg = nullptr; | ||
| uint32_t OutputSegmentOffset = 0; | ||
| }; | ||
|
|
||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif // LLD_WASM_INPUT_SEGMENT_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <vector> | ||
|
|
||
| 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<SpecificAllocBase *> Instances; | ||
| }; | ||
|
|
||
| template <class T> struct SpecificAlloc : public SpecificAllocBase { | ||
| void reset() override { Alloc.DestroyAll(); } | ||
| llvm::SpecificBumpPtrAllocator<T> Alloc; | ||
| }; | ||
|
|
||
| template <typename T, typename... U> T *make(U &&... Args) { | ||
| static SpecificAlloc<T> Alloc; | ||
| return new (Alloc.Alloc.Allocate()) T(std::forward<U>(Args)...); | ||
| } | ||
|
|
||
| inline void freeArena() { | ||
| for (SpecificAllocBase *Alloc : SpecificAllocBase::Instances) | ||
| Alloc->reset(); | ||
| BAlloc.Reset(); | ||
| } | ||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| include "llvm/Option/OptParser.td" | ||
|
|
||
| // For options whose names are multiple letters, either one dash or | ||
| // two can precede the option name except those that start with 'o'. | ||
| class F<string name>: Flag<["--", "-"], name>; | ||
| class J<string name>: Joined<["--", "-"], name>; | ||
| class S<string name>: Separate<["--", "-"], name>; | ||
|
|
||
| def L: JoinedOrSeparate<["-"], "L">, MetaVarName<"<dir>">, | ||
| 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<"<libName>">, | ||
| 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<"<path>">, | ||
| HelpText<"Path to file to write output">; | ||
|
|
||
| def threads: F<"threads">, HelpText<"Run the linker multi-threaded">; | ||
|
|
||
| def v: Flag<["-"], "v">, HelpText<"Display the version number">; | ||
|
|
||
| def version: F<"version">, HelpText<"Display the version number and exit">; | ||
|
|
||
| def verbose: F<"verbose">, HelpText<"Verbose mode">; | ||
|
|
||
| def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">; | ||
|
|
||
| def emit_relocs: F<"emit-relocs">, HelpText<"Generate relocations in output">; | ||
|
|
||
| def strip_all: F<"strip-all">, HelpText<"Strip all symbols">; | ||
|
|
||
| def strip_debug: F<"strip-debug">, HelpText<"Strip debugging information">; | ||
|
|
||
| def sysroot: J<"sysroot=">, HelpText<"Set the system root">; | ||
|
|
||
| def z: JoinedOrSeparate<["-"], "z">, MetaVarName<"<option>">, | ||
| HelpText<"Linker option extensions">; | ||
|
|
||
| def entry: S<"entry">, MetaVarName<"<entry>">, | ||
| HelpText<"Name of entry point symbol">; | ||
|
|
||
| def error_limit: J<"error-limit=">, | ||
| HelpText<"Maximum number of errors to emit before stopping (0 = no limit)">; | ||
|
|
||
| // The follow flags are unique to wasm | ||
|
|
||
| def global_base: J<"global-base=">, | ||
| HelpText<"Where to start to place global data">; | ||
|
|
||
| def initial_memory: J<"initial-memory=">, | ||
| HelpText<"Initial size of the linear memory">; | ||
|
|
||
| def max_memory: J<"max-memory=">, | ||
| HelpText<"Maximum size of the linear memory">; | ||
|
|
||
| def import_memory: F<"import-memory">, | ||
| HelpText<"Import memory from the environment">; | ||
|
|
||
| def allow_undefined: F<"allow-undefined">, | ||
| HelpText<"Allow undefined symbols in linked binary">; | ||
|
|
||
| def allow_undefined_file: J<"allow-undefined-file=">, | ||
| HelpText<"Allow symbols listed in <file> to be undefined in linked binary">; | ||
|
|
||
| def allow_undefined_file_s: Separate<["-"], "allow-undefined-file">, Alias<allow_undefined_file>; | ||
|
|
||
| // Aliases | ||
| def alias_initial_memory_i: Flag<["-"], "i">, Alias<initial_memory>; | ||
| def alias_max_memory_m: Flag<["-"], "m">, Alias<max_memory>; | ||
| def alias_relocatable_r: Flag<["-"], "r">, Alias<relocatable>; | ||
| def alias_entry_e: JoinedOrSeparate<["-"], "e">, Alias<entry>; | ||
| def alias_entry_entry: J<"entry=">, Alias<entry>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,330 @@ | ||
| //===- OutputSections.cpp -------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "OutputSections.h" | ||
|
|
||
| #include "Config.h" | ||
| #include "InputFiles.h" | ||
| #include "Memory.h" | ||
| #include "OutputSegment.h" | ||
| #include "SymbolTable.h" | ||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "lld/Common/Threads.h" | ||
| #include "llvm/ADT/Twine.h" | ||
| #include "llvm/Support/LEB128.h" | ||
|
|
||
| #define DEBUG_TYPE "lld" | ||
|
|
||
| using namespace llvm; | ||
| using namespace llvm::wasm; | ||
| using namespace lld; | ||
| using namespace lld::wasm; | ||
|
|
||
| enum class RelocEncoding { | ||
| Uleb128, | ||
| Sleb128, | ||
| I32, | ||
| }; | ||
|
|
||
| static StringRef sectionTypeToString(uint32_t SectionType) { | ||
| switch (SectionType) { | ||
| case WASM_SEC_CUSTOM: | ||
| return "CUSTOM"; | ||
| case WASM_SEC_TYPE: | ||
| return "TYPE"; | ||
| case WASM_SEC_IMPORT: | ||
| return "IMPORT"; | ||
| case WASM_SEC_FUNCTION: | ||
| return "FUNCTION"; | ||
| case WASM_SEC_TABLE: | ||
| return "TABLE"; | ||
| case WASM_SEC_MEMORY: | ||
| return "MEMORY"; | ||
| case WASM_SEC_GLOBAL: | ||
| return "GLOBAL"; | ||
| case WASM_SEC_EXPORT: | ||
| return "EXPORT"; | ||
| case WASM_SEC_START: | ||
| return "START"; | ||
| case WASM_SEC_ELEM: | ||
| return "ELEM"; | ||
| case WASM_SEC_CODE: | ||
| return "CODE"; | ||
| case WASM_SEC_DATA: | ||
| return "DATA"; | ||
| default: | ||
| fatal("invalid section type"); | ||
| } | ||
| } | ||
|
|
||
| std::string lld::toString(OutputSection *Section) { | ||
| std::string rtn = sectionTypeToString(Section->Type); | ||
| if (!Section->Name.empty()) | ||
| rtn += "(" + Section->Name + ")"; | ||
| return rtn; | ||
| } | ||
|
|
||
| static void applyRelocation(uint8_t *Buf, const OutputRelocation &Reloc) { | ||
| DEBUG(dbgs() << "write reloc: type=" << Reloc.Reloc.Type | ||
| << " index=" << Reloc.Reloc.Index << " new=" << Reloc.NewIndex | ||
| << " value=" << Reloc.Value << " offset=" << Reloc.Reloc.Offset | ||
| << "\n"); | ||
| Buf += Reloc.Reloc.Offset; | ||
| int64_t ExistingValue; | ||
| switch (Reloc.Reloc.Type) { | ||
| case R_WEBASSEMBLY_TYPE_INDEX_LEB: | ||
| case R_WEBASSEMBLY_FUNCTION_INDEX_LEB: | ||
| ExistingValue = decodeULEB128(Buf); | ||
| if (ExistingValue != Reloc.Reloc.Index) { | ||
| DEBUG(dbgs() << "existing value: " << decodeULEB128(Buf) << "\n"); | ||
| assert(decodeULEB128(Buf) == Reloc.Reloc.Index); | ||
| } | ||
| LLVM_FALLTHROUGH; | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_LEB: | ||
| case R_WEBASSEMBLY_GLOBAL_INDEX_LEB: | ||
| encodeULEB128(Reloc.Value, Buf, 5); | ||
| break; | ||
| case R_WEBASSEMBLY_TABLE_INDEX_SLEB: | ||
| ExistingValue = decodeSLEB128(Buf); | ||
| if (ExistingValue != Reloc.Reloc.Index) { | ||
| DEBUG(dbgs() << "existing value: " << decodeSLEB128(Buf) << "\n"); | ||
| assert(decodeSLEB128(Buf) == Reloc.Reloc.Index); | ||
| } | ||
| LLVM_FALLTHROUGH; | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: | ||
| encodeSLEB128(static_cast<int32_t>(Reloc.Value), Buf, 5); | ||
| break; | ||
| case R_WEBASSEMBLY_TABLE_INDEX_I32: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_I32: | ||
| support::endian::write32<support::little>(Buf, Reloc.Value); | ||
| break; | ||
| default: | ||
| llvm_unreachable("unknown relocation type"); | ||
| } | ||
| } | ||
|
|
||
| static void applyRelocations(uint8_t *Buf, | ||
| const std::vector<OutputRelocation> &Relocs) { | ||
| log("applyRelocations: count=" + Twine(Relocs.size())); | ||
| for (const OutputRelocation &Reloc : Relocs) { | ||
| applyRelocation(Buf, Reloc); | ||
| } | ||
| } | ||
|
|
||
| // Relocations contain an index into the function, global or table index | ||
| // space of the input file. This function takes a relocation and returns the | ||
| // relocated index (i.e. translates from the input index space to the output | ||
| // index space). | ||
| static uint32_t calcNewIndex(const ObjFile &File, const WasmRelocation &Reloc) { | ||
| switch (Reloc.Type) { | ||
| case R_WEBASSEMBLY_TYPE_INDEX_LEB: | ||
| return File.relocateTypeIndex(Reloc.Index); | ||
| case R_WEBASSEMBLY_FUNCTION_INDEX_LEB: | ||
| return File.relocateFunctionIndex(Reloc.Index); | ||
| case R_WEBASSEMBLY_TABLE_INDEX_I32: | ||
| case R_WEBASSEMBLY_TABLE_INDEX_SLEB: | ||
| return File.relocateTableIndex(Reloc.Index); | ||
| case R_WEBASSEMBLY_GLOBAL_INDEX_LEB: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_LEB: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_I32: | ||
| return File.relocateGlobalIndex(Reloc.Index); | ||
| default: | ||
| llvm_unreachable("unknown relocation type"); | ||
| } | ||
| } | ||
|
|
||
| // Take a vector of relocations from an input file and create output | ||
| // relocations based on them. Calculates the updated index and offset for | ||
| // each relocation as well as the value to write out in the final binary. | ||
| static void calcRelocations(const ObjFile &File, | ||
| ArrayRef<WasmRelocation> Relocs, | ||
| std::vector<OutputRelocation> &OutputRelocs, | ||
| int32_t OutputOffset) { | ||
| log("calcRelocations: " + File.getName() + " offset=" + Twine(OutputOffset)); | ||
| for (const WasmRelocation &Reloc : Relocs) { | ||
| int64_t NewIndex = calcNewIndex(File, Reloc); | ||
| OutputRelocation NewReloc; | ||
| NewReloc.Reloc = Reloc; | ||
| NewReloc.Reloc.Offset += OutputOffset; | ||
| NewReloc.NewIndex = NewIndex; | ||
| DEBUG(dbgs() << "reloc: type=" << Reloc.Type << " index=" << Reloc.Index | ||
| << " offset=" << Reloc.Offset << " new=" << NewIndex | ||
| << " newOffset=" << NewReloc.Reloc.Offset << "\n"); | ||
|
|
||
| switch (Reloc.Type) { | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_SLEB: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_I32: | ||
| case R_WEBASSEMBLY_MEMORY_ADDR_LEB: | ||
| NewReloc.Value = File.getRelocatedAddress(Reloc.Index); | ||
| if (NewReloc.Value != UINT32_MAX) | ||
| NewReloc.Value += Reloc.Addend; | ||
| break; | ||
| default: | ||
| NewReloc.Value = NewIndex; | ||
| } | ||
|
|
||
| OutputRelocs.emplace_back(NewReloc); | ||
| } | ||
| } | ||
|
|
||
| void OutputSection::createHeader(size_t BodySize) { | ||
| raw_string_ostream OS(Header); | ||
| debugWrite(OS.tell(), | ||
| "section type [" + Twine(sectionTypeToString(Type)) + "]"); | ||
| writeUleb128(OS, Type, nullptr); | ||
| writeUleb128(OS, BodySize, "section size"); | ||
| OS.flush(); | ||
| log("createHeader: " + toString(this) + " body=" + Twine(BodySize) + | ||
| " total=" + Twine(getSize())); | ||
| } | ||
|
|
||
| CodeSection::CodeSection(uint32_t NumFunctions, std::vector<ObjFile *> &Objs) | ||
| : OutputSection(WASM_SEC_CODE), InputObjects(Objs) { | ||
| raw_string_ostream OS(CodeSectionHeader); | ||
| writeUleb128(OS, NumFunctions, "function count"); | ||
| OS.flush(); | ||
| BodySize = CodeSectionHeader.size(); | ||
|
|
||
| for (ObjFile *File : InputObjects) { | ||
| if (!File->CodeSection) | ||
| continue; | ||
|
|
||
| File->CodeOffset = BodySize; | ||
| ArrayRef<uint8_t> Content = File->CodeSection->Content; | ||
| unsigned HeaderSize = 0; | ||
| decodeULEB128(Content.data(), &HeaderSize); | ||
|
|
||
| calcRelocations(*File, File->CodeSection->Relocations, | ||
| File->CodeRelocations, BodySize - HeaderSize); | ||
|
|
||
| size_t PayloadSize = Content.size() - HeaderSize; | ||
| BodySize += PayloadSize; | ||
| } | ||
|
|
||
| createHeader(BodySize); | ||
| } | ||
|
|
||
| void CodeSection::writeTo(uint8_t *Buf) { | ||
| log("writing " + toString(this)); | ||
| log(" size=" + Twine(getSize())); | ||
| Buf += Offset; | ||
|
|
||
| // Write section header | ||
| memcpy(Buf, Header.data(), Header.size()); | ||
| Buf += Header.size(); | ||
|
|
||
| uint8_t *ContentsStart = Buf; | ||
|
|
||
| // Write code section headers | ||
| memcpy(Buf, CodeSectionHeader.data(), CodeSectionHeader.size()); | ||
| Buf += CodeSectionHeader.size(); | ||
|
|
||
| // Write code section bodies | ||
| parallelForEach(InputObjects, [ContentsStart](ObjFile *File) { | ||
| if (!File->CodeSection) | ||
| return; | ||
|
|
||
| ArrayRef<uint8_t> Content(File->CodeSection->Content); | ||
|
|
||
| // Payload doesn't include the initial header (function count) | ||
| unsigned HeaderSize = 0; | ||
| decodeULEB128(Content.data(), &HeaderSize); | ||
|
|
||
| size_t PayloadSize = Content.size() - HeaderSize; | ||
| memcpy(ContentsStart + File->CodeOffset, Content.data() + HeaderSize, | ||
| PayloadSize); | ||
|
|
||
| log("applying relocations for: " + File->getName()); | ||
| if (File->CodeRelocations.size()) | ||
| applyRelocations(ContentsStart, File->CodeRelocations); | ||
| }); | ||
| } | ||
|
|
||
| uint32_t CodeSection::numRelocations() const { | ||
| uint32_t Count = 0; | ||
| for (ObjFile *File : InputObjects) | ||
| Count += File->CodeRelocations.size(); | ||
| return Count; | ||
| } | ||
|
|
||
| void CodeSection::writeRelocations(raw_ostream &OS) const { | ||
| for (ObjFile *File : InputObjects) | ||
| for (const OutputRelocation &Reloc : File->CodeRelocations) | ||
| writeReloc(OS, Reloc); | ||
| } | ||
|
|
||
| DataSection::DataSection(std::vector<OutputSegment *> &Segments) | ||
| : OutputSection(WASM_SEC_DATA), Segments(Segments) { | ||
| raw_string_ostream OS(DataSectionHeader); | ||
|
|
||
| writeUleb128(OS, Segments.size(), "data segment count"); | ||
| OS.flush(); | ||
| BodySize = DataSectionHeader.size(); | ||
|
|
||
| for (OutputSegment *Segment : Segments) { | ||
| raw_string_ostream OS(Segment->Header); | ||
| writeUleb128(OS, 0, "memory index"); | ||
| writeUleb128(OS, WASM_OPCODE_I32_CONST, "opcode:i32const"); | ||
| writeSleb128(OS, Segment->StartVA, "memory offset"); | ||
| writeUleb128(OS, WASM_OPCODE_END, "opcode:end"); | ||
| writeUleb128(OS, Segment->Size, "segment size"); | ||
| OS.flush(); | ||
| Segment->setSectionOffset(BodySize); | ||
| BodySize += Segment->Header.size(); | ||
| log("Data segment: size=" + Twine(Segment->Size)); | ||
| for (const InputSegment *InputSeg : Segment->InputSegments) { | ||
| uint32_t InputOffset = InputSeg->getInputSectionOffset(); | ||
| uint32_t OutputOffset = Segment->getSectionOffset() + | ||
| Segment->Header.size() + | ||
| InputSeg->getOutputSegmentOffset(); | ||
| calcRelocations(*InputSeg->File, InputSeg->Relocations, Relocations, | ||
| OutputOffset - InputOffset); | ||
| } | ||
| BodySize += Segment->Size; | ||
| } | ||
|
|
||
| createHeader(BodySize); | ||
| } | ||
|
|
||
| void DataSection::writeTo(uint8_t *Buf) { | ||
| log("writing " + toString(this) + " size=" + Twine(getSize()) + | ||
| " body=" + Twine(BodySize)); | ||
| Buf += Offset; | ||
|
|
||
| // Write section header | ||
| memcpy(Buf, Header.data(), Header.size()); | ||
| Buf += Header.size(); | ||
|
|
||
| uint8_t *ContentsStart = Buf; | ||
|
|
||
| // Write data section headers | ||
| memcpy(Buf, DataSectionHeader.data(), DataSectionHeader.size()); | ||
|
|
||
| parallelForEach(Segments, [ContentsStart](const OutputSegment *Segment) { | ||
| // Write data segment header | ||
| uint8_t *SegStart = ContentsStart + Segment->getSectionOffset(); | ||
| memcpy(SegStart, Segment->Header.data(), Segment->Header.size()); | ||
|
|
||
| // Write segment data payload | ||
| for (const InputSegment *Input : Segment->InputSegments) { | ||
| ArrayRef<uint8_t> Content(Input->Segment->Data.Content); | ||
| memcpy(SegStart + Segment->Header.size() + | ||
| Input->getOutputSegmentOffset(), | ||
| Content.data(), Content.size()); | ||
| } | ||
| }); | ||
|
|
||
| applyRelocations(ContentsStart, Relocations); | ||
| } | ||
|
|
||
| void DataSection::writeRelocations(raw_ostream &OS) const { | ||
| for (const OutputRelocation &Reloc : Relocations) | ||
| writeReloc(OS, Reloc); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| //===- OutputSections.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_OUTPUT_SECTIONS_H | ||
| #define LLD_WASM_OUTPUT_SECTIONS_H | ||
|
|
||
| #include "InputSegment.h" | ||
| #include "WriterUtils.h" | ||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "llvm/ADT/DenseMap.h" | ||
|
|
||
| using llvm::raw_ostream; | ||
| using llvm::raw_string_ostream; | ||
|
|
||
| namespace lld { | ||
|
|
||
| namespace wasm { | ||
| class OutputSection; | ||
| } | ||
| std::string toString(wasm::OutputSection *Section); | ||
|
|
||
| namespace wasm { | ||
|
|
||
| class OutputSegment; | ||
| class ObjFile; | ||
|
|
||
| class OutputSection { | ||
| public: | ||
| OutputSection(uint32_t Type, std::string Name = "") | ||
| : Type(Type), Name(Name) {} | ||
|
|
||
| virtual ~OutputSection() = default; | ||
|
|
||
| void setOffset(size_t NewOffset) { | ||
| log("setOffset: " + toString(this) + " -> " + Twine(NewOffset)); | ||
| Offset = NewOffset; | ||
| } | ||
|
|
||
| void createHeader(size_t BodySize); | ||
| virtual size_t getSize() const = 0; | ||
| virtual void writeTo(uint8_t *Buf) = 0; | ||
| virtual void finalizeContents() {} | ||
|
|
||
| std::string Header; | ||
| uint32_t Type; | ||
| std::string Name; | ||
|
|
||
| virtual uint32_t numRelocations() const { return 0; } | ||
| virtual void writeRelocations(raw_ostream &OS) const {} | ||
|
|
||
| protected: | ||
| size_t Offset = 0; | ||
| }; | ||
|
|
||
| class SyntheticSection : public OutputSection { | ||
| public: | ||
| SyntheticSection(uint32_t Type, std::string Name = "") | ||
| : OutputSection(Type, Name), BodyOutputStream(Body) { | ||
| if (!Name.empty()) | ||
| writeStr(BodyOutputStream, Name); | ||
| } | ||
|
|
||
| void writeTo(uint8_t *Buf) override { | ||
| assert(Offset); | ||
| log("writing " + toString(this)); | ||
| memcpy(Buf + Offset, Header.data(), Header.size()); | ||
| memcpy(Buf + Offset + Header.size(), Body.data(), Body.size()); | ||
| } | ||
|
|
||
| size_t getSize() const override { return Header.size() + Body.size(); } | ||
|
|
||
| void finalizeContents() override { | ||
| BodyOutputStream.flush(); | ||
| createHeader(Body.size()); | ||
| } | ||
|
|
||
| raw_ostream &getStream() { return BodyOutputStream; } | ||
|
|
||
| std::string Body; | ||
|
|
||
| protected: | ||
| raw_string_ostream BodyOutputStream; | ||
| }; | ||
|
|
||
| // Some synthetic sections (e.g. "name" and "linking") have subsections. | ||
| // Just like the synthetic sections themselves these need to be created before | ||
| // they can be written out (since they are preceded by their length). This | ||
| // class is used to create subsections and then write them into the stream | ||
| // of the parent section. | ||
| class SubSection : public SyntheticSection { | ||
| public: | ||
| explicit SubSection(uint32_t Type) : SyntheticSection(Type) {} | ||
|
|
||
| void writeToStream(raw_ostream &OS) { | ||
| writeBytes(OS, Header.data(), Header.size()); | ||
| writeBytes(OS, Body.data(), Body.size()); | ||
| } | ||
| }; | ||
|
|
||
| class CodeSection : public OutputSection { | ||
| public: | ||
| explicit CodeSection(uint32_t NumFunctions, std::vector<ObjFile *> &Objs); | ||
| size_t getSize() const override { return Header.size() + BodySize; } | ||
| void writeTo(uint8_t *Buf) override; | ||
| uint32_t numRelocations() const override; | ||
| void writeRelocations(raw_ostream &OS) const override; | ||
|
|
||
| protected: | ||
| std::vector<ObjFile *> &InputObjects; | ||
| std::string CodeSectionHeader; | ||
| size_t BodySize = 0; | ||
| }; | ||
|
|
||
| class DataSection : public OutputSection { | ||
| public: | ||
| explicit DataSection(std::vector<OutputSegment *> &Segments); | ||
| size_t getSize() const override { return Header.size() + BodySize; } | ||
| void writeTo(uint8_t *Buf) override; | ||
| uint32_t numRelocations() const override { return Relocations.size(); } | ||
| void writeRelocations(raw_ostream &OS) const override; | ||
|
|
||
| protected: | ||
| std::vector<OutputRelocation> Relocations; | ||
| std::vector<OutputSegment *> &Segments; | ||
| std::string DataSectionHeader; | ||
| size_t BodySize = 0; | ||
| }; | ||
|
|
||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif // LLD_WASM_OUTPUT_SECTIONS_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| //===- OutputSegment.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_OUTPUT_SEGMENT_H | ||
| #define LLD_WASM_OUTPUT_SEGMENT_H | ||
|
|
||
| #include "lld/Common/ErrorHandler.h" | ||
| #include "llvm/Object/Wasm.h" | ||
|
|
||
| namespace lld { | ||
| namespace wasm { | ||
|
|
||
| class InputSegment; | ||
|
|
||
| class OutputSegment { | ||
| public: | ||
| OutputSegment(StringRef N) : Name(N) {} | ||
|
|
||
| void addInputSegment(InputSegment *Segment) { | ||
| Alignment = std::max(Alignment, Segment->getAlignment()); | ||
| InputSegments.push_back(Segment); | ||
| Size = llvm::alignTo(Size, Segment->getAlignment()); | ||
| ; | ||
| Segment->setOutputSegment(this, Size); | ||
| Size += Segment->getSize(); | ||
| } | ||
|
|
||
| uint32_t getSectionOffset() const { return SectionOffset; } | ||
|
|
||
| void setSectionOffset(uint32_t Offset) { SectionOffset = Offset; } | ||
|
|
||
| StringRef Name; | ||
| uint32_t Alignment = 0; | ||
| uint32_t StartVA = 0; | ||
| std::vector<const InputSegment *> InputSegments; | ||
|
|
||
| // Sum of the size of the all the input segments | ||
| uint32_t Size = 0; | ||
|
|
||
| // Segment header | ||
| std::string Header; | ||
|
|
||
| private: | ||
| uint32_t SectionOffset = 0; | ||
| }; | ||
|
|
||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif // LLD_WASM_OUTPUT_SEGMENT_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| //===- Strings.cpp -------------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "Strings.h" | ||
| #include "Config.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/Demangle/Demangle.h" | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| // Returns the demangled C++ symbol name for Name. | ||
| Optional<std::string> lld::wasm::demangle(StringRef Name) { | ||
| // itaniumDemangle can be used to demangle strings other than symbol | ||
| // names which do not necessarily start with "_Z". Name can be | ||
| // either a C or C++ symbol. Don't call itaniumDemangle if the name | ||
| // does not look like a C++ symbol name to avoid getting unexpected | ||
| // result for a C symbol that happens to match a mangled type name. | ||
| if (!Name.startswith("_Z")) | ||
| return None; | ||
|
|
||
| char *Buf = itaniumDemangle(Name.str().c_str(), nullptr, nullptr, nullptr); | ||
| if (!Buf) | ||
| return None; | ||
| std::string S(Buf); | ||
| free(Buf); | ||
| return S; | ||
| } | ||
|
|
||
| std::string lld::wasm::displayName(StringRef Name) { | ||
| if (Config->Demangle) | ||
| if (Optional<std::string> S = demangle(Name)) | ||
| return "`" + *S + "'"; | ||
| return Name; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| //===- Strings.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_STRINGS_H | ||
| #define LLD_WASM_STRINGS_H | ||
|
|
||
| #include "llvm/ADT/Optional.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include <string> | ||
|
|
||
| namespace lld { | ||
| namespace wasm { | ||
|
|
||
| // Returns a demangled C++ symbol name. If Name is not a mangled | ||
| // name, it returns Optional::None. | ||
| llvm::Optional<std::string> demangle(llvm::StringRef Name); | ||
|
|
||
| std::string displayName(llvm::StringRef Name); | ||
|
|
||
| } // namespace wasm | ||
| } // namespace lld | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| //===- SymbolTable.cpp ----------------------------------------------------===// | ||
| // | ||
| // The LLVM Linker | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "SymbolTable.h" | ||
|
|
||
| #include "Config.h" | ||
| #include "Memory.h" | ||
| #include "Strings.h" | ||
| #include "lld/Common/ErrorHandler.h" | ||
|
|
||
| #include <unordered_set> | ||
|
|
||
| #define DEBUG_TYPE "lld" | ||
|
|
||
| using namespace llvm; | ||
| using namespace lld; | ||
| using namespace lld::wasm; | ||
|
|
||
| SymbolTable *lld::wasm::Symtab; | ||
|
|
||
| void SymbolTable::addFile(InputFile *File) { | ||
| log("Processing: " + toString(File)); | ||
| File->parse(); | ||
|
|
||
| if (auto *F = dyn_cast<ObjFile>(File)) | ||
| ObjectFiles.push_back(F); | ||
| } | ||
|
|
||
| void SymbolTable::reportRemainingUndefines() { | ||
| std::unordered_set<Symbol *> Undefs; | ||
| for (auto &I : Symtab) { | ||
| Symbol *Sym = I.second; | ||
| if (Sym->isUndefined() && !Sym->isWeak() && | ||
| Config->AllowUndefinedSymbols.count(Sym->getName()) == 0) { | ||
| Undefs.insert(Sym); | ||
| } | ||
| } | ||
|
|
||
| if (Undefs.empty()) | ||
| return; | ||
|
|
||
| for (ObjFile *File : ObjectFiles) | ||
| for (Symbol *Sym : File->getSymbols()) | ||
| if (Undefs.count(Sym)) | ||
| error(toString(File) + ": undefined symbol: " + toString(*Sym)); | ||
|
|
||
| for (Symbol *Sym : Undefs) | ||
| if (!Sym->getFile()) | ||
| error("undefined symbol: " + toString(*Sym)); | ||
| } | ||
|
|
||
| Symbol *SymbolTable::find(StringRef Name) { | ||
| auto It = Symtab.find(CachedHashStringRef(Name)); | ||
| if (It == Symtab.end()) | ||
| return nullptr; | ||
| return It->second; | ||
| } | ||
|
|
||
| std::pair<Symbol *, bool> SymbolTable::insert(StringRef Name) { | ||
| Symbol *&Sym = Symtab[CachedHashStringRef(Name)]; | ||
| if (Sym) | ||
| return {Sym, false}; | ||
| Sym = make<Symbol>(Name, false); | ||
| return {Sym, true}; | ||
| } | ||
|
|
||
| void SymbolTable::reportDuplicate(Symbol *Existing, InputFile *NewFile) { | ||
| error("duplicate symbol: " + toString(*Existing) + "\n>>> defined in " + | ||
| toString(Existing->getFile()) + "\n>>> defined in " + | ||
| (NewFile ? toString(NewFile) : "<internal>")); | ||
| } | ||
|
|
||
| static void checkSymbolTypes(Symbol *Existing, InputFile *F, | ||
| const WasmSymbol *New) { | ||
| if (Existing->isLazy()) | ||
| return; | ||
|
|
||
| bool NewIsFunction = New->Type == WasmSymbol::SymbolType::FUNCTION_EXPORT || | ||
| New->Type == WasmSymbol::SymbolType::FUNCTION_IMPORT; | ||
| if (Existing->isFunction() == NewIsFunction) | ||
| return; | ||
|
|
||
| std::string Filename = "<builtin>"; | ||
| if (Existing->getFile()) | ||
| Filename = toString(Existing->getFile()); | ||
| error("symbol type mismatch: " + New->Name + "\n>>> defined as " + | ||
| (Existing->isFunction() ? "Function" : "Global") + " in " + Filename + | ||
| "\n>>> defined as " + (NewIsFunction ? "Function" : "Global") + " in " + | ||
| F->getName()); | ||
| } | ||
|
|
||
| Symbol *SymbolTable::addDefinedGlobal(StringRef Name) { | ||
| DEBUG(dbgs() << "addDefinedGlobal: " << Name << "\n"); | ||
| Symbol *S; | ||
| bool WasInserted; | ||
| std::tie(S, WasInserted) = insert(Name); | ||
| if (WasInserted) | ||
| S->update(Symbol::DefinedGlobalKind); | ||
| else if (!S->isGlobal()) | ||
| error("symbol type mismatch: " + Name); | ||
| return S; | ||
| } | ||
|
|
||
| Symbol *SymbolTable::addDefined(InputFile *F, const WasmSymbol *Sym, | ||
| const InputSegment *Segment) { | ||
| DEBUG(dbgs() << "addDefined: " << Sym->Name << "\n"); | ||
| Symbol *S; | ||
| bool WasInserted; | ||
| Symbol::Kind Kind = Symbol::DefinedFunctionKind; | ||
| if (Sym->Type == WasmSymbol::SymbolType::GLOBAL_EXPORT) | ||
| Kind = Symbol::DefinedGlobalKind; | ||
|
|
||
| std::tie(S, WasInserted) = insert(Sym->Name); | ||
| if (WasInserted) { | ||
| S->update(Kind, F, Sym, Segment); | ||
| } else if (!S->isDefined()) { | ||
| // The existing symbol table entry is undefined. The new symbol replaces | ||
| // it | ||
| DEBUG(dbgs() << "resolving existing undefined symbol: " << Sym->Name | ||
| << "\n"); | ||
| checkSymbolTypes(S, F, Sym); | ||
| S->update(Kind, F, Sym, Segment); | ||
| } else if (Sym->isWeak()) { | ||
| // the new symbol is weak we can ignore it | ||
| DEBUG(dbgs() << "existing symbol takes precensence\n"); | ||
| } else if (S->isWeak()) { | ||
| // the new symbol is not weak and the existing symbol is, so we replace | ||
| // it | ||
| DEBUG(dbgs() << "replacing existing weak symbol\n"); | ||
| S->update(Kind, F, Sym, Segment); | ||
| } else { | ||
| // niether symbol is week. They conflict. | ||
| reportDuplicate(S, F); | ||
| } | ||
| return S; | ||
| } | ||
|
|
||
| Symbol *SymbolTable::addUndefinedFunction(StringRef Name) { | ||
| Symbol *S; | ||
| bool WasInserted; | ||
| std::tie(S, WasInserted) = insert(Name); | ||
| if (WasInserted) | ||
| S->update(Symbol::UndefinedFunctionKind); | ||
| else if (!S->isFunction()) | ||
| error("symbol type mismatch: " + Name); | ||
| return S; | ||
| } | ||
|
|
||
| Symbol *SymbolTable::addUndefined(InputFile *F, const WasmSymbol *Sym) { | ||
| DEBUG(dbgs() << "addUndefined: " << displayName(Sym->Name) << "\n"); | ||
| Symbol *S; | ||
| bool WasInserted; | ||
| Symbol::Kind Kind = Symbol::UndefinedFunctionKind; | ||
| if (Sym->Type == WasmSymbol::SymbolType::GLOBAL_IMPORT) | ||
| Kind = Symbol::UndefinedGlobalKind; | ||
| std::tie(S, WasInserted) = insert(Sym->Name); | ||
| if (WasInserted) { | ||
| S->update(Kind, F, Sym); | ||
| } else if (S->isLazy()) { | ||
| DEBUG(dbgs() << "resolved by existing lazy\n"); | ||
| auto *AF = cast<ArchiveFile>(S->getFile()); | ||
| AF->addMember(&S->getArchiveSymbol()); | ||
| } else if (S->isDefined()) { | ||
| DEBUG(dbgs() << "resolved by existing\n"); | ||
| checkSymbolTypes(S, F, Sym); | ||
| } | ||
| return S; | ||
| } | ||
|
|
||
| void SymbolTable::addLazy(ArchiveFile *F, const Archive::Symbol *Sym) { | ||
| DEBUG(dbgs() << "addLazy: " << displayName(Sym->getName()) << "\n"); | ||
| StringRef Name = Sym->getName(); | ||
| Symbol *S; | ||
| bool WasInserted; | ||
| std::tie(S, WasInserted) = insert(Name); | ||
| if (WasInserted) { | ||
| S->update(Symbol::LazyKind, F); | ||
| S->setArchiveSymbol(*Sym); | ||
| } else if (S->isUndefined()) { | ||
| // There is an existing undefined symbol. The can load from the | ||
| // archive. | ||
| DEBUG(dbgs() << "replacing existing undefined\n"); | ||
| F->addMember(Sym); | ||
| } | ||
| } |