diff --git a/llvm/docs/CodeGenerator.rst b/llvm/docs/CodeGenerator.rst --- a/llvm/docs/CodeGenerator.rst +++ b/llvm/docs/CodeGenerator.rst @@ -2075,7 +2075,8 @@ ---------------------- Tail call optimization, callee reusing the stack of the caller, is currently -supported on x86/x86-64 and PowerPC. It is performed if: +supported on x86/x86-64, PowerPC, and WebAssembly. It is performed on x86/x86-64 +and PowerPC if: * Caller and callee have the calling convention ``fastcc``, ``cc 10`` (GHC calling convention) or ``cc 11`` (HiPE calling convention). @@ -2103,6 +2104,10 @@ * On ppc32/64 GOT/PIC only module-local calls (visibility = hidden or protected) are supported. +On WebAssembly, tail calls are lowered to ``return_call`` and +``return_call_indirect`` instructions whenever the 'tail-call' target attribute +is enabled. + Example: Call as ``llc -tailcallopt test.ll``. diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCallIndirectFixup.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCallIndirectFixup.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyCallIndirectFixup.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyCallIndirectFixup.cpp @@ -65,14 +65,14 @@ using namespace WebAssembly; case PCALL_INDIRECT_VOID: return CALL_INDIRECT_VOID; - case PCALL_INDIRECT_I32: - return CALL_INDIRECT_I32; - case PCALL_INDIRECT_I64: - return CALL_INDIRECT_I64; - case PCALL_INDIRECT_F32: - return CALL_INDIRECT_F32; - case PCALL_INDIRECT_F64: - return CALL_INDIRECT_F64; + case PCALL_INDIRECT_i32: + return CALL_INDIRECT_i32; + case PCALL_INDIRECT_i64: + return CALL_INDIRECT_i64; + case PCALL_INDIRECT_f32: + return CALL_INDIRECT_f32; + case PCALL_INDIRECT_f64: + return CALL_INDIRECT_f64; case PCALL_INDIRECT_v16i8: return CALL_INDIRECT_v16i8; case PCALL_INDIRECT_v8i16: @@ -85,6 +85,10 @@ return CALL_INDIRECT_v4f32; case PCALL_INDIRECT_v2f64: return CALL_INDIRECT_v2f64; + case PCALL_INDIRECT_ExceptRef: + return CALL_INDIRECT_ExceptRef; + case PRET_CALL_INDIRECT: + return RET_CALL_INDIRECT; default: return INSTRUCTION_LIST_END; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp @@ -741,6 +741,7 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) { const auto *Call = cast(I); + // TODO: Support tail calls in FastISel if (Call->isMustTailCall() || Call->isInlineAsm() || Call->getFunctionType()->isVarArg()) return false; @@ -769,19 +770,19 @@ case MVT::i8: case MVT::i16: case MVT::i32: - Opc = IsDirect ? WebAssembly::CALL_I32 : WebAssembly::PCALL_INDIRECT_I32; + Opc = IsDirect ? WebAssembly::CALL_i32 : WebAssembly::PCALL_INDIRECT_i32; ResultReg = createResultReg(&WebAssembly::I32RegClass); break; case MVT::i64: - Opc = IsDirect ? WebAssembly::CALL_I64 : WebAssembly::PCALL_INDIRECT_I64; + Opc = IsDirect ? WebAssembly::CALL_i64 : WebAssembly::PCALL_INDIRECT_i64; ResultReg = createResultReg(&WebAssembly::I64RegClass); break; case MVT::f32: - Opc = IsDirect ? WebAssembly::CALL_F32 : WebAssembly::PCALL_INDIRECT_F32; + Opc = IsDirect ? WebAssembly::CALL_f32 : WebAssembly::PCALL_INDIRECT_f32; ResultReg = createResultReg(&WebAssembly::F32RegClass); break; case MVT::f64: - Opc = IsDirect ? WebAssembly::CALL_F64 : WebAssembly::PCALL_INDIRECT_F64; + Opc = IsDirect ? WebAssembly::CALL_f64 : WebAssembly::PCALL_INDIRECT_f64; ResultReg = createResultReg(&WebAssembly::F64RegClass); break; case MVT::v16i8: @@ -815,8 +816,8 @@ ResultReg = createResultReg(&WebAssembly::V128RegClass); break; case MVT::ExceptRef: - Opc = IsDirect ? WebAssembly::CALL_EXCEPT_REF - : WebAssembly::PCALL_INDIRECT_EXCEPT_REF; + Opc = IsDirect ? WebAssembly::CALL_ExceptRef + : WebAssembly::PCALL_INDIRECT_ExceptRef; ResultReg = createResultReg(&WebAssembly::EXCEPT_REFRegClass); break; default: diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def --- a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def @@ -15,6 +15,7 @@ HANDLE_NODETYPE(CALL1) HANDLE_NODETYPE(CALL0) +HANDLE_NODETYPE(RET_CALL) HANDLE_NODETYPE(RETURN) HANDLE_NODETYPE(ARGUMENT) // A wrapper node for TargetExternalSymbol, TargetGlobalAddress, and MCSymbol diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -643,13 +643,14 @@ if (CLI.IsPatchPoint) fail(DL, DAG, "WebAssembly doesn't support patch point yet"); - // WebAssembly doesn't currently support explicit tail calls. If they are - // required, fail. Otherwise, just disable them. - if ((CallConv == CallingConv::Fast && CLI.IsTailCall && - MF.getTarget().Options.GuaranteedTailCallOpt) || - (CLI.CS && CLI.CS.isMustTailCall())) - fail(DL, DAG, "WebAssembly doesn't support tail call yet"); - CLI.IsTailCall = false; + // Fail if tail calls are required but not enabled + if (!Subtarget->hasTailCall()) { + if ((CallConv == CallingConv::Fast && CLI.IsTailCall && + MF.getTarget().Options.GuaranteedTailCallOpt) || + (CLI.CS && CLI.CS.isMustTailCall())) + fail(DL, DAG, "WebAssembly 'tail-call' feature not enabled"); + CLI.IsTailCall = false; + } SmallVectorImpl &Ins = CLI.Ins; if (Ins.size() > 1) @@ -782,12 +783,21 @@ // registers. InTys.push_back(In.VT); } - InTys.push_back(MVT::Other); + + if (CLI.IsTailCall) { + // return_call doesn't return to the current frame + InTys.clear(); + InTys.push_back(MVT::Other); + InTys.push_back(MVT::Glue); + } else { + 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()) { + unsigned Opcode = CLI.IsTailCall ? WebAssemblyISD::RET_CALL + : (Ins.empty() ? WebAssemblyISD::CALL0 + : WebAssemblyISD::CALL1); + SDValue Res = DAG.getNode(Opcode, DL, InTyList, Ops); + if (CLI.IsTailCall || Ins.empty()) { Chain = Res; } else { InVals.push_back(Res); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td @@ -23,106 +23,110 @@ [(WebAssemblycallseq_end timm:$amt, timm:$amt2)]>; } // Uses = [SP32, SP64], Defs = [SP32, SP64], isCodeGenOnly = 1 -multiclass CALL { - defm CALL_#vt : I<(outs vt:$dst), (ins function32_op:$callee, variable_ops), - (outs), (ins function32_op:$callee), - [(set vt:$dst, (WebAssemblycall1 (i32 imm:$callee)))], - !strconcat(prefix, "call\t$dst, $callee"), - !strconcat(prefix, "call\t$callee"), - 0x10>; +multiclass CALL preds = []> { + defm CALL_#vt : + I<(outs rt:$dst), (ins function32_op:$callee, variable_ops), + (outs), (ins function32_op:$callee), + [(set (vt rt:$dst), (WebAssemblycall1 (i32 imm:$callee)))], + !strconcat(prefix, "call\t$dst, $callee"), + !strconcat(prefix, "call\t$callee"), + 0x10>, + Requires; let isCodeGenOnly = 1 in - defm PCALL_INDIRECT_#vt : I<(outs vt:$dst), (ins I32:$callee, variable_ops), - (outs), (ins I32:$callee), - [(set vt:$dst, (WebAssemblycall1 I32:$callee))], - "PSEUDO CALL INDIRECT\t$callee", - "PSEUDO CALL INDIRECT\t$callee">; + defm PCALL_INDIRECT_#vt : + I<(outs rt:$dst), (ins I32:$callee, variable_ops), + (outs), (ins I32:$callee), + [(set (vt rt:$dst), (WebAssemblycall1 I32:$callee))], + "PSEUDO CALL INDIRECT\t$callee", + "PSEUDO CALL INDIRECT\t$callee">, + Requires; - defm CALL_INDIRECT_#vt : I<(outs vt:$dst), - (ins TypeIndex:$type, i32imm:$flags, variable_ops), - (outs), (ins TypeIndex:$type, i32imm:$flags), - [], - !strconcat(prefix, "call_indirect\t$dst"), - !strconcat(prefix, "call_indirect\t$type"), - 0x11>; + defm CALL_INDIRECT_#vt : + I<(outs rt:$dst), + (ins TypeIndex:$type, i32imm:$flags, variable_ops), + (outs), (ins TypeIndex:$type, i32imm:$flags), + [], + !strconcat(prefix, "call_indirect\t$dst"), + !strconcat(prefix, "call_indirect\t$type"), + 0x11>, + Requires; } -multiclass SIMD_CALL { +let Uses = [SP32, SP64], isCall = 1 in { +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; +defm "" : CALL; - defm CALL_#vt : I<(outs V128:$dst), (ins function32_op:$callee, variable_ops), - (outs), (ins function32_op:$callee), - [(set (vt V128:$dst), - (WebAssemblycall1 (i32 imm:$callee)))], - !strconcat(prefix, "call\t$dst, $callee"), - !strconcat(prefix, "call\t$callee"), - 0x10>, - Requires<[HasSIMD128]>; +let IsCanonical = 1 in { +defm CALL_VOID : + I<(outs), (ins function32_op:$callee, variable_ops), + (outs), (ins function32_op:$callee), + [(WebAssemblycall0 (i32 imm:$callee))], + "call \t$callee", "call\t$callee", 0x10>; - let isCodeGenOnly = 1 in - defm PCALL_INDIRECT_#vt : I<(outs V128:$dst), - (ins I32:$callee, variable_ops), - (outs), (ins I32:$callee), - [(set (vt V128:$dst), - (WebAssemblycall1 I32:$callee))], - "PSEUDO CALL INDIRECT\t$callee", - "PSEUDO CALL INDIRECT\t$callee">, - Requires<[HasSIMD128]>; +let isReturn = 1 in +defm RET_CALL : + I<(outs), (ins function32_op:$callee, variable_ops), + (outs), (ins function32_op:$callee), + [(WebAssemblyretcall (i32 imm:$callee))], + "return_call \t$callee", "return_call\t$callee", 0x12>, + Requires<[HasTailCall]>; - defm CALL_INDIRECT_#vt : I<(outs V128:$dst), - (ins TypeIndex:$type, i32imm:$flags, variable_ops), - (outs), (ins TypeIndex:$type, i32imm:$flags), - [], - !strconcat(prefix, "call_indirect\t$dst"), - !strconcat(prefix, "call_indirect\t$type"), - 0x11>, - Requires<[HasSIMD128]>; -} +let isCodeGenOnly = 1 in +defm PCALL_INDIRECT_VOID : + I<(outs), (ins I32:$callee, variable_ops), + (outs), (ins I32:$callee), + [(WebAssemblycall0 I32:$callee)], + "PSEUDO CALL INDIRECT\t$callee", + "PSEUDO CALL INDIRECT\t$callee">; -let Uses = [SP32, SP64], isCall = 1 in { -defm "" : CALL; -defm "" : CALL; -defm "" : CALL; -defm "" : CALL; -defm "" : CALL; -defm "" : SIMD_CALL; -defm "" : SIMD_CALL; -defm "" : SIMD_CALL; -defm "" : SIMD_CALL; -defm "" : SIMD_CALL; -defm "" : SIMD_CALL; +defm CALL_INDIRECT_VOID : + I<(outs), (ins TypeIndex:$type, i32imm:$flags, variable_ops), + (outs), (ins TypeIndex:$type, i32imm:$flags), + [], + "call_indirect\t", "call_indirect\t$type", + 0x11>; -let IsCanonical = 1 in { -defm CALL_VOID : I<(outs), (ins function32_op:$callee, variable_ops), - (outs), (ins function32_op:$callee), - [(WebAssemblycall0 (i32 imm:$callee))], - "call \t$callee", "call\t$callee", 0x10>; +let isReturn = 1 in +defm RET_CALL_INDIRECT : + I<(outs), (ins TypeIndex:$type, i32imm:$flags, variable_ops), + (outs), (ins TypeIndex:$type, i32imm:$flags), + [], + "return_call_indirect\t", "return_call_indirect\t$type", + 0x13>, + Requires<[HasTailCall]>; -let isCodeGenOnly = 1 in -defm PCALL_INDIRECT_VOID : I<(outs), (ins I32:$callee, variable_ops), - (outs), (ins I32:$callee), - [(WebAssemblycall0 I32:$callee)], - "PSEUDO CALL INDIRECT\t$callee", - "PSEUDO CALL INDIRECT\t$callee">; +let isCodeGenOnly = 1, isReturn = 1 in +defm PRET_CALL_INDIRECT: + I<(outs), (ins I32:$callee, variable_ops), + (outs), (ins I32:$callee), + [(WebAssemblyretcall I32:$callee)], + "PSEUDO RET_CALL INDIRECT\t$callee", + "PSEUDO RET_CALL INDIRECT\t$callee">, + Requires<[HasTailCall]>; -defm CALL_INDIRECT_VOID : I<(outs), - (ins TypeIndex:$type, i32imm:$flags, - variable_ops), - (outs), (ins TypeIndex:$type, i32imm:$flags), - [], - "call_indirect\t", "call_indirect\t$type", - 0x11>; } // IsCanonical = 1 } // Uses = [SP32,SP64], isCall = 1 // Patterns for matching a direct call to a global address. def : Pat<(i32 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), - (CALL_I32 tglobaladdr:$callee)>; + (CALL_i32 tglobaladdr:$callee)>; def : Pat<(i64 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), - (CALL_I64 tglobaladdr:$callee)>; + (CALL_i64 tglobaladdr:$callee)>; def : Pat<(f32 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), - (CALL_F32 tglobaladdr:$callee)>; + (CALL_f32 tglobaladdr:$callee)>; def : Pat<(f64 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), - (CALL_F64 tglobaladdr:$callee)>; + (CALL_f64 tglobaladdr:$callee)>; def : Pat<(v16i8 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), (CALL_v16i8 tglobaladdr:$callee)>, Requires<[HasSIMD128]>; def : Pat<(v8i16 (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), @@ -137,19 +141,22 @@ (CALL_v2f64 tglobaladdr:$callee)>, Requires<[HasSIMD128]>; def : Pat<(ExceptRef (WebAssemblycall1 (WebAssemblywrapper tglobaladdr:$callee))), - (CALL_EXCEPT_REF tglobaladdr:$callee)>; + (CALL_ExceptRef tglobaladdr:$callee)>, + Requires<[HasExceptionHandling]>; def : Pat<(WebAssemblycall0 (WebAssemblywrapper tglobaladdr:$callee)), (CALL_VOID tglobaladdr:$callee)>; +def : Pat<(WebAssemblyretcall (WebAssemblywrapper tglobaladdr:$callee)), + (RET_CALL tglobaladdr:$callee)>, Requires<[HasTailCall]>; // Patterns for matching a direct call to an external symbol. def : Pat<(i32 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), - (CALL_I32 texternalsym:$callee)>; + (CALL_i32 texternalsym:$callee)>; def : Pat<(i64 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), - (CALL_I64 texternalsym:$callee)>; + (CALL_i64 texternalsym:$callee)>; def : Pat<(f32 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), - (CALL_F32 texternalsym:$callee)>; + (CALL_f32 texternalsym:$callee)>; def : Pat<(f64 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), - (CALL_F64 texternalsym:$callee)>; + (CALL_f64 texternalsym:$callee)>; def : Pat<(v16i8 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), (CALL_v16i8 texternalsym:$callee)>, Requires<[HasSIMD128]>; def : Pat<(v8i16 (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), @@ -164,6 +171,9 @@ (CALL_v2f64 texternalsym:$callee)>, Requires<[HasSIMD128]>; def : Pat<(ExceptRef (WebAssemblycall1 (WebAssemblywrapper texternalsym:$callee))), - (CALL_EXCEPT_REF texternalsym:$callee)>; + (CALL_ExceptRef texternalsym:$callee)>, + Requires<[HasExceptionHandling]>; def : Pat<(WebAssemblycall0 (WebAssemblywrapper texternalsym:$callee)), (CALL_VOID texternalsym:$callee)>; +def : Pat<(WebAssemblyretcall (WebAssemblywrapper texternalsym:$callee)), + (RET_CALL texternalsym:$callee)>, Requires<[HasTailCall]>; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td @@ -97,6 +97,9 @@ def WebAssemblycall1 : SDNode<"WebAssemblyISD::CALL1", SDT_WebAssemblyCall1, [SDNPHasChain, SDNPVariadic]>; +def WebAssemblyretcall : SDNode<"WebAssemblyISD::RET_CALL", + SDT_WebAssemblyCall0, + [SDNPHasChain, SDNPVariadic]>; def WebAssemblybr_table : SDNode<"WebAssemblyISD::BR_TABLE", SDT_WebAssemblyBrTable, [SDNPHasChain, SDNPVariadic]>; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMemIntrinsicResults.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMemIntrinsicResults.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyMemIntrinsicResults.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMemIntrinsicResults.cpp @@ -200,8 +200,8 @@ switch (MI.getOpcode()) { default: break; - case WebAssembly::CALL_I32: - case WebAssembly::CALL_I64: + case WebAssembly::CALL_i32: + case WebAssembly::CALL_i64: Changed |= optimizeCall(MBB, MI, MRI, MDT, LIS, TLI, LibInfo); break; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyPeephole.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyPeephole.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyPeephole.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyPeephole.cpp @@ -128,8 +128,8 @@ switch (MI.getOpcode()) { default: break; - case WebAssembly::CALL_I32: - case WebAssembly::CALL_I64: { + case WebAssembly::CALL_i32: + case WebAssembly::CALL_i64: { MachineOperand &Op1 = MI.getOperand(1); if (Op1.isSymbol()) { StringRef Name(Op1.getSymbolName()); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp @@ -107,14 +107,14 @@ switch (MI.getOpcode()) { case WebAssembly::CALL_VOID: case WebAssembly::CALL_VOID_S: - 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_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: @@ -127,8 +127,10 @@ case WebAssembly::CALL_v4f32_S: case WebAssembly::CALL_v2f64: case WebAssembly::CALL_v2f64_S: - case WebAssembly::CALL_EXCEPT_REF: - case WebAssembly::CALL_EXCEPT_REF_S: + case WebAssembly::CALL_ExceptRef: + case WebAssembly::CALL_ExceptRef_S: + case WebAssembly::RET_CALL: + case WebAssembly::RET_CALL_S: return true; default: return false; @@ -139,14 +141,14 @@ switch (MI.getOpcode()) { case WebAssembly::CALL_INDIRECT_VOID: case WebAssembly::CALL_INDIRECT_VOID_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_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: @@ -159,8 +161,10 @@ case WebAssembly::CALL_INDIRECT_v4f32_S: case WebAssembly::CALL_INDIRECT_v2f64: case WebAssembly::CALL_INDIRECT_v2f64_S: - case WebAssembly::CALL_INDIRECT_EXCEPT_REF: - case WebAssembly::CALL_INDIRECT_EXCEPT_REF_S: + case WebAssembly::CALL_INDIRECT_ExceptRef: + case WebAssembly::CALL_INDIRECT_ExceptRef_S: + case WebAssembly::RET_CALL_INDIRECT: + case WebAssembly::RET_CALL_INDIRECT_S: return true; default: return false; @@ -173,15 +177,19 @@ 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 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_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: @@ -194,16 +202,16 @@ case WebAssembly::CALL_v4f32_S: case WebAssembly::CALL_v2f64: case WebAssembly::CALL_v2f64_S: - case WebAssembly::CALL_EXCEPT_REF: - case WebAssembly::CALL_EXCEPT_REF_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_ExceptRef: + case WebAssembly::CALL_ExceptRef_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: @@ -216,8 +224,8 @@ case WebAssembly::CALL_INDIRECT_v4f32_S: case WebAssembly::CALL_INDIRECT_v2f64: case WebAssembly::CALL_INDIRECT_v2f64_S: - case WebAssembly::CALL_INDIRECT_EXCEPT_REF: - case WebAssembly::CALL_INDIRECT_EXCEPT_REF_S: + case WebAssembly::CALL_INDIRECT_ExceptRef: + case WebAssembly::CALL_INDIRECT_ExceptRef_S: return 1; default: llvm_unreachable("Not a call instruction"); diff --git a/llvm/test/CodeGen/WebAssembly/call.ll b/llvm/test/CodeGen/WebAssembly/call.ll --- a/llvm/test/CodeGen/WebAssembly/call.ll +++ b/llvm/test/CodeGen/WebAssembly/call.ll @@ -1,5 +1,7 @@ -; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -mattr=+sign-ext,+simd128 | FileCheck %s -; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -fast-isel -fast-isel-abort=1 -mattr=+sign-ext,+simd128 | FileCheck %s +; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -mattr=+sign-ext,+simd128 | FileCheck --check-prefixes=CHECK,NO-TAIL,SLOW-NO-TAIL %s +; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -fast-isel -fast-isel-abort=1 -mattr=+sign-ext,+simd128 | FileCheck --check-prefixes=CHECK,NO-TAIL,FAST,FAST-NO-TAIL %s +; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -mattr=+sign-ext,+simd128,+tail-call | FileCheck --check-prefixes=CHECK,TAIL,SLOW-TAIL %s +; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-keep-registers -fast-isel -fast-isel-abort=1 -mattr=+sign-ext,+simd128,+tail-call | FileCheck --check-prefixes=CHECK,TAIL,FAST-TAIL %s ; Test that basic call operations assemble as expected. @@ -176,8 +178,11 @@ ; CHECK-LABEL: tail_call_void_nullary: ; CHECK-NEXT: .functype tail_call_void_nullary () -> (){{$}} -; CHECK-NEXT: {{^}} call void_nullary{{$}} -; CHECK-NEXT: return{{$}} +; NO-TAIL-NEXT: {{^}} call void_nullary{{$}} +; NO-TAIL-NEXT: return{{$}} +; SLOW-TAIL-NEXT: {{^}} return_call void_nullary{{$}} +; FAST-TAIL-NEXT: {{^}} call void_nullary{{$}} +; FAST-TAIL-NEXT: return{{$}} define void @tail_call_void_nullary() { tail call void @void_nullary() ret void @@ -185,8 +190,11 @@ ; CHECK-LABEL: fastcc_tail_call_void_nullary: ; CHECK-NEXT: .functype fastcc_tail_call_void_nullary () -> (){{$}} -; CHECK-NEXT: {{^}} call void_nullary{{$}} -; CHECK-NEXT: return{{$}} +; NO-TAIL-NEXT: {{^}} call void_nullary{{$}} +; NO-TAIL-NEXT: return{{$}} +; SLOW-TAIL-NEXT: {{^}} return_call void_nullary{{$}} +; FAST-TAIL-NEXT: {{^}} call void_nullary{{$}} +; FAST-TAIL-NEXT: return{{$}} define void @fastcc_tail_call_void_nullary() { tail call fastcc void @void_nullary() ret void @@ -194,8 +202,11 @@ ; CHECK-LABEL: coldcc_tail_call_void_nullary: ; CHECK-NEXT: .functype coldcc_tail_call_void_nullary () -> (){{$}} -; CHECK-NEXT: {{^}} call void_nullary{{$}} -; CHECK-NEXT: return{{$}} +; NO-TAIL-NEXT: {{^}} call void_nullary{{$}} +; NO-TAIL-NEXT: return{{$}} +; SLOW-TAIL-NEXT: {{^}} return_call void_nullary{{$}} +; FAST-TAIL-NEXT: {{^}} call void_nullary{{$}} +; FAST-TAIL-NEXT: return{{$}} define void @coldcc_tail_call_void_nullary() { tail call coldcc void @void_nullary() ret void diff --git a/llvm/test/CodeGen/WebAssembly/tailcall.ll b/llvm/test/CodeGen/WebAssembly/tailcall.ll --- a/llvm/test/CodeGen/WebAssembly/tailcall.ll +++ b/llvm/test/CodeGen/WebAssembly/tailcall.ll @@ -1,19 +1,114 @@ -; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+tail-call | FileCheck %s +; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+tail-call | FileCheck --check-prefixes=CHECK,SLOW %s +; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -fast-isel -mattr=+tail-call | FileCheck --check-prefixes=CHECK,FAST %s ; Test that the tail-call attribute is accepted -; TODO(tlively): implement tail call target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-unknown" +%fn = type <{i32 (%fn, i32, i32)*}> +declare i1 @foo(i1) +declare i1 @bar(i1) + +; CHECK-LABEL: recursive_notail_nullary: +; CHECK: {{^}} call recursive_notail_nullary{{$}} +; CHECK-NEXT: return +define void @recursive_notail_nullary() { + notail call void @recursive_notail_nullary() + ret void +} + +; CHECK-LABEL: recursive_musttail_nullary: +; CHECK: return_call recursive_musttail_nullary{{$}} +define void @recursive_musttail_nullary() { + musttail call void @recursive_musttail_nullary() + ret void +} + +; CHECK-LABEL: recursive_tail_nullary: +; SLOW: return_call recursive_tail_nullary{{$}} +; FAST: {{^}} call recursive_tail_nullary{{$}} +; FAST-NEXT: return{{$}} +define void @recursive_tail_nullary() { + tail call void @recursive_tail_nullary() + ret void +} + +; CHECK-LABEL: recursive_notail: +; CHECK: i32.call $push[[L:[0-9]+]]=, recursive_notail, $0, $1{{$}} +; CHECK-NEXT: return $pop[[L]]{{$}} +define i32 @recursive_notail(i32 %x, i32 %y) { + %v = notail call i32 @recursive_notail(i32 %x, i32 %y) + ret i32 %v +} + +; CHECK-LABEL: recursive_musttail: +; CHECK: return_call recursive_musttail, $0, $1{{$}} +define i32 @recursive_musttail(i32 %x, i32 %y) { + %v = musttail call i32 @recursive_musttail(i32 %x, i32 %y) + ret i32 %v +} + ; CHECK-LABEL: recursive_tail: -; CHECK: i32.call $push[[L0:[0-9]+]]=, recursive_tail{{$}} -; CHECK-NEXT: return $pop[[L0]]{{$}} -define i32 @recursive_tail() { - %v = tail call i32 @recursive_tail() +; SLOW: return_call recursive_tail, $0, $1{{$}} +; FAST: i32.call $push[[L:[0-9]+]]=, recursive_tail, $0, $1{{$}} +; FAST-NEXT: return $pop[[L]]{{$}} +define i32 @recursive_tail(i32 %x, i32 %y) { + %v = tail call i32 @recursive_tail(i32 %x, i32 %y) ret i32 %v } +; CHECK-LABEL: indirect_notail: +; CHECK: i32.call_indirect $push[[L:[0-9]+]]=, $0, $1, $2, $0{{$}} +; CHECK-NEXT: return $pop[[L]]{{$}} +define i32 @indirect_notail(%fn %f, i32 %x, i32 %y) { + %p = extractvalue %fn %f, 0 + %v = notail call i32 %p(%fn %f, i32 %x, i32 %y) + ret i32 %v +} + +; CHECK-LABEL: indirect_musttail: +; CHECK: return_call_indirect , $0, $1, $2, $0{{$}} +define i32 @indirect_musttail(%fn %f, i32 %x, i32 %y) { + %p = extractvalue %fn %f, 0 + %v = musttail call i32 %p(%fn %f, i32 %x, i32 %y) + ret i32 %v +} + +; CHECK-LABEL: indirect_tail: +; CHECK: return_call_indirect , $0, $1, $2, $0{{$}} +define i32 @indirect_tail(%fn %f, i32 %x, i32 %y) { + %p = extractvalue %fn %f, 0 + %v = tail call i32 %p(%fn %f, i32 %x, i32 %y) + ret i32 %v +} + +; CHECK-LABEL: choice_notail: +; CHECK: i32.call_indirect $push[[L:[0-9]+]]=, $0, $pop{{[0-9]+}}{{$}} +; CHECK-NEXT: return $pop[[L]]{{$}} +define i1 @choice_notail(i1 %x) { + %p = select i1 %x, i1 (i1)* @foo, i1 (i1)* @bar + %v = notail call i1 %p(i1 %x) + ret i1 %v +} + +; CHECK-LABEL: choice_musttail: +; CHECK: return_call_indirect , $0, $pop{{[0-9]+}}{{$}} +define i1 @choice_musttail(i1 %x) { + %p = select i1 %x, i1 (i1)* @foo, i1 (i1)* @bar + %v = musttail call i1 %p(i1 %x) + ret i1 %v +} + +; CHECK-LABEL: choice_tail: +; SLOW: return_call_indirect , $0, $pop{{[0-9]+}}{{$}} +; FAST: i32.call_indirect $push[[L:[0-9]+]]=, $0, $pop{{[0-9]+}}{{$}} +; FAST: return $pop[[L]]{{$}} +define i1 @choice_tail(i1 %x) { + %p = select i1 %x, i1 (i1)* @foo, i1 (i1)* @bar + %v = tail call i1 %p(i1 %x) + ret i1 %v +} ; CHECK-LABEL: .section .custom_section.target_features ; CHECK-NEXT: .int8 1 ; CHECK-NEXT: .int8 43 diff --git a/llvm/test/DebugInfo/WebAssembly/dbg-value-move-reg-stackify.mir b/llvm/test/DebugInfo/WebAssembly/dbg-value-move-reg-stackify.mir --- a/llvm/test/DebugInfo/WebAssembly/dbg-value-move-reg-stackify.mir +++ b/llvm/test/DebugInfo/WebAssembly/dbg-value-move-reg-stackify.mir @@ -3,9 +3,9 @@ # CHECK: body: # CHECK: %1:i32 = I32_WRAP_I64 %0, # CHECK-NEXT: DBG_VALUE %1, -# CHECK-NEXT: %1:i32 = CALL_I32 @bar, +# CHECK-NEXT: %1:i32 = CALL_i32 @bar, # CHECK-NEXT: DBG_VALUE %1, -# CHECK-NEXT: %[[NEWREG:.*]]:i32 = CALL_I32 @bar, +# CHECK-NEXT: %[[NEWREG:.*]]:i32 = CALL_i32 @bar, # CHECK-NEXT: DBG_VALUE %[[NEWREG]], # CHECK-NEXT: CALL_VOID @foo, %[[NEWREG]], @@ -50,9 +50,9 @@ %0:i64 = ARGUMENT_i64 0, implicit $arguments %1:i32 = I32_WRAP_I64 %0:i64, implicit-def dead $arguments DBG_VALUE %1:i32, $noreg, !10, !DIExpression(), debug-location !13; :357:12 line no:357 - %1:i32 = CALL_I32 @bar, implicit-def dead $arguments, implicit $sp32, implicit $sp64 + %1:i32 = CALL_i32 @bar, implicit-def dead $arguments, implicit $sp32, implicit $sp64 DBG_VALUE %1:i32, $noreg, !11, !DIExpression(), debug-location !14; :357:12 line no:357 - %1:i32 = CALL_I32 @bar, implicit-def dead $arguments, implicit $sp32, implicit $sp64 + %1:i32 = CALL_i32 @bar, implicit-def dead $arguments, implicit $sp32, implicit $sp64 DBG_VALUE %1:i32, $noreg, !12, !DIExpression(), debug-location !15; :357:12 line no:357 CALL_VOID @foo, %1:i32, implicit-def dead $arguments, implicit $sp32, implicit $sp64 RETURN_VOID implicit-def dead $arguments diff --git a/llvm/test/MC/WebAssembly/tail-call-encodings.s b/llvm/test/MC/WebAssembly/tail-call-encodings.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/WebAssembly/tail-call-encodings.s @@ -0,0 +1,22 @@ +# RUN: llvm-mc -show-encoding -triple=wasm32-unkown-unknown -mattr=+tail-call < %s | FileCheck %s + +bar1: + .functype bar1 () -> () + end_function + +foo1: + .functype foo1 () -> () + + # CHECK: return_call bar1 # encoding: [0x12, + # CHECK-NEXT: fixup A - offset: 1, value: bar1, kind: fixup_uleb128_i32 + return_call bar1 + + end_function + +foo2: + .functype foo2 () -> () + + # CHECK: return_call_indirect 0 # encoding: [0x13,0x00,0x00] + return_call_indirect 0 + + end_function