mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-23 03:02:36 +01:00
[WebAssembly][InstrEmitter] Foundation for multivalue call lowering
Summary: WebAssembly is unique among upstream targets in that it does not at any point use physical registers to store values. Instead, it uses virtual registers to model positions in its value stack. This means that some target-independent lowering activities that would use physical registers need to use virtual registers instead for WebAssembly and similar downstream targets. This CL generalizes the existing `usesPhysRegsForPEI` lowering hook to `usesPhysRegsForValues` in preparation for using it in more places. One such place is in InstrEmitter for instructions that have variadic defs. On register machines, it only makes sense for these defs to be physical registers, but for WebAssembly they must be virtual registers like any other values. This CL changes InstrEmitter to check the new target lowering hook to determine whether variadic defs should be physical or virtual registers. These changes are necessary to support a generalized CALL instruction for WebAssembly that is capable of returning an arbitrary number of arguments. Fully implementing that instruction will require additional changes that are described in comments here but left for a follow up commit. Reviewers: aheejin, dschuff, qcolombet Subscribers: sbc100, jgravelle-google, hiraditya, sunfish, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D71484
This commit is contained in:
parent
024d2bb279
commit
7987888a3f
@ -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));
|
||||
@ -828,7 +831,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;
|
||||
|
@ -702,9 +702,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;
|
||||
|
||||
@ -850,18 +847,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
Block a user