当サイトの一部ページには、アフィリエイト・アドセンス・アソシエイト・プロモーション広告を掲載しています。

Amazonのアソシエイトとして、Security Akademeiaは適格販売により収入を得ています。

広告配信等の詳細については、プライバシーポリシーページに掲載しています。

消費者庁が、2023年10月1日から施行する景品表示法の規制対象(通称:ステマ規制)にならないよう、配慮して記事を作成しています。もし問題のある表現がありましたら、問い合わせページよりご連絡ください。

参考:令和5年10月1日からステルスマーケティングは景品表示法違反となります。 | 消費者庁

Call、Function、Return【NandGame編】

関数

関数は、ソフトウェアにおいておそらくもっとも重要な抽象化の概念です。

関数はコードの集まりであり、いくつかの入力(引数と呼ばれる)を受け取り、処理に使用するローカルストレージを持ち、処理結果を返します。

関数はプログラムのどこからでも実行(呼び出し)できます。関数が呼び出されると、呼び出されたアドレスがスタックに格納されます。その後、関数の処理が終わると、呼び出されたアドレスに戻ります。

関数を呼び出す際には、引数とローカルストレージ(関数内で使うローカル変数用の領域)もスタックに格納されます。

関数は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つの定数を設定しましょう。

定数名定数値
(メモリーアドレスに対応)
メモリーに格納されているデータの役目
ARGS1現在の関数の引数のアドレス。
LOCALS2関数のローカルストレージのアドレス。
RETVAL6関数の戻り値。

※実装のところで説明しますが、実際にはさらに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

References

References
1 ページ上では、セグメントやユニットと表現しています。
2 スタックに3つの値をPUSHしたためです。
3 “local"に複数形の’s’がついていることに注意してください。