Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.h =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.h +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.h @@ -77,6 +77,7 @@ 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 { Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -118,6 +118,13 @@ 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 @@ } 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 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 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 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 Tys; for (const auto &In : Ins) { @@ -360,7 +411,8 @@ 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 @@ } SDValue WebAssemblyTargetLowering::LowerReturn( - SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, + SDValue Chain, CallingConv::ID CallConv, bool /*IsVarArg*/, const SmallVectorImpl &Outs, const SmallVectorImpl &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 RetOps(1, Chain); RetOps.append(OutVals.begin(), OutVals.end()); @@ -392,29 +442,26 @@ 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 &Ins, SDLoc DL, SelectionDAG &DAG, SmallVectorImpl &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 @@ MF.getInfo()->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 @@ return LowerJumpTable(Op, DAG); case ISD::BR_JT: return LowerBR_JT(Op, DAG); + case ISD::VASTART: + return LowerVASTART(Op, DAG); } } @@ -529,6 +581,24 @@ 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(Subtarget->getRegisterInfo()) + ->getFrameRegister(DAG.getMachineFunction()); + SDValue FrameAddr = DAG.getCopyFromReg(DAG.getEntryNode(), DL, FP, PtrVT); + const Value *SV = cast(Op.getOperand(2))->getValue(); + return DAG.getStore(Op.getOperand(0), DL, FrameAddr, Op.getOperand(1), + MachinePointerInfo(SV), false, false, 0); +} + //===----------------------------------------------------------------------===// // WebAssembly Optimization Hooks //===----------------------------------------------------------------------===// Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrCall.td =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrCall.td +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrCall.td @@ -19,8 +19,8 @@ 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 { Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyInstrInfo.cpp @@ -28,7 +28,9 @@ #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, Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp @@ -204,6 +204,10 @@ 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); Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.h =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.h +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.h @@ -41,6 +41,10 @@ // Debug information queries. unsigned getFrameRegister(const MachineFunction &MF) const override; + + const TargetRegisterClass * + getPointerRegClass(const MachineFunction &MF, + unsigned Kind = 0) const override; }; } // end namespace llvm Index: llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.cpp =================================================================== --- llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.cpp +++ llvm/trunk/lib/Target/WebAssembly/WebAssemblyRegisterInfo.cpp @@ -67,3 +67,12 @@ 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().hasAddr64()) + return &WebAssembly::I64RegClass; + return &WebAssembly::I32RegClass; +} Index: llvm/trunk/test/CodeGen/WebAssembly/varargs.ll =================================================================== --- llvm/trunk/test/CodeGen/WebAssembly/varargs.ll +++ llvm/trunk/test/CodeGen/WebAssembly/varargs.ll @@ -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*)