1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-06-20

はじめに

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

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

asm 構文

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

公式ドキュメント

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

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

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

NOP命令

asm("nop")

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

dst = uninitialized Int32

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

p dst # 10

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

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

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

src = 10
dst = 0

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

p dst  # 10

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

a = 10
b = 20
c = uninitialized Int32

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

p c  # 30

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

dst1 = uninitialized Int32
dst2 = uninitialized Int32

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

p dst1
p dst2

Intel構文を使用する

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

dst = uninitialized Int32

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

p dst

Intrinsic

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

Crystalで利用できるIntrinsic命令は、Intrinsics モジュールで定義されています。

利用可能なIntrinsic命令一覧

memcpy - メモリコピー

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 - 重複可能なメモリ移動

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 - メモリ初期化

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

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

puts "Set: #{buffer}"

debugtrap - デバッガトラップ

Intrinsics.debugtrap

pause - CPUポーズ(x86/x64およびAArch64対応)

Intrinsics.pause

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

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

cycles = Intrinsics.read_cycle_counter

puts "Cycles: #{cycles}"

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

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

ビット反転(bitreverse

  • bitreverse8, bitreverse16, bitreverse32, bitreverse64, bitreverse128
value = 0b1101001_u8

result = Intrinsics.bitreverse8(value)

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

バイトスワップ(bswap)

  • bswap16, bswap32, bswap64, bswap128
value = 0x12345678_u32

result = Intrinsics.bswap32(value)

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

ポップカウント(popcount)

  • popcount8, popcount16, popcount32, popcount64, popcount128
value = 0b11010110_i32

count = Intrinsics.popcount32(value)

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

先頭ゼロカウント(countleading)

  • countleading8, countleading16, countleading32, countleading64, countleading128
value = 0b00001111_i32

count = Intrinsics.countleading32(value, false)

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

末尾ゼロカウント(counttrailing)

  • counttrailing8, counttrailing16, counttrailing32, counttrailing64, counttrailing128
value = 0b11110000_i32

count = Intrinsics.counttrailing32(value, false)

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

終わりに

Crystalは日本語の情報が多くないですが、DeepWikiに聞けば大抵のことは教えてもらえます。上記の記事もDeepWikiに聞いた情報をもとに、コードが実際に動くことを検証しながら、記事に落とし込んでいます。おすすめです。

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?