Quick answer: Burst compiles in the editor under a permissive x86 JIT and fails on AOT mobile targets when the job touches managed types, calls x86-specific intrinsics, leaves Safety Checks enabled in the player, or uses a strict FloatMode the platform cannot match. Replace managed types with NativeContainers and blittable structs, gate intrinsics on X86.Sse.IsSupported / Arm.Neon.IsSupported, set FloatMode to Fast, and disable Safety Checks for player builds.
Here is how to fix Unity Burst when the compiler fails on a platform build. Your job runs perfectly in the editor — you see the green checkmark in the Burst Inspector, frame times look great, you ship a build to TestFlight or to the Switch dev kit, and the build fails with a wall of compiler errors that reference symbols you have never seen. Or worse, the build succeeds but the job silently runs in managed mode at one-tenth the speed because Burst gave up and never told you. The editor and the AOT mobile backends use different code paths, and the gap between them is where this bug lives.
The Symptom
Burst failures on platform builds tend to look like one of these patterns:
Build aborts with a Burst error in the log. The Unity build process prints something like Burst error BC1361: The managed function `MyJob.Execute` is not supported by burst and the build does not produce an output binary. The error message references a method you wrote, but the actual offending code is sometimes a few stack frames deep.
Build succeeds but jobs run slow on device. No error appears in the build log, but on-device profiling shows the job taking ten times longer than it did in the editor. This means Burst silently fell back to the managed implementation. The Burst Inspector for the target platform will show the job marked with a yellow warning instead of a green check.
Crash on first job dispatch. The build runs until the moment a Burst-compiled job is scheduled, then crashes with a SIGILL (illegal instruction) or SIGBUS. This is almost always an x86 intrinsic compiled into an ARM binary — the CPU literally does not understand the instruction.
Wrong numerical results on device. The job runs but produces values that differ from the editor. This is the FloatMode trap: Fast allows reassociation that produces slightly different results, and platforms with stricter FPU defaults can amplify the difference.
What Causes This
Managed code in a Burst job. Anything that allocates on the GC heap, references a managed object, or uses a managed array is forbidden inside [BurstCompile]. This includes List<T>, string, T[], anonymous delegates, lambdas that capture, and any boxing operation. The editor JIT silently allows some of these; the AOT compiler does not.
Platform-incompatible intrinsics. Unity.Burst.Intrinsics.X86 exposes SSE/AVX intrinsics that exist only on x86 CPUs. Calling them on an ARM target fails to compile or emits an instruction that crashes at runtime. The corresponding ARM intrinsics live in Unity.Burst.Intrinsics.Arm.Neon.
FloatMode mismatch. The FloatMode attribute parameter controls IEEE 754 strictness. Strict disables reassociation and fast-math optimizations; Fast enables them. Some platforms’ default FPU rounding modes do not align with what Burst expects under Strict, so jobs that work on desktop produce wrong values on device.
Safety checks left on for player. Burst safety checks (bounds checking, container ownership) are useful in the editor but disabled by default in player builds for performance. If you accidentally enable them globally, AOT compilation can fail or produce dramatically slower code on memory-constrained platforms.
The Fix
Step 1: Open the Burst Inspector for the failing platform. Jobs > Burst > Open Inspector. At the top, change the Target dropdown to your failing platform (e.g., iOS, Android ARMv8, Switch). The inspector lists every Burst-compiled job. Each one shows green if it compiled cleanly, yellow if it fell back, red if it errored. Click a red entry to see the exact error.
Step 2: Replace managed types. Walk every red job and replace any managed type with its native equivalent. A typical refactor:
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
[BurstCompile(FloatMode = FloatMode.Fast, OptimizeFor = OptimizeFor.Performance)]
public struct ParticleUpdateJob : IJobParallelFor
{
// Blittable value types only; no class refs.
[ReadOnly] public NativeArray<float3> Velocities;
[ReadOnly] public float DeltaTime;
public NativeArray<float3> Positions;
public void Execute(int index)
{
// Pure math on blittable types — Burst-friendly.
Positions[index] += Velocities[index] * DeltaTime;
}
}
If you previously had List<Vector3>, switch to NativeList<float3> with Allocator.TempJob or Persistent. If you needed a string, use FixedString128. If you needed a callback, refactor to data-oriented dispatch (the calling code post-processes the result instead of the job invoking a delegate).
Step 3: Gate intrinsics by architecture. Any code that uses an x86 intrinsic must check X86.Sse.IsSupported at the top of the function and provide an ARM or scalar fallback. The compiler treats IsSupported as a constant per target so the unused branch is eliminated:
using Unity.Burst;
using Unity.Burst.Intrinsics;
using static Unity.Burst.Intrinsics.X86.Sse;
using static Unity.Burst.Intrinsics.Arm.Neon;
[BurstCompile]
public static unsafe void DotProduct4(
float* a, float* b, float* outResult)
{
if (X86.Sse.IsSupported)
{
v128 va = load_ps(a);
v128 vb = load_ps(b);
v128 prod = mul_ps(va, vb);
// Horizontal sum via SSE shuffles, then store scalar.
store_ss(outResult, prod);
}
else if (Arm.Neon.IsSupported)
{
v128 va = vld1q_f32(a);
v128 vb = vld1q_f32(b);
v128 prod = vmulq_f32(va, vb);
*outResult = vaddvq_f32(prod);
}
else
{
// Portable scalar fallback for any other target.
*outResult = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3];
}
}
Step 4: Set FloatMode and disable Safety Checks for player builds. On every job, prefer FloatMode = FloatMode.Fast unless you have a specific numerical reason for Strict. Open Jobs > Burst > Safety Checks and confirm the player setting is Off. Safety Checks On in a player build can mask AOT failures and tank performance.
Reading the Burst Error
Burst errors are dense but precise. Common ones and what they mean:
BC1091 — the job uses a managed type. The error names the type. Find it and replace with a native equivalent.
BC1361 — a managed function was reached. Often this is Debug.Log, UnityEngine.Object reference, or a string concatenation. Wrap calls in [Conditional("UNITY_EDITOR")] or remove them.
BC1051 — a constant could not be folded. Usually a non-blittable static field. Replace with a [ReadOnly] NativeArray or pass as a job field.
BC1370 — an intrinsic is not supported on the target. Add the right IsSupported guard.
Verifying on Device
After fixing, rebuild for the target and check the player log. On iOS use idevicesyslog, on Android use adb logcat -s Unity. Search for Burst: the runtime prints which jobs were AOT-compiled successfully versus which fell back. A successful fix shows every job listed as compiled. Profile the job on device with the Unity Profiler attached over USB to confirm the timing matches your editor expectations.
“The editor lies. It compiles your Burst job under conditions that no shipped device will ever match. The Burst Inspector with the platform target set is the only source of truth.”
Related Issues
If your Burst job compiles but skips silently at runtime without an error, see Burst Compiler Silently Skipping Job. For IL2CPP build failures more broadly, check IL2CPP Build Errors.
Always set the Burst Inspector target to your shipping platform and walk every yellow or red entry — do not trust the green editor checkmark.