Arm64 Assembly Language


2021.08.13: updated by


実験環境

本ページの実行例は以下の環境で実行したものです。


コマンド
  $ gcc -O -S sample.c
    Cのソースファイル sample.c をコンパイルしてアセンブリ言語のファイル sample.s を生成する
  $ as sample.s -o sample.o
    アセンブリ言語のファイル sample.s をアセンブルしてオブジェクトファイル sample.o を生成する
  $ ld sample.o -e _main -o a.out
    オブジェクトファイルやライブラリをリンクして実行可能ファイル a.out  を生成する
  $ ./a.out
    実行可能ファイル a.out の実行を開始する

アセンブリ言語のディレクティブ

現在のセクションを指定するディレクティブ

section  segname , sectname [[[ , type ] , attribute ] , sizeof_stub ]
指定したセクションを開始する
ディレクティブセクション説明
.text __TEXT,__text 命令を配置するセクションを開始する
.data __DATA,__data 初期値を持つデータを配置するセクションを開始する

Location Counter を動かすディレクティブ

.align     align_expression [ , 1byte_fill_expression [, max_bytes_to_fill] ]
.p2align   align_expression [ , 1byte_fill_expression [, max_bytes_to_fill] ]
.p2alignw  align_expression [ , 2byte_fill_expression [, max_bytes_to_fill] ]
.p2alignl  align_expression [ , 4byte_fill_expression [, max_bytes_to_fill] ]
.align32   align_expression [ , 4byte_fill_expression [, max_bytes_to_fill] ]
align ディレクティブは location counter を align_expression の境界に動かす。 $2^{0}$ から $2^{15}$ まで指定できる。
  (例) .align 3     $2^3 = 8$ バイトのアラインメントに合わせる
.org  expression [ , fill_expression ]
location counter をその値に設定する。 fill_expression が指定された場合は、 現在のlocation counter と新しい値の間をfill_expressionの値で埋める。
  (例)  .org 0x100    100H番地から開始する

データ生成用ディレクティブ

.ascii   [ "string" ] [ , "string" ] ...
.asciiz  [ "string" ] [ , "string" ] ...
.ascii はアスキー文字列を生成する。.asziizは最後に'\0' を追加する。
  (例) .ascii "apple\0"
       .asciiz "apple"
.byte  [ expression ] [ , expression ] ...
  1バイトデータを生成する
.short  [ expression ] [ , expression ] ...
  2バイトデータを生成する
.long  [ expression ] [ , expression ] ...
  4バイトデータを生成する
.quad  [ expression ] [ , expression ] ...
  8バイトデータを生成する
(例)
.byte  74,0112,0x4A,0x4a,'J                             | the same byte
.short 64206,0175316,0xface                             | the same short
.long  -1234,037777775456,0xfffffb2e                    | the same long
.quad  -1234,01777777777777777775456,0xfffffffffffffb2e | the same quad

シンボルを扱うディレクティブ

.globl  symbol_name
symbol_name を外部に見せる( external にする)。

AArch64


A64 メモリアドレッシング

Addressing
Mode
Example説明
Base Registerldr w1, [x0]w1 = *x0
ldr x1, [x0]x1 = *x0
Base Register
+ Offset
ldr w1, [x0,20]x1 = *(x0 + 20)
ldr x2, [x1, x0]x2 = *(x1 + x0)
ldr w2, [x1, x0, lsl 2]w2 = *(x1 + (x0 << 2))
ldr w2, [x0, w1, uxtw 2]w2 = *(x0 + (w1 << 2))
Pre-indexedldr w1, [x0, 4]!x0 += 4; w1 = *x0
ldr x1, [x0, 8]!x0 += 8; x1 = *x0
ldr x2, [x1, x0]!x2 = *(x1 + x0); x1 += x0
ldr x2, [x1, x0, lsl 2]!x2 = *(x1 + (x0 << 2)); x1 += x0 << 2
Post-indexedldr w2, [x1], 4w2 = *x1; x1 += 4
ldr x2, [x1], 8x2 = *x1; x1 += 8
ldr x2, [x1], x0x2 = *x1; x1 += x0
ldr x2, [x1], x0, lsl 2x2 = *x1; x1 += x0 << 2
PC relativeldr x2, labelx2 = *(pc + label_offset)

