Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14208] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)において、lencapなどの組み込み関数(builtin functions)の利用が、正しく関数呼び出しとして認識されるように修正するものです。これにより、特定の状況下でこれらの組み込み関数が定数式として誤って評価される問題を解決し、コンパイル時のエラーを適切に検出できるようになります。具体的には、typecheck.c内のcallrecv関数に、組み込み関数に対応するオペレーションコード(OCAP, OLEN, OCOPY, ONEW, OAPPEND, ODELETE)を追加することで、これらの組み込み関数が呼び出しとして扱われるようにしています。

コミット

  • コミットハッシュ: 48af64b2956c9cd2618588d3d678ff099ced89a5
  • Author: Daniel Morsing daniel.morsing@gmail.com
  • Date: Mon Oct 22 19:14:30 2012 +0200
  • コミットメッセージ:
    cmd/gc: Mark use of builtin functions as calls.
    
    Fixes #4097.
    
    R=rsc
    CC=golang-dev, gri
    https://golang.org/cl/6749059
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/48af64b2956c9cd2618588d3d678ff099ced89a5

元コミット内容

cmd/gc: Mark use of builtin functions as calls.

Fixes #4097.

R=rsc
CC=golang-dev, gri
https://golang.org/cl/6749059

変更の背景

この変更は、Goコンパイラが特定の組み込み関数(len, capなど)の呼び出しを、コンパイル時に定数として評価しようとする誤った挙動を修正するために行われました。Go言語では、一部の式はコンパイル時に定数として評価される必要がありますが、lencapのような組み込み関数は、その引数が実行時に決定される変数である場合、定数として評価することはできません。

元のコンパイラでは、len(s[len(s)-1])のような式において、内側のlen(s)-1は定数として評価できる場合でも、外側のlenが配列の要素に適用されているため、全体としては実行時に評価されるべき関数呼び出しであるにもかかわらず、コンパイラがこれを定数として扱おうとしていました。その結果、「must be constant(定数でなければならない)」という誤ったエラーが発生していました。

この問題はGoのIssue #4097として報告されており、このコミットはその問題を解決することを目的としています。コンパイラが組み込み関数を正しく「呼び出し」として認識することで、定数評価のルールが適切に適用され、不適切なエラーが回避されるようになります。

前提知識の解説

Go言語の組み込み関数 (Built-in Functions)

Go言語には、言語仕様の一部として定義され、特別な扱いを受けるいくつかの組み込み関数があります。これらはimportすることなく直接使用でき、コンパイラによって特別に処理されます。このコミットに関連する主な組み込み関数は以下の通りです。

  • len(v): vの長さ(配列、スライス、文字列、マップ、チャネルの要素数)を返します。
  • cap(v): vの容量(スライス、チャネルの最大容量)を返します。
  • copy(dst, src): srcスライスからdstスライスへ要素をコピーします。
  • new(Type): Type型の新しい項目を割り当て、その項目へのポインタを返します。
  • append(slice, elems...): スライスに要素を追加し、新しいスライスを返します。
  • delete(m, key): マップmからkeyに対応する要素を削除します。

これらの関数は、通常の関数とは異なり、コンパイラがそのセマンティクスを直接理解し、最適化や型チェックを行います。

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラの一つであり、Goソースコードを機械語に変換する役割を担っています。コンパイルプロセスは、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成など、複数のフェーズに分かれています。

  • 型チェック (Type Checking): ソースコードがGo言語の型システム規則に準拠しているかを確認するフェーズです。変数や関数の型が正しく使用されているか、式の型が期待される型と一致するかなどを検証します。このフェーズで、定数式の評価可能性も判断されます。
  • 中間表現 (IR): コンパイラは、ソースコードを直接機械語に変換するのではなく、まず抽象構文木(AST)などの中間表現に変換します。この中間表現に対して、型チェックや最適化が行われます。

定数評価 (Constant Evaluation)

Go言語では、コンパイル時に値が確定する式を「定数式」と呼びます。定数式は、プログラムの実行前にその値が計算され、バイナリに埋め込まれます。これにより、実行時の計算コストを削減し、パフォーマンスを向上させることができます。

しかし、すべての式が定数式として評価できるわけではありません。例えば、変数の値に依存する式や、実行時にI/Oを行う関数呼び出しなどは定数式ではありません。コンパイラは、どの式が定数式であるかを厳密に判断し、そうでない場合は実行時に評価されるコードを生成します。

技術的詳細

Goコンパイラのcmd/gcは、ソースコードを解析し、抽象構文木(AST)を構築します。このASTのノードは、Go言語の様々な構文要素(変数、関数呼び出し、演算子など)を表します。各ノードには、その種類を示すオペレーションコード(Opcode)が割り当てられています。

src/cmd/gc/typecheck.cファイルは、コンパイラの型チェックフェーズの主要な部分を実装しています。このファイルには、ASTノードの型をチェックし、必要に応じて変換を行うための関数群が含まれています。

callrecv関数は、特定のASTノードが「呼び出し」として扱われるべきかどうかを判断する役割を担っています。ここでいう「呼び出し」とは、関数呼び出し、メソッド呼び出し、チャネルからの受信操作などを指します。コンパイラがこれらの操作を正しく「呼び出し」として認識することは、その後のコード生成や最適化、特に定数評価の文脈で非常に重要です。

