http2/hpack: build static table with go generate

As the static version of headerFieldTable in hpack is generated in runtime,
we may use the go:generate to prepare the struct before the initialization phase.
This is supposed to save init time and allocations for many binaries,
as net/http imports hpack.

Before:
init golang.org/x/net/http2/hpack @1.1 ms, 0.097 ms clock, 21240 bytes, 29 allocs
After:
init golang.org/x/net/http2/hpack @0.67 ms, 0.015 ms clock, 8120 bytes, 9 allocs

Fixes golang/go#55881

Change-Id: Ia6575f67ffcba7cc4d75899b24a9c56deb58ccac
Reviewed-on: https://go-review.googlesource.com/c/net/+/434875
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
diff --git a/http2/hpack/gen.go b/http2/hpack/gen.go
new file mode 100644
index 0000000..de14ab0
--- /dev/null
+++ b/http2/hpack/gen.go
@@ -0,0 +1,184 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+// +build ignore
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"io/ioutil"
+	"os"
+	"sort"
+
+	"golang.org/x/net/http2/hpack"
+)
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
+var staticTableEntries = [...]hpack.HeaderField{
+	{Name: ":authority"},
+	{Name: ":method", Value: "GET"},
+	{Name: ":method", Value: "POST"},
+	{Name: ":path", Value: "/"},
+	{Name: ":path", Value: "/index.html"},
+	{Name: ":scheme", Value: "http"},
+	{Name: ":scheme", Value: "https"},
+	{Name: ":status", Value: "200"},
+	{Name: ":status", Value: "204"},
+	{Name: ":status", Value: "206"},
+	{Name: ":status", Value: "304"},
+	{Name: ":status", Value: "400"},
+	{Name: ":status", Value: "404"},
+	{Name: ":status", Value: "500"},
+	{Name: "accept-charset"},
+	{Name: "accept-encoding", Value: "gzip, deflate"},
+	{Name: "accept-language"},
+	{Name: "accept-ranges"},
+	{Name: "accept"},
+	{Name: "access-control-allow-origin"},
+	{Name: "age"},
+	{Name: "allow"},
+	{Name: "authorization"},
+	{Name: "cache-control"},
+	{Name: "content-disposition"},
+	{Name: "content-encoding"},
+	{Name: "content-language"},
+	{Name: "content-length"},
+	{Name: "content-location"},
+	{Name: "content-range"},
+	{Name: "content-type"},
+	{Name: "cookie"},
+	{Name: "date"},
+	{Name: "etag"},
+	{Name: "expect"},
+	{Name: "expires"},
+	{Name: "from"},
+	{Name: "host"},
+	{Name: "if-match"},
+	{Name: "if-modified-since"},
+	{Name: "if-none-match"},
+	{Name: "if-range"},
+	{Name: "if-unmodified-since"},
+	{Name: "last-modified"},
+	{Name: "link"},
+	{Name: "location"},
+	{Name: "max-forwards"},
+	{Name: "proxy-authenticate"},
+	{Name: "proxy-authorization"},
+	{Name: "range"},
+	{Name: "referer"},
+	{Name: "refresh"},
+	{Name: "retry-after"},
+	{Name: "server"},
+	{Name: "set-cookie"},
+	{Name: "strict-transport-security"},
+	{Name: "transfer-encoding"},
+	{Name: "user-agent"},
+	{Name: "vary"},
+	{Name: "via"},
+	{Name: "www-authenticate"},
+}
+
+type pairNameValue struct {
+	name, value string
+}
+
+type byNameItem struct {
+	name string
+	id   uint64
+}
+
+type byNameValueItem struct {
+	pairNameValue
+	id uint64
+}
+
+func headerFieldToString(f hpack.HeaderField) string {
+	return fmt.Sprintf("{Name: \"%s\", Value:\"%s\", Sensitive: %t}", f.Name, f.Value, f.Sensitive)
+}
+
+func pairNameValueToString(v pairNameValue) string {
+	return fmt.Sprintf("{name: \"%s\", value:\"%s\"}", v.name, v.value)
+}
+
+const header = `
+// go generate gen.go
+// Code generated by the command above; DO NOT EDIT.
+
+package hpack
+
+var staticTable = &headerFieldTable{
+	evictCount: 0,
+	byName: map[string]uint64{
+`
+
+//go:generate go run gen.go
+func main() {
+	var bb bytes.Buffer
+	fmt.Fprintf(&bb, header)
+	byName := make(map[string]uint64)
+	byNameValue := make(map[pairNameValue]uint64)
+	for index, entry := range staticTableEntries {
+		id := uint64(index) + 1
+		byName[entry.Name] = id
+		byNameValue[pairNameValue{entry.Name, entry.Value}] = id
+	}
+	// Sort maps for deterministic generation.
+	byNameItems := sortByName(byName)
+	byNameValueItems := sortByNameValue(byNameValue)
+
+	for _, item := range byNameItems {
+		fmt.Fprintf(&bb, "\"%s\":%d,\n", item.name, item.id)
+	}
+	fmt.Fprintf(&bb, "},\n")
+	fmt.Fprintf(&bb, "byNameValue: map[pairNameValue]uint64{\n")
+	for _, item := range byNameValueItems {
+		fmt.Fprintf(&bb, "%s:%d,\n", pairNameValueToString(item.pairNameValue), item.id)
+	}
+	fmt.Fprintf(&bb, "},\n")
+	fmt.Fprintf(&bb, "ents: []HeaderField{\n")
+	for _, value := range staticTableEntries {
+		fmt.Fprintf(&bb, "%s,\n", headerFieldToString(value))
+	}
+	fmt.Fprintf(&bb, "},\n")
+	fmt.Fprintf(&bb, "}\n")
+	genFile("static_table.go", &bb)
+}
+
+func sortByNameValue(byNameValue map[pairNameValue]uint64) []byNameValueItem {
+	var byNameValueItems []byNameValueItem
+	for k, v := range byNameValue {
+		byNameValueItems = append(byNameValueItems, byNameValueItem{k, v})
+	}
+	sort.Slice(byNameValueItems, func(i, j int) bool {
+		return byNameValueItems[i].id < byNameValueItems[j].id
+	})
+	return byNameValueItems
+}
+
+func sortByName(byName map[string]uint64) []byNameItem {
+	var byNameItems []byNameItem
+	for k, v := range byName {
+		byNameItems = append(byNameItems, byNameItem{k, v})
+	}
+	sort.Slice(byNameItems, func(i, j int) bool {
+		return byNameItems[i].id < byNameItems[j].id
+	})
+	return byNameItems
+}
+
+func genFile(name string, buf *bytes.Buffer) {
+	b, err := format.Source(buf.Bytes())
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := ioutil.WriteFile(name, b, 0644); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
diff --git a/http2/hpack/static_table.go b/http2/hpack/static_table.go
new file mode 100644
index 0000000..754a1eb
--- /dev/null
+++ b/http2/hpack/static_table.go
@@ -0,0 +1,188 @@
+// go generate gen.go
+// Code generated by the command above; DO NOT EDIT.
+
+package hpack
+
+var staticTable = &headerFieldTable{
+	evictCount: 0,
+	byName: map[string]uint64{
+		":authority":                  1,
+		":method":                     3,
+		":path":                       5,
+		":scheme":                     7,
+		":status":                     14,
+		"accept-charset":              15,
+		"accept-encoding":             16,
+		"accept-language":             17,
+		"accept-ranges":               18,
+		"accept":                      19,
+		"access-control-allow-origin": 20,
+		"age":                         21,
+		"allow":                       22,
+		"authorization":               23,
+		"cache-control":               24,
+		"content-disposition":         25,
+		"content-encoding":            26,
+		"content-language":            27,
+		"content-length":              28,
+		"content-location":            29,
+		"content-range":               30,
+		"content-type":                31,
+		"cookie":                      32,
+		"date":                        33,
+		"etag":                        34,
+		"expect":                      35,
+		"expires":                     36,
+		"from":                        37,
+		"host":                        38,
+		"if-match":                    39,
+		"if-modified-since":           40,
+		"if-none-match":               41,
+		"if-range":                    42,
+		"if-unmodified-since":         43,
+		"last-modified":               44,
+		"link":                        45,
+		"location":                    46,
+		"max-forwards":                47,
+		"proxy-authenticate":          48,
+		"proxy-authorization":         49,
+		"range":                       50,
+		"referer":                     51,
+		"refresh":                     52,
+		"retry-after":                 53,
+		"server":                      54,
+		"set-cookie":                  55,
+		"strict-transport-security":   56,
+		"transfer-encoding":           57,
+		"user-agent":                  58,
+		"vary":                        59,
+		"via":                         60,
+		"www-authenticate":            61,
+	},
+	byNameValue: map[pairNameValue]uint64{
+		{name: ":authority", value: ""}:                   1,
+		{name: ":method", value: "GET"}:                   2,
+		{name: ":method", value: "POST"}:                  3,
+		{name: ":path", value: "/"}:                       4,
+		{name: ":path", value: "/index.html"}:             5,
+		{name: ":scheme", value: "http"}:                  6,
+		{name: ":scheme", value: "https"}:                 7,
+		{name: ":status", value: "200"}:                   8,
+		{name: ":status", value: "204"}:                   9,
+		{name: ":status", value: "206"}:                   10,
+		{name: ":status", value: "304"}:                   11,
+		{name: ":status", value: "400"}:                   12,
+		{name: ":status", value: "404"}:                   13,
+		{name: ":status", value: "500"}:                   14,
+		{name: "accept-charset", value: ""}:               15,
+		{name: "accept-encoding", value: "gzip, deflate"}: 16,
+		{name: "accept-language", value: ""}:              17,
+		{name: "accept-ranges", value: ""}:                18,
+		{name: "accept", value: ""}:                       19,
+		{name: "access-control-allow-origin", value: ""}:  20,
+		{name: "age", value: ""}:                          21,
+		{name: "allow", value: ""}:                        22,
+		{name: "authorization", value: ""}:                23,
+		{name: "cache-control", value: ""}:                24,
+		{name: "content-disposition", value: ""}:          25,
+		{name: "content-encoding", value: ""}:             26,
+		{name: "content-language", value: ""}:             27,
+		{name: "content-length", value: ""}:               28,
+		{name: "content-location", value: ""}:             29,
+		{name: "content-range", value: ""}:                30,
+		{name: "content-type", value: ""}:                 31,
+		{name: "cookie", value: ""}:                       32,
+		{name: "date", value: ""}:                         33,
+		{name: "etag", value: ""}:                         34,
+		{name: "expect", value: ""}:                       35,
+		{name: "expires", value: ""}:                      36,
+		{name: "from", value: ""}:                         37,
+		{name: "host", value: ""}:                         38,
+		{name: "if-match", value: ""}:                     39,
+		{name: "if-modified-since", value: ""}:            40,
+		{name: "if-none-match", value: ""}:                41,
+		{name: "if-range", value: ""}:                     42,
+		{name: "if-unmodified-since", value: ""}:          43,
+		{name: "last-modified", value: ""}:                44,
+		{name: "link", value: ""}:                         45,
+		{name: "location", value: ""}:                     46,
+		{name: "max-forwards", value: ""}:                 47,
+		{name: "proxy-authenticate", value: ""}:           48,
+		{name: "proxy-authorization", value: ""}:          49,
+		{name: "range", value: ""}:                        50,
+		{name: "referer", value: ""}:                      51,
+		{name: "refresh", value: ""}:                      52,
+		{name: "retry-after", value: ""}:                  53,
+		{name: "server", value: ""}:                       54,
+		{name: "set-cookie", value: ""}:                   55,
+		{name: "strict-transport-security", value: ""}:    56,
+		{name: "transfer-encoding", value: ""}:            57,
+		{name: "user-agent", value: ""}:                   58,
+		{name: "vary", value: ""}:                         59,
+		{name: "via", value: ""}:                          60,
+		{name: "www-authenticate", value: ""}:             61,
+	},
+	ents: []HeaderField{
+		{Name: ":authority", Value: "", Sensitive: false},
+		{Name: ":method", Value: "GET", Sensitive: false},
+		{Name: ":method", Value: "POST", Sensitive: false},
+		{Name: ":path", Value: "/", Sensitive: false},
+		{Name: ":path", Value: "/index.html", Sensitive: false},
+		{Name: ":scheme", Value: "http", Sensitive: false},
+		{Name: ":scheme", Value: "https", Sensitive: false},
+		{Name: ":status", Value: "200", Sensitive: false},
+		{Name: ":status", Value: "204", Sensitive: false},
+		{Name: ":status", Value: "206", Sensitive: false},
+		{Name: ":status", Value: "304", Sensitive: false},
+		{Name: ":status", Value: "400", Sensitive: false},
+		{Name: ":status", Value: "404", Sensitive: false},
+		{Name: ":status", Value: "500", Sensitive: false},
+		{Name: "accept-charset", Value: "", Sensitive: false},
+		{Name: "accept-encoding", Value: "gzip, deflate", Sensitive: false},
+		{Name: "accept-language", Value: "", Sensitive: false},
+		{Name: "accept-ranges", Value: "", Sensitive: false},
+		{Name: "accept", Value: "", Sensitive: false},
+		{Name: "access-control-allow-origin", Value: "", Sensitive: false},
+		{Name: "age", Value: "", Sensitive: false},
+		{Name: "allow", Value: "", Sensitive: false},
+		{Name: "authorization", Value: "", Sensitive: false},
+		{Name: "cache-control", Value: "", Sensitive: false},
+		{Name: "content-disposition", Value: "", Sensitive: false},
+		{Name: "content-encoding", Value: "", Sensitive: false},
+		{Name: "content-language", Value: "", Sensitive: false},
+		{Name: "content-length", Value: "", Sensitive: false},
+		{Name: "content-location", Value: "", Sensitive: false},
+		{Name: "content-range", Value: "", Sensitive: false},
+		{Name: "content-type", Value: "", Sensitive: false},
+		{Name: "cookie", Value: "", Sensitive: false},
+		{Name: "date", Value: "", Sensitive: false},
+		{Name: "etag", Value: "", Sensitive: false},
+		{Name: "expect", Value: "", Sensitive: false},
+		{Name: "expires", Value: "", Sensitive: false},
+		{Name: "from", Value: "", Sensitive: false},
+		{Name: "host", Value: "", Sensitive: false},
+		{Name: "if-match", Value: "", Sensitive: false},
+		{Name: "if-modified-since", Value: "", Sensitive: false},
+		{Name: "if-none-match", Value: "", Sensitive: false},
+		{Name: "if-range", Value: "", Sensitive: false},
+		{Name: "if-unmodified-since", Value: "", Sensitive: false},
+		{Name: "last-modified", Value: "", Sensitive: false},
+		{Name: "link", Value: "", Sensitive: false},
+		{Name: "location", Value: "", Sensitive: false},
+		{Name: "max-forwards", Value: "", Sensitive: false},
+		{Name: "proxy-authenticate", Value: "", Sensitive: false},
+		{Name: "proxy-authorization", Value: "", Sensitive: false},
+		{Name: "range", Value: "", Sensitive: false},
+		{Name: "referer", Value: "", Sensitive: false},
+		{Name: "refresh", Value: "", Sensitive: false},
+		{Name: "retry-after", Value: "", Sensitive: false},
+		{Name: "server", Value: "", Sensitive: false},
+		{Name: "set-cookie", Value: "", Sensitive: false},
+		{Name: "strict-transport-security", Value: "", Sensitive: false},
+		{Name: "transfer-encoding", Value: "", Sensitive: false},
+		{Name: "user-agent", Value: "", Sensitive: false},
+		{Name: "vary", Value: "", Sensitive: false},
+		{Name: "via", Value: "", Sensitive: false},
+		{Name: "www-authenticate", Value: "", Sensitive: false},
+	},
+}
diff --git a/http2/hpack/tables.go b/http2/hpack/tables.go
index a66cfbe..8cbdf3f 100644
--- a/http2/hpack/tables.go
+++ b/http2/hpack/tables.go
@@ -96,8 +96,7 @@
 // meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
 // table, the return value i actually refers to the entry t.ents[t.len()-i].
 //
-// All tables are assumed to be a dynamic tables except for the global
-// staticTable pointer.
+// All tables are assumed to be a dynamic tables except for the global staticTable.
 //
 // See Section 2.3.3.
 func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
@@ -125,81 +124,6 @@
 	return k + 1
 }
 
-// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
-var staticTable = newStaticTable()
-var staticTableEntries = [...]HeaderField{
-	{Name: ":authority"},
-	{Name: ":method", Value: "GET"},
-	{Name: ":method", Value: "POST"},
-	{Name: ":path", Value: "/"},
-	{Name: ":path", Value: "/index.html"},
-	{Name: ":scheme", Value: "http"},
-	{Name: ":scheme", Value: "https"},
-	{Name: ":status", Value: "200"},
-	{Name: ":status", Value: "204"},
-	{Name: ":status", Value: "206"},
-	{Name: ":status", Value: "304"},
-	{Name: ":status", Value: "400"},
-	{Name: ":status", Value: "404"},
-	{Name: ":status", Value: "500"},
-	{Name: "accept-charset"},
-	{Name: "accept-encoding", Value: "gzip, deflate"},
-	{Name: "accept-language"},
-	{Name: "accept-ranges"},
-	{Name: "accept"},
-	{Name: "access-control-allow-origin"},
-	{Name: "age"},
-	{Name: "allow"},
-	{Name: "authorization"},
-	{Name: "cache-control"},
-	{Name: "content-disposition"},
-	{Name: "content-encoding"},
-	{Name: "content-language"},
-	{Name: "content-length"},
-	{Name: "content-location"},
-	{Name: "content-range"},
-	{Name: "content-type"},
-	{Name: "cookie"},
-	{Name: "date"},
-	{Name: "etag"},
-	{Name: "expect"},
-	{Name: "expires"},
-	{Name: "from"},
-	{Name: "host"},
-	{Name: "if-match"},
-	{Name: "if-modified-since"},
-	{Name: "if-none-match"},
-	{Name: "if-range"},
-	{Name: "if-unmodified-since"},
-	{Name: "last-modified"},
-	{Name: "link"},
-	{Name: "location"},
-	{Name: "max-forwards"},
-	{Name: "proxy-authenticate"},
-	{Name: "proxy-authorization"},
-	{Name: "range"},
-	{Name: "referer"},
-	{Name: "refresh"},
-	{Name: "retry-after"},
-	{Name: "server"},
-	{Name: "set-cookie"},
-	{Name: "strict-transport-security"},
-	{Name: "transfer-encoding"},
-	{Name: "user-agent"},
-	{Name: "vary"},
-	{Name: "via"},
-	{Name: "www-authenticate"},
-}
-
-func newStaticTable() *headerFieldTable {
-	t := &headerFieldTable{}
-	t.init()
-	for _, e := range staticTableEntries[:] {
-		t.addEntry(e)
-	}
-	return t
-}
-
 var huffmanCodes = [256]uint32{
 	0x1ff8,
 	0x7fffd8,
diff --git a/http2/hpack/tables_test.go b/http2/hpack/tables_test.go
index d963f36..66366a1 100644
--- a/http2/hpack/tables_test.go
+++ b/http2/hpack/tables_test.go
@@ -207,6 +207,12 @@
 		if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
 			t.Errorf("header index %d value = %q; want %q", i, got, want)
 		}
+		if got, want := staticTable.ents[i-1].Sensitive, false; got != want {
+			t.Errorf("header index %d sensitive = %t; want %t", i, got, want)
+		}
+		if got, want := strconv.Itoa(int(staticTable.byNameValue[pairNameValue{name: m[2], value: m[3]}])), m[1]; got != want {
+			t.Errorf("header by name %s value %s index = %s; want %s", m[2], m[3], got, want)
+		}
 	}
 	if err := bs.Err(); err != nil {
 		t.Error(err)