| name | asm-x64-to-arm64 |
| description | Convert x64 assembly to AArch64 arm64 — both inline asm (asm volatile / __asm__) and standalone MASM .asm files (PROC/ENDP, equ, macro/endm) translated to GAS .S form. Use when porting x64-specific asm to arm64, adding arm64 support alongside an existing x64 path, translating an entire MASM hot-loop file, or auditing code for missing arm64 coverage. |
| author | Hao Zeng |
Two scenarios this skill covers
- Inline asm in C/C++ —
asm volatile (...) / __asm__ (...) blocks gated
by #ifdef __x86_64__. Constraints, clobbers, and operand templates change;
the surrounding C does not.
- Standalone .asm files (MASM x64 → GAS arm64 .S) — entire files of
OPTION PROLOGUE:NONE / MY_PROC name, n / mov rax, [rsi] style code,
plus their macro headers. The dialect changes wholesale; translate the
directive layer first, then the body.
The instruction-level mappings (registers, ABI, EFLAGS↔NZCV, atomics, SIMD,
memory model) apply to both scenarios identically — only the syntactic
container differs.
Critical pre-translation check
Before applying any rules below, identify:
- Source x64 ABI variant — System V AMD64 (Linux/macOS) or Windows x64.
They differ in argument register order, callee-saved set, and shadow space.
See [[register-and-abi]].
- Target toolchain — clang integrated assembler (recommended; same
.S
works on Linux/macOS/Windows-on-ARM64) or Microsoft armasm64.exe (which
does not consume GAS syntax). See [[standalone-asm-dialect]].
- Concurrency requirements — any plain
mov to/from memory that survived
under x64 TSO must be reviewed for required ordering on arm64.
See [[memory-model-and-atomics]].
Spec dimensions
Detailed translation rules are organized by topic in references/specs/. Each
.md is paired with a structured .yaml for tooling.
| Dimension | Covers |
|---|
| [[inline-asm-constraints]] | GCC asm volatile operand constraints, %w/%x modifiers, clobber list, m operand restrictions, condition-code outputs |
| [[standalone-asm-dialect]] | MASM .asm → GAS .S directives (PROC/ENDP, equ, macro/endm, ptr keyword, includes), Apple _name symbol prefix, p2_ macro idiom, toolchain selection |
| [[register-and-abi]] | x64 ABI variant identification, AAPCS64 argument/callee-saved/scratch sets, special-purpose registers (XR, IP0/IP1, FP, LR, x18), TLS access, stack alignment, red zone, return-address handling |
| [[flags-and-conditions]] | S-suffix discipline, EFLAGS bits with no NZCV equivalent (AF/DF/PF), inverted carry polarity after cmp, conditional branch mnemonic mapping, CMOV→CSEL/CSET, CSDB speculation barrier |
| [[memory-addressing]] | Plain/offset/index/scaled/combined forms, RIP-relative addressing, arm64-only pre/post-indexed and load-pair forms, PUSH/POP→STP/LDP |
| [[memory-model-and-atomics]] | TSO→weak ordering, ldar/stlr single-access acquire/release, mfence/lfence/sfence→dmb, lock cmpxchg→casal/LL-SC, lock and→ldclral with complemented mask |
| [[bit-bulk-special-ops]] | bsr/bsf/popcnt/lzcnt/tzcnt, bit-test→tbz/tbnz, shrd/shld→extr, bswap→rev, REP/string→memcpy or unrolled loop, cache management, prefetch, rdtsc→cntvct_el0, pause→yield, cpuid→OS API |
| [[simd-sse-to-neon]] | XMM/YMM/ZMM→V registers, lane suffixes (.16b/.8h/.4s/.2d), SIMD load/store, integer arithmetic, bitwise (PANDN operand swap), shifts, compares, shuffles (palignr→ext), no-direct-equivalent ops (PMOVMSKB, AES-NI), inline-asm "=w" constraint |
Worked example reference
- [[lzma-dec-port]] — 7-zip's
Asm/x86/LzmaDecOpt.asm (1500-line MASM x64
hot loop) ported to Asm/arm64/LzmaDecOpt.S GAS arm64. Demonstrates the
macro-shim idiom end-to-end, register-pressure annotations, PSHIFT-style
data-width abstraction, calling-convention contract with the surrounding C,
and Windows-on-ARM64 toolchain selection.
Preprocessor guards
Wrap architecture-specific blocks with standard guards. Split Apple vs. Linux
arm64 only when the instruction actually differs (e.g. tpidrro_el0 vs
tpidr_el0):
#if defined(__x86_64__) || defined(__i386__)
#elif defined(__aarch64__) && defined(__APPLE__)
#elif defined(__aarch64__)
#else
#endif
Task
First identify which scenario applies.
Scenario A — inline asm: the target is asm volatile / __asm__ blocks
inside C/C++.
- Read the target code.
- Identify every block that is x64-specific (x64 instructions, named register
constraints, or
__x86_64__/__i386__ guards).
- For each block, determine the intent first (timing? CPU ID? atomic? SIMD?),
then apply the correct mapping from the relevant spec — do not mechanically
swap mnemonics.
- Rewrite operand constraints and clobbers per [[inline-asm-constraints]].
- Wrap both variants in appropriate preprocessor guards.
- Add a one-line comment only when arm64 semantics differ non-obviously from
x64 (e.g. added
dmb, changed zero-input behavior, implicit vs explicit
register).
- Do not change non-asm logic. Do not introduce unnecessary abstractions.
- Show the converted code and explain non-obvious translation decisions in
1–2 sentences.
Scenario B — standalone .asm file: the target is a full MASM x64 file (and
typically a paired macro header).
- Translate the macro header first per [[standalone-asm-dialect]]. It
defines the vocabulary the body uses; without it the body translation is
meaningless.
- Replace dialect directives (
.code → .text, PROC/ENDP → .global +
label, equ → .equ, macro/endm → .macro/.endm).
- Translate the procedure prologue/epilogue once and reuse: AAPCS64 typically
needs
stp x29, x30, [sp, #-16]! on entry and ldp + ret on exit. See
[[register-and-abi]].
- If the file is a long hot loop, consider the macro-shim idiom (see
[[lzma-dec-port]]) before line-by-line translation.
- Translate the body. For each x86 instruction: choose 2-op or 3-op form,
decide whether the S-suffix flag-setting variant is needed
([[flags-and-conditions]]), rewrite memory addressing
([[memory-addressing]]), translate
jcc to b.<cond> watching the
inverted carry semantics.
- Add
#ifdef __APPLE__ switches around every .globl for Mach-O underscore
prefix.
- Verify the symbol still satisfies any link-time version contract with
surrounding C (function name suffix
_3 etc.) and any shared struct layout
(offsetof() .equ constants must match what the C compiler produces).
- Wire into the build system: pick
clang -c --target=... (cross-platform
GAS) or armasm64.exe (Windows-only ARMASM dialect — last resort).
- Test: build, link, run a functional test (e.g. compress/decompress
round-trip), then a benchmark to confirm the asm path is actually faster
than the C fallback. If it isn't, the port likely has redundant flag-setting
or missed a pre/post-indexed addressing opportunity.