package encoder import ( "fmt" "math" "strings" "unsafe" "github.com/goccy/go-json/internal/runtime" ) const uintptrSize = 4 << (^uintptr(0) >> 63) type Opcode struct { Op OpType // operation type Type *runtime.Type // go type DisplayIdx int // opcode index Key []byte // struct field key EscapedKey []byte // struct field key ( HTML escaped ) PtrNum int // pointer number: e.g. double pointer is 2. DisplayKey string // key text to display IsTaggedKey bool // whether tagged key AnonymousKey bool // whether anonymous key AnonymousHead bool // whether anonymous head or not Indirect bool // whether indirect or not Nilcheck bool // whether needs to nilcheck or not AddrForMarshaler bool // whether needs to addr for marshaler or not IsNextOpPtrType bool // whether next operation is ptr type or not IsNilableType bool // whether type is nilable or not RshiftNum uint8 // use to take bit for judging whether negative integer or not Mask uint64 // mask for number Indent int // indent number Idx uintptr // offset to access ptr HeadIdx uintptr // offset to access slice/struct head ElemIdx uintptr // offset to access array/slice/map elem Length uintptr // offset to access slice/map length or array length MapIter uintptr // offset to access map iterator MapPos uintptr // offset to access position list for sorted map Offset uintptr // offset size from struct header Size uintptr // array/slice elem size MapKey *Opcode // map key MapValue *Opcode // map value Elem *Opcode // array/slice elem End *Opcode // array/slice/struct/map end PrevField *Opcode // prev struct field NextField *Opcode // next struct field Next *Opcode // next opcode Jmp *CompiledCode // for recursive call } func rshitNum(bitSize uint8) uint8 { return bitSize - 1 } func (c *Opcode) setMaskAndRshiftNum(bitSize uint8) { switch bitSize { case 8: c.Mask = math.MaxUint8 case 16: c.Mask = math.MaxUint16 case 32: c.Mask = math.MaxUint32 case 64: c.Mask = math.MaxUint64 } c.RshiftNum = rshitNum(bitSize) } func (c *Opcode) ToHeaderType() OpType { switch c.Op { case OpInt: return OpStructHeadInt case OpIntPtr: return OpStructHeadIntPtr case OpUint: return OpStructHeadUint case OpUintPtr: return OpStructHeadUintPtr case OpFloat32: return OpStructHeadFloat32 case OpFloat32Ptr: return OpStructHeadFloat32Ptr case OpFloat64: return OpStructHeadFloat64 case OpFloat64Ptr: return OpStructHeadFloat64Ptr case OpString: return OpStructHeadString case OpStringPtr: return OpStructHeadStringPtr case OpNumber: return OpStructHeadNumber case OpNumberPtr: return OpStructHeadNumberPtr case OpBool: return OpStructHeadBool case OpBoolPtr: 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() OpType { switch c.Op { case OpInt: return OpStructFieldInt case OpIntPtr: return OpStructFieldIntPtr case OpUint: return OpStructFieldUint case OpUintPtr: return OpStructFieldUintPtr case OpFloat32: return OpStructFieldFloat32 case OpFloat32Ptr: return OpStructFieldFloat32Ptr case OpFloat64: return OpStructFieldFloat64 case OpFloat64Ptr: return OpStructFieldFloat64Ptr case OpString: return OpStructFieldString case OpStringPtr: return OpStructFieldStringPtr case OpNumber: return OpStructFieldNumber case OpNumberPtr: return OpStructFieldNumberPtr case OpBool: return OpStructFieldBool case OpBoolPtr: 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) uintptr { return uintptr(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, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Indent: ctx.indent, Idx: opcodeOffset(ctx.ptrIndex), Next: next, } } 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, Type: c.Type, DisplayIdx: c.DisplayIdx, Key: c.Key, EscapedKey: c.EscapedKey, DisplayKey: c.DisplayKey, PtrNum: c.PtrNum, Mask: c.Mask, RshiftNum: c.RshiftNum, IsTaggedKey: c.IsTaggedKey, AnonymousKey: c.AnonymousKey, AnonymousHead: c.AnonymousHead, Indirect: c.Indirect, Nilcheck: c.Nilcheck, AddrForMarshaler: c.AddrForMarshaler, IsNextOpPtrType: c.IsNextOpPtrType, IsNilableType: c.IsNilableType, Indent: c.Indent, Idx: c.Idx, HeadIdx: c.HeadIdx, ElemIdx: c.ElemIdx, Length: c.Length, MapIter: c.MapIter, MapPos: c.MapPos, Offset: c.Offset, Size: c.Size, } codeMap[addr] = copied copied.MapKey = c.MapKey.copy(codeMap) copied.MapValue = c.MapValue.copy(codeMap) copied.Elem = c.Elem.copy(codeMap) 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-- code.Idx -= uintptrSize if code.HeadIdx > 0 { code.HeadIdx -= 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 uintptr if code.Op.CodeType() == CodeArrayHead { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d])`, code.DisplayIdx, strings.Repeat("-", code.Indent), code.Op, code.Idx/uintptrSize, code.HeadIdx/uintptrSize, code.ElemIdx/uintptrSize, length, ) } func (c *Opcode) dumpMapHead(code *Opcode) string { return fmt.Sprintf( `[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][mapIter:%d])`, code.DisplayIdx, strings.Repeat("-", code.Indent), code.Op, code.Idx/uintptrSize, code.HeadIdx/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("-", code.Indent), code.Op, code.Idx/uintptrSize, code.MapPos/uintptrSize, code.Length/uintptrSize, ) } func (c *Opcode) dumpElem(code *Opcode) string { var length uintptr if code.Op.CodeType() == CodeArrayElem { length = code.Length } else { length = code.Length / uintptrSize } return fmt.Sprintf( `[%d]%s%s ([idx:%d][headIdx:%d][elemIdx:%d][length:%d][size:%d])`, code.DisplayIdx, strings.Repeat("-", code.Indent), code.Op, code.Idx/uintptrSize, code.HeadIdx/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][headIdx:%d])`, code.DisplayIdx, strings.Repeat("-", code.Indent), code.Op, code.Idx/uintptrSize, code.DisplayKey, code.Offset, code.HeadIdx/uintptrSize, ) } 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("-", 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("-", 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("-", 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, DisplayIdx: ctx.opcodeIndex, Idx: idx, HeadIdx: idx, ElemIdx: elemIdx, Length: length, Indent: ctx.indent, } } func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode { return &Opcode{ Op: OpSliceElem, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), HeadIdx: head.Idx, ElemIdx: head.ElemIdx, Length: head.Length, Indent: ctx.indent, Size: size, } } func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() elemIdx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpArray, DisplayIdx: ctx.opcodeIndex, Idx: idx, HeadIdx: idx, ElemIdx: elemIdx, Indent: ctx.indent, Length: uintptr(alen), } } func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode { return &Opcode{ Op: OpArrayElem, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), ElemIdx: head.ElemIdx, HeadIdx: head.HeadIdx, Length: uintptr(length), Indent: ctx.indent, Size: 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, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Idx: idx, ElemIdx: elemIdx, Length: length, MapIter: mapIter, Indent: ctx.indent, } } func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode { return &Opcode{ Op: OpMapKey, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), ElemIdx: head.ElemIdx, Length: head.Length, MapIter: head.MapIter, Indent: ctx.indent, } } func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode { return &Opcode{ Op: OpMapValue, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), 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, DisplayIdx: ctx.opcodeIndex, Idx: idx, Length: head.Length, MapPos: mapPos, Indent: ctx.indent, Next: newEndOp(ctx), } } func newInterfaceCode(ctx *compileContext) *Opcode { return &Opcode{ Op: OpInterface, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), Indent: ctx.indent, Next: newEndOp(ctx), } } func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode { return &Opcode{ Op: OpRecursive, Type: ctx.typ, DisplayIdx: ctx.opcodeIndex, Idx: opcodeOffset(ctx.ptrIndex), Indent: ctx.indent, Next: newEndOp(ctx), Jmp: jmp, } }