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

  1. Align the 16-bit field to a 2-byte boundary that does not straddle an int word (avoid offset % 4 == 3).
  2. Do a non-atomic read-modify-write under an external lock if straddling is unavoidable.
  3. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/4ac4eefb64b214f5. Report an issue: GitHub.