命令は32 bit幅固定なので、64bitのアドレスをレジスタにロードするには工夫が必要となる。よく使われる方法は次の2通り。

  1. 「PC相対(±1Mbyte, 19bit+2)で指定されるアドレスに64bitデータを置いておく。レジスタにデータが置いてあるアドレスをloadする。そのアドレスからデータをレジスタにloadする
  2.     ldr  x0, =tmp     // データが置いてあるアドレス(tmpラベル)がx0に入る
        ldr  x0, [x0]     // x0が指すアドレスから、64bitデータをx0に読み込む
    .data
    tmp:   .quad 0x123456789abcdef0    // 64bitデータ
    
  3. 複数回に分けてレジスタにアドレスをロードする。
  4. // [例] x0レジスタに 0x123456789abcdef0 をロードする
    mov   x0, #0x1234, lsl #48
    movk  x0, #0x5678, lsl #32
    movk  x0, #0x9abc, lsl #16
    movk  x0, #0xdef0
    
[MOV命令のフォーマット]
    MOVK   Xd, #imm16[, LSL #shift]
    MOV    Xd, #imm16[, LSL #shift]
    MOV    Xd, Xs
    MOV    Xd, operand2

MOV: (MOVe) 指定された16bitにデータ値を設定し、他のビットを0にする。
MOVK: (MOVe Keep) 指定された16bitにデータ値を設定するが、他のビットの値は変更しない。
LSL: (Logical Shift Left) 左論理シフト。指定できる値は 16, 32, 48のどれか。

operand2 は3種類(a register and a shift, a register and an extension operation, a small number and a shift)。

aligning data

.data                            // データ領域の宣言 (プログラムコードは別領域の .text 領域に置かれる)
    .byte 0x3f                   // 8bitデータ
    .align 4                     // アドレスが4byte単位(下2bitが0)になるように揃える
    .word 0x12345678             // 32 bit データ
    .quad 0x123456789abcdef0    // 64 bit データ
    .octa 0x123456789abcdef0fedcba9876543210    // 128 bit データ

A64 条件ジャンプ命令

b.cond
condMeaning
eqEqual等しい
neNot equal等しくない
csCarry set (identical to HS)キャリーが1
hsUnsigned higher or same (identical to CS)符号なしで大か等しい
ccCarry clear (identical to LO)キャリーが0
loUnsigned lower (identical to CC)符号無しで小
miMinus or negative result
plPositive or zero result正または0
vsOverflowオーバーフロー
vcNo overflowオーバーフロー無し
hiUnsigned higher符号ありで大
lsUnsigned lower or same符号ありで小または等しい
geSigned greater than or equal符号付きで以上
ltSigned less than符号付きで未満
gtSigned greater than符号付きで大きい
leSigned less than or equal符号付きで以下
alAlways (default)無条件

Cコンパイラとアセンブラ


値を返す関数のアセンブリ・コード (1)

整数値を返す場合は、x0 を使う。 このレジスタは 64 bit の場合は x0, 32 bit の場合は w0 として指定する。

sample1.c
int sample1() {
  return 5;
}


sample1.s
生成方法: gcc -O -S sample1.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sample1                        ; -- Begin function sample1
	.p2align	2
_sample1:                               ; @sample1
	.cfi_startproc
; %bb.0:
	mov	w0, #5
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

値を返す関数のアセンブリ・コード (2)

実数値を返す場合は v0 を使う。 このレジスタはdouble (64 bit) の場合は d0, float (32 bit) の場合は f0 として指定する。

sample1b.c
double sample1() {
  return 5.0L;
}


sample1b.s
生成方法: gcc -O -S sample1b.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sample1                        ; -- Begin function sample1
	.p2align	2
_sample1:                               ; @sample1
	.cfi_startproc
; %bb.0:
	fmov	d0, #5.00000000
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

引数を渡す関数のアセンブリ・コード

整数やアドレスは x0 - x7 に入れて渡す。 浮動小数点数は v0 - v7 に入れて渡す。 レジスタが足りない場合は、後ろからスタックにpushして渡す。

sample2の実行例
nitta@arm64 % gcc -O sample2a.c sample2b.c sample2c.c -o sample2 
nitta@arm64 % ./sample2
450


sample2a.c
int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9) {
  int ret = 0;
  ret = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9;
  return ret;
}


sample2a.s
生成方法: gcc -O -S sample2a.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sum                            ; -- Begin function sum
	.p2align	2
_sum:                                   ; @sum
	.cfi_startproc
; %bb.0:
	ldr	w8, [sp]
	add	w9, w1, w0
	add	w9, w9, w2
	add	w9, w9, w3
	add	w9, w9, w4
	add	w9, w9, w5
	add	w9, w9, w6
	add	w9, w9, w7
	add	w0, w9, w8
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample2b.c
#include <stdio.h>
extern int sum(int,int,int,int,int,int,int,int,int);
void foo() {
  int ret = 0;
  ret = sum(10, 20, 30, 40, 50, 60, 70, 80, 90);
  printf("%d\n",ret);
}


