
ごく最近、私たちはHabréで、私たちが訪れた会議について話しました。 それらの1つはGopherCon 2016で、ほとんどすべての人がGo-shnyアセンブラーに関するRob "Commander" Pikeの話を覚えています 。 記事としてデザインされた彼のレポートの翻訳をお見せします。 私は、ウィキペディアの関連記事への可能な限り多くのリンクをテキストで提供しようとしました。
アセンブラー? これはマンモスのような古代のものです!

Rob PikeがGopherCon 2016で出演
よろしくお願いします! これは、長年にわたるアセンブリ言語に関する報告書に対する最も熱心な回答です。 あなたは尋ねるかもしれません:なぜアセンブラーについて話す必要があるのですか 理由はありますが、それについては後で。 また、アセンブリ言語がどのような興味を持っているかも尋ねることができます。 1970年に戻って、IBMマニュアルを見てみましょう。「最も重要なことは、アセンブラー言語の次の機能を理解することです。プログラマーは、System / 360マシンコードレベルでプログラミングしているようにSystem / 360コンピューターのすべての機能を使用できます」
多くの点で、これらの言葉は時代遅れですが、基本的な考え方は依然として真実です。アセンブラーは、最も基本的なレベルでコンピューターと対話する方法です。 実際、今日でもアセンブリ言語が必要です。 アセンブラーだけがプログラマーに任せられていた時代がありました。
その後、いわゆる高レベル言語、たとえばFortranやKobolが登場しました。これらはゆっくりと、しかし確実にアセンブラに取って代わりました。 しばらくの間、 C言語でさえアセンブラーと比較して高レベルと見なされていました。 しかし、アセンブラーの必要性は今日も残っています。アセンブラーが提供するコンピューターへのアクセスレベルのためです。
アセンブラーは、環境の初期ロード、 スタックの動作、 コンテキストの切り替えに必要です。 ちなみに、Goでは、ゴルーチン間の切り替えもアセンブラで実装されています。 また、パフォーマンスの問題もあります。コンパイラの結果よりも効率的に動作するコードを手動で作成できる場合があります。 たとえば、Go標準ライブラリの数学/大きなパッケージの大部分はアセンブラで記述されています。コンパイラをバイパスして、それよりも優れたものを実装すると、このライブラリの基本的な手順がはるかに効率的になるためです。 アセンブラーは、新しい珍しいデバイス機能や、高水準言語では使用できない機能(最新のプロセッサーの新しい暗号化命令など)を操作するために必要になる場合があります。
しかし、私にとって、アセンブリ言語で最も重要なことは、多くの人がコンピューターについてそれについて考えることです。 アセンブリ言語は、コンピューター操作の原則である一連の命令のアイデアを提供します。 アセンブラーでプログラムを作成しない場合でも(ここには何もないことを願っています)、コンピューターの動作を理解するためだけにアセンブラーに精通している必要があります。 ただし、アセンブリ言語自体にあまり注意を払うことはありません。これは私のレポートの考え方と一致しています。
多くの異なるアセンブラー
あなたの多くは、アセンブリ言語にあまり詳しくありません。 したがって、年代順を順守しようとして、いくつかの例を挙げます。
IBMシステム/ 360
1 PRINT NOGEN 2 STOCK1 START 0 3 BEGIN BALR 11,0 4 USING *,11 5 MVC NEWOH,OLDOH 6 AP NEWOH,RECPT 7 AP NEWOH,ISSUE 8 EOJ 11 OLDOH DC PL4'9' 12 RECPT DC PL4'4' 13 ISSUE DC PL4'6' 14 NEWOH DS PL4 15 END BEGIN
これはIBM System / 360アセンブラーです。 引用が最初にあったのはこのコンピューターについてでした。 意味に注意を払うのではなく、見てください。
そして、これは全体像を示すことです。
Apollo 11ガイダンスコンピューター
# TO ENTER A JOB REQUEST REQUIRING NO VAC AREA: COUNT 02/EXEC NOVAC INHINT AD FAKEPRET # LOC(MPAC +6) - LOC(QPRET) TS NEWPRIO # PRIORITY OF NEW JOB + NOVAC C(FIXLOC) EXTEND INDEX Q # Q WILL BE UNDISTURBED THROUGHOUT. DCA 0 # 2CADR OF JOB ENTERED. DXCH NEWLOC CAF EXECBANK XCH FBANK TS EXECTEM1 TCF NOVAC2 # ENTER EXECUTIVE BANK.
これは、Apollo 11オンボード制御コンピューター用のアセンブラーコードです。 彼のプログラムはすべて完全にアセンブラーで書かれています。 アセンブラーは私たちが月に行くのを助けてくれました。
PDP-10
TITLE COUNT A=1 ;Define a name for an accumulator. START: MOVSI A,-100 ;initialize loop counter. ;A contains -100,,0 LOOP: HRRZM A,TABLE(A) ;Use right half of A to index. AOBJN A,LOOP ;Add 1 to both halves (-77,,1 -76,,2 etc.) ;Jump if still negative. .VALUE ;Halt program. TABLE: BLOCK 100 ;Assemble space to fill up. END START ;End the assembly.
これはPDP-10のアセンブラーであり、他の例と比較すると非常にコメントされています。
PDP-11
/ a3 -- pdp-11 assembler pass 1 assem: jsr pc,readop jsr pc,checkeos br ealoop tst ifflg beq 3f cmp r4,$200 blos assem cmpb (r4),$21 /if bne 2f inc ifflg 2: cmpb (r4),$22 /endif bne assem dec ifflg br assem
これはPDP-11のスニペットです。 さらに、これはUnix v6のアセンブラー用のコードです-もちろん、アセンブラーで書かれています。 C言語は後で使用され始めました。
モトローラ68000
strtolower public link a6,#0 ;Set up stack frame movea 8(a6),a0 ;A0 = src, from stack movea 12(a6),a1 ;A1 = dst, from stack loop move.b (a0)+,d0 ;Load D0 from (src) cmpi #'A',d0 ;If D0 < 'A', blo copy ;skip cmpi #'Z',d0 ;If D0 > 'Z', bhi copy ;skip addi #'a'-'A',d0 ;D0 = lowercase(D0) copy move.b d0,(a1)+ ;Store D0 to (dst) bne loop ;Repeat while D0 <> NUL unlk a6 ;Restore stack frame rts ;Return end
Cray-1
ident slice V6 0 ; initialize S A4 S0 ; initialize *x A5 S1 ; initialize *y A3 S2 ; initialize i loop S0 A3 JSZ exit ; if S0 == 0 goto exit VL A3 ; set vector length V11 ,A4,1 ; load slice of x[i], stride 1 V12 ,A5,1 ; load slice of y[i], stride 1 V13 V11 *F V12 ; slice of x[i] * y[i] V6 V6 +F V13 ; partial sum A14 VL ; get vector length of this iteration A4 A4 + A14 ; *x = *x + VL A5 A5 + A14 ; *y = *y + VL A3 A3 - A14 ; i = i - VL J loop exit
これは、Motorola 68000用のアセンブラーであり、Cray-1用です。 私はこの例が好きです;それはロバート・グリーゼマーの論文からです。 それがすべて始まった方法を思い出してください。

