go-json/internal/encoder/compiler.go

1220 lines
30 KiB
Go

package encoder
import (
"encoding"
"encoding/json"
"fmt"
"reflect"
"strings"
"sync/atomic"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
var (
marshalJSONType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
jsonNumberType = reflect.TypeOf(json.Number(""))
cachedOpcodeSets []*OpcodeSet
cachedOpcodeMap unsafe.Pointer // map[uintptr]*OpcodeSet
typeAddr = &runtime.TypeAddr{}
)
func init() {
typeAddr = runtime.AnalyzeTypeAddr()
cachedOpcodeSets = make([]*OpcodeSet, typeAddr.AddrRange)
}
func loadOpcodeMap() map[uintptr]*OpcodeSet {
p := atomic.LoadPointer(&cachedOpcodeMap)
return *(*map[uintptr]*OpcodeSet)(unsafe.Pointer(&p))
}
func storeOpcodeSet(typ uintptr, set *OpcodeSet, m map[uintptr]*OpcodeSet) {
newOpcodeMap := make(map[uintptr]*OpcodeSet, len(m)+1)
newOpcodeMap[typ] = set
for k, v := range m {
newOpcodeMap[k] = v
}
atomic.StorePointer(&cachedOpcodeMap, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap)))
}
func compileToGetCodeSetSlowPath(typeptr uintptr) (*OpcodeSet, error) {
opcodeMap := loadOpcodeMap()
if codeSet, exists := opcodeMap[typeptr]; exists {
return codeSet, nil
}
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
code, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
})
if err != nil {
return nil, err
}
code = copyOpcode(code)
codeLength := code.TotalLength()
codeSet := &OpcodeSet{
Code: code,
CodeLength: codeLength,
}
storeOpcodeSet(typeptr, codeSet, opcodeMap)
return codeSet, nil
}
func compileHead(ctx *compileContext) (*Opcode, error) {
typ := ctx.typ
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON(ctx)
case implementsMarshalText(typ):
return compileMarshalText(ctx)
}
isPtr := false
orgType := typ
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
isPtr = true
}
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON(ctx)
case implementsMarshalText(typ):
return compileMarshalText(ctx)
}
if typ.Kind() == reflect.Map {
if isPtr {
return compilePtr(ctx.withType(runtime.PtrTo(typ)))
}
return compileMap(ctx.withType(typ))
} else if typ.Kind() == reflect.Struct {
code, err := compileStruct(ctx.withType(typ), isPtr)
if err != nil {
return nil, err
}
optimizeStructEnd(code)
linkRecursiveCode(code)
return code, nil
} else if isPtr && typ.Implements(marshalTextType) {
typ = orgType
}
code, err := compile(ctx.withType(typ), isPtr)
if err != nil {
return nil, err
}
optimizeStructEnd(code)
linkRecursiveCode(code)
return code, nil
}
func linkRecursiveCode(c *Opcode) {
for code := c; code.Op != OpEnd && code.Op != OpStructFieldRecursiveEnd; {
switch code.Op {
case OpStructFieldRecursive, OpStructFieldRecursivePtr:
if code.Jmp.Linked {
code = code.Next
continue
}
code.Jmp.Code = copyOpcode(code.Jmp.Code)
c := code.Jmp.Code
c.End.Next = newEndOp(&compileContext{})
c.Op = c.Op.PtrHeadToHead()
beforeLastCode := c.End
lastCode := beforeLastCode.Next
lastCode.Idx = beforeLastCode.Idx + uintptrSize
lastCode.ElemIdx = lastCode.Idx + uintptrSize
// extend length to alloc slot for elemIdx
totalLength := uintptr(code.TotalLength() + 1)
nextTotalLength := uintptr(c.TotalLength() + 1)
c.End.Next.Op = OpStructFieldRecursiveEnd
code.Jmp.CurLen = totalLength
code.Jmp.NextLen = nextTotalLength
code.Jmp.Linked = true
linkRecursiveCode(code.Jmp.Code)
code = code.Next
continue
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
}
func optimizeStructEnd(c *Opcode) {
for code := c; code.Op != OpEnd; {
if code.Op == OpStructFieldRecursive || code.Op == OpStructFieldRecursivePtr {
// ignore if exists recursive operation
return
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
for code := c; code.Op != OpEnd; {
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
case CodeStructEnd:
switch code.Op {
case OpStructEnd:
prev := code.PrevField
prevOp := prev.Op.String()
if strings.Contains(prevOp, "Head") ||
strings.Contains(prevOp, "Slice") ||
strings.Contains(prevOp, "Array") ||
strings.Contains(prevOp, "Map") ||
strings.Contains(prevOp, "MarshalJSON") ||
strings.Contains(prevOp, "MarshalText") {
// not exists field
code = code.Next
break
}
if prev.Op != prev.Op.FieldToEnd() {
prev.Op = prev.Op.FieldToEnd()
prev.Next = code.Next
}
code = code.Next
default:
code = code.Next
}
default:
code = code.Next
}
}
}
func implementsMarshalJSON(typ *runtime.Type) bool {
if !typ.Implements(marshalJSONType) {
return false
}
if typ.Kind() != reflect.Ptr {
return true
}
// type kind is reflect.Ptr
if !typ.Elem().Implements(marshalJSONType) {
return true
}
// needs to dereference
return false
}
func implementsMarshalText(typ *runtime.Type) bool {
if !typ.Implements(marshalTextType) {
return false
}
if typ.Kind() != reflect.Ptr {
return true
}
// type kind is reflect.Ptr
if !typ.Elem().Implements(marshalTextType) {
return true
}
// needs to dereference
return false
}
func compile(ctx *compileContext, isPtr bool) (*Opcode, error) {
typ := ctx.typ
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON(ctx)
case implementsMarshalText(typ):
return compileMarshalText(ctx)
}
switch typ.Kind() {
case reflect.Ptr:
return compilePtr(ctx)
case reflect.Slice:
elem := typ.Elem()
if elem.Kind() == reflect.Uint8 {
p := runtime.PtrTo(elem)
if !p.Implements(marshalJSONType) && !p.Implements(marshalTextType) {
return compileBytes(ctx)
}
}
return compileSlice(ctx)
case reflect.Array:
return compileArray(ctx)
case reflect.Map:
return compileMap(ctx)
case reflect.Struct:
return compileStruct(ctx, isPtr)
case reflect.Interface:
return compileInterface(ctx)
case reflect.Int:
return compileInt(ctx)
case reflect.Int8:
return compileInt8(ctx)
case reflect.Int16:
return compileInt16(ctx)
case reflect.Int32:
return compileInt32(ctx)
case reflect.Int64:
return compileInt64(ctx)
case reflect.Uint:
return compileUint(ctx)
case reflect.Uint8:
return compileUint8(ctx)
case reflect.Uint16:
return compileUint16(ctx)
case reflect.Uint32:
return compileUint32(ctx)
case reflect.Uint64:
return compileUint64(ctx)
case reflect.Uintptr:
return compileUint(ctx)
case reflect.Float32:
return compileFloat32(ctx)
case reflect.Float64:
return compileFloat64(ctx)
case reflect.String:
return compileString(ctx)
case reflect.Bool:
return compileBool(ctx)
}
return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)}
}
func convertPtrOp(code *Opcode) OpType {
ptrHeadOp := code.Op.HeadToPtrHead()
if code.Op != ptrHeadOp {
if code.PtrNum > 0 {
// ptr field and ptr head
code.PtrNum--
}
return ptrHeadOp
}
switch code.Op {
case OpInt:
return OpIntPtr
case OpUint:
return OpUintPtr
case OpFloat32:
return OpFloat32Ptr
case OpFloat64:
return OpFloat64Ptr
case OpString:
return OpStringPtr
case OpBool:
return OpBoolPtr
case OpBytes:
return OpBytesPtr
case OpArray:
return OpArrayPtr
case OpSlice:
return OpSlicePtr
case OpMap:
return OpMapPtr
case OpMarshalJSON:
return OpMarshalJSONPtr
case OpMarshalText:
return OpMarshalTextPtr
case OpInterface:
return OpInterfacePtr
case OpStructFieldRecursive:
return OpStructFieldRecursivePtr
}
return code.Op
}
func compileKey(ctx *compileContext) (*Opcode, error) {
typ := ctx.typ
switch {
case implementsMarshalJSON(typ):
return compileMarshalJSON(ctx)
case implementsMarshalText(typ):
return compileMarshalText(ctx)
}
switch typ.Kind() {
case reflect.Ptr:
return compilePtr(ctx)
case reflect.Interface:
return compileInterface(ctx)
case reflect.String:
return compileString(ctx)
case reflect.Int:
return compileIntString(ctx)
case reflect.Int8:
return compileInt8String(ctx)
case reflect.Int16:
return compileInt16String(ctx)
case reflect.Int32:
return compileInt32String(ctx)
case reflect.Int64:
return compileInt64String(ctx)
case reflect.Uint:
return compileUintString(ctx)
case reflect.Uint8:
return compileUint8String(ctx)
case reflect.Uint16:
return compileUint16String(ctx)
case reflect.Uint32:
return compileUint32String(ctx)
case reflect.Uint64:
return compileUint64String(ctx)
case reflect.Uintptr:
return compileUintString(ctx)
}
return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)}
}
func compilePtr(ctx *compileContext) (*Opcode, error) {
code, err := compile(ctx.withType(ctx.typ.Elem()), true)
if err != nil {
return nil, err
}
code.Op = convertPtrOp(code)
code.PtrNum++
return code, nil
}
func compileMarshalJSON(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpMarshalJSON)
typ := ctx.typ
if !typ.Implements(marshalJSONType) && runtime.PtrTo(typ).Implements(marshalJSONType) {
code.AddrForMarshaler = true
}
ctx.incIndex()
return code, nil
}
func compileMarshalText(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpMarshalText)
typ := ctx.typ
if !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType) {
code.AddrForMarshaler = true
}
ctx.incIndex()
return code, nil
}
const intSize = 32 << (^uint(0) >> 63)
func compileInt(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpInt)
code.setMaskAndRshiftNum(intSize)
ctx.incIndex()
return code, nil
}
func compileInt8(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpInt)
code.setMaskAndRshiftNum(8)
ctx.incIndex()
return code, nil
}
func compileInt16(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpInt)
code.setMaskAndRshiftNum(16)
ctx.incIndex()
return code, nil
}
func compileInt32(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpInt)
code.setMaskAndRshiftNum(32)
ctx.incIndex()
return code, nil
}
func compileInt64(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpInt)
code.setMaskAndRshiftNum(64)
ctx.incIndex()
return code, nil
}
func compileUint(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUint)
code.setMaskAndRshiftNum(intSize)
ctx.incIndex()
return code, nil
}
func compileUint8(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUint)
code.setMaskAndRshiftNum(8)
ctx.incIndex()
return code, nil
}
func compileUint16(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUint)
code.setMaskAndRshiftNum(16)
ctx.incIndex()
return code, nil
}
func compileUint32(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUint)
code.setMaskAndRshiftNum(32)
ctx.incIndex()
return code, nil
}
func compileUint64(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUint)
code.setMaskAndRshiftNum(64)
ctx.incIndex()
return code, nil
}
func compileIntString(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpIntString)
code.setMaskAndRshiftNum(intSize)
ctx.incIndex()
return code, nil
}
func compileInt8String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpIntString)
code.setMaskAndRshiftNum(8)
ctx.incIndex()
return code, nil
}
func compileInt16String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpIntString)
code.setMaskAndRshiftNum(16)
ctx.incIndex()
return code, nil
}
func compileInt32String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpIntString)
code.setMaskAndRshiftNum(32)
ctx.incIndex()
return code, nil
}
func compileInt64String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpIntString)
code.setMaskAndRshiftNum(64)
ctx.incIndex()
return code, nil
}
func compileUintString(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUintString)
code.setMaskAndRshiftNum(intSize)
ctx.incIndex()
return code, nil
}
func compileUint8String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUintString)
code.setMaskAndRshiftNum(8)
ctx.incIndex()
return code, nil
}
func compileUint16String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUintString)
code.setMaskAndRshiftNum(16)
ctx.incIndex()
return code, nil
}
func compileUint32String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUintString)
code.setMaskAndRshiftNum(32)
ctx.incIndex()
return code, nil
}
func compileUint64String(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpUintString)
code.setMaskAndRshiftNum(64)
ctx.incIndex()
return code, nil
}
func compileFloat32(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpFloat32)
ctx.incIndex()
return code, nil
}
func compileFloat64(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpFloat64)
ctx.incIndex()
return code, nil
}
func compileString(ctx *compileContext) (*Opcode, error) {
var op OpType
if ctx.typ == runtime.Type2RType(jsonNumberType) {
op = OpNumber
} else {
op = OpString
}
code := newOpCode(ctx, op)
ctx.incIndex()
return code, nil
}
func compileBool(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpBool)
ctx.incIndex()
return code, nil
}
func compileBytes(ctx *compileContext) (*Opcode, error) {
code := newOpCode(ctx, OpBytes)
ctx.incIndex()
return code, nil
}
func compileInterface(ctx *compileContext) (*Opcode, error) {
code := newInterfaceCode(ctx)
ctx.incIndex()
return code, nil
}
func compileSlice(ctx *compileContext) (*Opcode, error) {
elem := ctx.typ.Elem()
size := elem.Size()
header := newSliceHeaderCode(ctx)
ctx.incIndex()
code, err := compileListElem(ctx.withType(elem).incIndent())
if err != nil {
return nil, err
}
code.Indirect = true
// header => opcode => elem => end
// ^ |
// |________|
elemCode := newSliceElemCode(ctx, header, size)
ctx.incIndex()
end := newOpCode(ctx, OpSliceEnd)
ctx.incIndex()
header.Elem = elemCode
header.End = end
header.Next = code
code.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(elemCode))
elemCode.Next = code
elemCode.End = end
return (*Opcode)(unsafe.Pointer(header)), nil
}
func compileListElem(ctx *compileContext) (*Opcode, error) {
typ := ctx.typ
switch {
case !typ.Implements(marshalJSONType) && runtime.PtrTo(typ).Implements(marshalJSONType):
return compileMarshalJSON(ctx)
case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType):
return compileMarshalText(ctx)
default:
return compile(ctx, false)
}
}
func compileArray(ctx *compileContext) (*Opcode, error) {
typ := ctx.typ
elem := typ.Elem()
alen := typ.Len()
size := elem.Size()
header := newArrayHeaderCode(ctx, alen)
ctx.incIndex()
code, err := compileListElem(ctx.withType(elem).incIndent())
if err != nil {
return nil, err
}
code.Indirect = true
// header => opcode => elem => end
// ^ |
// |________|
elemCode := newArrayElemCode(ctx, header, alen, size)
ctx.incIndex()
end := newOpCode(ctx, OpArrayEnd)
ctx.incIndex()
header.Elem = elemCode
header.End = end
header.Next = code
code.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(elemCode))
elemCode.Next = code
elemCode.End = end
return (*Opcode)(unsafe.Pointer(header)), nil
}
func compileMap(ctx *compileContext) (*Opcode, error) {
// header => code => value => code => key => code => value => code => end
// ^ |
// |_______________________|
ctx = ctx.incIndent()
header := newMapHeaderCode(ctx)
ctx.incIndex()
typ := ctx.typ
keyType := ctx.typ.Key()
keyCode, err := compileKey(ctx.withType(keyType))
if err != nil {
return nil, err
}
value := newMapValueCode(ctx, header)
ctx.incIndex()
valueCode, err := compileMapValue(ctx.withType(typ.Elem()))
if err != nil {
return nil, err
}
valueCode.Indirect = true
key := newMapKeyCode(ctx, header)
ctx.incIndex()
ctx = ctx.decIndent()
header.MapKey = key
header.MapValue = value
end := newMapEndCode(ctx, header)
ctx.incIndex()
header.Next = keyCode
keyCode.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(value))
value.Next = valueCode
valueCode.BeforeLastCode().Next = (*Opcode)(unsafe.Pointer(key))
key.Next = keyCode
header.End = end
key.End = end
value.End = end
return (*Opcode)(unsafe.Pointer(header)), nil
}
func compileMapValue(ctx *compileContext) (*Opcode, error) {
switch ctx.typ.Kind() {
case reflect.Map:
return compilePtr(ctx.withType(runtime.PtrTo(ctx.typ)))
default:
return compile(ctx, false)
}
}
func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType {
headType := code.ToHeaderType()
switch {
case tag.IsOmitEmpty:
headType = headType.HeadToOmitEmptyHead()
case tag.IsString:
headType = headType.HeadToStringTagHead()
}
return headType
}
func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType {
fieldType := code.ToFieldType()
switch {
case tag.IsOmitEmpty:
fieldType = fieldType.FieldToOmitEmptyField()
case tag.IsString:
fieldType = fieldType.FieldToStringTagField()
}
return fieldType
}
func recursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode {
code := newRecursiveCode(ctx, jmp)
ctx.incIndex()
return code
}
func compiledCode(ctx *compileContext) *Opcode {
typ := ctx.typ
typeptr := uintptr(unsafe.Pointer(typ))
if cc, exists := ctx.structTypeToCompiledCode[typeptr]; exists {
return recursiveCode(ctx, cc)
}
return nil
}
func structHeader(ctx *compileContext, fieldCode *Opcode, valueCode *Opcode, tag *runtime.StructTag) *Opcode {
fieldCode.Indent--
op := optimizeStructHeader(valueCode, tag)
fieldCode.Op = op
fieldCode.Mask = valueCode.Mask
fieldCode.RshiftNum = valueCode.RshiftNum
fieldCode.PtrNum = valueCode.PtrNum
if op.IsMultipleOpHead() {
return valueCode.BeforeLastCode()
}
ctx.decOpcodeIndex()
return (*Opcode)(unsafe.Pointer(fieldCode))
}
func structField(ctx *compileContext, fieldCode *Opcode, valueCode *Opcode, tag *runtime.StructTag) *Opcode {
code := (*Opcode)(unsafe.Pointer(fieldCode))
op := optimizeStructField(valueCode, tag)
fieldCode.Op = op
fieldCode.PtrNum = valueCode.PtrNum
fieldCode.Mask = valueCode.Mask
fieldCode.RshiftNum = valueCode.RshiftNum
fieldCode.Jmp = valueCode.Jmp
if op.IsMultipleOpField() {
return valueCode.BeforeLastCode()
}
ctx.decIndex()
return code
}
func isNotExistsField(head *Opcode) bool {
if head == nil {
return false
}
if head.Op != OpStructHead {
return false
}
if !head.AnonymousHead {
return false
}
if head.Next == nil {
return false
}
if head.NextField == nil {
return false
}
if head.NextField.Op != OpStructAnonymousEnd {
return false
}
if head.Next.Op == OpStructAnonymousEnd {
return true
}
if head.Next.Op.CodeType() != CodeStructField {
return false
}
return isNotExistsField(head.Next)
}
func optimizeAnonymousFields(head *Opcode) {
code := head
var prev *Opcode
removedFields := map[*Opcode]struct{}{}
for {
if code.Op == OpStructEnd {
break
}
if code.Op == OpStructField {
codeType := code.Next.Op.CodeType()
if codeType == CodeStructField {
if isNotExistsField(code.Next) {
code.Next = code.NextField
diff := code.Next.DisplayIdx - code.DisplayIdx
for i := 0; i < diff; i++ {
code.Next.decOpcodeIndex()
}
linkPrevToNextField(code, removedFields)
code = prev
}
}
}
prev = code
code = code.NextField
}
}
type structFieldPair struct {
prevField *Opcode
curField *Opcode
isTaggedKey bool
linked bool
}
func anonymousStructFieldPairMap(tags runtime.StructTags, named string, valueCode *Opcode) map[string][]structFieldPair {
anonymousFields := map[string][]structFieldPair{}
f := valueCode
var prevAnonymousField *Opcode
removedFields := map[*Opcode]struct{}{}
for {
existsKey := tags.ExistsKey(f.DisplayKey)
isHeadOp := strings.Contains(f.Op.String(), "Head")
if existsKey && strings.Contains(f.Op.String(), "Recursive") {
// through
} else if isHeadOp && !f.AnonymousHead {
if existsKey {
// TODO: need to remove this head
f.Op = OpStructHead
f.AnonymousKey = true
f.AnonymousHead = true
} else if named == "" {
f.AnonymousHead = true
}
} else if named == "" && f.Op == OpStructEnd {
f.Op = OpStructAnonymousEnd
} else if existsKey {
diff := f.NextField.DisplayIdx - f.DisplayIdx
for i := 0; i < diff; i++ {
f.NextField.decOpcodeIndex()
}
linkPrevToNextField(f, removedFields)
}
if f.DisplayKey == "" {
if f.NextField == nil {
break
}
prevAnonymousField = f
f = f.NextField
continue
}
key := fmt.Sprintf("%s.%s", named, f.DisplayKey)
anonymousFields[key] = append(anonymousFields[key], structFieldPair{
prevField: prevAnonymousField,
curField: f,
isTaggedKey: f.IsTaggedKey,
})
if f.Next != nil && f.NextField != f.Next && f.Next.Op.CodeType() == CodeStructField {
for k, v := range anonymousFieldPairRecursively(named, f.Next) {
anonymousFields[k] = append(anonymousFields[k], v...)
}
}
if f.NextField == nil {
break
}
prevAnonymousField = f
f = f.NextField
}
return anonymousFields
}
func anonymousFieldPairRecursively(named string, valueCode *Opcode) map[string][]structFieldPair {
anonymousFields := map[string][]structFieldPair{}
f := valueCode
var prevAnonymousField *Opcode
for {
if f.DisplayKey != "" && f.AnonymousHead {
key := fmt.Sprintf("%s.%s", named, f.DisplayKey)
anonymousFields[key] = append(anonymousFields[key], structFieldPair{
prevField: prevAnonymousField,
curField: f,
isTaggedKey: f.IsTaggedKey,
})
if f.Next != nil && f.NextField != f.Next && f.Next.Op.CodeType() == CodeStructField {
for k, v := range anonymousFieldPairRecursively(named, f.Next) {
anonymousFields[k] = append(anonymousFields[k], v...)
}
}
}
if f.NextField == nil {
break
}
prevAnonymousField = f
f = f.NextField
}
return anonymousFields
}
func optimizeConflictAnonymousFields(anonymousFields map[string][]structFieldPair) {
removedFields := map[*Opcode]struct{}{}
for _, fieldPairs := range anonymousFields {
if len(fieldPairs) == 1 {
continue
}
// conflict anonymous fields
taggedPairs := []structFieldPair{}
for _, fieldPair := range fieldPairs {
if fieldPair.isTaggedKey {
taggedPairs = append(taggedPairs, fieldPair)
} else {
if !fieldPair.linked {
if fieldPair.prevField == nil {
// head operation
fieldPair.curField.Op = OpStructHead
fieldPair.curField.AnonymousHead = true
fieldPair.curField.AnonymousKey = true
} else {
diff := fieldPair.curField.NextField.DisplayIdx - fieldPair.curField.DisplayIdx
for i := 0; i < diff; i++ {
fieldPair.curField.NextField.decOpcodeIndex()
}
removedFields[fieldPair.curField] = struct{}{}
linkPrevToNextField(fieldPair.curField, removedFields)
}
fieldPair.linked = true
}
}
}
if len(taggedPairs) > 1 {
for _, fieldPair := range taggedPairs {
if !fieldPair.linked {
if fieldPair.prevField == nil {
// head operation
fieldPair.curField.Op = OpStructHead
fieldPair.curField.AnonymousHead = true
fieldPair.curField.AnonymousKey = true
} else {
diff := fieldPair.curField.NextField.DisplayIdx - fieldPair.curField.DisplayIdx
removedFields[fieldPair.curField] = struct{}{}
for i := 0; i < diff; i++ {
fieldPair.curField.NextField.decOpcodeIndex()
}
linkPrevToNextField(fieldPair.curField, removedFields)
}
fieldPair.linked = true
}
}
} else {
for _, fieldPair := range taggedPairs {
fieldPair.curField.IsTaggedKey = false
}
}
}
}
func isNilableType(typ *runtime.Type) bool {
switch typ.Kind() {
case reflect.Ptr:
return true
case reflect.Interface:
return true
case reflect.Slice:
return true
case reflect.Map:
return true
default:
return false
}
}
func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) {
if code := compiledCode(ctx); code != nil {
return code, nil
}
typ := ctx.typ
typeptr := uintptr(unsafe.Pointer(typ))
compiled := &CompiledCode{}
ctx.structTypeToCompiledCode[typeptr] = compiled
// header => code => structField => code => end
// ^ |
// |__________|
fieldNum := typ.NumField()
indirect := runtime.IfaceIndir(typ)
fieldIdx := 0
disableIndirectConversion := false
var (
head *Opcode
code *Opcode
prevField *Opcode
)
ctx = ctx.incIndent()
tags := runtime.StructTags{}
anonymousFields := map[string][]structFieldPair{}
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if runtime.IsIgnoredStructField(field) {
continue
}
tags = append(tags, runtime.StructTagFromField(field))
}
for i, tag := range tags {
field := tag.Field
fieldType := runtime.Type2RType(field.Type)
fieldOpcodeIndex := ctx.opcodeIndex
fieldPtrIndex := ctx.ptrIndex
ctx.incIndex()
nilcheck := true
addrForMarshaler := false
isIndirectSpecialCase := isPtr && i == 0 && fieldNum == 1
isNilableType := isNilableType(fieldType)
var valueCode *Opcode
switch {
case isIndirectSpecialCase && !isNilableType && isPtrMarshalJSONType(fieldType):
// *struct{ field T } => struct { field *T }
// func (*T) MarshalJSON() ([]byte, error)
// move pointer position from head to first field
code, err := compileMarshalJSON(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
valueCode = code
nilcheck = false
indirect = false
disableIndirectConversion = true
case isIndirectSpecialCase && !isNilableType && isPtrMarshalTextType(fieldType):
// *struct{ field T } => struct { field *T }
// func (*T) MarshalText() ([]byte, error)
// move pointer position from head to first field
code, err := compileMarshalText(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
valueCode = code
nilcheck = false
indirect = false
disableIndirectConversion = true
case isPtr && isPtrMarshalJSONType(fieldType):
// *struct{ field T }
// func (*T) MarshalJSON() ([]byte, error)
code, err := compileMarshalJSON(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
nilcheck = false
valueCode = code
case isPtr && isPtrMarshalTextType(fieldType):
// *struct{ field T }
// func (*T) MarshalText() ([]byte, error)
code, err := compileMarshalText(ctx.withType(fieldType))
if err != nil {
return nil, err
}
addrForMarshaler = true
nilcheck = false
valueCode = code
default:
code, err := compile(ctx.withType(fieldType), isPtr)
if err != nil {
return nil, err
}
valueCode = code
}
if field.Anonymous {
tagKey := ""
if tag.IsTaggedKey {
tagKey = tag.Key
}
for k, v := range anonymousStructFieldPairMap(tags, tagKey, valueCode) {
anonymousFields[k] = append(anonymousFields[k], v...)
}
valueCode.decIndent()
// fix issue144
if !(isPtr && strings.Contains(valueCode.Op.String(), "Marshal")) {
valueCode.Indirect = indirect
}
} else {
valueCode.Indirect = indirect
}
key := fmt.Sprintf(`"%s":`, tag.Key)
escapedKey := fmt.Sprintf(`%s:`, string(AppendEscapedString([]byte{}, tag.Key)))
fieldCode := &Opcode{
Type: valueCode.Type,
DisplayIdx: fieldOpcodeIndex,
Idx: opcodeOffset(fieldPtrIndex),
Next: valueCode,
Indent: ctx.indent,
AnonymousKey: field.Anonymous,
Key: []byte(key),
EscapedKey: []byte(escapedKey),
IsTaggedKey: tag.IsTaggedKey,
DisplayKey: tag.Key,
Offset: field.Offset,
Indirect: indirect,
Nilcheck: nilcheck,
AddrForMarshaler: addrForMarshaler,
}
if fieldIdx == 0 {
fieldCode.HeadIdx = fieldCode.Idx
code = structHeader(ctx, fieldCode, valueCode, tag)
head = fieldCode
prevField = fieldCode
} else {
fieldCode.HeadIdx = head.HeadIdx
code.Next = fieldCode
code = structField(ctx, fieldCode, valueCode, tag)
prevField.NextField = fieldCode
fieldCode.PrevField = prevField
prevField = fieldCode
}
fieldIdx++
}
ctx = ctx.decIndent()
structEndCode := &Opcode{
Op: OpStructEnd,
Type: nil,
Indent: ctx.indent,
Next: newEndOp(ctx),
}
// no struct field
if head == nil {
head = &Opcode{
Op: OpStructHead,
Type: typ,
DisplayIdx: ctx.opcodeIndex,
Idx: opcodeOffset(ctx.ptrIndex),
HeadIdx: opcodeOffset(ctx.ptrIndex),
Indent: ctx.indent,
NextField: structEndCode,
}
structEndCode.PrevField = head
ctx.incIndex()
code = head
}
structEndCode.DisplayIdx = ctx.opcodeIndex
structEndCode.Idx = opcodeOffset(ctx.ptrIndex)
ctx.incIndex()
if prevField != nil && prevField.NextField == nil {
prevField.NextField = structEndCode
structEndCode.PrevField = prevField
}
head.End = structEndCode
code.Next = structEndCode
optimizeConflictAnonymousFields(anonymousFields)
optimizeAnonymousFields(head)
ret := (*Opcode)(unsafe.Pointer(head))
compiled.Code = ret
delete(ctx.structTypeToCompiledCode, typeptr)
if !disableIndirectConversion && !head.Indirect && isPtr {
head.Indirect = true
}
return ret, nil
}
func isPtrMarshalJSONType(typ *runtime.Type) bool {
return !typ.Implements(marshalJSONType) && runtime.PtrTo(typ).Implements(marshalJSONType)
}
func isPtrMarshalTextType(typ *runtime.Type) bool {
return !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType)
}