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?

MIPS のアセンブリ言語で競技プログラミングの問題を解いてみる

Posted at

大学の講義で扱った MIPS のアセンブリ言語で競技プログラミングの問題を解いてみた。

環境構築

今回は、AWS EC2 の Ubuntu 24.04 (AMI ID: ami-05d38da78ce859165) で実行環境を構築してみた。

とりあえず環境をアップデートする。

sudo apt-get update
sudo apt-get -y upgrade

アセンブラ (Binutils) と実行ツール (QEMU) をインストールする。

sudo apt-get install -y binutils-mips-linux-gnu qemu-user

以下のようにして、アセンブリ言語のプログラム program.s を実行可能ファイル program に変換できる。

mips-linux-gnu-as -o program.o program.s
mips-linux-gnu-ld -o program program.o

以下のようにして、変換した実行可能ファイル program を実行できる。

qemu-mips program

今回の環境におけるアセンブリ言語

システムコール

今回の環境では、よく言われる「1 で整数を出力、10 で終了」などのシステムコールは使えないようである。
かわりに、https://syscalls.w3challs.com/?arch=mips_o32 で挙げられているシステムコールが使えるようである。
たとえば、以下のシステムコールが使える。

操作 $v0 $a0 $a1 $a2
終了 (exit) 0xfa1 終了コード - -
入力 (read) 0xfa3 ファイル
ディスクリプタ
ストア先ポインタ 最大入力バイト数
出力 (write) 0xfa4 ファイル
ディスクリプタ
ロード元ポインタ 出力バイト数

ファイルディスクリプタは、標準入力が 0、標準出力が 1、などである。
システムコールを実行すると、結果を表す値が $v0 に格納される。
また、callee-save レジスタの値は保持されるが、caller-save レジスタの値は破壊される可能性がある。
ただし、$a0$a1$a2 レジスタの値も保持される。

エントリポイント

プログラムの実行を開始する場所として、__start ラベルを定義することが求められる。
さらに、ただ定義するだけではなく、.global を使用して公開することも求められる。

.global __start
__start:
	# 正常終了する
	addi $v0, $zero, 0xfa1
	addi $a0, $zero, 0
	syscall

遅延分岐スロットを手動で扱う

MIPS といえば、分岐命令により飛ぶ場合でも分岐命令の1個次の命令を実行してから飛び先の実行に移る「遅延分岐スロット」の存在が特徴的である。

しかし、そのことを確認しようとして以下のプログラムを実行しても、命令 addi $a0, $zero, 1 は実行されず、終了コードは 0 となってしまう。

.global __start
__start:
	addi $v0, $zero, 0xfa1
	addi $a0, $zero, 0
	j dest
	addi $a0, $zero, 1 # 実行されない

dest:
	syscall

これは、アセンブラが命令列を以下のように自動で変更し、遅延分岐スロットが存在しないかのように実行させるためである。
(これは、mips-linux-gnu-objdump -d program で逆アセンブルすることにより確認できる)

Disassembly of section .text:

004000d0 <__start>:
  4000d0:       20020fa1        addi    v0,zero,4001
  4000d4:       08100038        j       4000e0 <dest>
  4000d8:       20040000        addi    a0,zero,0
  4000dc:       20040001        addi    a0,zero,1

004000e0 <dest>:
  4000e0:       0000000c        syscall
        ...

この自動変更を停止し、遅延分岐スロットを手動で考慮したプログラムをそのまま実行させるには、プログラムの先頭に .set noreorder を加えるとよい。

.set noreorder
.global __start
__start:
	addi $v0, $zero, 0xfa1
	addi $a0, $zero, 0
	j dest
	addi $a0, $zero, 1 # 実行される

dest:
	syscall

実装

PracticeA - Welcome to AtCoder
を解くプログラムを実装してみた。
これは、3個の整数と1行の文字列が与えられるので、これらの整数の和と与えられた文字列を空白区切りで出力する問題である。

.set noreorder
.text
.global __start
__start:
	addi $s0, $zero, 0    # 整数の和
	addi $s1, $zero, 3    # 読み込む整数の数
	# 文字を読み込む準備
	addi $a0, $zero, 0
	la $a1, data_buffer
	addi $a2, $zero, 1
read_ints:
	addi $s3, $zero, 0    # 今回読み込んだ整数
read_one_int:
	# 文字を読み込む
	addi $v0, $zero, 0xfa3
	syscall
	lb $t0, ($a1)
	# 今回読み込んだ整数に10を掛ける (ロード直後の値を用いるのは避ける)
	sll $t1, $s3, 1
	sll $t2, $s3, 3
	add $t1, $t1, $t2
	# 読み込んだ値が '0' 未満 (改行や空白) なら、読み込みを終了する
	slti $t2, $t0, 0x30
	bne $t2, $zero, read_one_int_end
	addi $t0, $t0, -0x30
	# 今回読み込んだ整数を更新する
	j read_one_int
	add $s3, $t1, $t0     # 遅延分岐スロット
read_one_int_end:
	addi $s1, $s1, -1
	bne $s1, $zero, read_ints
	add $s0, $s0, $s3     # 読み込んだ整数を足す (遅延分岐スロット)

	# 求めた整数の和を文字列に変換し、出力する (空白も足しておく)
	addi $s2, $zero, 10   # 除算用 (十進数)
	addi $s3, $a1, 15     # 変換結果の最後の要素の次の要素へのポインタ
	addi $s4, $s3, -1     # 次に変換結果を格納する要素へのポインタ
	addi $t0, $zero, 0x20
	sb $t0, ($s4)
write_int:
	# 整数の和を10で割り、余りに '0' を足して文字列として格納する
	div $s0, $s2
	mfhi $t0
	mflo $s0
	addi $t0, $t0, 0x30
	sb $t0, -1($s4)
	# 商が 0 でないなら、次の桁の変換に進む
	bne $s0, $zero, write_int
	addi $s4, $s4, -1     # 格納先を更新する (遅延分岐スロット)
	# 変換結果の文字列を出力する
	addi $v0, $zero, 0xfa4
	addi $a0, $zero, 1
	addi $a1, $s4, 0
	sub $a2, $s3, $s4
	syscall

	# 1行読み、そのまま出力する
	addi $s1, $zero, 0x0a # 改行コードを読み込んだら終了する用
	addi $a0, $zero, 0
	la $a1, data_buffer
	addi $a2, $zero, 1
print_line:
	addi $v0, $zero, 0xfa3
	syscall
	lb $s0, ($a1)
	addi $a0, $zero, 1
	addi $v0, $zero, 0xfa4
	syscall
	bne $s0, $s1, print_line

	# プログラムを終了する
	addi $a0, $zero, 0    # 終了コードの 0 と読み込み元の標準入力を兼ねる
	addi $v0, $zero, 0xfa1
	syscall

.data
data_buffer:
	.space 16

結論

Ubuntu 24.04 上で MIPS のアセンブラと実行用プログラムを用意し、競技プログラミングの問題を解くコードを動かすことができた。

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?