RobertGriseméerがGopherCon 2015に出演
これらはすべて異なる言語であることに気付くかもしれませんが、いくつかの点で似ています。共通の非常に明確な構造を持っています。
説明書
subroutine header label: instruction operand... ; comment ...
オペランド
register literal constant address register indirection (register as address) ...
通常、アセンブラー・プログラムは列に書き込まれます。左側にはラベルがあり、次に命令、オペランド、そして最後に右側にコメントがあります。 オペランドは通常、レジスタ、定数、またはメモリアドレスですが、異なるアーキテクチャに対して構文的には非常に似ています。 例外があります。 Crayの例は背景に対して際立っています。加算コマンドは、算術式のように+記号として記述されています。 しかし、意味はどこでも同じです。これは追加コマンドであり、これらはレジスタです。 つまり、これはすべて実際には同じことです。
言いかえれば、昔でも、私の時代でさえ、プロセッサーは現在とほぼ同じでした。 反例もありますが、詳細に注意を払わなければ、実際にはほとんどのプロセッサ(およびGoが実行されるすべてのプロセッサ)は同じままです。 詳細に触れない場合、興味深い結論に達することができます。これらすべてのコンピューターについて、共通の文法を作成できます。 この事実を理解するのに約30年かかりました。
ケンの素晴らしいアイデア

