Skip to content

Ldvirtftn implementation for interpreter #116661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 19, 2025

Conversation

davidwrighton
Copy link
Member

@davidwrighton davidwrighton commented Jun 14, 2025

Implement more complex call scenarios

  • ldvirtftn based (generic virtual calls)
  • code pointer based (certain static virtual calls)
  • ldvirtftn path to delegate creation

And as a bonus, to keep the tests working...

Rework where we attach intepreted code to a method, so that we never put the interpreter code bytes into the vtable (with this change, the vtable, etc, always have the InterpreterPrecode in it.

Add scheme for enabling logging of the CallStubGenerator.

Fix calling convention handling for jit calling convention to interpreter calling convention for Unix X64 and Windows Arm64.

- ldvirtftn based (generic virtual calls)
- code pointer based (certain static virtual calls)
- ldvirtftn path to delegate creation
…put the interpreter code bytes into the vtable (with this change, the vtable, etc, always have the InterpreterPrecode in it.
@Copilot Copilot AI review requested due to automatic review settings June 14, 2025 00:10
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jun 14, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements improvements for handling generic virtual calls in the interpreter by introducing an ldvirtftn-based pathway, supporting code pointer based static virtual calls, and updating delegate creation support.

  • Adds tests in the interpreter for GenericVirtualMethod and new delegate methods.
  • Updates JIT and interpreter helper functions to propagate interpreter code flags and allocate InterpreterPrecode correctly.
  • Modifies the interpreter compiler to support a new ldvirtftn path and a corresponding calli opcode.

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/tests/JIT/interpreter/Interpreter.cs Adds tests for generic virtual methods and new call paths.
src/coreclr/vm/prestub.cpp Removes inline InterpreterPrecode allocation and migrates it to JitCompileCodeLocked.
src/coreclr/vm/precode.h Introduces FromEntryPoint helper in InterpreterPrecode.
src/coreclr/vm/jitinterface.h & jitinterface.cpp Updates UnsafeJitFunction signature and handling for interpreter flags.
src/coreclr/interpreter/intops.def Fixes the opcode name for calli.
src/coreclr/interpreter/compiler.{h,cpp} Adds EmitPushLdvirtftn and updates ldvirtftn handling in call emission.
Comments suppressed due to low confidence (1)

src/coreclr/interpreter/compiler.cpp:2664

  • It would help to document or clarify the expected initialization and invariant of extraParamArgLocation to ensure its value is correctly maintained across different code paths.
assert(extraParamArgLocation == INT_MAX);

Comment on lines 55 to 58
void DECLSPEC_NORETURN Interp_CompilationFail(const char *msg)
{
// This is a fatal error, so we don't return.
throw std::exception();
Copy link
Preview

Copilot AI Jun 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider throwing a more descriptive exception (or a custom exception type) in Interp_CompilationFail instead of using std::exception, to improve clarity when a fatal compilation error occurs.

Suggested change
void DECLSPEC_NORETURN Interp_CompilationFail(const char *msg)
{
// This is a fatal error, so we don't return.
throw std::exception();
// Custom exception for fatal compilation errors
class InterpCompilationException : public std::exception
{
private:
const char* m_msg;
public:
explicit InterpCompilationException(const char* msg) : m_msg(msg) {}
virtual const char* what() const noexcept override
{
return m_msg;
}
};
void DECLSPEC_NORETURN Interp_CompilationFail(const char *msg)
{
// This is a fatal error, so we don't return.
throw InterpCompilationException(msg);

Copilot uses AI. Check for mistakes.

CORINFO_GENERICHANDLE_RESULT embedInfo;
m_compHnd->embedGenericHandle(pResolvedToken, true, m_methodInfo->ftn, &embedInfo);
assert(embedInfo.compileTimeHandle != NULL);
int typeVar = EmitGenericHandleAsVar(embedInfo);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my clarity, GenericHandle has nothing to do with generics ? The generic part just means that it can be any handle, for example a non-generic method handle ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

// This check really only applies to intrinsic Array.Address methods
if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE)
{
Interp_CompilationFail("Currently do not support LDFTN of Parameterized functions");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preexisting way of handling errors like this is too set m_hasInvalidCode to true and go to exit_bad_code. It would be nice to keep that concept or change to the new one everywhere.
Btw, I've found on Friday that we are not handling the CORJIT_BADCODE that we return at exit_bad_code at the caller, so it would need to be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now replacing all the m_hasInvalidCode stuff with a new exception based flow. It's simple/straightforward.

@@ -429,17 +429,6 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig)
LOG((LF_CLASSLOADER, LL_INFO1000000,
" In PrepareILBasedCode, calling JitCompileCode\n"));
pCode = JitCompileCode(pConfig);
#ifdef FEATURE_INTERPRETER
if (pConfig->IsInterpreterCode())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With your change, the IsInterpreterCode method and the underlying field is no longer needed. Can you please remove it?

callArgs[0] = thisVar;
callArgs[1] = typeVar;
callArgs[2] = methodVar;
callArgs[3] = -1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a named constant for this -1? I think it's the call args end of list sentinel?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have one: CALL_ARGS_TERMINATOR, it used just at few places.

{
// Resolve a virtual call using the helper function to a function pointer, and then call through that
assert(!"Need to support ldvirtftn path");
assert(extraParamArgLocation == INT_MAX);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a comment explaining what it means if this assert fails? I assume it's for methods that have a hidden extra parameter as part of their calling convention, and we don't support those yet

@kg
Copy link
Member

kg commented Jun 16, 2025

LGTM aside from existing comments

assert(!"Unsupported element type for STELEM");
m_hasInvalidCode = true;
goto exit_bad_code;
BADCODE("Unsupported element type for STELEM");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this unsupported in a sense that the other element types than the one we currently handle are invalid or in the sense that we don't support it yet?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. I'm just replacing the m_hasInvalidCode logic, not changing behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking because the new mechanism would now let the interpreter compilation return error code and we would fall back to JIT. So during the bringup, there may be cases when we think things are working interpreted, but they'd be running jitted instead. When we generate the error as a result of bad IL, it is ok, because the JIT would fail too.
So it seems that for cases when we don't support something yet, we should assert.

Copy link
Member

@janvorli janvorli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

Comment on lines 86 to 88
// FIXME this shouldn't be here
compHnd->setMethodAttribs(methodInfo->ftn, CORINFO_FLG_INTERPRETER);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// FIXME this shouldn't be here
compHnd->setMethodAttribs(methodInfo->ftn, CORINFO_FLG_INTERPRETER);

Can this and CORINFO_FLG_INTERPRETER be deleted too?

@@ -47,9 +47,10 @@ thread_local ICorJitInfo* t_InterpJitInfoTls = nullptr;
static const char *g_stackTypeString[] = { "I4", "I8", "R4", "R8", "O ", "VT", "MP", "F " };

/*****************************************************************************/
void DECLSPEC_NORETURN Interp_NOMEM()
void DECLSPEC_NORETURN Interp_CompilationFail(const char *msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void DECLSPEC_NORETURN Interp_CompilationFail(const char *msg)

This look unused. Delete it?

@janvorli
Copy link
Member

janvorli commented Jun 17, 2025

@davidwrighton I have tried to run this PR with coreclr tests and it crashes because of the JitCompileCodeLocked returning the interpreter precode in the ETW event logging below. It ends up calling EECodeInfo::GetMethodRegionInfo, but we've created the EECodeInfo from the interpreter precode address. It needs to be initialized from the interpreter IR bytecode address instead.
In other words, the ETW::MethodLog::MethodJitted needs to be passed the interpreter IR as pCode.

pCode = JitCompileCodeLocked(pConfig, pilHeader, pEntry, &sizeOfCode);
#ifdef FEATURE_EVENT_TRACE
ETW::MethodLog::MethodJitted(this,
&namespaceOrClassName,
&methodName,
&methodSignature,
pCode,
pConfig);
#endif //FEATURE_EVENT_TRACE

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

@am11 am11 added area-CodeGen-Interpreter-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jun 17, 2025
Copy link
Contributor

Tagging subscribers to this area: @BrzVlad, @janvorli, @kg
See info in area-owners.md if you want to be subscribed.

1 similar comment
Copy link
Contributor

Tagging subscribers to this area: @BrzVlad, @janvorli, @kg
See info in area-owners.md if you want to be subscribed.

@janvorli
Copy link
Member

@davidwrighton the interpreter test is failing on arm64 Windows in bc.GenericVirtualMethod with error code C0000602

@davidwrighton
Copy link
Member Author

@janvorli Yeah, I've spent my spare time today to setup an Windows Arm64 test machine for that. I suspect its another subtle bug in the calling convention work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants