ARM64とアセンブラ

ARM64 base instructions
https://developer.arm.com/documentation/ddi0602/2023-12/Base-Instructions?lang=en

汎用レジスタの数が30個、汎用レジスタのサイズが64ビット
よく使われる命令
– mov : レジスタ間のコピー
– ldr : メモリからレジスタに読み出し
– b : 無条件分岐
– bl : サブルーチン呼び出し
– add : 加算
– cmp : 比較
– str : レジスタからメモリへ格納
– ldb : スタックから取り出し
– stp : スタックへ格納
– adrp : レジスタにアドレスを設定
– cbz : 比較して0なら分岐
– b.eq : 等しければ分岐
– ret : サブルーチンから戻る
– sub : 減算
– b.ne : 異なれば分岐
– adr : レジスタにアドレスを設定
– cbnz : 比較して非0なら分岐
– and : ビット積

スタックへの複数レジスタの退避/復帰が2つのレジスタに限定されたLDP/STPに変わっている
ARM64では全ての命令を32ビットで表現

### メモリ、バイト、レジスタ、エンディアン
アセンブリでは2進数やエンディアンという概念を知っておく必要がある
8ビットが1バイト
アナログとは中間も扱うもので、デジタルは0と1のみ
10進数より16進数の方が使われる
補数(complement)を使うことで演算処理が早くなる
64ビットCPUではレジスタから16エクサバイトのメモリを指定できる
64bit レジスタはbytes, Half word, Word, Double Wordを扱える

### エンディアン
レジスタの内容の小さい側をメモリアドレスの小さい側に格納するのをリトルエンディアンと呼ぶ
x86, x86064, arm64はリトルエンディアン、PowerPCはバイエンディアン

Ubuntu(arm64)でassemblerの実行

NASMをインストールする
$ sudo apt install nasm

$ uname -sr
Linux 5.15.0-76-generic

$ nasm -v
NASM version 2.15.05

$ as -v
GNU assembler version 2.38 (aarch64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.38

$ ld -v
GNU ld (GNU Binutils for Ubuntu) 2.38

$ gcc –version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

hello.s

.text
        .global _start
_start:
        mov     x2,  #13    // x2  length
        adr     x1,  msg    // x1  string address
        mov     x0,  #1     // x0  stdout
        mov     x8,  #64
        svc     #0          // sys_write
        mov     x0,  xzr
        mov     x8,  #93
        svc     #0          // exit
msg:
        .asciz  "hello, world\n"

$ as -o hello.o hello.s
$ ld -o hello hello.o
$ ./hello
hello, world

ちなみに、CPUアーキテクチャがx86ではないので x86-64で記述するとエラーになる。

### x86-64とarm64の違い
x86 x64 は CISC(Complex Instruction Set Computer) と呼ばれる命令セット
「複雑な処理」を「少ない命令」で実行しようとする思想です。

arm は RISC(Reduced Instruction Set Computer) と呼ばれる命令セット

x64は高速でパワフル、PCでよく使用される
ARM64は低電力でモバイルデバイスに向いている

アセンブラ入門

### 基礎用語
アセンブラは1行1件で記述され、最大で4つのフィールドを持つ
コメント、オペランド、ニーモニック、名前
ニーモニックは、CPU命令と、ディレクティブ(アセンブラに対する支持)がある。

### ディレクティブ
mov ax, 1234H

cpu命令、レジスタ名は小文字
ディレクティブやラベルは大文字

B 2進数、O,Q 8進数、D 10進数、H 16進数
16進数は0を先頭につける

MSG1 DB ‘ABCDE’

@@: ラベル
@B 直近の先頭行側の@@ラベル参照
@F 直近の最終行側の@@ラベル参照

システムマクロ
ローカルラベル機能がある

### 機械語
CPUの命令のことを機械語(マシン語)という
メモリから1バイトの命令コードをCPU内の実行ユニット部が読み込むと命令デコーダによって解析され、命令タイプが決定する
e.g. 01001000 という命令が来たら 8086CPUでは AXレジスタの内容を1だけ減じる という仕事をする
2進数は覚えにくいので、16進数で4ビットごとに1桁として数え、0~Fで表現する
16ビットは2バイト

48という16進数の数字からAXレジスタから1減じる という意味を読み取ることができないので、機械語と1対1に対応しながら表意的な名前を与える方法が考え出され、それがアセンブラというプログラミング言語
dec ax という記述

mov ax, MYDT1 -> A1 0101
add ax,MYDT2 -> 03 06 0103
mov MYDT3, ax -> A3 0105

MYDT1 DW 10
MYDT2 DW 20
MYDT3 DW 0

より自然な表記が可能となり、C, C++, Javaなどの高級言語が生まれた
Cの場合は、Cコンパイラが機械語に変換
アセンブラソースプログラムが機械語に変換

[プログラミング言語の作り方] グループ化の()対応を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号
– グループ化の()対応

### source.3

a = (5-2) * 3;
print("a = (5-2) * 3 = ", a);

b = 2 * (3 - 5);
print("b = 2 * (3 - 5) = ", b);

### parser.js

function paren(){

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();

        console.log();
        op += expect(tokens,")");
        return right;
    }
    return value();
}

printなどの関数がleftに来る場合は、opが()になるが、計算上の()の場合は、opはreturnされない。
その場合の()で囲む場合は、tokens.shift()の直前で処理をする
L ASTのleftとrightの処理順序が重要

[プログラミング言語の作り方] 符号処理を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号

### source.3

a = -2 * 3;
print("a = -2 * 3 = ", a);

b = -2 * -3;
print("b = -2 * -3 = ", b);

### parser.js
単項の符号に対応するため、flagを追加。右結合なので、リセットでcommaであること、単項であるため、左辺読まずに最後のreturnでfunccallを呼ぶ

function flag(){

    var op;
    while(op = accept(tokens,"+","-")){

        var right = comma();

        if(op == "+") return right;

        return {left:{left:"0", op:"-",right:"1"}, op:"*",right};
    }
    return funccall();
}

$ node interpretor.js
token =[
‘a’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘3’,
‘;’, ‘print’,
‘(‘, ‘”a = -2 * 3 = “‘,
‘,’, ‘a’,
‘)’, ‘;’,
‘b’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘-‘,
‘3’, ‘;’,
‘print’, ‘(‘,
‘”b = -2 * -3 = “‘, ‘,’,
‘b’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘a’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: { left: ‘2’, op: ‘*’, right: ‘3’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”a = -2 * 3 = “‘, op: ‘,’, right: ‘a’ }
}
},
op: ‘;’,
right: {
left: ‘b’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: {
left: ‘2’,
op: ‘*’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}
}
}
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”b = -2 * -3 = “‘, op: ‘,’, right: ‘b’ }
}
},
op: ‘;’,
right: undefined
}
a = -2 * 3 = -6
b = -2 * -3 = 6

余白の有無関係なしに”-“をopとして解釈してしまうから、加算、引き算の後に、符号の処理をするのね
return {left:{left:”0″, op:”-“,right:”1″}, op:”*”,right};とすることで、-3がASTでは以下のように -1 * 3 と表される

right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}

[プログラミング言語の作り方] 掛け算、割り算を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算

### source.3

a = 1.24 / 2 - 1;
print("a = 1.24 / 2 - 1 = ", a);

b = 5 - 2 * 3;
print("b = 5 - 2 * 3 = ", b);

### parse.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function mul(){

    var left = funccall();

    var op;
    while(op = accept(tokens,"*","/")){

        var right = funccall();

        left = {left, op, right};
    }
    return left;
}

function plus(){

    var left = mul();

    var op;
    while(op = accept(tokens,"+","-")){

        var right = mul();

        left = {left, op, right};
    }
    return left;
}

function assign(){
    var left = plus();

    var op;
    while(op = accept(tokens, "=")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = assign();

    var op;
    while(op = accept(tokens,",")){
        var right = assign();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

### run.js

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if (a.op=="+"){
        return run(a.left) + run(a.right);
    } else if (a.op=="-"){
        return run(a.left) - run(a.right);
    } else if (a.op=="*"){
        return run(a.left) * run(a.right);
    } else if (a.op=="/"){
        return run(a.left) / run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

ASTのparse.jsで優先したいopをより深くvalueに近い位置でcallすれば、mulはplusよりも優先して処理される。

[プログラミング言語の作り方] 足し算、引き算対応を作る

足し算、引き算を追加する

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算

### source.3

a = 1 + 2 - 4;
print("a = 1 + 2 - 4 =", a);

b = 3.5 - 1.2 + 0.1;
print("b = 3.5 - 1.2 + 0.1 =", b);

### parse.js

function plus(){

    var left = funccall();

    var op;
    while(op = accept(tokens,"+","-")){

        var right = funccall();

        left = {left, op, right};
    }
    return left;
}

### run.js

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if (a.op=="+"){
        return run(a.left) + run(a.right);
    } else if (a.op=="-"){
        return run(a.left) - run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

$ node interpretor.js
token =[
‘a’,
‘=’,
‘1’,
‘+’,
‘2’,
‘-‘,
‘4’,
‘;’,
‘print’,
‘(‘,
‘”a = 1 + 2 – 4 =”‘,
‘,’,
‘a’,
‘)’,
‘;’,
‘b’,
‘=’,
‘3.5’,
‘-‘,
‘1.2’,
‘+’,
‘0.1’,
‘;’,
‘print’,
‘(‘,
‘”b = 3.5 – 1.2 + 0.1 =”‘,
‘,’,
‘b’,
‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘a’,
op: ‘=’,
right: {
left: { left: ‘1’, op: ‘+’, right: ‘2’ },
op: ‘-‘,
right: ‘4’
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”a = 1 + 2 – 4 =”‘, op: ‘,’, right: ‘a’ }
}
},
op: ‘;’,
right: {
left: ‘b’,
op: ‘=’,
right: {
left: { left: ‘3.5’, op: ‘-‘, right: ‘1.2’ },
op: ‘+’,
right: ‘0.1’
}
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”b = 3.5 – 1.2 + 0.1 =”‘, op: ‘,’, right: ‘b’ }
}
},
op: ‘;’,
right: undefined
}
a = 1 + 2 – 4 =-1
b = 3.5 – 1.2 + 0.1 =2.4

流れがわかってきましたね。

[プログラミング言語の作り方] グローバル変数、代入、変数参照(=)に対応を作る

グローバル変数、代入、変数参照に対応する。変数に型はないので、整数、実数、文字列が代入できる。
変数を宣言しないと、変数定義される。代入演算子としてイコール(=)を導入する。
変数を使ったら、変数に格納されている値を参照できるようにする。

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント

### source.3

a = b = 3;

msg = "a=";

print(msg, a, ", b=", b);

### parser.js
代入演算子(=)を扱うため、assign関数を導入
代入演算子(=)は右結合のため、right側がcommaになっている。a = (b = 1)

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function assign(){
    var left = funccall();

    var op;
    while(op = accept(tokens, "=")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = assign();

    var op;
    while(op = accept(tokens,",")){
        var right = assign();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}


a = b = 3 は、[a, '=', [b, '=', 3]] となる

### run.js
globalを作り、代入演算子の場合、globalオブジェクトに変数名をキーに値を格納する

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

変数の代入の場合は、"="で宣言したタイミングで、グローバル変数としてkey & valueで持っていて、keyが表示されたタイミングで valueを代入している。

ということはjavaのpublic, privateなども変数の持ち方を制限しているのかな。
"=" だけであれば、leftとrightに分けられるから比較的簡単か。これに"=>" "=<"などが増えた場合にどうなるかは興味深いね。

[プログラミング言語の作り方] コメント、数値、複数引数(コンマ)対応の作り方

言語仕様を拡張する
– 文字列
– 整数123, 実数123.4
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント

### source.3

print("引数1個目","引数1個目","引数1個目");
print("整数=",1234);
print("実数=",1234.5);

### lexer.js
\/\/.*$ =>コメント削除、mオプションが必要
\d+\.\d+ =>実数
\d+ => 整数
“.*?” => 文字列
\w+ => print
\s => 空白、改行
. => セミコロン、カッコ、その他

module.exports = function(source){
    var tokens = source.split(/\/\/.*$|(\d+\.\d+|\d+|".*?"|\w+)|\s|(.)/m);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = funccall();

    var op;
    while(op = accept(tokens,",")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

### run.js
1234, 1234.5は文字列になっているので、1を掛けて数値化
tree形式から配列化している
flatに[left, right]に戻す

const { error } = require("./utils");

module.exports = run;

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

opに追加する場合は、字句解析の正規表現と実行時の処理を追加する
カンマがあった場合は、left, op, rightで入れ子にするが、なかった場合は、そのまま処理を続ける

$ node interpretor.js
token =[
‘print’, ‘(‘, ‘”引数1個目”‘,
‘,’, ‘”引数2個目”‘, ‘,’,
‘”引数3個目”‘, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”引数4″‘,
‘)’, ‘;’, ‘print’,
‘(‘, ‘”整数=”‘, ‘,’,
‘1234’, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”実数=”‘,
‘,’, ‘1234.5’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘print’,
op: ‘()’,
right: {
left: { left: ‘”引数1個目”‘, op: ‘,’, right: ‘”引数2個目”‘ },
op: ‘,’,
right: ‘”引数3個目”‘
}
},
op: ‘;’,
right: { left: ‘print’, op: ‘()’, right: ‘”引数4″‘ }
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”整数=”‘, op: ‘,’, right: ‘1234’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”実数=”‘, op: ‘,’, right: ‘1234.5’ }
}
},
op: ‘;’,
right: undefined
}
引数1個目引数2個目引数3個目
引数4
整数=1234
実数=1234.5

javascriptのsplitメソッド

splitは指定した文字列または正規表現によって文字列が分割され、結果が配列になって帰って来る

var str = "あ、い、う";
console.log(str.split("、"));

$ node main.js
[ ‘あ’, ‘い’, ‘う’ ]

正規表現

console.log(str.split(/[0-9]./));

区切り文字を残す場合

var str = "あ、い、う";
console.log(str.split(/(?<=、)/g));

()で囲まれていると残り、囲まれていないと削除

console.log(str.split(/(、)/));

$ node main.js
[ ‘あ’, ‘、’, ‘い’, ‘、’, ‘う’ ]