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

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

このコミットは、Go言語の初期のコンパイラ(6c、AMD64アーキテクチャ向けのCコンパイラ)におけるポインタの差分計算に関するバグ修正、またはその関連ロジックの調整を目的としています。具体的には、byte* - byte* の結果が int32 ではなく int64 であるべきという問題に対処しています。

コミット

commit 5e5476c2feebab6582dba4db1e47d8d02196bb14
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 5 19:09:04 2009 -0800

    6c: byte* - byte* should be int64, not int32.
    
    R=ken
    OCL=24507
    CL=24507

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

https://github.com/golang/go/commit/5e5476c2feebab6582dba4db1e47d8d02196bb14

元コミット内容

6c: byte* - byte* should be int64, not int32.

R=ken
OCL=24507
CL=24507

変更の背景

このコミットは、Go言語の初期開発段階、特にPlan 9 Cコンパイラ(6c)をGoのツールチェインに適合させていた時期に行われたものです。当時のGoコンパイラは、Plan 9のCコンパイラをベースにしており、6cはそのAMD64アーキテクチャ向けのコンパイラを指します。

コミットメッセージ「byte* - byte* should be int64, not int32.」が示すように、この変更の背景には、ポインタ同士の減算結果の型に関する問題がありました。C言語やGo言語において、ポインタ同士の減算は、それらのポインタが指す要素間の距離(要素数)を計算します。この距離は、通常、符号付き整数型で表現されます。

64ビットシステムでは、メモリ空間が非常に広大であるため、ポインタ間の距離も int32 の範囲(約±20億)を超える可能性があります。したがって、byte*(バイトポインタ)のようなポインタの差分は、int32 ではなく int64 で表現されるべきです。もし int32 で計算されると、大きなアドレス差がある場合にオーバーフローが発生し、不正な結果やクラッシュにつながる可能性がありました。

このコミットは、このような潜在的なバグを防ぎ、64ビットアーキテクチャ上でのポインタ演算の正確性を保証するために行われました。

前提知識の解説

  • Plan 9 Cコンパイラ (6c): Go言語の初期のコンパイラは、Plan 9オペレーティングシステムで使われていたCコンパイラ群(8c for ARM, 6c for AMD64, 5c for PowerPCなど)をベースにしていました。これらのコンパイラは、非常にシンプルで移植性が高く、Go言語のクロスコンパイル環境の基盤となりました。6c は特にAMD64(x86-64)アーキテクチャ向けのコンパイラを指します。
  • ポインタ演算: C言語やGo言語において、ポインタはメモリ上のアドレスを指す変数です。ポインタ同士の減算は、両ポインタが指すアドレス間の距離を、指している型のサイズ単位で計算します。例えば、int* p1, p2; の場合、p2 - p1(アドレス(p2) - アドレス(p1)) / sizeof(int) となります。結果は整数型になります。
  • int32int64:
    • int32: 32ビット符号付き整数型。約 -2,147,483,648 から 2,147,483,647 までの値を表現できます。
    • int64: 64ビット符号付き整数型。約 -9,223,372,036,854,775,808 から 9,223,372,036,854,775,807 までの値を表現できます。 64ビットシステムでは、ポインタのアドレス空間が64ビットであるため、ポインタ間の距離も64ビットで表現できる必要があります。
  • ewidth: これはPlan 9コンパイラ内部で使われる概念で、"effective width" または "element width" の略であると考えられます。特定の型がメモリ上で占めるビット幅や、その型が表現できる値の範囲を示すために使われます。
  • TINDTLONG: これらもPlan 9コンパイラ内部の型定義に関連する定数です。
    • TIND: "Type Index" の略で、ポインタ型(またはインデックス型)を表す内部定数である可能性が高いです。
    • TLONG: "Type Long" の略で、long 型(またはそれに相当する整数型)を表す内部定数であると考えられます。

技術的詳細

このコミットは、src/cmd/cc/sub.c ファイル内の arith 関数を変更しています。arith 関数は、コンパイラのセマンティック分析フェーズにおいて、算術演算子の型チェックと結果型の決定を行う役割を担っています。

変更された行は以下の通りです。

--- a/src/cmd/cc/sub.c
+++ b/src/cmd/cc/sub.c
@@ -722,7 +722,7 @@ arith(Node *n, int f)
 		if(w < 1 || n->left->type->link == T || n->left->type->link->width < 1)
 			goto bad;
 		n->type = types[ewidth[TIND] <= ewidth[TLONG]? TLONG: TVLONG];
