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
34 changes: 2 additions & 32 deletions src/coreclr/jit/emit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8516,40 +8516,10 @@ void emitter::emitOutputDataSec(dataSecDsc* sec, AllocMemChunk* chunks)

if (m_compiler->opts.compReloc)
{
#ifdef TARGET_ARM
// The runtime and ILC will handle setting the thumb bit on the async resumption stub entrypoint,
// either directly in the emitAsyncResumeStubEntryPoint value (runtime) or will add the thumb bit
// to the symbol definition (ilc). ReadyToRun is different here: it emits method symbols without the
// thumb bit, then during fixups, the runtime adds the thumb bit. This works for all cases where
// the method entrypoint is fixed up at runtime, but doesn't hold for the resumption stub, which is
// emitted as a direct call without the typical indirection cell + fixup. This is okay in this case
// (while regular method calls could not do this) because the async method and its resumption stub
// are tightly coupled and effectively funclets of the same method. However, this means that
// crossgen needs the reloc for the resumption stubs entrypoint to include the thumb bit. Until we
// unify the behavior of crossgen with the runtime and ilc, we will work around this by emitting the
// reloc with the addend for the thumb bit.
if (m_compiler->IsReadyToRun())
{
emitRecordRelocationWithAddlDelta(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint,
CorInfoReloc::DIRECT, 1);
}
else
#endif
{
emitRecordRelocation(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint, CorInfoReloc::DIRECT);
}
emitRecordRelocation(&aDstRW[i].Resume, emitAsyncResumeStubEntryPoint, CorInfoReloc::DIRECT);
if (target != nullptr)
{
#ifdef TARGET_ARM
if (m_compiler->IsReadyToRun())
{
emitRecordRelocationWithAddlDelta(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT, 1);
}
else
#endif
{
emitRecordRelocation(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT);
}
emitRecordRelocation(&aDstRW[i].DiagnosticIP, target, CorInfoReloc::DIRECT);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/coreclr/jit/emitarm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5307,9 +5307,10 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i)
assert(ins == INS_movw || ins == INS_movt);
distVal = (ssize_t)emitOffsetToPtr(dstOffs);

// ILC defines method symbols with the thumb bit already set, so don't add it here.
// For ReadyToRun and non-relocatable code (runtime JIT), we set it ourselves.
if (!m_compiler->IsNativeAot())
// ILC and crossgen2 defines method symbols with the thumb bit already set, so don't add it here.
// Assume compilations with relocs will put the thumb bit in the symbol.
// For non-relocatable code (runtime JIT), we set it ourselves.
if (!(m_compiler->opts.compReloc))
Comment thread
jtschuster marked this conversation as resolved.
{
distVal += 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,12 +409,8 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
}

bool isMethod = node is IMethodBodyNode or AssemblyStubNode;
#if !READYTORUN
long thumbBit = _nodeFactory.Target.Architecture == TargetArchitecture.ARM && isMethod ? 1 : 0;
#else
// R2R records the thumb bit in the addend when needed, so we don't have to do it here.
long thumbBit = 0;
#endif

if (node is WasmTypeNode signature)
{
RecordMethodSignature(signature);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

<ItemGroup>
<ProjectReference Include="../ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj" />
<ProjectReference Include="../crossgen2/crossgen2_inbuild.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

<!-- Test case source files are embedded as resources so we can compile them with Roslyn at test time.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

public static class ExceptionHandling
{
public static int CallDependency() => InlineableLib.GetValue();

public static int MethodWithExceptionInfo(int value)
{
try
{
return 100 / value;
}
catch (DivideByZeroException)
{
return -1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@ static void Validate(ReadyToRunReader reader)
}
}

[Fact]
public void ArmThumbBitRelocationTargets()
{
var inlineableLib = new CompiledAssembly
{
AssemblyName = "InlineableLib",
SourceResourceNames = ["CrossModuleInlining/Dependencies/InlineableLib.cs"],
};
var exceptionHandling = new CompiledAssembly
{
AssemblyName = nameof(ArmThumbBitRelocationTargets),
SourceResourceNames = ["CrossModuleInlining/ExceptionHandling.cs"],
References = [inlineableLib],
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(ArmThumbBitRelocationTargets),
[
new(nameof(ArmThumbBitRelocationTargets),
[
new CrossgenAssembly(exceptionHandling),
new CrossgenAssembly(inlineableLib) { Kind = Crossgen2InputKind.Reference },
])
{
Options = [Crossgen2Option.TargetArchArm],
Validate = Validate,
},
]));

static void Validate(ReadyToRunReader reader)
{
string diag;
Assert.True(R2RAssert.HasExpectedArmThumbBitTargets(reader, out diag), diag);
}
}

[Fact]
public void TransitiveReferences()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ internal enum Crossgen2Option
ObjectFormat,
HotColdSplitting,
Optimize,
TargetArch,
TargetOS,
TargetArchArm,
}

internal static class Crossgen2OptionsExtensions
Expand All @@ -61,8 +60,7 @@ internal static class Crossgen2OptionsExtensions
Crossgen2Option.ObjectFormat => $"--object-format",
Crossgen2Option.HotColdSplitting => $"--hot-cold-splitting",
Crossgen2Option.Optimize => $"--optimize",
Crossgen2Option.TargetArch => $"--target-arch",
Crossgen2Option.TargetOS => $"--target-os",
Crossgen2Option.TargetArchArm => $"--targetarch:arm",
Comment thread
jtschuster marked this conversation as resolved.
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -63,6 +64,176 @@ public static bool HasManifestRef(ReadyToRunReader reader, string assemblyName,
return found;
}

/// <summary>
/// Returns true if ARM R2R relocations preserve the Thumb bit only for raw runtime function
/// start RVAs, while non-code pointers keep even RVAs.
/// </summary>
public static bool HasExpectedArmThumbBitTargets(ReadyToRunReader reader, out string diagnostic)
{
if (reader.Machine != Machine.ArmThumb2)
{
diagnostic = $"Expected ARM Thumb2 image, but found {reader.Machine}.";
return false;
}

var failures = new List<string>();

bool result = true;
result &= SectionRVAIsEven(reader, ReadyToRunSectionType.ExceptionInfo, failures);
result &= SectionRVAIsEven(reader, ReadyToRunSectionType.DelayLoadMethodCallThunks, failures);
result &= DelayLoadHelperImportTargetsAreOdd(reader, failures);
result &= ExceptionInfoMethodRVAsAreEven(reader, failures);
result &= RuntimeFunctionStartRVAsAreOdd(reader, failures);

diagnostic = result
? "ARM Thumb-bit relocation targets are encoded as expected."
: string.Join(Environment.NewLine, failures);
return result;
}

private static bool SectionRVAIsEven(ReadyToRunReader reader, ReadyToRunSectionType sectionType, List<string> failures)
{
if (!reader.ReadyToRunHeader.Sections.TryGetValue(sectionType, out ReadyToRunSection section))
{
failures.Add($"Expected {sectionType} section not found.");
return false;
}

bool result = true;
if (section.Size <= 0)
{
failures.Add($"Expected {sectionType} section to be non-empty.");
result = false;
}

if ((section.RelativeVirtualAddress & 1) != 0)
{
failures.Add($"{sectionType} section RVA 0x{section.RelativeVirtualAddress:X8} should be even.");
result = false;
}

return result;
}

private static bool ExceptionInfoMethodRVAsAreEven(ReadyToRunReader reader, List<string> failures)
{
if (!reader.ReadyToRunHeader.Sections.TryGetValue(ReadyToRunSectionType.ExceptionInfo, out ReadyToRunSection section))
{
return true;
}

bool result = true;
int offset = reader.CompositeReader.GetOffset(section.RelativeVirtualAddress);
int endOffset = offset + section.Size;
int entries = 0;
for (; offset + 2 * sizeof(int) <= endOffset; offset += 2 * sizeof(int))
{
int methodRva = BinaryPrimitives.ReadInt32LittleEndian(reader.Image.AsSpan(offset));
if (methodRva == -1)
{
break;
}

entries++;
if ((methodRva & 1) != 0)
{
failures.Add($"ExceptionInfo method RVA 0x{methodRva:X8} should be even.");
result = false;
}
}

if (entries == 0)
{
failures.Add("Expected ExceptionInfo to contain at least one method entry.");
result = false;
}

return result;
}

private static bool DelayLoadHelperImportTargetsAreOdd(ReadyToRunReader reader, List<string> failures)
{
bool result = true;
int entries = 0;
foreach (ReadyToRunImportSection section in reader.ImportSections)
{
if ((section.Flags & ReadyToRunImportSectionFlags.PCode) == 0)
{
continue;
}

int offset = reader.CompositeReader.GetOffset(section.SectionRVA);
int endOffset = offset + section.SectionSize;
for (; offset + section.EntrySize <= endOffset; offset += section.EntrySize)
{
int targetAddress = BinaryPrimitives.ReadInt32LittleEndian(reader.Image.AsSpan(offset));
if (targetAddress == 0)
{
continue;
}

entries++;
if ((targetAddress & 1) == 0)
{
failures.Add($"PCode import target 0x{targetAddress:X8} should have the Thumb bit set.");
result = false;
}
}
}

if (entries == 0)
{
failures.Add("Expected at least one non-empty PCode import target.");
result = false;
}

return result;
}

private static bool RuntimeFunctionStartRVAsAreOdd(ReadyToRunReader reader, List<string> failures)
{
if (!reader.ReadyToRunHeader.Sections.TryGetValue(ReadyToRunSectionType.RuntimeFunctions, out ReadyToRunSection section))
{
failures.Add("Expected RuntimeFunctions section not found.");
return false;
}

int runtimeFunctionSize = 2 * sizeof(int);
if (section.Size < runtimeFunctionSize)
{
failures.Add($"Expected RuntimeFunctions section to contain at least one entry, but size is {section.Size}.");
return false;
}

bool result = true;
int offset = reader.CompositeReader.GetOffset(section.RelativeVirtualAddress);
int count = section.Size / runtimeFunctionSize;
int entries = 0;
for (int index = 0; index < count; index++, offset += runtimeFunctionSize)
{
int startRva = BinaryPrimitives.ReadInt32LittleEndian(reader.Image.AsSpan(offset));
if (startRva == -1)
{
break;
}

entries++;
if ((startRva & 1) == 0)
{
failures.Add($"RuntimeFunctions[{index}] start RVA 0x{startRva:X8} should have the Thumb bit set.");
result = false;
}
}

if (entries == 0)
{
failures.Add("Expected RuntimeFunctions to contain at least one entry.");
result = false;
}

return result;
}

/// <summary>
/// Returns true if the CrossModuleInlineInfo section records that <paramref name="inlinerMethodName"/>
/// inlined <paramref name="inlineeMethodName"/> and the inlinee is encoded as a cross-module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public class DelayLoadHelperImport : Import
public ReadyToRunHelper HelperId => _helper;

public DelayLoadHelperImport(
NodeFactory factory,
ImportSectionNode importSectionNode,
ReadyToRunHelper helper,
Signature instanceSignature,
bool useVirtualCall = false,
NodeFactory factory,
ImportSectionNode importSectionNode,
ReadyToRunHelper helper,
Signature instanceSignature,
bool useVirtualCall = false,
bool useJumpableStub = false,
MethodDesc callingMethod = null)
: base(importSectionNode, instanceSignature, callingMethod)
Expand Down Expand Up @@ -87,7 +87,7 @@ public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory f
// This needs to be an empty target pointer since it will be filled in with Module*
// when loaded by CoreCLR
dataBuilder.EmitReloc(_delayLoadHelper,
factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64, factory.Target.CodeDelta);
factory.Target.PointerSize == 4 ? RelocType.IMAGE_REL_BASED_HIGHLOW : RelocType.IMAGE_REL_BASED_DIR64);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
// First, emit the actual EH records in sequence and store map from methods to the EH record symbols
for (int index = 0; index < _methodNodes.Count; index++)
{
exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB);
exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB, -factory.Target.CodeDelta);
exceptionInfoLookupBuilder.EmitReloc(_ehInfoNode, RelocType.IMAGE_REL_BASED_ADDR32NB, _ehInfoOffsets[index]);
}

Expand Down
Loading
Loading