[ARM64, ARM64 SNAPDRAGON] Lowering errors on Mac M1 and phone crashes on Snapdragon devices

Hello.

My team has run into an interesting problem when attempting to compile ModuleScripts with NCG enabled.

On x86-64 devices, NCG compiles completely fine. Compilation runs as usual, and the game join process is smooth.
On ARM64 devices, the IR phase of NCG fails for our Crypto ModuleScripts, and we get lowering errors printed in the Console. (Note: this has only been tested on Mac M1)
On Snapdragon processors, the machine code makes it to execution, causing a hard crash of not only the Roblox App, but a soft reboot of the phone (probably because bad ARM64 machine code was piped to the CPU, raising an Undefined Instruction Exception)

Images are below:

[ARM64 (Mac M1)]:

.

[ARM64 (Tested on Snapdragon 888, recorded on a Mac because the recording software on my phone dies when the phone Soft Reboots.)]:

External Media

And ADB crash logs are below:

We are hoping for all our scripts in our game to run with NCG enabled, so we hope that this can get fixed soon.

Thanks!

1 Like

Weird thought they fixed this Codegen changes arguments when a function is called through fast import

1 Like

Hi @RodotIV

Can you confirm if the issue is still occurring on your end?

Thanks!

Hello,

I have just checked the issue is indeed still occurring.

1 Like

Thank you for the report.

There are code complexity limits that can be reached in certain functions, so some failures are expected.
This failures are not preventing code execution in the VM, so it is not blocking you from releasing your experience.
Of course we aim to never have those failures, so we have work planned to improve ARM64 lowering and avoid the limit in these hashing functions.

As for the crash, we could not confirm that it is related to NCG so it is more likely to be some different issue (the log you provided has more errors about Vulkan rendering).
You should open a separate bug report for that crash and provide us with a way to reproduce it.

1 Like

Hey

Thanks for the confirmation, I’ll try to get my team to get compilation errors fixed.

In the meantime, I’ll create a seperate bug report when I have time about the game crashing on mobile devices.

Thanks for the help!

We have a fix for the lowering failure implemented and it should go out with the 690 update this week.

Was this caused because ARM64 has fewer registers compared to others or something? Leading to register spilling issues in ncg for functions with many live values?

ARM64 actually had more registers than x64 and while it does support spilling, it did not trigger it in a few key locations leading to this failure.
x64 would actually begin spilling earlier and that allowed it to support more live values than arm64 without required spills.

1 Like

Having a quick look at the register allocation setup, I see ARM64 is only using 18 of its 31 general purpose registers (avoiding x19 x28 callee saved registers and X0-X13, but I think thats from SME) and 24 of 32 SIMD registers. So while ARM64 theoretically has more registers than x64, was the actual usable register count limited to be similar to or even lower than x64 because codegen might not be using that much in context? Or maybe saving overhead, not sure just had a quick look.

1 Like

Available upper registers are non-volatile and require preservation, which in turn uses extra stack size reducing the general-purpose spill area size.
We might still rebalance to use a few more of ‘x’ registers, but previously we saw more pressure on ‘q’ registers so we allocated accordingly.

1 Like

Sweet!

May you explain why spilling was not being triggered in some places (From what I know as of now, spilling will trigger when the allocator runs out of the explicitly allowed register sets passed in IrLoweringA64 :

IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)

    : build(build)
    , helpers(helpers)
    , function(function)
    , stats(stats)
    , regs(build, function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}})
    , valueTracker(function)
    , exitHandlerMap(~0u)
{
    valueTracker.setRestoreCallack(
        this,
        [](void* context, IrInst& inst)
        {
            IrLoweringA64* self = static_cast<IrLoweringA64*>(context);
            self->regs.restoreReg(inst);
        }
    );
}

(What I thought earlier was that the allocator believed a free register was still available when in reality all are being used)

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.