From 5d14f9a72492442a999bcab1fa056b2d01e53795 Mon Sep 17 00:00:00 2001 From: nichinichisou Date: Tue, 8 Sep 2026 21:31:52 +0800 Subject: [PATCH] ARM64/ELF: Inline single-precision literal pool reads --- .../InstructionSets/NewArmV8InstructionSet.cs | 39 +++++++++++++++++++ LibCpp2IL/Elf/ElfFile.cs | 13 +++++++ LibCpp2IL/Il2CppBinary.cs | 7 ++++ 3 files changed, 59 insertions(+) diff --git a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs index 0ba9188f1..d23af8ca1 100644 --- a/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs @@ -1,5 +1,7 @@ using System; +using System.Buffers.Binary; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Disarm; using Cpp2IL.Core.Api; @@ -189,6 +191,34 @@ public override List GetIsilFromMethod(MethodAnalysisContext contex return instructions; } + /// + /// Reads the single-precision value held at a constant address, when that address is in a segment + /// the runtime cannot write. A writable segment only holds its final value once the runtime has + /// initialised it, so the bytes there are not a constant. + /// + private static bool TryReadSingleLiteral(MethodAnalysisContext context, long address, + [NotNullWhen(true)] out FloatLiteral? literal) + { + literal = null; + + if (address <= 0) + return false; + + var binary = context.AppContext.Binary; + + if (!binary.IsVirtualAddressReadOnly((ulong)address) + || !binary.TryMapVirtualAddressToRaw((ulong)address, out var raw) || raw < 0) + return false; + + var content = binary.GetRawBinaryContent(); + + if (raw + sizeof(float) > content.Length) + return false; + + literal = new FloatLiteral(BinaryPrimitives.ReadSingleLittleEndian(content.Slice((int)raw, sizeof(float)))); + return true; + } + private void ConvertInstructionStatement(Arm64Instruction instruction, List instructions, List addresses, MethodAnalysisContext context) { var address = instruction.Address; @@ -472,6 +502,15 @@ void EmitConditionalAssign(Arm64ConditionCode condition, Action emitTrueValue, A ? new MemoryOperand(addend: (long)address + instruction.Op1Imm) : MemOperand(); + // A single-precision load from a constant address in a segment the runtime cannot + // write is a literal pool entry, so its value is known right here. Restricted to Sn + // because the width is then unambiguous - an LDR Dn may hold two .2S lanes rather + // than one double, and reading it as a double would invent a value. + if (instruction.Op0Reg is >= Arm64Register.S0 and <= Arm64Register.S31 + && source is MemoryOperand { IsConstant: true } poolEntry + && TryReadSingleLiteral(context, poolEntry.Addend, out var poolValue)) + source = poolValue; + if (instruction.Op0Kind == Arm64OperandKind.Register && IsReg31(instruction.Op0Reg)) Add(address, OpCode.Nop); // load to xzr = prefetch, discard else diff --git a/LibCpp2IL/Elf/ElfFile.cs b/LibCpp2IL/Elf/ElfFile.cs index 1f34a833f..9184ae771 100644 --- a/LibCpp2IL/Elf/ElfFile.cs +++ b/LibCpp2IL/Elf/ElfFile.cs @@ -526,6 +526,19 @@ public override (ulong pCodeRegistration, ulong pMetadataRegistration) FindCodeA public override long RawLength => _raw.Length; + public override bool IsVirtualAddressReadOnly(ulong addr) + { + var segment = _elfProgramHeaderEntries.FirstOrDefault(x => x.Type == ElfProgramEntryType.PT_LOAD + && addr >= x.VirtualAddress + && addr < x.VirtualAddress + x.VirtualSize); + + //Bytes past RawSize are zero-filled at load time (.bss) rather than read from the file, so only + //the file-backed part of a non-writable segment holds a constant. + return segment != null + && (segment.Flags & ElfProgramHeaderFlags.PF_W) == 0 + && addr < segment.VirtualAddress + segment.RawSize; + } + public override long MapVirtualAddressToRaw(ulong addr, bool throwOnError = true) { var section = _elfProgramHeaderEntries.FirstOrDefault(x => addr >= x.VirtualAddress && addr < x.VirtualAddress + x.VirtualSize); diff --git a/LibCpp2IL/Il2CppBinary.cs b/LibCpp2IL/Il2CppBinary.cs index 80110baa8..fc2de0050 100644 --- a/LibCpp2IL/Il2CppBinary.cs +++ b/LibCpp2IL/Il2CppBinary.cs @@ -518,6 +518,13 @@ public ulong GetMethodPointer(int methodIndex, Il2CppVariableWidthIndex false; + /// + /// Whether the bytes at the given virtual address cannot change once the binary is loaded, which + /// makes them a compile-time constant. Defaults to false, so a caller only gets a true answer from + /// a binary format that actually reports segment permissions. + /// + public virtual bool IsVirtualAddressReadOnly(ulong addr) => false; + public virtual bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name) { name = null;