package json import ( "encoding" "fmt" "reflect" "strings" "sync/atomic" "unsafe" ) type compiledCode struct { code *opcode linked bool // whether recursive code already have linked curLen uintptr nextLen uintptr } type opcodeSet struct { code *opcode codeLength int } var ( marshalJSONType = reflect.TypeOf((*Marshaler)(nil)).Elem() marshalTextType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() cachedOpcode unsafe.Pointer // map[uintptr]*opcodeSet baseTypeAddr uintptr cachedOpcodeSets []*opcodeSet ) const ( maxAcceptableTypeAddrRange = 1024 * 1024 * 2 // 2 Mib ) //go:linkname typelinks reflect.typelinks func typelinks() ([]unsafe.Pointer, [][]int32) //go:linkname rtypeOff reflect.rtypeOff func rtypeOff(unsafe.Pointer, int32) unsafe.Pointer func setupOpcodeSets() error { sections, offsets := typelinks() if len(sections) != 1 { return fmt.Errorf("failed to get sections") } if len(offsets) != 1 { return fmt.Errorf("failed to get offsets") } section := sections[0] offset := offsets[0] var ( min uintptr = uintptr(^uint(0)) max uintptr = 0 ) for i := 0; i < len(offset); i++ { typ := (*rtype)(rtypeOff(section, offset[i])) addr := uintptr(unsafe.Pointer(typ)) if min > addr { min = addr } if max < addr { max = addr } if typ.Kind() == reflect.Ptr { addr = uintptr(unsafe.Pointer(typ.Elem())) if min > addr { min = addr } if max < addr { max = addr } } } addrRange := uintptr(max) - uintptr(min) if addrRange == 0 { return fmt.Errorf("failed to get address range of types") } if addrRange > maxAcceptableTypeAddrRange { return fmt.Errorf("too big address range %d", addrRange) } cachedOpcodeSets = make([]*opcodeSet, addrRange) baseTypeAddr = min return nil } func init() { if err := setupOpcodeSets(); err != nil { // fallback to slow path } } func encodeCompileToGetCodeSet(typeptr uintptr) (*opcodeSet, error) { if cachedOpcodeSets == nil { return encodeCompileToGetCodeSetSlowPath(typeptr) } if codeSet := cachedOpcodeSets[typeptr-baseTypeAddr]; codeSet != nil { return codeSet, nil } // noescape trick for header.typ ( reflect.*rtype ) copiedType := *(**rtype)(unsafe.Pointer(&typeptr)) code, err := encodeCompileHead(&encodeCompileContext{ typ: copiedType, root: true, structTypeToCompiledCode: map[uintptr]*compiledCode{}, }) if err != nil { return nil, err } code = copyOpcode(code) codeLength := code.totalLength() codeSet := &opcodeSet{ code: code, codeLength: codeLength, } cachedOpcodeSets[int(typeptr-baseTypeAddr)] = codeSet return codeSet, nil } func encodeCompileToGetCodeSetSlowPath(typeptr uintptr) (*opcodeSet, error) { opcodeMap := loadOpcodeMap() if codeSet, exists := opcodeMap[typeptr]; exists { return codeSet, nil } // noescape trick for header.typ ( reflect.*rtype ) copiedType := *(**rtype)(unsafe.Pointer(&typeptr)) code, err := encodeCompileHead(&encodeCompileContext{ typ: copiedType, root: true, 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 loadOpcodeMap() map[uintptr]*opcodeSet { p := atomic.LoadPointer(&cachedOpcode) 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(&cachedOpcode, *(*unsafe.Pointer)(unsafe.Pointer(&newOpcodeMap))) } func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) { typ := ctx.typ switch { case typ.Implements(marshalJSONType): return encodeCompileMarshalJSON(ctx) case rtype_ptrTo(typ).Implements(marshalJSONType): return encodeCompileMarshalJSONPtr(ctx) case typ.Implements(marshalTextType): return encodeCompileMarshalText(ctx) case rtype_ptrTo(typ).Implements(marshalTextType): return encodeCompileMarshalTextPtr(ctx) } isPtr := false orgType := typ if typ.Kind() == reflect.Ptr { typ = typ.Elem() isPtr = true } if typ.Kind() == reflect.Map { return encodeCompileMap(ctx.withType(typ), isPtr) } else if typ.Kind() == reflect.Struct { code, err := encodeCompileStruct(ctx.withType(typ), isPtr) if err != nil { return nil, err } encodeConvertHeadOnlyCode(code, isPtr) encodeOptimizeStructEnd(code) encodeLinkRecursiveCode(code) return code, nil } else if isPtr && typ.Implements(marshalTextType) { typ = orgType } else if isPtr && typ.Implements(marshalJSONType) { typ = orgType } code, err := encodeCompile(ctx.withType(typ)) if err != nil { return nil, err } encodeConvertHeadOnlyCode(code, isPtr) encodeOptimizeStructEnd(code) encodeLinkRecursiveCode(code) return code, nil } func encodeLinkRecursiveCode(c *opcode) { for code := c; code.op != opEnd && code.op != opStructFieldRecursiveEnd; { switch code.op { case opStructFieldRecursive, opStructFieldPtrAnonymousHeadRecursive, opStructFieldAnonymousHeadRecursive: if code.jmp.linked { code = code.next continue } code.jmp.code = copyOpcode(code.jmp.code) c := code.jmp.code c.end.next = newEndOp(&encodeCompileContext{}) 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 encodeLinkRecursiveCode(code.jmp.code) code = code.next continue } switch code.op.codeType() { case codeArrayElem, codeSliceElem, codeMapKey: code = code.end default: code = code.next } } } func encodeOptimizeStructEnd(c *opcode) { for code := c; code.op != opEnd; { if code.op == opStructFieldRecursive { // 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 if strings.Contains(prev.op.String(), "Head") { // 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 encodeConvertHeadOnlyCode(c *opcode, isPtrHead bool) { if c.nextField == nil { return } if c.nextField.op.codeType() != codeStructEnd { return } switch c.op { case opStructFieldHead: encodeConvertHeadOnlyCode(c.next, false) if !strings.Contains(c.next.op.String(), "Only") { return } c.op = opStructFieldHeadOnly case opStructFieldHeadOmitEmpty: encodeConvertHeadOnlyCode(c.next, false) if !strings.Contains(c.next.op.String(), "Only") { return } c.op = opStructFieldHeadOmitEmptyOnly case opStructFieldHeadStringTag: encodeConvertHeadOnlyCode(c.next, false) if !strings.Contains(c.next.op.String(), "Only") { return } c.op = opStructFieldHeadStringTagOnly case opStructFieldPtrHead: } if strings.Contains(c.op.String(), "Marshal") { return } if strings.Contains(c.op.String(), "Slice") { return } if strings.Contains(c.op.String(), "Map") { return } isPtrOp := strings.Contains(c.op.String(), "Ptr") if isPtrOp && !isPtrHead { c.op = c.op.headToOnlyHead() } else if !isPtrOp && isPtrHead { c.op = c.op.headToPtrHead().headToOnlyHead() } else if isPtrOp && isPtrHead { c.op = c.op.headToPtrHead().headToOnlyHead() } } func encodeImplementsMarshaler(typ *rtype) bool { switch { case typ.Implements(marshalJSONType): return true case rtype_ptrTo(typ).Implements(marshalJSONType): return true case typ.Implements(marshalTextType): return true case rtype_ptrTo(typ).Implements(marshalTextType): return true } return false } func encodeCompile(ctx *encodeCompileContext) (*opcode, error) { typ := ctx.typ switch { case typ.Implements(marshalJSONType): return encodeCompileMarshalJSON(ctx) case rtype_ptrTo(typ).Implements(marshalJSONType): return encodeCompileMarshalJSONPtr(ctx) case typ.Implements(marshalTextType): return encodeCompileMarshalText(ctx) case rtype_ptrTo(typ).Implements(marshalTextType): return encodeCompileMarshalTextPtr(ctx) } switch typ.Kind() { case reflect.Ptr: return encodeCompilePtr(ctx) case reflect.Slice: elem := typ.Elem() if !encodeImplementsMarshaler(elem) && elem.Kind() == reflect.Uint8 { return encodeCompileBytes(ctx) } return encodeCompileSlice(ctx) case reflect.Array: return encodeCompileArray(ctx) case reflect.Map: return encodeCompileMap(ctx, true) case reflect.Struct: return encodeCompileStruct(ctx, false) case reflect.Interface: return encodeCompileInterface(ctx) case reflect.Int: return encodeCompileInt(ctx) case reflect.Int8: return encodeCompileInt8(ctx) case reflect.Int16: return encodeCompileInt16(ctx) case reflect.Int32: return encodeCompileInt32(ctx) case reflect.Int64: return encodeCompileInt64(ctx) case reflect.Uint: return encodeCompileUint(ctx) case reflect.Uint8: return encodeCompileUint8(ctx) case reflect.Uint16: return encodeCompileUint16(ctx) case reflect.Uint32: return encodeCompileUint32(ctx) case reflect.Uint64: return encodeCompileUint64(ctx) case reflect.Uintptr: return encodeCompileUint(ctx) case reflect.Float32: return encodeCompileFloat32(ctx) case reflect.Float64: return encodeCompileFloat64(ctx) case reflect.String: return encodeCompileString(ctx) case reflect.Bool: return encodeCompileBool(ctx) } return nil, &UnsupportedTypeError{Type: rtype2type(typ)} } func encodeCompileKey(ctx *encodeCompileContext) (*opcode, error) { typ := ctx.typ switch { case rtype_ptrTo(typ).Implements(marshalJSONType): return encodeCompileMarshalJSONPtr(ctx) case rtype_ptrTo(typ).Implements(marshalTextType): return encodeCompileMarshalTextPtr(ctx) } switch typ.Kind() { case reflect.Ptr: return encodeCompilePtr(ctx) case reflect.Interface: return encodeCompileInterface(ctx) case reflect.String: return encodeCompileString(ctx) case reflect.Int: return encodeCompileIntString(ctx) case reflect.Int8: return encodeCompileInt8String(ctx) case reflect.Int16: return encodeCompileInt16String(ctx) case reflect.Int32: return encodeCompileInt32String(ctx) case reflect.Int64: return encodeCompileInt64String(ctx) case reflect.Uint: return encodeCompileUintString(ctx) case reflect.Uint8: return encodeCompileUint8String(ctx) case reflect.Uint16: return encodeCompileUint16String(ctx) case reflect.Uint32: return encodeCompileUint32String(ctx) case reflect.Uint64: return encodeCompileUint64String(ctx) case reflect.Uintptr: return encodeCompileUintString(ctx) } return nil, &UnsupportedTypeError{Type: rtype2type(typ)} } func encodeCompilePtr(ctx *encodeCompileContext) (*opcode, error) { ptrOpcodeIndex := ctx.opcodeIndex ptrIndex := ctx.ptrIndex ctx.incIndex() code, err := encodeCompile(ctx.withType(ctx.typ.Elem())) if err != nil { return nil, err } ptrHeadOp := code.op.headToPtrHead() if code.op != ptrHeadOp { code.op = ptrHeadOp code.decOpcodeIndex() ctx.decIndex() return code, nil } c := ctx.context() c.opcodeIndex = ptrOpcodeIndex c.ptrIndex = ptrIndex return newOpCodeWithNext(c, opPtr, code), nil } func encodeCompileMarshalJSON(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opMarshalJSON) ctx.incIndex() return code, nil } func encodeCompileMarshalJSONPtr(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalJSON) ctx.incIndex() return code, nil } func encodeCompileMarshalText(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opMarshalText) ctx.incIndex() return code, nil } func encodeCompileMarshalTextPtr(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalText) ctx.incIndex() return code, nil } func encodeCompileInt(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt) ctx.incIndex() return code, nil } func encodeCompileInt8(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt8) ctx.incIndex() return code, nil } func encodeCompileInt16(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt16) ctx.incIndex() return code, nil } func encodeCompileInt32(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt32) ctx.incIndex() return code, nil } func encodeCompileInt64(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt64) ctx.incIndex() return code, nil } func encodeCompileUint(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint) ctx.incIndex() return code, nil } func encodeCompileUint8(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint8) ctx.incIndex() return code, nil } func encodeCompileUint16(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint16) ctx.incIndex() return code, nil } func encodeCompileUint32(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint32) ctx.incIndex() return code, nil } func encodeCompileUint64(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint64) ctx.incIndex() return code, nil } func encodeCompileIntString(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opIntString) ctx.incIndex() return code, nil } func encodeCompileInt8String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt8String) ctx.incIndex() return code, nil } func encodeCompileInt16String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt16String) ctx.incIndex() return code, nil } func encodeCompileInt32String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt32String) ctx.incIndex() return code, nil } func encodeCompileInt64String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opInt64String) ctx.incIndex() return code, nil } func encodeCompileUintString(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUintString) ctx.incIndex() return code, nil } func encodeCompileUint8String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint8String) ctx.incIndex() return code, nil } func encodeCompileUint16String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint16String) ctx.incIndex() return code, nil } func encodeCompileUint32String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint32String) ctx.incIndex() return code, nil } func encodeCompileUint64String(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opUint64String) ctx.incIndex() return code, nil } func encodeCompileFloat32(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opFloat32) ctx.incIndex() return code, nil } func encodeCompileFloat64(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opFloat64) ctx.incIndex() return code, nil } func encodeCompileString(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opString) ctx.incIndex() return code, nil } func encodeCompileBool(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opBool) ctx.incIndex() return code, nil } func encodeCompileBytes(ctx *encodeCompileContext) (*opcode, error) { code := newOpCode(ctx, opBytes) ctx.incIndex() return code, nil } func encodeCompileInterface(ctx *encodeCompileContext) (*opcode, error) { code := newInterfaceCode(ctx) ctx.incIndex() return code, nil } func encodeCompileSlice(ctx *encodeCompileContext) (*opcode, error) { ctx.root = false elem := ctx.typ.Elem() size := elem.Size() header := newSliceHeaderCode(ctx) ctx.incIndex() code, err := encodeCompile(ctx.withType(ctx.typ.Elem()).incIndent()) if err != nil { return nil, err } // 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 encodeCompileArray(ctx *encodeCompileContext) (*opcode, error) { ctx.root = false typ := ctx.typ elem := typ.Elem() alen := typ.Len() size := elem.Size() header := newArrayHeaderCode(ctx, alen) ctx.incIndex() code, err := encodeCompile(ctx.withType(elem).incIndent()) if err != nil { return nil, err } // 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 } //go:linkname mapiterinit reflect.mapiterinit //go:noescape func mapiterinit(mapType *rtype, m unsafe.Pointer) unsafe.Pointer //go:linkname mapiterkey reflect.mapiterkey //go:noescape func mapiterkey(it unsafe.Pointer) unsafe.Pointer //go:linkname mapiternext reflect.mapiternext //go:noescape func mapiternext(it unsafe.Pointer) //go:linkname maplen reflect.maplen //go:noescape func maplen(m unsafe.Pointer) int func encodeCompileMap(ctx *encodeCompileContext, withLoad bool) (*opcode, error) { // header => code => value => code => key => code => value => code => end // ^ | // |_______________________| ctx = ctx.incIndent() header := newMapHeaderCode(ctx, withLoad) ctx.incIndex() typ := ctx.typ keyType := ctx.typ.Key() keyCode, err := encodeCompileKey(ctx.withType(keyType)) if err != nil { return nil, err } value := newMapValueCode(ctx, header) ctx.incIndex() valueType := typ.Elem() valueCode, err := encodeCompile(ctx.withType(valueType)) if err != nil { return nil, err } 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 encodeTypeToHeaderType(ctx *encodeCompileContext, code *opcode) opType { switch code.op { case opPtr: ptrNum := 1 c := code ctx.decIndex() for { if code.next.op == opPtr { ptrNum++ code = code.next ctx.decIndex() } break } c.ptrNum = ptrNum if ptrNum > 1 { switch code.next.op { case opInt: return opStructFieldHeadIntNPtr case opInt8: return opStructFieldHeadInt8NPtr case opInt16: return opStructFieldHeadInt16NPtr case opInt32: return opStructFieldHeadInt32NPtr case opInt64: return opStructFieldHeadInt64NPtr case opUint: return opStructFieldHeadUintNPtr case opUint8: return opStructFieldHeadUint8NPtr case opUint16: return opStructFieldHeadUint16NPtr case opUint32: return opStructFieldHeadUint32NPtr case opUint64: return opStructFieldHeadUint64NPtr case opFloat32: return opStructFieldHeadFloat32NPtr case opFloat64: return opStructFieldHeadFloat64NPtr case opString: return opStructFieldHeadStringNPtr case opBool: return opStructFieldHeadBoolNPtr } } else { switch code.next.op { case opInt: return opStructFieldHeadIntPtr case opInt8: return opStructFieldHeadInt8Ptr case opInt16: return opStructFieldHeadInt16Ptr case opInt32: return opStructFieldHeadInt32Ptr case opInt64: return opStructFieldHeadInt64Ptr case opUint: return opStructFieldHeadUintPtr case opUint8: return opStructFieldHeadUint8Ptr case opUint16: return opStructFieldHeadUint16Ptr case opUint32: return opStructFieldHeadUint32Ptr case opUint64: return opStructFieldHeadUint64Ptr case opFloat32: return opStructFieldHeadFloat32Ptr case opFloat64: return opStructFieldHeadFloat64Ptr case opString: return opStructFieldHeadStringPtr case opBool: return opStructFieldHeadBoolPtr } } case opInt: return opStructFieldHeadInt case opInt8: return opStructFieldHeadInt8 case opInt16: return opStructFieldHeadInt16 case opInt32: return opStructFieldHeadInt32 case opInt64: return opStructFieldHeadInt64 case opUint: return opStructFieldHeadUint case opUint8: return opStructFieldHeadUint8 case opUint16: return opStructFieldHeadUint16 case opUint32: return opStructFieldHeadUint32 case opUint64: return opStructFieldHeadUint64 case opFloat32: return opStructFieldHeadFloat32 case opFloat64: return opStructFieldHeadFloat64 case opString: return opStructFieldHeadString case opBool: return opStructFieldHeadBool case opMapHead: return opStructFieldHeadMap case opMapHeadLoad: return opStructFieldHeadMapLoad case opArrayHead: return opStructFieldHeadArray case opSliceHead: return opStructFieldHeadSlice case opStructFieldHead: return opStructFieldHeadStruct case opMarshalJSON: return opStructFieldHeadMarshalJSON case opMarshalText: return opStructFieldHeadMarshalText } return opStructFieldHead } func encodeTypeToFieldType(ctx *encodeCompileContext, code *opcode) opType { switch code.op { case opPtr: ptrNum := 1 ctx.decIndex() c := code for { if code.next.op == opPtr { ptrNum++ code = code.next ctx.decIndex() } break } c.ptrNum = ptrNum if ptrNum > 1 { switch code.next.op { case opInt: return opStructFieldIntNPtr case opInt8: return opStructFieldInt8NPtr case opInt16: return opStructFieldInt16NPtr case opInt32: return opStructFieldInt32NPtr case opInt64: return opStructFieldInt64NPtr case opUint: return opStructFieldUintNPtr case opUint8: return opStructFieldUint8NPtr case opUint16: return opStructFieldUint16NPtr case opUint32: return opStructFieldUint32NPtr case opUint64: return opStructFieldUint64NPtr case opFloat32: return opStructFieldFloat32NPtr case opFloat64: return opStructFieldFloat64NPtr case opString: return opStructFieldStringNPtr case opBool: return opStructFieldBoolNPtr } } else { switch code.next.op { case opInt: return opStructFieldIntPtr case opInt8: return opStructFieldInt8Ptr case opInt16: return opStructFieldInt16Ptr case opInt32: return opStructFieldInt32Ptr case opInt64: return opStructFieldInt64Ptr case opUint: return opStructFieldUintPtr case opUint8: return opStructFieldUint8Ptr case opUint16: return opStructFieldUint16Ptr case opUint32: return opStructFieldUint32Ptr case opUint64: return opStructFieldUint64Ptr case opFloat32: return opStructFieldFloat32Ptr case opFloat64: return opStructFieldFloat64Ptr case opString: return opStructFieldStringPtr case opBool: return opStructFieldBoolPtr } } case opInt: return opStructFieldInt case opInt8: return opStructFieldInt8 case opInt16: return opStructFieldInt16 case opInt32: return opStructFieldInt32 case opInt64: return opStructFieldInt64 case opUint: return opStructFieldUint case opUint8: return opStructFieldUint8 case opUint16: return opStructFieldUint16 case opUint32: return opStructFieldUint32 case opUint64: return opStructFieldUint64 case opFloat32: return opStructFieldFloat32 case opFloat64: return opStructFieldFloat64 case opString: return opStructFieldString case opBool: return opStructFieldBool case opMapHead: return opStructFieldMap case opMapHeadLoad: return opStructFieldMapLoad case opArrayHead: return opStructFieldArray case opSliceHead: return opStructFieldSlice case opStructFieldHead: return opStructFieldStruct case opMarshalJSON: return opStructFieldMarshalJSON case opMarshalText: return opStructFieldMarshalText } return opStructField } func encodeOptimizeStructHeader(ctx *encodeCompileContext, code *opcode, tag *structTag) opType { headType := encodeTypeToHeaderType(ctx, code) switch { case tag.isOmitEmpty: headType = headType.headToOmitEmptyHead() case tag.isString: headType = headType.headToStringTagHead() } return headType } func encodeOptimizeStructField(ctx *encodeCompileContext, code *opcode, tag *structTag) opType { fieldType := encodeTypeToFieldType(ctx, code) switch { case tag.isOmitEmpty: fieldType = fieldType.fieldToOmitEmptyField() case tag.isString: fieldType = fieldType.fieldToStringTagField() } return fieldType } func encodeRecursiveCode(ctx *encodeCompileContext, jmp *compiledCode) *opcode { code := newRecursiveCode(ctx, jmp) ctx.incIndex() return code } func encodeCompiledCode(ctx *encodeCompileContext) *opcode { typ := ctx.typ typeptr := uintptr(unsafe.Pointer(typ)) if compiledCode, exists := ctx.structTypeToCompiledCode[typeptr]; exists { return encodeRecursiveCode(ctx, compiledCode) } return nil } func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode { fieldCode.indent-- op := encodeOptimizeStructHeader(ctx, valueCode, tag) fieldCode.op = op fieldCode.ptrNum = valueCode.ptrNum switch op { case opStructFieldHead, opStructFieldHeadSlice, opStructFieldHeadArray, opStructFieldHeadMap, opStructFieldHeadMapLoad, opStructFieldHeadStruct, opStructFieldHeadOmitEmpty, opStructFieldHeadOmitEmptySlice, opStructFieldHeadOmitEmptyArray, opStructFieldHeadOmitEmptyMap, opStructFieldHeadOmitEmptyMapLoad, opStructFieldHeadOmitEmptyStruct, opStructFieldHeadStringTag: return valueCode.beforeLastCode() } ctx.decOpcodeIndex() return (*opcode)(unsafe.Pointer(fieldCode)) } func encodeStructField(ctx *encodeCompileContext, fieldCode *opcode, valueCode *opcode, tag *structTag) *opcode { code := (*opcode)(unsafe.Pointer(fieldCode)) op := encodeOptimizeStructField(ctx, valueCode, tag) fieldCode.op = op fieldCode.ptrNum = valueCode.ptrNum switch op { case opStructField, opStructFieldSlice, opStructFieldArray, opStructFieldMap, opStructFieldMapLoad, opStructFieldStruct, opStructFieldOmitEmpty, opStructFieldOmitEmptySlice, opStructFieldOmitEmptyArray, opStructFieldOmitEmptyMap, opStructFieldOmitEmptyMapLoad, opStructFieldOmitEmptyStruct, opStructFieldStringTag: return valueCode.beforeLastCode() } ctx.decIndex() return code } func encodeIsNotExistsField(head *opcode) bool { if head == nil { return false } if head.op != opStructFieldAnonymousHead { 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 encodeIsNotExistsField(head.next) } func encodeOptimizeAnonymousFields(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 encodeIsNotExistsField(code.next) { code.next = code.nextField diff := code.next.displayIdx - code.displayIdx for i := 0; i < diff; i++ { code.next.decOpcodeIndex() } encodeLinkPrevToNextField(code, removedFields) code = prev } } } prev = code code = code.nextField } } type structFieldPair struct { prevField *opcode curField *opcode isTaggedKey bool linked bool } func encodeAnonymousStructFieldPairMap(typ *rtype, tags 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) op := f.op.headToAnonymousHead() if existsKey && (f.next.op == opStructFieldPtrAnonymousHeadRecursive || f.next.op == opStructFieldAnonymousHeadRecursive) { // through } else if op != f.op { if existsKey { f.op = opStructFieldAnonymousHead } else if named == "" { f.op = op } } 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() } encodeLinkPrevToNextField(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 encodeAnonymousStructFieldPairMap(typ, tags, named, f.next) { anonymousFields[k] = append(anonymousFields[k], v...) } } if f.nextField == nil { break } prevAnonymousField = f f = f.nextField } return anonymousFields } func encodeOptimizeConflictAnonymousFields(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 = opStructFieldAnonymousHead } else { diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx for i := 0; i < diff; i++ { fieldPair.curField.nextField.decOpcodeIndex() } removedFields[fieldPair.curField] = struct{}{} encodeLinkPrevToNextField(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 = opStructFieldAnonymousHead } else { diff := fieldPair.curField.nextField.displayIdx - fieldPair.curField.displayIdx removedFields[fieldPair.curField] = struct{}{} for i := 0; i < diff; i++ { fieldPair.curField.nextField.decOpcodeIndex() } encodeLinkPrevToNextField(fieldPair.curField, removedFields) } fieldPair.linked = true } } } else { for _, fieldPair := range taggedPairs { fieldPair.curField.isTaggedKey = false } } } } func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) { ctx.root = false if code := encodeCompiledCode(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() fieldIdx := 0 var ( head *opcode code *opcode prevField *opcode ) ctx = ctx.incIndent() tags := structTags{} anonymousFields := map[string][]structFieldPair{} for i := 0; i < fieldNum; i++ { field := typ.Field(i) if isIgnoredStructField(field) { continue } tags = append(tags, structTagFromField(field)) } for i, tag := range tags { field := tag.field fieldType := type2rtype(field.Type) if isPtr && i == 0 { // head field of pointer structure at top level // if field type is pointer and implements MarshalJSON or MarshalText, // it need to operation of dereference of pointer. if field.Type.Kind() == reflect.Ptr && (field.Type.Implements(marshalJSONType) || field.Type.Implements(marshalTextType)) { fieldType = rtype_ptrTo(fieldType) } } fieldOpcodeIndex := ctx.opcodeIndex fieldPtrIndex := ctx.ptrIndex ctx.incIndex() valueCode, err := encodeCompile(ctx.withType(fieldType)) if err != nil { return nil, err } if field.Anonymous { if valueCode.op == opPtr && valueCode.next.op == opStructFieldRecursive { valueCode = valueCode.next valueCode.decOpcodeIndex() ctx.decIndex() valueCode.op = opStructFieldPtrHeadRecursive } tagKey := "" if tag.isTaggedKey { tagKey = tag.key } for k, v := range encodeAnonymousStructFieldPairMap(typ, tags, tagKey, valueCode) { anonymousFields[k] = append(anonymousFields[k], v...) } } key := fmt.Sprintf(`"%s":`, tag.key) escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key))) fieldCode := &opcode{ typ: valueCode.typ, 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, } if fieldIdx == 0 { fieldCode.headIdx = fieldCode.idx code = encodeStructHeader(ctx, fieldCode, valueCode, tag) head = fieldCode prevField = fieldCode } else { fieldCode.headIdx = head.headIdx code.next = fieldCode code = encodeStructField(ctx, fieldCode, valueCode, tag) prevField.nextField = fieldCode fieldCode.prevField = prevField prevField = fieldCode } fieldIdx++ } ctx = ctx.decIndent() structEndCode := &opcode{ op: opStructEnd, typ: nil, indent: ctx.indent, next: newEndOp(ctx), } // no struct field if head == nil { head = &opcode{ op: opStructFieldHead, typ: 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 encodeOptimizeConflictAnonymousFields(anonymousFields) encodeOptimizeAnonymousFields(head) ret := (*opcode)(unsafe.Pointer(head)) compiled.code = ret delete(ctx.structTypeToCompiledCode, typeptr) return ret, nil }