左からロバート・グリスメイヤー、ロブ・パイク、ケン・トンプソン
1980年代半ば頃、ケントンプソンと私は開発について考え始め、それがプラン9に変わりました。 Kenは新しいCコンパイラを作成しました。これはGoツールのCコンパイラの基礎を形成し、最近まで使用されていました。 National 32000プロセッサを使用したSequent対称マルチプロセッサコンピュータ上にありました。 私の意見では、これは集積回路として市販されている最初の32ビットマイクロプロセッサでした。 しかし、ケンは1つの興味深いことをしました-ある人はそれを理解しませんでしたが、それは非常に重要であることが判明しました。 これは一般的にケンの特徴的な機能です。
コンパイラは機械語命令を生成しませんでした-擬似コードのようなものを生成しました。 次に、2番目のプログラム(実際にはリンカー )がコンパイラーの結果を取得し、これらの疑似命令を実際の命令に変換しました。
命令のような
MOVW $0, var
ちょうどなる可能性があります
XORW R1, R1 STORE R1, var
抽象的な例を挙げます。 たとえば、変数にゼロを設定するMOVW命令があります。 コンピュータで実行されるリンカによって発行されるコードは、たとえば、両方のオペランドとして同じレジスタが指定されている(つまり、レジスタ値をリセットする)XORW命令と、このレジスタの値を変数に入れるSTOREで構成されます。 詳細については心配しないでください。ここでのポイントは、コンピューターが実行する命令がアセンブラーで入力したものと正確に一致しない場合があるということです。
疑似命令に基づいて実際の命令をコンパイルするこのプロセスは、命令選択と呼ばれるものです。 疑似命令のすばらしい例は、ケンがRETを呼び出した関数からのreturnステートメントです。 30年前から呼ばれていますが、その実装は実行するコンピューターによって異なります。 一部のコンピューターのマニュアルではRETと呼ばれていますが、別のコンピューターでは、レジスターに含まれるアドレスへの移行、特殊レジスターのアドレスへのリダイレクト、またはまったく別のことができます。
したがって、アセンブラは、コンパイラが生成する疑似命令を記述する方法と見なすことができます。 プラン9の世界では、他のほとんどのアーキテクチャとは異なり、コンパイラーはアセンブラーを実行しません。データはリンカーに直接転送されます。

その結果、プロセスは次のようになります。 一番上の行は、従来のアーキテクチャにほぼ対応しています。 私の意見では、GCCは今日と同じように機能します。 これはコンパイラであり、高レベルのコードを受け取り、アセンブリ言語コードに変換します。 アセンブラーは実際の命令を生成し、リンカーは個々のパーツをリンクしてバイナリを作成します。 下の2行は、プラン9の状況を示しています。実際、アセンブラーは半分に分割されています。半分はコンパイラーに残り、2番目はリンカーの一部になりました。 赤い線を横切る矢印は、バイナリ表現の疑似命令のストリームです。 Plan 9のアセンブラーの目的の1つは、リンカーが処理する疑似命令に変換するテキスト形式の命令を作成する機能を提供することです。
アセンブラーゴー
この状況は、多くの世代にわたって続いています。 また、Plan 9アセンブラーもそれに対応していました。これらは、アーキテクチャごとに独自のYacc文法を持つ個別のCプログラムでした。 それらは独特のセットまたはパッケージを形成しましたが、これらは別個の独立して書かれたプログラムでした。 彼らは共通のコードを持っていましたが、完全ではありません...一般的に、すべてが複雑でした。 その後、Goが表示され始めました... 2007年に... Goコンパイラ-8gと6gがこの面白い名前のプログラム群に追加されました。 そして、彼らは同じモデルを使用しました。 これらは図の中央の線に対応しています。 また、この分離の実装方法は、内部Goデバイスに多くの利点をもたらしましたが、今日は詳しく説明する時間がありません。

