Call、Function、Return【NandGame編】
目次
はじめに
いつもブログをご覧いただきありがとうございます。
コーストFIRE中のIPUSIRONです😀
関数
関数は、ソフトウェアにおいておそらくもっとも重要な抽象化の概念です。
関数はコードの集まりであり、いくつかの入力(引数と呼ばれる)を受け取り、処理に使用するローカルストレージを持ち、処理結果を返します。
関数はプログラムのどこからでも実行(呼び出し)できます。関数が呼び出されると、呼び出されたアドレスがスタックに格納されます。その後、関数の処理が終わると、呼び出されたアドレスに戻ります。
関数を呼び出す際には、引数とローカルストレージ(関数内で使うローカル変数用の領域)もスタックに格納されます。
関数は3つのブロック[1]ページ上では、セグメントやユニットと表現しています。から構成されます。
- Call・・・関数がプログラムのどこかから呼び出される。
- Function・・・関数の開始点
- Return・・・関数の終わり
これらの3つはお互い連携し合い、データの受け渡しは決められたルールに従います。
Callレベル
NandGameでは、Functionsステージに7つのレベルが用意されています。
まずは先に示した3つのブロックCall、Function、Returnは、それぞれマクロCALL、FUNCTION、RETURNに対応しています。順に、NandGameのアセンブリー言語でこれらのマクロを実装していきます。
Callレベルのゴールは、マクロCALLを実装することです。
マクロCALLは、関数を呼び出します。その際、スタックを準備し、指定された関数ラベル(当レベルでは"functionName")にジャンプし、状態を復元します。
呼び出し前に、0個以上の値がスタックに置かれます。
引数の数のプレースホルダーは"argumentCount"とします。
関数を呼び出す際に使う共有メモリースロット
関数の呼び出し規則では、呼び出し元と呼び出し先でデータを共有するために3つのスロット(データを保持する入れ物のようなもの)が必要です。NandGameではメモリー上にこの3つのスロットを設けます。
これまでスタックポインタ用にSPという定数(定数値は0)を定義していました。今回は加えて次の3つの定数を設定しましょう。
定数名 | 定数値 (メモリーアドレスに対応) | メモリーに格納されているデータの役目 |
---|---|---|
ARGS | 1 | 現在の関数の引数のアドレス。 |
LOCALS | 2 | 関数のローカルストレージのアドレス。 |
RETVAL | 6 | 関数の戻り値。 |
※実装のところで説明しますが、実際にはさらに1つTEMPという定数(定数値は3)も定義します。
Callの前処理
関数の呼び出された際に、前処理として次が実行されます。
①ARGSの現在値(ARGSのメモリーアドレス値の内容)をスタックにPUSHする。
②LOCALSの現在値をスタックにPUSHする。
③リターンアドレス(ジャンプ直後のアドレス)をPUSHする。
④「SP-argumentCount-3[2]スタックに3つの値をPUSHしたためです。」によって計算した新しいアドレス値を、ARGSにセットとする。
⑤functionNameにより与えられたアドレスにジャンプする。
Callの後処理
関数の処理が終了した後、後処理として次が実行されます。
①ARGSの現在値を一時的に保存する。
②LOCALS値をスタックから復元する。
③ARGS値をスタックから復元する。
④SP(スタックポインタ)にステップ①の値をセットする。
⑤RETVAL値をスタックにPUSHする。
Callレベルを解く
1:(グローバルな)定数を定義する
プログラム内でDEFINEを使って定義してもよいですが、ここでは右側の"Shared Constants"で定義しておきます。
2:コードを設計する
前処理、主処理、後処理の3つのブロックに分けて実装してきます。
3:コードを実装する
# Assembler code
#以下はShared Constantsで定義済み.
#DEFINE SP 0
#DEFINE ARGS 1
#DEFINE LOCALS 2
#DEFINE TEMP 3
#DEFINE RETVAL 6
#######################
# 前処理.
#######################
# ①
PUSH_STATIC ARGS
# ②
PUSH_STATIC LOCALS
# ③
PUSH_VALUE ret_addr
# ④
A = SP
D = *A
A = argumentCount
D = D - A
D = D - 1
D = D - 1
D = D - 1
A = ARGS
*A = D
#######################
# 関数の主処理.
#######################
GOTO functionName
#######################
# 後処理.
#######################
ret_addr:
# ①
A = ARGS
D = *A
A = TEMP
*A = D
# ②
POP_STATIC LOCALS
# ③
POP_STATIC ARGS
# ④
A = TEMP
D = *A
A = SP
*A = D
# ⑤
PUSH_STATIC RETVAL
Functionレベル
Functionレベルのゴールは、マクロFUNTIONを実装することです。
マクロFUNTIONは、関数を呼び出された直後(関数のコード群の実行直前)に、スタックを調整し、ローカルストレージを確保します。
ローカルストレージのサイズは、localsCount[3]“local"に複数形の’s’がついていることに注意してください。で指定されます。
Functionrレベルを解く
具体的な処理としては次のとおりです。
①スタック上におけるローカル変数用の(最初の)アドレスを、LOCALSに保存する。
②localsCount分だけスタックの領域を確保する。つまり、SP(スタックポインタ)が指すアドレスをlocalCount分だけ増やす。
functionName:
PUSH_STATIC SP
POP_STATIC LOCALS
A = localsCount
D = A
A = SP
*A = D + *A
Returnレベル
Functionレベルのゴールは、マクロRETURNを実装することです。
マクロRETURNは、関数を実行した直後(関数のコード群の終了直後)に、戻り値をメモリーアドレスRETVALに格納し、スタックを復元します。
Returnレベルを解く
具体的な処理としては次のとおりです。
①スタックの先頭値を取得し、RETVAL値とする。
②LOCALS値をSP値として設定する。
③スタックからリターンアドレスを取得し、そのアドレスにジャンプする。
POP_STATIC RETVAL
PUSH_STATIC LOCALS
POP_STATIC SP
POP_A
JMP