diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp
index 45d931af5529aa..dea39ad1a35c3b 100644
--- a/src/coreclr/interpreter/compiler.cpp
+++ b/src/coreclr/interpreter/compiler.cpp
@@ -2233,7 +2233,7 @@ void InterpCompiler::CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo)
default:
assert(0);
}
- if (opcode == CEE_THROW || opcode == CEE_ENDFINALLY || opcode == CEE_RETHROW)
+ if (opcode == CEE_THROW || opcode == CEE_ENDFINALLY || opcode == CEE_RETHROW || opcode == CEE_JMP)
GetBB((int32_t)(ip - codeStart));
}
}
@@ -3900,6 +3900,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
bool isVirtual = (*m_ip == CEE_CALLVIRT);
bool isDelegateInvoke = false;
+ bool isJmp = (*m_ip == CEE_JMP);
CORINFO_RESOLVED_TOKEN resolvedCallToken;
CORINFO_CALL_INFO callInfo;
@@ -3943,6 +3944,16 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
BADCODE("Vararg methods are not supported in interpreted code");
}
+ if (isJmp)
+ {
+ if (callInfo.sig.numArgs != m_methodInfo->args.numArgs ||
+ callInfo.sig.retType != m_methodInfo->args.retType ||
+ callInfo.sig.callConv != m_methodInfo->args.callConv)
+ {
+ BADCODE("Incompatible target for CEE_JMP");
+ }
+ }
+
// Inject call to callsite callout helper
EmitCallsiteCallout(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper);
@@ -4031,6 +4042,16 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
CORINFO_ARG_LIST_HANDLE args;
args = callInfo.sig.args;
+ if (isJmp)
+ {
+ assert(tailcall);
+ // CEE_JMP is simulated as a tail call, so we need to load the current method's args
+ for (int i = 0; i < numArgsFromStack; i++)
+ {
+ EmitLoadVar(i);
+ }
+ }
+
for (int iActualArg = 0, iLogicalArg = 0; iActualArg < numArgs; iActualArg++)
{
if (iActualArg == extraParamArgLocation)
@@ -6929,6 +6950,18 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo)
EmitUnaryArithmeticOp(INTOP_NOT_I4);
m_ip++;
break;
+ case CEE_JMP:
+ {
+ CHECK_STACK(0);
+ if (m_pCBB->clauseType != BBClauseNone)
+ {
+ // CEE_JMP inside a funclet is not allowed
+ BADCODE("CEE_JMP inside funclet");
+ }
+ EmitCall(pConstrainedToken, readonly, true /* tailcall */, false /*newObj*/, false /*isCalli*/);
+ linkBBlocks = false;
+ break;
+ }
case CEE_CALLVIRT:
case CEE_CALL:
EmitCall(pConstrainedToken, readonly, tailcall, false /*newObj*/, false /*isCalli*/);
diff --git a/src/tests/JIT/Directed/Directed_1.csproj b/src/tests/JIT/Directed/Directed_1.csproj
index 66ec17b4004a62..f67d9fc445dacd 100644
--- a/src/tests/JIT/Directed/Directed_1.csproj
+++ b/src/tests/JIT/Directed/Directed_1.csproj
@@ -2,6 +2,7 @@
+
diff --git a/src/tests/JIT/Directed/Directed_3.csproj b/src/tests/JIT/Directed/Directed_3.csproj
index 22f7e7688b5fd7..52a10d33386556 100644
--- a/src/tests/JIT/Directed/Directed_3.csproj
+++ b/src/tests/JIT/Directed/Directed_3.csproj
@@ -5,6 +5,7 @@
+
diff --git a/src/tests/JIT/Directed/jmp/genericjmp.il b/src/tests/JIT/Directed/jmp/genericjmp.il
new file mode 100644
index 00000000000000..e0ed32fd94a1d5
--- /dev/null
+++ b/src/tests/JIT/Directed/jmp/genericjmp.il
@@ -0,0 +1,112 @@
+.assembly JmpGenericTest {}
+.assembly extern System.Console
+{
+ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
+ .ver 0:0:0:0
+}
+.assembly extern System.Runtime
+{
+ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
+ .ver 0:0:0:0
+}
+.assembly extern xunit.core {}
+.assembly extern xunit.assert {}
+
+.assembly genericjmp { }
+
+.class public auto ansi beforefieldinit JmpGenericToGeneric extends [System.Runtime]System.Object
+{
+ .method public static void Target() cil managed
+ {
+ .maxstack 8
+ ldstr "Target called"
+ call void [System.Console]System.Console::WriteLine(string)
+ ret
+ }
+
+ .method public static void Source() cil managed
+ {
+ jmp void JmpGenericToGeneric::Target()
+ }
+
+ .method public static void TestEntryPoint() cil managed
+ {
+ .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = (
+ 01 00 00 00
+ )
+ call void JmpGenericToGeneric::Source()
+ ret
+ }
+}
+
+.class public auto ansi beforefieldinit JmpGenericToRegular extends [System.Runtime]System.Object
+{
+ .method public static void Target() cil managed
+ {
+ .maxstack 8
+ ldstr "Regular Target called"
+ call void [System.Console]System.Console::WriteLine(string)
+ ret
+ }
+
+ .method public static void Source() cil managed
+ {
+ jmp void JmpGenericToRegular::Target()
+ }
+
+ .method public static void TestEntryPoint() cil managed
+ {
+ .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = (
+ 01 00 00 00
+ )
+ .try
+ {
+ call void JmpGenericToRegular::Source()
+ ldstr "JMP from generic to regular method is not allowed"
+ call void [xunit.assert]Xunit.Assert::Fail(string)
+ leave END
+ }
+ catch [System.Runtime]System.InvalidProgramException
+ {
+ leave END
+ }
+ END:
+ ret
+ }
+}
+
+.class public auto ansi beforefieldinit JmpRegularToGeneric extends [System.Runtime]System.Object
+{
+ .method public static void Target() cil managed
+ {
+ .maxstack 8
+ ldstr "Generic Target called"
+ call void [System.Console]System.Console::WriteLine(string)
+ ret
+ }
+
+ .method public static void Source() cil managed
+ {
+ jmp void JmpRegularToGeneric::Target()
+ }
+
+ .method public static void TestEntryPoint() cil managed
+ {
+ .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = (
+ 01 00 00 00
+ )
+ .try
+ {
+ call void JmpRegularToGeneric::Source()
+ ldstr "JMP from regular to generic method is not allowed"
+ call void [xunit.assert]Xunit.Assert::Fail(string)
+ leave END
+ }
+ catch [System.Runtime]System.InvalidProgramException
+ {
+ leave END
+ }
+ END:
+ ret
+ }
+}
\ No newline at end of file
diff --git a/src/tests/JIT/Directed/jmp/genericjmp.ilproj b/src/tests/JIT/Directed/jmp/genericjmp.ilproj
new file mode 100644
index 00000000000000..e30982fc15569e
--- /dev/null
+++ b/src/tests/JIT/Directed/jmp/genericjmp.ilproj
@@ -0,0 +1,9 @@
+
+
+ Full
+ 1
+
+
+
+
+