Skip to content

Add ThisptrRetbufPrecode to set of precodes handled by StubPrecode path #112548

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 15 commits into from
Mar 18, 2025

Conversation

davidwrighton
Copy link
Member

@davidwrighton davidwrighton commented Feb 14, 2025

This adds the ThisPtrRetBufPrecode to the set of codes handled by the StubPrecode stub pasting path.

Perf changes from about 2.35 ns per iteration of a minimal loop with a ThisPtrRetBufPrecode to about 3.03 ns per iteration on my computer (Windows X64). I expect the cost to be a little less on an Arm64 machine since the old ThisPtrRetBufPrecode was a bit more expensive there. However, this removes the previous scheme where we allocate and jit custom stubs for this, so I think the simplification is worth the runtime cost.

The overall change has a size increase though, as this work adds support for this type of precode to the cDAC, as well as some tests.

Test app

 using System.Diagnostics;

namespace ThisPtrRetBufPerfTest
{

    internal class Program
    {
        public struct StructThatNeedsRetBuf
        {
            long x;
            long y;
            long z;
        }

        public static StructThatNeedsRetBuf TakesAnObject(object obj)
        {
            return default(StructThatNeedsRetBuf);
        }

        public static void TestDelegate(int count, Func<StructThatNeedsRetBuf> del)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                del();
            }
            sw.Stop();
            Console.WriteLine($"Time for {count} iterations {sw.ElapsedMilliseconds}ms");
        }

        static void Main(string[] args)
        {
            object o = new Program();

            Func<StructThatNeedsRetBuf> aDelegate = typeof(Program).GetMethod(nameof(TakesAnObject))!.CreateDelegate<Func<StructThatNeedsRetBuf>>(o);

            TestDelegate(1000, aDelegate);
            TestDelegate(100_000_000, aDelegate);
            TestDelegate(100_000_000, aDelegate);
            TestDelegate(100_000_000, aDelegate);
            TestDelegate(100_000_000, aDelegate);
        }
    }
}

- Use the StubPrecode logic to create UMEntryThunk instances
- Allocate them all on the Global LoaderAllocator
- Unify the creation of UMEntryThunks between the C++/CLI fixup generation path and the delegate creation path
- Remove ThunkHeapStubManager as it is now subsumed by RangeSectionStubManager
- Tweak code paths around all of this so that we don't have unnecessary indirections (This change will add 2 memory loads to the UMEntryThunk path on x86/x64, but that should be fairly immaterial compared to the cost of an actual reverse p/invoke)
…he UMEntryThunks to not successfully allocate/work
Copy link
Contributor

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

@davidwrighton davidwrighton changed the title [DRAFT] Add ThisptrRetbufPrecode to set of precodes handled by StubPrecode path Add ThisptrRetbufPrecode to set of precodes handled by StubPrecode path Mar 11, 2025
@davidwrighton davidwrighton marked this pull request as ready for review March 11, 2025 21:37
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 adds support for handling the ThisPtrRetBufPrecode in the StubPrecode path by updating both the design documentation and the precode resolution logic.

  • Updated design doc to include Version 2 of the precode contract with ThisPtrRetBufPrecode support.
  • Modified the GetMethodDesc methods to use different offsets based on the precode contract version.
Comments suppressed due to low confidence (2)

docs/design/datacontracts/PrecodeStubs.md:27

  • [nitpick] There is a naming inconsistency between the PR title ('ThisPtrRetBufPrecode') and the documentation ('ThisPointerRetBufPrecodeType'). Consider aligning the naming for consistency.
| PrecodeMachineDescriptor | ThisPointerRetBufPrecodeType | precode sort byte for this pointer ret buf precodes |

docs/design/datacontracts/PrecodeStubs.md:180

  • The branch for ContractVersion(PrecodeStubs) == 1 in this implementation throws a NotImplementedException, which could lead to runtime errors if this code path is hit. Consider adding a clarifying comment or an appropriate fallback to ensure production stability.
throw new NotImplementedException(); // TODO(cdac)

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 modulo two nits.

; rcx -This pointer
; rdx -ReturnBuffer
LEAF_ENTRY ThisPtrRetBufPrecodeWorker, _TEXT
mov METHODDESC_REGISTER, [METHODDESC_REGISTER + 0]
Copy link
Member

