Reland "[WebAssembly][InstrEmitter] Foundation for multivalue call lowering"

This reverts commit 649aba93a2, now that
the approach started there has been shown to be workable in the patch
series culminating in https://reviews.llvm.org/D74192.
This commit is contained in:
Thomas Lively 2020-02-05 18:17:11 -08:00
parent 42cab985fd
commit 7b64a59060
13 changed files with 267 additions and 47 deletions

View File

@ -361,11 +361,13 @@ public:
raw_pwrite_stream *DwoOut, CodeGenFileType FileType,
MCContext &Context);
/// True if the target uses physical regs at Prolog/Epilog insertion
/// time. If true (most machines), all vregs must be allocated before
/// PEI. If false (virtual-register machines), then callee-save register
/// spilling and scavenging are not needed or used.
virtual bool usesPhysRegsForPEI() const { return true; }
/// True if the target uses physical regs (as nearly all targets do). False
/// for stack machines such as WebAssembly and other virtual-register
/// machines. If true, all vregs must be allocated before PEI. If false, then
/// callee-save register spilling and scavenging are not needed or used. If
/// false, implicitly defined registers will still be assumed to be physical
/// registers, except that variadic defs will be allocated vregs.
virtual bool usesPhysRegsForValues() const { return true; }
/// True if the target wants to use interprocedural register allocation by
/// default. The -enable-ipra flag can be used to override this.

View File

