XSAVEOPT raises an illegal instruction under Rosetta on an M5 running
macOS 27.0.1, as XSAVEC does, while XSAVE and XRSTOR save and restore
x87, SSE and AVX state in the standard format. The PS5's Zen 2 has
XSAVEOPT, so code built for it can use the instruction without a CPUID
check.
XSAVEOPT differs from XSAVE only in the init and modified optimizations:
it may skip writing state that is in its initial configuration or
unchanged since the last XRSTOR from the same area. XRSTOR restores the
same state from the area XSAVE writes, so --to-rosetta rewrites
XSAVEOPT (NP 0F AE /6, memory operand) to XSAVE (/4) in place, keeping
the prefixes and the operand, as RDSEED becomes RDRAND. CLWB (66 0F AE
/6), MFENCE, UMONITOR, UMWAIT and the Intel target are left alone.
XSAVEC is not converted: it writes the compacted format.
The converter tests check the rewrite for the plain, REX.W,
RSP-relative, extended-base, RIP-relative and FS-relative forms, check
that the neighbouring 0F AE encodings are not taken, and execute the
rewritten XSAVEOPT64 followed by XRSTOR on YMM3 in the x86-64 build.
The --to-rosetta integration test now covers XSAVEOPT as well.
When the instruction after an rsp operand that cannot move in place is
a branch, a rip-relative instruction or a freeing add, the operand's stub
could stay shorter than a jump and the leaf was skipped. The stub now
also takes the contiguous sequential instructions before the operand,
moving their own rsp operands by the shift; the branch-target, site and
existing-stub checks run on the widened range, and the instructions it
covers leave the in-place moves. zlib's inflate_fast, whose only operand
left is a 4-byte cmp %rdi,(%rsp) between an add and a jne, frames this
way.
The backward extension is tibzejoker's, from their comment on
boykopovar/AnyPS5#5640: with it, 243 of Grand Theft Auto V's 991
red-zone leaves frame (240 before), inflate_fast among them, and one
leaf is left with no room for a stub.
red_zone_framer's leaf whose operand at rsp sits before a rip-relative
load now frames; the one before an exit stays refused, its forward
extension reaching the exit's pops.
Co-authored-by: tibzejoker <[email protected]>
A 4-byte 256-bit store at the start of a segment followed by an AMD-only
site that can only be emitted in place (RDSEED, CLWB, MOVNTSS, ...) had no
preceding instruction to move, so WindowPlacer gave up with "No room for a
jump to the stub of 256-bit store within its neighbours or a nearby island".
Try a last-resort window placement that allows EmitOutOfLine to use the
in-place lowering for the follower. Preferred preceding and following moves
are unchanged.
Function starts were direct call targets plus 16-byte-aligned code after
int3 padding. A function reached only through a pointer and not preceded
by int3 then ended up inside the previous region, and its pop rbp; ret
exit could get the epilogue lea without a matching prologue.
The bounds now come from the executable's .eh_frame FDEs (the existing
EhFrameReader), each function is decoded from its FDE start to its FDE
end, and code outside every FDE is not framed. Conversion stops with an
error when the executable has no FDE. Sites added in one pass also count
as taken for the next function.
A leaf whose rsp operand could not keep its encoding after the shift
was skipped: an 8-bit displacement that overflows, an operand at (%rsp)
without a displacement, or an indexed (%rsp,%idx,s) operand. Such an
instruction now moves to a stub, re-encoded with the moved displacement
in 8 bits when it fits and in 32 bits otherwise, followed by a jump
back.
The site covers the instruction and the sequential instructions after
it until it is 5 bytes long. It is refused when a branch lands inside
it, when it would have to cover a branch, ret or rip-relative
instruction (these are not relocated), or when it overlaps the
prologue, an exit, another operand site or an existing stub. Legacy,
REX and VEX forms are re-encoded; EVEX operands, whose 8-bit
displacement is scaled, are still refused. A leaf with no room for a
site is skipped as "rsp operand that cannot move and has no room for a
stub". Operands inside a site that could move in place are carried by
the stub instead, so the site's bytes stay as they were.
The report counts the operand stubs and the operands they carry.
The skip of syscall patterns outside the unwind functions let some real
syscalls through. The scan now merges nested and overlapping unwind
ranges with the instruction scanner's MergeCodeRanges, so a pattern
inside an outer function is refused. A constant syscall number is also
looked for in the bytes in front of the pattern, whatever alignment the
decoding took: a mov eax, mov rax or xor eax, eax that ends at the
pattern or before register moves and zeroings that leave rax alone. The
decoded lookback requires each instruction to end where the next one
starts. Guest modules without PT_SCE_DYNLIBDATA get no unwind ranges,
so their scan stays strict even when their unwind table is read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CLZERO (0F 01 FC) clears the cache line at the logical address in rAX and applies a GS override, so mark its register-form encoding as a memory operand. RDPRU (0F 01 FD) has no memory operand and stays unchanged.
UMONITOR (F3 0F AE /6) takes its effective address from the register operand and uses DS with segment overrides, so mark its register-form encoding as a memory operand. UMWAIT and TPAUSE use the same opcode with F2/66 and have no memory operand.
MASKMOVQ, MASKMOVDQU, VMASKMOVDQU, MONITOR and MONITORX use a register-form ModRM byte but still access memory at DS:rDI or DS:rAX. Mark them as memory operands and cover the encodings in the Windows and macOS GS trap tests.
Rosetta ends the process instead of raising SIGSEGV or SIGBUS when a
VMASKMOVPS, VMASKMOVPD, VPMASKMOVD or VPMASKMOVQ store faults on any
element other than an active first element ("rosetta error: unexpectedly
need to EmulateForward on a synchronous exception"). Measured on an M5
with macOS 27.0.1 for all eight 128- and 256-bit store forms. A masked
store into a page the macOS write watch keeps read-only therefore ends
the title.
--to-rosetta now moves each masked store to a stub that saves RAX, RCX
and RFLAGS below the red zone, computes the operand address with LEA,
reads the mask sign bits with VMOVMSKPS or VMOVMSKPD, and writes to the
first and last byte of every active element with LOCK OR of 0. Those
writes fault normally, so the write watch sees the page and unprotects
it, and they leave memory unchanged. The stub then restores the
registers and runs the original store. Segment and address-size
prefixes are kept on the probes, RSP-based operands skip the spill
frame, and RIP-relative operands are relocated. Masked loads, the
Intel target and a plain relink are unchanged.
masked_store_lowering decodes every form, register and extension,
checks the stub bytes, and on x86-64 Linux and macOS runs 1728 stubs
across all forms, six addressing modes, six offsets around a read-only
page and six mask patterns. It checks the stored bytes, that exactly one
fault reaches the handler when an active element touches the page,
that no register, flag or red-zone byte changes, and that RSP is
restored. Without the probes the x86-64 build aborts under Rosetta.
test_to_rosetta.py checks that only --to-rosetta lowers a VMASKMOVPS.
MCOMMIT becomes STC plus NOP padding and RDPRU goes through a
runtime-checked stub returning the host TSC for MPERF/APERF while
other selectors yield zero, with converter and execution tests.
docs/dev/debt/relinker.md records the new lowering and its unverified
assumptions.
A short AMD-only instruction takes the following instructions into its stub until its site fits a jump. The loop did not check which instructions an earlier 256-bit store island had already claimed, so with --to-rosetta two trampolines covered the same bytes: for je; jmp; vmovups [rdi],ymm0; rcpps xmm1,xmm2 followed by adds, the RCPPS site at +8 overlapped the island at +11, the patched code crashed, and the real patchers failed with 'AMD-only site bytes changed before patching'. A claimed follower now ends the attempt: an optional instruction such as RCPPS or RDPID is kept, and any other instruction fails with an error naming the moved follower.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A short AMD-only instruction lowered through a stub takes the following instructions with it. When one of them is an AMD-only instruction that is replaced in place (MOVNTSS, MOVNTSD, MONITORX, MWAITX, and RDSEED and CLWB for Rosetta), MatchSequence found no out-of-line lowering for it and the conversion failed with 'AMD-only instruction sequence has no out-of-line lowering', for example on extrq xmm0,xmm1 followed by movntss [rdi],xmm0. A following instruction with an in-place replacement now has that replacement moved into the stub, with its RIP-relative displacement relocated. Out-of-line emission for 256-bit store windows is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In PS4 executables and modules the bytes between unwind functions are
16-byte slots of function tails and padding, and a tail can begin inside
an instruction, such as a lone REX byte before a 15-byte NOP. The
syscall and call-site scans decode the executable segment linearly, so
such a slot failed the relink with "Cannot decode instruction".
For images with a PT_SCE_DYNLIBDATA segment the scans now receive the
unwind function ranges. Outside every range an undecodable byte is
skipped and decoding resumes at the next byte, and a decoding that would
cross into a range ends at its start, so gap bytes cannot change how a
function is decoded. Inside a range a decode failure still fails the
relink, and images without unwind ranges are scanned as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rosetta sets RDTSCP's ECX to its constant TSC_AUX, 0xAAAAAAAB, which is
not a processor index. --to-rosetta already makes RDPID return processor
0 for the same reason, so code reading the processor through RDTSCP saw
a different value from code reading it through RDPID.
--to-rosetta now moves RDTSCP into an optional stub that runs the
instruction unchanged and then loads 0 into ECX with a MOV, which
leaves the counter in EDX:EAX and the flags as they were. A site with no
movable instruction after it for the jump is kept, as for RDPID. The
Intel target and a plain relink are unchanged.
A 256-bit store is 4 bytes, so its stub window takes a neighbouring
instruction to make room for the jump. The window took the following
instruction first even when it was a guest TLS access (an FS or GS prefix).
The macOS writer cannot rewrite a TLS access inside a stub and refuses the
module with "AMD-only instruction is also a guest TLS access".
The window now tries the following and the preceding instruction without a
segment prefix first, and falls back to the old order only when neither
works, so outputs that can keep the access in the stub do not change.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
66 E8, 66 E9 and 66 0F 80-8F without REX.W take a 16-bit displacement on
AMD processors, the console included. Every near relative branch with that
prefix truncates its target to 16 bits, so the decoder reports no branch
target for it and stubs refuse to move it.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
GuestImageReader::ReadModuleNames lost its only caller in 41126bfa5.
EmitShiftImm and ElfReader::ReadSection have no callers.
ValidationPolicy's NID, SCE structure size, dynamic field and syscall
instruction checks are never called, ValidateSyscallAbsence is empty,
and RegisterLibraryImport only fills the library set that the unused
NID check reads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Rosetta split covered only VEX scalar compares. #5642 measured the
same lost load after legacy CMPSS and CMPSD, and the decoder already
hands them to the matcher with the right length and RIP offset.
Recognise F3 or F2, segment overrides and an optional REX before
0F C2 /r ib, with the same predicate rule as the VEX forms. A 66, 67 or
LOCK prefix, or both F2 and F3, keeps the instruction inline as before.
DecodeInstruction returned before reading the ModRM of FF /3 and FF /5,
so a far CALL or JMP through a RIP-relative pointer had no RIP-relative
displacement. A stub that moved such a JMP copied the displacement
without relocating it.
The decoder was compared with iced-x86 (AMD decoding) over 3.99 million
encodings: every opcode of the legacy, 0F, 0F38 and 0F3A maps under 34
prefix combinations and the VEX 0F, 0F38 and 0F3A maps, each with 48
ModRM forms. Apart from this, it agrees on length, RIP-relative offset,
control flow kind and branch target for every valid Zen 2 instruction
except near branches with a 66 prefix, recorded as technical debt, and
kernel-only returns, which it treats as sequential.
Add the comparison's metadata as cases: one RIP-relative encoding per
opcode, map and immediate size, every FF /r form, and the branch forms
with their flow kind and target. The existing cases check lengths only;
a loop or jrcxz decoded as an unconditional jump passed the whole suite.