演習第3回:アセンブラプログラミング(1)サブルーチン(説明資料

概要

MIPSのシミュレータspim上で実行可能なアセンブリ言語のコードを用いてプログラムを作成する。

実用的な処理を行うためのプログラムでは、ひとまとまりの処理を独立なものとして定義し、それを再利用するサブルーチンを用いる。

アセンブリ言語においては、レジスタの種類と数およびその使用目的に厳格な決まりがあるため,特にサブルーチンの使用に当たっては注意が必要である.サブルーチンにデータを渡すためにはレジスタ$a0~$a3を用いる。レジスタ$v0と$v1はサブルーチンの戻り値を格納することになっているし、$v0はシステムコール番号を設定するためにも使う。

作業用レジスタは$s0~$s7および$t0~$t9の2種類があるがサブルーチンの使用に当たっては区別して使わなければならない。$s0~$s7はサブルーチンを呼んでも呼び出し側では書き換えられない決まりになっているので、サブルーチン内で書き換える必要がある場合には、サブルーチン内で退避と復元が必要である。それに対し、$t0~$t9はサブルーチン内で書き換えても良いことになっているため、必要な場合はサブルーチンを呼び出す側で退避および復元が必要となる。

今回の課題では、サブルーチン内ででtレジスタは使用禁止。サブルーチン内で使用する作業用レジスタはそれぞれ$s0~$s7を番号の若い方から順に使用する。もちろん各ルーチンの実行前後でレジスタの値が変わらないよう一時待避が必要。

サブルーチンの使用に当たって必須であるレジスタの退避の方法としてスタック(stack)を用いる。

サブルーチンでデータを渡すレジスタは$a0~$a3の4つと決まっている。データの個数が4を超える場合はどうしたらよいのか?サブルーチンの処理結果のデータを呼び側に渡すためのレジスタは$v0と$v1の2つであるが、3つ以上のデータを返す場合はどうしたらよいか?メモリに必要な分のデータ領域を確保し,その領域のアドレスをメインルーチンとサブルーチンの間で受け渡しすれば,この問題は解決する。スタックを使うこともこの問題を解決するための必須テクニックである。

レポートの提出期限:2026年7月2日(木)23:59:59

課題1(メモリアクセス・大域配列)

キーボードから読み込んだ複数の整数値をメモリー上に一時保存し、読み込みが完了してから総和の計算を行ないたい。これを実現するための以下の2つのサブルーチンを作成せよ。実行して正しく機能することを示すこと.

その1:サブルーチンsum

【機能】引数$a0で指定されたアドレスから始まるメモリーに、引数$a1で指定された個数の32ビット整数値が格納されているとし、これらの総和を求めて、その値を$v0で返す。データ数を示す$a1は1以上の整数と仮定してよい。

sumへ渡す2つの引数は、C言語で言うと配列の先頭のポインタと配列の要素数に当たる。総和を求める対象は配列の要素に当たり、$a0 で渡されたアドレスを1ワードずつ (4バイトずつ) 増加させることで、その要素に順にアクセスすることが可能になる。C言語における、ポインタ変数 p を増加させながら値を参照する*p++ の振舞いと同等である。

sumの機能を確かめるために、下記のテスト用メインルーチンを使用しても良い。ただし、sumはこのテスト用メインルーチンに特化した実装を行なってはならない。あくまでも、上記【機能】を満たす汎用のサブルーチンである。

sumの機能を確かめるプログラムは testsum.s とする.

# サブルーチン sum のテスト .data list: .word 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 .text .globl main main: addi $sp, $sp, -4 # スタックに1ワードの領域を確保 sw $ra, 0($sp) # 戻り先アドレスの退避 la $a0, list # 第1引数=データ領域listのアドレス li $a1, 10 # 第2引数=10 (データ数) jal sum # サブルーチンsumの呼び出し move $a0, $v0 # システムコールの引数=sumの戻り値$v0 (総和) li $v0, 1 # print_integerの指定 syscall # 総和の出力 lw $ra, 0($sp) # 戻り先アドレスの復元 addi $sp, $sp, 4 # スタックに確保した領域の開放 jr $ra # ここから sum の定義を書く

その2:サブルーチンread

【機能】引数$a0で指定されたアドレスから始まるメモリ領域に対し、引数$a1で指定された個数の32ビット整数をキーボードから読み込み順次格納する。C言語で言う配列に整数値を順次読み込む機能と考えてよい。データ数を示す$a1は1以上の整数と仮定してよい。

サブルーチンreadでは整数値の読み込みのためにシステムコールが必要になる。また引数として受け取った$a0、$a1もシステムコールで値が変更される可能性があるため、そのまま継続して利用するのは不適切である。

readとsum を組み合わせたの時の動作を確かめるために、下記のメインルーチンを使用してもよい。ただし、readとsumはこのテスト用メインルーチンに特化した実装を行なってはならない。あくまでも、上記【機能】を満たす汎用のサブルーチンである。

readとsumの機能を確かめる以下のプログラムは testreadsum.s とする.

# サブルーチン read, sum のテスト # スタック上に5ワード分の読み込み用バッファ領域を確保して、 # 5つの値の読み込みと総和の計算を実行する。 .text .globl main main: addi $sp, $sp, -24 # スタックに 6 ワードの領域を確保 # 0($sp)は$raの待避用 # 4($sp)から5ワード分は配列変数として利用する sw $ra, 0($sp) # 戻り先アドレスの退避 addi $a0, $sp, 4 # 第1引数 配列変数用領域のアドレス ($sp + 4) li $a1, 5 # 第2引数=5 (データ数) jal read # サブルーチン read の呼び出し # → 配列変数にデータが読み込まれる addi $a0, $sp, 4 # 第1引数=配列変数用領域のアドレス ($sp + 4) li $a1, 5 # 第2引数=5 (データ数) jal sum # サブルーチン sum の呼び出し # → $v0に総和が入る move $a0, $v0 # システムコールの引数 = sumの戻り値$v0 (総和) li $v0, 1 # print_integerの指定 syscall # 総和の出力 lw $ra, 0($sp) # 戻り先アドレスの復元 addi $sp, $sp, 24 # スタックに確保した領域の開放 jr $ra # ここから sum と read の定義を書く

課題2(サブルーチンの入れ子)「互いに素であることの判定」

2つの自然数aとbの最大公約数を計算するサブルーチン"gcd"を作成し、これを用いてaとbが互いに素であるかどうか判定するプログラム"coprime.s"を作成せよ。最大公約数の算法としてはユークリッドの互除法が有名である。サブルーチンgcdは再起呼び出しでないアルゴリズムで実装すること。

(spim) load "coprime.s" (spim) run a= 24 b= 12 not coprime (spim) run a= 24 b= 23 coprime (spim)

サブルーチンgcdの機能を確かめるためのmainプログラムは次のものを使って良い。gcdは2つの整数を$a0と$a1で受け取り、その最大公約数を計算して$v0で返す。

#-------------------------------------------------------------------------- # # gcd: 入力された2整数の最大公約数を計算する(2整数は0以上であると仮定) # 非再帰的アルゴリズム # # 情報領域演習第二C演習第4回課題2(2026年6月26日) # # #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- # データセグメント #-------------------------------------------------------------------------- .data prmpta: .asciiz "a= " # aの入力プロンプト prmptb: .asciiz "b= " # bの入力プロンプト ans: .asciiz "gcd(a,b)= " # aとbの最大公約数 newln: .asciiz "\n" # 改行 #-------------------------------------------------------------------------- # テキストセグメント #-------------------------------------------------------------------------- .text .globl main main: sub $sp, $sp, 4 # スタックに1ワードの領域を確保 sw $ra, 0($sp) # 戻り先アドレスの退避 li $v0, 4 # print the prompt "a = " la $a0, prmpta syscall li $v0, 5 # read integer from the keyboard syscall move $t0, $v0 # $t0 <- a li $v0, 4 # print the prompt "b = " la $a0, prmptb syscall li $v0, 5 # read integer from the keyboard syscall move $t1, $v0 # $t1 <- b # gcd関数を呼び出す move $a0, $t0 # 引数1 (a) move $a1, $t1 # 引数2 (b) jal gcd move $t3, $v0 # $t3に結果を保存 # 結果の表示 li $v0, 4 # print "gcd(a,b)= " la $a0, ans syscall li $v0, 1 move $a0, $t3 syscall li $v0, 4 la $a0, newln syscall # プログラムの終了 lw $ra, 0($sp) # 戻り先アドレスの復元 addi $sp, $sp, 4 # スタックに確保した領域の開放 jr $ra #-------------------------------------------------------------------------- # # gcd: 最大公約数(greatest common divisor)をユークリッドの互除法で計算 # # 入力 # $a0:a # $a1:b # # 出力 # $v0:gcd(a,b) aとbの最大公約数 # #-------------------------------------------------------------------------- gcd: ...

サブルーチンcoprimeの機能を確かめるためのmainプログラムは次のものを使って良い。coprimeは2つの整数を$a0と$a1で受け取り、互いに素であれは整数値1を、互いに素ではない場合は整数値0を$v0で返す。

#-------------------------------------------------------------------------- # # coprime: 入力された2整数が互いに素であるかどうか判定する # # 情報領域演習第二C演習第3回課題2(2026年6月26日) # # #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- # データセグメント #-------------------------------------------------------------------------- .data prmpta: .asciiz "a= " # aの入力プロンプト prmptb: .asciiz "b= " # bの入力プロンプト yes: .asciiz "coprime\n" # 互いに素である no: .asciiz "not coprime\n" # 互いに素ではない #-------------------------------------------------------------------------- # テキストセグメント #-------------------------------------------------------------------------- .text .globl main main: sub $sp, $sp, 4 # スタックに1ワードの領域を確保 sw $ra, 0($sp) # 戻り先アドレスの退避 li $v0, 4 # print the prompt "a = " la $a0, prmpta syscall li $v0, 5 # read integer from the keyboard syscall move $t0, $v0 # $t0 <- a li $v0, 4 # print the prompt "b = " la $a0, prmptb syscall li $v0, 5 # read integer from the keyboard syscall move $t1, $v0 # $t1 <- b # coprime関数を呼び出す move $a0, $t0 # 引数1 (a) move $a1, $t1 # 引数2 (b) jal coprime # 結果の表示 beq $v0, $zero, not_coprime li $v0, 4 la $a0, yes syscall b end_main not_coprime: li $v0, 4 la $a0, no syscall end_main: # プログラムの終了 lw $ra, 0($sp) # 戻り先アドレスの復元 addi $sp, $sp, 4 # スタックに確保した領域の開放 jr $ra #-------------------------------------------------------------------------- # # comprime: 入力された2整数が互いに素であるかどうか判定する # # 入力 # $a0:a # $a1:b # # 出力 # $v0:1 (互いに素である) # :0 (互いに素ではない) # #-------------------------------------------------------------------------- coprime: ...

課題3:文字列の処理

入力れた文字列中の文字種を判定するプログラム"charkind.s"を作れ.

入力文字列を格納するための ASCII型の長めのバッファの定義と、バッファ中の文字位置を差し示すポインタが必要である。

空白は "s"、演算子(+, -, *, /)は"O"、数字は"D"と表示する(下記実行例を参照)。

文字種の判定にはASCII文字コードにおいて、NULLは0、改行は10、空白は32、数字は48(文字リテラルは'0')から57(文字リテラルは'9')であることを利用する。

(spim) load "charkind.s" (spim) run expression: 3 4 + DsDsO (spim)

charkindのプログラム例の冒頭を以下に示す。

#-------------------------------------------------------------------------- # # charkind: 入力された文字列中の文字種を判定する # # 文字種は次の3種類 # 空白: s # 数字(1桁):D # 演算子(+,-,*,/):O # # 情報領域演習第二C演習第3回課題3(2026年6月26日) # # #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- # データセグメント #-------------------------------------------------------------------------- .data prompt: .asciiz "expression: " # プロンプト isspc: .asciiz "s" # 空白 isopr: .asciiz "O" # 演算子 isdgt: .asciiz "D" # 数字 newln: .asciiz "\n" # 改行 buffer: .space 1024 # 入力文字列バッファ(1024バイト)

SPIMプログラム開発と動作確認について

★ MIPSシミュレータSPIMの使い方については SPIM を参照のこと。

★ SPIMをリセットするためには reinitialize コマンドを使ってください。このコマンドは、SPIMを起動したまま修正したプログラムを読み込む前に実行します。

★ 実行例は複数示す必要があります。その他、課題実行、レポート作成にあたっては レポート に従うこと。