Skip to content

Commit 34f3187

Browse files
tamirmsclaude
andcommitted
go: optimize XDR decoding performance and reduce allocations
Performance optimizations for Go XDR decoding: - Use byte slice Decoder instead of io.Reader for zero-copy decoding - Preserve slice capacity during decoding (grow-only, no shrinking) - Handle optional types without unnecessary allocations - Union arms with primitive types decode directly into value fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9796d62 commit 34f3187

File tree

9 files changed

+892
-726
lines changed

9 files changed

+892
-726
lines changed

lib/xdrgen/generators/go.rb

Lines changed: 430 additions & 130 deletions
Large diffs are not rendered by default.

spec/output/generator_spec_go/block_comments.x/MyXDR_generated.go

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,22 @@ var XdrFilesSHA256 = map[string]string{
2323
"spec/fixtures/generator/block_comments.x": "e13131bc4134f38da17b9d5e9f67d2695a69ef98e3ef272833f4c18d0cc88a30",
2424
}
2525

26-
var ErrMaxDecodingDepthReached = errors.New("maximum decoding depth reached")
27-
2826
type xdrType interface {
2927
xdrType()
3028
}
3129

32-
type decoderFrom interface {
33-
DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error)
34-
}
35-
36-
// Unmarshal reads an xdr element from `r` into `v`.
37-
func Unmarshal(r io.Reader, v interface{}) (int, error) {
38-
return UnmarshalWithOptions(r, v, xdr.DefaultDecodeOptions)
39-
}
30+
// ErrMaxDecodingDepthReached is returned when the maximum decoding depth is
31+
// exceeded. This prevents stack overflow from deeply nested structures.
32+
var ErrMaxDecodingDepthReached = errors.New("maximum decoding depth reached")
4033

