diff --git a/internal/go-json/cmd/generator/vm.go.tmpl b/internal/go-json/cmd/generator/vm.go.tmpl index 5d3b95713d..6c8292d01c 100644 --- a/internal/go-json/cmd/generator/vm.go.tmpl +++ b/internal/go-json/cmd/generator/vm.go.tmpl @@ -403,48 +403,41 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b break } b = appendStructHead(ctx, b) - iter := mapiterinit(code.Type, uptr) - ctx.KeepRefs = append(ctx.KeepRefs, iter) - store(ctxptr, code.ElemIdx, 0) - store(ctxptr, code.Length, uintptr(mlen)) - store(ctxptr, code.MapIter, uintptr(iter)) + mapCtx := encoder.NewMapContext(mlen) + mapiterinit(code.Type, uptr, &mapCtx.Iter) + store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) + ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendMapKeyIndent(ctx, code.Next, b) } else { - mapCtx := encoder.NewMapContext(mlen) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) - store(ctxptr, code.End.MapPos, uintptr(unsafe.Pointer(mapCtx))) + mapCtx.Start = len(b) + mapCtx.First = len(b) } - key := mapiterkey(iter) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: - idx := load(ctxptr, code.ElemIdx) - length := load(ctxptr, code.Length) + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) + idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { - if idx < length { + if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) - store(ctxptr, code.ElemIdx, idx) - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - key := mapiterkey(iter) + mapCtx.Idx = int(idx) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) + encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - if idx < length { - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - store(ctxptr, code.ElemIdx, idx) - key := mapiterkey(iter) + mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] + if idx < mapCtx.Len { + mapCtx.Idx = int(idx) + mapCtx.Start = len(b) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { @@ -452,46 +445,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } case encoder.OpMapValue: + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) + mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] + mapCtx.Start = len(b) } - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - value := mapitervalue(iter) + value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) - mapiternext(iter) + mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. - length := int(load(ctxptr, code.Length)) - ptr := load(ctxptr, code.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - pos := mapCtx.Pos - for i := 0; i < length; i++ { - startKey := pos[i*2] - startValue := pos[i*2+1] - var endValue int - if i+1 < length { - endValue = pos[i*2+2] - } else { - endValue = len(b) - } - mapCtx.Slice.Items = append(mapCtx.Slice.Items, encoder.MapItem{ - Key: b[startKey:startValue], - Value: b[startValue:endValue], - }) - } + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) - b = b[:pos[0]] + b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) diff --git a/internal/go-json/encoder/decode_rune.go b/internal/go-json/encoder/decode_rune.go new file mode 100644 index 0000000000..1087f0b616 --- /dev/null +++ b/internal/go-json/encoder/decode_rune.go @@ -0,0 +1,127 @@ +package encoder + +import "unicode/utf8" + +const ( + // The default lowest and highest continuation byte. + locb = 128 //0b10000000 + hicb = 191 //0b10111111 + + // These names of these constants are chosen to give nice alignment in the + // table below. The first nibble is an index into acceptRanges or F for + // special one-byte cases. The second nibble is the Rune length or the + // Status for the special one-byte case. + xx = 0xF1 // invalid: size 1 + as = 0xF0 // ASCII: size 1 + s1 = 0x02 // accept 0, size 2 + s2 = 0x13 // accept 1, size 3 + s3 = 0x03 // accept 0, size 3 + s4 = 0x23 // accept 2, size 3 + s5 = 0x34 // accept 3, size 4 + s6 = 0x04 // accept 0, size 4 + s7 = 0x44 // accept 4, size 4 +) + +// first is information about the first byte in a UTF-8 sequence. +var first = [256]uint8{ + // 1 2 3 4 5 6 7 8 9 A B C D E F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F + // 1 2 3 4 5 6 7 8 9 A B C D E F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF + xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF + s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF + s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF + s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF +} + +// acceptRange gives the range of valid values for the second byte in a UTF-8 +// sequence. +type acceptRange struct { + lo uint8 // lowest value for second byte. + hi uint8 // highest value for second byte. +} + +const ( + lineSep = byte(168) //'\u2028' + paragraphSep = byte(169) //'\u2029' +) + +type decodeRuneState int + +const ( + validUTF8State decodeRuneState = iota + runeErrorState + lineSepState + paragraphSepState +) + +func decodeRuneInString(s string) (decodeRuneState, int) { + n := len(s) + s0 := s[0] + x := first[s0] + if x >= as { + // The following code simulates an additional check for x == xx and + // handling the ASCII and invalid cases accordingly. This mask-and-or + // approach prevents an additional branch. + mask := rune(x) << 31 >> 31 // Create 0x0000 or 0xFFFF. + if rune(s[0])&^mask|utf8.RuneError&mask == utf8.RuneError { + return runeErrorState, 1 + } + return validUTF8State, 1 + } + sz := int(x & 7) + var accept acceptRange + switch x >> 4 { + case 0: + accept = acceptRange{locb, hicb} + case 1: + accept = acceptRange{0xA0, hicb} + case 2: + accept = acceptRange{locb, 0x9F} + case 3: + accept = acceptRange{0x90, hicb} + case 4: + accept = acceptRange{locb, 0x8F} + } + if n < sz { + return runeErrorState, 1 + } + s1 := s[1] + if s1 < accept.lo || accept.hi < s1 { + return runeErrorState, 1 + } + if sz <= 2 { + return validUTF8State, 2 + } + s2 := s[2] + if s2 < locb || hicb < s2 { + return runeErrorState, 1 + } + if sz <= 3 { + // separator character prefixes: [2]byte{226, 128} + if s0 == 226 && s1 == 128 { + switch s2 { + case lineSep: + return lineSepState, 3 + case paragraphSep: + return paragraphSepState, 3 + } + } + return validUTF8State, 3 + } + s3 := s[3] + if s3 < locb || hicb < s3 { + return runeErrorState, 1 + } + return validUTF8State, 4 +} diff --git a/internal/go-json/encoder/encoder.go b/internal/go-json/encoder/encoder.go index f21ec1d4d8..f4d23ebb73 100644 --- a/internal/go-json/encoder/encoder.go +++ b/internal/go-json/encoder/encoder.go @@ -222,33 +222,54 @@ func (m *Mapslice) Swap(i, j int) { m.Items[i], m.Items[j] = m.Items[j], m.Items[i] } +//nolint:structcheck,unused +type mapIter struct { + key unsafe.Pointer + elem unsafe.Pointer + t unsafe.Pointer + h unsafe.Pointer + buckets unsafe.Pointer + bptr unsafe.Pointer + overflow unsafe.Pointer + oldoverflow unsafe.Pointer + startBucket uintptr + offset uint8 + wrapped bool + B uint8 + i uint8 + bucket uintptr + checkBucket uintptr +} + type MapContext struct { - Pos []int + Start int + First int + Idx int Slice *Mapslice Buf []byte + Len int + Iter mapIter } var mapContextPool = sync.Pool{ New: func() interface{} { - return &MapContext{} + return &MapContext{ + Slice: &Mapslice{}, + } }, } func NewMapContext(mapLen int) *MapContext { ctx := mapContextPool.Get().(*MapContext) - if ctx.Slice == nil { - ctx.Slice = &Mapslice{ - Items: make([]MapItem, 0, mapLen), - } - } - if cap(ctx.Pos) < (mapLen*2 + 1) { - ctx.Pos = make([]int, 0, mapLen*2+1) - ctx.Slice.Items = make([]MapItem, 0, mapLen) + if len(ctx.Slice.Items) < mapLen { + ctx.Slice.Items = make([]MapItem, mapLen) } else { - ctx.Pos = ctx.Pos[:0] - ctx.Slice.Items = ctx.Slice.Items[:0] + ctx.Slice.Items = ctx.Slice.Items[:mapLen] } ctx.Buf = ctx.Buf[:0] + ctx.Iter = mapIter{} + ctx.Idx = 0 + ctx.Len = mapLen return ctx } @@ -256,17 +277,17 @@ func ReleaseMapContext(c *MapContext) { mapContextPool.Put(c) } -//go:linkname MapIterInit reflect.mapiterinit +//go:linkname MapIterInit runtime.mapiterinit //go:noescape -func MapIterInit(mapType *runtime.Type, m unsafe.Pointer) unsafe.Pointer +func MapIterInit(mapType *runtime.Type, m unsafe.Pointer, it *mapIter) //go:linkname MapIterKey reflect.mapiterkey //go:noescape -func MapIterKey(it unsafe.Pointer) unsafe.Pointer +func MapIterKey(it *mapIter) unsafe.Pointer //go:linkname MapIterNext reflect.mapiternext //go:noescape -func MapIterNext(it unsafe.Pointer) +func MapIterNext(it *mapIter) //go:linkname MapLen reflect.maplen //go:noescape diff --git a/internal/go-json/encoder/map112.go b/internal/go-json/encoder/map112.go index 31858d0001..e96ffadf7a 100644 --- a/internal/go-json/encoder/map112.go +++ b/internal/go-json/encoder/map112.go @@ -1,3 +1,4 @@ +//go:build !go1.13 // +build !go1.13 package encoder @@ -5,4 +6,4 @@ package encoder import "unsafe" //go:linkname MapIterValue reflect.mapitervalue -func MapIterValue(it unsafe.Pointer) unsafe.Pointer +func MapIterValue(it *mapIter) unsafe.Pointer diff --git a/internal/go-json/encoder/map113.go b/internal/go-json/encoder/map113.go index f49c27bedb..9b69dcc360 100644 --- a/internal/go-json/encoder/map113.go +++ b/internal/go-json/encoder/map113.go @@ -1,3 +1,4 @@ +//go:build go1.13 // +build go1.13 package encoder @@ -5,4 +6,4 @@ package encoder import "unsafe" //go:linkname MapIterValue reflect.mapiterelem -func MapIterValue(it unsafe.Pointer) unsafe.Pointer +func MapIterValue(it *mapIter) unsafe.Pointer diff --git a/internal/go-json/encoder/opcode.go b/internal/go-json/encoder/opcode.go index e61dff4b0f..8b8c8b0678 100644 --- a/internal/go-json/encoder/opcode.go +++ b/internal/go-json/encoder/opcode.go @@ -39,10 +39,8 @@ type Opcode struct { Type *runtime.Type // go type Jmp *CompiledCode // for recursive call - ElemIdx uint32 // offset to access array/slice/map elem - Length uint32 // offset to access slice/map length or array length - MapIter uint32 // offset to access map iterator - MapPos uint32 // offset to access position list for sorted map + ElemIdx uint32 // offset to access array/slice elem + Length uint32 // offset to access slice length or array length Indent uint32 // indent number Size uint32 // array/slice elem size DisplayIdx uint32 // opcode index @@ -91,8 +89,6 @@ func (c *Opcode) MaxIdx() uint32 { c.Idx, c.ElemIdx, c.Length, - c.MapIter, - c.MapPos, c.Size, } { if max < value { @@ -341,8 +337,6 @@ func copyOpcode(code *Opcode) *Opcode { DisplayKey: c.DisplayKey, ElemIdx: c.ElemIdx, Length: c.Length, - MapIter: c.MapIter, - MapPos: c.MapPos, Size: c.Size, Indent: c.Indent, Jmp: c.Jmp, @@ -448,26 +442,21 @@ func (c *Opcode) dumpHead(code *Opcode) string { func (c *Opcode) dumpMapHead(code *Opcode) string { return fmt.Sprintf( - `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, - code.ElemIdx/uintptrSize, - code.Length/uintptrSize, - code.MapIter/uintptrSize, ) } func (c *Opcode) dumpMapEnd(code *Opcode) string { return fmt.Sprintf( - `[%03d]%s%s ([idx:%d][mapPos:%d][length:%d])`, + `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, - code.MapPos/uintptrSize, - code.Length/uintptrSize, ) } @@ -504,25 +493,21 @@ func (c *Opcode) dumpField(code *Opcode) string { func (c *Opcode) dumpKey(code *Opcode) string { return fmt.Sprintf( - `[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, - code.ElemIdx/uintptrSize, - code.Length/uintptrSize, - code.MapIter/uintptrSize, ) } func (c *Opcode) dumpValue(code *Opcode) string { return fmt.Sprintf( - `[%03d]%s%s ([idx:%d][mapIter:%d])`, + `[%03d]%s%s ([idx:%d])`, code.DisplayIdx, strings.Repeat("-", int(code.Indent)), code.Op, code.Idx/uintptrSize, - code.MapIter/uintptrSize, ) } @@ -629,19 +614,11 @@ func newArrayElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, leng func newMapHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode { idx := opcodeOffset(ctx.ptrIndex) ctx.incPtrIndex() - elemIdx := opcodeOffset(ctx.ptrIndex) - ctx.incPtrIndex() - length := opcodeOffset(ctx.ptrIndex) - ctx.incPtrIndex() - mapIter := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMap, Type: typ, Idx: idx, DisplayIdx: ctx.opcodeIndex, - ElemIdx: elemIdx, - Length: length, - MapIter: mapIter, Indent: ctx.indent, } } @@ -650,11 +627,8 @@ func newMapKeyCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode return &Opcode{ Op: OpMapKey, Type: typ, - Idx: opcodeOffset(ctx.ptrIndex), + Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, - ElemIdx: head.ElemIdx, - Length: head.Length, - MapIter: head.MapIter, Indent: ctx.indent, } } @@ -663,28 +637,20 @@ func newMapValueCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opco return &Opcode{ Op: OpMapValue, Type: typ, - Idx: opcodeOffset(ctx.ptrIndex), + Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, - ElemIdx: head.ElemIdx, - Length: head.Length, - MapIter: head.MapIter, Indent: ctx.indent, } } func newMapEndCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode { - mapPos := opcodeOffset(ctx.ptrIndex) - ctx.incPtrIndex() - idx := opcodeOffset(ctx.ptrIndex) return &Opcode{ Op: OpMapEnd, Type: typ, - Idx: idx, - Next: newEndOp(ctx, typ), + Idx: head.Idx, DisplayIdx: ctx.opcodeIndex, - Length: head.Length, - MapPos: mapPos, Indent: ctx.indent, + Next: newEndOp(ctx, typ), } } diff --git a/internal/go-json/encoder/string.go b/internal/go-json/encoder/string.go index a699dba19c..236e2e9927 100644 --- a/internal/go-json/encoder/string.go +++ b/internal/go-json/encoder/string.go @@ -3,7 +3,6 @@ package encoder import ( "math/bits" "reflect" - "unicode/utf8" "unsafe" ) @@ -349,53 +348,6 @@ var needEscape = [256]bool{ var hex = "0123456789abcdef" -// escapeIndex finds the index of the first char in `s` that requires escaping. -// A char requires escaping if it's outside of the range of [0x20, 0x7F] or if -// it includes a double quote or backslash. -// If no chars in `s` require escaping, the return value is -1. -func escapeIndex(s string) int { - chunks := stringToUint64Slice(s) - for _, n := range chunks { - // combine masks before checking for the MSB of each byte. We include - // `n` in the mask to check whether any of the *input* byte MSBs were - // set (i.e. the byte was outside the ASCII range). - mask := n | below(n, 0x20) | contains(n, '"') | contains(n, '\\') - if (mask & msb) != 0 { - return bits.TrailingZeros64(mask&msb) / 8 - } - } - - valLen := len(s) - for i := len(chunks) * 8; i < valLen; i++ { - if needEscape[s[i]] { - return i - } - } - - return -1 -} - -// below return a mask that can be used to determine if any of the bytes -// in `n` are below `b`. If a byte's MSB is set in the mask then that byte was -// below `b`. The result is only valid if `b`, and each byte in `n`, is below -// 0x80. -func below(n uint64, b byte) uint64 { - return n - expand(b) -} - -// contains returns a mask that can be used to determine if any of the -// bytes in `n` are equal to `b`. If a byte's MSB is set in the mask then -// that byte is equal to `b`. The result is only valid if `b`, and each -// byte in `n`, is below 0x80. -func contains(n uint64, b byte) uint64 { - return (n ^ expand(b)) - lsb -} - -// expand puts the specified byte into each of the 8 bytes of a uint64. -func expand(b byte) uint64 { - return lsb * uint64(b) -} - //nolint:govet func stringToUint64Slice(s string) []uint64 { return *(*[]uint64)(unsafe.Pointer(&reflect.SliceHeader{ @@ -489,10 +441,9 @@ ESCAPE_END: i = j + 1 j = j + 1 continue - } - // This encodes bytes < 0x20 except for \t, \n and \r. - if c < 0x20 { + case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) @@ -501,18 +452,14 @@ ESCAPE_END: continue } - r, size := utf8.DecodeRuneInString(s[j:]) - - if r == utf8.RuneError && size == 1 { + state, size := decodeRuneInString(s[j:]) + switch state { + case runeErrorState: buf = append(buf, s[i:j]...) buf = append(buf, `\ufffd`...) - i = j + size - j = j + size + i = j + 1 + j = j + 1 continue - } - - switch r { - case '\u2028', '\u2029': // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, @@ -520,14 +467,19 @@ ESCAPE_END: // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. + case lineSepState: buf = append(buf, s[i:j]...) - buf = append(buf, `\u202`...) - buf = append(buf, hex[r&0xF]) - i = j + size - j = j + size + buf = append(buf, `\u2028`...) + i = j + 3 + j = j + 3 + continue + case paragraphSepState: + buf = append(buf, s[i:j]...) + buf = append(buf, `\u2029`...) + i = j + 3 + j = j + 3 continue } - j += size } @@ -540,19 +492,37 @@ func appendString(buf []byte, s string) []byte { return append(buf, `""`...) } buf = append(buf, '"') - var escapeIdx int + var ( + i, j int + ) if valLen >= 8 { - if escapeIdx = escapeIndex(s); escapeIdx < 0 { - return append(append(buf, s...), '"') + chunks := stringToUint64Slice(s) + for _, n := range chunks { + // combine masks before checking for the MSB of each byte. We include + // `n` in the mask to check whether any of the *input* byte MSBs were + // set (i.e. the byte was outside the ASCII range). + mask := n | (n - (lsb * 0x20)) | + ((n ^ (lsb * '"')) - lsb) | + ((n ^ (lsb * '\\')) - lsb) + if (mask & msb) != 0 { + j = bits.TrailingZeros64(mask&msb) / 8 + goto ESCAPE_END + } } + valLen := len(s) + for i := len(chunks) * 8; i < valLen; i++ { + if needEscape[s[i]] { + j = i + goto ESCAPE_END + } + } + return append(append(buf, s...), '"') } - - i := 0 - j := escapeIdx +ESCAPE_END: for j < valLen { c := s[j] - if c >= 0x20 && c <= 0x7f && c != '\\' && c != '"' { + if !needEscape[c] { // fast path: most of the time, printable ascii characters are used j++ continue @@ -594,10 +564,9 @@ func appendString(buf []byte, s string) []byte { i = j + 1 j = j + 1 continue - } - // This encodes bytes < 0x20 except for \t, \n and \r. - if c < 0x20 { + case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F buf = append(buf, s[i:j]...) buf = append(buf, `\u00`...) buf = append(buf, hex[c>>4], hex[c&0xF]) @@ -606,18 +575,14 @@ func appendString(buf []byte, s string) []byte { continue } - r, size := utf8.DecodeRuneInString(s[j:]) - - if r == utf8.RuneError && size == 1 { + state, size := decodeRuneInString(s[j:]) + switch state { + case runeErrorState: buf = append(buf, s[i:j]...) buf = append(buf, `\ufffd`...) - i = j + size - j = j + size + i = j + 1 + j = j + 1 continue - } - - switch r { - case '\u2028', '\u2029': // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, @@ -625,14 +590,19 @@ func appendString(buf []byte, s string) []byte { // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. + case lineSepState: buf = append(buf, s[i:j]...) - buf = append(buf, `\u202`...) - buf = append(buf, hex[r&0xF]) - i = j + size - j = j + size + buf = append(buf, `\u2028`...) + i = j + 3 + j = j + 3 + continue + case paragraphSepState: + buf = append(buf, s[i:j]...) + buf = append(buf, `\u2029`...) + i = j + 3 + j = j + 3 continue } - j += size } diff --git a/internal/go-json/encoder/vm/vm.go b/internal/go-json/encoder/vm/vm.go index 5d3b95713d..6c8292d01c 100644 --- a/internal/go-json/encoder/vm/vm.go +++ b/internal/go-json/encoder/vm/vm.go @@ -403,48 +403,41 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b break } b = appendStructHead(ctx, b) - iter := mapiterinit(code.Type, uptr) - ctx.KeepRefs = append(ctx.KeepRefs, iter) - store(ctxptr, code.ElemIdx, 0) - store(ctxptr, code.Length, uintptr(mlen)) - store(ctxptr, code.MapIter, uintptr(iter)) + mapCtx := encoder.NewMapContext(mlen) + mapiterinit(code.Type, uptr, &mapCtx.Iter) + store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) + ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendMapKeyIndent(ctx, code.Next, b) } else { - mapCtx := encoder.NewMapContext(mlen) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) - store(ctxptr, code.End.MapPos, uintptr(unsafe.Pointer(mapCtx))) + mapCtx.Start = len(b) + mapCtx.First = len(b) } - key := mapiterkey(iter) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: - idx := load(ctxptr, code.ElemIdx) - length := load(ctxptr, code.Length) + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) + idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { - if idx < length { + if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) - store(ctxptr, code.ElemIdx, idx) - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - key := mapiterkey(iter) + mapCtx.Idx = int(idx) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) + encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - if idx < length { - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - store(ctxptr, code.ElemIdx, idx) - key := mapiterkey(iter) + mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] + if idx < mapCtx.Len { + mapCtx.Idx = int(idx) + mapCtx.Start = len(b) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { @@ -452,46 +445,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } case encoder.OpMapValue: + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) + mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] + mapCtx.Start = len(b) } - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - value := mapitervalue(iter) + value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) - mapiternext(iter) + mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. - length := int(load(ctxptr, code.Length)) - ptr := load(ctxptr, code.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - pos := mapCtx.Pos - for i := 0; i < length; i++ { - startKey := pos[i*2] - startValue := pos[i*2+1] - var endValue int - if i+1 < length { - endValue = pos[i*2+2] - } else { - endValue = len(b) - } - mapCtx.Slice.Items = append(mapCtx.Slice.Items, encoder.MapItem{ - Key: b[startKey:startValue], - Value: b[startValue:endValue], - }) - } + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) - b = b[:pos[0]] + b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) diff --git a/internal/go-json/encoder/vm_color/vm.go b/internal/go-json/encoder/vm_color/vm.go index 45a7c13e07..41b326e601 100644 --- a/internal/go-json/encoder/vm_color/vm.go +++ b/internal/go-json/encoder/vm_color/vm.go @@ -403,48 +403,41 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b break } b = appendStructHead(ctx, b) - iter := mapiterinit(code.Type, uptr) - ctx.KeepRefs = append(ctx.KeepRefs, iter) - store(ctxptr, code.ElemIdx, 0) - store(ctxptr, code.Length, uintptr(mlen)) - store(ctxptr, code.MapIter, uintptr(iter)) + mapCtx := encoder.NewMapContext(mlen) + mapiterinit(code.Type, uptr, &mapCtx.Iter) + store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) + ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendMapKeyIndent(ctx, code.Next, b) } else { - mapCtx := encoder.NewMapContext(mlen) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) - store(ctxptr, code.End.MapPos, uintptr(unsafe.Pointer(mapCtx))) + mapCtx.Start = len(b) + mapCtx.First = len(b) } - key := mapiterkey(iter) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: - idx := load(ctxptr, code.ElemIdx) - length := load(ctxptr, code.Length) + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) + idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { - if idx < length { + if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) - store(ctxptr, code.ElemIdx, idx) - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - key := mapiterkey(iter) + mapCtx.Idx = int(idx) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) + encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - if idx < length { - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - store(ctxptr, code.ElemIdx, idx) - key := mapiterkey(iter) + mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] + if idx < mapCtx.Len { + mapCtx.Idx = int(idx) + mapCtx.Start = len(b) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { @@ -452,46 +445,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } case encoder.OpMapValue: + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) + mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] + mapCtx.Start = len(b) } - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - value := mapitervalue(iter) + value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) - mapiternext(iter) + mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. - length := int(load(ctxptr, code.Length)) - ptr := load(ctxptr, code.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - pos := mapCtx.Pos - for i := 0; i < length; i++ { - startKey := pos[i*2] - startValue := pos[i*2+1] - var endValue int - if i+1 < length { - endValue = pos[i*2+2] - } else { - endValue = len(b) - } - mapCtx.Slice.Items = append(mapCtx.Slice.Items, encoder.MapItem{ - Key: b[startKey:startValue], - Value: b[startValue:endValue], - }) - } + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) - b = b[:pos[0]] + b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) diff --git a/internal/go-json/encoder/vm_color_indent/vm.go b/internal/go-json/encoder/vm_color_indent/vm.go index 5270e11988..f3f6d0b9c1 100644 --- a/internal/go-json/encoder/vm_color_indent/vm.go +++ b/internal/go-json/encoder/vm_color_indent/vm.go @@ -403,48 +403,41 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b break } b = appendStructHead(ctx, b) - iter := mapiterinit(code.Type, uptr) - ctx.KeepRefs = append(ctx.KeepRefs, iter) - store(ctxptr, code.ElemIdx, 0) - store(ctxptr, code.Length, uintptr(mlen)) - store(ctxptr, code.MapIter, uintptr(iter)) + mapCtx := encoder.NewMapContext(mlen) + mapiterinit(code.Type, uptr, &mapCtx.Iter) + store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) + ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendMapKeyIndent(ctx, code.Next, b) } else { - mapCtx := encoder.NewMapContext(mlen) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) - store(ctxptr, code.End.MapPos, uintptr(unsafe.Pointer(mapCtx))) + mapCtx.Start = len(b) + mapCtx.First = len(b) } - key := mapiterkey(iter) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: - idx := load(ctxptr, code.ElemIdx) - length := load(ctxptr, code.Length) + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) + idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { - if idx < length { + if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) - store(ctxptr, code.ElemIdx, idx) - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - key := mapiterkey(iter) + mapCtx.Idx = int(idx) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) + encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - if idx < length { - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - store(ctxptr, code.ElemIdx, idx) - key := mapiterkey(iter) + mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] + if idx < mapCtx.Len { + mapCtx.Idx = int(idx) + mapCtx.Start = len(b) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { @@ -452,46 +445,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } case encoder.OpMapValue: + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) + mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] + mapCtx.Start = len(b) } - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - value := mapitervalue(iter) + value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) - mapiternext(iter) + mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. - length := int(load(ctxptr, code.Length)) - ptr := load(ctxptr, code.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - pos := mapCtx.Pos - for i := 0; i < length; i++ { - startKey := pos[i*2] - startValue := pos[i*2+1] - var endValue int - if i+1 < length { - endValue = pos[i*2+2] - } else { - endValue = len(b) - } - mapCtx.Slice.Items = append(mapCtx.Slice.Items, encoder.MapItem{ - Key: b[startKey:startValue], - Value: b[startValue:endValue], - }) - } + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) - b = b[:pos[0]] + b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx) diff --git a/internal/go-json/encoder/vm_indent/vm.go b/internal/go-json/encoder/vm_indent/vm.go index a73416034e..88d925c814 100644 --- a/internal/go-json/encoder/vm_indent/vm.go +++ b/internal/go-json/encoder/vm_indent/vm.go @@ -403,48 +403,41 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b break } b = appendStructHead(ctx, b) - iter := mapiterinit(code.Type, uptr) - ctx.KeepRefs = append(ctx.KeepRefs, iter) - store(ctxptr, code.ElemIdx, 0) - store(ctxptr, code.Length, uintptr(mlen)) - store(ctxptr, code.MapIter, uintptr(iter)) + mapCtx := encoder.NewMapContext(mlen) + mapiterinit(code.Type, uptr, &mapCtx.Iter) + store(ctxptr, code.Idx, uintptr(unsafe.Pointer(mapCtx))) + ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendMapKeyIndent(ctx, code.Next, b) } else { - mapCtx := encoder.NewMapContext(mlen) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - ctx.KeepRefs = append(ctx.KeepRefs, unsafe.Pointer(mapCtx)) - store(ctxptr, code.End.MapPos, uintptr(unsafe.Pointer(mapCtx))) + mapCtx.Start = len(b) + mapCtx.First = len(b) } - key := mapiterkey(iter) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next case encoder.OpMapKey: - idx := load(ctxptr, code.ElemIdx) - length := load(ctxptr, code.Length) + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) + idx := mapCtx.Idx idx++ if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { - if idx < length { + if idx < mapCtx.Len { b = appendMapKeyIndent(ctx, code, b) - store(ctxptr, code.ElemIdx, idx) - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - key := mapiterkey(iter) + mapCtx.Idx = int(idx) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { b = appendObjectEnd(ctx, code, b) + encoder.ReleaseMapContext(mapCtx) code = code.End.Next } } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) - if idx < length { - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - store(ctxptr, code.ElemIdx, idx) - key := mapiterkey(iter) + mapCtx.Slice.Items[mapCtx.Idx].Value = b[mapCtx.Start:len(b)] + if idx < mapCtx.Len { + mapCtx.Idx = int(idx) + mapCtx.Start = len(b) + key := mapiterkey(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(key)) code = code.Next } else { @@ -452,46 +445,27 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b } } case encoder.OpMapValue: + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) if (ctx.Option.Flag & encoder.UnorderedMapOption) != 0 { b = appendColon(ctx, b) } else { - ptr := load(ctxptr, code.End.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - mapCtx.Pos = append(mapCtx.Pos, len(b)) + mapCtx.Slice.Items[mapCtx.Idx].Key = b[mapCtx.Start:len(b)] + mapCtx.Start = len(b) } - ptr := load(ctxptr, code.MapIter) - iter := ptrToUnsafePtr(ptr) - value := mapitervalue(iter) + value := mapitervalue(&mapCtx.Iter) store(ctxptr, code.Next.Idx, uintptr(value)) - mapiternext(iter) + mapiternext(&mapCtx.Iter) code = code.Next case encoder.OpMapEnd: // this operation only used by sorted map. - length := int(load(ctxptr, code.Length)) - ptr := load(ctxptr, code.MapPos) - mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(ptr)) - pos := mapCtx.Pos - for i := 0; i < length; i++ { - startKey := pos[i*2] - startValue := pos[i*2+1] - var endValue int - if i+1 < length { - endValue = pos[i*2+2] - } else { - endValue = len(b) - } - mapCtx.Slice.Items = append(mapCtx.Slice.Items, encoder.MapItem{ - Key: b[startKey:startValue], - Value: b[startValue:endValue], - }) - } + mapCtx := (*encoder.MapContext)(ptrToUnsafePtr(load(ctxptr, code.Idx))) sort.Sort(mapCtx.Slice) buf := mapCtx.Buf for _, item := range mapCtx.Slice.Items { buf = appendMapKeyValue(ctx, code, buf, item.Key, item.Value) } buf = appendMapEnd(ctx, code, buf) - b = b[:pos[0]] + b = b[:mapCtx.First] b = append(b, buf...) mapCtx.Buf = buf encoder.ReleaseMapContext(mapCtx)