20201120のGoに関する記事は3件です。

Goのdeferの仕組みをきちんと理解する

deferって関数の最後で実行させたい処理に使うんでしょ、程度に考えていたが、きちんと理解をしようとdeferについて深堀りしてみた。

deferについてざっくり説明

  • deferで処理を記述しておくとfuncの最後で実行してくれる。
  • 以下の例だと、"今日は"が実行され、funcの終わりになるので、その後"金曜日です。"が実行される。
func hogehoge() {
    defer fmt.Println("金曜日です。")
    fmt.Println("今日は....")
}
今日は....
金曜日です。

ちょっと深堀り

Tour of Goの説明によると

  1. 囲まれているfunctionがreturnされるまで実行が見送られる。
  2. defer呼び出しの引数は即時に評価されるが、実行がreturnされるまで見送られる。

との説明がある。
1はなんとなく理解できるが、2の"即時に評価される"という記述が気になったのであとで詳しく解説したい。

また、プログラミング言語Goの説明によると

  1. deferを使うことでリソースの開放などがきれいに記述できる。
  2. deferの正しい位置は資源の獲得に成功した直後

という旨の説明がある。
以下が例である。

resp, err := http.Get(url)
defer resp.Body.Close() //本当はdeferの前にエラー判定いれるべき

Javaとかだと関数の最後にclose()を記述したりすることが多く、たまーにclose()を書き忘れたりすることもあるが、
goであればdefer使ってリソースを取得した直後に開放処理入れておくのが推奨されている。

即時に評価される、とは?

実際にコードを見たほうが早いと思うので、以下のサンプルを見て欲しい。

func main() {
    defer today()()
    fmt.Println("今日は....")
}

func today() func() {
    fmt.Println("金曜日ですか?")
    return func() {fmt.Println("それとも土曜日ですか?")}
}

これを実行するとどうなるだろうか?
deferはmainが終わる時に呼び出されるから、今日は...がまず呼び出され、次にtoday()内の金曜日ですか?、最後にそれとも土曜日ですか?という流れるになるかと思うかもしれない。

しかし、実行してみると

金曜日ですか?
今日は....
それとも土曜日ですか?

となるのである。さてなぜだろう?
ポイントはtoday()の返り値が関数型である点だ。

前項のdefer呼び出しの引数は即時に評価されるが、という部分に注目して欲しい。

defer today()()の記述の部分で評価が実施されるのである。(なんで括弧が2つあるのか?と一瞬思う方もいるかもしれないが、話がそれるので最後に解説したい)

なので、

fmt.Println("金曜日ですか?")

が実行される。その後、main内の処理に進み、

fmt.Println("今日は....")

が実行される。

その後、遅延実行で

fmt.Println("それとも土曜日ですか?")

という流れである。

defer today()()の括弧が2つあるのはなぜ??

括弧2ついらないんじゃない??と思うひともいるかもしれないが、これは必要である。ためしに括弧を1つで実施するとどうなるだろうか?

func main() {
    defer today()
    fmt.Println("今日は....")
}

func today() func() {
    fmt.Println("金曜日ですか?")
    return func() {fmt.Println("それとも土曜日ですか?")}
}
今日は....
金曜日ですか?

順番が変わった上に、土曜日が消えている。。。。

defer today() //1
defer today()() //2

この2つの記述は根本的に違うのである。

1はあくまで、deferの処理でtoday関数を実行する。つまり、main関数の最後にtoday()が実行されるのである。この処理では無名関数がreturnされているが、無名関数は実行されないということなのである。

それに対し、2はtoday()でリターンされた関数をmain関数の最後に実施するのである。わかりやすく記述すると2はdeferの処理で無名関数func() {fmt.Println("それとも土曜日ですか?")}を実行しているのである。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

制限時間付き標準入力

プログラムを書いていて一度は制限時間のある、標準入力を作りたいと思ったことはあると思います。因みに僕はありません。
以外と簡単に Go で実現できたのでメモがてら書きます。いつかの自分のためですね。

方法

方法はいたって簡単で、標準入力を受け取るサブルーチンを作って、入力テキストをチャンネルで受け取るだけです。

in := make(chan string, 1)
go func() {
    sc := bufio.NewScanner(os.Stdin)
    sc.Scan()
    in <- sc.Text()
}()

上記のチァンネルと time.NewTimertime.NewTicker を組み合わせることで簡単に実現できます。

