internal/gen/bitfield: added package to simplify bit fiddling
Change-Id: Ie54d050135a39f7fc3df419fcbdc042363b7e216
Reviewed-on: https://go-review.googlesource.com/96735
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Ross Light <light@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/gen/bitfield/bitfield.go b/internal/gen/bitfield/bitfield.go
new file mode 100644
index 0000000..a8d0a48
--- /dev/null
+++ b/internal/gen/bitfield/bitfield.go
@@ -0,0 +1,226 @@
+// Copyright 2018 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.
+
+// Package bitfield converts annotated structs into integer values.
+//
+// Any field that is marked with a bitfield tag is compacted. The tag value has
+// two parts. The part before the comma determines the method name for a
+// generated type. If left blank the name of the field is used.
+// The part after the comma determines the number of bits to use for the
+// representation.
+package bitfield
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+// Config determines settings for packing and generation. If a Config is used,
+// the same Config should be used for packing and generation.
+type Config struct {
+ // NumBits fixes the maximum allowed bits for the integer representation.
+ // If NumBits is not 8, 16, 32, or 64, the actual underlying integer size
+ // will be the next largest available.
+ NumBits uint
+
+ // If Package is set, code generation will write a package clause.
+ Package string
+
+ // TypeName is the name for the generated type. By default it is the name
+ // of the type of the value passed to Gen.
+ TypeName string
+}
+
+var nullConfig = &Config{}
+
+// Pack packs annotated bit ranges of struct x in an integer.
+//
+// Only fields that have a "bitfield" tag are compacted.
+func Pack(x interface{}, c *Config) (packed uint64, err error) {
+ packed, _, err = pack(x, c)
+ return
+}
+
+func pack(x interface{}, c *Config) (packed uint64, nBit uint, err error) {
+ if c == nil {
+ c = nullConfig
+ }
+ nBits := c.NumBits
+ v := reflect.ValueOf(x)
+ v = reflect.Indirect(v)
+ t := v.Type()
+ pos := 64 - nBits
+ if nBits == 0 {
+ pos = 0
+ }
+ for i := 0; i < v.NumField(); i++ {
+ v := v.Field(i)
+ field := t.Field(i)
+ f, err := parseField(field)
+
+ if err != nil {
+ return 0, 0, err
+ }
+ if f.nBits == 0 {
+ continue
+ }
+ value := uint64(0)
+ switch v.Kind() {
+ case reflect.Bool:
+ if v.Bool() {
+ value = 1
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ value = v.Uint()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ x := v.Int()
+ if x < 0 {
+ return 0, 0, fmt.Errorf("bitfield: negative value for field %q not allowed", field.Name)
+ }
+ value = uint64(x)
+ }
+ if value > (1<<f.nBits)-1 {
+ return 0, 0, fmt.Errorf("bitfield: value %#x of field %q does not fit in %d bits", value, field.Name, f.nBits)
+ }
+ shift := 64 - pos - f.nBits
+ if pos += f.nBits; pos > 64 {
+ return 0, 0, fmt.Errorf("bitfield: no more bits left for field %q", field.Name)
+ }
+ packed |= value << shift
+ }
+ if nBits == 0 {
+ nBits = posToBits(pos)
+ packed >>= (64 - nBits)
+ }
+ return packed, nBits, nil
+}
+
+type field struct {
+ name string
+ value uint64
+ nBits uint
+}
+
+// parseField parses a tag of the form [<name>][:<nBits>][,<pos>[..<end>]]
+func parseField(field reflect.StructField) (f field, err error) {
+ s, ok := field.Tag.Lookup("bitfield")
+ if !ok {
+ return f, nil
+ }
+ switch field.Type.Kind() {
+ case reflect.Bool:
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ default:
+ return f, fmt.Errorf("bitfield: field %q is not an integer or bool type", field.Name)
+ }
+ bits := s
+ f.name = ""
+
+ if i := strings.IndexByte(s, ','); i >= 0 {
+ bits = s[:i]
+ f.name = s[i+1:]
+ }
+ if bits != "" {
+ nBits, err := strconv.ParseUint(bits, 10, 8)
+ if err != nil {
+ return f, fmt.Errorf("bitfield: invalid bit size for field %q: %v", field.Name, err)
+ }
+ f.nBits = uint(nBits)
+ }
+ if f.nBits == 0 {
+ if field.Type.Kind() == reflect.Bool {
+ f.nBits = 1
+ } else {
+ f.nBits = uint(field.Type.Bits())
+ }
+ }
+ if f.name == "" {
+ f.name = field.Name
+ }
+ return f, err
+}
+
+func posToBits(pos uint) (bits uint) {
+ switch {
+ case pos <= 8:
+ bits = 8
+ case pos <= 16:
+ bits = 16
+ case pos <= 32:
+ bits = 32
+ case pos <= 64:
+ bits = 64
+ default:
+ panic("unreachable")
+ }
+ return bits
+}
+
+// Gen generates code for unpacking integers created with Pack.
+func Gen(w io.Writer, x interface{}, c *Config) error {
+ if c == nil {
+ c = nullConfig
+ }
+ _, nBits, err := pack(x, c)
+ if err != nil {
+ return err
+ }
+
+ t := reflect.TypeOf(x)
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if c.TypeName == "" {
+ c.TypeName = t.Name()
+ }
+ firstChar := []rune(c.TypeName)[0]
+
+ buf := &bytes.Buffer{}
+
+ print := func(w io.Writer, format string, args ...interface{}) {
+ if _, e := fmt.Fprintf(w, format+"\n", args...); e != nil && err == nil {
+ err = fmt.Errorf("bitfield: write failed: %v", err)
+ }
+ }
+
+ pos := uint(0)
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ f, _ := parseField(field)
+ if f.nBits == 0 {
+ continue
+ }
+ shift := nBits - pos - f.nBits
+ pos += f.nBits
+
+ retType := field.Type.Name()
+ print(buf, "\nfunc (%c %s) %s() %s {", firstChar, c.TypeName, f.name, retType)
+ if field.Type.Kind() == reflect.Bool {
+ print(buf, "\tconst bit = 1 << %d", shift)
+ print(buf, "\treturn %c&bit == bit", firstChar)
+ } else {
+ print(buf, "\treturn %s((%c >> %d) & %#x)", retType, firstChar, shift, (1<<f.nBits)-1)
+ }
+ print(buf, "}")
+ }
+
+ if c.Package != "" {
+ print(w, "// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.\n")
+ print(w, "package %s\n", c.Package)
+ }
+
+ bits := posToBits(pos)
+
+ print(w, "type %s uint%d", c.TypeName, bits)
+
+ if _, err := io.Copy(w, buf); err != nil {
+ return fmt.Errorf("bitfield: write failed: %v", err)
+ }
+ return nil
+}
diff --git a/internal/gen/bitfield/bitfield_test.go b/internal/gen/bitfield/bitfield_test.go
new file mode 100644
index 0000000..789f86d
--- /dev/null
+++ b/internal/gen/bitfield/bitfield_test.go
@@ -0,0 +1,230 @@
+// Copyright 2018 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.
+
+package bitfield
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "testing"
+)
+
+type myUint8 uint8
+
+type test1 struct { // 28 bits
+ foo uint16 `bitfield:",fob"`
+ Bar int8 `bitfield:"5,baz"`
+ Foo uint64
+ bar myUint8 `bitfield:"3"`
+ Bool bool `bitfield:""`
+ Baz int8 `bitfield:"3"`
+}
+
+type test2 struct {
+ larger1 uint16 `bitfield:"32"`
+ larger2 uint16 `bitfield:"32"`
+}
+
+type tooManyBits struct {
+ u1 uint16 `bitfield:"12"`
+ u2 uint16 `bitfield:"12"`
+ u3 uint16 `bitfield:"12"`
+ u4 uint16 `bitfield:"12"`
+ u5 uint16 `bitfield:"12"`
+ u6 uint16 `bitfield:"12"`
+}
+
+type just64 struct {
+ foo uint64 `bitfield:""`
+}
+
+type toUint8 struct {
+ foo bool `bitfield:""`
+}
+
+type toUint16 struct {
+ foo int `bitfield:"9"`
+}
+
+type faultySize struct {
+ foo uint64 `bitfield:"a"`
+}
+
+type faultyType struct {
+ foo *int `bitfield:"5"`
+}
+
+var (
+ maxed = test1{
+ foo: 0xffff,
+ Bar: 0x1f,
+ Foo: 0xffff,
+ bar: 0x7,
+ Bool: true,
+ Baz: 0x7,
+ }
+ alternate1 = test1{
+ foo: 0xffff,
+ bar: 0x7,
+ Baz: 0x7,
+ }
+ alternate2 = test1{
+ Bar: 0x1f,
+ Bool: true,
+ }
+ overflow = test1{
+ Bar: 0x3f,
+ }
+ negative = test1{
+ Bar: -1,
+ }
+)
+
+func TestPack(t *testing.T) {
+ testCases := []struct {
+ desc string
+ x interface{}
+ nBits uint
+ out uint64
+ ok bool
+ }{
+ {"maxed out fields", maxed, 0, 0xfffffff0, true},
+ {"maxed using less bits", maxed, 28, 0x0fffffff, true},
+
+ {"alternate1", alternate1, 0, 0xffff0770, true},
+ {"alternate2", alternate2, 0, 0x0000f880, true},
+
+ {"just64", &just64{0x0f0f0f0f}, 00, 0xf0f0f0f, true},
+ {"just64", &just64{0x0f0f0f0f}, 64, 0xf0f0f0f, true},
+ {"just64", &just64{0xffffFFFF}, 64, 0xffffffff, true},
+ {"to uint8", &toUint8{true}, 0, 0x80, true},
+ {"to uint16", &toUint16{1}, 0, 0x0080, true},
+ // errors
+ {"overflow", overflow, 0, 0, false},
+ {"too many bits", &tooManyBits{}, 0, 0, false},
+ {"fault size", &faultySize{}, 0, 0, false},
+ {"fault type", &faultyType{}, 0, 0, false},
+ {"negative", negative, 0, 0, false},
+ {"not enough bits", maxed, 27, 0, false},
+ }
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("%T/%s", tc.x, tc.desc), func(t *testing.T) {
+ v, err := Pack(tc.x, &Config{NumBits: tc.nBits})
+ if ok := err == nil; v != tc.out || ok != tc.ok {
+ t.Errorf("got %#x, %v; want %#x, %v (%v)", v, ok, tc.out, tc.ok, err)
+ }
+ })
+ }
+}
+
+func TestRoundtrip(t *testing.T) {
+ testCases := []struct {
+ x test1
+ }{
+ {maxed},
+ {alternate1},
+ {alternate2},
+ }
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ v, err := Pack(tc.x, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := tc.x
+ want.Foo = 0 // not stored
+ x := myInt(v)
+ got := test1{
+ foo: x.fob(),
+ Bar: x.baz(),
+ bar: x.bar(),
+ Bool: x.Bool(),
+ Baz: x.Baz(),
+ }
+ if got != want {
+ t.Errorf("\ngot %#v\nwant %#v (%#x)", got, want, v)
+ }
+ })
+ }
+}
+
+func TestGen(t *testing.T) {
+ testCases := []struct {
+ desc string
+ x interface{}
+ config *Config
+ ok bool
+ out string
+ }{{
+ desc: "test1",
+ x: &test1{},
+ ok: true,
+ out: test1Gen,
+ }, {
+ desc: "test1 with options",
+ x: &test1{},
+ config: &Config{Package: "bitfield", TypeName: "myInt"},
+ ok: true,
+ out: mustRead("gen1_test.go"),
+ }, {
+ desc: "test1 with alternative bits",
+ x: &test1{},
+ config: &Config{NumBits: 28, Package: "bitfield", TypeName: "myInt2"},
+ ok: true,
+ out: mustRead("gen2_test.go"),
+ }, {
+ desc: "failure",
+ x: &test1{},
+ config: &Config{NumBits: 27}, // Too few bits.
+ ok: false,
+ out: "",
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ w := &bytes.Buffer{}
+ err := Gen(w, tc.x, tc.config)
+ if ok := err == nil; ok != tc.ok {
+ t.Fatalf("got %v; want %v (%v)", ok, tc.ok, err)
+ }
+ got := w.String()
+ if got != tc.out {
+ t.Errorf("got:\n%s\nwant:\n%s", got, tc.out)
+ }
+ })
+ }
+}
+
+const test1Gen = `type test1 uint32
+
+func (t test1) fob() uint16 {
+ return uint16((t >> 16) & 0xffff)
+}
+
+func (t test1) baz() int8 {
+ return int8((t >> 11) & 0x1f)
+}
+
+func (t test1) bar() myUint8 {
+ return myUint8((t >> 8) & 0x7)
+}
+
+func (t test1) Bool() bool {
+ const bit = 1 << 7
+ return t&bit == bit
+}
+
+func (t test1) Baz() int8 {
+ return int8((t >> 4) & 0x7)
+}
+`
+
+func mustRead(filename string) string {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ panic(err)
+ }
+ return string(b)
+}
diff --git a/internal/gen/bitfield/gen1_test.go b/internal/gen/bitfield/gen1_test.go
new file mode 100644
index 0000000..2844b9d
--- /dev/null
+++ b/internal/gen/bitfield/gen1_test.go
@@ -0,0 +1,26 @@
+// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.
+
+package bitfield
+
+type myInt uint32
+
+func (m myInt) fob() uint16 {
+ return uint16((m >> 16) & 0xffff)
+}
+
+func (m myInt) baz() int8 {
+ return int8((m >> 11) & 0x1f)
+}
+
+func (m myInt) bar() myUint8 {
+ return myUint8((m >> 8) & 0x7)
+}
+
+func (m myInt) Bool() bool {
+ const bit = 1 << 7
+ return m&bit == bit
+}
+
+func (m myInt) Baz() int8 {
+ return int8((m >> 4) & 0x7)
+}
diff --git a/internal/gen/bitfield/gen2_test.go b/internal/gen/bitfield/gen2_test.go
new file mode 100644
index 0000000..ad5f72a
--- /dev/null
+++ b/internal/gen/bitfield/gen2_test.go
@@ -0,0 +1,26 @@
+// Code generated by golang.org/x/text/internal/gen/bitfield. DO NOT EDIT.
+
+package bitfield
+
+type myInt2 uint32
+
+func (m myInt2) fob() uint16 {
+ return uint16((m >> 12) & 0xffff)
+}
+
+func (m myInt2) baz() int8 {
+ return int8((m >> 7) & 0x1f)
+}
+
+func (m myInt2) bar() myUint8 {
+ return myUint8((m >> 4) & 0x7)
+}
+
+func (m myInt2) Bool() bool {
+ const bit = 1 << 3
+ return m&bit == bit
+}
+
+func (m myInt2) Baz() int8 {
+ return int8((m >> 0) & 0x7)
+}