forked from OSchip/llvm-project
[WebAssembly] Initial varargs support.
Full varargs support will depend on prologue/epilogue support, but this patch gets us started with most of the basic infrastructure. Differential Revision: http://reviews.llvm.org/D15231 llvm-svn: 254799
This commit is contained in:
parent
b6761c2e1e
commit
35bfb24c28
|
@ -118,6 +118,13 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
|
|||
setOperationAction(ISD::ExternalSymbol, MVTPtr, Custom);
|
||||
setOperationAction(ISD::JumpTable, MVTPtr, Custom);
|
||||
|
||||
// Take the default expansion for va_arg, va_copy, and va_end. There is no
|
||||
// default action for va_start, so we do that custom.
|
||||
setOperationAction(ISD::VASTART, MVT::Other, Custom);
|
||||
setOperationAction(ISD::VAARG, MVT::Other, Expand);
|
||||
setOperationAction(ISD::VACOPY, MVT::Other, Expand);
|
||||
setOperationAction(ISD::VAEND, MVT::Other, Expand);
|
||||
|
||||
for (auto T : {MVT::f32, MVT::f64}) {
|
||||
// Don't expand the floating-point types to constant pools.
|
||||
setOperationAction(ISD::ConstantFP, T, Legal);
|
||||
|
@ -314,23 +321,67 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
|
|||
}
|
||||
|
||||
bool IsVarArg = CLI.IsVarArg;
|
||||
if (IsVarArg)
|
||||
fail(DL, DAG, "WebAssembly doesn't support varargs yet");
|
||||
unsigned NumFixedArgs = CLI.NumFixedArgs;
|
||||
auto PtrVT = getPointerTy(MF.getDataLayout());
|
||||
|
||||
// Analyze operands of the call, assigning locations to each operand.
|
||||
SmallVector<CCValAssign, 16> ArgLocs;
|
||||
CCState CCInfo(CallConv, IsVarArg, MF, ArgLocs, *DAG.getContext());
|
||||
unsigned NumBytes = CCInfo.getNextStackOffset();
|
||||
|
||||
auto PtrVT = getPointerTy(MF.getDataLayout());
|
||||
auto Zero = DAG.getConstant(0, DL, PtrVT, true);
|
||||
if (IsVarArg) {
|
||||
// Outgoing non-fixed arguments are placed at the top of the stack. First
|
||||
// compute their offsets and the total amount of argument stack space
|
||||
// needed.
|
||||
for (SDValue Arg :
|
||||
make_range(OutVals.begin() + NumFixedArgs, OutVals.end())) {
|
||||
EVT VT = Arg.getValueType();
|
||||
assert(VT != MVT::iPTR && "Legalized args should be concrete");
|
||||
Type *Ty = VT.getTypeForEVT(*DAG.getContext());
|
||||
unsigned Offset =
|
||||
CCInfo.AllocateStack(MF.getDataLayout().getTypeAllocSize(Ty),
|
||||
MF.getDataLayout().getABITypeAlignment(Ty));
|
||||
CCInfo.addLoc(CCValAssign::getMem(ArgLocs.size(), VT.getSimpleVT(),
|
||||
Offset, VT.getSimpleVT(),
|
||||
CCValAssign::Full));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned NumBytes = CCInfo.getAlignedCallFrameSize();
|
||||
|
||||
auto NB = DAG.getConstant(NumBytes, DL, PtrVT, true);
|
||||
Chain = DAG.getCALLSEQ_START(Chain, NB, DL);
|
||||
|
||||
if (IsVarArg) {
|
||||
// For non-fixed arguments, next emit stores to store the argument values
|
||||
// to the stack at the offsets computed above.
|
||||
SDValue SP = DAG.getCopyFromReg(
|
||||
Chain, DL, getStackPointerRegisterToSaveRestore(), PtrVT);
|
||||
unsigned ValNo = 0;
|
||||
SmallVector<SDValue, 8> Chains;
|
||||
for (SDValue Arg :
|
||||
make_range(OutVals.begin() + NumFixedArgs, OutVals.end())) {
|
||||
assert(ArgLocs[ValNo].getValNo() == ValNo &&
|
||||
"ArgLocs should remain in order and only hold varargs args");
|
||||
unsigned Offset = ArgLocs[ValNo++].getLocMemOffset();
|
||||
SDValue Add = DAG.getNode(ISD::ADD, DL, PtrVT, SP,
|
||||
DAG.getConstant(Offset, DL, PtrVT));
|
||||
Chains.push_back(DAG.getStore(Chain, DL, Arg, Add,
|
||||
MachinePointerInfo::getStack(MF, Offset),
|
||||
false, false, 0));
|
||||
}
|
||||
if (!Chains.empty())
|
||||
Chain = DAG.getNode(ISD::TokenFactor, DL, MVT::Other, Chains);
|
||||
}
|
||||
|
||||
// Compute the operands for the CALLn node.
|
||||
SmallVector<SDValue, 16> Ops;
|
||||
Ops.push_back(Chain);
|
||||
Ops.push_back(Callee);
|
||||
Ops.append(OutVals.begin(), OutVals.end());
|
||||
|
||||
// Add all fixed arguments. Note that for non-varargs calls, NumFixedArgs
|
||||
// isn't reliable.
|
||||
Ops.append(OutVals.begin(),
|
||||
IsVarArg ? OutVals.begin() + NumFixedArgs : OutVals.end());
|
||||
|
||||
SmallVector<EVT, 8> Tys;
|
||||
for (const auto &In : Ins) {
|
||||
|
@ -360,7 +411,8 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
|
|||
Chain = Res.getValue(1);
|
||||
}
|
||||
|
||||
Chain = DAG.getCALLSEQ_END(Chain, NB, Zero, SDValue(), DL);
|
||||
SDValue Unused = DAG.getUNDEF(PtrVT);
|
||||
Chain = DAG.getCALLSEQ_END(Chain, NB, Unused, SDValue(), DL);
|
||||
|
||||
return Chain;
|
||||
}
|
||||
|
@ -374,15 +426,13 @@ bool WebAssemblyTargetLowering::CanLowerReturn(
|
|||
}
|
||||
|
||||
SDValue WebAssemblyTargetLowering::LowerReturn(
|
||||
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
|
||||
SDValue Chain, CallingConv::ID CallConv, bool /*IsVarArg*/,
|
||||
const SmallVectorImpl<ISD::OutputArg> &Outs,
|
||||
const SmallVectorImpl<SDValue> &OutVals, SDLoc DL,
|
||||
SelectionDAG &DAG) const {
|
||||
assert(Outs.size() <= 1 && "WebAssembly can only return up to one value");
|
||||
if (!CallingConvSupported(CallConv))
|
||||
fail(DL, DAG, "WebAssembly doesn't support non-C calling conventions");
|
||||
if (IsVarArg)
|
||||
fail(DL, DAG, "WebAssembly doesn't support varargs yet");
|
||||
|
||||
SmallVector<SDValue, 4> RetOps(1, Chain);
|
||||
RetOps.append(OutVals.begin(), OutVals.end());
|
||||
|
@ -392,29 +442,26 @@ SDValue WebAssemblyTargetLowering::LowerReturn(
|
|||
for (const ISD::OutputArg &Out : Outs) {
|
||||
assert(!Out.Flags.isByVal() && "byval is not valid for return values");
|
||||
assert(!Out.Flags.isNest() && "nest is not valid for return values");
|
||||
assert(Out.IsFixed && "non-fixed return value is not valid");
|
||||
if (Out.Flags.isInAlloca())
|
||||
fail(DL, DAG, "WebAssembly hasn't implemented inalloca results");
|
||||
if (Out.Flags.isInConsecutiveRegs())
|
||||
fail(DL, DAG, "WebAssembly hasn't implemented cons regs results");
|
||||
if (Out.Flags.isInConsecutiveRegsLast())
|
||||
fail(DL, DAG, "WebAssembly hasn't implemented cons regs last results");
|
||||
if (!Out.IsFixed)
|
||||
fail(DL, DAG, "WebAssembly doesn't support non-fixed results yet");
|
||||
}
|
||||
|
||||
return Chain;
|
||||
}
|
||||
|
||||
SDValue WebAssemblyTargetLowering::LowerFormalArguments(
|
||||
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
|
||||
SDValue Chain, CallingConv::ID CallConv, bool /*IsVarArg*/,
|
||||
const SmallVectorImpl<ISD::InputArg> &Ins, SDLoc DL, SelectionDAG &DAG,
|
||||
SmallVectorImpl<SDValue> &InVals) const {
|
||||
MachineFunction &MF = DAG.getMachineFunction();
|
||||
|
||||
if (!CallingConvSupported(CallConv))
|
||||
fail(DL, DAG, "WebAssembly doesn't support non-C calling conventions");
|
||||
if (IsVarArg)
|
||||
fail(DL, DAG, "WebAssembly doesn't support varargs yet");
|
||||
|
||||
// Set up the incoming ARGUMENTS value, which serves to represent the liveness
|
||||
// of the incoming values before they're represented by virtual registers.
|
||||
|
@ -443,6 +490,9 @@ SDValue WebAssemblyTargetLowering::LowerFormalArguments(
|
|||
MF.getInfo<WebAssemblyFunctionInfo>()->addParam(In.VT);
|
||||
}
|
||||
|
||||
// Incoming varargs arguments are on the stack and will be accessed through
|
||||
// va_arg, so we don't need to do anything for them here.
|
||||
|
||||
return Chain;
|
||||
}
|
||||
|
||||
|
@ -464,6 +514,8 @@ SDValue WebAssemblyTargetLowering::LowerOperation(SDValue Op,
|
|||
return LowerJumpTable(Op, DAG);
|
||||
case ISD::BR_JT:
|
||||
return LowerBR_JT(Op, DAG);
|
||||
case ISD::VASTART:
|
||||
return LowerVASTART(Op, DAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,6 +581,24 @@ SDValue WebAssemblyTargetLowering::LowerBR_JT(SDValue Op,
|
|||
return DAG.getNode(WebAssemblyISD::TABLESWITCH, DL, MVT::Other, Ops);
|
||||
}
|
||||
|
||||
SDValue WebAssemblyTargetLowering::LowerVASTART(SDValue Op,
|
||||
SelectionDAG &DAG) const {
|
||||
SDLoc DL(Op);
|
||||
EVT PtrVT = getPointerTy(DAG.getMachineFunction().getDataLayout());
|
||||
|
||||
// The incoming non-fixed arguments are placed on the top of the stack, with
|
||||
// natural alignment, at the point of the call, so the base pointer is just
|
||||
// the current frame pointer.
|
||||
DAG.getMachineFunction().getFrameInfo()->setFrameAddressIsTaken(true);
|
||||
unsigned FP =
|
||||
static_cast<const WebAssemblyRegisterInfo *>(Subtarget->getRegisterInfo())
|
||||
->getFrameRegister(DAG.getMachineFunction());
|
||||
SDValue FrameAddr = DAG.getCopyFromReg(DAG.getEntryNode(), DL, FP, PtrVT);
|
||||
const Value *SV = cast<SrcValueSDNode>(Op.getOperand(2))->getValue();
|
||||
return DAG.getStore(Op.getOperand(0), DL, FrameAddr, Op.getOperand(1),
|
||||
MachinePointerInfo(SV), false, false, 0);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// WebAssembly Optimization Hooks
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -77,6 +77,7 @@ private:
|
|||
SDValue LowerExternalSymbol(SDValue Op, SelectionDAG &DAG) const;
|
||||
SDValue LowerBR_JT(SDValue Op, SelectionDAG &DAG) const;
|
||||
SDValue LowerJumpTable(SDValue Op, SelectionDAG &DAG) const;
|
||||
SDValue LowerVASTART(SDValue Op, SelectionDAG &DAG) const;
|
||||
};
|
||||
|
||||
namespace WebAssembly {
|
||||
|
|
|
@ -19,8 +19,8 @@ let Defs = [ARGUMENTS] in {
|
|||
let isCodeGenOnly = 1 in {
|
||||
def ADJCALLSTACKDOWN : I<(outs), (ins i64imm:$amt),
|
||||
[(WebAssemblycallseq_start timm:$amt)]>;
|
||||
def ADJCALLSTACKUP : I<(outs), (ins i64imm:$amt1, i64imm:$amt2),
|
||||
[(WebAssemblycallseq_end timm:$amt1, timm:$amt2)]>;
|
||||
def ADJCALLSTACKUP : I<(outs), (ins i64imm:$amt),
|
||||
[(WebAssemblycallseq_end timm:$amt, undef)]>;
|
||||
} // isCodeGenOnly = 1
|
||||
|
||||
multiclass CALL<WebAssemblyRegClass vt> {
|
||||
|
|
|
@ -28,7 +28,9 @@ using namespace llvm;
|
|||
#include "WebAssemblyGenInstrInfo.inc"
|
||||
|
||||
WebAssemblyInstrInfo::WebAssemblyInstrInfo(const WebAssemblySubtarget &STI)
|
||||
: RI(STI.getTargetTriple()) {}
|
||||
: WebAssemblyGenInstrInfo(WebAssembly::ADJCALLSTACKDOWN,
|
||||
WebAssembly::ADJCALLSTACKUP),
|
||||
RI(STI.getTargetTriple()) {}
|
||||
|
||||
void WebAssemblyInstrInfo::copyPhysReg(MachineBasicBlock &MBB,
|
||||
MachineBasicBlock::iterator I,
|
||||
|
|
|
@ -204,6 +204,10 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
|
|||
continue;
|
||||
unsigned VReg = MO.getReg();
|
||||
|
||||
// Don't stackify physregs like SP or FP.
|
||||
if (!TargetRegisterInfo::isVirtualRegister(VReg))
|
||||
continue;
|
||||
|
||||
if (MFI.isVRegStackified(VReg)) {
|
||||
if (MO.isDef())
|
||||
Stack.push_back(VReg);
|
||||
|
|
|
@ -67,3 +67,12 @@ WebAssemblyRegisterInfo::getFrameRegister(const MachineFunction &MF) const {
|
|||
const WebAssemblyFrameLowering *TFI = getFrameLowering(MF);
|
||||
return Regs[TFI->hasFP(MF)][TT.isArch64Bit()];
|
||||
}
|
||||
|
||||
const TargetRegisterClass *
|
||||
WebAssemblyRegisterInfo::getPointerRegClass(const MachineFunction &MF,
|
||||
unsigned Kind) const {
|
||||
assert(Kind == 0 && "Only one kind of pointer on WebAssembly");
|
||||
if (MF.getSubtarget<WebAssemblySubtarget>().hasAddr64())
|
||||
return &WebAssembly::I64RegClass;
|
||||
return &WebAssembly::I32RegClass;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ public:
|
|||
|
||||
// Debug information queries.
|
||||
unsigned getFrameRegister(const MachineFunction &MF) const override;
|
||||
|
||||
const TargetRegisterClass *
|
||||
getPointerRegClass(const MachineFunction &MF,
|
||||
unsigned Kind = 0) const override;
|
||||
};
|
||||
|
||||
} // end namespace llvm
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
; RUN: llc < %s -asm-verbose=false | FileCheck %s
|
||||
|
||||
; Test varargs constructs.
|
||||
|
||||
target datalayout = "e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown"
|
||||
|
||||
; Test va_start.
|
||||
|
||||
; TODO: Test va_start.
|
||||
|
||||
;define void @start(i8** %ap, ...) {
|
||||
;entry:
|
||||
; %0 = bitcast i8** %ap to i8*
|
||||
; call void @llvm.va_start(i8* %0)
|
||||
; ret void
|
||||
;}
|
||||
|
||||
; Test va_end.
|
||||
|
||||
; CHECK-LABEL: end:
|
||||
; CHECK-NEXT: .param i32{{$}}
|
||||
; CHECK-NEXT: return{{$}}
|
||||
define void @end(i8** %ap) {
|
||||
entry:
|
||||
%0 = bitcast i8** %ap to i8*
|
||||
call void @llvm.va_end(i8* %0)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test va_copy.
|
||||
|
||||
; CHECK-LABEL: copy:
|
||||
; CHECK-NEXT: .param i32, i32{{$}}
|
||||
; CHECK-NEXT: i32.load $push0=, $1{{$}}
|
||||
; CHECK-NEXT: i32.store $discard=, $0, $pop0{{$}}
|
||||
; CHECK-NEXT: return{{$}}
|
||||
define void @copy(i8** %ap, i8** %bp) {
|
||||
entry:
|
||||
%0 = bitcast i8** %ap to i8*
|
||||
%1 = bitcast i8** %bp to i8*
|
||||
call void @llvm.va_copy(i8* %0, i8* %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test va_arg with an i8 argument.
|
||||
|
||||
; CHECK-LABEL: arg_i8:
|
||||
; CHECK-NEXT: .param i32{{$}}
|
||||
; CHECK-NEXT: .result i32{{$}}
|
||||
; CHECK-NEXT: .local i32{{$}}
|
||||
; CHECK-NEXT: i32.load $1=, $0{{$}}
|
||||
; CHECK-NEXT: i32.const $push0=, 4{{$}}
|
||||
; CHECK-NEXT: i32.add $push1=, $1, $pop0{{$}}
|
||||
; CHECK-NEXT: i32.store $discard=, $0, $pop1{{$}}
|
||||
; CHECK-NEXT: i32.load $push2=, $1{{$}}
|
||||
; CHECK-NEXT: return $pop2{{$}}
|
||||
define i8 @arg_i8(i8** %ap) {
|
||||
entry:
|
||||
%t = va_arg i8** %ap, i8
|
||||
ret i8 %t
|
||||
}
|
||||
|
||||
; Test va_arg with an i32 argument.
|
||||
|
||||
; CHECK-LABEL: arg_i32:
|
||||
; CHECK-NEXT: .param i32{{$}}
|
||||
; CHECK-NEXT: .result i32{{$}}
|
||||
; CHECK-NEXT: .local i32{{$}}
|
||||
; CHECK-NEXT: i32.load $push0=, $0{{$}}
|
||||
; CHECK-NEXT: i32.const $push1=, 3{{$}}
|
||||
; CHECK-NEXT: i32.add $push2=, $pop0, $pop1{{$}}
|
||||
; CHECK-NEXT: i32.const $push3=, -4{{$}}
|
||||
; CHECK-NEXT: i32.and $1=, $pop2, $pop3{{$}}
|
||||
; CHECK-NEXT: i32.const $push4=, 4{{$}}
|
||||
; CHECK-NEXT: i32.add $push5=, $1, $pop4{{$}}
|
||||
; CHECK-NEXT: i32.store $discard=, $0, $pop5{{$}}
|
||||
; CHECK-NEXT: i32.load $push6=, $1{{$}}
|
||||
; CHECK-NEXT: return $pop6{{$}}
|
||||
define i32 @arg_i32(i8** %ap) {
|
||||
entry:
|
||||
%t = va_arg i8** %ap, i32
|
||||
ret i32 %t
|
||||
}
|
||||
|
||||
; Test va_arg with an i128 argument.
|
||||
|
||||
; CHECK-LABEL: arg_i128:
|
||||
; CHECK-NEXT: .param i32, i32{{$}}
|
||||
; CHECK-NEXT: .local
|
||||
; CHECK: i32.and
|
||||
; CHECK: i64.load
|
||||
; CHECK: i64.load
|
||||
; CHECK: return{{$}}
|
||||
define i128 @arg_i128(i8** %ap) {
|
||||
entry:
|
||||
%t = va_arg i8** %ap, i128
|
||||
ret i128 %t
|
||||
}
|
||||
|
||||
; Test a varargs call with no actual arguments.
|
||||
|
||||
declare void @callee(...)
|
||||
|
||||
; CHECK-LABEL: caller_none:
|
||||
; CHECK-NEXT: call callee{{$}}
|
||||
; CHECK-NEXT: return{{$}}
|
||||
define void @caller_none() {
|
||||
call void (...) @callee()
|
||||
ret void
|
||||
}
|
||||
|
||||
; TODO: Test a varargs call with actual arguments.
|
||||
|
||||
;define void @caller_some() {
|
||||
; call void (...) @callee(i32 0, double 2.0)
|
||||
; ret void
|
||||
;}
|
||||
|
||||
declare void @llvm.va_start(i8*)
|
||||
declare void @llvm.va_end(i8*)
|
||||
declare void @llvm.va_copy(i8*, i8*)
|
Loading…
Reference in New Issue