timer := time.NewTimer(time.Second * 10)
select {
  case text := <-in:
    // 入力を使った処理
  case <-timer.C:
    // タイムアウト時の処理
}

適当に作ったサンプル

上記の方法で適当に作ったサンプルです。ローカルで実行してみると雰囲気を掴めると思います。

package main

import (
    "bufio"
    "fmt"
    "os"
    "time"
)

func main() {
    in := make(chan string, 1)
    go func() {
        sc := bufio.NewScanner(os.Stdin)
        sc.Scan()
        in <- sc.Text()
    }()

    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    fmt.Println("制限時間は5秒。坊っちゃんの作者を入力して")
    for i := 0; i < 5; i++ {
        select {
        case text := <-in:
            if text == "夏目漱石" {
                fmt.Printf("入力できてすごい\n")
            } else {
                fmt.Printf("間違えているよ\n%s\n", text)
            }
            i = 5
        case <-ticker.C:
            switch i {
            case 0:
                fmt.Println("制限時間あるので早めに")
            case 1:
                fmt.Println("はやくはやく")
            case 2:
                fmt.Println("邪魔だと思う?")
            case 3:
                fmt.Println("僕もそう思う")
            case 4:
                fmt.Println("ゲームオーバー(笑)")
            }
        }
    }
}

締め

簡単でしたが、いつ使うんでしょうね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"go build"した時に何が起きているのか?

この記事は How “go build” Works の翻訳記事です。

go buildはどのようにして最も単純なGolangプログラムをコンパイルしているのでしょうか?

この記事ではその疑問を解消することを目標にしています。

最も単純な以下のプログラムについて考えてみましょう。

// main.go
package main

func main() {}

go build main.goを実行すると、1.1Mbの実行可能ファイルmainが出力され、何も実行されません。 この何もしないバイナリを作成するために、go buildは何をしたのでしょうか?

go buildコマンドはいくつかの便利なオプションを提供しています。

  1. -work: go buildは、作業ファイル用の一時フォルダーを作成します。 この引数は、そのフォルダーの場所を出力し、ビルド後に削除しません
  2. -a: Golangは、以前にビルドされたパッケージをキャッシュします。 -ago buildにキャッシュを無視させるので、ビルドはすべてのステップを出力します
  3. -p 1: これにより、処理が単一スレッドで行われるように設定され、出力が線形にログに記録されます
  4. -x: go buildは、compileなどの他のGolangツールのラッパーです。 -xは、これらのツールに送信されるコマンドと引数を出力します

go build -work -a -p 1 -x main.goを実行するとmainだけでなく大量のログが出て、buildmainを作成する際に行われていることを我々に教えてくれます。

ログはまず以下の内容を出力します。

WORK=/var/folders/rw/gtb29xf92fv23f0zqsg42s840000gn/T/go-build940616988

これは、構造が次のような作業ディレクトリです。

├── b001
│   ├── _pkg_.a
│   ├── exe
│   ├── importcfg
│   └── importcfg.link
├── b002
│   └── ...
├── b003
│   └── ...
├── b004
│   └── ...
├── b006
│   └── ...
├── b007
│   └── ...
└── b008
    └── ...

go buildは、完了する必要のあるタスクのアクショングラフを定義します。

このグラフの各アクションは、独自のサブディレクトリ(NewObjdirで定義)を取得します。

グラフの最初のノードb001は、メインバイナリをコンパイルするためのルートタスクです。

依存する各アクションの数は大きく、最後はb008です。 (b005がどこに行ったのかわかりませんが問題ないと思われるので割愛します)

b008

実行される最初のアクションは、グラフの末端のb008です。

mkdir -p $WORK/b008/

cat >$WORK/b008/importcfg << 'EOF'
# import config
EOF

cd /<..>/src/runtime/internal/sys

/<..>/compile 
  -o $WORK/b008/_pkg_.a 
  -trimpath "$WORK/b008=>" 
  -p runtime/internal/sys 
  -std 
  -+ 
  -complete 
  -buildid gEtYPexVP43wWYWCxFKi/gEtYPexVP43wWYWCxFKi 
  -goversion go1.14.7 
  -D "" 
  -importcfg $WORK/b008/importcfg 
  -pack 
  -c=16 
  ./arch.go ./arch_amd64.go ./intrinsics.go ./intrinsics_common.go ./stubs.go ./sys.go ./zgoarch_amd64.go ./zgoos_darwin.go ./zversion.go

