Skip to content

# Missing Code Transformation: IR.ConvertU64ToR8 (and ConvertI64ToR8 silently truncates on x86) #1297

Open

Description

@leandrown

Description

Compiling code that converts a ulong to double fails with a "Missing Code Transformation" build error on the BareMetal x86 target. Investigating this surfaced a second, more concerning issue: the signed equivalent (long to double), which does compile on x86, silently produces incorrect results for values above roughly 2^31.

This was found while implementing a system tick counter for a hobby OS project built on MOSA BareMetal (x86). The counter is a ulong, and dividing it (as double) to compute elapsed seconds triggered the build failure below.

Version tested

Mosa.Platform/Mosa.Platform.x86/Mosa.DeviceSystem/Mosa.Tools.Package 2.6.1.1669. Confirmed via source inspection that the relevant transforms are unchanged as of the current master (checked against a commit from May 2026), so this is not version-specific to 1669.

Environment

  • Visual Studio 2026 Community, v18.9.2
  • .NET SDK 10.0.400
  • OS: Windows 10.0.26200 (win-x64)
  • Target: BareMetal x86, tested in QEMU (qemu-system-i386)
  • Project targets net10.0, LangVersion 14.0

Problem 1 — IR.ConvertU64ToR8 has no real implementation on any platform

Steps to reproduce

Compile a method that converts a ulong to double, e.g.:

public static double ToSeconds(ulong ticks, uint frequencyHz)
{
    return (double)ticks / frequencyHz;
}

Actual behavior

Error => Missing Code Transformation: 00020: IR.ConvertU64ToR8 XMM#0 [R8] <= EAX [I8] in <MethodName>

Expected behavior

Either the conversion compiles and produces a correct result, or a clear, documented limitation is surfaced earlier (e.g. at the C# level) rather than a low-level IR transform error.

Root cause

IR.ConvertU64ToR8 has no real code transformation implemented on any platform I could find (x86, x64, ARM32). The only related file is a constant-folding optimization (Source/Mosa.Compiler.Framework/Transforms/Optimizations/Auto/ConstantFolding/ConvertU64ToR8.cs), which only helps when the value is a compile-time constant — it doesn't help for runtime values, which is the overwhelmingly common case.

Problem 2 — IR.ConvertI64ToR8 compiles on x86 but silently truncates

While looking for a workaround (casting to long first, since it's signed), I found that the signed conversion, while it does compile on x86, is itself incomplete.

Root cause

Source/Mosa.Compiler.x86/Transforms/BaseIR/ConvertI64ToR8.cs:

public override void Transform(Context context, Transform transform)
{
    transform.SplitOperand(context.Operand1, out var op1Low, out _);
    context.SetInstruction(X86.Cvtsi2sd32, context.Result, op1Low);
}

This splits the 64-bit operand into low/high 32-bit halves and discards the high half, converting only the low 32 bits via Cvtsi2sd32. For any value whose magnitude exceeds ~2^31, this silently produces a wrong result — no exception, no crash, just an incorrect double.

For comparison, the x64 version (Source/Mosa.Compiler.x64/Transforms/BaseIR/ConvertI64ToR8.cs) is correct — it uses Cvtsi2sd64 with the full 64-bit register:

public override void Transform(Context context, Transform transform)
{
    context.SetInstruction(X64.Cvtsi2sd64, context.Result, context.Operand1);
}

So this is x86-32-bit-specific. By contrast, ConvertU32ToR8 (uint to double) is correctly implemented on x86 (Source/Mosa.Compiler.x86/Transforms/BaseIR/ConvertU32ToR8.cs) — it uses the full 32-bit value directly via Cvtsi2sd32, nothing is discarded, since the source is already 32-bit.

Why this is worse than Problem 1

Problem 1 is a hard compile error — annoying, but safe (you find out immediately). Problem 2 is silent: code compiles, appears to work in early testing (small values), and only produces wrong output once a counter or accumulator crosses ~2^31 — e.g. after about 25 days of uptime for a tick counter running at 1kHz. This seems like the more dangerous of the two.

Historical context

I found a MOSA Discord conversation from April 2022 (#compiler channel) where charsleysa and tgiphil solved essentially the same class of problem, but in the opposite direction (double to int64, i.e. ConvertR8ToI64/ConvertR8ToU64). They discussed and rejected the legacy x87 FPU (FLD/FISTTP), confirmed CVTTSD2SI doesn't support a 64-bit destination in 32-bit x86 mode, and ultimately implemented a pure-software solution: manually decomposing/reconstructing the IEEE 754 sign/exponent/mantissa bits, avoiding both the FPU and SSE entirely. That solution is present in current master as Mosa.Runtime.Math.Conversion.R8ToI8/R8ToU8/etc. (Source/Mosa.Runtime/Math/Conversion.cs), invoked via RuntimeCall-category transforms.

Notably, charsleysa wrote at the time: "though I'll probably also add the reverse conversion too" — explicitly acknowledging that the reverse direction (int-to-double, i.e. this issue) would also be needed. As far as I can tell from the current source, that follow-up was never added — there's no I8ToR8/U8ToR8/I4ToR8/U4ToR8 anywhere in Conversion.cs today. I wasn't able to confirm from GitHub search alone whether a PR was ever opened and abandoned, or if it just fell off the list — flagging in case it rings a bell for anyone who was around at the time.

Suggested approach

Given the precedent above, the same bit-decomposition technique — run in reverse (constructing a double's sign/exponent/mantissa from an integer instead of extracting them) — seems like a natural fit, consistent with how R8ToI8/etc. avoid the FPU/SSE limitations on x86 entirely.

Workaround in use

For anyone hitting this: avoid converting a large-magnitude 64-bit integer to double directly. Split the value using integer arithmetic first (e.g. divide into a "whole units" part and a small remainder), and only convert the resulting smaller-magnitude values to double. This sidesteps both problems as long as the "whole units" part stays under ~2^31 in your use case.

Additional notes

  • No similar open issue found in the tracker as of this report.
  • Happy to test a candidate fix against a real bare-metal repro if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions