go-json/encode_compile.go

1405 lines
35 KiB
Go

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
existsCachedOpcodeSets bool
)
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)
existsCachedOpcodeSets = true
baseTypeAddr = min
return nil
}
func init() {
if err := setupOpcodeSets(); err != nil {
// fallback to slow path
}
}
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
}