Choose a reason for hiding this comment

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

A nit - use the ThisPtrRetBufPrecodeData__Target instead of 0 like in the Unix code?

// We get the precode type in two phases:
// 1. Read the precode type from the intruction address.
// 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub,
// but it could also be a pinvoke precode
Copy link
Member

Choose a reason for hiding this comment

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

A nit - this comment would be worth updating to mention the new precode

Comment on lines 15 to 65
internal enum KnownPrecodeType
{
Stub = 1,
PInvokeImport, // also known as NDirectImport in the runtime
Fixup,
ThisPtrRetBuf,
}

internal abstract class ValidPrecode
{
public TargetPointer InstrPointer { get; }
public KnownPrecodeType PrecodeType { get; }

protected ValidPrecode(TargetPointer instrPointer, KnownPrecodeType precodeType)
{
InstrPointer = instrPointer;
PrecodeType = precodeType;
}

internal abstract TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor);

}

internal class StubPrecode : ValidPrecode
{
internal StubPrecode(TargetPointer instrPointer, KnownPrecodeType type = KnownPrecodeType.Stub) : base(instrPointer, type) { }

internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor)
{
TargetPointer stubPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize;
Data.StubPrecodeData_2 stubPrecodeData = target.ProcessedData.GetOrAdd<Data.StubPrecodeData_2>(stubPrecodeDataAddress);
return stubPrecodeData.SecretParam;
}
}

public sealed class PInvokeImportPrecode : StubPrecode
{
internal PInvokeImportPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.PInvokeImport) { }
}

public sealed class FixupPrecode : ValidPrecode
{
internal FixupPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.Fixup) { }
internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor)
{
TargetPointer fixupPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize;
Data.FixupPrecodeData fixupPrecodeData = target.ProcessedData.GetOrAdd<Data.FixupPrecodeData>(fixupPrecodeDataAddress);
return fixupPrecodeData.MethodDesc;

}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could re-use these existing code from PrecodeStubs_1.

This would require a an extra file, but I think eventually it may make sense to have a folder and namespace for each contract that has helpers or multiple versions.

Comment on lines 364 to 385
public void TestPrecodeStubPrecodeExpectedMethodDesc_1(PrecodeTestDescriptor test)
{
var builder = new PrecodeBuilder(test.Arch, /* Test version 1 of the contract */1);
builder.AddPlatformMetadata(test);

TargetPointer expectedMethodDesc = new TargetPointer(0xeeee_eee0u); // arbitrary
TargetCodePointer stub1 = builder.AddStubPrecodeEntry("Stub 1", test, expectedMethodDesc);

var target = CreateTarget(builder);
Assert.NotNull(target);

var precodeContract = target.Contracts.PrecodeStubs;

Assert.NotNull(precodeContract);

var actualMethodDesc = precodeContract.GetMethodDescFromStubAddress(stub1);
Assert.Equal(expectedMethodDesc, actualMethodDesc);
}

[Theory]
[MemberData(nameof(PrecodeTestDescriptorData))]
public void TestPrecodeStubPrecodeExpectedMethodDesc_2(PrecodeTestDescriptor test)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if it makes more sense to write tests for each feature instead of for each contract version. On contracts versions that don't support a feature, the test can be skipped.

For example have the existing test run on both contract versions then the ThisPtrRetBufPrecodeEntry only run on version 2.

@davidwrighton davidwrighton merged commit b71e09a into dotnet:main Mar 18, 2025
148 of 151 checks passed
@am11
Copy link
Member

am11 commented Mar 19, 2025

@davidwrighton browser-wasm was failing the build when it was merged:

  /__w/1/s/src/coreclr/classlibnative/../vm/precode.h:268:8: error: redefinition of 'ThisPtrRetBufPrecode'
    268 | struct ThisPtrRetBufPrecode : StubPrecode
        |        ^
  /__w/1/s/src/coreclr/classlibnative/../vm/wasm/cgencpu.h:53:8: note: previous definition is here
     53 | struct ThisPtrRetBufPrecode {
        |        ^

@github-actions github-actions bot locked and limited conversation to collaborators Apr 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants