oracle/graal · error · IllegalArgumentException
Update spans the word, not supported
Error message
Update spans the word, not supported
What it means
UnsafeSupport implements sub-word CAS (compareAndExchangeShort) by read-modify-write of the enclosing 4-byte word. A short aligned such that it straddles a word boundary (offset & 3 == 3) cannot be updated atomically within one int word, so it rejects the request with IllegalArgumentException rather than silently breaking atomicity.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/UnsafeSupport.java:98
if (nativeMemory != null && o == null) {
return nativeMemory.getInt(offset, MemoryAccessMode.VOLATILE);
}
return UNSAFE.getIntVolatile(o, offset);
}
private static boolean doCAS(Object o, long offset, int expected, int value, NativeMemory nativeMemory) throws IllegalMemoryAccessException {
if (nativeMemory != null && o == null) {
return nativeMemory.compareAndExchangeInt(offset, expected, value) == expected;
}
return UNSAFE.compareAndSwapInt(o, offset, expected, value);
}
static short compareAndExchangeShort(
Object o, long offset,
short expected,
short x, NativeMemory nativeMemory) throws IllegalMemoryAccessException {
if ((offset & 3) == 3) {
throw new IllegalArgumentException("Update spans the word, not supported");
}
long wordOffset = offset & ~3;
int shift = (int) (offset & 3) << 3;
if (isBigEndian()) {
shift = 16 - shift;
}
int mask = 0xFFFF << shift;
int maskedExpected = (expected & 0xFFFF) << shift;
int maskedX = (x & 0xFFFF) << shift;
int fullWord;
do {
fullWord = doGetIntVolatile(o, wordOffset, nativeMemory);
if ((fullWord & mask) != maskedExpected) {
return (short) ((fullWord & mask) >> shift);
}
} while (!doCAS(o, wordOffset,
fullWord, (fullWord & ~mask) | maskedX, nativeMemory));
return expected;View on GitHub (pinned to a66e9ccd1d)
Solutions
- Align the 16-bit field to a 2-byte boundary that does not straddle an int word (avoid offset % 4 == 3).
- Do a non-atomic read-modify-write under an external lock if straddling is unavoidable.
- Use compareAndExchangeInt over the containing word with manual masking instead of the short variant.
Example fix
// before short v = unsafe.compareAndExchangeShort(buf, 7, exp, upd); // 7 % 4 == 3 -> throws // after int wordOff = 7 & ~3; int shift = (7 & 3) * 8; int mask = 0xFFFF << shift; // CAS the containing int word with masked expected/new values
Defensive patterns
Strategy: validation
Validate before calling
if ((offset & 3) == 3) {
// realign field or use int-word CAS with masking
} Prevention
- Layout packed structs so 16-bit fields never sit at offsets ≡ 3 (mod 4).
- Test CAS code on both endiannesses and at odd offsets.
When it happens
Trigger: Guest code calling Unsafe.compareAndExchangeShort/compareAndSetShort at a byte offset congruent to 3 mod 4 on a byte[] or object (unaligned short field packing, protocol buffer decode buffers, off-heap struct emulation inside Java arrays).
Common situations: Performance libraries packing structs into byte[] and CAS-ing 16-bit fields at odd offsets; data-format parsers that assume host-level atomicity guarantees; code tested only on little-endian hardware where offsets happened to align.
Related errors
- arrayOffset is less than baseOffset
- arrayOffset is beyond array length
- Cannot bind label to negative position %d
- Out of scratch registers: %s
- Branch target %d out of bounds
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/4ac4eefb64b214f5.
Report an issue: GitHub.