mirror of https://github.com/goccy/go-json.git
Optimize opcode.idx and seenPtr
This commit is contained in:
parent
572d4842a5
commit
a6276c4d8e
20
encode.go
20
encode.go
|
@ -164,9 +164,9 @@ func (e *Encoder) encode(v interface{}) error {
|
||||||
}
|
}
|
||||||
ctx := codeSet.ctx.Get().(*encodeRuntimeContext)
|
ctx := codeSet.ctx.Get().(*encodeRuntimeContext)
|
||||||
p := uintptr(header.ptr)
|
p := uintptr(header.ptr)
|
||||||
ctx.reset()
|
|
||||||
ctx.init(p)
|
ctx.init(p)
|
||||||
err := e.run(ctx, code)
|
seenPtr := map[uintptr]struct{}{}
|
||||||
|
err := e.run(ctx, seenPtr, code)
|
||||||
if e.enabledIndent {
|
if e.enabledIndent {
|
||||||
codeSet.codeIndent.Put(code)
|
codeSet.codeIndent.Put(code)
|
||||||
} else {
|
} else {
|
||||||
|
@ -211,7 +211,6 @@ func (e *Encoder) encode(v interface{}) error {
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return &encodeRuntimeContext{
|
return &encodeRuntimeContext{
|
||||||
ptrs: make([]uintptr, codeLength),
|
ptrs: make([]uintptr, codeLength),
|
||||||
seenPtr: map[uintptr]struct{}{},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -219,19 +218,22 @@ func (e *Encoder) encode(v interface{}) error {
|
||||||
cachedOpcode.set(typeptr, codeSet)
|
cachedOpcode.set(typeptr, codeSet)
|
||||||
p := uintptr(header.ptr)
|
p := uintptr(header.ptr)
|
||||||
ctx := codeSet.ctx.Get().(*encodeRuntimeContext)
|
ctx := codeSet.ctx.Get().(*encodeRuntimeContext)
|
||||||
ctx.reset()
|
|
||||||
ctx.init(p)
|
ctx.init(p)
|
||||||
|
|
||||||
|
var c *opcode
|
||||||
if e.enabledIndent {
|
if e.enabledIndent {
|
||||||
err := e.run(ctx, codeIndent)
|
c = codeIndent
|
||||||
codeSet.ctx.Put(ctx)
|
} else {
|
||||||
return err
|
c = code
|
||||||
}
|
}
|
||||||
if err := e.run(ctx, code); err != nil {
|
|
||||||
|
seenPtr := map[uintptr]struct{}{}
|
||||||
|
if err := e.run(ctx, seenPtr, c); err != nil {
|
||||||
codeSet.ctx.Put(ctx)
|
codeSet.ctx.Put(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
codeSet.ctx.Put(ctx)
|
codeSet.ctx.Put(ctx)
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) encodeInt(v int) {
|
func (e *Encoder) encodeInt(v int) {
|
||||||
|
|
|
@ -269,7 +269,8 @@ func (e *Encoder) compileInterface(ctx *encodeCompileContext) (*opcode, error) {
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opInterface,
|
op: opInterface,
|
||||||
typ: ctx.typ,
|
typ: ctx.typ,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
next: newEndOp(ctx),
|
next: newEndOp(ctx),
|
||||||
},
|
},
|
||||||
|
@ -590,7 +591,8 @@ func (e *Encoder) recursiveCode(ctx *encodeCompileContext, code *compiledCode) *
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opStructFieldRecursive,
|
op: opStructFieldRecursive,
|
||||||
typ: ctx.typ,
|
typ: ctx.typ,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
next: newEndOp(ctx),
|
next: newEndOp(ctx),
|
||||||
},
|
},
|
||||||
|
@ -726,7 +728,7 @@ func (e *Encoder) optimizeAnonymousFields(head *structFieldCode) {
|
||||||
if codeType == codeStructField {
|
if codeType == codeStructField {
|
||||||
if e.isNotExistsField(code.next.toStructFieldCode()) {
|
if e.isNotExistsField(code.next.toStructFieldCode()) {
|
||||||
code.next = code.nextField
|
code.next = code.nextField
|
||||||
diff := code.next.idx - code.idx
|
diff := code.next.displayIdx - code.displayIdx
|
||||||
for i := 0; i < diff; i++ {
|
for i := 0; i < diff; i++ {
|
||||||
code.next.decOpcodeIndex()
|
code.next.decOpcodeIndex()
|
||||||
}
|
}
|
||||||
|
@ -763,7 +765,7 @@ func (e *Encoder) anonymousStructFieldPairMap(typ *rtype, tags structTags, value
|
||||||
} else if f.op == opStructEnd {
|
} else if f.op == opStructEnd {
|
||||||
f.op = opStructAnonymousEnd
|
f.op = opStructAnonymousEnd
|
||||||
} else if existsKey {
|
} else if existsKey {
|
||||||
diff := f.nextField.idx - f.idx
|
diff := f.nextField.displayIdx - f.displayIdx
|
||||||
for i := 0; i < diff; i++ {
|
for i := 0; i < diff; i++ {
|
||||||
f.nextField.decOpcodeIndex()
|
f.nextField.decOpcodeIndex()
|
||||||
}
|
}
|
||||||
|
@ -814,7 +816,7 @@ func (e *Encoder) optimizeConflictAnonymousFields(anonymousFields map[string][]s
|
||||||
// head operation
|
// head operation
|
||||||
fieldPair.curField.op = opStructFieldAnonymousHead
|
fieldPair.curField.op = opStructFieldAnonymousHead
|
||||||
} else {
|
} else {
|
||||||
diff := fieldPair.curField.nextField.idx - fieldPair.curField.idx
|
diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx
|
||||||
for i := 0; i < diff; i++ {
|
for i := 0; i < diff; i++ {
|
||||||
fieldPair.curField.nextField.decOpcodeIndex()
|
fieldPair.curField.nextField.decOpcodeIndex()
|
||||||
}
|
}
|
||||||
|
@ -831,7 +833,7 @@ func (e *Encoder) optimizeConflictAnonymousFields(anonymousFields map[string][]s
|
||||||
// head operation
|
// head operation
|
||||||
fieldPair.curField.op = opStructFieldAnonymousHead
|
fieldPair.curField.op = opStructFieldAnonymousHead
|
||||||
} else {
|
} else {
|
||||||
diff := fieldPair.curField.nextField.idx - fieldPair.curField.idx
|
diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx
|
||||||
for i := 0; i < diff; i++ {
|
for i := 0; i < diff; i++ {
|
||||||
fieldPair.curField.nextField.decOpcodeIndex()
|
fieldPair.curField.nextField.decOpcodeIndex()
|
||||||
}
|
}
|
||||||
|
@ -920,7 +922,8 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
fieldCode := &structFieldCode{
|
fieldCode := &structFieldCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
typ: valueCode.typ,
|
typ: valueCode.typ,
|
||||||
idx: fieldOpcodeIndex,
|
displayIdx: fieldOpcodeIndex,
|
||||||
|
idx: uintptr(fieldOpcodeIndex) * 8,
|
||||||
next: valueCode,
|
next: valueCode,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
|
@ -960,7 +963,8 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opStructFieldHead,
|
op: opStructFieldHead,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
nextField: structEndCode,
|
nextField: structEndCode,
|
||||||
|
@ -972,7 +976,8 @@ func (e *Encoder) compileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode,
|
||||||
code = (*opcode)(unsafe.Pointer(head))
|
code = (*opcode)(unsafe.Pointer(head))
|
||||||
}
|
}
|
||||||
|
|
||||||
structEndCode.idx = ctx.opcodeIndex
|
structEndCode.displayIdx = ctx.opcodeIndex
|
||||||
|
structEndCode.idx = uintptr(ctx.opcodeIndex) * 8
|
||||||
ctx.incOpcodeIndex()
|
ctx.incOpcodeIndex()
|
||||||
|
|
||||||
if ctx.withIndent {
|
if ctx.withIndent {
|
||||||
|
|
|
@ -60,11 +60,6 @@ func (c *encodeCompileContext) decOpcodeIndex() {
|
||||||
|
|
||||||
type encodeRuntimeContext struct {
|
type encodeRuntimeContext struct {
|
||||||
ptrs []uintptr
|
ptrs []uintptr
|
||||||
seenPtr map[uintptr]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *encodeRuntimeContext) reset() {
|
|
||||||
c.seenPtr = map[uintptr]struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *encodeRuntimeContext) init(p uintptr) {
|
func (c *encodeRuntimeContext) init(p uintptr) {
|
||||||
|
|
|
@ -15,7 +15,8 @@ func copyOpcode(code *opcode) *opcode {
|
||||||
type opcodeHeader struct {
|
type opcodeHeader struct {
|
||||||
op opType
|
op opType
|
||||||
typ *rtype
|
typ *rtype
|
||||||
idx int
|
displayIdx int
|
||||||
|
idx uintptr
|
||||||
indent int
|
indent int
|
||||||
next *opcode
|
next *opcode
|
||||||
}
|
}
|
||||||
|
@ -24,6 +25,7 @@ func (h *opcodeHeader) copy(codeMap map[uintptr]*opcode) *opcodeHeader {
|
||||||
return &opcodeHeader{
|
return &opcodeHeader{
|
||||||
op: h.op,
|
op: h.op,
|
||||||
typ: h.typ,
|
typ: h.typ,
|
||||||
|
displayIdx: h.displayIdx,
|
||||||
idx: h.idx,
|
idx: h.idx,
|
||||||
indent: h.indent,
|
indent: h.indent,
|
||||||
next: h.next.copy(codeMap),
|
next: h.next.copy(codeMap),
|
||||||
|
@ -43,7 +45,8 @@ func newOpCodeWithNext(ctx *encodeCompileContext, op opType, next *opcode) *opco
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: op,
|
op: op,
|
||||||
typ: ctx.typ,
|
typ: ctx.typ,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
next: next,
|
next: next,
|
||||||
},
|
},
|
||||||
|
@ -79,7 +82,7 @@ func (c *opcode) beforeLastCode() *opcode {
|
||||||
func (c *opcode) length() int {
|
func (c *opcode) length() int {
|
||||||
var idx int
|
var idx int
|
||||||
for code := c; code.op != opEnd; {
|
for code := c; code.op != opEnd; {
|
||||||
idx = code.idx
|
idx = code.displayIdx
|
||||||
switch code.op.codeType() {
|
switch code.op.codeType() {
|
||||||
case codeArrayElem:
|
case codeArrayElem:
|
||||||
code = code.toArrayElemCode().end
|
code = code.toArrayElemCode().end
|
||||||
|
@ -96,7 +99,8 @@ func (c *opcode) length() int {
|
||||||
|
|
||||||
func (c *opcode) decOpcodeIndex() {
|
func (c *opcode) decOpcodeIndex() {
|
||||||
for code := c; code.op != opEnd; {
|
for code := c; code.op != opEnd; {
|
||||||
code.idx--
|
code.displayIdx--
|
||||||
|
code.idx -= 8
|
||||||
switch code.op.codeType() {
|
switch code.op.codeType() {
|
||||||
case codeArrayElem:
|
case codeArrayElem:
|
||||||
code = code.toArrayElemCode().end
|
code = code.toArrayElemCode().end
|
||||||
|
@ -153,22 +157,22 @@ func (c *opcode) dump() string {
|
||||||
indent := strings.Repeat(" ", code.indent)
|
indent := strings.Repeat(" ", code.indent)
|
||||||
switch code.op.codeType() {
|
switch code.op.codeType() {
|
||||||
case codeArrayElem:
|
case codeArrayElem:
|
||||||
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.idx, indent, code.op, unsafe.Pointer(code)))
|
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.displayIdx, indent, code.op, unsafe.Pointer(code)))
|
||||||
code = code.toArrayElemCode().end
|
code = code.toArrayElemCode().end
|
||||||
case codeSliceElem:
|
case codeSliceElem:
|
||||||
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.idx, indent, code.op, unsafe.Pointer(code)))
|
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.displayIdx, indent, code.op, unsafe.Pointer(code)))
|
||||||
code = code.toSliceElemCode().end
|
code = code.toSliceElemCode().end
|
||||||
case codeMapKey:
|
case codeMapKey:
|
||||||
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.idx, indent, code.op, unsafe.Pointer(code)))
|
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.displayIdx, indent, code.op, unsafe.Pointer(code)))
|
||||||
code = code.toMapKeyCode().end
|
code = code.toMapKeyCode().end
|
||||||
case codeStructField:
|
case codeStructField:
|
||||||
sf := code.toStructFieldCode()
|
sf := code.toStructFieldCode()
|
||||||
key := sf.displayKey
|
key := sf.displayKey
|
||||||
offset := sf.offset
|
offset := sf.offset
|
||||||
codes = append(codes, fmt.Sprintf("[%d]%s%s [%s:%d] ( %p )", code.idx, indent, code.op, key, offset, unsafe.Pointer(code)))
|
codes = append(codes, fmt.Sprintf("[%d]%s%s [%s:%d] ( %p )", code.displayIdx, indent, code.op, key, offset, unsafe.Pointer(code)))
|
||||||
code = code.next
|
code = code.next
|
||||||
default:
|
default:
|
||||||
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.idx, indent, code.op, unsafe.Pointer(code)))
|
codes = append(codes, fmt.Sprintf("[%d]%s%s ( %p )", code.displayIdx, indent, code.op, unsafe.Pointer(code)))
|
||||||
code = code.next
|
code = code.next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +229,8 @@ func newSliceHeaderCode(ctx *encodeCompileContext) *sliceHeaderCode {
|
||||||
return &sliceHeaderCode{
|
return &sliceHeaderCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opSliceHead,
|
op: opSliceHead,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -235,7 +240,8 @@ func newSliceElemCode(ctx *encodeCompileContext, size uintptr) *sliceElemCode {
|
||||||
return &sliceElemCode{
|
return &sliceElemCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opSliceElem,
|
op: opSliceElem,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
size: size,
|
size: size,
|
||||||
|
@ -308,7 +314,8 @@ func newArrayHeaderCode(ctx *encodeCompileContext, alen int) *arrayHeaderCode {
|
||||||
return &arrayHeaderCode{
|
return &arrayHeaderCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opArrayHead,
|
op: opArrayHead,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
len: uintptr(alen),
|
len: uintptr(alen),
|
||||||
|
@ -319,7 +326,8 @@ func newArrayElemCode(ctx *encodeCompileContext, alen int, size uintptr) *arrayE
|
||||||
return &arrayElemCode{
|
return &arrayElemCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opArrayElem,
|
op: opArrayElem,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
},
|
},
|
||||||
len: uintptr(alen),
|
len: uintptr(alen),
|
||||||
size: size,
|
size: size,
|
||||||
|
@ -538,7 +546,8 @@ func newMapHeaderCode(ctx *encodeCompileContext, withLoad bool) *mapHeaderCode {
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: op,
|
op: op,
|
||||||
typ: ctx.typ,
|
typ: ctx.typ,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -548,7 +557,8 @@ func newMapKeyCode(ctx *encodeCompileContext) *mapKeyCode {
|
||||||
return &mapKeyCode{
|
return &mapKeyCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opMapKey,
|
op: opMapKey,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -558,7 +568,8 @@ func newMapValueCode(ctx *encodeCompileContext) *mapValueCode {
|
||||||
return &mapValueCode{
|
return &mapValueCode{
|
||||||
opcodeHeader: &opcodeHeader{
|
opcodeHeader: &opcodeHeader{
|
||||||
op: opMapValue,
|
op: opMapValue,
|
||||||
idx: ctx.opcodeIndex,
|
displayIdx: ctx.opcodeIndex,
|
||||||
|
idx: uintptr(ctx.opcodeIndex) * 8,
|
||||||
indent: ctx.indent,
|
indent: ctx.indent,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
17
encode_vm.go
17
encode_vm.go
|
@ -11,17 +11,16 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func load(base uintptr, idx int) uintptr {
|
func load(base uintptr, idx uintptr) uintptr {
|
||||||
return *(*uintptr)(unsafe.Pointer(base + uintptr(idx)*8))
|
return *(*uintptr)(unsafe.Pointer(base + idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func store(base uintptr, idx int, p uintptr) {
|
func store(base uintptr, idx uintptr, p uintptr) {
|
||||||
*(*uintptr)(unsafe.Pointer(base + uintptr(idx)*8)) = p
|
*(*uintptr)(unsafe.Pointer(base + idx)) = p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
|
func (e *Encoder) run(ctx *encodeRuntimeContext, seenPtr map[uintptr]struct{}, code *opcode) error {
|
||||||
ctxptr := ctx.ptr()
|
ctxptr := ctx.ptr()
|
||||||
seenPtr := ctx.seenPtr
|
|
||||||
for {
|
for {
|
||||||
switch code.op {
|
switch code.op {
|
||||||
case opPtr:
|
case opPtr:
|
||||||
|
@ -144,10 +143,9 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
|
||||||
}
|
}
|
||||||
ctx := &encodeRuntimeContext{
|
ctx := &encodeRuntimeContext{
|
||||||
ptrs: make([]uintptr, c.length()),
|
ptrs: make([]uintptr, c.length()),
|
||||||
seenPtr: seenPtr,
|
|
||||||
}
|
}
|
||||||
ctx.init(uintptr(header.ptr))
|
ctx.init(uintptr(header.ptr))
|
||||||
if err := e.run(ctx, c); err != nil {
|
if err := e.run(ctx, seenPtr, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
code = ifaceCode.next
|
code = ifaceCode.next
|
||||||
|
@ -567,10 +565,9 @@ func (e *Encoder) run(ctx *encodeRuntimeContext, code *opcode) error {
|
||||||
recursiveCode := newRecursiveCode(recursive)
|
recursiveCode := newRecursiveCode(recursive)
|
||||||
ctx := &encodeRuntimeContext{
|
ctx := &encodeRuntimeContext{
|
||||||
ptrs: make([]uintptr, recursiveCode.length()),
|
ptrs: make([]uintptr, recursiveCode.length()),
|
||||||
seenPtr: seenPtr,
|
|
||||||
}
|
}
|
||||||
ctx.init(ptr)
|
ctx.init(ptr)
|
||||||
if err := e.run(ctx, recursiveCode); err != nil {
|
if err := e.run(ctx, seenPtr, recursiveCode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
code = recursive.next
|
code = recursive.next
|
||||||
|
|
Loading…
Reference in New Issue