779 changes: 779 additions & 0 deletions lld/test/wasm/Inputs/many-funcs.ll

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions lld/test/wasm/Inputs/ret32.ll
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)
}
7 changes: 7 additions & 0 deletions lld/test/wasm/Inputs/ret64.ll
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
}
12 changes: 12 additions & 0 deletions lld/test/wasm/Inputs/weak-alias.ll
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
}
24 changes: 24 additions & 0 deletions lld/test/wasm/archive.ll
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
110 changes: 110 additions & 0 deletions lld/test/wasm/call-indirect.ll
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
6 changes: 6 additions & 0 deletions lld/test/wasm/conflict.test
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
64 changes: 64 additions & 0 deletions lld/test/wasm/data-layout.ll
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
22 changes: 22 additions & 0 deletions lld/test/wasm/entry.ll
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
45 changes: 45 additions & 0 deletions lld/test/wasm/function-imports-first.ll
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: ...
40 changes: 40 additions & 0 deletions lld/test/wasm/function-imports.ll
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: ...
18 changes: 18 additions & 0 deletions lld/test/wasm/function-index.test
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 ]
13 changes: 13 additions & 0 deletions lld/test/wasm/import-memory.test
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
9 changes: 9 additions & 0 deletions lld/test/wasm/invalid-stack-size.test
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
4 changes: 4 additions & 0 deletions lld/test/wasm/lit.local.cfg
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']
81 changes: 81 additions & 0 deletions lld/test/wasm/local-symbols.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: ...
699 changes: 699 additions & 0 deletions lld/test/wasm/many-functions.ll

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions lld/test/wasm/relocatable.ll
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: ...
6 changes: 6 additions & 0 deletions lld/test/wasm/strip-debug.test
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
12 changes: 12 additions & 0 deletions lld/test/wasm/symtol-type-mismatch.ll
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
4 changes: 4 additions & 0 deletions lld/test/wasm/undefined-entry.test
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
23 changes: 23 additions & 0 deletions lld/test/wasm/undefined.ll
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
}
16 changes: 16 additions & 0 deletions lld/test/wasm/version.ll
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
92 changes: 92 additions & 0 deletions lld/test/wasm/weak-alias-overide.ll
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: ...
82 changes: 82 additions & 0 deletions lld/test/wasm/weak-alias.ll
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: ...
86 changes: 86 additions & 0 deletions lld/test/wasm/weak-external.ll
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: ...
3 changes: 2 additions & 1 deletion lld/tools/lld/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ target_link_libraries(lld
lldDriver
lldELF
lldMinGW
lldWasm
)

install(TARGETS lld
RUNTIME DESTINATION bin)

if(NOT LLD_SYMLINKS_TO_CREATE)
set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld ld64.lld)
set(LLD_SYMLINKS_TO_CREATE lld-link ld.lld ld64.lld wasm-ld)
endif()

foreach(link ${LLD_SYMLINKS_TO_CREATE})
Expand Down
4 changes: 4 additions & 0 deletions lld/tools/lld/lld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum Flavor {
Gnu, // -flavor gnu
WinLink, // -flavor link
Darwin, // -flavor darwin
Wasm, // -flavor wasm
};

LLVM_ATTRIBUTE_NORETURN static void die(const Twine &S) {
Expand All @@ -44,6 +45,7 @@ LLVM_ATTRIBUTE_NORETURN static void die(const Twine &S) {
static Flavor getFlavor(StringRef S) {
return StringSwitch<Flavor>(S)
.CasesLower("ld", "ld.lld", "gnu", Gnu)
.CasesLower("wasm", "ld-wasm", Wasm)
.CaseLower("link", WinLink)
.CasesLower("ld64", "ld64.lld", "darwin", Darwin)
.Default(Invalid);
Expand Down Expand Up @@ -118,6 +120,8 @@ int main(int Argc, const char **Argv) {
return !coff::link(Args, true);
case Darwin:
return !mach_o::link(Args);
case Wasm:
return !wasm::link(Args, true);
default:
die("lld is a generic driver.\n"
"Invoke ld.lld (Unix), ld (macOS) or lld-link (Windows) instead.");
Expand Down
28 changes: 28 additions & 0 deletions lld/wasm/CMakeLists.txt
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
)
52 changes: 52 additions & 0 deletions lld/wasm/Config.h
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
357 changes: 357 additions & 0 deletions lld/wasm/Driver.cpp
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();
}
275 changes: 275 additions & 0 deletions lld/wasm/InputFiles.cpp
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();
}
150 changes: 150 additions & 0 deletions lld/wasm/InputFiles.h
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
25 changes: 25 additions & 0 deletions lld/wasm/InputSegment.cpp
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;
}
74 changes: 74 additions & 0 deletions lld/wasm/InputSegment.h
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
52 changes: 52 additions & 0 deletions lld/wasm/Memory.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
89 changes: 89 additions & 0 deletions lld/wasm/Options.td
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>;
330 changes: 330 additions & 0 deletions lld/wasm/OutputSections.cpp
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);
}
138 changes: 138 additions & 0 deletions lld/wasm/OutputSections.h
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
56 changes: 56 additions & 0 deletions lld/wasm/OutputSegment.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
40 changes: 40 additions & 0 deletions lld/wasm/Strings.cpp
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;
}
29 changes: 29 additions & 0 deletions lld/wasm/Strings.h
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
191 changes: 191 additions & 0 deletions lld/wasm/SymbolTable.cpp
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);
}
}
Loading