Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/design/datacontracts/DebugInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ public readonly struct DebugVarInfo
IEnumerable<DebugVarInfo> GetMethodVarInfo(TargetCodePointer pCode, out uint codeOffset);
```

`codeOffset` is `pCode` minus the method's code start. The code start is the entry point of the native code version containing `pCode` (`ICodeVersions.GetNativeCode`), mapped by `IExecutionManager.GetDiagnosticCodeStartFromEntryPoint`: to the interpreter bytecode for an interpreted method, and on portable-entrypoint targets to the R2R virtual IP. Native `ClrDataAccess::GetMethodVarInfo` maps only interpreter entry points (`GetInterpreterCodeFromEntryPointIfPresent`); the R2R portable-entrypoint mapping follows the runtime's `GetDiagnosticCodeStartFromEntryPoint`.

### Vars Data Encoding

Each variable entry in the Vars section is nibble-encoded as follows:
Expand Down
5 changes: 3 additions & 2 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -2002,8 +2002,9 @@ Determining if a method supports multiple code versions:
MethodDesc md = _methodDescs[methodDesc.Address];
if (md.IsEligibleForTieredCompilation)
return true;
// MethodDesc::IsEligibleForReJIT
if (_target.Contracts.ReJIT.IsEnabled())
// MethodDesc::IsEligibleForReJIT. The ReJIT contract is advertised only with PROFILING_SUPPORTED;
// targets without it (wasm) also lack FEATURE_REJIT, so nothing is ReJIT-eligible.
if (_target.Contracts.TryGetContract(out IReJIT reJit) && reJit.IsEnabled())
{
if (!md.IsIL)
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,14 @@ IEnumerable<DebugVarInfo> IDebugInfo.GetMethodVarInfo(TargetCodePointer pCode, o
throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}.");
TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool _);

// Compute code offset from the method's native code entry point, not from the code block start.
// GetStartAddress returns the start of the current code block (which may be a funclet for exception
// handlers). Variable location offsets are always relative to the method entry point, so we must use
// GetNativeCode from the NativeCodeVersion, matching the native DAC's GetMethodVarInfo which uses
// NativeCodeVersion::GetNativeCode() for this purpose
// Variable offsets are relative to the method's code start, not the code block start
// (GetStartAddress may be a funclet). Map the native code version's entry point to the code
// it starts: interpreter bytecode, or on portable-entrypoint targets the R2R virtual IP.
ICodeVersions cv = _target.Contracts.CodeVersions;
NativeCodeVersionHandle ncvh = cv.GetNativeCodeVersionForIP(pCode);
if (!ncvh.Valid)
throw new InvalidOperationException($"No NativeCodeVersion found for native code {pCode}.");
TargetCodePointer nativeCodeStart = cv.GetNativeCode(ncvh);
TargetCodePointer nativeCodeStart = _eman.GetDiagnosticCodeStartFromEntryPoint(cv.GetNativeCode(ncvh));
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target));

if (debugInfo == TargetPointer.Null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1997,8 +1997,9 @@ bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc)
MethodDesc md = _methodDescs[methodDesc.Address];
if (md.IsEligibleForTieredCompilation)
return true;
// MethodDesc::IsEligibleForReJIT
if (_target.Contracts.ReJIT.IsEnabled())
// MethodDesc::IsEligibleForReJIT. The ReJIT contract is advertised only with PROFILING_SUPPORTED;
// targets without it (wasm) also lack FEATURE_REJIT, so nothing is ReJIT-eligible.
if (_target.Contracts.TryGetContract(out IReJIT reJit) && reJit.IsEnabled())
{
if (!md.IsIL)
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,43 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(string version, MockTarget.Arch
}
}

/// <summary>
/// Variable offsets are relative to the code that a method's entry point starts, as native
/// <c>ClrDataAccess::GetMethodVarInfo</c> computes with <c>GetInterpreterCodeFromEntryPointIfPresent</c>:
/// an interpreter precode or portable entry point maps to the code it runs (interpreter bytecode
/// or the R2R virtual IP), while a JIT entry point is already the code start.
/// </summary>
[Theory]
[InlineData(0x0040_0000ul, 0x0040_0000ul)]
[InlineData(0x0040_0000ul, 0x8001_0100ul)]
public void GetMethodVarInfo_CodeOffsetIsRelativeToDiagnosticCodeStart(ulong entryPoint, ulong codeStart)
{
const uint CodeOffset = 0x2;
TargetCodePointer pCode = new(codeStart + CodeOffset);
CodeBlockHandle codeBlock = new(new TargetPointer(0x7000));
NativeCodeVersionHandle nativeCodeVersion = NativeCodeVersionHandle.CreateSynthetic(new TargetPointer(0x1000));

Mock<IExecutionManager> executionManager = new();
executionManager.Setup(e => e.GetCodeBlockHandle(pCode)).Returns(codeBlock);
executionManager.Setup(e => e.GetDebugInfo(codeBlock, out It.Ref<bool>.IsAny)).Returns(TargetPointer.Null);
executionManager
.Setup(e => e.GetDiagnosticCodeStartFromEntryPoint(new TargetCodePointer(entryPoint)))
.Returns(new TargetCodePointer(codeStart));
Mock<ICodeVersions> codeVersions = new();
codeVersions.Setup(c => c.GetNativeCodeVersionForIP(pCode)).Returns(nativeCodeVersion);
codeVersions.Setup(c => c.GetNativeCode(nativeCodeVersion)).Returns(new TargetCodePointer(entryPoint));

Target target = new TestPlaceholderTarget.Builder(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = false })
.AddMockContract(executionManager)
.AddMockContract(codeVersions)
.AddMockContract(new Mock<IPlatformMetadata>())
.AddContract<IDebugInfo>(version: "c1")
.Build();

Assert.Empty(target.Contracts.DebugInfo.GetMethodVarInfo(pCode, out uint codeOffset));
Assert.Equal(CodeOffset, codeOffset);
}

[Theory]
[MemberData(nameof(StdArchAllVersions))]
public void GetDebugInfo_R2R_NoDebugInfoSection_ReturnsNull(string version, MockTarget.Architecture arch)
Expand Down Expand Up @@ -365,10 +402,15 @@ public void GetDebugInfo_R2R_NoDebugInfoSection_ReturnsNull(string version, Mock
codeVersions.Setup(c => c.GetNativeCode(nativeCodeVersion)).Returns(methodStart);

MockMemorySpace.MemoryContext memoryContext = emBuilder.Builder.GetMemoryContext();
Mock<IPrecodeStubs> precodeStubs = new();
precodeStubs.Setup(p => p.GetInterpreterCodeFromInterpreterPrecodeIfPresent(methodStart)).Returns(methodStart);
Target target = CreateTarget(emBuilder, configureTarget: targetBuilder => targetBuilder
.UseReader(ReadWithReadableZeroPage)
.AddGlobals((Constants.Globals.FeaturePortableEntrypoints, 0ul))
.AddContract<IFeatureFlags>(version: "c1")
.AddContract<IDebugInfo>(version: "c1")
.AddMockContract(codeVersions));
.AddMockContract(codeVersions)
.AddMockContract(precodeStubs));
IExecutionManager em = target.Contracts.ExecutionManager;

CodeBlockHandle? handle = em.GetCodeBlockHandle(pCode);
Expand Down
40 changes: 40 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/MethodDescTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,46 @@ public void IsArrayMethod(MockTarget.Architecture arch)
}
}

/// <summary>
/// The ReJIT contract is advertised only by runtimes built with profiler support. Without it,
/// native <c>MethodDesc::IsEligibleForReJIT</c> is false, so a method that is not eligible for
/// tiered compilation is not versionable.
/// </summary>
[Theory]
[InlineData(false, false, false)]
[InlineData(true, false, false)]
[InlineData(true, true, true)]
public void IsVersionable_ReJITContractOptional(bool advertiseReJIT, bool reJITEnabled, bool expected)
{
MockTarget.Architecture arch = new() { IsLittleEndian = true, Is64Bit = true };
TargetPointer methodDescAddress = TargetPointer.Null;
Mock<IReJIT> reJIT = new();
reJIT.Setup(r => r.IsEnabled()).Returns(reJITEnabled);
Mock<ICodeVersions> codeVersions = new();
codeVersions.Setup(c => c.CodeVersionManagerSupportsMethod(It.IsAny<TargetPointer>())).Returns(true);

IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(
arch,
methodDescBuilder =>
{
MockMethodTable objectMethodTable = methodDescBuilder.RTSBuilder.SystemObjectMethodTable;
byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment);
MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("testMethod", methodDescSize);
chunk.MethodTable = objectMethodTable.Address;
chunk.Size = methodDescSize;
chunk.Count = 1;
methodDescAddress = new TargetPointer(chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout).Address);
},
configureTarget: targetBuilder =>
{
targetBuilder.AddMockContract(codeVersions);
if (advertiseReJIT)
targetBuilder.AddMockContract(reJIT);
});

Assert.Equal(expected, rts.IsVersionable(rts.GetMethodDescHandle(methodDescAddress)));
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void IsDynamicMethod(MockTarget.Architecture arch)
Expand Down
Loading