Skip to content

native-int: SIMD&FP loads/stores and literal loads stay inside regions - #61

Merged
dylanneve1 merged 1 commit into
masterfrom
perf/native-int-vldst
Oct 22, 2026
Merged

dylanneve1 merged 1 commit into
masterfrom
perf/native-int-vldst

Conversation

@dylanneve1

Copy link
Copy Markdown
Collaborator

A census of a native-int iOS boot found SIMD&FP loads/stores and literal loads to be the most common instructions ending native-int regions in the middle of a TB. (PAC branches and B/BL come up more often, but they end the TB anyway.) memcpy and objc paths use Q pairs. This PR covers them:

  • Instructions: LDR/STR of B/H/S/D/Q registers in every addressing mode; LDP/STP of S/D/Q registers; LDR (literal) into general-purpose and SIMD&FP registers. SIMD&FP forms only apply without SVE/SME.
  • How the data moves: vector registers aren't mapped to host registers, so the data goes through the CPU state. A load is the host load into v31 (v30 for the second register of a pair), then a 128-bit store to the CPU state. That also zeroes the bits above the access, as the guest load does.
  • TLB and slow path: a pair takes one TLB check covering both registers. The slow path calls the TCG helper once per register, like TCG does. Literal addresses are the guest pc plus the offset.

Testing:

  • native_int_gen.py now also emits these forms (all sizes and addressing modes, pairs, page-crossing) and literal loads, and folds V0-V7 and V16-V31 into the hash.
  • native_int_check.S test 8: Q and D-pair loads across a page boundary, and a faulting Q load whose destination stays unchanged and whose preceding registers are exact.
  • run-a13-tests.py: 41/41 on an M1 macOS build. A 300-seed soak had 0 mismatches against native-int off and against the host CPU.
  • 4 restore boots with native-int=on on the Android emulator all reached waiting_host (median 9.8 s) with no panic.

🤖 Generated with Claude Code

LDR/STR of B/H/S/D/Q registers (unsigned and unscaled offsets, pre/post-
index, register offset), LDP/STP of S/D/Q registers, and LDR (literal)
into general-purpose and SIMD&FP registers are emitted as native-int ops
instead of ending the region (on cores without SVE/SME, where a SIMD&FP
load writes just the low 128 bits). The data goes through the CPU state
(the vector registers aren't mapped to host registers): a load is the host
load into v31 (v30 for the second register of a pair) and a 128-bit store
to the CPU state, which also zeroes the bits above the access as the guest
load does. A pair takes one TLB check over both registers. The slow path
calls the helper once per register, like TCG. Literal addresses are the
guest pc plus the offset.

In a native-int boot, these were the most common instructions that ended
regions in the middle of a TB, after the PAC branches and B/BL that end TBs
anyway (memcpy/objc paths use Q pairs).

Tests: native_int_gen.py now also emits SIMD&FP loads/stores (all sizes,
all addressing modes, pairs, page-crossing through the x27 window) and
literal loads, and folds V0-V7/V16-V31 into the hash (the host harness
leaves the callee-saved V8-V15 alone). native_int_check.S test 8: Q and
D-pair loads across a page boundary, and a faulting Q load whose
destination must stay unchanged with the registers before it exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dylanneve1
dylanneve1 merged commit cefb274 into master Oct 22, 2026
5 checks passed
@dylanneve1
dylanneve1 deleted the perf/native-int-vldst branch November 2, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant