Crystal言語でインラインアセンブリを記述する

## はじめに

計算処理を特に高速化したいときや、コンピュータで遊んでみたいときに、CPUに対する命令を直接書いてみたくなるかもしれません。そのような時に、Crystalでは、インラインアセンブリを使うことができます。

CrystalはLLVMコンパイラ基盤の上に構築されたプログラミング言語ですので、LLVMの多くの機能を使うことができます。`Intrinsic` 関数を使ったり、`asm` 構文を記述することができます。

## asm 構文

Crystal は `asm` キーワードを使ってインラインアセンブリを記述できます。

[公式ドキュメント](https://crystal-lang.org/reference/1.16/syntax_and_semantics/asm.html)

```
asm("template" : outputs : inputs : clobbers : flags)
```

- `template` - LLVM統合アセンブラの構文に従ったアセンブリコード
- `outputs`  - 出力オペランド
- `inputs`   - 入力オペランド
- `clobbers` - 破壊されるレジスタ
- `flags`    - オプションフラグ

`asm()` の内部をコロン `:` で区切る構文はCrystal言語の文法としては完全に異質ですが、これはGCCのインラインアセンブリ構文真似たものになっているようです。

実際の例を見ていきます。

### NOP命令

```cr
asm("nop")
```

### 値を出力オペランドに設定

```cr
dst = uninitialized Int32

asm("mov $$10, $0" : "=r"(dst))

p dst # 10
```

紛らわしいですが `$$10` は10の即値リテラルです。`$0` はオペランドプレースホルダです。

`uninitialized Int32` を使っていますが、`dst = 0` と初期化しても動作します。

### 入力オペランドを使用する

```cr
src = 10
dst = 0

asm("mov $1, $0" : "=r"(dst) : "r"(src))

p dst  # 10
```

### 複数の入力オペランドを使用する

```cr
a = 10
b = 20
c = uninitialized Int32

asm("add $2, $0" : "=r"(c) : "0"(a), "r"(b))

p c  # 30
```

### 複数の出力オペランドを使用する

```cr
dst1 = uninitialized Int32
dst2 = uninitialized Int32

asm("
  mov $$10, $0
  mov $$20, $1" : "=r"(dst1), "=r"(dst2))

p dst1
p dst2
```

### Intel構文を使用する

Intel構文を使うこともできます。

```cr
dst = uninitialized Int32

asm("mov dword ptr [$0], 10" :: "r"(pointerof(dst)) :: "intel")

p dst
```

## Intrinsic

比較的単純な命令に関しては、LLVMがIntrinsicを提供しています。Intrinsicは最適化の対象になり、プラットフォームごとに異なる命令を書く必要がありません。また、インタープリターも対応しています。ただし、ほとんどの計算についてすでにCrystalの標準ライブラリのメソッドが用意されており、Intrinsicを直接使ってもパフォーマンスが向上するケースは少ないかもしれません。

Crystalで利用できるIntrinsic命令は、[Intrinsics](https://crystal-lang.org/api/Intrinsics.html) モジュールで定義されています。

### 利用可能なIntrinsic命令一覧

#### `memcpy` - メモリコピー

```cr
src = Slice(UInt8).new(10) { |i| i.to_u8 }  
dest = Slice(UInt8).new(10, 0_u8)  
  
Intrinsics.memcpy(dest, src, 10, is_volatile: false)  

puts "Copied: #{dest}"
```

#### `memmove` - 重複可能なメモリ移動

```cr
buffer = Slice(UInt8).new(10) { |i| i.to_u8 }

Intrinsics.memmove(buffer.to_unsafe + 3, buffer.to_unsafe, 5, is_volatile: false)

puts "Moved: #{buffer}"
```

#### `memset` - メモリ初期化

```cr
buffer = Slice(UInt8).new(10, 0_u8)

Intrinsics.memset(buffer, 0xFF_u8, 10, is_volatile: false)

puts "Set: #{buffer}"
```

#### `debugtrap` - デバッガトラップ

```cr
Intrinsics.debugtrap
```

#### `pause` - CPUポーズ（x86/x64およびAArch64対応）

```cr
Intrinsics.pause
```

これらはCrystalで並列計算を制御する `Mutex` や `SpinLock` の実装に使われているらしい。

#### `read_cycle_counter` - CPUサイクルカウンタ読み取り

```cr
cycles = Intrinsics.read_cycle_counter

puts "Cycles: #{cycles}"
```

以下のようにループを使えば、CPUのサイクルカウンタが増えていく様子を見ることができます。

```cr
loop do
  cycles = Intrinsics.read_cycle_counter
  puts "Cycles: #{cycles}"
  sleep 1.second
end
```

#### ビット反転（bitreverse

- `bitreverse8`, `bitreverse16`, `bitreverse32`, `bitreverse64`, `bitreverse128`

```cr
value = 0b1101001_u8

result = Intrinsics.bitreverse8(value)

puts "Reversed: #{result.to_s(2)}" # 10010110
```

#### バイトスワップ（bswap)

- `bswap16`, `bswap32`, `bswap64`, `bswap128`

```cr
value = 0x12345678_u32

result = Intrinsics.bswap32(value)

puts "Swapped: 0x#{result.to_s(16)}" # 0x78563412
```

#### ポップカウント（popcount）

- `popcount8`, `popcount16`, `popcount32`, `popcount64`, `popcount128`

```cr
value = 0b11010110_i32

count = Intrinsics.popcount32(value)

puts "Bit count: #{count}" # 5
```

#### 先頭ゼロカウント（countleading）

- `countleading8`, `countleading16`, `countleading32`, `countleading64`, `countleading128`

```cr
value = 0b00001111_i32

count = Intrinsics.countleading32(value, false)

puts "Leading zeros: #{count}" # 4
```

#### 末尾ゼロカウント（counttrailing）

- `counttrailing8`, `counttrailing16`, `counttrailing32`, `counttrailing64`, `counttrailing128`

```cr
value = 0b11110000_i32

count = Intrinsics.counttrailing32(value, false)

puts "Trailing zeros: #{count}" # 4
```

## 終わりに

Crystalは日本語の情報が多くないですが、[DeepWiki](https://deepwiki.com/crystal-lang/crystal)に聞けば大抵のことは教えてもらえます。上記の記事もDeepWikiに聞いた情報をもとに、コードが実際に動くことを検証しながら、記事に落とし込んでいます。おすすめです。

この記事は以上です。よいCrystalライフを！