以前のcallrecv関数では、OCALLINTER(インターフェースメソッド呼び出し)、OCALLFUNC(通常の関数呼び出し)、ORECV(チャネル受信)といったオペレーションコードは「呼び出し」として認識されていましたが、lencapなどの組み込み関数に対応するオペレーションコード(OCAP, OLEN, OCOPY, ONEW, OAPPEND, ODELETE)は含まれていませんでした。

このため、コンパイラはこれらの組み込み関数を、通常の関数呼び出しとは異なる方法で処理しようとし、結果として、実行時に評価されるべき式を定数式として誤って扱ってしまうことがありました。特に、lencapの引数が定数でない場合、その結果も定数にはなり得ないため、この誤った認識が「must be constant」エラーを引き起こしていました。

このコミットは、callrecv関数にこれらの組み込み関数のオペレーションコードを追加することで、コンパイラがこれらの組み込み関数も「呼び出し」として正しく認識し、適切な型チェックとコード生成を行うように修正しています。

コアとなるコードの変更箇所

このコミットでは、主に以下の2つのファイルが変更されています。

  1. src/cmd/gc/typecheck.c: callrecv関数に、組み込み関数に対応するオペレーションコードが追加されました。

    --- a/src/cmd/gc/typecheck.c
    +++ b/src/cmd/gc/typecheck.c
    @@ -245,6 +245,12 @@ callrecv(Node *n)
     	case OCALLINTER:
     	case OCALLFUNC:
     	case ORECV:
    +	case OCAP:
    +	case OLEN:
    +	case OCOPY:
    +	case ONEW:
    +	case OAPPEND:
    +	case ODELETE:
     		return 1;
     	}
    
  2. test/fixedbugs/issue4097.go: このコミットによって修正される問題(Issue #4097)を再現し、修正が正しく適用されたことを検証するための新しいテストケースが追加されました。

    --- /dev/null
    +++ b/test/fixedbugs/issue4097.go
    @@ -0,0 +1,11 @@
    +// errorcheck
    +
    +// Copyright 2012 The Go Authors.  All rights reserved.
    +// Use of this source code is governed by a BSD-style
    +// license that can be found in the LICENSE file.
    +
    +package foo
    +
    +var s [][10]int
    +const m = len(s[len(s)-1]) // ERROR "must be constant"
    +
    

コアとなるコードの解説

src/cmd/gc/typecheck.c の変更

callrecv関数は、Goコンパイラの型チェックフェーズにおいて、与えられたASTノードnが「呼び出し」操作を表すかどうかを判断します。この関数が1を返すと、そのノードは呼び出しとして扱われ、0を返すとそうではないと判断されます。

変更前は、OCALLINTER(インターフェースメソッド呼び出し)、OCALLFUNC(通常の関数呼び出し)、ORECV(チャネルからの受信)のみが呼び出しとして認識されていました。

このコミットでは、以下の組み込み関数に対応するオペレーションコードがcase文に追加されました。

  • OCAP: cap()関数
  • OLEN: len()関数
  • OCOPY: copy()関数
  • ONEW: new()関数
  • OAPPEND: append()関数
  • ODELETE: delete()関数

これらのオペレーションコードがcallrecv関数によって「呼び出し」として明示的にマークされることで、コンパイラはこれらの組み込み関数が定数式の一部として評価されるべきではないことを正しく認識するようになります。これにより、len(s[len(s)-1])のような式が、定数として評価できない場合に「must be constant」という適切なエラーを生成するようになります。これは、コンパイラが実行時に評価されるべき式を定数として扱おうとする誤った挙動を防ぐための重要な修正です。

test/fixedbugs/issue4097.go の追加

この新しいテストファイルは、// errorcheckディレクティブを含んでいます。これは、このファイルがコンパイル時に特定のエラーを生成することを期待するテストであることを示します。

テストコードの核心は以下の行です。

var s [][10]int
const m = len(s[len(s)-1]) // ERROR "must be constant"
  • var s [][10]int: s[10]int型のスライスを要素とするスライスとして宣言されています。これは実行時にその長さが変動する可能性がある変数です。
  • const m = len(s[len(s)-1]): ここで問題が発生します。constキーワードは、mがコンパイル時に評価される定数であることを要求します。
    • 内側のlen(s)-1は、sが変数であるため、その長さはコンパイル時には確定しません。したがって、s[len(s)-1]という式全体もコンパイル時には確定しません。
    • さらに、外側のlen()関数は、実行時に評価されるべきs[len(s)-1]というスライス(または配列)の長さを取得しようとしています。
    • len関数自体は組み込み関数ですが、その引数が定数でない場合、lenの結果も定数にはなり得ません。

このテストケースは、len関数が変数に適用されているため、その結果が定数になり得ないことを示しています。修正前のコンパイラでは、この状況で「must be constant」というエラーが適切に検出されませんでした。このコミットの修正により、lenが「呼び出し」として正しく認識されるようになり、コンパイラはmが定数として初期化できないことを検出し、期待される「must be constant」エラーを生成するようになります。これにより、コンパイラの定数評価ロジックの正確性が向上します。

関連リンク

参考にした情報源リンク