From ef35aaf22197a74d60600946ad7b01b703dbb6e1 Mon Sep 17 00:00:00 2001 From: sef Date: Sat, 29 Aug 2026 11:59:37 -0500 Subject: [PATCH] Resolve field offsets through byref bases in ResolveFieldOffsets ResolveFieldOffsets only succeeds when the memory operand's base local has a normal instance type with a populated .Fields list. When the base is a ByRefTypeAnalysisContext (a T& local - e.g. a `ref MyStruct data` parameter), .Fields is always empty, because the byref wrapper is a ReferencedTypeAnalysisContext with no Definition. The offset therefore never resolves, and every field read through that ref parameter falls through to the "Unmanaged memory load" diagnostic - even though Ldfld accepts a managed pointer operand directly, which is the same precedent IlGenerator's addend==0 byref dereference case already relies on. Resolve field offsets against the referent (.ElementType) when the base is a byref, rather than against the byref wrapper itself. Measured on a shipped Unity 6 IL2CPP title (x86-64, metadata v31): 20,807 -> 20,336 "Unmanaged memory load" sites, i.e. 471 eliminated. Spot-checked one newly-resolved site against Il2CppInspector's dumped field layout for the same struct: byref base + 0x1C resolved to the field the dump lists at 0x1C. --- Cpp2IL.Core/Analysis/MetadataResolver.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Cpp2IL.Core/Analysis/MetadataResolver.cs b/Cpp2IL.Core/Analysis/MetadataResolver.cs index de039881..2df35d03 100644 --- a/Cpp2IL.Core/Analysis/MetadataResolver.cs +++ b/Cpp2IL.Core/Analysis/MetadataResolver.cs @@ -150,9 +150,15 @@ public static bool ResolveFieldOffsets(MethodAnalysisContext method) if (memory.Base is not LocalVariable local || local?.Type == null) continue; + // A byref-typed base (a managed pointer to a value type) has no fields of its + // own - IlGenerator's Ldfld emission accepts a managed pointer operand directly + // (same as it does for the addend==0 dereference case), so field resolution walks + // the referent's layout instead of the byref wrapper's, which is always empty. + var baseType = local.Type is ByRefTypeAnalysisContext { ElementType: { } referent } ? referent : local.Type; + // check if static field access - var staticOwner = (local.Type as StaticFieldStorageTypeAnalysisContext)?.OwnerType; - var owner = staticOwner ?? local.Type; + var staticOwner = (baseType as StaticFieldStorageTypeAnalysisContext)?.OwnerType; + var owner = staticOwner ?? baseType; var genericOwner = owner as GenericInstanceTypeAnalysisContext; FieldAnalysisContext? field;