forked from OSchip/llvm-project
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:
parent
42cab985fd
commit
7b64a59060
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
HANDLE_NODETYPE(CALL1)
|
||||
HANDLE_NODETYPE(CALL0)
|
||||
HANDLE_NODETYPE(CALL)
|
||||
HANDLE_NODETYPE(RET_CALL)
|
||||
HANDLE_NODETYPE(RETURN)
|
||||
HANDLE_NODETYPE(ARGUMENT)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue