[インデックス 132] ファイルの概要
このコミットで変更された src/cmd/6g/cgen.c
ファイルは、Goコンパイラのバックエンドの一部であり、特にAMD64アーキテクチャ(6g
は64-bit Goコンパイラを指す)向けのコード生成を担当しています。cgen.c
は、Go言語の抽象構文木(AST)を中間表現に変換し、最終的にターゲットマシン(この場合はAMD64)の機械語命令を生成する過程で重要な役割を果たします。Go言語の組み込み関数や演算子(len
など)の処理もこのファイルで行われます。
コミット
このコミットは、Go言語の組み込み関数である len
が文字列に対して使用された際のバグを修正するものです。具体的には、文字列の長さを計算し、その結果を正しくレジスタに格納するためのコード生成ロジックが修正されました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b6eca3534c9d2901ac1001d98ac6af0ee3547a6d
元コミット内容
commit b6eca3534c9d2901ac1001d98ac6af0ee3547a6d
Author: Ken Thompson <ken@golang.org>
Date: Sun Jun 8 17:46:28 2008 -0700
bug in len
SVN=121618
変更の背景
このコミットは、Go言語の初期開発段階におけるバグ修正の一環として行われました。len
関数が文字列の長さを取得する際に、コンパイラが生成するコードに誤りがあり、正しい結果が得られない、または実行時エラーが発生する可能性がありました。特に、文字列の値を評価する際に、その結果が最終的なlen
関数の戻り値(整数)を格納するレジスタに直接書き込まれてしまい、文字列の長さ情報が失われる、あるいは不正な値が格納されるという問題があったと考えられます。この修正は、文字列の評価結果を一時的なレジスタに格納し、そこから文字列の長さを抽出して最終的な結果レジスタに移動するという、より堅牢なコード生成パスを確立するために必要でした。
前提知識の解説
- Go言語の
len
関数:len
はGo言語の組み込み関数で、文字列、スライス、配列、マップ、チャネルの要素数を返します。文字列の場合、そのバイト数を返します。 - Goコンパイラ
6g
:6g
は、Go言語の初期のコンパイラツールチェーンの一部で、AMD64アーキテクチャ(64-bit Intel/AMDプロセッサ)をターゲットとするコンパイラを指します。Goコンパイラは、ソースコードを解析し、抽象構文木(AST)を構築した後、中間表現を経て最終的に機械語コードを生成します。 cgen.c
: このファイルは、Goコンパイラのコード生成フェーズにおけるC言語ソースコードです。ASTノードをトラバースし、対応するアセンブリ命令を生成するロジックが含まれています。Node
構造体: Goコンパイラ内部で、プログラムの抽象構文木(AST)の各要素(変数、式、関数呼び出しなど)を表すデータ構造です。regalloc
: "register allocation"(レジスタ割り当て)の略で、コンパイラがプログラムの実行中に使用する値をCPUのレジスタに割り当てるプロセスです。効率的なコード生成には、レジスタの適切な利用が不可欠です。gmove
: "Go move"の略で、コンパイラが生成するアセンブリコードにおいて、ある場所(レジスタやメモリ)から別の場所へ値を移動させる操作を抽象化したものです。OLEN
: Goコンパイラの内部表現で、len
演算子を表すオペレーションコードです。isptrto(nl->type, TSTRING)
:nl
というノードの型が文字列へのポインタであるかどうかをチェックする関数です。これは、len
関数が文字列に対して適用されていることを確認するために使用されます。res
とn1
:res
(Node *res
):cgen
関数の第二引数で、現在のASTノードの評価結果を格納すべき最終的なレジスタまたはメモリ位置を表すノードです。n1
(Node n1
またはNode *n1
):regalloc
によって新しく割り当てられた一時的なレジスタまたはメモリ位置を表すノードです。中間結果を一時的に保持するために使用されます。
技術的詳細
このバグは、len
関数が文字列に適用される際のコード生成ロジックの不備に起因していました。Go言語において、文字列は通常、データへのポインタと長さのペアとして内部的に表現されます。len
関数は、このペアから長さを抽出して整数値として返す必要があります。
元のコード cgen(nl, res);
では、文字列のオペランド nl
の評価結果(つまり、文字列のポインタと長さのペア)を、len
関数の最終的な結果(整数値の長さ)を格納すべき res
レジスタに直接書き込もうとしていました。これは、型が異なる値を不適切な場所に格納しようとする試みであり、結果としてres
レジスタには文字列の長さではなく、文字列のポインタやその他のデータが格納されてしまう可能性がありました。これにより、len
関数の戻り値が不正になるか、後続の処理で誤った値が使用されることでプログラムがクラッシュするなどの問題が発生していました。
修正後のコード cgen(nl, &n1);
では、まず文字列のオペランド nl
を評価し、その結果(文字列のポインタと長さのペア)を一時的なレジスタ n1
に格納します。その後、n1.op = OINDREG; n1.type = types[TINT32]; gmove(&n1, res);
という行が続きます。これは、n1
に格納された文字列の表現から、その長さの部分を抽出し(OINDREG
とTINT32
の組み合わせがその抽出操作を示唆)、最終的な結果レジスタ res
に移動させることを意味します。この二段階のアプローチにより、文字列の評価と長さの抽出が分離され、len
関数が常に正しい整数値を返すことが保証されます。
コアとなるコードの変更箇所
--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -144,7 +144,7 @@ cgen(Node *n, Node *res)
case OLEN:
if(isptrto(nl->type, TSTRING)) {
regalloc(&n1, types[tptr], res);
- cgen(nl, res);
+ cgen(nl, &n1);
n1.op = OINDREG;
n1.type = types[TINT32];
gmove(&n1, res);
コアとなるコードの解説
変更は src/cmd/6g/cgen.c
ファイルの cgen
関数内の OLEN
(len
演算子)を処理する部分にあります。
-
変更前:
cgen(nl, res);
この行は、
len
関数のオペランドである文字列nl
のコードを生成し、その結果を直接res
(len
関数の最終結果である整数値を格納する場所)に格納しようとしていました。しかし、nl
の評価結果は文字列のデータ構造(ポインタと長さ)であり、res
が期待する整数値とは異なるため、問題が発生していました。 -
変更後:
cgen(nl, &n1);
この行では、
nl
(文字列オペランド)のコードを生成し、その結果を一時的に割り当てられたレジスタn1
に格納するように変更されました。n1
はregalloc(&n1, types[tptr], res);
によって、文字列のポインタを保持できる型(tptr
)として割り当てられています。これにより、文字列のデータ構造が一時的にn1
に保持されます。この変更の後には、以下の行が続きます。
n1.op = OINDREG; n1.type = types[TINT32]; gmove(&n1, res);
これらの行は、
n1
に格納された文字列のデータ構造から、その長さの部分を抽出(OINDREG
は間接参照を示唆し、文字列構造体内の長さにアクセスする操作に対応すると考えられる)し、その結果をTINT32
(32-bit整数型)としてres
に移動させる役割を担っています。この修正により、len
関数は文字列の長さを正しく計算し、適切なレジスタに格納できるようになりました。
関連リンク
- Go言語公式ウェブサイト: https://golang.org/
- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語の
len
関数に関するドキュメント: https://pkg.go.dev/builtin#len
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/6g/cgen.c
のコミット履歴) - Go言語のコンパイラ設計に関する一般的な知識
- Go言語の文字列の内部表現に関する情報