/<..>/buildid -w $WORK/b008/_pkg_.a

cp $WORK/b008/_pkg_.a /<..>/Caches/go-build/01/01b...60a-d

b008では

  1. アクションディレクトリを作成します(すべてのアクションがこれを行うため、以降ではこの記述を省略します)
  2. ツールcompileで使用するimportcfgファイルを作成します(空です)
  3. ディレクトリをruntime/internal/sysパッケージのソースフォルダに変更します。 このパッケージには、ランタイムで使用される定数が含まれています
  4. パッケージをコンパイルします
  5. buildidを使用してメタデータをパッケージに書き込み(-w)、パッケージをgo-buildキャッシュにコピーします(すべてのパッケージがキャッシュされるため、以降ではこの記述を省略します)

これをツールcompileに送信された引数に分解してみましょう(go tool compile --helpでも説明されています)。

  1. -o 出力先のファイル
  2. -trimpath ソースファイルのパスからprefix$WORK/b008=>を取り除く
  3. -p importで使われるパッケージ名をセット
  4. -std compiling standard library(現時点ではよくわかりませんでした)
  5. -+ compiling runtime(これもわかりませんでした)
  6. -complete コンパイラはCまたはアセンブリではなく完全なパッケージを出力
  7. -buildid メタデータにbuild idを付与する
  8. -goversion コンパイルされたパッケージに必要なバージョン
  9. -D ローカルなインポートで使う相対パスは""
  10. -importcfg インポート設定ファイルは他のパッケージを参照
  11. -pack パッケージをオブジェクトファイル.oではなくアーカイブ.aとして作る
  12. -c ビルド時にどれだけ並列で処理するか
  13. パッケージ内のファイルのリスト

これらの引数のほとんどはすべてのcompileコマンドで同じなので、以降ではこの記述を省略します。

b008の出力はruntime/internal/sysに対応している$WORK/b008/_pkg_.aというアーカイブファイルです。

buildid

buildidとは何かを説明しましょう。

buildidの形式は<actionid>/<contentid>です。

これは、パッケージをキャッシュして go buildのパフォーマンスを向上させるためのインデックスとして使用されます。

<actionid>は、アクション(すべての呼び出し、引数、および入力ファイル)のハッシュです。 <contentid>は、出力 .aファイルのハッシュです。

go buildアクションごとに、同じ<actionid>を持つ別のアクションによって作成されたコンテンツをキャッシュで検索できます。

これは buildid.goに実装されています。

buildidはメタデータとしてファイルに保存されるため、<contentid>を取得するために毎回ハッシュする必要はありません。 このIDは、 go tool buildid <file>で確認できます(バイナリでも機能します)。

上記の b008のログでは、buildIDcompileツールによってgEtYPexVP43wWYWCxFKi/gEtYPexVP43wWYWCxFKiとして設定されています。

これは単なるプレースホルダーであり、後でキャッシュされる前に、 go tool buildid -wで正しいgEtYPexVP43wWYWCxFKi/b-rPboOuD0POrlJWPTEiに上書きされます。

b007

次はb007です

cat >$WORK/b007/importcfg << 'EOF'
# import config
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF

cd /<..>/src/runtime/internal/math

/<..>/compile 
  -o $WORK/b007/_pkg_.a 
  -p runtime/internal/math 
  -importcfg $WORK/b007/importcfg 
  ...
  ./math.go
  1. packagefile runtime/internal/sys=$WORK/b008/_pkg_.aと書かれたimportcfgを作成しています これはb007b008に依存していることを示します
  2. runtime/internal/mathをコンパイル math.goの中身を覗いてみると確かにb008で作られたruntime/internal/sysをインポートしています

b007の出力はruntime/internal/mathに対応している$WORK/b007/_pkg_.aというアーカイブファイルです。

b006

cat >$WORK/b006/go_asm.h << 'EOF'
EOF

cd /<..>/src/runtime/internal/atomic

/<..>/asm 
  -I $WORK/b006/ 
  -I /<..>/go/1.14.7/libexec/pkg/include 
  -D GOOS_darwin 
  -D GOARCH_amd64 
  -gensymabis 
  -o $WORK/b006/symabis 
  ./asm_amd64.s

/<..>/asm 
  -I $WORK/b006/ 
  -I /<..>/go/1.14.7/libexec/pkg/include 
  -D GOOS_darwin 
  -D GOARCH_amd64 
  -o $WORK/b006/asm_amd64.o 
  ./asm_amd64.s

