From d631a21b590e2298e2e7c8d38c86369d5e6e2783 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Mon, 22 Nov 2021 13:20:03 +0900 Subject: [PATCH] add opcode generator --- internal/encoder/code.go | 754 +++++++++++++++++++++++++--- internal/encoder/compiler.go | 8 +- internal/encoder/compiler_norace.go | 3 + internal/encoder/compiler_race.go | 3 + internal/encoder/context.go | 2 + 5 files changed, 688 insertions(+), 82 deletions(-) diff --git a/internal/encoder/code.go b/internal/encoder/code.go index 7986345..68303ea 100644 --- a/internal/encoder/code.go +++ b/internal/encoder/code.go @@ -1,7 +1,9 @@ package encoder import ( + "fmt" "reflect" + "unsafe" "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" @@ -9,7 +11,23 @@ import ( type Code interface { Type() CodeType2 - ToOpcode() []*Opcode + ToOpcode(*compileContext) Opcodes +} + +type Opcodes []*Opcode + +func (o Opcodes) First() *Opcode { + if len(o) == 0 { + return nil + } + return o[0] +} + +func (o Opcodes) Last() *Opcode { + if len(o) == 0 { + return nil + } + return o[len(o)-1] } type CodeType2 int @@ -29,6 +47,7 @@ const ( CodeTypeBytes CodeTypeMarshalJSON CodeTypeMarshalText + CodeTypeRecursive ) type IntCode struct { @@ -42,8 +61,19 @@ func (c *IntCode) Type() CodeType2 { return CodeTypeInt } -func (c *IntCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *IntCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, OpIntPtr) + case c.isString: + code = newOpCode(ctx, OpIntString) + default: + code = newOpCode(ctx, OpInt) + } + code.NumBitSize = c.bitSize + ctx.incIndex() + return Opcodes{code} } type UintCode struct { @@ -57,8 +87,19 @@ func (c *UintCode) Type() CodeType2 { return CodeTypeUint } -func (c *UintCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *UintCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, OpUintPtr) + case c.isString: + code = newOpCode(ctx, OpUintString) + default: + code = newOpCode(ctx, OpUint) + } + code.NumBitSize = c.bitSize + ctx.incIndex() + return Opcodes{code} } type FloatCode struct { @@ -72,8 +113,26 @@ func (c *FloatCode) Type() CodeType2 { return CodeTypeFloat } -func (c *FloatCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *FloatCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + switch c.bitSize { + case 32: + code = newOpCode(ctx, OpFloat32Ptr) + default: + code = newOpCode(ctx, OpFloat64Ptr) + } + default: + switch c.bitSize { + case 32: + code = newOpCode(ctx, OpFloat32) + default: + code = newOpCode(ctx, OpFloat64) + } + } + ctx.incIndex() + return Opcodes{code} } type StringCode struct { @@ -86,8 +145,24 @@ func (c *StringCode) Type() CodeType2 { return CodeTypeString } -func (c *StringCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *StringCode) ToOpcode(ctx *compileContext) Opcodes { + isJsonNumberType := c.typ == runtime.Type2RType(jsonNumberType) + var code *Opcode + if c.isPtr { + if isJsonNumberType { + code = newOpCode(ctx, OpNumberPtr) + } else { + code = newOpCode(ctx, OpStringPtr) + } + } else { + if isJsonNumberType { + code = newOpCode(ctx, OpNumber) + } else { + code = newOpCode(ctx, OpString) + } + } + ctx.incIndex() + return Opcodes{code} } type BoolCode struct { @@ -100,44 +175,16 @@ func (c *BoolCode) Type() CodeType2 { return CodeTypeBool } -func (c *BoolCode) ToOpcode() []*Opcode { - return []*Opcode{} -} - -type SliceCode struct { - typ *runtime.Type -} - -func (c *SliceCode) Type() CodeType2 { - return CodeTypeSlice -} - -func (c *SliceCode) ToOpcode() []*Opcode { - return []*Opcode{} -} - -type ArrayCode struct { - typ *runtime.Type -} - -func (c *ArrayCode) Type() CodeType2 { - return CodeTypeArray -} - -func (c *ArrayCode) ToOpcode() []*Opcode { - return []*Opcode{} -} - -type MapCode struct { - typ *runtime.Type -} - -func (c *MapCode) Type() CodeType2 { - return CodeTypeMap -} - -func (c *MapCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *BoolCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, OpBoolPtr) + default: + code = newOpCode(ctx, OpBool) + } + ctx.incIndex() + return Opcodes{code} } type BytesCode struct { @@ -149,8 +196,125 @@ func (c *BytesCode) Type() CodeType2 { return CodeTypeBytes } -func (c *BytesCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *BytesCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, OpBytesPtr) + default: + code = newOpCode(ctx, OpBytes) + } + ctx.incIndex() + return Opcodes{code} +} + +type SliceCode struct { + typ *runtime.Type + value Code +} + +func (c *SliceCode) Type() CodeType2 { + return CodeTypeSlice +} + +func (c *SliceCode) ToOpcode(ctx *compileContext) Opcodes { + // header => opcode => elem => end + // ^ | + // |________| + size := c.typ.Elem().Size() + header := newSliceHeaderCode(ctx) + ctx.incIndex() + codes := c.value.ToOpcode(ctx) + elemCode := newSliceElemCode(ctx, header, size) + ctx.incIndex() + end := newOpCode(ctx, OpSliceEnd) + ctx.incIndex() + header.End = end + header.Next = codes.First() + codes.Last().Next = elemCode + elemCode.Next = codes.First() + elemCode.End = end + return Opcodes{header} +} + +type ArrayCode struct { + typ *runtime.Type + value Code +} + +func (c *ArrayCode) Type() CodeType2 { + return CodeTypeArray +} + +func (c *ArrayCode) ToOpcode(ctx *compileContext) Opcodes { + // header => opcode => elem => end + // ^ | + // |________| + elem := c.typ.Elem() + alen := c.typ.Len() + size := elem.Size() + + header := newArrayHeaderCode(ctx, alen) + ctx.incIndex() + + codes := c.value.ToOpcode(ctx) + + elemCode := newArrayElemCode(ctx, header, alen, size) + ctx.incIndex() + + end := newOpCode(ctx, OpArrayEnd) + ctx.incIndex() + + header.End = end + header.Next = codes.First() + codes.Last().Next = elemCode + elemCode.Next = codes.First() + elemCode.End = end + return Opcodes{header} +} + +type MapCode struct { + typ *runtime.Type + key Code + value Code +} + +func (c *MapCode) Type() CodeType2 { + return CodeTypeMap +} + +func (c *MapCode) ToOpcode(ctx *compileContext) Opcodes { + // header => code => value => code => key => code => value => code => end + // ^ | + // |_______________________| + ctx = ctx.incIndent() + header := newMapHeaderCode(ctx) + ctx.incIndex() + + keyCodes := c.key.ToOpcode(ctx) + + value := newMapValueCode(ctx, header) + ctx.incIndex() + valueCodes := c.value.ToOpcode(ctx) + + key := newMapKeyCode(ctx, header) + ctx.incIndex() + + ctx = ctx.decIndent() + + end := newMapEndCode(ctx, header) + ctx.incIndex() + + header.Next = keyCodes.First() + keyCodes.Last().Next = value + value.Next = valueCodes.First() + valueCodes.Last().Next = key + key.Next = keyCodes.First() + + header.End = end + key.End = end + value.End = end + return Opcodes{header} } type StructCode struct { @@ -158,14 +322,79 @@ type StructCode struct { isPtr bool fields []*StructFieldCode disableIndirectConversion bool + isIndirect bool + isRecursive bool + recursiveCodes Opcodes } func (c *StructCode) Type() CodeType2 { return CodeTypeStruct } -func (c *StructCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes { + // header => code => structField => code => end + // ^ | + // |__________| + var recursive *Opcode + compiled := &CompiledCode{} + if c.isRecursive { + recursive = newRecursiveCode(ctx, compiled) + ctx.incIndex() + } + if len(c.recursiveCodes) > 0 { + c.linkRecursiveCode(compiled, c.recursiveCodes) + return Opcodes{recursive} + } + codes := Opcodes{} + var prevField *Opcode + for idx, field := range c.fields { + isFirstField := idx == 0 + isEndField := idx == len(c.fields)-1 + fieldCodes := field.ToOpcode(ctx, isFirstField, isEndField) + for _, code := range fieldCodes { + if c.isIndirect { + code.Flags |= IndirectFlags + } + } + if len(codes) > 0 { + codes.Last().Next = fieldCodes.First() + } + if prevField != nil { + prevField.NextField = fieldCodes.First() + } + prevField = fieldCodes.First() + codes = append(codes, fieldCodes...) + } + if c.isRecursive { + c.recursiveCodes = codes + c.linkRecursiveCode(compiled, c.recursiveCodes) + return Opcodes{recursive} + } + return codes +} + +func (c *StructCode) linkRecursiveCode(compiled *CompiledCode, codes Opcodes) { + compiled.Code = copyOpcode(codes.First()) + code := compiled.Code + code.End.Next = newEndOp(&compileContext{}) + code.Op = code.Op.PtrHeadToHead() + + beforeLastCode := code.End + lastCode := beforeLastCode.Next + + lastCode.Idx = beforeLastCode.Idx + uintptrSize + lastCode.ElemIdx = lastCode.Idx + uintptrSize + lastCode.Length = lastCode.Idx + 2*uintptrSize + + // extend length to alloc slot for elemIdx + length + totalLength := uintptr(codes.First().TotalLength() + 3) + nextTotalLength := uintptr(code.TotalLength() + 3) + + code.End.Next.Op = OpRecursiveEnd + + compiled.CurLen = totalLength + compiled.NextLen = nextTotalLength + compiled.Linked = true } func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) { @@ -173,7 +402,7 @@ func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) { for _, field := range c.fields { if field.isAnonymous { structCode := field.getAnonymousStruct() - if structCode != nil { + if structCode != nil && !structCode.isRecursive { structCode.removeFieldsByTags(tags) if len(structCode.fields) > 0 { fields = append(fields, field) @@ -189,9 +418,25 @@ func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) { c.fields = fields } +func (c *StructCode) enableIndirect() { + if c.isIndirect { + return + } + c.isIndirect = true + if len(c.fields) == 0 { + return + } + structCode := c.fields[0].getStruct() + if structCode == nil { + return + } + structCode.enableIndirect() +} + type StructFieldCode struct { typ *runtime.Type key string + tag *runtime.StructTag value Code offset uintptr isAnonymous bool @@ -202,15 +447,128 @@ type StructFieldCode struct { isNextOpPtrType bool } +func (c *StructFieldCode) getStruct() *StructCode { + value := c.value + ptr, ok := value.(*PtrCode) + if ok { + value = ptr.value + } + structCode, ok := value.(*StructCode) + if ok { + return structCode + } + return nil +} + func (c *StructFieldCode) getAnonymousStruct() *StructCode { if !c.isAnonymous { return nil } - code, ok := c.value.(*StructCode) - if ok { - return code + return c.getStruct() +} + +func (c *StructFieldCode) ToOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes { + var key string + if ctx.escapeKey { + rctx := &RuntimeContext{Option: &Option{Flag: HTMLEscapeOption}} + key = fmt.Sprintf(`%s:`, string(AppendString(rctx, []byte{}, c.key))) + } else { + key = fmt.Sprintf(`"%s":`, c.key) + } + var flags OpFlags + if c.isAnonymous { + flags |= AnonymousKeyFlags + } + if c.isTaggedKey { + flags |= IsTaggedKeyFlags + } + if c.isNilableType { + flags |= IsNilableTypeFlags + } + if c.isNilCheck { + flags |= NilCheckFlags + } + if c.isAddrForMarshaler { + flags |= AddrForMarshalerFlags + } + if c.isNextOpPtrType { + flags |= IsNextOpPtrTypeFlags + } + field := &Opcode{ + Idx: opcodeOffset(ctx.ptrIndex), + Flags: flags, + Key: key, + Offset: uint32(c.offset), + Type: c.typ, + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + DisplayKey: c.key, + } + ctx.incIndex() + codes := c.value.ToOpcode(ctx) + if isFirstField { + op := optimizeStructHeader(codes.First(), c.tag) + field.Op = op + field.NumBitSize = codes.First().NumBitSize + field.PtrNum = codes.First().PtrNum + fieldCodes := Opcodes{field} + if op.IsMultipleOpHead() { + field.Next = codes.First() + fieldCodes = append(fieldCodes, codes...) + } else { + ctx.decIndex() + } + if isEndField && !c.isAnonymous { + end := &Opcode{ + Op: OpStructEnd, + Idx: opcodeOffset(ctx.ptrIndex), + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + } + fieldCodes.Last().Next = end + fieldCodes = append(fieldCodes, end) + ctx.incIndex() + } + return fieldCodes + } + op := optimizeStructField(codes.First(), c.tag) + field.Op = op + field.NumBitSize = codes.First().NumBitSize + field.PtrNum = codes.First().PtrNum + + fieldCodes := Opcodes{field} + if op.IsMultipleOpField() { + field.Next = codes.First() + fieldCodes = append(fieldCodes, codes...) + } else { + // optimize codes + ctx.decIndex() + } + if isEndField && !c.isAnonymous { + if isEnableStructEndOptimizationType(c.value.Type()) { + field.Op = field.Op.FieldToEnd() + } else { + end := &Opcode{ + Op: OpStructEnd, + Idx: opcodeOffset(ctx.ptrIndex), + DisplayIdx: ctx.opcodeIndex, + Indent: ctx.indent, + } + fieldCodes.Last().Next = end + fieldCodes = append(fieldCodes, end) + ctx.incIndex() + } + } + return fieldCodes +} + +func isEnableStructEndOptimizationType(typ CodeType2) bool { + switch typ { + case CodeTypeInt, CodeTypeUint, CodeTypeFloat, CodeTypeString, CodeTypeBool: + return true + default: + return false } - return nil } type InterfaceCode struct { @@ -222,8 +580,16 @@ func (c *InterfaceCode) Type() CodeType2 { return CodeTypeInterface } -func (c *InterfaceCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes { + var code *Opcode + switch { + case c.isPtr: + code = newOpCode(ctx, OpInterfacePtr) + default: + code = newOpCode(ctx, OpInterface) + } + ctx.incIndex() + return Opcodes{code} } type MarshalJSONCode struct { @@ -234,8 +600,22 @@ func (c *MarshalJSONCode) Type() CodeType2 { return CodeTypeMarshalJSON } -func (c *MarshalJSONCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes { + code := newOpCode(ctx, OpMarshalJSON) + typ := c.typ + if isPtrMarshalJSONType(typ) { + code.Flags |= AddrForMarshalerFlags + } + if typ.Implements(marshalJSONContextType) || runtime.PtrTo(typ).Implements(marshalJSONContextType) { + code.Flags |= MarshalerContextFlags + } + if isNilableType(typ) { + code.Flags |= IsNilableTypeFlags + } else { + code.Flags &= ^IsNilableTypeFlags + } + ctx.incIndex() + return Opcodes{code} } type MarshalTextCode struct { @@ -246,21 +626,35 @@ func (c *MarshalTextCode) Type() CodeType2 { return CodeTypeMarshalText } -func (c *MarshalTextCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes { + code := newOpCode(ctx, OpMarshalText) + typ := c.typ + if !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType) { + code.Flags |= AddrForMarshalerFlags + } + if isNilableType(typ) { + code.Flags |= IsNilableTypeFlags + } else { + code.Flags &= ^IsNilableTypeFlags + } + ctx.incIndex() + return Opcodes{code} } type PtrCode struct { - typ *runtime.Type - value Code + typ *runtime.Type + value Code + ptrNum uint8 } func (c *PtrCode) Type() CodeType2 { return CodeTypePtr } -func (c *PtrCode) ToOpcode() []*Opcode { - return []*Opcode{} +func (c *PtrCode) ToOpcode(ctx *compileContext) Opcodes { + codes := c.value.ToOpcode(ctx) + codes.First().PtrNum = c.ptrNum + return codes } func type2code(ctx *compileContext) (Code, error) { @@ -450,6 +844,46 @@ func compileFloat642(ctx *compileContext, isPtr bool) (*FloatCode, error) { return &FloatCode{typ: ctx.typ, bitSize: 64, isPtr: isPtr}, nil } +func compileIntString2(ctx *compileContext) (*IntCode, error) { + return &IntCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil +} + +func compileInt8String2(ctx *compileContext) (*IntCode, error) { + return &IntCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil +} + +func compileInt16String2(ctx *compileContext) (*IntCode, error) { + return &IntCode{typ: ctx.typ, bitSize: 8, isString: true}, nil +} + +func compileInt32String2(ctx *compileContext) (*IntCode, error) { + return &IntCode{typ: ctx.typ, bitSize: 16, isString: true}, nil +} + +func compileInt64String2(ctx *compileContext) (*IntCode, error) { + return &IntCode{typ: ctx.typ, bitSize: 64, isString: true}, nil +} + +func compileUintString2(ctx *compileContext) (*UintCode, error) { + return &UintCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil +} + +func compileUint8String2(ctx *compileContext) (*UintCode, error) { + return &UintCode{typ: ctx.typ, bitSize: intSize, isString: true}, nil +} + +func compileUint16String2(ctx *compileContext) (*UintCode, error) { + return &UintCode{typ: ctx.typ, bitSize: 8, isString: true}, nil +} + +func compileUint32String2(ctx *compileContext) (*UintCode, error) { + return &UintCode{typ: ctx.typ, bitSize: 16, isString: true}, nil +} + +func compileUint64String2(ctx *compileContext) (*UintCode, error) { + return &UintCode{typ: ctx.typ, bitSize: 64, isString: true}, nil +} + func compileString2(ctx *compileContext, isString bool) (*StringCode, error) { return &StringCode{typ: ctx.typ, isString: isString}, nil } @@ -459,15 +893,47 @@ func compileBool2(ctx *compileContext, isString bool) (*BoolCode, error) { } func compileSlice2(ctx *compileContext) (*SliceCode, error) { - return &SliceCode{typ: ctx.typ}, nil + elem := ctx.typ.Elem() + code, err := compileListElem2(ctx.withType(elem)) + if err != nil { + return nil, err + } + if code.Type() == CodeTypeStruct { + structCode := code.(*StructCode) + structCode.enableIndirect() + } + return &SliceCode{typ: ctx.typ, value: code}, nil } func compileArray2(ctx *compileContext) (*ArrayCode, error) { - return &ArrayCode{typ: ctx.typ}, nil + typ := ctx.typ + elem := typ.Elem() + code, err := compileListElem2(ctx.withType(elem)) + if err != nil { + return nil, err + } + if code.Type() == CodeTypeStruct { + structCode := code.(*StructCode) + structCode.enableIndirect() + } + return &ArrayCode{typ: ctx.typ, value: code}, nil } func compileMap2(ctx *compileContext) (*MapCode, error) { - return &MapCode{typ: ctx.typ}, nil + typ := ctx.typ + keyCode, err := compileMapKey(ctx.withType(typ.Key())) + if err != nil { + return nil, err + } + valueCode, err := compileMapValue2(ctx.withType(typ.Elem())) + if err != nil { + return nil, err + } + if valueCode.Type() == CodeTypeStruct { + structCode := valueCode.(*StructCode) + structCode.enableIndirect() + } + return &MapCode{typ: ctx.typ, key: keyCode, value: valueCode}, nil } func compileBytes2(ctx *compileContext, isPtr bool) (*BytesCode, error) { @@ -491,21 +957,109 @@ func compilePtr2(ctx *compileContext) (*PtrCode, error) { if err != nil { return nil, err } - return &PtrCode{typ: ctx.typ, value: code}, nil + ptr, ok := code.(*PtrCode) + if ok { + return &PtrCode{typ: ctx.typ, value: ptr.value, ptrNum: ptr.ptrNum + 1}, nil + } + return &PtrCode{typ: ctx.typ, value: code, ptrNum: 1}, nil +} + +func compileListElem2(ctx *compileContext) (Code, error) { + typ := ctx.typ + switch { + case isPtrMarshalJSONType(typ): + return compileMarshalJSON2(ctx) + case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType): + return compileMarshalText2(ctx) + case typ.Kind() == reflect.Map: + return compilePtr2(ctx.withType(runtime.PtrTo(typ))) + default: + code, err := type2codeWithPtr(ctx, false) + if err != nil { + return nil, err + } + ptr, ok := code.(*PtrCode) + if ok { + if ptr.value.Type() == CodeTypeMap { + ptr.ptrNum++ + } + } + return code, nil + } +} + +func compileMapKey(ctx *compileContext) (Code, error) { + typ := ctx.typ + switch { + case implementsMarshalJSON(typ): + return compileMarshalJSON2(ctx) + case implementsMarshalText(typ): + return compileMarshalText2(ctx) + } + switch typ.Kind() { + case reflect.Ptr: + return compilePtr2(ctx) + case reflect.String: + return compileString2(ctx, false) + case reflect.Int: + return compileIntString2(ctx) + case reflect.Int8: + return compileInt8String2(ctx) + case reflect.Int16: + return compileInt16String2(ctx) + case reflect.Int32: + return compileInt32String2(ctx) + case reflect.Int64: + return compileInt64String2(ctx) + case reflect.Uint: + return compileUintString2(ctx) + case reflect.Uint8: + return compileUint8String2(ctx) + case reflect.Uint16: + return compileUint16String2(ctx) + case reflect.Uint32: + return compileUint32String2(ctx) + case reflect.Uint64: + return compileUint64String2(ctx) + case reflect.Uintptr: + return compileUintString2(ctx) + } + return nil, &errors.UnsupportedTypeError{Type: runtime.RType2Type(typ)} +} + +func compileMapValue2(ctx *compileContext) (Code, error) { + switch ctx.typ.Kind() { + case reflect.Map: + return compilePtr2(ctx.withType(runtime.PtrTo(ctx.typ))) + default: + code, err := type2codeWithPtr(ctx, false) + if err != nil { + return nil, err + } + ptr, ok := code.(*PtrCode) + if ok { + if ptr.value.Type() == CodeTypeMap { + ptr.ptrNum++ + } + } + return code, nil + } } func compileStruct2(ctx *compileContext, isPtr bool) (*StructCode, error) { - //typeptr := uintptr(unsafe.Pointer(typ)) - //compiled := &CompiledCode{} - //ctx.structTypeToCompiledCode[typeptr] = compiled - // header => code => structField => code => end - // ^ | - // |__________| typ := ctx.typ + typeptr := uintptr(unsafe.Pointer(typ)) + if code, exists := ctx.structTypeToCode[typeptr]; exists { + derefCode := *code + derefCode.isRecursive = true + return &derefCode, nil + } + indirect := runtime.IfaceIndir(typ) + code := &StructCode{typ: typ, isPtr: isPtr, isIndirect: indirect} + ctx.structTypeToCode[typeptr] = code + fieldNum := typ.NumField() - //indirect := runtime.IfaceIndir(typ) tags := typeToStructTags(typ) - code := &StructCode{typ: typ, isPtr: isPtr} fields := []*StructFieldCode{} for i, tag := range tags { isOnlyOneFirstField := i == 0 && fieldNum == 1 @@ -518,12 +1072,35 @@ func compileStruct2(ctx *compileContext, isPtr bool) (*StructCode, error) { if structCode != nil { structCode.removeFieldsByTags(tags) } + if isAssignableIndirect(field, isPtr) { + if indirect { + structCode.isIndirect = true + } else { + structCode.isIndirect = false + } + } + } else { + structCode := field.getStruct() + if structCode != nil { + if indirect { + // if parent is indirect type, set child indirect property to true + structCode.isIndirect = true + } else { + // if parent is not indirect type, set child indirect property to false. + // but if parent's indirect is false and isPtr is true, then indirect must be true. + // Do this only if indirectConversion is enabled at the end of compileStruct. + structCode.isIndirect = false + } + } } fields = append(fields, field) } fieldMap := getFieldMap(fields) duplicatedFieldMap := getDuplicatedFieldMap(fieldMap) code.fields = filteredDuplicatedFields(fields, duplicatedFieldMap) + if !code.disableIndirectConversion && !indirect && isPtr { + code.enableIndirect() + } return code, nil } @@ -532,7 +1109,7 @@ func getFieldMap(fields []*StructFieldCode) map[string][]*StructFieldCode { for _, field := range fields { if field.isAnonymous { structCode := field.getAnonymousStruct() - if structCode != nil { + if structCode != nil && !structCode.isRecursive { for k, v := range getFieldMap(structCode.fields) { fieldMap[k] = append(fieldMap[k], v...) } @@ -571,7 +1148,7 @@ func filteredDuplicatedFields(fields []*StructFieldCode, duplicatedFieldMap map[ for _, field := range fields { if field.isAnonymous { structCode := field.getAnonymousStruct() - if structCode != nil { + if structCode != nil && !structCode.isRecursive { structCode.fields = filteredDuplicatedFields(structCode.fields, duplicatedFieldMap) if len(structCode.fields) > 0 { filteredFields = append(filteredFields, field) @@ -629,6 +1206,7 @@ func (c *StructCode) compileStructField(ctx *compileContext, tag *runtime.Struct fieldCode := &StructFieldCode{ typ: fieldType, key: tag.Key, + tag: tag, offset: field.Offset, isAnonymous: field.Anonymous && !tag.IsTaggedKey, isTaggedKey: tag.IsTaggedKey, @@ -644,6 +1222,7 @@ func (c *StructCode) compileStructField(ctx *compileContext, tag *runtime.Struct fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false + c.isIndirect = false c.disableIndirectConversion = true case isMovePointerPositionFromHeadToFirstMarshalTextFieldCase(fieldType, isIndirectSpecialCase): code, err := compileMarshalText2(ctx.withType(fieldType)) @@ -653,6 +1232,7 @@ func (c *StructCode) compileStructField(ctx *compileContext, tag *runtime.Struct fieldCode.value = code fieldCode.isAddrForMarshaler = true fieldCode.isNilCheck = false + c.isIndirect = false c.disableIndirectConversion = true case isPtr && isPtrMarshalJSONType(fieldType): // *struct{ field T } @@ -688,3 +1268,17 @@ func (c *StructCode) compileStructField(ctx *compileContext, tag *runtime.Struct } return fieldCode, nil } + +func isAssignableIndirect(fieldCode *StructFieldCode, isPtr bool) bool { + if isPtr { + return false + } + codeType := fieldCode.value.Type() + if codeType == CodeTypeMarshalJSON { + return false + } + if codeType == CodeTypeMarshalText { + return false + } + return true +} diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index 91c6181..7b83a0a 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -12,7 +12,6 @@ import ( "github.com/goccy/go-json/internal/errors" "github.com/goccy/go-json/internal/runtime" - "github.com/k0kubun/pp" ) type marshalerContext interface { @@ -102,7 +101,12 @@ func compileHead(ctx *compileContext) (*Opcode, error) { if err != nil { return nil, err } - pp.Println(code) + //pp.Println(code) + newCtx := *ctx + codes := code.ToOpcode(&newCtx) + codes.Last().Next = newEndOp(ctx) + //pp.Println(codes) + fmt.Println(codes.First().Dump()) typ := ctx.typ switch { diff --git a/internal/encoder/compiler_norace.go b/internal/encoder/compiler_norace.go index 9d337f1..0115e77 100644 --- a/internal/encoder/compiler_norace.go +++ b/internal/encoder/compiler_norace.go @@ -1,3 +1,4 @@ +//go:build !race // +build !race package encoder @@ -23,6 +24,7 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { noescapeKeyCode, err := compileHead(&compileContext{ typ: copiedType, structTypeToCompiledCode: map[uintptr]*CompiledCode{}, + structTypeToCode: map[uintptr]*StructCode{}, }) if err != nil { return nil, err @@ -30,6 +32,7 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { escapeKeyCode, err := compileHead(&compileContext{ typ: copiedType, structTypeToCompiledCode: map[uintptr]*CompiledCode{}, + structTypeToCode: map[uintptr]*StructCode{}, escapeKey: true, }) if err != nil { diff --git a/internal/encoder/compiler_race.go b/internal/encoder/compiler_race.go index 3a239e9..38ff447 100644 --- a/internal/encoder/compiler_race.go +++ b/internal/encoder/compiler_race.go @@ -1,3 +1,4 @@ +//go:build race // +build race package encoder @@ -29,6 +30,7 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { noescapeKeyCode, err := compileHead(&compileContext{ typ: copiedType, structTypeToCompiledCode: map[uintptr]*CompiledCode{}, + structTypeToCode: map[uintptr]*StructCode{}, }) if err != nil { return nil, err @@ -36,6 +38,7 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) { escapeKeyCode, err := compileHead(&compileContext{ typ: copiedType, structTypeToCompiledCode: map[uintptr]*CompiledCode{}, + structTypeToCode: map[uintptr]*StructCode{}, escapeKey: true, }) if err != nil { diff --git a/internal/encoder/context.go b/internal/encoder/context.go index 61b8908..6a2b2b0 100644 --- a/internal/encoder/context.go +++ b/internal/encoder/context.go @@ -15,6 +15,7 @@ type compileContext struct { indent uint32 escapeKey bool structTypeToCompiledCode map[uintptr]*CompiledCode + structTypeToCode map[uintptr]*StructCode parent *compileContext } @@ -27,6 +28,7 @@ func (c *compileContext) context() *compileContext { indent: c.indent, escapeKey: c.escapeKey, structTypeToCompiledCode: c.structTypeToCompiledCode, + structTypeToCode: c.structTypeToCode, parent: c, } }