41-
// UnmarshalWithOptions works like Unmarshal but uses decoding options.
42-
func UnmarshalWithOptions(r io.Reader, v interface{}, options xdr.DecodeOptions) (int, error) {
43-
if decodable, ok := v.(decoderFrom); ok {
44-
d := xdr.NewDecoderWithOptions(r, options)
45-
return decodable.DecodeFrom(d, options.MaxDepth)
34+
// Unmarshal reads an xdr element from `data` into `v`.
35+
func Unmarshal(data []byte, v interface{}) (int, error) {
36+
if decodable, ok := v.(xdr.DecoderFrom); ok {
37+
d := xdr.NewDecoder(data)
38+
return decodable.DecodeFrom(d, d.MaxDepth())
4639
}
4740
// delegate to xdr package's Unmarshal
48-
return xdr.UnmarshalWithOptions(r, v, options)
41+
return xdr.Unmarshal(data, v)
4942
}
5043

5144
// Marshal writes an xdr element `v` into `w`.
@@ -74,15 +67,18 @@ type AccountFlags int32
7467
const (
7568
AccountFlagsAuthRequiredFlag AccountFlags = 1
7669
)
70+
const (
71+
_AccountFlags_Min int32 = 1
72+
_AccountFlags_Max int32 = 1
73+
)
7774
var accountFlagsMap = map[int32]string{
7875
1: "AccountFlagsAuthRequiredFlag",
7976
}
8077

8178
// ValidEnum validates a proposed value for this enum. Implements
8279
// the Enum interface for AccountFlags
8380
func (e AccountFlags) ValidEnum(v int32) bool {
84-
_, ok := accountFlagsMap[v]
85-
return ok
81+
return v >= _AccountFlags_Min && v <= _AccountFlags_Max
8682
}
8783
// String returns the name of `e`
8884
func (e AccountFlags) String() string {
@@ -92,24 +88,23 @@ func (e AccountFlags) String() string {
9288

9389
// EncodeTo encodes this value using the Encoder.
9490
func (e AccountFlags) EncodeTo(enc *xdr.Encoder) error {
95-
if _, ok := accountFlagsMap[int32(e)]; !ok {
91+
if int32(e) < _AccountFlags_Min || int32(e) > _AccountFlags_Max {
9692
return fmt.Errorf("'%d' is not a valid AccountFlags enum value", e)
9793
}
9894
_, err := enc.EncodeInt(int32(e))
9995
return err
10096
}
101-
var _ decoderFrom = (*AccountFlags)(nil)
102-
// DecodeFrom decodes this value using the Decoder.
97+
var _ xdr.DecoderFrom = (*AccountFlags)(nil)
98+
// DecodeFrom decodes this value from the given decoder.
10399
func (e *AccountFlags) DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error) {
104100
if maxDepth == 0 {
105101
return 0, fmt.Errorf("decoding AccountFlags: %w", ErrMaxDecodingDepthReached)
106102
}
107-
maxDepth -= 1
108103
v, n, err := d.DecodeInt()
109104
if err != nil {
110105
return n, fmt.Errorf("decoding AccountFlags: %w", err)
111106
}
112-
if _, ok := accountFlagsMap[v]; !ok {
107+
if v < _AccountFlags_Min || v > _AccountFlags_Max {
113108
return n, fmt.Errorf("'%d' is not a valid AccountFlags enum value", v)
114109
}
115110
*e = AccountFlags(v)
@@ -125,11 +120,8 @@ func (s AccountFlags) MarshalBinary() ([]byte, error) {
125120

126121
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
127122
func (s *AccountFlags) UnmarshalBinary(inp []byte) error {
128-
r := bytes.NewReader(inp)
129-
o := xdr.DefaultDecodeOptions
130-
o.MaxInputLen = len(inp)
131-
d := xdr.NewDecoderWithOptions(r, o)
132-
_, err := s.DecodeFrom(d, o.MaxDepth)
123+
d := xdr.NewDecoder(inp)
124+
_, err := s.DecodeFrom(d, d.MaxDepth())
133125
return err
134126
}
135127

spec/output/generator_spec_go/const.x/MyXDR_generated.go

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,22 @@ var XdrFilesSHA256 = map[string]string{
2323
"spec/fixtures/generator/const.x": "0bff3b37592fcc16cad2fe10b9a72f5d39d033a114917c24e86a9ebd9cda9c37",
2424
}
2525

26-
var ErrMaxDecodingDepthReached = errors.New("maximum decoding depth reached")
27-
2826
type xdrType interface {
2927
xdrType()
3028
}
3129

32-
type decoderFrom interface {
33-
DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error)
34-
}
35-
36-
// Unmarshal reads an xdr element from `r` into `v`.
37-
func Unmarshal(r io.Reader, v interface{}) (int, error) {
38-
return UnmarshalWithOptions(r, v, xdr.DefaultDecodeOptions)
39-
}
30+
// ErrMaxDecodingDepthReached is returned when the maximum decoding depth is
31+
// exceeded. This prevents stack overflow from deeply nested structures.
32+
var ErrMaxDecodingDepthReached = errors.New("maximum decoding depth reached")
4033

41-
// UnmarshalWithOptions works like Unmarshal but uses decoding options.
42-
func UnmarshalWithOptions(r io.Reader, v interface{}, options xdr.DecodeOptions) (int, error) {
43-
if decodable, ok := v.(decoderFrom); ok {
44-
d := xdr.NewDecoderWithOptions(r, options)
45-
return decodable.DecodeFrom(d, options.MaxDepth)
34+
// Unmarshal reads an xdr element from `data` into `v`.
35+
func Unmarshal(data []byte, v interface{}) (int, error) {
36+
if decodable, ok := v.(xdr.DecoderFrom); ok {
37+
d := xdr.NewDecoder(data)
38+
return decodable.DecodeFrom(d, d.MaxDepth())
4639
}
4740
// delegate to xdr package's Unmarshal
48-
return xdr.UnmarshalWithOptions(r, v, options)
41+
return xdr.Unmarshal(data, v)
4942
}
5043

5144
// Marshal writes an xdr element `v` into `w`.
@@ -77,14 +70,16 @@ type TestArray [Foo]int32
7770
// EncodeTo encodes this value using the Encoder.
7871
func (s *TestArray) EncodeTo(e *xdr.Encoder) error {
7972
var err error
80-
if _, err = e.EncodeInt(int32(s)); err != nil {
73+
for i := 0; i < len(s); i++ {
74+
if _, err = e.EncodeInt(int32(s[i])); err != nil {
8175
return err
8276
}
77+
}
8378
return nil
8479
}
8580

86-
var _ decoderFrom = (*TestArray)(nil)
87-
// DecodeFrom decodes this value using the Decoder.
81+
var _ xdr.DecoderFrom = (*TestArray)(nil)
82+
// DecodeFrom decodes this value from the given decoder.
8883
func (s *TestArray) DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error) {
8984
if maxDepth == 0 {
9085
return 0, fmt.Errorf("decoding TestArray: %w", ErrMaxDecodingDepthReached)
@@ -93,11 +88,13 @@ func (s *TestArray) DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error) {
9388
var err error
9489
var n, nTmp int
9590
var v [Foo]int32
96-
v, nTmp, err = d.DecodeInt()
91+
for i := 0; i < len(v); i++ {
92+
v[i], nTmp, err = d.DecodeInt()
9793
n += nTmp
9894
if err != nil {
9995
return n, fmt.Errorf("decoding Int: %w", err)
10096
}
97+
}
10198
*s = TestArray(v)
10299
return n, nil
103100
}
@@ -112,11 +109,8 @@ func (s TestArray) MarshalBinary() ([]byte, error) {
112109

113110
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
114111
func (s *TestArray) UnmarshalBinary(inp []byte) error {
115-
r := bytes.NewReader(inp)
116-
o := xdr.DefaultDecodeOptions
117-
o.MaxInputLen = len(inp)
118-
d := xdr.NewDecoderWithOptions(r, o)
119-
_, err := s.DecodeFrom(d, o.MaxDepth)
112+
d := xdr.NewDecoder(inp)
113+
_, err := s.DecodeFrom(d, d.MaxDepth())
120114
return err
121115
}
122116

@@ -142,14 +136,19 @@ func (e TestArray2) XDRMaxSize() int {
142136
// EncodeTo encodes this value using the Encoder.
143137
func (s TestArray2) EncodeTo(e *xdr.Encoder) error {
144138
var err error
145-
if _, err = e.EncodeInt(int32(s)); err != nil {
139+
if _, err = e.EncodeUint(uint32(len(s))); err != nil {
140+
return err
141+
}
142+
for i := 0; i < len(s); i++ {
143+
if _, err = e.EncodeInt(int32(s[i])); err != nil {
146144
return err
147145
}
146+
}
148147
return nil
149148
}
150149

151-
var _ decoderFrom = (*TestArray2)(nil)
152-
// DecodeFrom decodes this value using the Decoder.
150+
var _ xdr.DecoderFrom = (*TestArray2)(nil)
151+
// DecodeFrom decodes this value from the given decoder.
153152
func (s *TestArray2) DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error) {
154153
if maxDepth == 0 {
155154
return 0, fmt.Errorf("decoding TestArray2: %w", ErrMaxDecodingDepthReached)
@@ -158,10 +157,33 @@ func (s *TestArray2) DecodeFrom(d *xdr.Decoder, maxDepth uint) (int, error) {
158157
var err error
159158
var n, nTmp int
160159
var v []int32
161-
v, nTmp, err = d.DecodeInt()
160+
var l uint32
161+
l, nTmp, err = d.DecodeUint()
162+
n += nTmp
163+
if err != nil {
164+
return n, fmt.Errorf("decoding Int: %w", err)
165+
}
166+
if l > 1 {
167+
return n, fmt.Errorf("decoding int32: data size (%d) exceeds size limit (1)", l)
168+
}
169+
if l == 0 {
170+
v = v[:0]
171+
} else {
172+
if uint(d.Remaining()) < uint(l) {
173+
return n, fmt.Errorf("decoding int32: length (%d) exceeds remaining input length (%d)", l, d.Remaining())
174+
}
175+
if cap(v) >= int(l) {
176+
v = v[:l]
177+
} else {
178+
v = make([]int32, l)
179+
}
180+
for i := uint32(0); i < l; i++ {
181+
v[i], nTmp, err = d.DecodeInt()
162182
n += nTmp
163183
if err != nil {
164184
return n, fmt.Errorf("decoding Int: %w", err)
185+
}
186+
}
165187
}
166188
*s = TestArray2(v)
167189
return n, nil
@@ -177,11 +199,8 @@ func (s TestArray2) MarshalBinary() ([]byte, error) {
177199

178200
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
179201
func (s *TestArray2) UnmarshalBinary(inp []byte) error {
180-
r := bytes.NewReader(inp)
181-
o := xdr.DefaultDecodeOptions
182-
o.MaxInputLen = len(inp)
183-
d := xdr.NewDecoderWithOptions(r, o)
184-
_, err := s.DecodeFrom(d, o.MaxDepth)
202+
d := xdr.NewDecoder(inp)
203+
_, err := s.DecodeFrom(d, d.MaxDepth())
185204
return err
186205
}
187206

0 commit comments

Comments
 (0)