sample2b.s
生成方法: gcc -O -S sample2b.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_foo                            ; -- Begin function foo
	.p2align	2
_foo:                                   ; @foo
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32                     ; =32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	w8, #90
	str	w8, [sp]
	mov	w0, #10
	mov	w1, #20
	mov	w2, #30
	mov	w3, #40
	mov	w4, #50
	mov	w5, #60
	mov	w6, #70
	mov	w7, #80
	bl	_sum
                                        ; kill: def $w0 killed $w0 def $x0
	str	x0, [sp]
Lloh0:
	adrp	x0, l_.str@PAGE
Lloh1:
	add	x0, x0, l_.str@PAGEOFF
	bl	_printf
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	add	sp, sp, #32                     ; =32
	ret
	.loh AdrpAdd	Lloh0, Lloh1
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"%d\n"

.subsections_via_symbols


sample2c.c
extern void foo();
int main(int argc, char** argv) {
  foo();
  return 0;
}


sample2c.s
生成方法: gcc -O -S sample2c.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	stp	x29, x30, [sp, #-16]!           ; 16-byte Folded Spill
	mov	x29, sp
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	bl	_foo
	mov	w0, #0
	ldp	x29, x30, [sp], #16             ; 16-byte Folded Reload
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

引数を渡す関数のアセンブリ・コード (2) 実数の場合

sample21の実行例
nitta@arm64 % gcc -O sample21a.c sample21b.c sample21c.c -o sample21 
nitta@arm64 % ./sample21
550.0


sample21a.c
double sum(double x1,double x2, double x3, double x4, double x5, double x6, double x7, double x8, double x9, double x10) {
  double ret = 0;
  ret = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10;
  return ret;
}


sample21a.s
生成方法: gcc -O -S sample21a.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sum                            ; -- Begin function sum
	.p2align	2
_sum:                                   ; @sum
	.cfi_startproc
; %bb.0:
	ldp	d17, d16, [sp]
	fadd	d0, d0, d1
	fadd	d0, d0, d2
	fadd	d0, d0, d3
	fadd	d0, d0, d4
	fadd	d0, d0, d5
	fadd	d0, d0, d6
	fadd	d0, d0, d7
	fadd	d0, d0, d17
	fadd	d0, d0, d16
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample21b.c
#include <stdio.h>
extern double sum(double,double,double,double,double,double,double,double,double,double);
void foo() {
  double ret = 0;
  ret = sum(10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0,100.0);
  printf("%lf\n",ret);
}


sample21b.s
生成方法: gcc -O -S sample21b.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_foo                            ; -- Begin function foo
	.p2align	2
_foo:                                   ; @foo
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32                     ; =32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	x8, #4636737291354636288
	mov	x9, #140737488355328
	movk	x9, #16470, lsl #48
	stp	x9, x8, [sp]
	mov	x8, #4630826316843712512
	fmov	d3, x8
	mov	x8, #4632233691727265792
	fmov	d4, x8
	mov	x8, #4633641066610819072
	fmov	d5, x8
	mov	x8, #140737488355328
	movk	x8, #16465, lsl #48
	fmov	d6, x8
	mov	x8, #4635329916471083008
	fmov	d7, x8
	fmov	d0, #10.00000000
	fmov	d1, #20.00000000
	fmov	d2, #30.00000000
	bl	_sum
	str	d0, [sp]
Lloh0:
	adrp	x0, l_.str@PAGE
Lloh1:
	add	x0, x0, l_.str@PAGEOFF
	bl	_printf
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	add	sp, sp, #32                     ; =32
	ret
	.loh AdrpAdd	Lloh0, Lloh1
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"%lf\n"

.subsections_via_symbols


sample21c.c
extern void foo(void);
int main(int argc, char** argv) {
  foo();
  return 0;
}


sample21c.s
生成方法: gcc -O -S sample21c.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	stp	x29, x30, [sp, #-16]!           ; 16-byte Folded Spill
	mov	x29, sp
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	bl	_foo
	mov	w0, #0
	ldp	x29, x30, [sp], #16             ; 16-byte Folded Reload
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

Cコンパイラのオプティマイズ(最適化)・オプション

sample3.c
extern void exit(int);
int main(int argc,char** argv) {
  exit(0);
}


sample3.s
生成方法: gcc -S sample3.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32                     ; =32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	w8, #0
	stur	wzr, [x29, #-4]
	str	w0, [sp, #8]
	str	x1, [sp]
	mov	x0, x8
	bl	_exit
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample3.s
生成方法: gcc -O -S sample3.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	stp	x29, x30, [sp, #-16]!           ; 16-byte Folded Spill
	mov	x29, sp
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	w0, #0
	bl	_exit
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample3.c をオプティマイズ・オプションを変えてコンパイルしてみる。
オブジェクト・ファイルを逆アセンブルして出力されたアセンブル言語を調べる。
実行可能ファイルを生成して実行する。
vermeer-2:i486 nitta$ gcc -c -g sample3.c
vermeer-2:i486 nitta$ objdump -d -S sample3.o

sample3.o:     file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; int main(int argc,char** argv) {
       0:      55 	   pushq %rbp
       1:      48 89 e5    movq	 %rsp, %rbp
       4:      48 83 ec 10 	 subq  $16, %rsp
       8:      b8 05 00 00 00 	 movl  $5, %eax
       d:      c7 45 fc 00 00 00 00    movl	$0, -4(%rbp)
      14:      89 7d f8    movl	 %edi, -8(%rbp)
      17:      48 89 75 f0 	 movq  %rsi, -16(%rbp)
; exit(5);
      1b:	89 c7	movl	%eax, %edi
      1d:	e8 00 00 00 00 	callq 0 <_main+0x22>
vermeer-2:i486 nitta$ gcc -O -c -g sample3.c
vermeer-2:i486 nitta$ objdump -d -S sample3.o

sample3.o:     file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; int main(int argc,char** argv) {
       0:      55 	   pushq %rbp
       1:      48 89 e5    movq	 %rsp, %rbp
; exit(5);
       4:	bf 05 00 00 00	movl	$5, %edi
       9:	e8 00 00 00 00 	callq	0 <_main+0xE>
vermeer-2:i486 nitta$ gcc -O sample3.c -o sample3
vermeer-2:i486 nitta$ ./sample3
vermeer-2:i486 nitta$ echo $?
5
vermeer-2:i486 nitta$ 

C言語の関数内にアセンブリ言語のコードを記述する

Arm64については、 arm Developer サイトの armclang Inline Assembler の説明が最も詳しいようだ。

__asm キーワードを用いると、C言語の関数中にアセンブリ言語のコードを記述できる。

__asm(code [: output_operand_list [input_operand_list [: clobbered_register_list]]]);

output_operandinput_operandはそれぞれ次の要素から構成される。

[ [asm_symbolic_name]]  constraint_string  (c_variable_name)

output_operandinput_operand は 順に0, 1, ... と数字がふられるので、 アセンブリ言語のコード code 中で '$番号' の形(たとえば $0 とか $1)で参照できる。

armのtemplate modifiersについては arm Developer の Inline assembly template modifiers を参照のこと。

output_operandconstraint stringはprefixとして、 '=' (a variable overwriting an existig value) または '+' (when reading and writing) から始まる。

prefixの後、1文字以上のconstraintが記述される。 'r' はレジスタを、'm' はメモリを意味する。 GCCのasm文のConstraints で指定する文字の説明はこちらを参照のこと。 CPU固有のConstrainsの説明は こちらにあり、ARM64は AArch64 として記載されている。 Arm64のConstraintsについては Arm Compiler armclang Reference Guide の Constraint codes for AArch64 stateの方がわかりやすいと思う。

constraint string には Register constraint, Memory constraint, Immediate value constraint の3種類ある。

output_operandconstraint stringは'=' prefix (書き込みを意味する)が指定される。たとえば "=r" のように。

sample4a.c
int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9) {
  int ret;
  __asm__("                                  \n\
	mov	%w0, %w9                    \n\
	add	%w0, %w0, %w1                \n\
	add	%w0, %w0, %w2                \n\
	add	%w0, %w0, %w3                \n\
	add	%w0, %w0, %w4                \n\
	add	%w0, %w0, %w5                \n\
	add	%w0, %w0, %w6                \n\
	add	%w0, %w0, %w7                \n\
	add	%w0, %w0, %w8                \n\
"
	  : "=&r"(ret)
	  : "r" (x1), "r" (x2), "r" (x3), "r" (x4), "r" (x5), "r" (x6), "r" (x7), "r" (x8), "r" (x9)
	  :
	  );
  return ret;
}


sample4a.s
生成方法: gcc -O -S sample4a.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sum                            ; -- Begin function sum
	.p2align	2
_sum:                                   ; @sum
	.cfi_startproc
; %bb.0:
	ldr	w9, [sp]
	; InlineAsm Start

	mov	w8, w9
	add	w8, w8, w0
	add	w8, w8, w1
	add	w8, w8, w2
	add	w8, w8, w3
	add	w8, w8, w4
	add	w8, w8, w5
	add	w8, w8, w6
	add	w8, w8, w7

	; InlineAsm End
	mov	x0, x8
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample4b.c
int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9) {
  int ret;
  __asm__("                                  \n\
	ldr	w8, [sp]                     \n\
	add	w9, w1, w0                   \n\
	add	w9, w9, w2                   \n\
	add	w9, w9, w3                   \n\
	add	w9, w9, w4                   \n\
	add	w9, w9, w5                   \n\
	add	w9, w9, w6                   \n\
	add	w9, w9, w7                   \n\
	add	%w0, w9, w8                   \n\
"
	  : "=r"(ret)
	  : /* no input operands */
	  : "w0", "w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "w9"
	  );
  return ret;
}


sample4b.s
生成方法: gcc -O -S sample4b.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_sum                            ; -- Begin function sum
	.p2align	2
_sum:                                   ; @sum
	.cfi_startproc
; %bb.0:
	; InlineAsm Start

	ldr	w8, [sp]
	add	w9, w1, w0
	add	w9, w9, w2
	add	w9, w9, w3
	add	w9, w9, w4
	add	w9, w9, w5
	add	w9, w9, w6
	add	w9, w9, w7
	add	w10, w9, w8

	; InlineAsm End
	mov	x0, x10
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


sample4c.c
#include <stdio.h>

extern int sum(int x1,int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9);

void foo() {
  int ret = 0;
  ret = sum(10, 20, 30, 40, 50, 60, 70, 80, 90);
  printf("%d\n",ret);
}

int main(int argc, char** argv) {
  foo();
  return 0;
}


sample4c.s
生成方法: gcc -O -S sample4c.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_foo                            ; -- Begin function foo
	.p2align	2
_foo:                                   ; @foo
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32                     ; =32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	w8, #90
	str	w8, [sp]
	mov	w0, #10
	mov	w1, #20
	mov	w2, #30
	mov	w3, #40
	mov	w4, #50
	mov	w5, #60
	mov	w6, #70
	mov	w7, #80
	bl	_sum
                                        ; kill: def $w0 killed $w0 def $x0
	str	x0, [sp]
Lloh0:
	adrp	x0, l_.str@PAGE
Lloh1:
	add	x0, x0, l_.str@PAGEOFF
	bl	_printf
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	add	sp, sp, #32                     ; =32
	ret
	.loh AdrpAdd	Lloh0, Lloh1
	.cfi_endproc
                                        ; -- End function
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	stp	x29, x30, [sp, #-16]!           ; 16-byte Folded Spill
	mov	x29, sp
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	bl	_foo
	mov	w0, #0
	ldp	x29, x30, [sp], #16             ; 16-byte Folded Reload
	ret
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"%d\n"

.subsections_via_symbols


sample4a.c + sample4c.c の実行例
arm64@manet % gcc -O sample4a.c sample4c.c -o sample4a
arm64@manet % ./sample4a
450


sample4b.c + sample4c.c の実行例
arm64@manet % gcc -O sample4b.c sample4c.c -o sample4b
arm64@manet % ./sample4b
450

arm Developer: Arm Compiler armclang Reference Guide: Inline Assembly

arm Developer サイトで公開されている Arm Compiler armclang Reference Guide の中の armclang Inline Assembler から関数内inline assemblyに関係する部分だけをまとめる。

Inline assembly statements within a function

__asm [volatile](
    "assembly string"
    [ : output_operands
    [ : input_operands
    [ : clobbers ] ] ]
);
assembly string はアセンブリで記述されたコードから構成された1つの文字列である。 文字列の中に'%'で始まるテンプレート ( "%modifiernumber" または "%modifier[name]" ) を含めることができる。 このtemplate modifier はオプションのコードで、最終的なアセンブリ文の形式を 修飾する
種類template
modifier
説明
定数cimmediate operand(即値オペランド)。'#'なしで数字を表示する。
nimmediate operand(即値オペランド)。'#'なしで数字を負にして表示する。
メモリmメモリ参照。汎用レジスタにアドレスを保持するように割り当てる。[]つきで表示する。
AArch64aoperand constraintは必ず'r'。[]で囲まれたレジスタ名を表示する。メモリオペランドとして適切。
woperand constraintは必ず'r'。32-bitレジスタ名 W を表示する。
xoperand constraintは必ず'r'。32-bitレジスタ名 X を表示する。
boperand constraintは'w'か'x'。8-bitレジスタ名 B を表示する。
hoperand constraintは'w'か'x'。16-bitレジスタ名 H を表示する。
soperand constraintは'w'か'x'。32-bitレジスタ名 S を表示する。
doperand constraintは'w'か'x'。64-bitレジスタ名 D を表示する。
qoperand constraintは'w'か'x'。128-bitレジスタ名 Q を表示する。
ex01_modifier.c
int foo() {
    int val;
    __asm("        \n\
        ldr %w0, 1f  \n\
        b 2f        \n\
    1:              \n\
        .word %c1     // %c1 は 0x12345678 と変換される。#0x12345678 だとエラーになる。 \n\
    2: \n\
    "
    : "=r" (val)
    : "i" (0x12345678));
    return val;
}


ex01_modifier.s
生成方法: gcc -O -S ex01_modifier.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_foo                            ; -- Begin function foo
	.p2align	2
_foo:                                   ; @foo
	.cfi_startproc
; %bb.0:
	; InlineAsm Start

	ldr	w0, Ltmp0
	b	Ltmp1
Ltmp0:
	.long	305419896
Ltmp1:	; 305419896 は 0x12345678 と変換される。#0x12345678 だとエラーになる。 

	; InlineAsm End
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


ex02_modifier.c
float add(float a, float b) {
    float result;
    __asm("fadd %s0, %s1, %s2"
        : "=w" (result)
        : "w" (a), "w" (b));
    return result;
}


ex02_modifier.s
生成方法: gcc -O -S ex02_modifier.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_add                            ; -- Begin function add
	.p2align	2
_add:                                   ; @add
	.cfi_startproc
; %bb.0:
	; InlineAsm Start
	fadd	s0, s0, s1
	; InlineAsm End
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols


ex03_modifier.c
int saturating_add(int a, int b) {
  int result;
  __asm("add %w0, %w[lhs], %w[rhs]"
	: "=r" (result)
        : [lhs] "r" (a), [rhs] "r" (b)
	);
  return result;
}


ex03_modifier.s
生成方法: gcc -O -S ex03_modifier.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_saturating_add                 ; -- Begin function saturating_add
	.p2align	2
_saturating_add:                        ; @saturating_add
	.cfi_startproc
; %bb.0:
	; InlineAsm Start
	add	w0, w0, w1
	; InlineAsm End
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols
number は0から始まり、オペランドリストの順に対応する数字である。 オペランドはオペランドリスト中でnameを与えられていれば、番号ではなく名前で指定できる。 nameは[]で囲むこと。 output_operand_listinput_operand_list
[name] "constraint" (value)
または
"constraint" (value)
constraint は文字列で、assembly string 中でどのように使われるかを指定する。 output_operand中では、そのオペランドがread onlyかread and writeかを指定する
constraint
modifier
説明
=writeのみ。input_operandのreadが終わった後でしか変更されないことが保証されているので、コンパイラはinput_operandと同じレジスタやメモリに割り当ててもよい。
+read and write。
=&writeのみ。input_operandのreadよりも前に変更される可能性があるので、コンパイラはinput_operand同じレジスタに割り当ててはいけない。
種類constraint
code
説明
定数i「定数」または「大域変数や関数のアドレス」
n定数
メモリmメモリ参照。汎用レジスタにアドレスを保持するように割り当てる。[]つきで表示する。
AArch64r64-bit 汎用レジスタ(X0-X30)
wSIMD or floating-point register (V0-V31)
xoperand 必ず 128-bit vector型。(SIMD V0-V15)
z定数0。XZR or WZR として表示される。
I定数[0, 4095]。12 までの左シフトはオプション。
J定数[-4095, 0]。12 までの左シフトはオプション。
K32 bit論理演算のための定数。
L64 bit論理演算のための定数。
M32 bitレジスタへのMOV命令のための定数。(MOVZ, MOVN, MOVK)
N64 bitレジスタへのMOV命令のための定数。(MOVZ, MOVN, MOVK)
volatileは、assembly stringの中で副作用があるがoutput_operands, input_operands, clobbers でそれが表現されない場合に必要である。 output_operandsがない場合はコンパイラが自動的にvolatile宣言を追加するが、 プログラマがきちんと明記した方がよい。

演習


M1 Mac (MacOS X) 上で演習を行います。

Arm64 のアセンブリ言語で書いたプログラムを実行させてみることにしましょう。 手元の Mac 上で「ターミナル」すなわちシェル(対話環境)を起動して下さい。

多くの部分はCでプログラミングをし、部分的に Arm64 のアセンブリ言語で記述することにしましょう。


課題5a: if文のコードを調べる

提出ファイルex5a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題5a」 http://nw.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai5a

if 文のコードを調べなさい。 適切なCの関数を ex5a.c に定義し、gccを使ってアセブリ言語のコード ex5a.s を出力し、 それにわかりやすいコメントを書き加えてから 提出しなさい。

GCCのアセンブリ言語の中では、

がコメントとなります。

あまりに簡単な条件式でコンパイラが成り立つか否かを判断できる場合は、 if 文のコードをださないことがあることに注意して下さい。 つまり、以下のようなCのコードを書いても駄目だ、ということです。

  (例)
    if (1 > 0) { ... } ←コンパイル時に、いつでも真と判明する
  (例2)
    int a = 500, b=450;
    if (a < b) { ... } ← コンパイル時に、いつでも偽と判明する


課題6a: for文のコードを調べる

提出ファイルex6a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題6a」 http://nw.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai6a

for 文のコードを調べなさい。 適切なCの関数を ex6a.c に定義し、gccを使ってアセブリ言語のコード ex6a.s を出力し、 それにわかりやすいコメントを書き加えてから提出して下さい。


課題7a: 値を返す関数をアセンブリ言語で記述する

提出ファイルex7a.s
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://nw.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai7a

1から「引数で指定された整数」までの和を返す関数を 機械語で書きなさい。 Cの関数の中に __asm 文でアセンブリ言語の命令を書くこと。 それにわかりやすいコメントを書き加えてから提出しなさい。

GCCではLocal Label の文字列は 'L' で始まる必要がある(たとえばL0001)。


課題8a: 引数を受け取り値を返す関数をアセンブリ言語で記述する

提出ファイルex8a.c
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題7a」 http://nw.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai8a

引数として与えられた 64bit整数が奇数であれば1を、 奇数でなければ 0 を返す関数 int isodd(int n) の 本体をアセンブリ言語で書きなさい。

ただし、 __asm の中では機械語命令としては「and だけ」を使うこと。

動作することを確認したら、ソースを提出しなさい。

ex8a.c
long isodd(long n) {
  long ret = 0;
  __asm("  \n\
    # and 命令だけを使って題意の動作をするプログラムを書きなさい \n\
"
	: "=r"(ret)
	: "r" (n)
	);
  return ret;
}


oddmain.c
#include <stdio.h>
#include <stdlib.h>
extern long isodd(int);
int main(int argc, char **argv) {
  long n;
  printf("input integer: ");
  scanf("%ld",&n);
  printf("%ld\n",isodd(n));
}


oddmain.c + ex8a.c の実行例
arm64@manet% gcc -O oddmain.c ex8a.c -o ex8a
arm64@manet% ./ex8a
input integer: 5
1
arm64@manet% ./ex8a
input integer: 4
0

[注意] C言語で関数を作っておいてそれをコンパイルしてアセンブリ言語を出させる方法で、 上記の機械語だけを使ったコードがでるかもしれません。


課題9a: 再帰関数をアセンブリ言語で記述する

提出ファイルex9a.c
コメント欄:なし
提出先: 「宿題提出Web: コンピュータ・アーキテクチャ:課題9a」 http://nw.tsuda.ac.jp/class/arch/local/handin/up.php?id=kadai9a

フィボナッチ数を再帰的に計算する関数 long fib(long) の本体をアセンブリ言語で書きなさい。

  fib(0) = 1
  fib(1) = 1
  fib(n) = fib(n-2) + fib(n-1)

基本的に次と同等の動作をする関数を定義してほしい。

fib.c
long fib(long n) {
  long result = 1L;
   if (n >= 2) {
     result = fib(n-1) + fib(n-2);
   }
  return result;
}


fib.s
生成方法: gcc -O -S fib.c
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_fib                            ; -- Begin function fib
	.p2align	2
_fib:                                   ; @fib
	.cfi_startproc
; %bb.0:
	stp	x20, x19, [sp, #-32]!           ; 16-byte Folded Spill
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	.cfi_offset w19, -24
	.cfi_offset w20, -32
	subs	x19, x0, #2                     ; =2
	b.lt	LBB0_2
; %bb.1:
	sub	x0, x0, #1                      ; =1
	bl	_fib
	mov	x20, x0
	mov	x0, x19
	bl	_fib
	add	x0, x0, x20
	b	LBB0_3
LBB0_2:
	mov	w0, #1
LBB0_3:
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	ldp	x20, x19, [sp], #32             ; 16-byte Folded Reload
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

ただし、Cコンパイラに余計なことさせないために関数宣言は void fib(void) とすること。 また、__asm の中で使うことのできる機械語命令は、以下の種類だけとする。

  stp, str, ldp, ldr, add, sub, cmp, mov, b.lt, b.gt, b.le, b.ge, b, bl, ret

ラベル、レジスタ名、定数などは自由に使用して構わない。 メモリとのデータのやり取りが最小限になるように工夫すること。 すなわち、引数や途中結果などを保存するには、 値が保存されることが保証されているレジスタ(x19 〜 x28 など) をスタックに保存して用いればよい。 x0 〜 x18 は呼び出した関数で値が変更される可能性がある。)

x31 は stack pointer (sp), x30はlink register (blの時の戻り番地)、x29 は frame pointer として使う。

動作することを確認したら、ソースを提出しなさい。

[注意] gcc ではleaf function (他の関数を呼び出さない関数)に対する最適化として Red zone を使用することがある。 すなわち、スタック・ポインタを動かさずに、スタックポインタよりも 128 byte 小さい アドレスまでのメモリ領域を勝手に使うことがある。 したがって、gccがleaf functionとして認識している関数の中で、__asm の中で callq (関数呼び出し)を行う場合は、-mno-red-zone オプションを使って Red zoneによる最適化を禁止する必要がある。

ex9a.cの骨子
void fib(void) として宣言する
long fib(long n) {
  long result;
  __asm volatile("\n\
        ...略...                                 \n\
	stp    x29, x30, [sp, #-16]!             \n\
        add    x29, sp, #16                      \n\
                                                 \n\
                                                 \n\
        // %x0 は変数resultに64bitでアクセスする \n\
	// %x1 は変数nに64bitでアクセスする      \n\
        ...略...                                 \n\
                                                 \n\
                                                 \n\
L01:    \n\
	// x0 に返り値を入れる                   \n\
	ldp    x29, x30, [sp], #16               \n\
        ...略...                                 \n\
	ret                                      \n\
"
		 : "=&r" (result)
		 : "r" (n)
		 : "x0"
    );
    return result;
}

void fib(void) {
  // push %rbp
  // movq %rsp, %rbp
  // これより上は、 コンパイラが自動的に生成する
  __asm__ __volatile__(
                         // %%rdi に引数が入っている
                         // 返り値は %%rax に入れる
		       :
		       );
  // これより下はコンパイラが自動的に生成する
  // pop %rbp
  // retq
}


fibmain.c
#include <stdio.h>
#include <stdlib.h>
extern long fib(long);
int main(int argc, char **argv) {
  long n;
  printf("input integer: ");
  scanf("%ld",&n);
  printf("%ld\n",fib(n));
}


ex9a.c + fibmain.c の実行例
arm64@manet % gcc -mno-red-zone -O ex9a.c fibmain.c -o ex9a
arm64@manet % ./ex9a
input integer: 1
1
arm64@manet % ./ex9a
input integer: 2
2
arm64@manet % ./ex9a
input integer: 5
8
arm64@manet % ./ex9a
input integer: 20
10946

補足説明

__asm文では次のシンタックスで、C言語の変数をアセンブリ言語の記述の中で利用することができる。
__asm( 機械語命令 : output : input : 値を変更するregister)

便利な機能ではあるが、一部無駄が生じることがあるので注意が必要である。

fibmain2.c
#include <stdio.h>
#include <stdlib.h>
extern long fib(long);
int main(int argc, char **argv) {
  printf("%ld\n",fib(45L));
}


fib.c + fibmain2.c の実行例
arm64@manet % gcc -mno-red-zone -O fib.c fibmain2.c -o fib-time
arm64@manet % time ./fib-time
1836311903
./fib-time  4.94s user 0.02s system 93% cpu 5.286 total


ex9a.c + fibmain2.c の実行例
arm64@manet % gcc -mno-red-zone -O ex9a.c fibmain2.c -o ex9b-time
arm64@manet % time ./ex9a-time
1836311903
./ex9a-time  5.40s user 0.02s system 92% cpu 5.848 total
上の例では、C言語のみで記述した関数の方が、__asm()文を使ってアセンブリ言語を用いた関数よりも実行速度が速い 状態である。

http://nw.tsuda.ac.jp/