[インデックス 14935] ファイルの概要
このコミットは、Go言語のコンパイラツールチェーンの一部である cmd/6c
(64-bit x86アーキテクチャ向けCコンパイラ) と cmd/8c
(32-bit x86アーキテクチャ向けCコンパイラ) におけるアセンブリリスト出力の書式設定に関するバグ修正とコードクリーンアップを目的としています。具体的には、Prog
(プログラム命令) の出力時に FmtLong
フラグが誤って適用され、アセンブリリストに不要な "!!" (感嘆符) が多数表示される問題を解決します。また、関連する list.c
ファイル間のコードの類似性を高め、差分を容易に識別できるように整理しています。
コミット
commit 60826e0be6e62ae76f5771b22894bd3dc8bade10
Author: Anthony Martin <ality@pbrane.org>
Date: Fri Jan 18 19:00:38 2013 -0800
cmd/6c, cmd/8c: fix print format for Prog
The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.
I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/60826e0be6e62ae76f5771b22894bd3dc8bade10
元コミット内容
cmd/6c, cmd/8c: fix print format for Prog
The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.
I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045
変更の背景
この変更の主な背景は、Go言語のコンパイラが生成するアセンブリリストの可読性の問題にありました。具体的には、cmd/6c
および cmd/8c
コンパイラがアセンブリコードを整形して出力する際に、FmtLong
という書式フラグが不適切に適用されることで、本来表示されるべきではない "!!" という文字列が多数混入していました。
この "!!" は、通常、コンパイラがアドレスやオフセットの表現に問題がある、あるいは予期しない値に遭遇した場合にデバッグ目的で出力されるマーカーです。しかし、このバグでは、ATEXT
(関数コードセクション) の Prog
(命令) を %D
(オペランドの書式指定子) で出力する際にのみ FmtLong
フラグを使用すべきであるにもかかわらず、ADATA
(データセクション) 以外のすべての Prog
に対して誤ってこのフラグが適用されていました。結果として、正しくフォーマットされるべきアセンブリ命令のオペランドにも "!!" が付加され、アセンブリリストが非常に読みにくい状態になっていました。
このコミットは、この書式設定の誤りを修正し、アセンブリリストから不要な "!!" を排除することで、開発者が生成されたアセンブリコードをより容易にデバッグ・解析できるようにすることを目的としています。また、src/cmd/6c/list.c
と src/cmd/8c/list.c
のコード構造を統一することで、将来的なメンテナンスや両ファイル間の差分比較を容易にするという副次的な目的も持っています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のコンパイラツールチェーンとアセンブリに関する基本的な知識が必要です。
-
Go言語のコンパイラツールチェーン: Go言語は、
go build
コマンドを通じてソースコードをコンパイルし、実行可能なバイナリを生成します。このプロセスには、様々なツールが関与しており、cmd/6c
やcmd/8c
はその一部です。cmd/6c
: Go言語のソースコードをx86-64 (AMD64) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。Goのコンパイラは、歴史的にC言語で書かれた部分が多く、その名残としてc
が付いています。cmd/8c
: Go言語のソースコードをx86 (IA-32) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。 これらのコンパイラは、Goのソースコードを中間表現に変換し、最終的にターゲットアーキテクチャのアセンブリコードを生成します。
-
アセンブリリスト (Assembly Listing): コンパイラが生成したアセンブリコードを、元のソースコードの行番号やコメントなどと共に整形して表示したものです。デバッグやパフォーマンスチューニングの際に、Goのコードがどのように機械語に変換されるかを理解するために使用されます。
-
Prog
(プログラム命令): Goコンパイラの内部表現において、単一のアセンブリ命令を表す構造体です。各Prog
は、命令の種類 (例:MOV
,ADD
), オペランド (例: レジスタ、メモリ位置、定数), およびその他のメタデータを含みます。 -
ATEXT
とADATA
:ATEXT
: アセンブリコードにおいて、実行可能な命令 (関数本体など) が配置されるセクションを指します。ADATA
: アセンブリコードにおいて、初期化されたデータ (グローバル変数など) が配置されるセクションを指します。
-
FmtLong
フラグ: Goコンパイラの内部で、特定の値を「長い形式」でフォーマットすべきかどうかを示すフラグです。このコンテキストでは、特に64ビットのオフセット値を2つの32ビット値として表示する際に使用されることを意図していました。 -
%D
verb (書式指定子): C言語のprintf
関数やGoコンパイラの内部書式設定ルーチンで使用される書式指定子の一つです。この文脈では、Prog
構造体内のオペランド (アドレスや定数など) を整形して出力するために使用されます。 -
list.c
ファイル:cmd/6c
とcmd/8c
の両方に存在するファイルで、アセンブリリストの生成と書式設定に関するロジックが含まれています。Pconv
(Prog変換) やDconv
(Data/Operand変換) といった関数が定義されており、これらがアセンブリ命令やオペランドの表示形式を決定します。
技術的詳細
このコミットの技術的な核心は、src/cmd/6c/list.c
と src/cmd/8c/list.c
内の Pconv
および Dconv
関数の変更にあります。これらの関数は、Goコンパイラがアセンブリリストを生成する際に、Prog
構造体やその中のアドレス (Adr
構造体) を文字列に変換する役割を担っています。
Pconv
関数の変更
Pconv
関数は、Prog
構造体全体を整形して出力します。変更前は、%lD
という書式指定子を使用していました。これは、Dconv
関数が FmtLong
フラグを考慮してオペランドを整形することを期待していました。しかし、この FmtLong
フラグの適用ロジックに問題がありました。
// 変更前 (src/cmd/6c/list.c)
default:
sprint(str, "(%L)\t%A\t%D,%lD",
p->lineno, p->as, &p->from, &p->to);
break;
// 変更後 (src/cmd/6c/list.c)
default:
sprint(str, "(%L)\t%A\t%D,%D",
p->lineno, p->as, &p->from, &p->to);
break;
この変更により、p->to
オペランドの書式設定において、明示的に FmtLong
フラグを強制する %lD
ではなく、通常の %D
を使用するようになりました。これにより、Dconv
関数が FmtLong
フラグを適切に処理するようになります。
Dconv
関数の変更
Dconv
関数は、Adr
(アドレス) 構造体を整形して出力します。この関数内で FmtLong
フラグの処理ロジックが修正されました。
変更前は、FmtLong
フラグが設定されている場合、D_CONST
(定数) 以外のアドレスに対しては無条件に "!!" を付加していました。そして、D_CONST
の場合は $lld-lld
形式で出力していました。このロジックが、ATEXT
の Prog
の場合にのみ FmtLong
を使用すべきという意図と合致していませんでした。
// 変更前 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
if(i != D_CONST) {
// ATEXT dst is not constant
sprint(str, "!!%D", a);
goto brk;
}
sprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
(a->offset>>32)&0xffffffffLL);
goto brk;
}
// 変更後 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
if(i == D_CONST)
sprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
else {
// ATEXT dst is not constant
sprint(str, "!!%D", a);
}
goto brk;
}
この修正により、FmtLong
フラグが設定されている場合でも、D_CONST
でない場合にのみ "!!" が付加されるようになりました。これは、ATEXT
の Prog
の宛先が定数でない場合にのみ "!!" を表示するという、本来の意図に沿った動作です。
また、D_AUTO
(スタック上の自動変数) と D_PARAM
(関数パラメータ) の書式設定ロジックも簡素化され、if(a->sym)
の条件分岐がより簡潔に記述されるようになりました。
さらに、src/cmd/8c/list.c
では、D_CONST2
(2つのオフセットを持つ定数) の処理が追加され、FmtLong
フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、ATEXT
以外のコンテキストで D_CONST2
が現れるべきではないという前提に基づいています。
コードのクリーンアップ
このコミットでは、regstr
配列の初期化におけるコメントの書式も統一されています。これは機能的な変更ではありませんが、6c
と 8c
の list.c
ファイル間の類似性を高め、コードの保守性を向上させるためのクリーンアップです。これにより、両ファイルの差分がより明確になり、将来的な変更が容易になります。
コアとなるコードの変更箇所
diff --git a/src/cmd/6c/list.c b/src/cmd/6c/list.c
index 7e2d153289..b5a60ac9a2 100644
--- a/src/cmd/6c/list.c
+++ b/src/cmd/6c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
break;
default:
- sprint(str, "(%L)\t%A\t%D,%lD",
+ sprint(str, "(%L)\t%A\t%D,%D",
\tp->lineno, p->as, &p->from, &p->to);
break;
}
@@ -120,13 +120,12 @@ Dconv(Fmt *fp)
i = a->type;
if(fp->flags & FmtLong) {
-\t\tif(i != D_CONST) {
+\t\tif(i == D_CONST)
+\t\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
+\t\telse {
\t\t\t// ATEXT dst is not constant
\t\t\tsprint(str, "!!%D", a);
-\t\t\tgoto brk;
\t\t}\n-\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
-\t\t\t(a->offset>>32)&0xffffffffLL);\n \t\tgoto brk;
\t}\n
@@ -138,7 +137,6 @@ Dconv(Fmt *fp)
\t\tgoto brk;
\t}\n \tswitch(i) {\n-\n \tdefault:\n \t\tif(a->offset)\n \t\t\tsprint(str, "$%lld,%R", a->offset, i);\
@@ -159,24 +157,21 @@ Dconv(Fmt *fp)
\t\tbreak;\n \n \tcase D_STATIC:\n-\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name,\n-\t\t\ta->offset);\n+\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name, a->offset);\n \t\tbreak;\n \n \tcase D_AUTO:\n-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(SP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(SP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(SP)", a->offset);\n \t\tbreak;\n \n \tcase D_PARAM:\n-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(FP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(FP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(FP)", a->offset);\n \t\tbreak;\n \n \tcase D_CONST:\
@@ -210,7 +205,7 @@ conv:
char*\tregstr[] =
{
-\t"AL",\t\t/* [D_AL] */
+\t"AL",\t/* [D_AL] */
\t"CL",
\t"DL",
\t"BL",
@@ -227,7 +222,7 @@ char*\tregstr[] =
\t"R14B",
\t"R15B",
-\t"AX",\t\t/* [D_AX] */
+\t"AX",\t/* [D_AX] */
\t"CX",
\t"DX",
\t"BX",
@@ -249,7 +244,7 @@ char*\tregstr[] =
\t"DH",
\t"BH",
-\t"F0",\t\t/* [D_F0] */
+\t"F0",\t/* [D_F0] */
\t"F1",
\t"F2",
\t"F3",
@@ -284,20 +279,20 @@ char*\tregstr[] =
\t"X14",
\t"X15",
-\t"CS",\t\t/* [D_CS] */
+\t"CS",\t/* [D_CS] */
\t"SS",
\t"DS",
\t"ES",
\t"FS",
\t"GS",
-\t"GDTR",\t\t/* [D_GDTR] */
-\t"IDTR",\t\t/* [D_IDTR] */
-\t"LDTR",\t\t/* [D_LDTR] */
-\t"MSW",\t\t/* [D_MSW] */
-\t"TASK",\t\t/* [D_TASK] */
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
-\t"CR0",\t\t/* [D_CR] */
+\t"CR0",\t/* [D_CR] */
\t"CR1",
\t"CR2",
\t"CR3",
@@ -314,7 +309,7 @@ char*\tregstr[] =
\t"CR14",
\t"CR15",
-\t"DR0",\t\t/* [D_DR] */
+\t"DR0",\t/* [D_DR] */
\t"DR1",
\t"DR2",
\t"DR3",
@@ -323,7 +318,7 @@ char*\tregstr[] =
\t"DR6",
\t"DR7",
-\t"TR0",\t\t/* [D_TR] */
+\t"TR0",\t/* [D_TR] */
\t"TR1",
\t"TR2",
\t"TR3",
@@ -332,7 +327,7 @@ char*\tregstr[] =
\t"TR6",
\t"TR7",
-\t"NONE",\t\t/* [D_NONE] */
+\t"NONE",\t/* [D_NONE] */
};
int
diff --git a/src/cmd/8c/list.c b/src/cmd/8c/list.c
index 16a41ac368..8506e08efb 100644
--- a/src/cmd/8c/list.c
+++ b/src/cmd/8c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
break;
default:
-\t\tsprint(str, "(%L)\t%A\t%D,%lD",
+\t\tsprint(str, "(%L)\t%A\t%D,%D",
\t\t\tp->lineno, p->as, &p->from, &p->to);\
break;\
}\
@@ -118,6 +118,17 @@ Dconv(Fmt *fp)
\ta = va_arg(fp->args, Adr*);\
\ti = a->type;\
+\n+\tif(fp->flags & FmtLong) {\n+\t\tif(i == D_CONST2)\n+\t\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\n+\t\telse {\n+\t\t\t// ATEXT dst is not constant\n+\t\t\tsprint(str, "!!%D", a);\n+\t\t}\n+\t\tgoto brk;\n+\t}\n+\
\tif(i >= D_INDIR) {\
\t\tif(a->offset)\
\t\t\tsprint(str, "%d(%R)", a->offset, i-D_INDIR);\
@@ -126,7 +137,6 @@ Dconv(Fmt *fp)
\t\tgoto brk;\
\t}\
\tswitch(i) {\
-\n \tdefault:\
\t\tif(a->offset)\
\t\t\tsprint(str, "$%d,%R", a->offset, i);\
@@ -147,12 +157,14 @@ Dconv(Fmt *fp)
\t\tbreak;\
\n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%d(SB)", a->sym->name,\n-\t\t\ta->offset);\
+\t\tsprint(str, "%s<>+%d(SB)", a->sym->name, a->offset);\
\t\tbreak;\
\n \tcase D_AUTO:\
-\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\
+\t\tif(a->sym)\n+\t\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%d(SP)", a->offset);\
\t\tbreak;\
\n \tcase D_PARAM:\
@@ -167,7 +179,10 @@ Dconv(Fmt *fp)
\t\tbreak;\
\n \tcase D_CONST2:\
-\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\
+\t\tif(!(fp->flags & FmtLong)) {\n+\t\t\t// D_CONST2 outside of ATEXT should not happen\n+\t\t\tsprint(str, "!!$%d-%d", a->offset, a->offset2);\n+\t\t}\
\t\tbreak;\
\n \tcase D_FCONST:\
@@ -197,7 +212,7 @@ conv:
char*\tregstr[] =
{
-\t"AL",\t/*[D_AL]*/
+\t"AL",\t/* [D_AL] */
\t"CL",
\t"DL",
\t"BL",
@@ -206,7 +221,7 @@ char*\tregstr[] =
\t"DH",
\t"BH",
-\t"AX",\t/*[D_AX]*/
+\t"AX",\t/* [D_AX] */
\t"CX",
\t"DX",
\t"BX",
@@ -215,7 +230,7 @@ char*\tregstr[] =
\t"SI",
\t"DI",
-\t"F0",\t/*[D_F0]*/
+\t"F0",\t/* [D_F0] */
\t"F1",
\t"F2",
\t"F3",
@@ -224,20 +239,20 @@ char*\tregstr[] =
\t"F6",
\t"F7",
-\t"CS",\t/*[D_CS]*/
+\t"CS",\t/* [D_CS] */
\t"SS",
\t"DS",
\t"ES",
\t"FS",
\t"GS",
-\t"GDTR",\t/*[D_GDTR]*/
-\t"IDTR",\t/*[D_IDTR]*/
-\t"LDTR",\t/*[D_LDTR]*/
-\t"MSW",\t/*[D_MSW] */
-\t"TASK",\t/*[D_TASK]*/
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
-\t"CR0",\t/*[D_CR]*/
+\t"CR0",\t/* [D_CR] */
\t"CR1",
\t"CR2",
\t"CR3",
@@ -246,7 +261,7 @@ char*\tregstr[] =
\t"CR6",
\t"CR7",
-\t"DR0",\t/*[D_DR]*/
+\t"DR0",\t/* [D_DR] */
\t"DR1",
\t"DR2",
\t"DR3",
@@ -255,7 +270,7 @@ char*\tregstr[] =
\t"DR6",
\t"DR7",
-\t"TR0",\t/*[D_TR]*/
+\t"TR0",\t/* [D_TR] */
\t"TR1",
\t"TR2",
\t"TR3",
@@ -264,7 +279,7 @@ char*\tregstr[] =
\t"TR6",
\t"TR7",
-\t"X0",\t/*[D_X0]*/
+\t"X0",\t/* [D_X0] */
\t"X1",
\t"X2",
\t"X3",
@@ -273,7 +288,7 @@ char*\tregstr[] =
\t"X6",
\t"X7",
-\t"NONE",\t/*[D_NONE]*/
+\t"NONE",\t/* [D_NONE] */
};
int
コアとなるコードの解説
src/cmd/6c/list.c
および src/cmd/8c/list.c
の Pconv
関数
- 変更点:
sprint
関数の書式文字列において、%lD
が%D
に変更されました。 - 解説:
- 変更前:
sprint(str, "(%L)\t%A\t%D,%lD", ... &p->to);
- 変更後:
sprint(str, "(%L)\t%A\t%D,%D", ... &p->to);
%lD
は、Dconv
関数にFmtLong
フラグを強制的に渡すことを意味していました。しかし、このフラグはATEXT
のProg
の場合にのみ特定の目的 (64ビットオフセットの表示) で使用されるべきでした。- この変更により、
p->to
(命令の宛先オペランド) の書式設定は、Dconv
関数内のFmtLong
フラグの適切なロジックに委ねられるようになります。これにより、不要な "!!" の出力が抑制されます。
- 変更前:
src/cmd/6c/list.c
および src/cmd/8c/list.c
の Dconv
関数
- 変更点:
FmtLong
フラグが設定されている場合の条件分岐ロジックが修正されました。 - 解説:
- 変更前:
if(fp->flags & FmtLong) { if(i != D_CONST) { ... } else { ... } }
- 変更後:
if(fp->flags & FmtLong) { if(i == D_CONST) { ... } else { ... } }
- この修正は、
FmtLong
フラグが設定されている場合に、D_CONST
(定数) であるかどうかで処理を分岐させるようにしました。 D_CONST
の場合 (i == D_CONST
) は、$lld-lld
形式でオフセットを出力します。これは、64ビットの定数を2つの32ビット値として表示する本来の目的です。D_CONST
でない場合 (else
ブロック) は、ATEXT
の宛先が定数でないことを示唆するため、"!!" を付加してDconv
を再帰的に呼び出します。これにより、ATEXT
のProg
の宛先が定数でない場合にのみ "!!" が表示されるようになり、他の不適切な場所での "!!" の表示がなくなります。src/cmd/8c/list.c
では、D_CONST2
の処理が追加され、FmtLong
フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、D_CONST2
がATEXT
以外のコンテキストで現れるべきではないという前提に基づいています。
- 変更前:
D_AUTO
および D_PARAM
の書式設定 (両ファイル共通)
- 変更点:
if(a->sym)
の条件分岐が簡素化されました。 - 解説:
- 変更前は、
if(a->sym)
のブロック内にbreak;
があり、その後にsprint(str, "%lld(SP)", a->offset);
のような行が続いていました。 - 変更後:
if(a->sym) { ... } else { ... }
の形式になり、シンボルがある場合とない場合の処理がより明確に分離されました。これは機能的な変更ではなく、コードの可読性と保守性を向上させるためのクリーンアップです。
- 変更前は、
regstr
配列のコメント書式 (両ファイル共通)
- 変更点:
regstr
配列の初期化におけるコメントのタブとスペースの配置が統一されました。 - 解説: これは純粋なコードスタイルの変更であり、機能には影響しません。
cmd/6c/list.c
とcmd/8c/list.c
の間でコードの類似性を高め、diff
コマンドで比較した際に真の機能的な差分がより際立つようにするためのクリーンアップです。
これらの変更により、Goコンパイラが生成するアセンブリリストの正確性と可読性が大幅に向上し、開発者がアセンブリコードを理解しやすくなりました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/6c/list.c
とsrc/cmd/8c/list.c
): https://github.com/golang/go/tree/master/src/cmd - Go言語のアセンブリに関するドキュメント (例:
go doc cmd/asm
やgo tool compile -S
の出力に関する情報): https://go.dev/doc/asm - Go言語のコンパイラに関する一般的な情報 (Goのブログやカンファレンスの発表など)
- C言語の
printf
書式指定子に関する一般的な情報 (特に%D
や%lD
のようなカスタム書式指定子の概念を理解するため) - Go言語のGerrit CL (Change-list) 7128045: https://golang.org/cl/7128045 (コミットメッセージに記載されているリンク)
[インデックス 14935] ファイルの概要
このコミットは、Go言語のコンパイラツールチェーンの一部である cmd/6c
(64-bit x86アーキテクチャ向けCコンパイラ) と cmd/8c
(32-bit x86アーキテクチャ向けCコンパイラ) におけるアセンブリリスト出力の書式設定に関するバグ修正とコードクリーンアップを目的としています。具体的には、Prog
(プログラム命令) の出力時に FmtLong
フラグが誤って適用され、アセンブリリストに不要な "!!" (感嘆符) が多数表示される問題を解決します。また、関連する list.c
ファイル間のコードの類似性を高め、差分を容易に識別できるように整理しています。
コミット
commit 60826e0be6e62ae76f5771b22894bd3dc8bade10
Author: Anthony Martin <ality@pbrane.org>
Date: Fri Jan 18 19:00:38 2013 -0800
cmd/6c, cmd/8c: fix print format for Prog
The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.
I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/60826e0be6e6ae76f5771b22894bd3dc8bade10
元コミット内容
cmd/6c, cmd/8c: fix print format for Prog
The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.
I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045
変更の背景
この変更の主な背景は、Go言語のコンパイラが生成するアセンブリリストの可読性の問題にありました。具体的には、cmd/6c
および cmd/8c
コンパイラがアセンブリコードを整形して出力する際に、FmtLong
という書式フラグが不適切に適用されることで、本来表示されるべきではない "!!" という文字列が多数混入していました。
この "!!" は、通常、コンパイラがアドレスやオフセットの表現に問題がある、あるいは予期しない値に遭遇した場合にデバッグ目的で出力されるマーカーです。しかし、このバグでは、ATEXT
(関数コードセクション) の Prog
(命令) を %D
(オペランドの書式指定子) で出力する際にのみ FmtLong
フラグを使用すべきであるにもかかわらず、ADATA
(データセクション) 以外のすべての Prog
に対して誤ってこのフラグが適用されていました。結果として、正しくフォーマットされるべきアセンブリ命令のオペランドにも "!!" が付加され、アセンブリリストが非常に読みにくい状態になっていました。
このコミットは、この書式設定の誤りを修正し、アセンブリリストから不要な "!!" を排除することで、開発者が生成されたアセンブリコードをより容易にデバッグ・解析できるようにすることを目的としています。また、src/cmd/6c/list.c
と src/cmd/8c/list.c
のコード構造を統一することで、将来的なメンテナンスや両ファイル間の差分比較を容易にするという副次的な目的も持っています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のコンパイラツールチェーンとアセンブリに関する基本的な知識が必要です。
-
Go言語のコンパイラツールチェーン: Go言語は、
go build
コマンドを通じてソースコードをコンパイルし、実行可能なバイナリを生成します。このプロセスには、様々なツールが関与しており、cmd/6c
やcmd/8c
はその一部です。cmd/6c
: Go言語のソースコードをx86-64 (AMD64) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。Goのコンパイラは、歴史的にPlan 9のツールチェーンをベースにしており、その名残としてc
が付いています。cmd/8c
: Go言語のソースコードをx86 (IA-32) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。 これらのコンパイラは、Goのソースコードを中間表現に変換し、最終的にターゲットアーキテクチャのアセンブリコードを生成します。補足: 現在のGoコンパイラ (cmd/compile
) はGo 1.5以降、Go言語自体で書かれたセルフホスト型であり、6c
や8c
は現代のGo開発では直接使用されません。しかし、このコミットが作成された時点では、これらはGoツールチェーンの重要な一部でした。
-
アセンブリリスト (Assembly Listing): コンパイラが生成したアセンブリコードを、元のソースコードの行番号やコメントなどと共に整形して表示したものです。デバッグやパフォーマンスチューニングの際に、Goのコードがどのように機械語に変換されるかを理解するために使用されます。
-
Prog
(プログラム命令): Goコンパイラの内部表現において、単一のアセンブリ命令を表す構造体です。各Prog
は、命令の種類 (例:MOV
,ADD
), オペランド (例: レジスタ、メモリ位置、定数), およびその他のメタデータを含みます。 -
ATEXT
とADATA
:ATEXT
: アセンブリコードにおいて、実行可能な命令 (関数本体など) が配置されるセクションを指します。ADATA
: アセンブリコードにおいて、初期化されたデータ (グローバル変数など) が配置されるセクションを指します。
-
FmtLong
フラグ: Goコンパイラの内部で、特定の値を「長い形式」でフォーマットすべきかどうかを示すフラグです。このコンテキストでは、特に64ビットのオフセット値を2つの32ビット値として表示する際に使用されることを意図していました。これはGoの標準fmt
パッケージのフラグではなく、コンパイラ内部の書式設定ロジックで使用されるカスタムフラグです。 -
%D
verb (書式指定子): C言語のprintf
関数やGoコンパイラの内部書式設定ルーチンで使用される書式指定子の一つです。この文脈では、Prog
構造体内のオペランド (アドレスや定数など) を整形して出力するために使用されます。 -
list.c
ファイル:cmd/6c
とcmd/8c
の両方に存在するファイルで、アセンブリリストの生成と書式設定に関するロジックが含まれています。Pconv
(Prog変換) やDconv
(Data/Operand変換) といった関数が定義されており、これらがアセンブリ命令やオペランドの表示形式を決定します。
技術的詳細
このコミットの技術的な核心は、src/cmd/6c/list.c
と src/cmd/8c/list.c
内の Pconv
および Dconv
関数の変更にあります。これらの関数は、Goコンパイラがアセンブリリストを生成する際に、Prog
構造体やその中のアドレス (Adr
構造体) を文字列に変換する役割を担っています。
Pconv
関数の変更
Pconv
関数は、Prog
構造体全体を整形して出力します。変更前は、%lD
という書式指定子を使用していました。これは、Dconv
関数が FmtLong
フラグを考慮してオペランドを整形することを期待していました。しかし、この FmtLong
フラグの適用ロジックに問題がありました。
// 変更前 (src/cmd/6c/list.c)
default:
sprint(str, "(%L)\t%A\t%D,%lD",
p->lineno, p->as, &p->from, &p->to);
break;
// 変更後 (src/cmd/6c/list.c)
default:
sprint(str, "(%L)\t%A\t%D,%D",
p->lineno, p->as, &p->from, &p->to);
break;
この変更により、p->to
オペランドの書式設定において、明示的に FmtLong
フラグを強制する %lD
ではなく、通常の %D
を使用するようになりました。これにより、Dconv
関数が FmtLong
フラグを適切に処理するようになります。
Dconv
関数の変更
Dconv
関数は、Adr
(アドレス) 構造体を整形して出力します。この関数内で FmtLong
フラグの処理ロジックが修正されました。
変更前は、FmtLong
フラグが設定されている場合、D_CONST
(定数) 以外のアドレスに対しては無条件に "!!" を付加していました。そして、D_CONST
の場合は $lld-lld
形式で出力していました。このロジックが、ATEXT
の Prog
の場合にのみ FmtLong
を使用すべきという意図と合致していませんでした。
// 変更前 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
if(i != D_CONST) {
// ATEXT dst is not constant
sprint(str, "!!%D", a);
goto brk;
}
sprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
(a->offset>>32)&0xffffffffLL);
goto brk;
}
// 変更後 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
if(i == D_CONST)
sprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
else {
// ATEXT dst is not constant
sprint(str, "!!%D", a);
}
goto brk;
}
この修正により、FmtLong
フラグが設定されている場合でも、D_CONST
でない場合にのみ "!!" が付加されるようになりました。これは、ATEXT
の Prog
の宛先が定数でない場合にのみ "!!" を表示するという、本来の意図に沿った動作です。
また、D_AUTO
(スタック上の自動変数) と D_PARAM
(関数パラメータ) の書式設定ロジックも簡素化され、if(a->sym)
の条件分岐がより簡潔に記述されるようになりました。
さらに、src/cmd/8c/list.c
では、D_CONST2
(2つのオフセットを持つ定数) の処理が追加され、FmtLong
フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、ATEXT
以外のコンテキストで D_CONST2
が現れるべきではないという前提に基づいています。
コードのクリーンアップ
このコミットでは、regstr
配列の初期化におけるコメントの書式も統一されています。これは機能的な変更ではありませんが、6c
と 8c
の list.c
ファイル間の類似性を高め、コードの保守性を向上させるためのクリーンアップです。これにより、両ファイルの差分がより明確になり、将来的な変更が容易になります。
コアとなるコードの変更箇所
diff --git a/src/cmd/6c/list.c b/src/cmd/6c/list.c
index 7e2d153289..b5a60ac9a2 100644
--- a/src/cmd/6c/list.c
+++ b/src/cmd/6c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
break;
default:
- sprint(str, "(%L)\t%A\t%D,%lD",
+ sprint(str, "(%L)\t%A\t%D,%D",
\tp->lineno, p->as, &p->from, &p->to);
break;
}
@@ -120,13 +120,12 @@ Dconv(Fmt *fp)
i = a->type;
if(fp->flags & FmtLong) {
-\t\tif(i != D_CONST) {
+\t\tif(i == D_CONST)
+\t\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
+\t\telse {
\t\t\t// ATEXT dst is not constant
\t\t\tsprint(str, "!!%D", a);
-\t\t\tgoto brk;
\t\t}\n-\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
-\t\t\t(a->offset>>32)&0xffffffffLL);\n \t\tgoto brk;
\t}\n
@@ -138,7 +137,6 @@ Dconv(Fmt *fp)
\t\tgoto brk;
\t}\n \tswitch(i) {\n-\n \tdefault:\
\t\tif(a->offset)\
\t\t\tsprint(str, "$%lld,%R", a->offset, i);\
@@ -159,24 +157,21 @@ Dconv(Fmt *fp)
\t\tbreak;\n \n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name,\n-\t\t\ta->offset);\n+\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name, a->offset);\n \t\tbreak;\n \n \tcase D_AUTO:\
-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(SP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(SP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(SP)", a->offset);\n \t\tbreak;\n \n \tcase D_PARAM:\
-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(FP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(FP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(FP)", a->offset);\n \t\tbreak;\n \n \tcase D_CONST:\
@@ -210,7 +205,7 @@ conv:
char*\tregstr[] =
{
-\t"AL",\t\t/* [D_AL] */
+\t"AL",\t/* [D_AL] */
\t"CL",
\t"DL",
\t"BL",
@@ -227,7 +222,7 @@ char*\tregstr[] =
\t"R14B",
\t"R15B",
-\t"AX",\t\t/* [D_AX] */
+\t"AX",\t/* [D_AX] */
\t"CX",
\t"DX",
\t"BX",
@@ -249,7 +244,7 @@ char*\tregstr[] =
\t"DH",
\t"BH",
-\t"F0",\t\t/* [D_F0] */
+\t"F0",\t/* [D_F0] */
\t"F1",
\t"F2",
\t"F3",
@@ -284,20 +279,20 @@ char*\tregstr[] =
\t"X14",
\t"X15",
-\t"CS",\t\t/* [D_CS] */
+\t"CS",\t/* [D_CS] */
\t"SS",
\t"DS",
\t"ES",
\t"FS",
\t"GS",
-\t"GDTR",\t\t/* [D_GDTR] */
-\t"IDTR",\t\t/* [D_IDTR] */
-\t"LDTR",\t\t/* [D_LDTR] */
-\t"MSW",\t\t/* [D_MSW] */
-\t"TASK",\t\t/* [D_TASK] */
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
-\t"CR0",\t\t/* [D_CR] */
+\t"CR0",\t/* [D_CR] */
\t"CR1",
\t"CR2",
\t"CR3",
@@ -314,7 +309,7 @@ char*\tregstr[] =
\t"CR14",
\t"CR15",
-\t"DR0",\t\t/* [D_DR] */
+\t"DR0",\t/* [D_DR] */
\t"DR1",
\t"DR2",
\t"DR3",
@@ -323,7 +318,7 @@ char*\tregstr[] =
\t"DR6",
\t"DR7",
-\t"TR0",\t\t/* [D_TR] */
+\t"TR0",\t/* [D_TR] */
\t"TR1",
\t"TR2",
\t"TR3",
@@ -332,7 +327,7 @@ char*\tregstr[] =
\t"TR6",
\t"TR7",
-\t"NONE",\t\t/* [D_NONE] */
+\t"NONE",\t/* [D_NONE] */
};
int
diff --git a/src/cmd/8c/list.c b/src/cmd/8c/list.c
index 16a41ac368..8506e08efb 100644
--- a/src/cmd/8c/list.c
+++ b/src/cmd/8c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
break;
default:
-\t\tsprint(str, "(%L)\t%A\t%D,%lD",
+\t\tsprint(str, "(%L)\t%A\t%D,%D",
\t\t\tp->lineno, p->as, &p->from, &p->to);\
break;\
}\
@@ -118,6 +118,17 @@ Dconv(Fmt *fp)
\ta = va_arg(fp->args, Adr*);\
\ti = a->type;\
+\n+\tif(fp->flags & FmtLong) {\n+\t\tif(i == D_CONST2)\n+\t\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\n+\t\telse {\n+\t\t\t// ATEXT dst is not constant\n+\t\t\tsprint(str, "!!%D", a);\n+\t\t}\n+\t\tgoto brk;\n+\t}\n+\
\tif(i >= D_INDIR) {\
\t\tif(a->offset)\
\t\t\tsprint(str, "%d(%R)", a->offset, i-D_INDIR);\
@@ -126,7 +137,6 @@ Dconv(Fmt *fp)
\t\tgoto brk;\
\t}\
\tswitch(i) {\
-\n \tdefault:\
\t\tif(a->offset)\
\t\t\tsprint(str, "$%d,%R", a->offset, i);\
@@ -147,12 +157,14 @@ Dconv(Fmt *fp)
\t\tbreak;\
\n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%d(SB)", a->sym->name,\n-\t\t\ta->offset);\
+\t\tsprint(str, "%s<>+%d(SB)", a->sym->name, a->offset);\
\t\tbreak;\
\n \tcase D_AUTO:\
-\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\
+\t\tif(a->sym)\n+\t\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%d(SP)", a->offset);\
\t\tbreak;\
\n \tcase D_PARAM:\
@@ -167,7 +179,10 @@ Dconv(Fmt *fp)
\t\tbreak;\
\n \tcase D_CONST2:\
-\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\
+\t\tif(!(fp->flags & FmtLong)) {\n+\t\t\t// D_CONST2 outside of ATEXT should not happen\n+\t\t\tsprint(str, "!!$%d-%d", a->offset, a->offset2);\n+\t\t}\
\t\tbreak;\
\n \tcase D_FCONST:\
@@ -197,7 +212,7 @@ conv:
char*\tregstr[] =
{
-\t"AL",\t/*[D_AL]*/
+\t"AL",\t/* [D_AL] */
\t"CL",
\t"DL",
\t"BL",
@@ -206,7 +221,7 @@ char*\tregstr[] =
\t"DH",
\t"BH",
-\t"AX",\t/*[D_AX]*/
+\t"AX",\t/* [D_AX] */
\t"CX",
\t"DX",
\t"BX",
@@ -215,7 +230,7 @@ char*\tregstr[] =
\t"SI",
\t"DI",
-\t"F0",\t/*[D_F0]*/
+\t"F0",\t/* [D_F0] */
\t"F1",
\t"F2",
\t"F3",
@@ -224,20 +239,20 @@ char*\tregstr[] =
\t"F6",
\t"F7",
-\t"CS",\t/*[D_CS]*/
+\t"CS",\t/* [D_CS] */
\t"SS",
\t"DS",
\t"ES",
\t"FS",
\t"GS",
-\t"GDTR",\t/*[D_GDTR]*/
-\t"IDTR",\t/*[D_IDTR]*/
-\t"LDTR",\t/*[D_LDTR]*/
-\t"MSW",\t/*[D_MSW] */
-\t"TASK",\t/*[D_TASK]*/
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
-\t"CR0",\t/*[D_CR]*/
+\t"CR0",\t/* [D_CR] */
\t"CR1",
\t"CR2",
\t"CR3",
@@ -246,7 +261,7 @@ char*\tregstr[] =
\t"CR6",
\t"CR7",
-\t"DR0",\t/*[D_DR]*/
+\t"DR0",\t/* [D_DR] */
\t"DR1",
\t"DR2",
\t"DR3",
@@ -255,7 +270,7 @@ char*\tregstr[] =
\t"DR6",
\t"DR7",
-\t"TR0",\t/*[D_TR]*/
+\t"TR0",\t/* [D_TR] */
\t"TR1",
\t"TR2",
\t"TR3",
@@ -264,7 +279,7 @@ char*\tregstr[] =
\t"TR6",
\t"TR7",
-\t"X0",\t/*[D_X0]*/
+\t"X0",\t/* [D_X0] */
\t"X1",
\t"X2",
\t"X3",
@@ -273,7 +288,7 @@ char*\tregstr[] =
\t"X6",
\t"X7",
-\t"NONE",\t/*[D_NONE]*/
+\t"NONE",\t/* [D_NONE] */
};
int
コアとなるコードの解説
src/cmd/6c/list.c
および src/cmd/8c/list.c
の Pconv
関数
- 変更点:
sprint
関数の書式文字列において、%lD
が%D
に変更されました。 - 解説:
- 変更前:
sprint(str, "(%L)\t%A\t%D,%lD", ... &p->to);
- 変更後:
sprint(str, "(%L)\t%A\t%D,%D", ... &p->to);
%lD
は、Dconv
関数にFmtLong
フラグを強制的に渡すことを意味していました。しかし、このフラグはATEXT
のProg
の場合にのみ特定の目的 (64ビットオフセットの表示) で使用されるべきでした。- この変更により、
p->to
(命令の宛先オペランド) の書式設定は、Dconv
関数内のFmtLong
フラグの適切なロジックに委ねられるようになります。これにより、不要な "!!" の出力が抑制されます。
- 変更前:
src/cmd/6c/list.c
および src/cmd/8c/list.c
の Dconv
関数
- 変更点:
FmtLong
フラグが設定されている場合の条件分岐ロジックが修正されました。 - 解説:
- 変更前:
if(fp->flags & FmtLong) { if(i != D_CONST) { ... } else { ... } }
- 変更後:
if(fp->flags & FmtLong) { if(i == D_CONST) { ... } else { ... } }
- この修正は、
FmtLong
フラグが設定されている場合に、D_CONST
(定数) であるかどうかで処理を分岐させるようにしました。 D_CONST
の場合 (i == D_CONST
) は、$lld-lld
形式でオフセットを出力します。これは、64ビットの定数を2つの32ビット値として表示する本来の目的です。D_CONST
でない場合 (else
ブロック) は、ATEXT
の宛先が定数でないことを示唆するため、"!!" を付加してDconv
を再帰的に呼び出します。これにより、ATEXT
のProg
の宛先が定数でない場合にのみ "!!" が表示されるようになり、他の不適切な場所での "!!" の表示がなくなります。src/cmd/8c/list.c
では、D_CONST2
の処理が追加され、FmtLong
フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、D_CONST2
がATEXT
以外のコンテキストで現れるべきではないという前提に基づいています。
- 変更前:
D_AUTO
および D_PARAM
の書式設定 (両ファイル共通)
- 変更点:
if(a->sym)
の条件分岐が簡素化されました。 - 解説:
- 変更前は、
if(a->sym)
のブロック内にbreak;
があり、その後にsprint(str, "%lld(SP)", a->offset);
のような行が続いていました。 - 変更後:
if(a->sym) { ... } else { ... }
の形式になり、シンボルがある場合とない場合の処理がより明確に分離されました。これは機能的な変更ではなく、コードの可読性と保守性を向上させるためのクリーンアップです。
- 変更前は、
regstr
配列のコメント書式 (両ファイル共通)
- 変更点:
regstr
配列の初期化におけるコメントのタブとスペースの配置が統一されました。 - 解説: これは純粋なコードスタイルの変更であり、機能には影響しません。
cmd/6c/list.c
とcmd/8c/list.c
の間でコードの類似性を高め、diff
コマンドで比較した際に真の機能的な差分がより際立つようにするためのクリーンアップです。
これらの変更により、Goコンパイラが生成するアセンブリリストの正確性と可読性が大幅に向上し、開発者がアセンブリコードを理解しやすくなりました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のIssueトラッカー: https://github.com/golang/go/issues
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/6c/list.c
とsrc/cmd/8c/list.c
): https://github.com/golang/go/tree/master/src/cmd - Go言語のアセンブリに関するドキュメント (例:
go doc cmd/asm
やgo tool compile -S
の出力に関する情報): https://go.dev/doc/asm - Go言語のGerrit CL (Change-list) 7128045: https://golang.org/cl/7128045 (コミットメッセージに記載されているリンク)
- Go cmd/6c cmd/8c compiler (Web Search Result): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFUlnJt8rW74Em_FXd5O_e2Nq4dgYbazxpUFRuxASuL9W_rQKdlSs-pKigVaYAiScyS02fBfDhagJE9di0RPqi12SkXoJaS66pUXzfApVFe4vfZ3CHr60JxhUYCoCGWN-QyqdNo5jtstE14HplPh5Tg6yv2DW2js6fCJKhP3dnxphCBNsZr1MnRqxeksI1z-tGN3sXpLEZmDpRq
- Go assembly listing format (Web Search Result): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHwaaUtCXrp3nuJyEmcela1CXWbR1wI_-DNWQKj0Guu9k5KpO6ODmCuj4vOq2F0UzH16iZCFY7tBRwvkzJQNXr04UZLy9JCBDj-0nKQeos5bwg=
- Go FmtLong flag (Web Search Result): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEI0rkmpXk4IDCjxnEpZZwO4Mdnc_9_hrDJBW9zTW-lR2VHPmll6pIx2c_gmdjkZnMsYxJwEkpe2snHEijRWW775Vhx5-VlgeYHd9SjCxG3fQ==