-		if(1 && ewidth[TIND] > ewidth[TLONG]){
+		if(0 && ewidth[TIND] > ewidth[TLONG]){
 			n1 = new1(OXXX, Z, Z);
 			*n1 = *n;
 			n->op = OCAST;

このコードスニペットは、ポインタ減算(n->opOSUB で、かつオペランドがポインタ型の場合)の処理の一部です。

元のコードでは、if(1 && ewidth[TIND] > ewidth[TLONG]) という条件がありました。

  • 1 && ... は常に真となるため、実質的に if(ewidth[TIND] > ewidth[TLONG]) と同じです。
  • この条件は、「ポインタ型(TIND)の有効幅が long 型(TLONG)の有効幅よりも大きい場合」をチェックしています。これは、ポインタが long 型よりも広いビット幅を持つアーキテクチャ(例えば、64ビットシステムで long が32ビットの場合)を想定していると考えられます。

この if ブロックの内部では、ポインタ減算の結果を OCAST(型キャスト)する処理が行われていました。これは、ポインタ減算の結果がデフォルトで int32 になるような状況で、明示的に int64 にキャストするためのロジックだった可能性があります。

しかし、コミットによってこの条件が if(0 && ewidth[TIND] > ewidth[TLONG]) に変更されました。

  • 0 && ... は常に偽となるため、この if ブロック内のコードは実行されなくなります

この変更は、以下のいずれかの理由によるものと考えられます。

  1. ポインタ減算の結果型が既に int64 に修正された: コンパイラの他の部分で、ポインタ減算の結果がデフォルトで int64 になるように修正されたため、この明示的なキャストロジックが不要になった。
  2. 条件が常に偽であるべき: ewidth[TIND](ポインタの幅)が ewidth[TLONG](longの幅)より大きいという前提が、特定のアーキテクチャやGoの型システムにおいて誤りであるか、またはこの条件が満たされるべきではないと判断された。例えば、64ビットシステムでは long も64ビットになるため、ewidth[TIND]ewidth[TLONG] が等しくなる場合がある。
  3. デバッグ/テストコードの無効化: この if ブロックが、特定の型幅の挙動をテストするためのデバッグコードや一時的な回避策であり、本番環境では無効化されるべきだった。

コミットメッセージ「byte* - byte* should be int64, not int32.」と合わせて考えると、最も可能性が高いのは1番目のシナリオです。つまり、ポインタ減算の結果が int64 になるようにコンパイラの他の部分で根本的な修正が行われ、この if ブロック内の明示的なキャストロジックが冗長になったため、無効化されたと推測されます。これにより、int32 への切り捨てを防ぎ、常に int64 の結果を保証するようになります。

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

変更は src/cmd/cc/sub.c ファイルの722行目付近にあります。

// src/cmd/cc/sub.c
// ...
722 		if(w < 1 || n->left->type->link == T || n->left->type->link->width < 1)
723 			goto bad;
724 		n->type = types[ewidth[TIND] <= ewidth[TLONG]? TLONG: TVLONG];
725-		if(1 && ewidth[TIND] > ewidth[TLONG]){
726+		if(0 && ewidth[TIND] > ewidth[TLONG]){
727 			n1 = new1(OXXX, Z, Z);
728 			*n1 = *n;
729 			n->op = OCAST;
// ...

コアとなるコードの解説

変更された行は、ポインタ減算の結果の型を決定し、必要に応じてキャストを行うロジックの一部です。

  • n->type = types[ewidth[TIND] <= ewidth[TLONG]? TLONG: TVLONG];
    • この行は、ポインタ減算の結果の型を決定しています。
    • ewidth[TIND] はポインタ型の幅、ewidth[TLONG]long 型の幅です。
    • もしポインタの幅が long の幅以下であれば TLONGlong 型)を、そうでなければ TVLONGlong long 型、またはそれに相当するより広い整数型)を結果の型として選択しています。これは、ポインタ減算の結果が少なくともポインタの幅を表現できる整数型になるようにするためのものです。
  • if(1 && ewidth[TIND] > ewidth[TLONG]) から if(0 && ewidth[TIND] > ewidth[TLONG]) への変更
    • この変更により、if 文の条件が常に偽となり、このブロック内のコードは実行されなくなりました。
    • このブロック内では、n1 = new1(OXXX, Z, Z); *n1 = *n; n->op = OCAST; といった処理が行われていました。これは、現在のノード n をコピーし、その操作を OCAST(型キャスト)に変更することで、ポインタ減算の結果を明示的にキャストしようとしていたことを示唆しています。
    • このブロックが無効化されたということは、この明示的なキャストが不要になった、あるいは別の方法でポインタ減算の結果型が適切に処理されるようになったことを意味します。

結論として、このコミットは、Go言語の初期コンパイラにおいて、ポインタ減算の結果が64ビット整数(int64)として正しく扱われるようにするための、より広範な修正の一部であると考えられます。特定の条件分岐を無効化することで、冗長なキャスト処理を削除し、コンパイラのロジックを簡素化しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式GitHubリポジトリ
  • Plan 9 Cコンパイラに関する一般的な情報(Go言語の歴史的背景として)
  • C言語におけるポインタ演算と型に関する一般的な知識
  • 64ビットシステムにおけるデータ型とポインタの挙動に関する一般的な知識
  • コミットメッセージとコード差分からの推測