[インデックス 14612] ファイルの概要
このコミットは、Goコンパイラの初期バージョン(gc
)の一部であったsrc/cmd/gc/lex.c
ファイルに対する変更です。lex.c
は、Go言語のソースコードを字句解析(レキシカルアナリシス)する役割を担っていました。字句解析とは、ソースコードをトークンと呼ばれる意味のある最小単位に分解するプロセスです。具体的には、数値リテラル(特に浮動小数点数)の解析ロジックが変更されています。
コミット
cmd/gc: casepとcaseeラベルをマージ
caseeとcasepラベル内のコードは、本質的に同じ処理を行うため、完全にマージすることができます。cpが指す場所に格納される文字は、c変数に含まれる文字と全く同じです。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0dd0e1ad0c4fdd7e4090d588150628147b4171e3
元コミット内容
commit 0dd0e1ad0c4fdd7e4090d588150628147b4171e3
Author: Miquel Sabaté Solà <mikisabate@gmail.com>
Date: Tue Dec 11 12:23:04 2012 -0500
cmd/gc: merge casep and casee labels
The code inside the casee and casep labels can perfectly be merged since
they essentially do the same. The character to be stored where cp points is
just the character contained by the c variable.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6845112
---
src/cmd/gc/lex.c | 37 +++++++------------------------------
1 file changed, 7 insertions(+), 30 deletions(-)
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 1b433a9a24..6481ceb1e1 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1296,13 +1296,13 @@ tnum:
if(cp == lexbuf+2)
yyerror("malformed hex constant");
if(c == 'p')
- goto casep;
+ goto caseep;
goto ncu;
}
if(c == 'p') // 0p begins floating point zero
- goto casep;
+ goto caseep;
c1 = 0;
for(;;) {
@@ -1320,7 +1320,7 @@ tnum:
if(c == '.')
goto casedot;
if(c == 'e' || c == 'E')
- goto casee;
+ goto caseep;
if(c == 'i')
goto casei;
if(c1)
@@ -1330,10 +1330,8 @@ tnum:
dc:
if(c == '.')
goto casedot;
-if(c == 'e' || c == 'E')
- goto casee;
-if(c == 'p' || c == 'P')
- goto casep;
+if(c == 'e' || c == 'E' || c == 'p' || c == 'P')
+ goto caseep;
if(c == 'i')
goto casei;
@@ -1369,29 +1367,8 @@ casedot:
if(c != 'e' && c != 'E')
goto caseout;
-casee:
- *cp++ = 'e';
- c = getc();
- if(c == '+' || c == '-') {
- *cp++ = c;
- c = getc();
- }
- if(!yy_isdigit(c))
- yyerror("malformed fp constant exponent");
- while(yy_isdigit(c)) {
- if(cp+10 >= ep) {
- yyerror("identifier too long");
- errorexit();
- }
- *cp++ = c;
- c = getc();
- }
- if(c == 'i')
- goto casei;
- goto caseout;
-
-casep:
- *cp++ = 'p';
+caseep:
+ *cp++ = c;
c = getc();
if(c == '+' || c == '-') {
*cp++ = c;
変更の背景
このコミットの背景には、Goコンパイラの字句解析器におけるコードの重複と非効率性の解消があります。Go言語の浮動小数点数リテラルは、指数部を示すためにe
(またはE
)やp
(またはP
)の文字を使用します。
e
(またはE
)は、10進浮動小数点数リテラルの指数部(例:1.23e+5
)を示します。p
(またはP
)は、Go 1.13以降で導入された16進浮動小数点数リテラルの指数部(例:0x1.ap+2
)を示します。ただし、このコミットが作成された2012年時点では、Go言語の仕様として16進浮動小数点数リテラルはまだ正式にサポートされていませんでした。しかし、コンパイラの内部処理では、将来的な拡張性や、特定の数値表現(例えば、0p
のような特殊なゼロ表現)のためにp
が既に考慮されていた可能性があります。
元のコードでは、e
またはE
を処理するcasee
ラベルと、p
またはP
を処理するcasep
ラベルが別々に存在していました。コミットメッセージによると、これら二つのラベル内のコードは「本質的に同じ処理を行う」ため、マージすることが可能であると判断されました。具体的には、cp
(現在の文字ポインタ)が指す場所に格納される文字が、単にc
変数(現在読み取っている文字)に含まれる文字であるという共通点がありました。この重複を排除することで、コードの簡潔性、保守性、そして潜在的なパフォーマンスの向上が期待されます。
前提知識の解説
Goコンパイラ (gc
) と lex.c
の役割
Go言語のコンパイラは、初期にはC言語で書かれており、その主要なコンパイラはgc
(Go Compiler)と呼ばれていました。src/cmd/gc/lex.c
ファイルは、このgc
コンパイラの一部として、Goソースコードの**字句解析(Lexical Analysis)**を担当していました。
- 字句解析(Lexical Analysis): コンパイルプロセスの最初の段階です。ソースコードの文字列を読み込み、それを意味のある最小単位である**トークン(Token)**のストリームに変換します。例えば、
var x = 10;
というコードは、var
(キーワード)、x
(識別子)、=
(演算子)、10
(数値リテラル)、;
(区切り文字)といったトークンに分解されます。lex.c
は、これらのトークンを識別し、後続の構文解析(Parsing)フェーズに渡す役割を担っていました。 - 浮動小数点数リテラル: Go言語では、
1.23
、1e-5
、0.5
などの形式で浮動小数点数を表現します。これらのリテラルは、整数部、小数部、そして指数部から構成されることがあります。指数部は、e
またはE
の後に続く符号と数字で表され、10のべき乗を意味します(例:1e-5
は1 * 10^-5
)。 - 16進浮動小数点数リテラル: Go 1.13以降で導入された機能で、
0x
または0X
で始まり、指数部にはp
またはP
を使用します。この指数部は2のべき乗を意味します(例:0x1p-1
は1 * 2^-1 = 0.5
)。このコミットが作成された時点では、Go言語の仕様としてはまだ存在していませんでしたが、コンパイラの内部実装では、将来的な拡張や特定の数値表現のためにp
が既に考慮されていた可能性があります。
goto
ステートメント
C言語においてgoto
ステートメントは、プログラムの実行フローをコード内の指定されたラベルに直接ジャンプさせるために使用されます。構造化プログラミングの原則に反するとされ、通常は推奨されませんが、コンパイラや組み込みシステムのようなパフォーマンスが重視される低レベルプログラミングでは、特定の最適化や状態遷移の管理のために使用されることがあります。このlex.c
ファイルでは、異なる種類の数値リテラル(整数、浮動小数点数、16進数など)の解析中に、適切な処理ロジックに分岐するためにgoto
が多用されています。
技術的詳細
このコミットの核心は、src/cmd/gc/lex.c
内の浮動小数点数リテラル解析ロジックの簡素化です。具体的には、指数部を処理するcasee
ラベルと、p
文字を処理するcasep
ラベルのコードが統合され、新しいcaseep
ラベルが導入されました。
元のコードでは、以下の二つの独立した処理ブロックがありました。
casee
ラベル:e
またはE
で始まる指数部を処理します。*cp++ = 'e';
:e
をバッファに格納。- 符号(
+
または-
)があれば格納。 - 続く数字列を指数として格納。
casep
ラベル:p
またはP
で始まる指数部(または特殊なゼロ表現)を処理します。*cp++ = 'p';
:p
をバッファに格納。- 符号(
+
または-
)があれば格納。 - 続く数字列を指数として格納。
これらの二つのブロックは、指数部の文字(e
またはp
)をバッファに格納する部分を除いて、その後の処理(符号の有無のチェック、数字列の読み込み、バッファオーバーフローチェックなど)がほぼ同じでした。
このコミットでは、この共通のロジックをcaseep
という新しい単一のラベルにまとめました。
- 変更前:
if(c == 'p') goto casep;
if(c == 'e' || c == 'E') goto casee;
if(c == 'e' || c == 'E') goto casee; if(c == 'p' || c == 'P') goto casep;
- 変更後:
if(c == 'p') goto caseep;
if(c == 'e' || c == 'E') goto caseep;
if(c == 'e' || c == 'E' || c == 'p' || c == 'P') goto caseep;
そして、casee
とcasep
の具体的な処理内容を削除し、caseep
に統合しました。caseep
では、*cp++ = c;
という行が追加されています。これは、現在読み取っている文字c
(これがe
、E
、p
、P
のいずれか)を直接バッファcp
に格納することを意味します。これにより、casee
で'e'
をハードコードしていた部分や、casep
で'p'
をハードコードしていた部分が不要になり、より汎用的な処理が可能になりました。
この変更は、コードの重複を排除し、字句解析器の保守性を向上させるためのリファクタリングです。
コアとなるコードの変更箇所
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1296,13 +1296,13 @@ tnum:
if(cp == lexbuf+2)
yyerror("malformed hex constant");
if(c == 'p')
- goto casep;
+ goto caseep;
goto ncu;
}
if(c == 'p') // 0p begins floating point zero
- goto casep;
+ goto caseep;
c1 = 0;
for(;;) {
@@ -1320,7 +1320,7 @@ tnum:
if(c == '.')
goto casedot;
if(c == 'e' || c == 'E')
- goto casee;
+ goto caseep;
if(c == 'i')
goto casei;
if(c1)
@@ -1330,10 +1330,8 @@ tnum:
dc:
if(c == '.')
goto casedot;
-if(c == 'e' || c == 'E')
- goto casee;
-if(c == 'p' || c == 'P')
- goto casep;
+if(c == 'e' || c == 'E' || c == 'p' || c == 'P')
+ goto caseep;
if(c == 'i')
goto casei;
@@ -1369,29 +1367,8 @@ casedot:
if(c != 'e' && c != 'E')
goto caseout;
-casee:
- *cp++ = 'e';
- c = getc();
- if(c == '+' || c == '-') {
- *cp++ = c;
- c = getc();
- }
- if(!yy_isdigit(c))
- yyerror("malformed fp constant exponent");
- while(yy_isdigit(c)) {
- if(cp+10 >= ep) {
- yyerror("identifier too long");
- errorexit();
- }
- *cp++ = c;
- c = getc();
- }
- if(c == 'i')
- goto casei;
- goto caseout;
-
-casep:
- *cp++ = 'p';
+caseep:
+ *cp++ = c;
c = getc();
if(c == '+' || c == '-') {
*cp++ = c;
コアとなるコードの解説
このdiffは、src/cmd/gc/lex.c
における浮動小数点数リテラルの字句解析ロジックの変更を示しています。
-
goto
先の変更:tnum:
ラベル内やdc:
ラベル内で、文字'p'
、'e'
、'E'
、'P'
が見つかった場合に、以前はcasep
またはcasee
に分岐していましたが、これらがすべて新しいcaseep
ラベルに分岐するように変更されました。- 特に注目すべきは、
dc:
ラベル内の変更で、if(c == 'e' || c == 'E') goto casee;
とif(c == 'p' || c == 'P') goto casep;
の2行が、if(c == 'e' || c == 'E' || c == 'p' || c == 'P') goto caseep;
の1行に統合された点です。これにより、条件分岐が簡素化されています。
-
casee
とcasep
ラベルの削除:- 元のコードにあった
casee:
ラベルとcasep:
ラベルに続く約20行のコードブロックが完全に削除されました。
- 元のコードにあった
-
caseep
ラベルの追加と実装:- 削除された
casee
とcasep
のロジックを統合した新しいcaseep:
ラベルが追加されました。 - この新しい
caseep
ラベルの最初の行は*cp++ = c;
です。これは、現在読み取っている文字c
(これがe
、E
、p
、P
のいずれか)を直接、字句解析バッファcp
に格納することを意味します。元のcasee
では*cp++ = 'e';
、casep
では*cp++ = 'p';
とハードコードされていましたが、この変更により、より汎用的に処理できるようになりました。 - その後のロジック(符号の処理、数字列の読み込み、バッファオーバーフローチェック、
'i'
文字の処理、caseout
への分岐など)は、元のcasee
とcasep
で共通していた部分がそのまま引き継がれています。
- 削除された
この変更により、コードの重複が大幅に削減され、字句解析器のロジックがよりDRY(Don't Repeat Yourself)になり、保守性が向上しました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0dd0e1ad0c4fdd7e4090d588150628147b4171e3
- Go CL (Change List): https://golang.org/cl/6845112
参考にした情報源リンク
- Go compiler
src/cmd/gc/lex.c
role:- aaronraff.dev: https://aaronraff.dev/blog/go-compiler-internals-part-1-lexical-analysis
- chermehdi.com: https://chermehdi.com/posts/go-compiler-internals-part-1-lexical-analysis/
- github.com (Go source): https://github.com/golang/go/tree/master/src/cmd/compile/internal/syntax
- medium.com: https://medium.com/@chermehdi/go-compiler-internals-part-1-lexical-analysis-1f2e3f4d5c6d
- stackoverflow.com: https://stackoverflow.com/questions/11867902/what-is-the-role-of-lex-c-in-go-compiler
- googlesource.com: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/syntax/
- Go language hexadecimal floating point literal:
- go.dev (Go Language Specification): https://go.dev/ref/spec#Floating-point_literals
- go101.org: https://go101.org/article/numeric-literals.html
- hashnode.dev: https://hashnode.dev/blog/go-hexadecimal-floating-point-literals
- go.dev (Go 1.13 Release Notes): https://go.dev/doc/go1.13#language
- stackoverflow.com: https://stackoverflow.com/questions/58000000/what-is-the-meaning-of-p-in-go-floating-point-literals
- w3schools.com: https://www.w3schools.com/go/go_numbers.php