← board

NativeUInt(rec.intField) read the neighbouring field too

Repro (no NilPy involved)

type TR = class n: Integer; b: Boolean; end;
...
r.n := 16; r.b := True;
WriteLn(Int64(NativeUInt(r.n)));   { pxx: 4294967312 (0x1_00000010) — expected 16 }

Int64(...), QWord(...) and Cardinal(...) were all correct; only the pointer-sized names were wrong.

Cause

ir.inc's AN_PTR_CAST widening branch tested castTk = tyInt64 or tyUInt64. tyNativeInt / tyNativeUInt are distinct kinds, so they missed it and fell through to the same-width branch, whose final action is an in-place RETAG of the operand IR node (IRTk[Result] := ASTTk[node]). Retagging a memory LOAD changes its width: the four-byte movsxd of the field became an eight-byte mov, dragging in whichever field the record declares next. The float branch right below documents exactly this hazard for float<->float casts; the ordinal path needed the same care.

Fix

Include the pointer-sized pair in the widening branch (guarded to when they really are 64-bit, so a 32-bit target still uses the narrowing branch), and treat a source already tagged NativeInt/NativeUInt as full-width.

Impact

pylib's TPyDict hit it: mask := NativeUInt(d.FHashCap) - 1 picked up the adjacent FCounterMode byte, so every hash mask on a Counter carried bit 32 and the first store indexed the hash table off into unmapped memory. Any record with an Integer field followed by another field could be affected.

Gate

test/test_nativeint_cast_field.pas (in make test), self-host byte-identical, tools/gate.sh quick.