Language Reference

Lexical structure definition

Instructions for the aarch64 assembling backend use the following lexical structure

Base units

The following base syntax units are recognized by the parser.

Instruction

instruction : ident ("." ident)* (arg ("," arg)* )? ;

Arguments

arg : register | registerlist | labelref | reference | modifier_expr | immediate ;

register : scalar_reg | vector_reg ;

scalar_reg : static_reg_name | dynamic_reg_family "(" expr ")"

vector_reg : ( vector_reg_name | "V" "(" expr ")" ) "." vector_width_spec element_specifier ? ;

register_list : "{ comma_list | dash_list | amount_list "}" element_specifier ? ;

comma_list : register ("," register) * ;

dash_list : register "-" register ;

amount_list : register "*" expr ;

element_specifier : "[" expr "]" ;

reference : "[" refitem ("," refitem)* "]" !"? ;

refitem : register | modifier_expr | immediate ;

modifier_expr : modifier immediate? ;

immediate : "#"? expr ;

Reference

Instructions

The language used by dynasm-rs in aarch64 mode is close to the assembly dialect described in official ARM documentation. Several additions have been made to support dynamic registers and to ensure the Rust parser can handle parsing the language.

The largest difference is in the notation of vector registers. In ARM assembly, the lane count comes before the element size as in v1.16b. But in dynasm-rs, this is reversed as bare identifiers cannot start with numbers. So the used notation ends up being v1.b16. Next to this, the register section will describe the syntax used for addressing registers.

Operands

Register

There are two ways to reference registers in dynasm-rs, either via their static name, or via dynamic register references. Dynamic register references allow the exact register choice to be made at runtime. Note that this does prevent optimizations to register-specific forms. However, the expression inside a dynamic register reference may be evaluated multiple times.

The following table lists all available static registers, their dynamic family name and their encoding when they are used dynamically.

Table 1: dynasm-rs registers (aarch64)

Family64-bit32-bit64-bit32-bit8-bit16-bit32-bit64-bit128-bitvector
Dynamic EncodingXWXSPWSPBHSDQV
0x0w0x0w0b0h0s0d0q0v0
1x1w1x1w1b1h1s1d1q1v1
2x2w2x2w2b2h2s2d2q2v2
3x3w3x3w3b3h3s3d3q3v3
4x4w4x4w4b4h4s4d4q4v4
5x5w5x5w5b5h5s5d5q5v5
6x6w6x6w6b6h6s6d6q6v6
7x7w7x7w7b7h7s7d7q7v7
8x8w8x8w8b8h8s8d8q8v8
9x9w9x9w9b9h9s9d9q9v9
10x10w10x10w10b10h10s10d10q10v10
11x11w11x11w11b11h11s11d11q11v11
12x12w12x12w12b12h12s12d12q12v12
13x13w13x13w13b13h13s13d13q13v13
14x14w14x14w14b14h14s14d14q14v14
15x15w15x15w15b15h15s15d15q15v15
16x16w16x16w16b16h16s16d16q16v16
17x17w17x17w17b17h17s17d17q17v17
18x18w18x18w18b18h18s18d18q18v18
19x19w19x19w19b19h19s19d19q19v19
20x20w20x20w20b20h20s20d20q20v20
21x21w21x21w21b21h21s21d21q21v21
22x22w22x22w22b22h22s22d22q22v22
23x23w23x23w23b23h23s23d23q23v23
24x24w24x24w24b24h24s24d24q24v24
25x25w25x25w25b25h25s25d25q25v25
26x26w26x26w26b26h26s26d26q26v26
27x27w27x27w27b27h27s27d27q27v27
28x28w28x28w28b28h28s28d28q28v28
29x29w29x29w29b29h29s29d29q29v29
30x30w30x30w30b30h30s30d30q30v30
31xzrwzrspwspb31h31s31d31q31v31

When used statically, the notation simply matchers the given name in the table. When used dynamically, the syntax is similar to a function call: X(reg_number), where reg_number is one of the given dynamic encodings listed in the table.

As aarch64 either uses scalar register 31 as the zero register xzr or the stack pointer register sp, two separate families of registers exist to encode this possible difference (as it can influence instruction variant choice).

A special case are the vector registers. These are never used as bare registers, but need to have the element size they are being accessed with postfixed to the register. This element size can be:

