package encoder import ( "fmt" "strings" "unsafe" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) type OpFlags uint8 const ( AnonymousHeadFlags OpFlags = 1 << 0 AnonymousKeyFlags OpFlags = 1 << 1 IndirectFlags OpFlags = 1 << 2 IsTaggedKeyFlags OpFlags = 1 << 3 NilCheckFlags OpFlags = 1 << 4 AddrForMarshalerFlags OpFlags = 1 << 5 IsNextOpPtrTypeFlags OpFlags = 1 << 6 IsNilableTypeFlags OpFlags = 1 << 7 ) type Opcode struct { Op OpType // operation type Idx uint32 // offset to access ptr Next *Opcode // next opcode End *Opcode // array/slice/struct/map end NextField *Opcode // next struct field Key string // struct field key Offset uint32 // offset size from struct header PtrNum uint8 // pointer number: e.g. double pointer is 2. Flags OpFlags NumBitSize uint8 _ [1]uint8 // 1 Type *runtime.Type // go type PrevField *Opcode // prev struct field Jmp *CompiledCode // for recursive call ElemIdx uint32 // offset to access array/slice/map elem Length uint32 // offset to access slice/map length or array length MapIter uint32 // offset to access map iterator MapPos uint32 // offset to access position list for sorted map Indent uint32 // indent number Size uint32 // array/slice elem size DisplayIdx uint32 // opcode index DisplayKey string // key text to display } func (c *Opcode) ToHeaderType(isString bool) OpType { switch c.Op { case OpInt: if isString { return OpStructHeadIntString } return OpStructHeadInt case OpIntPtr: if isString { return OpStructHeadIntPtrString } return OpStructHeadIntPtr case OpUint: if isString { return OpStructHeadUintString } return OpStructHeadUint case OpUintPtr: if isString { return OpStructHeadUintPtrString } return OpStructHeadUintPtr case OpFloat32: if isString { return OpStructHeadFloat32String } return OpStructHeadFloat32 case OpFloat32Ptr: if isString { return OpStructHeadFloat32PtrString } return OpStructHeadFloat32Ptr case OpFloat64: if isString { return OpStructHeadFloat64String } return OpStructHeadFloat64 case OpFloat64Ptr: if isString { return OpStructHeadFloat64PtrString } return OpStructHeadFloat64Ptr case OpString: if isString { return OpStructHeadStringString } return OpStructHeadString case OpStringPtr: if isString { return OpStructHeadStringPtrString } return OpStructHeadStringPtr case OpNumber: if isString { return OpStructHeadNumberString } return OpStructHeadNumber case OpNumberPtr: if isString { return OpStructHeadNumberPtrString } return OpStructHeadNumberPtr case OpBool: if isString { return OpStructHeadBoolString } return OpStructHeadBool case OpBoolPtr: if isString { return OpStructHeadBoolPtrString } return OpStructHeadBoolPtr case OpBytes: return OpStructHeadBytes case OpBytesPtr: return OpStructHeadBytesPtr case OpMap: return OpStructHeadMap case OpMapPtr: c.Op = OpMap return OpStructHeadMapPtr case OpArray: return OpStructHeadArray case OpArrayPtr: c.Op = OpArray return OpStructHeadArrayPtr case OpSlice: return OpStructHeadSlice case OpSlicePtr: c.Op = OpSlice return OpStructHeadSlicePtr case OpMarshalJSON: return OpStructHeadMarshalJSON case OpMarshalJSONPtr: return OpStructHeadMarshalJSONPtr case OpMarshalText: return OpStructHeadMarshalText case OpMarshalTextPtr: return OpStructHeadMarshalTextPtr } return OpStructHead } func (c *Opcode) ToFieldType(isString bool) OpType { switch c.Op { case OpInt: if isString { return OpStructFieldIntString } return OpStructFieldInt case OpIntPtr: if isString { return OpStructFieldIntPtrString } return OpStructFieldIntPtr case OpUint: if isString { return OpStructFieldUintString } return OpStructFieldUint case OpUintPtr: if isString { return OpStructFieldUintPtrString } return OpStructFieldUintPtr case OpFloat32: if isString { return OpStructFieldFloat32String } return OpStructFieldFloat32 case OpFloat32Ptr: if isString { return OpStructFieldFloat32PtrString } return OpStructFieldFloat32Ptr case OpFloat64: if isString { return OpStructFieldFloat64String } return OpStructFieldFloat64 case OpFloat64Ptr: if isString { return OpStructFieldFloat64PtrString } return OpStructFieldFloat64Ptr case OpString: if isString { return OpStructFieldStringString } return OpStructFieldString case OpStringPtr: if isString { return OpStructFieldStringPtrString } return OpStructFieldStringPtr case OpNumber: if isString { return OpStructFieldNumberString } return OpStructFieldNumber case OpNumberPtr: if isString { return OpStructFieldNumberPtrString } return OpStructFieldNumberPtr case OpBool: if isString { return OpStructFieldBoolString } return OpStructFieldBool case OpBoolPtr: if isString { return OpStructFieldBoolPtrString } return OpStructFieldBoolPtr case OpBytes: return OpStructFieldBytes case OpBytesPtr: return OpStructFieldBytesPtr case OpMap: return OpStructFieldMap case OpMapPtr: c.Op = OpMap return OpStructFieldMapPtr case OpArray: return OpStructFieldArray case OpArrayPtr: c.Op = OpArray return OpStructFieldArrayPtr case OpSlice: return OpStructFieldSlice case OpSlicePtr: c.Op = OpSlice return OpStructFieldSlicePtr case OpMarshalJSON: return OpStructFieldMarshalJSON case OpMarshalJSONPtr: return OpStructFieldMarshalJSONPtr case OpMarshalText: return OpStructFieldMarshalText case OpMarshalTextPtr: return OpStructFieldMarshalTextPtr } return OpStructField } func newOpCode(ctx *compileContext, op OpType) *Opcode { return newOpCodeWithNext(ctx, op, newEndOp(ctx)) } func opcodeOffset(idx int) uint32 { return uint32(idx) * uintptrSize } func copyOpcode(code *Opcode) *Opcode { codeMap := map[uintptr]*Opcode{} return code.copy(codeMap) } func newOpCodeWithNext(ctx *compileContext, op OpType, next *Opcode) *Opcode { return &Opcode{ Op: op, Idx: opcodeOffset(ctx.ptrIndex), Next: next, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newEndOp(ctx *compileContext) *Opcode { return newOpCodeWithNext(ctx, OpEnd, nil) } func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode { if c == nil { return nil } addr := uintptr(unsafe.Pointer(c)) if code, exists := codeMap[addr]; exists { return code } copied := &Opcode{ Op: c.Op, Key: c.Key, PtrNum: c.PtrNum, NumBitSize: c.NumBitSize, Flags: c.Flags, Idx: c.Idx, Offset: c.Offset, Type: c.Type, DisplayIdx: c.DisplayIdx, DisplayKey: c.DisplayKey, ElemIdx: c.ElemIdx, Length: c.Length, MapIter: c.MapIter, MapPos: c.MapPos, Size: c.Size, Indent: c.Indent, } codeMap[addr] = copied copied.End = c.End.copy(codeMap) copied.PrevField = c.PrevField.copy(codeMap) copied.NextField = c.NextField.copy(codeMap) copied.Next = c.Next.copy(codeMap) copied.Jmp = c.Jmp return copied } func (c *Opcode) BeforeLastCode() *Opcode { code := c for { var nextCode *Opcode switch code.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: nextCode = code.End default: nextCode = code.Next } if nextCode.Op == OpEnd { return code } code = nextCode } } func (c *Opcode) TotalLength() int { var idx int for code := c; code.Op != OpEnd; { idx = int(code.Idx / uintptrSize) if code.Op == OpRecursiveEnd { break } switch code.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: code = code.End default: code = code.Next } } return idx + 2 // opEnd + 1 } func (c *Opcode) decOpcodeIndex() { for code := c; code.Op != OpEnd; { code.DisplayIdx-- if code.Idx > 0 { code.Idx -= uintptrSize } if code.ElemIdx > 0 { code.ElemIdx -= uintptrSize } if code.MapIter > 0 { code.MapIter -= uintptrSize } if code.Length > 0 && code.Op.CodeType() != CodeArrayHead && code.Op.CodeType() != CodeArrayElem { code.Length -= uintptrSize } switch code.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: code = code.End default: code = code.Next } } } func (c *Opcode) decIndent() { for code := c; code.Op != OpEnd; { code.Indent-- switch code.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: code = code.End default: code = code.Next } } } func (c *Opcode) dumpHead(code *Opcode) string { var length uint32 if code.Op.CodeType() == CodeArrayHead { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, length, ) } func (c *Opcode) dumpMapHead(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, code.Length/uintptrSize, code.MapIter/uintptrSize, ) } func (c *Opcode) dumpMapEnd(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.MapPos/uintptrSize, code.Length/uintptrSize, ) } func (c *Opcode) dumpElem(code *Opcode) string { var length uint32 if code.Op.CodeType() == CodeArrayElem { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, length, code.Size, ) } func (c *Opcode) dumpField(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][key:%s][offset:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.DisplayKey, code.Offset, ) } func (c *Opcode) dumpKey(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.ElemIdx/uintptrSize, code.Length/uintptrSize, code.MapIter/uintptrSize, ) } func (c *Opcode) dumpValue(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, code.MapIter/uintptrSize, ) } func (c *Opcode) Dump() string { codes := []string{} for code := c; code.Op != OpEnd; { switch code.Op.CodeType() { case CodeSliceHead: codes = append(codes, c.dumpHead(code)) code = code.Next case CodeMapHead: codes = append(codes, c.dumpMapHead(code)) code = code.Next case CodeArrayElem, CodeSliceElem: codes = append(codes, c.dumpElem(code)) code = code.End case CodeMapKey: codes = append(codes, c.dumpKey(code)) code = code.End case CodeMapValue: codes = append(codes, c.dumpValue(code)) code = code.Next case CodeMapEnd: codes = append(codes, c.dumpMapEnd(code)) code = code.Next case CodeStructField: codes = append(codes, c.dumpField(code)) code = code.Next case CodeStructEnd: codes = append(codes, c.dumpField(code)) code = code.Next default: codes = append(codes, fmt.Sprintf( "[%d]%s%s ([idx:%d])", code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, )) code = code.Next } } return strings.Join(codes, "\n") } func prevField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode { if _, exists := removedFields[code]; exists { return prevField(code.PrevField, removedFields) } return code } func nextField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode { if _, exists := removedFields[code]; exists { return nextField(code.NextField, removedFields) } return code } func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) { prev := prevField(cur.PrevField, removedFields) prev.NextField = nextField(cur.NextField, removedFields) code := prev fcode := cur for { var nextCode *Opcode switch code.Op.CodeType() { case CodeArrayElem, CodeSliceElem, CodeMapKey: nextCode = code.End default: nextCode = code.Next } if nextCode == fcode { code.Next = fcode.Next break } else if nextCode.Op == OpEnd { break } code = nextCode } } func newSliceHeaderCode(ctx *compileContext) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() length := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpSlice, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Length: length, Indent: ctx.indent, } } func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode { return &Opcode{ Op: OpSliceElem, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: head.Length, Indent: ctx.indent, Size: uint32(size), } } func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpArray, Idx: idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Indent: ctx.indent, Length: uint32(alen), } } func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode { return &Opcode{ Op: OpArrayElem, Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: uint32(length), Indent: ctx.indent, Size: uint32(size), } } func newMapHeaderCode(ctx *compileContext) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() length := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() mapIter := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMap, Idx: idx, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, ElemIdx: elemIdx, Length: length, MapIter: mapIter, Indent: ctx.indent, } } func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode { return &Opcode{ Op: OpMapKey, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: head.Length, MapIter: head.MapIter, Indent: ctx.indent, } } func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode { return &Opcode{ Op: OpMapValue, Idx: opcodeOffset(ctx.ptrIndex), DisplayIdx: ctx.opcodeIndex, ElemIdx: head.ElemIdx, Length: head.Length, MapIter: head.MapIter, Indent: ctx.indent, } } func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode { mapPos := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() idx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMapEnd, Idx: idx, Next: newEndOp(ctx), DisplayIdx: ctx.opcodeIndex, Length: head.Length, MapPos: mapPos, Indent: ctx.indent, } } func newInterfaceCode(ctx *compileContext) *Opcode { return &Opcode{ Op: OpInterface, Idx: opcodeOffset(ctx.ptrIndex), Next: newEndOp(ctx), Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, } } func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode { return &Opcode{ Op: OpRecursive, Idx: opcodeOffset(ctx.ptrIndex), Next: newEndOp(ctx), Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, Jmp: jmp, } }