KDOC 54: Goのiotaの仕組みを見る

Go言語にはiotaというキーワードがある。enum的なものを実現したいときによく使う機能で、整数を自動的に割り振ってくれる。

例えば、↓のように、Languageというグループとして定数をまとめて定義できる。実際に割り当てられている値が何か意識しなくてよい。

type Language int
const (
        Japanese Language = iota
        English
        Chinese
)

これをどうやっているのか、iotaの仕組みについて調べる。まず生成されるアセンブリコードを比較してみる↓。

package main
import "fmt"
func main() {
        const a = iota + 999
        fmt.Print(a)
}
# command-line-arguments
main.main STEXT size=105 args=0x0 locals=0x50 funcid=0x0 align=0x0
	0x0000 00000 (/tmp/babel-u4OtHM/1acYKX.go:3)	TEXT	main.main(SB), ABIInternal, $80-0
	0x0000 00000 (/tmp/babel-u4OtHM/1acYKX.go:3)	CMPQ	SP, 16(R14)
	0x0004 00004 (/tmp/babel-u4OtHM/1acYKX.go:3)	PCDATA	$0, $-2
	0x0004 00004 (/tmp/babel-u4OtHM/1acYKX.go:3)	JLS	98
	0x0006 00006 (/tmp/babel-u4OtHM/1acYKX.go:3)	PCDATA	$0, $-1
	0x0006 00006 (/tmp/babel-u4OtHM/1acYKX.go:3)	PUSHQ	BP
	0x0007 00007 (/tmp/babel-u4OtHM/1acYKX.go:3)	MOVQ	SP, BP
	0x000a 00010 (/tmp/babel-u4OtHM/1acYKX.go:3)	SUBQ	$72, SP
	0x000e 00014 (/tmp/babel-u4OtHM/1acYKX.go:3)	FUNCDATA	$0, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/1acYKX.go:3)	FUNCDATA	$1, gclocals·/9is4gq24Q1h4ArwHiu1tg==(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/1acYKX.go:3)	FUNCDATA	$2, main.main.stkobj(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVUPS	X15, main..autotmp_0+56(SP)
	0x0014 00020 (/tmp/babel-u4OtHM/1acYKX.go:5)	LEAQ	main..autotmp_0+56(SP), AX
	0x0019 00025 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	AX, main..autotmp_2+24(SP)
	0x001e 00030 (/tmp/babel-u4OtHM/1acYKX.go:5)	LEAQ	type:int(SB), DX
	0x0025 00037 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	DX, main..autotmp_0+56(SP)
	0x002a 00042 (/tmp/babel-u4OtHM/1acYKX.go:5)	LEAQ	main..stmp_0(SB), DX
	0x0031 00049 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	DX, main..autotmp_0+64(SP)
	0x0036 00054 (/tmp/babel-u4OtHM/1acYKX.go:5)	JMP	56
	0x0038 00056 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	AX, main..autotmp_1+32(SP)
	0x003d 00061 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	$1, main..autotmp_1+40(SP)
	0x0046 00070 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	$1, main..autotmp_1+48(SP)
	0x004f 00079 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVL	$1, BX
	0x0054 00084 (/tmp/babel-u4OtHM/1acYKX.go:5)	MOVQ	BX, CX
	0x0057 00087 (/tmp/babel-u4OtHM/1acYKX.go:5)	PCDATA	$1, $0
	0x0057 00087 (/tmp/babel-u4OtHM/1acYKX.go:5)	CALL	fmt.Print(SB)
	0x005c 00092 (/tmp/babel-u4OtHM/1acYKX.go:6)	ADDQ	$72, SP
	0x0060 00096 (/tmp/babel-u4OtHM/1acYKX.go:6)	POPQ	BP
	0x0061 00097 (/tmp/babel-u4OtHM/1acYKX.go:6)	RET
	0x0062 00098 (/tmp/babel-u4OtHM/1acYKX.go:6)	NOP
	0x0062 00098 (/tmp/babel-u4OtHM/1acYKX.go:3)	PCDATA	$1, $-1
	0x0062 00098 (/tmp/babel-u4OtHM/1acYKX.go:3)	PCDATA	$0, $-2
	0x0062 00098 (/tmp/babel-u4OtHM/1acYKX.go:3)	CALL	runtime.morestack_noctxt(SB)
	0x0067 00103 (/tmp/babel-u4OtHM/1acYKX.go:3)	PCDATA	$0, $-1
	0x0067 00103 (/tmp/babel-u4OtHM/1acYKX.go:3)	JMP	0
	0x0000 49 3b 66 10 76 5c 55 48 89 e5 48 83 ec 48 44 0f  I;f.v\UH..H..HD.
	0x0010 11 7c 24 38 48 8d 44 24 38 48 89 44 24 18 48 8d  .|$8H.D$8H.D$.H.
	0x0020 15 00 00 00 00 48 89 54 24 38 48 8d 15 00 00 00  .....H.T$8H.....
	0x0030 00 48 89 54 24 40 eb 00 48 89 44 24 20 48 c7 44  .H.T$@..H.D$ H.D
	0x0040 24 28 01 00 00 00 48 c7 44 24 30 01 00 00 00 bb  $(....H.D$0.....
	0x0050 01 00 00 00 48 89 d9 e8 00 00 00 00 48 83 c4 48  ....H.......H..H
	0x0060 5d c3 e8 00 00 00 00 eb 97                       ]........
	rel 2+0 t=R_USEIFACE type:int+0
	rel 33+4 t=R_PCREL type:int+0
	rel 45+4 t=R_PCREL main..stmp_0+0
	rel 88+4 t=R_CALL fmt.Print+0
	rel 99+4 t=R_CALL runtime.morestack_noctxt+0
go:cuinfo.producer.main SDWARFCUINFO dupok size=0
	0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69              -N -l regabi
go:cuinfo.packagename.main SDWARFCUINFO dupok size=0
	0x0000 6d 61 69 6e                                      main
main..inittask SNOPTRDATA size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+0 t=R_INITORDER fmt..inittask+0
main..stmp_0 SRODATA static size=8
	0x0000 e7 03 00 00 00 00 00 00                          ........
runtime.memequal64·f SRODATA dupok size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+8 t=R_ADDR runtime.memequal64+0
runtime.gcbits.0100000000000000 SRODATA dupok size=8
	0x0000 01 00 00 00 00 00 00 00                          ........
type:.namedata.*[1]interface {}- SRODATA dupok size=18
	0x0000 00 10 2a 5b 31 5d 69 6e 74 65 72 66 61 63 65 20  ..*[1]interface
	0x0010 7b 7d                                            {}
runtime.nilinterequal·f SRODATA dupok size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+8 t=R_ADDR runtime.nilinterequal+0
runtime.gcbits.0200000000000000 SRODATA dupok size=8
	0x0000 02 00 00 00 00 00 00 00                          ........
type:[1]interface {} SRODATA dupok size=72
	0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
	0x0010 6e df 95 c2 02 08 08 11 00 00 00 00 00 00 00 00  n...............
	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0040 01 00 00 00 00 00 00 00                          ........
	rel 24+8 t=R_ADDR runtime.nilinterequal·f+0
	rel 32+8 t=R_ADDR runtime.gcbits.0200000000000000+0
	rel 40+4 t=R_ADDROFF type:.namedata.*[1]interface {}-+0
	rel 44+4 t=RelocType(-32763) type:*[1]interface {}+0
	rel 48+8 t=R_ADDR type:interface {}+0
	rel 56+8 t=R_ADDR type:[]interface {}+0
type:*[1]interface {} SRODATA dupok size=56
	0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
	0x0010 a8 f1 a8 c9 08 08 08 36 00 00 00 00 00 00 00 00  .......6........
	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0030 00 00 00 00 00 00 00 00                          ........
	rel 24+8 t=R_ADDR runtime.memequal64·f+0
	rel 32+8 t=R_ADDR runtime.gcbits.0100000000000000+0
	rel 40+4 t=R_ADDROFF type:.namedata.*[1]interface {}-+0
	rel 48+8 t=R_ADDR type:[1]interface {}+0
gclocals·g5+hNtRBP6YXNjfog7aZjQ== SRODATA dupok size=8
	0x0000 01 00 00 00 00 00 00 00                          ........
gclocals·/9is4gq24Q1h4ArwHiu1tg== SRODATA dupok size=9
	0x0000 01 00 00 00 06 00 00 00 00                       .........
main.main.stkobj SRODATA static size=24
	0x0000 01 00 00 00 00 00 00 00 f0 ff ff ff 10 00 00 00  ................
	0x0010 10 00 00 00 00 00 00 00                          ........
	rel 20+4 t=R_ADDROFF runtime.gcbits.0200000000000000+0
package main
import "fmt"
func main() {
        fmt.Print(999)
}
# command-line-arguments
main.main STEXT size=105 args=0x0 locals=0x50 funcid=0x0 align=0x0
	0x0000 00000 (/tmp/babel-u4OtHM/rJMaJY.go:3)	TEXT	main.main(SB), ABIInternal, $80-0
	0x0000 00000 (/tmp/babel-u4OtHM/rJMaJY.go:3)	CMPQ	SP, 16(R14)
	0x0004 00004 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PCDATA	$0, $-2
	0x0004 00004 (/tmp/babel-u4OtHM/rJMaJY.go:3)	JLS	98
	0x0006 00006 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PCDATA	$0, $-1
	0x0006 00006 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PUSHQ	BP
	0x0007 00007 (/tmp/babel-u4OtHM/rJMaJY.go:3)	MOVQ	SP, BP
	0x000a 00010 (/tmp/babel-u4OtHM/rJMaJY.go:3)	SUBQ	$72, SP
	0x000e 00014 (/tmp/babel-u4OtHM/rJMaJY.go:3)	FUNCDATA	$0, gclocals·g5+hNtRBP6YXNjfog7aZjQ==(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/rJMaJY.go:3)	FUNCDATA	$1, gclocals·/9is4gq24Q1h4ArwHiu1tg==(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/rJMaJY.go:3)	FUNCDATA	$2, main.main.stkobj(SB)
	0x000e 00014 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVUPS	X15, main..autotmp_0+56(SP)
	0x0014 00020 (/tmp/babel-u4OtHM/rJMaJY.go:4)	LEAQ	main..autotmp_0+56(SP), AX
	0x0019 00025 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	AX, main..autotmp_2+24(SP)
	0x001e 00030 (/tmp/babel-u4OtHM/rJMaJY.go:4)	LEAQ	type:int(SB), DX
	0x0025 00037 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	DX, main..autotmp_0+56(SP)
	0x002a 00042 (/tmp/babel-u4OtHM/rJMaJY.go:4)	LEAQ	main..stmp_0(SB), DX
	0x0031 00049 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	DX, main..autotmp_0+64(SP)
	0x0036 00054 (/tmp/babel-u4OtHM/rJMaJY.go:4)	JMP	56
	0x0038 00056 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	AX, main..autotmp_1+32(SP)
	0x003d 00061 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	$1, main..autotmp_1+40(SP)
	0x0046 00070 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	$1, main..autotmp_1+48(SP)
	0x004f 00079 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVL	$1, BX
	0x0054 00084 (/tmp/babel-u4OtHM/rJMaJY.go:4)	MOVQ	BX, CX
	0x0057 00087 (/tmp/babel-u4OtHM/rJMaJY.go:4)	PCDATA	$1, $0
	0x0057 00087 (/tmp/babel-u4OtHM/rJMaJY.go:4)	CALL	fmt.Print(SB)
	0x005c 00092 (/tmp/babel-u4OtHM/rJMaJY.go:5)	ADDQ	$72, SP
	0x0060 00096 (/tmp/babel-u4OtHM/rJMaJY.go:5)	POPQ	BP
	0x0061 00097 (/tmp/babel-u4OtHM/rJMaJY.go:5)	RET
	0x0062 00098 (/tmp/babel-u4OtHM/rJMaJY.go:5)	NOP
	0x0062 00098 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PCDATA	$1, $-1
	0x0062 00098 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PCDATA	$0, $-2
	0x0062 00098 (/tmp/babel-u4OtHM/rJMaJY.go:3)	CALL	runtime.morestack_noctxt(SB)
	0x0067 00103 (/tmp/babel-u4OtHM/rJMaJY.go:3)	PCDATA	$0, $-1
	0x0067 00103 (/tmp/babel-u4OtHM/rJMaJY.go:3)	JMP	0
	0x0000 49 3b 66 10 76 5c 55 48 89 e5 48 83 ec 48 44 0f  I;f.v\UH..H..HD.
	0x0010 11 7c 24 38 48 8d 44 24 38 48 89 44 24 18 48 8d  .|$8H.D$8H.D$.H.
	0x0020 15 00 00 00 00 48 89 54 24 38 48 8d 15 00 00 00  .....H.T$8H.....
	0x0030 00 48 89 54 24 40 eb 00 48 89 44 24 20 48 c7 44  .H.T$@..H.D$ H.D
	0x0040 24 28 01 00 00 00 48 c7 44 24 30 01 00 00 00 bb  $(....H.D$0.....
	0x0050 01 00 00 00 48 89 d9 e8 00 00 00 00 48 83 c4 48  ....H.......H..H
	0x0060 5d c3 e8 00 00 00 00 eb 97                       ]........
	rel 2+0 t=R_USEIFACE type:int+0
	rel 33+4 t=R_PCREL type:int+0
	rel 45+4 t=R_PCREL main..stmp_0+0
	rel 88+4 t=R_CALL fmt.Print+0
	rel 99+4 t=R_CALL runtime.morestack_noctxt+0
go:cuinfo.producer.main SDWARFCUINFO dupok size=0
	0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69              -N -l regabi
go:cuinfo.packagename.main SDWARFCUINFO dupok size=0
	0x0000 6d 61 69 6e                                      main
main..inittask SNOPTRDATA size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+0 t=R_INITORDER fmt..inittask+0
main..stmp_0 SRODATA static size=8
	0x0000 e7 03 00 00 00 00 00 00                          ........
runtime.memequal64·f SRODATA dupok size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+8 t=R_ADDR runtime.memequal64+0
runtime.gcbits.0100000000000000 SRODATA dupok size=8
	0x0000 01 00 00 00 00 00 00 00                          ........
type:.namedata.*[1]interface {}- SRODATA dupok size=18
	0x0000 00 10 2a 5b 31 5d 69 6e 74 65 72 66 61 63 65 20  ..*[1]interface
	0x0010 7b 7d                                            {}
runtime.nilinterequal·f SRODATA dupok size=8
	0x0000 00 00 00 00 00 00 00 00                          ........
	rel 0+8 t=R_ADDR runtime.nilinterequal+0
runtime.gcbits.0200000000000000 SRODATA dupok size=8
	0x0000 02 00 00 00 00 00 00 00                          ........
type:[1]interface {} SRODATA dupok size=72
	0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
	0x0010 6e df 95 c2 02 08 08 11 00 00 00 00 00 00 00 00  n...............
	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0040 01 00 00 00 00 00 00 00                          ........
	rel 24+8 t=R_ADDR runtime.nilinterequal·f+0
	rel 32+8 t=R_ADDR runtime.gcbits.0200000000000000+0
	rel 40+4 t=R_ADDROFF type:.namedata.*[1]interface {}-+0
	rel 44+4 t=RelocType(-32763) type:*[1]interface {}+0
	rel 48+8 t=R_ADDR type:interface {}+0
	rel 56+8 t=R_ADDR type:[]interface {}+0
type:*[1]interface {} SRODATA dupok size=56
	0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
	0x0010 a8 f1 a8 c9 08 08 08 36 00 00 00 00 00 00 00 00  .......6........
	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0030 00 00 00 00 00 00 00 00                          ........
	rel 24+8 t=R_ADDR runtime.memequal64·f+0
	rel 32+8 t=R_ADDR runtime.gcbits.0100000000000000+0
	rel 40+4 t=R_ADDROFF type:.namedata.*[1]interface {}-+0
	rel 48+8 t=R_ADDR type:[1]interface {}+0
gclocals·g5+hNtRBP6YXNjfog7aZjQ== SRODATA dupok size=8
	0x0000 01 00 00 00 00 00 00 00                          ........
gclocals·/9is4gq24Q1h4ArwHiu1tg== SRODATA dupok size=9
	0x0000 01 00 00 00 06 00 00 00 00                       .........
main.main.stkobj SRODATA static size=24
	0x0000 01 00 00 00 00 00 00 00 f0 ff ff ff 10 00 00 00  ................
	0x0010 10 00 00 00 00 00 00 00                          ........
	rel 20+4 t=R_ADDROFF runtime.gcbits.0200000000000000+0

↑これらは全く同じ結果になるので、iotaは事前に値を展開していることがわかる。Goではインスタンスや実行するまで値が決まらないものはconstに指定できないので、この動作は予想できる。少なくともGoアセンブリに変換する直前では単純なintになっているのだ。

コードを見てみる。↓constの宣言部分を処理する部分を見る。constはカッコを使ってグループで宣言でき、そのグループごとにループを回す。その1回分の処理が以下だ。グループごとにforループからとったindexがあって、enumの初期値に基づいてiotaの値がどうなるか計算している。だからiotaを1はじまりとかにできるわけだ。

case *syntax.ConstDecl:
	top := len(check.delayed)

	// iota is the index of the current constDecl within the group
	if first < 0 || s.Group == nil || list[index-1].(*syntax.ConstDecl).Group != s.Group {
		first = index
		last = nil
	}
	iota := constant.MakeInt64(int64(index - first))

↓そして、作成したローカル変数iotaでconstを初期化する。対応した初期化式が与えられていれば、そちらで上書きする。

for i, name := range s.NameList {
	obj := NewConst(name.Pos(), pkg, name.Value, nil, iota)
	lhs[i] = obj

	var init syntax.Expr
	if i < len(values) {
		init = values[i]
	}

	check.constDecl(obj, last.Type, init, inherited)
}

↓初期化式initを評価して、その結果を x に設定する。 x を使って、constの値を設定、初期化する。iotaキーワードが使われていれば初期化式initはnilのはずなので、評価が行われず x は変化しない。

var x operand
if init != nil {
	if inherited {
		// The initialization expression is inherited from a previous
		// constant declaration, and (error) positions refer to that
		// expression and not the current constant declaration. Use
		// the constant identifier position for any errors during
		// init expression evaluation since that is all we have
		// (see issues go.dev/issue/42991, go.dev/issue/42992).
		check.errpos = obj.pos
	}
	check.expr(nil, &x, init)
}
check.initConst(obj, &x)

x の値を使って値をセットする。

lhs.val = x.val

↓ちなみにCheckerは、型チェックの状態を保持する構造体である。

// A Checker maintains the state of the type checker.
// It must be created with NewChecker.
type Checker struct {

つまり、constにiotaキーワードを使った場合、型チェックの過程で実際の値が決まる、という感じになっている。

関連

  • 追加調査: 理解を試すため、境界的事例を試してみる。文法上有効だが直感に反する部分を探す