cat >$WORK/b006/importcfg << 'EOF'
# import config
EOF

/<..>/compile 
  -o $WORK/b006/_pkg_.a 
  -p runtime/internal/atomic 
  -symabis $WORK/b006/symabis 
  -asmhdr $WORK/b006/go_asm.h 
  -importcfg $WORK/b006/importcfg
  ...
  ./atomic_amd64.go ./stubs.go

/<..>/pack r $WORK/b006/_pkg_.a $WORK/b006/asm_amd64.o

ここで、通常の.goファイルから抜け出し、低レベルのGoアセンブリ.sファイルの処理を開始します。

  1. ヘッダファイルgo_asm.hを作成
  2. 低レベルな関数が集まったruntime/internal/atomicパッケージに移動
  3. ツールgo tool asmgo tool asm --helpで説明)を実行して、symabis "Symbol Application Binary Interfaces(ABI)ファイル" を作成し、次にオブジェクトファイルasm_amd64.oを作成
  4. compileを使用して、symabisファイルと -asmhdrを含むヘッダーを含む_pkg_.aファイルを作成
  5. packコマンドでasm_amd64.o_pkg_.aの中に加える

asmツールはここでは以下の引数を伴って呼び出されています。

  1. -I: アクション b007およびlibexec/pkg/includesフォルダーを含めます。 includesには3つのファイルasm_ppc64x.hfuncdata.htextflag.hがあり、すべて低レベルの関数定義があります。 例えば、FIXED_FRAMEは、スタックフレームの固定部分のサイズを定義します
  2. -D: 事前定義されたシンボルを含める
  3. -gensymabis: symabisファイルを作成する
  4. -o: 出力先のファイル

b006の出力はruntime/internal/atomicに対応している$WORK/b006/_pkg_.aというアーカイブファイルです。

b004

cd /<..>/src/internal/cpu

/<..>/asm ... -o $WORK/b004/symabis ./cpu_x86.s
/<..>/asm ... -o $WORK/b004/cpu_x86.o ./cpu_x86.s

/<..>/compile ... -o $WORK/b004/_pkg_.a ./cpu.go ./cpu_amd64.go ./cpu_x86.go

/<..>/pack r $WORK/b004/_pkg_.a $WORK/b004/cpu_x86.o

b004は対象がinternal/cpuに変わった以外はb006と同様です。

最初にsymabisとオブジェクトファイルをcpu_x86.sをアセンブルして作成して、goファイルをコンパイルした後、それらを合わせて、アーカイブ_pkg_.aを作成します。

b004の出力はinternal/cpuに対応している$WORK/b004/_pkg_.aというアーカイブファイルです。

b003

cat >$WORK/b003/go_asm.h << 'EOF'
EOF

cd /<..>/src/internal/bytealg

/<..>/asm ... -o $WORK/b003/symabis ./compare_amd64.s ./count_amd64.s ./equal_amd64.s ./index_amd64.s ./indexbyte_amd64.s

cat >$WORK/b003/importcfg << 'EOF'
# import config
packagefile internal/cpu=$WORK/b004/_pkg_.a
EOF

/<..>/compile ... -o $WORK/b003/_pkg_.a -p internal/bytealg ./bytealg.go ./compare_native.go ./count_native.go ./equal_generic.go ./equal_native.go ./index_amd64.go ./index_native.go ./indexbyte_native.go

/<..>/asm ... -o $WORK/b003/compare_amd64.o ./compare_amd64.s
/<..>/asm ... -o $WORK/b003/count_amd64.o ./count_amd64.s
/<..>/asm ... -o $WORK/b003/equal_amd64.o ./equal_amd64.s
/<..>/asm ... -o $WORK/b003/index_amd64.o ./index_amd64.s
/<..>/asm ... -o $WORK/b003/indexbyte_amd64.o ./indexbyte_amd64.s

/<..>/pack r $WORK/b003/_pkg_.a $WORK/b003/compare_amd64.o $WORK/b003/count_amd64.o $WORK/b003/equal_amd64.o $WORK/b003/index_amd64.o $WORK/b003/indexbyte_amd64.o

b003もやることはb004b006と同じです。

このパッケージの主な問題は、多くのオブジェクトファイル.oを作成するために複数の.sファイルがあり、それぞれを_pkg_.aファイルに追加する必要があることです。

b003の出力はinternal/bytealgに対応している$WORK/b003/_pkg_.aというアーカイブファイルです。