ラスコックスがGopherCon 2015でパフォーマンス
Go 1.3では、すべてのCコードを取り除き、すべてをGoのみに実装したかったのです。 しばらく時間がかかります。 しかし、Go 1.3のリリースでは、このプロセスを開始しました。 すべては、Russ( Russ Cox )がリンカーの大部分を取り、それを分離したという事実から始まりました。 これがliblinkライブラリの出現方法であり、そのほとんどはいわゆる命令選択アルゴリズムによって占有されていました。 現在、このライブラリはobjと呼ばれていますが、その後はliblinkと呼ばれていました。 コンパイラーは、このliblinkライブラリーを使用して、疑似命令を実際の命令に変換しました。 この決定を支持するいくつかの議論がありました。
これらの中で最も重要なのは、組み立てプロセスの高速化です。 コンパイラは現在より多くの作業を行っていますが、今では命令を選択していますが、リンカはこれを実行していました。 このようなデバイスのおかげで、彼はこれを各ライブラリに対して1回だけ実行しています。 以前は、たとえばfmtパッケージをビルドする必要がある場合、 毎回Printfの指示の選択が再度実行されていました。 明らかに、これは愚かです。 ここで一度実行すると、リンカーはこれを実行する必要がなくなります。 その結果、コンパイラーは遅くなりますが、アセンブリー全体は速くなります。 アセンブラも同じ方法で構築でき、objライブラリを使用できます。
このステージの最も重要な機能は、ユーザーにとって何も変わっていないことです。入力言語は同じままで、出力は同じままで、それでも同じバイナリファイルであり、詳細のみが異なります。 古いアーキテクチャの一般的な概要を次に示します。コンパイラ、アセンブラ、リンカ。 プラン9の世界では、リンカーはコンパイラーまたはアセンブラーによって制御されます。
古いアーキテクチャ:

新しいアーキテクチャ:

バージョン1.3では、このようなデバイスに切り替えました。 ご覧のとおり、今でははるかに伝統的なリンカがあります:命令の選択を実行するobjライブラリはリンカの一部ではなくなり、コンパイラとアセンブラの最終段階になったため、実際の命令を受け取ります。 つまり、プロセスにアセンブラーを含めると、アセンブラーとコンパイラーは古いスキームとの整合性が向上します。
この新しいGoアセンブラは奇妙なものです。 単純に何もありません。 彼は何をしていますか? 疑似命令のテキスト記述をリンカの実際の命令に変換します。 1.5では、大きな一歩を踏み出しました-Cを取り除きました。このため、1.3および1.4で多くの準備作業を行い、この問題はようやく解決されました。 Russは、Cで作成されたGoコンパイラー(および、ちなみにリンカー)の古いソースコードをGoプログラムに変換するトランスレーターを作成しました。
古いliblinkライブラリはライブラリセットに組み込まれており、ここではまとめてobjと呼びます。 その結果、objと呼ばれるものがあります。これは移植可能な部分であり、各アーキテクチャの機能に関する情報を保存するマシン依存の部分を持つサブディレクトリです。 いくつかのレポートがこの作業に当てられています。これ自体は興味深い話です。 2年前、ラスはGopherConでプレゼンテーションを行いましたが、彼はすでにかなり時代遅れです。 実際、私たちは彼が当時言ったことを正確にはしませんでした。 そして、GopherFest 2015で、バージョン1.5の変更のより一般的でより正確な概要を紹介しました。
GOOS=darwin GOARCH=arm go tool compile prog.go
だからここに。 コンパイラは、6g、8g、およびその他の奇妙な名前と呼ばれていました。 これは、コンパイルと呼ばれる1つのプログラムです。 コンパイルツールを実行し、標準のGOOS環境変数(「goose」を「goose」と発音)およびGOARCH(「gorch」を「酔っぱらった、バーの貪欲な男」と発音)の値を設定して調整します。これが名前の正式な発音です。 リンカについても同じことをしました。 リンクツールがあり、GOOSとGOARCHの値を設定します-プログラムをコンパイルできます。 「1つのコンパイラでこれらすべてのアーキテクチャをどのようにサポートできますか?」 クロスプラットフォームのコンパイルが非常に難しいことは誰もが知っています。」 実際、いいえ。 事前にすべてを準備する必要があります。 これに注意してください。入力言語はGoだけです。 コンパイラの観点から見ると、結果も同じです。バイナリ形式の疑似命令がobjライブラリに転送されます。 つまり、ツールの起動時に変数の値を設定するだけで、objライブラリを構成する必要があります。 あなたはすぐにそれを行う方法を学びます。
アセンブラについては、CからGoへのアセンブラの機械翻訳を実行しましたが、これは理想的なソリューションではなく、私はそれが好きではありませんでした。 私はGoをゼロから書くことを、それらすべてを置き換える唯一のプログラム、asmを提案しました。 セットアップは、GOOSとGOARCHを介してのみ行われます。 アセンブリ言語とGoは同じものではないことに気付くかもしれません。 各プロセッサには、独自の命令セット、独自のレジスタセットがあり、これは単一の出力言語ではありません。 それをどうしますか? しかし、実際には、それらは本質的に同じです。 ご覧ください。
package add func add(a, b int) int { return a + b }
以下は、2つの整数を加算して合計を返す簡単なプログラムの例です。 -Sフラグを使用してアセンブラコードを表示した場合にコンパイラが提供する擬似命令は表示しません。 また、多くの余分なものを削除しました-この方法を使用すると、多くの不要なものも表示されますが、この段階でコンパイラーはまさにそのような疑似命令を生成します。
32ビットx86(386)
TEXT add(SB), $0-12 MOVL a+4(FP), BX ADDL b+8(FP), BX MOVL BX, 12(FP) RET
64ビットx86(amd64)
TEXT add(SB), $0-24 MOVQ b+16(FP), AX MOVQ a+8(FP), CX ADDQ CX, AX MOVQ AX, 24(FP) RET
32ビットアーム
TEXT add(SB), $-4-12 MOVW a(FP), R0 MOVW b+4(FP), R1 ADD R1, R0 MOVW R0, 8(FP) RET
64ビットアーム(arm64)
TEXT add(SB), $-8-24 MOVD a(FP), R0 MOVD b+8(FP), R1 ADD R1, R0 MOVD R0, 16(FP) RET
S390(s390x)
TEXT add(SB), $0-24 MOVD a(FP), R1 MOVD b+8(FP), R2 ADD R2, R1, R1 MOVD R1, 16(FP) RET
32ビットアーキテクチャのオプションを次に示します。 詳細については考えないでください。 全体像を見てください。 64ビットx86アーキテクチャの結果は、 AMD64とも呼ばれ、32ビットARMアーキテクチャ 、64ビットARMアーキテクチャ、およびIBM System / 390アーキテクチャの結果です。 私たちにとっては新しいものですが、他のすべてにとっては明らかにそうではありません。
64ビットMIPS(mips64)
TEXT add(SB), $-8-24 MOVV a(FP), R1 MOVV b+8(FP), R2 ADDVU R2, R1 MOVV R1, 16(FP) RET
64ビットの電源(ppc64le)
TEXT add(SB), $0-24 MOVD a(FP), R2 MOVD b+8(FP), R3 ADD R3, R2 MOVD R2, 16(FP) RET
64ビットMIPSアーキテクチャのコードは次のとおりです。64ビットPOWERアーキテクチャは次のとおりです。 似ていることに気付くかもしれません。 その理由は、実際には、それらがまったく同じ言語であるためです。 その理由の1つは、そのように配置されていることです。実際、National 32000アセンブラーを30年間使用し、使用されたハードウェアのみを変更しました。 しかし、それらのいくつかは本当に同一だからです。 これらは、命令、レジスタ、オペランド、定数値、ラベルにすぎません-すべて同じです。 唯一の重要な違いは、命令とレジスタの名前が異なることです。 オフセットも異なる場合がありますが、機械語のサイズによって異なります。
それはすべて、Kenが書いたNational 32000アセンブラーに要約されています。 これは、Kenが想像するように、最新のPowerPCに適合したNational 32000アセンブリ言語です。 したがって、必要なものすべて-共通の入力言語、バックエンドのobjライブラリ-があり、アセンブラーで記述できます。 このアプローチでは、問題が発生します。NationalまたはPowerPCのリーダーシップを取り、アセンブラー言語を見ると、そのようには見えないことがわかります。 あるレベルでは実際には疑似命令であるため、構文は異なり、命令名も異なる場合があります。 実際、問題ではありません。
初心者には、Goアセンブラーの外観(これらすべての大文字と奇妙なもの)が深刻に混乱する可能性があります。 しかし、これらすべてのコンピューターに共通のアセンブリ言語があるため、以下で説明するすばらしい結果を得ることができます。 したがって、これは正当な妥協案であり、それを達成することはそれほど難しくないと考えています。 68000およびアセンブラーKenでプログラミングすることを学ぶ価値があります。また、PowerPC用のプログラムを自動的に作成できます。 違いは何ですか?
どのように機能しますか? アセンブラーバージョン1.5では、アセンブラーが理想的だと考えています。 彼にコンピューターを渡してください-そして彼は彼のためにアセンブラーを翻訳します。 これは完全にGoで書かれた新しいプログラムです。 一般的なレキシカルアナライザーと構文アナライザーがあり、入力コード、ユーザーが提供するすべてのものを取得し、命令をバイナリ形式の命令を記述するデータ構造に変換してから、特定のプラットフォームに関する情報を含む新しいobjライブラリに結果を転送します。
アセンブラの基礎となるコードのほとんどは完全に移植可能であり、アーキテクチャに関する興味深い情報は何も含まれていませんが、レジスタ名に関する情報を含むテーブルがあります。 オペランドの操作に関連するものがまだいくつかありますが、非常に簡単です。 そして、これらはすべて、GOARCH変数の値に従ってプログラムが開始されるときに構成されます。 GOOSは、掘り下げない非常にまれなケースで使用されます。主な特性はGOARCHによって決定されます。 archと呼ばれるアセンブラ用の内部パッケージもあります。これらのテーブルはオンザフライで作成され、objライブラリから動的に抽出されます。 そして、これが実際のコードのスニペットです。
import ( "cmd/internal/obj" "cmd/internal/obj/x86" ) func archX86(linkArch *obj.LinkArch) *Arch { register := make(map[string]int16) // Create maps for easy lookup of instruction names etc. for i, s := range x86.Register { register[s] = int16(i + x86.REG_AL) } instructions := make(map[string]obj.As) for i, s := range obj.Anames { instructions[s] = x86.As(i) } return &Arch{ Instructions: instructions, Register: register, ... } }
少し簡略化されていますが、本質を伝えています。 これは、内部アセンブラパッケージであるarchです。 これは、x86アーキテクチャ用にアセンブラ全体を構成する手順です。 このコードは、32ビットと64ビットの両方のアーキテクチャで使用されますが、この観点からは同一です。 そして、ループを開始します... objプロシージャ用にx86パッケージで定義されたobjライブラリからレジスタ名を反復処理するループ。 そして、objからのデータに従ってレジスタ名とバイナリに一致するマップを設定するだけです。 そして、指示についても同じことを行います。
これらのコードは実際には命令コードではなく、マニュアルには記載されていません。 これはすべてのものの文字通りアルファベット順のリストです-これらは単なる命令であり、実際のコマンドではないことを繰り返します。 しかし、今ではすべてのレジスタの名前とすべての命令がわかっています。 そして、返すアーキテクチャの説明には、これらの2つのマップのみが含まれます。 ここにあなたが知っている命令の名前、ここにあなたが知っているレジスタの名前、そして私が言及していないいくつかの事柄がありますが、すべてはそこでとても簡単です。 これは、アセンブリ言語をこれらのコンピューターの指示に変換するために必要なすべての情報です。実際、パーサーは単に文字列のマッチングを行って正しい命令を見つけます。列1にあり、単語が含まれています。これは命令です。命令テーブルで探しています。とても簡単です。
ADDW AX, BX &obj.Prog{ As: arch.Instructions["ADDW"], From: obj.Addr{Reg: arch.Register["AX"]}, To: obj.Addr{Reg: arch.Register["BX"]}, ... }
以下に例を示します。 « » (ADDW) 386, . : ADDW AX, BX. , . , , — , , ADDW . A — , . , . — AX, BX. , , : , , obj. , . , , — . .
, , . . , , , … , , , . これは問題ではありません。
, obj. : , , obj, , , obj , . , obj , . .
, , , , . . , , , … obj , , . , , , . . , , . それは、一般に、すべてです。 A/B- — . .
386, – AMD64. , . , PowerPC, – . , , , . , obj, .
-, . , Go, Yacc. , . . Go, , . , , , . , . , . . . . .
, , , , , open-source-. Git , , , , , . これは素晴らしい。 -, . , open-source- .
, , , , obj . ? . , , … — , pprof, — -, , , , , . , PDF , . , , — ? . , . . , .
— , . . . . , , . , PDF , – . .
, , , , .
おわりに
. , , . , . , , . , , . , . — , . — , — . , , , .
Go. ありがとう