@ -237,7 +237,7 @@ bool PEI::runOnMachineFunction(MachineFunction &MF) {
stashEntryDbgValues(*SaveBlock, EntryDbgValues);
// Handle CSR spilling and restoring, for targets that need it.
if (MF.getTarget().usesPhysRegsForPEI())
if (MF.getTarget().usesPhysRegsForValues())
spillCalleeSavedRegs(MF);
// Allow the target machine to make final modifications to the function

View File

@ -195,7 +195,10 @@ void InstrEmitter::CreateVirtualRegisters(SDNode *Node,
"IMPLICIT_DEF should have been handled as a special case elsewhere!");
unsigned NumResults = CountResults(Node);
for (unsigned i = 0; i < II.getNumDefs(); ++i) {
bool HasVRegVariadicDefs = !MF->getTarget().usesPhysRegsForValues() &&
II.isVariadic() && II.variadicOpsAreDefs();
unsigned NumVRegs = HasVRegVariadicDefs ? NumResults : II.getNumDefs();
for (unsigned i = 0; i < NumVRegs; ++i) {
// If the specific node value is only used by a CopyToReg and the dest reg
// is a vreg in the same register class, use the CopyToReg'd destination
// register instead of creating a new vreg.
@ -216,7 +219,7 @@ void InstrEmitter::CreateVirtualRegisters(SDNode *Node,
RC = VTRC;
}
if (II.OpInfo[i].isOptionalDef()) {
if (II.OpInfo != nullptr && II.OpInfo[i].isOptionalDef()) {
// Optional def must be a physical register.
VRBase = cast<RegisterSDNode>(Node->getOperand(i-NumResults))->getReg();
assert(Register::isPhysicalRegister(VRBase));
@ -829,7 +832,10 @@ EmitMachineNode(SDNode *Node, bool IsClone, bool IsCloned,
unsigned NumImpUses = 0;
unsigned NodeOperands =
countOperands(Node, II.getNumOperands() - NumDefs, NumImpUses);
bool HasPhysRegOuts = NumResults > NumDefs && II.getImplicitDefs()!=nullptr;
bool HasVRegVariadicDefs = !MF->getTarget().usesPhysRegsForValues() &&
II.isVariadic() && II.variadicOpsAreDefs();
bool HasPhysRegOuts = NumResults > NumDefs &&
II.getImplicitDefs() != nullptr && !HasVRegVariadicDefs;
#ifndef NDEBUG
unsigned NumMIOperands = NodeOperands + NumResults;
if (II.isVariadic())

View File

@ -509,8 +509,8 @@ inline bool isCallIndirect(unsigned Opc) {
/// Returns the operand number of a callee, assuming the argument is a call
/// instruction.
inline unsigned getCalleeOpNo(unsigned Opc) {
switch (Opc) {
inline const MachineOperand &getCalleeOp(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::CALL_VOID:
case WebAssembly::CALL_VOID_S:
case WebAssembly::CALL_INDIRECT_VOID:
@ -519,7 +519,7 @@ inline unsigned getCalleeOpNo(unsigned Opc) {
case WebAssembly::RET_CALL_S:
case WebAssembly::RET_CALL_INDIRECT:
case WebAssembly::RET_CALL_INDIRECT_S:
return 0;
return MI.getOperand(0);
case WebAssembly::CALL_i32:
case WebAssembly::CALL_i32_S:
case WebAssembly::CALL_i64:
@ -564,7 +564,10 @@ inline unsigned getCalleeOpNo(unsigned Opc) {
case WebAssembly::CALL_INDIRECT_v2f64_S:
case WebAssembly::CALL_INDIRECT_exnref:
case WebAssembly::CALL_INDIRECT_exnref_S:
return 1;
return MI.getOperand(1);
case WebAssembly::CALL:
case WebAssembly::CALL_S:
return MI.getOperand(MI.getNumDefs());
default:
llvm_unreachable("Not a call instruction");
}

View File

@ -15,6 +15,7 @@
HANDLE_NODETYPE(CALL1)
HANDLE_NODETYPE(CALL0)
HANDLE_NODETYPE(CALL)
HANDLE_NODETYPE(RET_CALL)
HANDLE_NODETYPE(RETURN)
HANDLE_NODETYPE(ARGUMENT)

View File

@ -206,6 +206,25 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
}
break;
}
case WebAssemblyISD::CALL: {
// CALL has both variable operands and variable results, but ISel only
// supports one or the other. Split calls into two nodes glued together, one
// for the operands and one for the results. These two nodes will be
// recombined in a custom inserter hook.
// TODO: Split CALL
SmallVector<SDValue, 16> Ops;
for (size_t i = 1; i < Node->getNumOperands(); ++i) {
SDValue Op = Node->getOperand(i);
if (Op->getOpcode() == WebAssemblyISD::Wrapper)
Op = Op->getOperand(0);
Ops.push_back(Op);
}
Ops.push_back(Node->getOperand(0));
MachineSDNode *Call =
CurDAG->getMachineNode(WebAssembly::CALL, DL, Node->getVTList(), Ops);
ReplaceNode(Node, Call);
return;
}
default:
break;

View File

@ -699,9 +699,6 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
}
SmallVectorImpl<ISD::InputArg> &Ins = CLI.Ins;
if (Ins.size() > 1)
fail(DL, DAG, "WebAssembly doesn't support more than 1 returned value yet");
SmallVectorImpl<ISD::OutputArg> &Outs = CLI.Outs;
SmallVectorImpl<SDValue> &OutVals = CLI.OutVals;
@ -847,18 +844,27 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
}
InTys.push_back(MVT::Other);
SDVTList InTyList = DAG.getVTList(InTys);
SDValue Res =
DAG.getNode(Ins.empty() ? WebAssemblyISD::CALL0 : WebAssemblyISD::CALL1,
DL, InTyList, Ops);
if (Ins.empty()) {
Chain = Res;
} else {
InVals.push_back(Res);
Chain = Res.getValue(1);
unsigned Opc;
// TODO: Remove CALL0 and CALL1 in favor of CALL
switch (Ins.size()) {
case 0:
Opc = WebAssemblyISD::CALL0;
break;
case 1:
Opc = WebAssemblyISD::CALL1;
break;
default:
Opc = WebAssemblyISD::CALL;
break;
}
SDVTList InTyList = DAG.getVTList(InTys);
SDValue Res = DAG.getNode(Opc, DL, InTyList, Ops);
return Chain;
for (size_t I = 0; I < Ins.size(); ++I)
InVals.push_back(Res.getValue(I));
// Return the chain
return Res.getValue(Ins.size());
}
bool WebAssemblyTargetLowering::CanLowerReturn(

View File

@ -55,6 +55,15 @@ multiclass CALL<ValueType vt, WebAssemblyRegClass rt, string prefix,
}
let Uses = [SP32, SP64], isCall = 1 in {
// TODO: Split CALL into separate nodes for operands and results.
// TODO: Add an indirect version of the variadic call, delete CALL_*
let variadicOpsAreDefs = 1 in
defm CALL :
I<(outs), (ins function32_op:$callee, variable_ops),
(outs), (ins function32_op:$callee), [],
"call \t$callee", "call\t$callee", 0x10>;
defm "" : CALL<i32, I32, "i32.">;
defm "" : CALL<i64, I64, "i64.">;
defm "" : CALL<f32, F32, "f32.">;
@ -68,6 +77,7 @@ defm "" : CALL<v4f32, V128, "v128.", [HasSIMD128]>;
defm "" : CALL<v2f64, V128, "v128.", [HasSIMD128]>;
let IsCanonical = 1 in {
defm CALL_VOID :
I<(outs), (ins function32_op:$callee, variable_ops),
(outs), (ins function32_op:$callee),

View File

@ -135,12 +135,12 @@ static void convertImplicitDefToConstZero(MachineInstr *MI,
// Determine whether a call to the callee referenced by
// MI->getOperand(CalleeOpNo) reads memory, writes memory, and/or has side
// effects.
static void queryCallee(const MachineInstr &MI, unsigned CalleeOpNo, bool &Read,
bool &Write, bool &Effects, bool &StackPointer) {
static void queryCallee(const MachineInstr &MI, bool &Read, bool &Write,
bool &Effects, bool &StackPointer) {
// All calls can use the stack pointer.
StackPointer = true;
const MachineOperand &MO = MI.getOperand(CalleeOpNo);
const MachineOperand &MO = WebAssembly::getCalleeOp(MI);
if (MO.isGlobal()) {
const Constant *GV = MO.getGlobal();
if (const auto *GA = dyn_cast<GlobalAlias>(GV))
@ -252,8 +252,7 @@ static void query(const MachineInstr &MI, AliasAnalysis &AA, bool &Read,
// Analyze calls.
if (MI.isCall()) {
unsigned CalleeOpNo = WebAssembly::getCalleeOpNo(MI.getOpcode());
queryCallee(MI, CalleeOpNo, Read, Write, Effects, StackPointer);
queryCallee(MI, Read, Write, Effects, StackPointer);
}
}

View File

@ -47,7 +47,7 @@ public:
TargetTransformInfo getTargetTransformInfo(const Function &F) override;
bool usesPhysRegsForPEI() const override { return false; }
bool usesPhysRegsForValues() const override { return false; }
yaml::MachineFunctionInfo *createDefaultFuncInfoYAML() const override;
yaml::MachineFunctionInfo *

View File

@ -49,7 +49,7 @@ bool WebAssembly::mayThrow(const MachineInstr &MI) {
if (!MI.isCall())
return false;
const MachineOperand &MO = MI.getOperand(getCalleeOpNo(MI.getOpcode()));
const MachineOperand &MO = getCalleeOp(MI);
assert(MO.isGlobal() || MO.isSymbol());
if (MO.isSymbol()) {
@ -79,3 +79,67 @@ bool WebAssembly::mayThrow(const MachineInstr &MI) {
// original LLVm IR? (Even when the callee may throw)
return true;
}
inline const MachineOperand &getCalleeOp(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case WebAssembly::CALL_VOID:
case WebAssembly::CALL_VOID_S:
case WebAssembly::CALL_INDIRECT_VOID:
case WebAssembly::CALL_INDIRECT_VOID_S:
case WebAssembly::RET_CALL:
case WebAssembly::RET_CALL_S:
case WebAssembly::RET_CALL_INDIRECT:
case WebAssembly::RET_CALL_INDIRECT_S:
return MI.getOperand(0);
case WebAssembly::CALL_i32:
case WebAssembly::CALL_i32_S:
case WebAssembly::CALL_i64:
case WebAssembly::CALL_i64_S:
case WebAssembly::CALL_f32:
case WebAssembly::CALL_f32_S:
case WebAssembly::CALL_f64:
case WebAssembly::CALL_f64_S:
case WebAssembly::CALL_v16i8:
case WebAssembly::CALL_v16i8_S:
case WebAssembly::CALL_v8i16:
case WebAssembly::CALL_v8i16_S:
case WebAssembly::CALL_v4i32:
case WebAssembly::CALL_v4i32_S:
case WebAssembly::CALL_v2i64:
case WebAssembly::CALL_v2i64_S:
case WebAssembly::CALL_v4f32:
case WebAssembly::CALL_v4f32_S:
case WebAssembly::CALL_v2f64:
case WebAssembly::CALL_v2f64_S:
case WebAssembly::CALL_exnref:
case WebAssembly::CALL_exnref_S:
case WebAssembly::CALL_INDIRECT_i32:
case WebAssembly::CALL_INDIRECT_i32_S:
case WebAssembly::CALL_INDIRECT_i64:
case WebAssembly::CALL_INDIRECT_i64_S:
case WebAssembly::CALL_INDIRECT_f32:
case WebAssembly::CALL_INDIRECT_f32_S:
case WebAssembly::CALL_INDIRECT_f64:
case WebAssembly::CALL_INDIRECT_f64_S:
case WebAssembly::CALL_INDIRECT_v16i8:
case WebAssembly::CALL_INDIRECT_v16i8_S:
case WebAssembly::CALL_INDIRECT_v8i16:
case WebAssembly::CALL_INDIRECT_v8i16_S:
case WebAssembly::CALL_INDIRECT_v4i32:
case WebAssembly::CALL_INDIRECT_v4i32_S:
case WebAssembly::CALL_INDIRECT_v2i64:
case WebAssembly::CALL_INDIRECT_v2i64_S:
case WebAssembly::CALL_INDIRECT_v4f32:
case WebAssembly::CALL_INDIRECT_v4f32_S:
case WebAssembly::CALL_INDIRECT_v2f64:
case WebAssembly::CALL_INDIRECT_v2f64_S:
case WebAssembly::CALL_INDIRECT_exnref:
case WebAssembly::CALL_INDIRECT_exnref_S:
return MI.getOperand(1);
case WebAssembly::CALL:
case WebAssembly::CALL_S:
return MI.getOperand(MI.getNumDefs());
default:
llvm_unreachable("Not a call instruction");
}
}

View File

@ -44,6 +44,10 @@ template <typename T> MachineBasicBlock *getBottom(const T *Unit) {
return Bottom;
}
/// Returns the operand number of a callee, assuming the argument is a call
/// instruction.
const MachineOperand &getCalleeOp(const MachineInstr &MI);
} // end namespace WebAssembly
} // end namespace llvm

View File

@ -1,29 +1,116 @@
; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+multivalue | FileCheck %s
; RUN: llc < %s --filetype=obj -mattr=+multivalue | obj2yaml | FileCheck %s --check-prefix OBJ
; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+multivalue,+tail-call | FileCheck %s
; RUN: llc < %s --filetype=obj -mattr=+multivalue,+tail-call | obj2yaml | FileCheck %s --check-prefix OBJ
; Test that the multivalue returns, function types, and block types
; work as expected.
; Test that the multivalue calls, returns, function types, and block
; types work as expected.
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"
%pair = type { i32, i32 }
%packed_pair = type <{ i32, i32 }>
%pair = type { i32, i64 }
%packed_pair = type <{ i32, i64 }>
; CHECK-LABEL: pair_const:
; CHECK-NEXT: .functype pair_const () -> (i32, i64)
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 42{{$}}
; CHECK-NEXT: i64.const $push[[L1:[0-9]+]]=, 42{{$}}
; CHECK-NEXT: return $pop[[L0]], $pop[[L1]]{{$}}
define %pair @pair_const() {
ret %pair { i32 42, i64 42 }
}
; CHECK-LABEL: packed_pair_const:
; CHECK-NEXT: .functype packed_pair_const () -> (i32, i64)
; CHECK-NEXT: i32.const $push[[L0:[0-9]+]]=, 42{{$}}
; CHECK-NEXT: i64.const $push[[L1:[0-9]+]]=, 42{{$}}
; CHECK-NEXT: return $pop[[L0]], $pop[[L1]]{{$}}
define %packed_pair @packed_pair_const() {
ret %packed_pair <{ i32 42, i64 42 }>
}
; CHECK-LABEL: pair_ident:
; CHECK-NEXT: .functype pair_ident (i32, i32) -> (i32, i32)
; CHECK-NEXT: .functype pair_ident (i32, i64) -> (i32, i64)
; CHECK-NEXT: return $0, $1{{$}}
define %pair @pair_ident(%pair %p) {
ret %pair %p
}
; CHECK-LABEL: packed_pair_ident:
; CHECK-NEXT: .functype packed_pair_ident (i32, i32) -> (i32, i32)
; CHECK-NEXT: .functype packed_pair_ident (i32, i64) -> (i32, i64)
; CHECK-NEXT: return $0, $1{{$}}
define %packed_pair @packed_pair_ident(%packed_pair %p) {
ret %packed_pair %p
}
;; TODO: Multivalue calls are a WIP and do not necessarily produce
;; correct output. For now, just check that they don't cause any
;; crashes.
define void @pair_call() {
%p = call %pair @pair_const()
ret void
}
define void @packed_pair_call() {
%p = call %packed_pair @packed_pair_const()
ret void
}
define %pair @pair_call_return() {
%p = call %pair @pair_const()
ret %pair %p
}
define %packed_pair @packed_pair_call_return() {
%p = call %packed_pair @packed_pair_const()
ret %packed_pair %p
}
define %pair @pair_tail_call() {
%p = musttail call %pair @pair_const()
ret %pair %p
}
define %packed_pair @packed_pair_tail_call() {
%p = musttail call %packed_pair @packed_pair_const()
ret %packed_pair %p
}
define i32 @pair_call_return_first() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 0
ret i32 %v
}
define i32 @packed_pair_call_return_first() {
%p = call %packed_pair @packed_pair_const()
%v = extractvalue %packed_pair %p, 0
ret i32 %v
}
define i64 @pair_call_return_second() {
%p = call %pair @pair_const()
%v = extractvalue %pair %p, 1
ret i64 %v
}
define i64 @packed_pair_call_return_second() {
%p = call %packed_pair @packed_pair_const()
%v = extractvalue %packed_pair %p, 1
ret i64 %v
}
define %pair @pair_pass_through(%pair %p) {
%r = call %pair @pair_ident(%pair %p)
ret %pair %r
}
define %packed_pair @packed_pair_pass_through(%packed_pair %p) {
%r = call %packed_pair @packed_pair_ident(%packed_pair %p)
ret %packed_pair %r
}
; CHECK-LABEL: minimal_loop:
; CHECK-NEXT: .functype minimal_loop (i32) -> (i32, i64)
; CHECK-NEXT: .LBB{{[0-9]+}}_1:
@ -31,7 +118,7 @@ define %packed_pair @packed_pair_ident(%packed_pair %p) {
; CHECK-NEXT: br 0{{$}}
; CHECK-NEXT: .LBB{{[0-9]+}}_2:
; CHECK-NEXT: end_loop{{$}}
define {i32, i64} @minimal_loop(i32* %p) {
define %pair @minimal_loop(i32* %p) {
entry:
br label %loop
loop:
@ -39,23 +126,42 @@ loop:
}
; CHECK-LABEL: .section .custom_section.target_features
; CHECK-NEXT: .int8 1
; 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: - I32
; OBJ-NEXT: - I32
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; 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: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - Index: 4
; OBJ-NEXT: ParamTypes: []
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I64
; OBJ-NEXT: - Index: 5
; OBJ-NEXT: ParamTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: ReturnTypes:
; OBJ-NEXT: - I32
; OBJ-NEXT: - I64