Heejin Ahn c921ac724f
[WebAssembly] Enable multivalue return when multivalue ABI is used (#88492)
Multivalue feature of WebAssembly has been standardized for several
years now. I think it makes sense to be able to enable it in the feature
section by default for our clang/llvm-produced binaries so that the
multivalue feature can be used as necessary when necessary within our
toolchain and also when running other optimizers (e.g. wasm-opt) after
the LLVM code generation.

But some WebAssembly toolchains, such as Emscripten, do not provide both
mulvalue-returning and not-multivalue-returning versions of libraries.
Also allowing the uses of multivalue in the features section does not
necessarily mean we generate them whenever we can to the fullest, which
is a different code generation / optimization option.

So this makes the lowering of multivalue returns conditional on the use
of 'experimental-mv' target ABI. This ABI is turned off by default and
turned on by passing `-Xclang -target-abi -Xclang experimental-mv` to
`clang`, or `-target-abi experimental-mv` to `clang -cc1` or `llc`.

But the purpose of this PR is not tying the multivalue lowering to this
specific 'experimental-mv'. 'experimental-mv' is just one multivalue ABI
we currently have, and it is still experimental, meaning it is not very
well optimized or tuned for performance. (e.g. it does not have the
limitation of the max number of multivalue-lowered values, which can be
detrimental to performance.) We may change the name of this ABI, or
improve it, or add a new multivalue ABI in the future. Also I heard that
WASI is planning to add their multivalue ABI soon. So the plan is,
whenever any one of multivalue ABIs is enabled, we enable the lowering
of multivalue returns in the backend. We currently have only
'experimental-mv' in the repo so we only check for that in this PR.

Related past discussions:
 #82714
https://github.com/WebAssembly/tool-conventions/pull/223#issuecomment-2008298652
2024-04-23 17:48:59 +09:00

314 lines
10 KiB
LLVM

; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -mcpu=mvp -mattr=+multivalue,+tail-call -target-abi=experimental-mv | FileCheck %s
; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -mcpu=mvp -mattr=+reference-types,+multivalue,+tail-call -target-abi=experimental-mv | FileCheck --check-prefix REF %s
; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mcpu=mvp -mattr=+multivalue,+tail-call -target-abi=experimental-mv | FileCheck %s --check-prefix REGS
; RUN: llc < %s --filetype=obj -mcpu=mvp -mattr=+multivalue,+tail-call -target-abi=experimental-mv | obj2yaml | FileCheck %s --check-prefix OBJ
; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -mcpu=mvp -mattr=+multivalue,+tail-call | FileCheck %s --check-prefix NO-MULTIVALUE
; Test that the multivalue calls, returns, function types, and block
; types work as expected.
target triple = "wasm32-unknown-unknown"
%pair = type { i32, i64 }
%rpair = type { i64, i32 }
declare void @use_i32(i32)
declare void @use_i64(i64)
; CHECK-LABEL: pair_const:
; CHECK-NEXT: .functype pair_const () -> (i32, i64)
; CHECK-NEXT: i32.const 42{{$}}
; CHECK-NEXT: i64.const 42{{$}}
; CHECK-NEXT: end_function{{$}}
; NO-MULTIVALUE-NOT: .functype pair_const () -> (i32, i64)
define %pair @pair_const() {
ret %pair { i32 42, i64 42 }
}
; CHECK-LABEL: pair_ident:
; CHECK-NEXT: .functype pair_ident (i32, i64) -> (i32, i64)
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: end_function{{$}}
define %pair @pair_ident(%pair %p) {
ret %pair %p
}
; CHECK-LABEL: pair_call:
; CHECK-NEXT: .functype pair_call () -> ()
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $drop=, $drop=, pair_const{{$}}
define void @pair_call() {
%p = call %pair @pair_const()
ret void
}
; CHECK-LABEL: pair_call_return:
; CHECK-NEXT: .functype pair_call_return () -> (i32, i64)
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $push{{[0-9]+}}=, $push{{[0-9]+}}=, pair_const{{$}}
define %pair @pair_call_return() {
%p = call %pair @pair_const()
ret %pair %p
}
; CHECK-LABEL: pair_call_indirect:
; CHECK-NEXT: .functype pair_call_indirect (i32) -> (i32, i64)
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: call_indirect () -> (i32, i64){{$}}
; REF: call_indirect __indirect_function_table, () -> (i32, i64){{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call_indirect $push{{[0-9]+}}=, $push{{[0-9]+}}=, $0{{$}}
define %pair @pair_call_indirect(ptr %f) {
%p = call %pair %f()
ret %pair %p
}
; CHECK-LABEL: pair_tail_call:
; CHECK-NEXT: .functype pair_tail_call () -> (i32, i64)
; CHECK-NEXT: return_call pair_const{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: return_call pair_const{{$}}
define %pair @pair_tail_call() {
%p = musttail call %pair @pair_const()
ret %pair %p
}
; CHECK-LABEL: pair_call_return_first:
; CHECK-NEXT: .functype pair_call_return_first () -> (i32)
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $push{{[0-9]+}}=, $drop=, pair_const{{$}}
define i32 @pair_call_return_first() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 0
ret i32 %v
}
; CHECK-LABEL: pair_call_return_second:
; CHECK-NEXT: .functype pair_call_return_second () -> (i64)
; CHECK-NEXT: .local i64{{$}}
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $drop=, $0=, pair_const{{$}}
define i64 @pair_call_return_second() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 1
ret i64 %v
}
; CHECK-LABEL: pair_call_use_first:
; CHECK-NEXT: .functype pair_call_use_first () -> ()
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: call use_i32{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $push{{[0-9]+}}=, $drop=, pair_const{{$}}
define void @pair_call_use_first() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 0
call void @use_i32(i32 %v)
ret void
}
; CHECK-LABEL: pair_call_use_second:
; CHECK-NEXT: .functype pair_call_use_second () -> ()
; CHECK-NEXT: .local i64
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: drop{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: call use_i64{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $drop=, $0=, pair_const{{$}}
define void @pair_call_use_second() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 1
call void @use_i64(i64 %v)
ret void
}
; CHECK-LABEL: pair_call_use_first_return_second:
; CHECK-NEXT: .functype pair_call_use_first_return_second () -> (i64)
; CHECK-NEXT: .local i64{{$}}
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: call use_i32{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $push{{[0-9]+}}=, $0=, pair_const{{$}}
define i64 @pair_call_use_first_return_second() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 0
call void @use_i32(i32 %v)
%r = extractvalue %pair %p, 1
ret i64 %r
}
; CHECK-LABEL: pair_call_use_second_return_first:
; CHECK-NEXT: .functype pair_call_use_second_return_first () -> (i32)
; CHECK-NEXT: .local i32, i64{{$}}
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: local.set 1{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: call use_i64{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $0=, $1=, pair_const{{$}}
define i32 @pair_call_use_second_return_first() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 1
call void @use_i64(i64 %v)
%r = extractvalue %pair %p, 0
ret i32 %r
}
; CHECK-LABEL: pair_pass_through:
; CHECK-NEXT: .functype pair_pass_through (i32, i64) -> (i32, i64)
; CHECK-NEXT: local.get 0
; CHECK-NEXT: local.get 1
; CHECK-NEXT: call pair_ident{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $push{{[0-9]+}}=, $push{{[0-9]+}}=, pair_ident, $0, $1{{$}}
define %pair @pair_pass_through(%pair %p) {
%r = call %pair @pair_ident(%pair %p)
ret %pair %r
}
; CHECK-LABEL: pair_swap:
; CHECK-NEXT: .functype pair_swap (i32, i64) -> (i64, i32)
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
define %rpair @pair_swap(%pair %p) {
%first = extractvalue %pair %p, 0
%second = extractvalue %pair %p, 1
%r1 = insertvalue %rpair undef, i32 %first, 1
%r2 = insertvalue %rpair %r1, i64 %second, 0
ret %rpair %r2
}
; CHECK-LABEL: pair_call_swap:
; CHECK-NEXT: .functype pair_call_swap () -> (i64, i32)
; CHECK-NEXT: .local i32, i64{{$}}
; CHECK-NEXT: call pair_const{{$}}
; CHECK-NEXT: local.set 1{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $0=, $1=, pair_const{{$}}
define %rpair @pair_call_swap() {
%p = call %pair @pair_const()
%first = extractvalue %pair %p, 0
%second = extractvalue %pair %p, 1
%r1 = insertvalue %rpair undef, i32 %first, 1
%r2 = insertvalue %rpair %r1, i64 %second, 0
ret %rpair %r2
}
; CHECK-LABEL: pair_pass_through_swap:
; CHECK-NEXT: .functype pair_pass_through_swap (i32, i64) -> (i64, i32)
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: call pair_ident{{$}}
; CHECK-NEXT: local.set 1{{$}}
; CHECK-NEXT: local.set 0{{$}}
; CHECK-NEXT: local.get 1{{$}}
; CHECK-NEXT: local.get 0{{$}}
; CHECK-NEXT: end_function{{$}}
; REGS: call $0=, $1=, pair_ident, $0, $1{{$}}
define %rpair @pair_pass_through_swap(%pair %p) {
%p1 = call %pair @pair_ident(%pair %p)
%first = extractvalue %pair %p1, 0
%second = extractvalue %pair %p1, 1
%r1 = insertvalue %rpair undef, i32 %first, 1
%r2 = insertvalue %rpair %r1, i64 %second, 0
ret %rpair %r2
}
; CHECK-LABEL: minimal_loop:
; CHECK-NEXT: .functype minimal_loop (i32) -> (i32, i64)
; CHECK-NEXT: .LBB{{[0-9]+}}_1:
; CHECK-NEXT: loop () -> (i32, i64)
; CHECK-NEXT: br 0{{$}}
; CHECK-NEXT: .LBB{{[0-9]+}}_2:
; CHECK-NEXT: end_loop{{$}}
; CHECK-NEXT: end_function{{$}}
define %pair @minimal_loop(ptr %p) {
entry:
br label %loop
loop:
br label %loop
}
; CHECK-LABEL: .section .custom_section.target_features
; CHECK-NEXT: .int8 2
; CHECK-NEXT: .int8 43
; CHECK-NEXT: .int8 10
; CHECK-NEXT: .ascii "multivalue"
; CHECK-NEXT: .int8 43
; CHECK-NEXT: .int8 9
; CHECK-NEXT: .ascii "tail-call"
; OBJ-LABEL: - Type: TYPE
; OBJ-NEXT: Signatures:
; OBJ-NEXT: - Index: 0
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64
; OBJ-NEXT: - Index: 1
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64
; OBJ-NEXT: - Index: 2
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes: []
; OBJ-NEXT: - Index: 3
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64
; OBJ-NEXT: - Index: 4
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - Index: 5
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I64
; OBJ-NEXT: - Index: 6
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: ReturnTypes: []
; OBJ-NEXT: - Index: 7
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I64
; OBJ-NEXT: ReturnTypes: []
; OBJ-NEXT: - Index: 8
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I64
; OBJ-NEXT: - I32
; OBJ-NEXT: - Index: 9
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I64
; OBJ-NEXT: - I32