From bc89f8eb0112326c9ae12402f2719a434a21f5ea Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:18:56 +0100 Subject: [PATCH] Ensure memory ordering in LazyWithContext for ARM64 Add Thread.MemoryBarrier() calls to LazyWithContext<'T, 'Ctxt> to prevent stale reads and writes on weakly-ordered architectures like ARM64. Barriers are placed after checking funcOrException and before setting it to null, ensuring correct visibility of x.value across threads. Comments clarify the reasoning for these changes. --- src/Compiler/Utilities/illib.fs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Utilities/illib.fs b/src/Compiler/Utilities/illib.fs index e58e4e2b756..7236d96a1c0 100644 --- a/src/Compiler/Utilities/illib.fs +++ b/src/Compiler/Utilities/illib.fs @@ -1044,7 +1044,12 @@ type LazyWithContext<'T, 'Ctxt> = member x.Force(ctxt: 'Ctxt) = match x.funcOrException with - | null -> x.value + | null -> + // Ensure we read the latest value after seeing funcOrException as null. + // On weakly-ordered architectures like ARM64, without this barrier the CPU + // can reorder the reads and we may observe a stale (default/null) x.value. + Thread.MemoryBarrier() + x.value | _ -> // Enter the lock in case another thread is in the process of evaluating the result Monitor.Enter x @@ -1066,6 +1071,11 @@ type LazyWithContext<'T, 'Ctxt> = try let res = f ctxt x.value <- res + // Ensure the write to x.value is globally visible before funcOrException + // is set to null, so that readers on the fast path of Force see the result. + // Without this barrier, ARM64 may reorder the stores causing readers to see + // funcOrException as null but x.value as stale (Unchecked.defaultof). + Thread.MemoryBarrier() x.funcOrException <- null res with RecoverableException exn ->