[インデックス 1389] ファイルの概要
このコミットは、Goコンパイラのバックエンドの一部である src/cmd/6g/gen.c
ファイルに対する変更です。6g
は、Go言語の初期のコンパイラツールチェーンの一部であり、Plan 9アーキテクチャをベースにしたGoコンパイラのx86-64 (AMD64) アーキテクチャ向けコードジェネレータです。gen.c
は、抽象構文木 (AST) や中間表現 (IR) からターゲットマシンコードを生成する役割を担っています。具体的には、アセンブリ命令の生成やレジスタ割り当てに関連する処理が含まれています。
コミット
このコミットは、Ken Thompsonによって2008年12月20日に行われました。コミットメッセージは「LEAQ bug」と簡潔に記されており、LEAQ
命令に関連するバグの修正が目的であることが示唆されています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7cfe782a693456346d92376e230dbb3b72cd6184
元コミット内容
commit 7cfe782a693456346d92376e230dbb3b72cd6184
Author: Ken Thompson <ken@golang.org>
Date: Sat Dec 20 16:05:12 2008 -0800
LEAQ bug
R=r
OCL=21684
CL=21684
---
src/cmd/6g/gen.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/cmd/6g/gen.c b/src/cmd/6g/gen.c
index 0f625c649b..640243f7f5 100644
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -880,7 +880,7 @@ cgen_callret(Node *n, Node *res)\n void\n cgen_aret(Node *n, Node *res)\n {\n-\tNode nod1;\n+\tNode nod1, nod2;\n \tType *fp, *t;\n \tIter flist;\n \n@@ -900,7 +900,13 @@ cgen_aret(Node *n, Node *res)\n \tnod1.xoffset = fp->width;\n \tnod1.type = fp->type;\n \n-\tgins(ALEAQ, &nod1, res);\n+\tif(res->op != OREGISTER) {\n+print(\"its 1\\n\");\n+\t\tregalloc(&nod2, types[tptr], res);\n+\t\tgins(ALEAQ, &nod1, &nod2);\n+\t\tgins(AMOVQ, &nod2, res);\n+\t} else\n+\t\tgins(ALEAQ, &nod1, res);\n }\n \n void
変更の背景
このコミットは「LEAQ bug」という簡潔なメッセージで示されている通り、LEAQ
(Load Effective Address) 命令の生成に関するバグを修正するものです。LEAQ
命令は、メモリのアドレスを計算し、その結果をレジスタにロードするために使用されます。これはポインタ演算や構造体のアドレス計算など、様々な場面で利用されます。
コンパイラがコードを生成する際、中間表現 (IR) の結果を最終的なマシンコードに変換します。この過程で、変数のアドレスをレジスタにロードする必要がある場合、LEAQ
命令が使われます。しかし、この命令のターゲット(結果を格納する場所)がレジスタではない場合、直接 LEAQ
を発行すると問題が発生する可能性があります。
元のコードでは、LEAQ
命令のターゲットが常に res
という Node
オブジェクトに直接指定されていました。res
がレジスタではない場合(例えば、メモリ上の場所を指す場合)、LEAQ
命令はレジスタをオペランドとして期待するため、不正なコードが生成されるか、コンパイラがクラッシュするなどの問題を引き起こす可能性がありました。このバグは、LEAQ
命令のセマンティクスと、コンパイラのレジスタ割り当ておよびコード生成ロジックの間の不整合に起因していたと考えられます。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Goコンパイラ (6g): Go言語の初期のコンパイラの一つで、x86-64アーキテクチャ向けのコードを生成します。コンパイラは、ソースコードを解析し、中間表現に変換し、最終的にターゲットマシンが実行できるアセンブリコードを生成します。
LEAQ
(Load Effective Address) 命令: x86-64アーキテクチャのアセンブリ命令の一つです。LEA
は "Load Effective Address" の略で、メモリのアドレスを計算し、その結果を汎用レジスタに格納します。例えば、LEAQ (%rax, %rbx, 4), %rcx
は、%rax + %rbx * 4
のアドレスを計算し、その結果を%rcx
レジスタに格納します。これは、ポインタの計算や、定数による乗算を伴うアドレス計算を効率的に行うために使われます。MOV
命令がメモリの内容をロードするのに対し、LEA
はアドレス自体をロードします。Node
構造体: コンパイラ内部で、抽象構文木 (AST) や中間表現 (IR) のノードを表すために使われるデータ構造です。各Node
は、変数、定数、演算子、関数呼び出しなど、プログラムの要素に対応します。Node
には、その種類 (op
)、型 (type
)、値、レジスタ割り当て情報などが含まれます。OREGISTER
:Node
のop
フィールドが取りうる値の一つで、そのNode
がレジスタを表していることを示します。Type
構造体: コンパイラ内部で、Go言語の型システムを表すために使われるデータ構造です。types[tptr]
は、ポインタ型を表すグローバルなType
オブジェクトへの参照と考えられます。regalloc(Node *n, Type *t, Node *res)
: レジスタ割り当てを行う関数です。n
にレジスタを割り当て、そのレジスタの情報をn
に格納します。t
は割り当てるレジスタの型、res
は結果を格納する最終的な場所(ヒント)です。gins(Op op, Node *src, Node *dst)
: アセンブリ命令を生成する関数です。op
は命令の種類(例:ALEAQ
,AMOVQ
)、src
はソースオペランド、dst
はデスティネーションオペランドです。ALEAQ
:LEAQ
命令に対応する内部的なオペレーションコードです。AMOVQ
:MOVQ
(Move Quadword) 命令に対応する内部的なオペレーションコードです。MOVQ
は、64ビットの値を移動させる命令です。
技術的詳細
このコミットの技術的な核心は、cgen_aret
関数における LEAQ
命令の生成ロジックの改善にあります。cgen_aret
関数は、おそらく関数の戻り値のアドレスを計算し、それを特定の場所に格納する役割を担っています。
元のコードでは、gins(ALEAQ, &nod1, res);
という行で、nod1
のアドレスを計算し、その結果を res
に直接格納しようとしていました。ここで問題となるのは、res
が必ずしもレジスタであるとは限らない点です。LEAQ
命令は、その結果をレジスタに格納することを前提としています。もし res
がメモリ上の場所を指す Node
であった場合、LEAQ
命令は不正なオペランドを受け取ることになり、コンパイラの誤動作やクラッシュにつながります。
修正後のコードでは、この問題を解決するために条件分岐が導入されました。
if(res->op != OREGISTER)
: まず、res
がレジスタではないかどうかをチェックします。- 一時レジスタの導入: もし
res
がレジスタではない場合、一時的なレジスタnod2
を導入します。regalloc(&nod2, types[tptr], res);
:nod2
にポインタ型 (types[tptr]
) のレジスタを割り当てます。res
はレジスタ割り当てのヒントとして使われます。gins(ALEAQ, &nod1, &nod2);
:nod1
のアドレスを計算し、その結果を一時レジスタnod2
に格納するLEAQ
命令を生成します。これでLEAQ
命令のデスティネーションが常にレジスタになります。gins(AMOVQ, &nod2, res);
: 一時レジスタnod2
に格納されたアドレスを、本来のデスティネーションであるres
に移動させるMOVQ
命令を生成します。これにより、res
がメモリ上の場所であっても、正しくアドレスが格納されます。
- 既存のロジックの維持: もし
res
が既にレジスタである場合は、元のgins(ALEAQ, &nod1, res);
のロジックをそのまま使用します。この場合、LEAQ
命令のデスティネーションは既にレジスタであるため、問題ありません。
この修正により、LEAQ
命令が常に適切なデスティネーション(レジスタ)を持つことが保証され、コンパイラの堅牢性が向上しました。これは、コンパイラのコード生成におけるレジスタ割り当てと命令セマンティクスの厳密な遵守の重要性を示す典型的な例です。
コアとなるコードの変更箇所
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -880,7 +880,7 @@ cgen_callret(Node *n, Node *res)\n void\n cgen_aret(Node *n, Node *res)\n {\n-\tNode nod1;\n+\tNode nod1, nod2;\n \tType *fp, *t;\n \tIter flist;\n \n@@ -900,7 +900,13 @@ cgen_aret(Node *n, Node *res)\n \tnod1.xoffset = fp->width;\n \tnod1.type = fp->type;\n \n-\tgins(ALEAQ, &nod1, res);\n+\tif(res->op != OREGISTER) {\n+print(\"its 1\\n\");\n+\t\tregalloc(&nod2, types[tptr], res);\n+\t\tgins(ALEAQ, &nod1, &nod2);\n+\t\tgins(AMOVQ, &nod2, res);\n+\t} else\n+\t\tgins(ALEAQ, &nod1, res);\n }\n \n void
コアとなるコードの解説
cgen_aret
関数内の変更点に焦点を当てて解説します。
-
Node nod1;
からNode nod1, nod2;
への変更:nod2
という新しいNode
型の変数が追加されました。これは、LEAQ
命令の結果を一時的に保持するためのレジスタを表すために使用されます。
-
gins(ALEAQ, &nod1, res);
の変更:-
この行が、より複雑な条件分岐に置き換えられました。
-
if(res->op != OREGISTER) { ... }
:- この条件は、
res
ノードがレジスタではない場合に真となります。つまり、LEAQ
命令の直接のデスティネーションとしてres
を使用できない状況です。 print("its 1\\n");
: デバッグ用の出力と思われます。このパスが実行されたことを示すために一時的に追加された可能性があります。regalloc(&nod2, types[tptr], res);
:nod2
にポインタ型 (types[tptr]
) のレジスタを割り当てます。res
は、レジスタ割り当てのヒントとして渡されます。これにより、nod2
は一時的なレジスタとして機能します。gins(ALEAQ, &nod1, &nod2);
:nod1
のアドレスを計算し、その結果を新しく割り当てられた一時レジスタnod2
に格納するLEAQ
命令を生成します。これでLEAQ
命令のデスティネーションは常にレジスタになります。gins(AMOVQ, &nod2, res);
: 一時レジスタnod2
に格納されたアドレス(nod1
のアドレス)を、最終的なデスティネーションであるres
に移動させるMOVQ
命令を生成します。これにより、res
がメモリ上の場所であっても、正しくアドレスが書き込まれます。
- この条件は、
-
else gins(ALEAQ, &nod1, res);
:res
が既にレジスタである場合(res->op == OREGISTER
)、元のロジックがそのまま適用されます。この場合、LEAQ
命令のデスティネーションは既にレジスタであるため、追加のMOVQ
は不要です。
-
この修正は、コンパイラが生成するアセンブリコードの正確性を保証するために不可欠であり、特定の命令(LEAQ
)のセマンティクスと、コンパイラのレジスタ割り当て戦略との整合性を保つための典型的なパターンを示しています。
関連リンク
- Go言語の初期のコンパイラに関する情報:
- Goのコンパイラツールチェーンの歴史や設計思想について、公式ドキュメントや初期の設計ドキュメントを参照すると理解が深まります。
- Go Compiler Design (これは一般的なGoコンパイラの設計に関するものですが、初期の設計思想も含まれる可能性があります)
- x86-64 (AMD64) アセンブリ命令セット:
LEAQ
やMOVQ
命令の詳細な動作については、IntelまたはAMDの命令セットリファレンスマニュアルを参照するのが最も正確です。- Intel® 64 and IA-32 Architectures Software Developer’s Manuals
- コンパイラのレジスタ割り当て:
- コンパイラにおけるレジスタ割り当てのアルゴリズムや課題について学ぶことで、このコミットの背景にある技術的な複雑さをより深く理解できます。
- Wikipedia: Register allocation
参考にした情報源リンク
- GitHub: golang/go repository
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals (LEAQ, MOVQ 命令の確認のため)
- Wikipedia: Load effective address (LEAQ 命令の一般的な説明のため)
- Go Compiler Design (Goコンパイラの一般的な理解のため)
- Go 6g compiler source code (コミット対象ファイルのコンテキスト理解のため)
- Go issue tracker (関連するバグ報告や議論がないか確認するため)
- Go mailing lists/forums (初期のGo開発に関する議論がないか確認するため)# [インデックス 1389] ファイルの概要
このコミットは、Goコンパイラのバックエンドの一部である src/cmd/6g/gen.c
ファイルに対する変更です。6g
は、Go言語の初期のコンパイラツールチェーンの一部であり、Plan 9アーキテクチャをベースにしたGoコンパイラのx86-64 (AMD64) アーキテクチャ向けコードジェネレータです。gen.c
は、抽象構文木 (AST) や中間表現 (IR) からターゲットマシンコードを生成する役割を担っています。具体的には、アセンブリ命令の生成やレジスタ割り当てに関連する処理が含まれています。
コミット
このコミットは、Ken Thompsonによって2008年12月20日に行われました。コミットメッセージは「LEAQ bug」と簡潔に記されており、LEAQ
命令に関連するバグの修正が目的であることが示唆されています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7cfe782a693456346d92376e230dbb3b72cd6184
元コミット内容
commit 7cfe782a693456346d92376e230dbb3b72cd6184
Author: Ken Thompson <ken@golang.org>
Date: Sat Dec 20 16:05:12 2008 -0800
LEAQ bug
R=r
OCL=21684
CL=21684
---
src/cmd/6g/gen.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)\n
diff --git a/src/cmd/6g/gen.c b/src/cmd/6g/gen.c
index 0f625c649b..640243f7f5 100644
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -880,7 +880,7 @@ cgen_callret(Node *n, Node *res)\n void\n cgen_aret(Node *n, Node *res)\n {\n-\tNode nod1;\n+\tNode nod1, nod2;\n \tType *fp, *t;\n \tIter flist;\n \n@@ -900,7 +900,13 @@ cgen_aret(Node *n, Node *res)\n \tnod1.xoffset = fp->width;\n \tnod1.type = fp->type;\n \n-\tgins(ALEAQ, &nod1, res);\n+\tif(res->op != OREGISTER) {\n+print(\"its 1\\n\");\n+\t\tregalloc(&nod2, types[tptr], res);\n+\t\tgins(ALEAQ, &nod1, &nod2);\n+\t\tgins(AMOVQ, &nod2, res);\n+\t} else\n+\t\tgins(ALEAQ, &nod1, res);\n }\n \n void
変更の背景
このコミットは「LEAQ bug」という簡潔なメッセージで示されている通り、LEAQ
(Load Effective Address) 命令の生成に関するバグを修正するものです。LEAQ
命令は、メモリのアドレスを計算し、その結果をレジスタにロードするために使用されます。これはポインタ演算や構造体のアドレス計算など、様々な場面で利用されます。
コンパイラがコードを生成する際、中間表現 (IR) の結果を最終的なマシンコードに変換します。この過程で、変数のアドレスをレジスタにロードする必要がある場合、LEAQ
命令が使われます。しかし、この命令のターゲット(結果を格納する場所)がレジスタではない場合、直接 LEAQ
を発行すると問題が発生する可能性があります。
元のコードでは、LEAQ
命令のターゲットが常に res
という Node
オブジェクトに直接指定されていました。res
がレジスタではない場合(例えば、メモリ上の場所を指す場合)、LEAQ
命令はレジスタをオペランドとして期待するため、不正なコードが生成されるか、コンパイラがクラッシュするなどの問題を引き起こす可能性がありました。このバグは、LEAQ
命令のセマンティクスと、コンパイラのレジスタ割り当ておよびコード生成ロジックの間の不整合に起因していたと考えられます。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Goコンパイラ (6g): Go言語の初期のコンパイラの一つで、x86-64アーキテクチャ向けのコードを生成します。コンパイラは、ソースコードを解析し、中間表現に変換し、最終的にターゲットマシンが実行できるアセンブリコードを生成します。
LEAQ
(Load Effective Address) 命令: x86-64アーキテクチャのアセンブリ命令の一つです。LEA
は "Load Effective Address" の略で、メモリのアドレスを計算し、その結果を汎用レジスタに格納します。例えば、LEAQ (%rax, %rbx, 4), %rcx
は、%rax + %rbx * 4
のアドレスを計算し、その結果を%rcx
レジスタに格納します。これは、ポインタの計算や、定数による乗算を伴うアドレス計算を効率的に行うために使われます。MOV
命令がメモリの内容をロードするのに対し、LEA
はアドレス自体をロードします。Node
構造体: コンパイラ内部で、抽象構文木 (AST) や中間表現 (IR) のノードを表すために使われるデータ構造です。各Node
は、変数、定数、演算子、関数呼び出しなど、プログラムの要素に対応します。Node
には、その種類 (op
)、型 (type
)、値、レジスタ割り当て情報などが含まれます。OREGISTER
:Node
のop
フィールドが取りうる値の一つで、そのNode
がレジスタを表していることを示します。Type
構造体: コンパイラ内部で、Go言語の型システムを表すために使われるデータ構造です。types[tptr]
は、ポインタ型を表すグローバルなType
オブジェクトへの参照と考えられます。regalloc(Node *n, Type *t, Node *res)
: レジスタ割り当てを行う関数です。n
にレジスタを割り当て、そのレジスタの情報をn
に格納します。t
は割り当てるレジスタの型、res
は結果を格納する最終的な場所(ヒント)です。gins(Op op, Node *src, Node *dst)
: アセンブリ命令を生成する関数です。op
は命令の種類(例:ALEAQ
,AMOVQ
)、src
はソースオペランド、dst
はデスティネーションオペランドです。ALEAQ
:LEAQ
命令に対応する内部的なオペレーションコードです。AMOVQ
:MOVQ
(Move Quadword) 命令に対応する内部的なオペレーションコードです。MOVQ
は、64ビットの値を移動させる命令です。
技術的詳細
このコミットの技術的な核心は、cgen_aret
関数における LEAQ
命令の生成ロジックの改善にあります。cgen_aret
関数は、おそらく関数の戻り値のアドレスを計算し、それを特定の場所に格納する役割を担っています。
元のコードでは、gins(ALEAQ, &nod1, res);
という行で、nod1
のアドレスを計算し、その結果を res
に直接格納しようとしていました。ここで問題となるのは、res
が必ずしもレジスタであるとは限らない点です。LEAQ
命令は、その結果をレジスタに格納することを前提としています。もし res
がメモリ上の場所を指す Node
であった場合、LEAQ
命令は不正なオペランドを受け取ることになり、コンパイラの誤動作やクラッシュにつながります。
修正後のコードでは、この問題を解決するために条件分岐が導入されました。
if(res->op != OREGISTER)
: まず、res
がレジスタではないかどうかをチェックします。- 一時レジスタの導入: もし
res
がレジスタではない場合、一時的なレジスタnod2
を導入します。regalloc(&nod2, types[tptr], res);
:nod2
にポインタ型 (types[tptr]
) のレジスタを割り当てます。res
はレジスタ割り当てのヒントとして使われます。gins(ALEAQ, &nod1, &nod2);
:nod1
のアドレスを計算し、その結果を一時レジスタnod2
に格納するLEAQ
命令を生成します。これでLEAQ
命令のデスティネーションが常にレジスタになります。gins(AMOVQ, &nod2, res);
: 一時レジスタnod2
に格納されたアドレスを、本来のデスティネーションであるres
に移動させるMOVQ
命令を生成します。これにより、res
がメモリ上の場所であっても、正しくアドレスが格納されます。
- 既存のロジックの維持: もし
res
が既にレジスタである場合は、元のgins(ALEAQ, &nod1, res);
のロジックをそのまま使用します。この場合、LEAQ
命令のデスティネーションは既にレジスタであるため、問題ありません。
この修正により、LEAQ
命令が常に適切なデスティネーション(レジスタ)を持つことが保証され、コンパイラの堅牢性が向上しました。これは、コンパイラのコード生成におけるレジスタ割り当てと命令セマンティクスの厳密な遵守の重要性を示す典型的な例です。
コアとなるコードの変更箇所
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -880,7 +880,7 @@ cgen_callret(Node *n, Node *res)\n void\n cgen_aret(Node *n, Node *res)\n {\n-\tNode nod1;\n+\tNode nod1, nod2;\n \tType *fp, *t;\n \tIter flist;\n \n@@ -900,7 +900,13 @@ cgen_aret(Node *n, Node *res)\n \tnod1.xoffset = fp->width;\n \tnod1.type = fp->type;\n \n-\tgins(ALEAQ, &nod1, res);\n+\tif(res->op != OREGISTER) {\n+print(\"its 1\\n\");\n+\t\tregalloc(&nod2, types[tptr], res);\n+\t\tgins(ALEAQ, &nod1, &nod2);\n+\t\tgins(AMOVQ, &nod2, res);\n+\t} else\n+\t\tgins(ALEAQ, &nod1, res);\n }\n \n void
コアとなるコードの解説
cgen_aret
関数内の変更点に焦点を当てて解説します。
-
Node nod1;
からNode nod1, nod2;
への変更:nod2
という新しいNode
型の変数が追加されました。これは、LEAQ
命令の結果を一時的に保持するためのレジスタを表すために使用されます。
-
gins(ALEAQ, &nod1, res);
の変更:-
この行が、より複雑な条件分岐に置き換えられました。
-
if(res->op != OREGISTER) { ... }
:- この条件は、
res
ノードがレジスタではない場合に真となります。つまり、LEAQ
命令の直接のデスティネーションとしてres
を使用できない状況です。 print("its 1\\n");
: デバッグ用の出力と思われます。このパスが実行されたことを示すために一時的に追加された可能性があります。regalloc(&nod2, types[tptr], res);
:nod2
にポインタ型 (types[tptr]
) のレジスタを割り当てます。res
は、レジスタ割り当てのヒントとして渡されます。これにより、nod2
は一時的なレジスタとして機能します。gins(ALEAQ, &nod1, &nod2);
:nod1
のアドレスを計算し、その結果を新しく割り当てられた一時レジスタnod2
に格納するLEAQ
命令を生成します。これでLEAQ
命令のデスティネーションは常にレジスタになります。gins(AMOVQ, &nod2, res);
: 一時レジスタnod2
に格納されたアドレス(nod1
のアドレス)を、最終的なデスティネーションであるres
に移動させるMOVQ
命令を生成します。これにより、res
がメモリ上の場所であっても、正しくアドレスが書き込まれます。
- この条件は、
-
else gins(ALEAQ, &nod1, res);
:res
が既にレジスタである場合(res->op == OREGISTER
)、元のロジックがそのまま適用されます。この場合、LEAQ
命令のデスティネーションは既にレジスタであるため、追加のMOVQ
は不要です。
-
この修正は、コンパイラが生成するアセンブリコードの正確性を保証するために不可欠であり、特定の命令(LEAQ
)のセマンティクスと、コンパイラのレジスタ割り当て戦略との整合性を保つための典型的なパターンを示しています。
関連リンク
- Go言語の初期のコンパイラに関する情報:
- Goのコンパイラツールチェーンの歴史や設計思想について、公式ドキュメントや初期の設計ドキュメントを参照すると理解が深まります。
- Go Compiler Design (これは一般的なGoコンパイラの設計に関するものですが、初期の設計思想も含まれる可能性があります)
- x86-64 (AMD64) アセンブリ命令セット:
LEAQ
やMOVQ
命令の詳細な動作については、IntelまたはAMDの命令セットリファレンスマニュアルを参照するのが最も正確です。- Intel® 64 and IA-32 Architectures Software Developer’s Manuals
- コンパイラのレジスタ割り当て:
- コンパイラにおけるレジスタ割り当てのアルゴリズムや課題について学ぶことで、このコミットの背景にある技術的な複雑さをより深く理解できます。
- Wikipedia: Register allocation
参考にした情報源リンク
- GitHub: golang/go repository
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals (LEAQ, MOVQ 命令の確認のため)
- Wikipedia: Load effective address (LEAQ 命令の一般的な説明のため)
- Go Compiler Design (Goコンパイラの一般的な理解のため)
- Go 6g compiler source code (コミット対象ファイルのコンテキスト理解のため)
- Go issue tracker (関連するバグ報告や議論がないか確認するため)
- Go mailing lists/forums (初期のGo開発に関する議論がないか確認するため)