b002

cat >$WORK/b002/go_asm.h << 'EOF'
EOF

cd /<..>/src/runtime

/<..>/asm 
  ... 
  -o $WORK/b002/symabis 
  ./asm.s ./asm_amd64.s ./duff_amd64.s ./memclr_amd64.s ./memmove_amd64.s ./preempt_amd64.s ./rt0_darwin_amd64.s ./sys_darwin_amd64.s

cat >$WORK/b002/importcfg << 'EOF'
# import config
packagefile internal/bytealg=$WORK/b003/_pkg_.a
packagefile internal/cpu=$WORK/b004/_pkg_.a
packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a
packagefile runtime/internal/math=$WORK/b007/_pkg_.a
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF

/<..>/compile 
  -o $WORK/b002/_pkg_.a 
  ...
  -p runtime 
  ./alg.go ./atomic_pointer.go ./cgo.go ./cgocall.go ./cgocallback.go ./cgocheck.go ./chan.go ./checkptr.go ./compiler.go ./complex.go ./cpuflags.go ./cpuflags_amd64.go ./cpuprof.go ./cputicks.go ./debug.go ./debugcall.go ./debuglog.go ./debuglog_off.go ./defs_darwin_amd64.go ./env_posix.go ./error.go ./extern.go ./fastlog2.go ./fastlog2table.go ./float.go ./hash64.go ./heapdump.go ./iface.go ./lfstack.go ./lfstack_64bit.go ./lock_sema.go ./malloc.go ./map.go ./map_fast32.go ./map_fast64.go ./map_faststr.go ./mbarrier.go ./mbitmap.go ./mcache.go ./mcentral.go ./mem_darwin.go ./mfinal.go ./mfixalloc.go ./mgc.go ./mgcmark.go ./mgcscavenge.go ./mgcstack.go ./mgcsweep.go ./mgcsweepbuf.go ./mgcwork.go ./mheap.go ./mpagealloc.go ./mpagealloc_64bit.go ./mpagecache.go ./mpallocbits.go ./mprof.go ./mranges.go ./msan0.go ./msize.go ./mstats.go ./mwbbuf.go ./nbpipe_pipe.go ./netpoll.go ./netpoll_kqueue.go ./os_darwin.go ./os_nonopenbsd.go ./panic.go ./plugin.go ./preempt.go ./preempt_nonwindows.go ./print.go ./proc.go ./profbuf.go ./proflabel.go ./race0.go ./rdebug.go ./relax_stub.go ./runtime.go ./runtime1.go ./runtime2.go ./rwmutex.go ./select.go ./sema.go ./signal_amd64.go ./signal_darwin.go ./signal_darwin_amd64.go ./signal_unix.go ./sigqueue.go ./sizeclasses.go ./slice.go ./softfloat64.go ./stack.go ./string.go ./stubs.go ./stubs_amd64.go ./stubs_nonlinux.go ./symtab.go ./sys_darwin.go ./sys_darwin_64.go ./sys_nonppc64x.go ./sys_x86.go ./time.go ./time_nofake.go ./timestub.go ./trace.go ./traceback.go ./type.go ./typekind.go ./utf8.go ./vdso_in_none.go ./write_err.go

/<..>/asm ... -o $WORK/b002/asm.o ./asm.s
/<..>/asm ... -o $WORK/b002/asm_amd64.o ./asm_amd64.s
/<..>/asm ... -o $WORK/b002/duff_amd64.o ./duff_amd64.s
/<..>/asm ... -o $WORK/b002/memclr_amd64.o ./memclr_amd64.s
/<..>/asm ... -o $WORK/b002/memmove_amd64.o ./memmove_amd64.s
/<..>/asm ... -o $WORK/b002/preempt_amd64.o ./preempt_amd64.s
/<..>/asm ... -o $WORK/b002/rt0_darwin_amd64.o ./rt0_darwin_amd64.s
/<..>/asm ... -o $WORK/b002/sys_darwin_amd64.o ./sys_darwin_amd64.s

/<..>/pack r $WORK/b002/_pkg_.a $WORK/b002/asm.o $WORK/b002/asm_amd64.o $WORK/b002/duff_amd64.o $WORK/b002/memclr_amd64.o $WORK/b002/memmove_amd64.o $WORK/b002/preempt_amd64.o $WORK/b002/rt0_darwin_amd64.o $WORK/b002/sys_darwin_amd64.o