This means that the base for for adressing a register statically looks like V1.B or V(num).B. Additionally, many instructions also require the lane count to be specified for the vector register access. As discussed before, this is appended after the element size specifier like V1.B8 or V(num).B16. Finally, vector registers can support a lane element specifier if an instruction aims to only use a certain lane of a vector register. In this case the lane count is always optional. This lane is defined by an index expression postfixed to the vector register like V1.B[1] or V(num).B[lane].

Register lists

Several vector instructions in aarch64 address a list of registers as single operands. There are several syntaxes supported by dynasm-rs for register lists:

Table 2: dynasm-rs register list types

TypeExample
Comma list{ Vn.B, Vn+1.B, Vn+2.B, Vn+3.B }
Dash list{ Vn.B - Vn+3.B }
Amount list{ Vn.B * 4 }

Each of these list notations is interpreted exactly the same by dynasm-rs. The first two are also standard ARM notation, the third format is added by dynasm-rs to handle dynamic registers in register lists as otherwise the amount could only be calculated at runtime. Just like vector registers, register lists support an optional element specifier after them: { Vn.B * 4 }[1].

Jump targets

All flow control instructions and instructions featuring PC-relative addressing have a jump target as argument. This jump target will feature a label reference as described in the common language reference. Note that this reference must be encoded in a limited amount of bits due to the fixed-width aarch64 instruction set, so check the instruction reference to see what the maximum offset range is.

Memory references

As a load-store architecture, the aarch64 instruction set only has a limited amount of instructions capable of addressing memory. Further more, it supports a limited set of addressing modes. The available addressing modes for each instruction are listed directly in the instruction reference. All possible addressing modes are summarized in the table below as well.

Table 3: dynasm-rs memory reference formats

SyntaxExplanation
[Xn|SP]A WSP family register is used as the address to be resolved.
[Xn|SP {, #imm } ]A WSP family register is used as base with an optional integer offset as the address to be resolved.
[Xn|SP, #imm ]!A WSP family register is used as base with an integer offset as the address to be resolved. The final address is written back to the base register.
[Xn|SP], #immA WSP family register is used as the base address to be resolved. Then the immediate is added to the base register and written back.
[Xn|SP, Wm|Xm {, MOD { #imm } } ]A WSP family register is used as base with an (optionally shifted) index register to compute the final address to be resolved.
[Xn|SP], Xm A WSP family register is used as the base address to be resolved. Then the second register is added to the base register and written back.

Modifiers

Several instructions in aarch64, as well as the indexed register addressing mode, support a so-called modifier that change the way the core interprets another argument. The instruction reference shows the supported modifiers for each instruction, and the following table lists all of them:

Table 4: aarch64 modifiers

Modifierimmediate requireddescription
LSLyesLogical shift left
LSRyesLogical shift right
ASRyesArithmetic shift right
RORyesRotate right
UXTBnoUnsigned extend byte
UXTHnoUnsigned extend halfword
UXTWnoUnsigned extend word
UXTXnoUnsigned extend doubleword
SXTBnoSigned extend byte
SXTHnoSigned extend halfword
SXTWnoSigned extend word
SXTXnoSigned extend doubleword
MSLyesShift left, inserting ones

Modifiers can also take an immediate as argument. For shifting modifiers the immediate is required, for extending modifiers it is optional and acts as an extra shift left if provided.

Immediates

Dynasm-rs supports both ARM immediate notation #1 and bare immediate notation 1. As a fixed width instruction set, immediates are bitfields in the respective instructions and will have a limited range. This range can be found for any immediate in the instruction reference. Additionally. Several special immediate classes are distinguished in the aarch64 instruction set. The following table lists all of these.

Table 5: aarch64 special immediate types

Immediate typedescription
Wide immediateA 32 or 64-bit immediate which is encoded by taking 16 bits and shifting them 0, 16, 32, or 48 bits left, with possible inversion afterwards.
Logical immediateA 32 or 64-bit bitfield, composed out of repeated 2, 4, 8, 16, 32 or 64-bit elements with the first n bits set to 1, and then rotated afterwards. All 0 or all 1 cannot be encoded.
Stretched immediateA 64-bit immediate encoded in 8 bits a:b:c:d:e:f:g:h which encodes the binary value 0baaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh.
Floating point immediateA short, float or double value encoded into 8 bits. It can represent any value in the format (-1.0)^s * 2.0^e * (1.0 + m / 16.0) where -3 <= e <= 4, 0 <= m <= 15, s = [0, 1].