Repository: dotnet/runtime
File: src/coreclr/jit/lower.cpp (C++)

Code before:
//----------------------------------------------------------------------------------------------
// LowerDelegateInvoke: lower a delegate invoke, accessing fields of the delgate
//
// Arguments:
//     call - call representing a delegate invoke.
//
// Return Value:
//    Control expr for the delegate invoke call, for futher lowering.
//
GenTree* Lowering::LowerDelegateInvoke(GenTreeCall* call)
{
    noway_assert(call->gtCallType == CT_USER_FUNC);

    assert((m_compiler->info.compCompHnd->getMethodAttribs(call->gtCallMethHnd) &
            (CORINFO_FLG_DELEGATE_INVOKE | CORINFO_FLG_FINAL)) == (CORINFO_FLG_DELEGATE_INVOKE | CORINFO_FLG_FINAL));

    CallArg* thisArg = nullptr;

    if (call->IsTailCallViaJitHelper())
    {
        thisArg = call->gtArgs.GetArgByIndex(0);
    }
    else
    {
        thisArg = call->gtArgs.GetThisArg();
    }

    assert(thisArg != nullptr);
    GenTree* thisArgNode = thisArg->GetNode();
    assert(thisArgNode != nullptr);

#if HAS_FIXED_REGISTER_SET
    assert(thisArgNode->OperIs(GT_PUTARG_REG));
    GenTree* thisExpr = thisArgNode->AsOp()->gtOp1;
    LIR::Use thisExprUse(BlockRange(), &thisArgNode->AsOp()->gtOp1, thisArgNode);
#else
    GenTree* thisExpr = thisArgNode;
    LIR::Use thisExprUse(BlockRange(), &thisArg->NodeRef(), call);
#endif

    // We're going to use the 'this' expression multiple times, so make a local to copy it.

    GenTree* base;
    if (thisExpr->OperIs(GT_LCL_VAR))
    {
        base = m_compiler->gtNewLclvNode(thisExpr->AsLclVar()->GetLclNum(), thisExpr->TypeGet());
    }
    else if (thisExpr->OperIs(GT_LCL_FLD))
    {
        base = m_compiler->gtNewLclFldNode(thisExpr->AsLclFld()->GetLclNum(), thisExpr->TypeGet(),
                                           thisExpr->AsLclFld()->GetLclOffs());

Review comment (bug):
In the tailcall-via-JIT-helper path, this uses GetArgByIndex(0) to locate the delegate object. On TARGET_WASM, a non-user WasmShadowStackPointer arg is pushed to the front of the arg list, so arg 0 can be the shadow SP rather than the delegate. This will break delegate invoke lowering for tailcalls on WASM. Prefer using GetUserArgByIndex(0) (or otherwise skipping non-user well-known args) so the logic remains correct when late/hidden args are present.

Code after:
//----------------------------------------------------------------------------------------------
// LowerDelegateInvoke: lower a delegate invoke, accessing fields of the delegate
//
// Arguments:
//     call - call representing a delegate invoke.
//
// Return Value:
//    Control expr for the delegate invoke call, for further lowering.
//
GenTree* Lowering::LowerDelegateInvoke(GenTreeCall* call)
{
    noway_assert(call->gtCallType == CT_USER_FUNC);

    assert((m_compiler->info.compCompHnd->getMethodAttribs(call->gtCallMethHnd) &
            (CORINFO_FLG_DELEGATE_INVOKE | CORINFO_FLG_FINAL)) == (CORINFO_FLG_DELEGATE_INVOKE | CORINFO_FLG_FINAL));

    CallArg* thisArg = nullptr;

    if (call->IsTailCallViaJitHelper())
    {
        thisArg = call->gtArgs.GetArgByIndex(0);
    }
    else
    {
        thisArg = call->gtArgs.GetThisArg();
    }

    assert(thisArg != nullptr);
    GenTree* thisArgNode = thisArg->GetNode();
    assert(thisArgNode != nullptr);

#if HAS_FIXED_REGISTER_SET
    assert(thisArgNode->OperIs(GT_PUTARG_REG));
    GenTree* thisExpr = thisArgNode->AsOp()->gtOp1;
    LIR::Use thisExprUse(BlockRange(), &thisArgNode->AsOp()->gtOp1, thisArgNode);
#else
    GenTree* thisExpr = thisArgNode;
    LIR::Use thisExprUse(BlockRange(), &thisArg->NodeRef(), call);
#endif

    // We're going to use the 'this' expression multiple times, so make a local to copy it.

    GenTree* base;
    if (thisExpr->OperIs(GT_LCL_VAR))
    {
        base = m_compiler->gtNewLclvNode(thisExpr->AsLclVar()->GetLclNum(), thisExpr->TypeGet());
    }
    else if (thisExpr->OperIs(GT_LCL_FLD))
    {
        base = m_compiler->gtNewLclFldNode(thisExpr->AsLclFld()->GetLclNum(), thisExpr->TypeGet(),
                                           thisExpr->AsLclFld()->GetLclOffs());