goprotobuf: remove unsafe buffer scratch allocator
This is part of the work towards making protobufs run on App Engine.
By the way, it's faster now.
Before:
proto_test.BenchmarkUnmarshal 20000 87053 ns/op
After:
proto_test.BenchmarkUnmarshal 20000 84825 ns/op
R=dsymonds, rsc, ken, r
CC=golang-dev
http://codereview.appspot.com/5310048
diff --git a/proto/decode.go b/proto/decode.go
index fe28821..fd3960d 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -333,7 +333,6 @@
st := t.Elem()
prop := GetProperties(st)
required, reqFields := prop.reqCount, uint64(0)
- sbase := getsbase(prop) // scratch area for data items
var err os.Error
for err == nil && o.index < len(o.buf) {
@@ -354,7 +353,7 @@
fieldnum, ok := prop.tags[tag]
if !ok {
// Maybe it's an extension?
- o.ptr = base
+ o.ptr = base // copy the address here to avoid a heap allocation.
iv := unsafe.Unreflect(t, unsafe.Pointer(&o.ptr))
if e, ok := iv.(extendableProto); ok && isExtensionField(e, int32(tag)) {
if err = o.skip(st, tag, wire); err == nil {
@@ -381,7 +380,7 @@
continue
}
}
- err = dec(o, p, base, sbase)
+ err = dec(o, p, base)
if err == nil && p.Required {
// Successfully decoded a required field.
if tag <= 64 {
@@ -424,100 +423,102 @@
// For each,
// u is the decoded value,
// v is a pointer to the field (pointer) in the struct
-// x is a pointer to the preallocated scratch space to hold the decoded value.
+
+// Sizes of the pools to allocate inside the Buffer.
+// The goal is modest amortization and allocation on at lest 16-byte boundaries.
+const (
+ boolPoolSize = 16
+ int32PoolSize = 8
+ int64PoolSize = 4
+)
// Decode a bool.
-func (o *Buffer) dec_bool(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_bool(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
- v := (**uint8)(unsafe.Pointer(base + p.offset))
- x := (*uint8)(unsafe.Pointer(sbase + p.scratch))
- *x = uint8(u)
- *v = x
+ if len(o.bools) == 0 {
+ o.bools = make([]bool, boolPoolSize)
+ }
+ o.bools[0] = u != 0
+ v := (**bool)(unsafe.Pointer(base + p.offset))
+ *v = &o.bools[0]
+ o.bools = o.bools[1:]
return nil
}
// Decode an int32.
-func (o *Buffer) dec_int32(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_int32(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
+ if len(o.int32s) == 0 {
+ o.int32s = make([]int32, int32PoolSize)
+ }
+ o.int32s[0] = int32(u)
v := (**int32)(unsafe.Pointer(base + p.offset))
- x := (*int32)(unsafe.Pointer(sbase + p.scratch))
- *x = int32(u)
- *v = x
+ *v = &o.int32s[0]
+ o.int32s = o.int32s[1:]
return nil
}
// Decode an int64.
-func (o *Buffer) dec_int64(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_int64(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
+ if len(o.int64s) == 0 {
+ o.int64s = make([]int64, int64PoolSize)
+ }
+ o.int64s[0] = int64(u)
v := (**int64)(unsafe.Pointer(base + p.offset))
- x := (*int64)(unsafe.Pointer(sbase + p.scratch))
- *x = int64(u)
- *v = x
+ *v = &o.int64s[0]
+ o.int64s = o.int64s[1:]
return nil
}
// Decode a string.
-func (o *Buffer) dec_string(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_string(p *Properties, base uintptr) os.Error {
s, err := o.DecodeStringBytes()
if err != nil {
return err
}
+ sp := new(string)
+ *sp = s
v := (**string)(unsafe.Pointer(base + p.offset))
- x := (*string)(unsafe.Pointer(sbase + p.scratch))
- *x = s
- *v = x
+ *v = sp
return nil
}
// Decode a slice of bytes ([]byte).
-func (o *Buffer) dec_slice_byte(p *Properties, base uintptr, sbase uintptr) os.Error {
- b, err := o.DecodeRawBytes(false)
+func (o *Buffer) dec_slice_byte(p *Properties, base uintptr) os.Error {
+ b, err := o.DecodeRawBytes(true)
if err != nil {
return err
}
- x := (*[]uint8)(unsafe.Pointer(base + p.offset))
-
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, b...)
+ v := (*[]byte)(unsafe.Pointer(base + p.offset))
+ *v = b
return nil
}
// Decode a slice of bools ([]bool).
-func (o *Buffer) dec_slice_bool(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_bool(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
- x := (*[]bool)(unsafe.Pointer(base + p.offset))
-
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, u != 0)
+ v := (*[]bool)(unsafe.Pointer(base + p.offset))
+ *v = append(*v, u != 0)
return nil
}
// Decode a slice of bools ([]bool) in packed format.
-func (o *Buffer) dec_slice_packed_bool(p *Properties, base uintptr, sbase uintptr) os.Error {
- x := (*[]bool)(unsafe.Pointer(base + p.offset))
+func (o *Buffer) dec_slice_packed_bool(p *Properties, base uintptr) os.Error {
+ v := (*[]bool)(unsafe.Pointer(base + p.offset))
nn, err := o.DecodeVarint()
if err != nil {
@@ -525,12 +526,7 @@
}
nb := int(nn) // number of bytes of encoded bools
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
+ y := *v
for i := 0; i < nb; i++ {
u, err := p.valDec(o)
if err != nil {
@@ -539,31 +535,24 @@
y = append(y, u != 0)
}
- *x = y
+ *v = y
return nil
}
// Decode a slice of int32s ([]int32).
-func (o *Buffer) dec_slice_int32(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_int32(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
- x := (*[]int32)(unsafe.Pointer(base + p.offset))
-
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, int32(u))
+ v := (*[]int32)(unsafe.Pointer(base + p.offset))
+ *v = append(*v, int32(u))
return nil
}
// Decode a slice of int32s ([]int32) in packed format.
-func (o *Buffer) dec_slice_packed_int32(p *Properties, base uintptr, sbase uintptr) os.Error {
- x := (*[]int32)(unsafe.Pointer(base + p.offset))
+func (o *Buffer) dec_slice_packed_int32(p *Properties, base uintptr) os.Error {
+ v := (*[]int32)(unsafe.Pointer(base + p.offset))
nn, err := o.DecodeVarint()
if err != nil {
@@ -571,11 +560,7 @@
}
nb := int(nn) // number of bytes of encoded int32s
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
+ y := *v
fin := o.index + nb
for o.index < fin {
@@ -586,31 +571,26 @@
y = append(y, int32(u))
}
- *x = y
+ *v = y
return nil
}
// Decode a slice of int64s ([]int64).
-func (o *Buffer) dec_slice_int64(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_int64(p *Properties, base uintptr) os.Error {
u, err := p.valDec(o)
if err != nil {
return err
}
- x := (*[]int64)(unsafe.Pointer(base + p.offset))
+ v := (*[]int64)(unsafe.Pointer(base + p.offset))
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, int64(u))
+ y := *v
+ *v = append(y, int64(u))
return nil
}
// Decode a slice of int64s ([]int64) in packed format.
-func (o *Buffer) dec_slice_packed_int64(p *Properties, base uintptr, sbase uintptr) os.Error {
- x := (*[]int64)(unsafe.Pointer(base + p.offset))
+func (o *Buffer) dec_slice_packed_int64(p *Properties, base uintptr) os.Error {
+ v := (*[]int64)(unsafe.Pointer(base + p.offset))
nn, err := o.DecodeVarint()
if err != nil {
@@ -618,12 +598,7 @@
}
nb := int(nn) // number of bytes of encoded int64s
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
+ y := *v
fin := o.index + nb
for o.index < fin {
u, err := p.valDec(o)
@@ -633,48 +608,38 @@
y = append(y, int64(u))
}
- *x = y
+ *v = y
return nil
}
// Decode a slice of strings ([]string).
-func (o *Buffer) dec_slice_string(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_string(p *Properties, base uintptr) os.Error {
s, err := o.DecodeStringBytes()
if err != nil {
return err
}
- x := (*[]string)(unsafe.Pointer(base + p.offset))
+ v := (*[]string)(unsafe.Pointer(base + p.offset))
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, s)
+ y := *v
+ *v = append(y, s)
return nil
}
// Decode a slice of slice of bytes ([][]byte).
-func (o *Buffer) dec_slice_slice_byte(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_slice_byte(p *Properties, base uintptr) os.Error {
b, err := o.DecodeRawBytes(true)
if err != nil {
return err
}
- x := (*[][]byte)(unsafe.Pointer(base + p.offset))
+ v := (*[][]byte)(unsafe.Pointer(base + p.offset))
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
-
- *x = append(y, b)
+ y := *v
+ *v = append(y, b)
return nil
}
// Decode a group.
-func (o *Buffer) dec_struct_group(p *Properties, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_struct_group(p *Properties, base uintptr) os.Error {
ptr := (**struct{})(unsafe.Pointer(base + p.offset))
typ := p.stype.Elem()
structv := unsafe.New(typ)
@@ -687,7 +652,7 @@
}
// Decode an embedded message.
-func (o *Buffer) dec_struct_message(p *Properties, base uintptr, sbase uintptr) (err os.Error) {
+func (o *Buffer) dec_struct_message(p *Properties, base uintptr) (err os.Error) {
raw, e := o.DecodeRawBytes(false)
if e != nil {
return e
@@ -718,30 +683,26 @@
}
// Decode a slice of embedded messages.
-func (o *Buffer) dec_slice_struct_message(p *Properties, base uintptr, sbase uintptr) os.Error {
- return o.dec_slice_struct(p, false, base, sbase)
+func (o *Buffer) dec_slice_struct_message(p *Properties, base uintptr) os.Error {
+ return o.dec_slice_struct(p, false, base)
}
// Decode a slice of embedded groups.
-func (o *Buffer) dec_slice_struct_group(p *Properties, base uintptr, sbase uintptr) os.Error {
- return o.dec_slice_struct(p, true, base, sbase)
+func (o *Buffer) dec_slice_struct_group(p *Properties, base uintptr) os.Error {
+ return o.dec_slice_struct(p, true, base)
}
// Decode a slice of structs ([]*struct).
-func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base uintptr, sbase uintptr) os.Error {
+func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base uintptr) os.Error {
- x := (*[]*struct{})(unsafe.Pointer(base + p.offset))
- y := *x
- if cap(y) == 0 {
- initSlice(unsafe.Pointer(x), sbase+p.scratch)
- y = *x
- }
+ v := (*[]*struct{})(unsafe.Pointer(base + p.offset))
+ y := *v
typ := p.stype.Elem()
structv := unsafe.New(typ)
bas := uintptr(structv)
y = append(y, (*struct{})(structv))
- *x = y
+ *v = y
if is_group {
err := o.unmarshalType(p.stype, is_group, bas)
diff --git a/proto/encode.go b/proto/encode.go
index e2141c0..73f2eff 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -152,8 +152,7 @@
// This is the format used for the bytes protocol buffer
// type and for embedded messages.
func (p *Buffer) EncodeRawBytes(b []byte) os.Error {
- lb := len(b)
- p.EncodeVarint(uint64(lb))
+ p.EncodeVarint(uint64(len(b)))
p.buf = append(p.buf, b...)
return nil
}
@@ -161,10 +160,8 @@
// EncodeStringBytes writes an encoded string to the Buffer.
// This is the format used for the proto2 string type.
func (p *Buffer) EncodeStringBytes(s string) os.Error {
-
- // this works because strings and slices are the same.
- y := *(*[]byte)(unsafe.Pointer(&s))
- p.EncodeRawBytes(y)
+ p.EncodeVarint(uint64(len(s)))
+ p.buf = append(p.buf, s...)
return nil
}
diff --git a/proto/extensions.go b/proto/extensions.go
index d329b90..f1c52cc 100644
--- a/proto/extensions.go
+++ b/proto/extensions.go
@@ -31,7 +31,6 @@
package proto
-
/*
* Types and routines for supporting protocol buffer extensions.
*/
@@ -200,14 +199,11 @@
props.Init(t, "irrelevant_name", extension.Tag, 0)
base := unsafe.New(t)
- var sbase uintptr
if t.Elem().Kind() == reflect.Struct {
- // props.dec will be dec_struct_message, which does not refer to sbase.
+ // props.dec will be dec_struct_message.
*(*unsafe.Pointer)(base) = unsafe.New(t.Elem())
- } else {
- sbase = uintptr(unsafe.New(t.Elem()))
}
- if err := props.dec(o, props, uintptr(base), sbase); err != nil {
+ if err := props.dec(o, props, uintptr(base)); err != nil {
return nil, err
}
return unsafe.Unreflect(t, base), nil
diff --git a/proto/lib.go b/proto/lib.go
index b816e91..a24c780 100644
--- a/proto/lib.go
+++ b/proto/lib.go
@@ -198,7 +198,11 @@
index int // write point
freelist [10][]byte // list of available buffers
nfreelist int // number of free buffers
- ptr uintptr // scratch area for pointers
+ ptr uintptr // used to avoid a heap allocation.
+ // pools of basic types to amortize allocation.
+ bools []bool
+ int32s []int32
+ int64s []int64
}
// NewBuffer allocates a new Buffer and initializes its internal data to
diff --git a/proto/properties.go b/proto/properties.go
index b64481d..fc24517 100644
--- a/proto/properties.go
+++ b/proto/properties.go
@@ -71,7 +71,7 @@
// Decoders are defined in decode.go
// A decoder creates a value from its wire representation.
// Unrecognized subelements are saved in unrec.
-type decoder func(p *Buffer, prop *Properties, base uintptr, sbase uintptr) os.Error
+type decoder func(p *Buffer, prop *Properties, base uintptr) os.Error
// A valueDecoder decodes a single integer in a particular encoding.
type valueDecoder func(o *Buffer) (x uint64, err os.Error)
@@ -83,7 +83,6 @@
tags map[int]int // map from proto tag to struct field number
origNames map[string]int // map from original name to struct field number
order []int // list of struct field numbers in tag order
- nscratch uintptr // size of scratch space
}
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
@@ -117,11 +116,8 @@
tagbuf [8]byte
stype reflect.Type
- dec decoder
- valDec valueDecoder // set for bool and numeric types only
- scratch uintptr
- sizeof uintptr // calculations of scratch space
- alignof uintptr
+ dec decoder
+ valDec valueDecoder // set for bool and numeric types only
// If this is a packable field, this will be the decoder for the packed version of the field.
packedDec decoder
@@ -233,15 +229,6 @@
// Initialize the fields for encoding and decoding.
func (p *Properties) setEncAndDec(typ reflect.Type) {
- var vbool bool
- var vbyte byte
- var vint32 int32
- var vint64 int64
- var vfloat32 float32
- var vfloat64 float64
- var vstring string
- var vslice []byte
-
p.enc = nil
p.dec = nil
@@ -258,33 +245,21 @@
case reflect.Bool:
p.enc = (*Buffer).enc_bool
p.dec = (*Buffer).dec_bool
- p.alignof = unsafe.Alignof(vbool)
- p.sizeof = unsafe.Sizeof(vbool)
case reflect.Int32, reflect.Uint32:
p.enc = (*Buffer).enc_int32
p.dec = (*Buffer).dec_int32
- p.alignof = unsafe.Alignof(vint32)
- p.sizeof = unsafe.Sizeof(vint32)
case reflect.Int64, reflect.Uint64:
p.enc = (*Buffer).enc_int64
p.dec = (*Buffer).dec_int64
- p.alignof = unsafe.Alignof(vint64)
- p.sizeof = unsafe.Sizeof(vint64)
case reflect.Float32:
p.enc = (*Buffer).enc_int32 // can just treat them as bits
p.dec = (*Buffer).dec_int32
- p.alignof = unsafe.Alignof(vfloat32)
- p.sizeof = unsafe.Sizeof(vfloat32)
case reflect.Float64:
p.enc = (*Buffer).enc_int64 // can just treat them as bits
p.dec = (*Buffer).dec_int64
- p.alignof = unsafe.Alignof(vfloat64)
- p.sizeof = unsafe.Sizeof(vfloat64)
case reflect.String:
p.enc = (*Buffer).enc_string
p.dec = (*Buffer).dec_string
- p.alignof = unsafe.Alignof(vstring)
- p.sizeof = unsafe.Sizeof(vstring) + startSize*unsafe.Sizeof(vbyte)
case reflect.Struct:
p.stype = t1
if p.Wire == "bytes" {
@@ -309,8 +284,6 @@
}
p.dec = (*Buffer).dec_slice_bool
p.packedDec = (*Buffer).dec_slice_packed_bool
- p.alignof = unsafe.Alignof(vbool)
- p.sizeof = startSize * unsafe.Sizeof(vbool)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
switch t2.Bits() {
case 32:
@@ -321,8 +294,6 @@
}
p.dec = (*Buffer).dec_slice_int32
p.packedDec = (*Buffer).dec_slice_packed_int32
- p.alignof = unsafe.Alignof(vint32)
- p.sizeof = startSize * unsafe.Sizeof(vint32)
case 64:
if p.Packed {
p.enc = (*Buffer).enc_slice_packed_int64
@@ -331,14 +302,10 @@
}
p.dec = (*Buffer).dec_slice_int64
p.packedDec = (*Buffer).dec_slice_packed_int64
- p.alignof = unsafe.Alignof(vint64)
- p.sizeof = startSize * unsafe.Sizeof(vint64)
case 8:
if t2.Kind() == reflect.Uint8 {
p.enc = (*Buffer).enc_slice_byte
p.dec = (*Buffer).dec_slice_byte
- p.alignof = unsafe.Alignof(vbyte)
- p.sizeof = startSize * unsafe.Sizeof(vbyte)
}
default:
logNoSliceEnc(t1, t2)
@@ -355,8 +322,6 @@
}
p.dec = (*Buffer).dec_slice_int32
p.packedDec = (*Buffer).dec_slice_packed_int32
- p.alignof = unsafe.Alignof(vfloat32)
- p.sizeof = startSize * unsafe.Sizeof(vfloat32)
case 64:
// can just treat them as bits
if p.Packed {
@@ -366,8 +331,6 @@
}
p.dec = (*Buffer).dec_slice_int64
p.packedDec = (*Buffer).dec_slice_packed_int64
- p.alignof = unsafe.Alignof(vfloat64)
- p.sizeof = startSize * unsafe.Sizeof(vfloat64)
default:
logNoSliceEnc(t1, t2)
break
@@ -375,8 +338,6 @@
case reflect.String:
p.enc = (*Buffer).enc_slice_string
p.dec = (*Buffer).dec_slice_string
- p.alignof = unsafe.Alignof(vstring)
- p.sizeof = startSize * unsafe.Sizeof(vstring)
case reflect.Ptr:
switch t3 := t2.Elem(); t3.Kind() {
default:
@@ -390,8 +351,6 @@
p.enc = (*Buffer).enc_slice_struct_message
p.dec = (*Buffer).dec_slice_struct_message
}
- p.alignof = unsafe.Alignof(vslice)
- p.sizeof = startSize * unsafe.Sizeof(vslice)
}
case reflect.Slice:
switch t2.Elem().Kind() {
@@ -401,8 +360,6 @@
case reflect.Uint8:
p.enc = (*Buffer).enc_slice_slice_byte
p.dec = (*Buffer).dec_slice_slice_byte
- p.alignof = unsafe.Alignof(vslice)
- p.sizeof = startSize * unsafe.Sizeof(vslice)
}
}
}
@@ -461,11 +418,8 @@
p := new(Properties)
p.Init(f.Type, f.Name, f.Tag.Get("protobuf"), f.Offset)
if f.Name == "XXX_extensions" { // special case
- var vmap map[int32][]byte
p.enc = (*Buffer).enc_map
p.dec = nil // not needed
- p.alignof = unsafe.Alignof(vmap)
- p.sizeof = unsafe.Sizeof(vmap)
}
prop.Prop[i] = p
prop.order[i] = i
@@ -485,41 +439,24 @@
sort.Sort(prop)
// build required counts
- // build scratch offsets
// build tags
reqCount := 0
- scratch := uintptr(0)
prop.tags = make(map[int]int)
prop.origNames = make(map[string]int)
for i, p := range prop.Prop {
if p.Required {
reqCount++
}
- scratch = align(scratch, p.alignof)
- p.scratch = scratch
- scratch += p.sizeof
prop.tags[p.Tag] = i
prop.origNames[p.OrigName] = i
}
prop.reqCount = reqCount
- prop.nscratch = scratch
propertiesMap[t] = prop
mutex.Unlock()
return prop
}
-// Alignment of the data in the scratch area. It doesn't have to be
-// exact, just conservative. Returns the first number >= o that divides s.
-func align(o uintptr, s uintptr) uintptr {
- if s != 0 {
- for o%uintptr(s) != 0 {
- o++
- }
- }
- return o
-}
-
// Return the field index of the named field.
// Returns nil if there is no such field.
func fieldIndex(t reflect.Type, name string) []int {
@@ -555,23 +492,6 @@
return
}
-// Allocate the aux space containing all the decoded data. The structure
-// handed into Unmarshal is filled with pointers to this newly allocated
-// data.
-func getsbase(prop *StructProperties) uintptr {
- var vbyteptr *byte
- if prop.nscratch == 0 {
- return 0
- }
-
- // allocate the decode space as pointers
- // so that the GC will scan it for pointers
- n := uintptr(unsafe.Sizeof(vbyteptr))
- b := make([]*byte, (prop.nscratch+n-1)/n)
- sbase := uintptr(unsafe.Pointer(&b[0]))
- return sbase
-}
-
// A global registry of enum types.
// The generated code will register the generated maps by calling RegisterEnum.