b002を見ればこれまでのアクションがなぜ必要だったのかわかります。

b002はGoのバイナリの実行に必要なruntimeパッケージ全てを含んでいます。例えば、b002には mgc.goというGoのGCの実装も含まれています。これはb004(internal/cpu)とb006(runtime/internal/atomic)をインポートしています。

b002はコアライブラリの中では最も複雑なパッケージかもしれませんが、ビルド自体は今までと同じ工程で行われます。つまりasmcompileして出力されたファイルをpackして_pkg_.aにしています。

b002の出力はruntimeに対応している$WORK/b002/_pkg_.aというアーカイブファイルです。

b001

cat >$WORK/b001/importcfg << 'EOF'
# import config
packagefile runtime=$WORK/b002/_pkg_.a
EOF

cd /<..>/main

/<..>/compile ... -o $WORK/b001/_pkg_.a -p main ./main.go

cat >$WORK/b001/importcfg.link << 'EOF'
packagefile command-line-arguments=$WORK/b001/_pkg_.a
packagefile runtime=$WORK/b002/_pkg_.a
packagefile internal/bytealg=$WORK/b003/_pkg_.a
packagefile internal/cpu=$WORK/b004/_pkg_.a
packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a
packagefile runtime/internal/math=$WORK/b007/_pkg_.a
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF

/<..>/link 
  -o $WORK/b001/exe/a.out 
  -importcfg $WORK/b001/importcfg.link 
  -buildmode=exe 
  -buildid=yC-qrh2sY_qI0zh2-NE7/owNzOBTqPO00FkqK0_lF/HPXqvMz_4PvKsQzqGWgD/yC-qrh2sY_qI0zh2-NE7 
  -extld=clang 
  $WORK/b001/_pkg_.a

mv $WORK/b001/exe/a.out main

First it builds an importcfg that includes runtime built in b002 to then compile main.go to pkg.a

  1. 最初に、b002runtimeを含むようにしてimportcfgを作成した後、main.goをコンパイルして_pkg_.aを作成します。
  2. 以前に登場した全パッケージに加えてcommand-line-arguments=$WORK/b001/_pkg_.aを含むimportcfg.linkを作成したら、linkコマンドでそれらをリンクして実行ファイルを作成します。
  3. 最後にmainにリネームして出力先に移動します。

linkの引数の補足をしておきましょう。

  1. -buildmode: 実行ファイルをビルドする
  2. -extld: 外部のリンカを参照する

ようやくお目当てのものが手に入りました。

b001から生まれるのがmainバイナリです。

Bazelとの類似点

効率的なキャッシュを実現するためのアクショングラフの作成は、Bazelが高速ビルドに使用するビルドツールと同じアイデアです。

Golangの actionidcontentidは、Bazelがキャッシュで使用するactioncachecontent-addressable store(CAS)に対応しています。

BazelはGoogleの製品であり、Golangも同様です。 彼らがソフトウェアを迅速そして正確にビルドする方法について同様の哲学をもったことは非常に合理的と言えるでしょう。

Bazelの rules_goパッケージでは、builderコードで go buildを再実装する方法を確認できます。

アクショングラフ、フォルダ管理、およびキャッシュはBazelによって外部で処理されるため、これは非常にクリーンな実装です。

次のステップへ

go buildは、今回のような何もしないプログラムでも、それをコンパイルするために多くのことを行っていました。

ツール( compile asm)やその入力ファイルと出力ファイル(.a .o .s)については今回はあまり詳しく説明しませんでした。

また、今回は最も基本的なプログラムをコンパイルしているだけです。

次のようにしてコンパイルをもっと複雑なものにできます。

  1. 他のパッケージをインポートする 例えばHello worldを出力するためfmtをインポートするとさらに23個のアクションがアクショングラフに追加される
  2. 外部パッケージを参照するためにgo.modを使用する
  3. GOOSGOARCHの値を変えて他のアーキテクチャ向けにビルドする 例えば、wasm向けのコンパイルではアクションや引数の内容が全く異なってくる

go buildを実行してログを検査することは、Goコンパイラがどのように機能するかを学ぶためのアプローチとしてはトップダウンなアプローチです。 基礎から学びたい場合は次のようなリソースに飛び込むのに最適な出発点です。

  1. Introduction to the Go compiler
  2. Go: Overview of the Compiler
  3. Go at Google: Language Design in the Service of Software Engineering
  4. build.go
  5. compile/main.go

References

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む