summaryrefslogtreecommitdiff
path: root/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ugorji/go/codec/fast-path.go.tmpl')
-rw-r--r--vendor/github.com/ugorji/go/codec/fast-path.go.tmpl555
1 files changed, 555 insertions, 0 deletions
diff --git a/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl b/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl
new file mode 100644
index 000000000..56801ee5c
--- /dev/null
+++ b/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl
@@ -0,0 +1,555 @@
+// +build !notfastpath
+// +build !codec.notfastpath
+
+// Copyright (c) 2012-2020 Ugorji Nwoke. All rights reserved.
+// Use of this source code is governed by a MIT license found in the LICENSE file.
+
+// Code generated from fast-path.go.tmpl - DO NOT EDIT.
+
+package codec
+
+// Fast path functions try to create a fast path encode or decode implementation
+// for common maps and slices.
+//
+// We define the functions and register them in this single file
+// so as not to pollute the encode.go and decode.go, and create a dependency in there.
+// This file can be omitted without causing a build failure.
+//
+// The advantage of fast paths is:
+// - Many calls bypass reflection altogether
+//
+// Currently support
+// - slice of all builtin types (numeric, bool, string, []byte)
+// - maps of builtin types to builtin or interface{} type, EXCEPT FOR
+// keys of type uintptr, int8/16/32, uint16/32, float32/64, bool, interface{}
+// AND values of type type int8/16/32, uint16/32
+// This should provide adequate "typical" implementations.
+//
+// Note that fast track decode functions must handle values for which an address cannot be obtained.
+// For example:
+// m2 := map[string]int{}
+// p2 := []interface{}{m2}
+// // decoding into p2 will bomb if fast track functions do not treat like unaddressable.
+//
+
+{{/*
+fastpathEncMapStringUint64R (called by fastpath...switch)
+EncMapStringUint64V (called by codecgen)
+
+fastpathEncSliceBoolR: (called by fastpath...switch) (checks f.ti.mbs and calls one of them below)
+EncSliceBoolV (also called by codecgen)
+EncAsMapSliceBoolV (delegate when mapbyslice=true)
+
+fastpathDecSliceIntfR (called by fastpath...switch) (calls Y or N below depending on if it can be updated)
+DecSliceIntfX (called by codecgen) (calls Y below)
+DecSliceIntfY (delegate when slice CAN be updated)
+DecSliceIntfN (delegate when slice CANNOT be updated e.g. from array or non-addressable slice)
+
+fastpathDecMap...R (called by fastpath...switch) (calls L or X? below)
+DecMap...X (called by codecgen)
+DecMap...L (delegated to by both above)
+*/ -}}
+
+import (
+ "reflect"
+ "sort"
+)
+
+const fastpathEnabled = true
+
+{{/*
+const fastpathMapBySliceErrMsg = "mapBySlice requires even slice length, but got %v"
+*/ -}}
+
+type fastpathT struct {}
+
+var fastpathTV fastpathT
+
+type fastpathE struct {
+ {{/* rtid uintptr */ -}}
+ rt reflect.Type
+ encfn func(*Encoder, *codecFnInfo, reflect.Value)
+ decfn func(*Decoder, *codecFnInfo, reflect.Value)
+}
+
+type fastpathA [{{ .FastpathLen }}]fastpathE
+type fastpathARtid [{{ .FastpathLen }}]uintptr
+
+var fastpathAv fastpathA
+var fastpathAvRtid fastpathARtid
+
+type fastpathAslice struct{}
+
+func (fastpathAslice) Len() int { return {{ .FastpathLen }} }
+func (fastpathAslice) Less(i, j int) bool {
+ return fastpathAvRtid[uint(i)] < fastpathAvRtid[uint(j)]
+}
+func (fastpathAslice) Swap(i, j int) {
+ fastpathAvRtid[uint(i)], fastpathAvRtid[uint(j)] = fastpathAvRtid[uint(j)], fastpathAvRtid[uint(i)]
+ fastpathAv[uint(i)], fastpathAv[uint(j)] = fastpathAv[uint(j)], fastpathAv[uint(i)]
+}
+
+func fastpathAvIndex(rtid uintptr) int {
+ // use binary search to grab the index (adapted from sort/search.go)
+ // Note: we use goto (instead of for loop) so this can be inlined.
+ // h, i, j := 0, 0, {{ .FastpathLen }}
+ var h, i uint
+ var j uint = {{ .FastpathLen }}
+LOOP:
+ if i < j {
+ h = (i + j) >> 1 // avoid overflow when computing h // h = i + (j-i)/2
+ if fastpathAvRtid[h] < rtid {
+ i = h + 1
+ } else {
+ j = h
+ }
+ goto LOOP
+ }
+ if i < {{ .FastpathLen }} && fastpathAvRtid[i] == rtid {
+ return int(i)
+ }
+ return -1
+}
+
+
+// due to possible initialization loop error, make fastpath in an init()
+func init() {
+ var i uint = 0
+ fn := func(v interface{},
+ fe func(*Encoder, *codecFnInfo, reflect.Value),
+ fd func(*Decoder, *codecFnInfo, reflect.Value)) {
+ xrt := reflect.TypeOf(v)
+ xptr := rt2id(xrt)
+ fastpathAvRtid[i] = xptr
+ fastpathAv[i] = fastpathE{xrt, fe, fd}
+ i++
+ }
+ {{/* do not register []byte in fast-path */}}
+ {{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+ fn([]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R)
+ {{end}}{{end}}{{end}}
+
+ {{range .Values}}{{if not .Primitive}}{{if .MapKey -}}
+ fn(map[{{ .MapKey }}]{{ .Elem }}(nil), (*Encoder).{{ .MethodNamePfx "fastpathEnc" false }}R, (*Decoder).{{ .MethodNamePfx "fastpathDec" false }}R)
+ {{end}}{{end}}{{end}}
+
+ sort.Sort(fastpathAslice{})
+}
+
+// -- encode
+
+// -- -- fast path type switch
+func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool {
+ switch v := iv.(type) {
+{{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+ case []{{ .Elem }}:
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
+ case *[]{{ .Elem }}:
+ if *v == nil {
+ e.e.EncodeNil()
+ } else {
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
+ }
+{{end}}{{end}}{{end -}}
+
+{{range .Values}}{{if not .Primitive}}{{if .MapKey -}}
+ case map[{{ .MapKey }}]{{ .Elem }}:
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
+ case *map[{{ .MapKey }}]{{ .Elem }}:
+ if *v == nil {
+ e.e.EncodeNil()
+ } else {
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(*v, e)
+ }
+{{end}}{{end}}{{end -}}
+
+ default:
+ _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
+ return false
+ }
+ return true
+}
+
+// -- -- fast path functions
+{{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
+ var v []{{ .Elem }}
+ if rv.Kind() == reflect.Array {
+ rvGetSlice4Array(rv, &v)
+ } else {
+ v = rv2i(rv).([]{{ .Elem }})
+ }
+ if f.ti.mbs {
+ fastpathTV.{{ .MethodNamePfx "EncAsMap" false }}V(v, e)
+ } else {
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(v, e)
+ }
+}
+func (fastpathT) {{ .MethodNamePfx "Enc" false }}V(v []{{ .Elem }}, e *Encoder) {
+ {{/* if v == nil { e.e.EncodeNil(); return } */ -}}
+ {{ if eq .Elem "uint8" "byte" -}}
+ e.e.EncodeStringBytesRaw(v)
+ {{ else -}}
+ e.arrayStart(len(v))
+ for j := range v {
+ e.arrayElem()
+ {{ encmd .Elem "v[j]"}}
+ }
+ e.arrayEnd()
+ {{ end -}}
+}
+func (fastpathT) {{ .MethodNamePfx "EncAsMap" false }}V(v []{{ .Elem }}, e *Encoder) {
+ {{/* if v == nil { e.e.EncodeNil() } else */ -}}
+ e.haltOnMbsOddLen(len(v))
+ {{/*
+ if len(v)&1 != 0 { // similar to &1==1 or %2 == 1
+ e.errorf(fastpathMapBySliceErrMsg, len(v))
+ }
+ */ -}}
+ e.mapStart(len(v) >> 1) // e.mapStart(len(v) / 2)
+ for j := range v {
+ if j&1 == 0 { // if j%2 == 0 {
+ e.mapElemKey()
+ } else {
+ e.mapElemValue()
+ }
+ {{ encmd .Elem "v[j]"}}
+ }
+ e.mapEnd()
+}
+{{end}}{{end}}{{end -}}
+
+{{range .Values}}{{if not .Primitive}}{{if .MapKey -}}
+func (e *Encoder) {{ .MethodNamePfx "fastpathEnc" false }}R(f *codecFnInfo, rv reflect.Value) {
+ fastpathTV.{{ .MethodNamePfx "Enc" false }}V(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), e)
+}
+func (fastpathT) {{ .MethodNamePfx "Enc" false }}V(v map[{{ .MapKey }}]{{ .Elem }}, e *Encoder) {
+ {{/* if v == nil { e.e.EncodeNil(); return } */ -}}
+ e.mapStart(len(v))
+ if e.h.Canonical { {{/* need to figure out .NoCanonical */}}
+ {{if eq .MapKey "interface{}"}}{{/* out of band */ -}}
+ var mksv []byte = make([]byte, 0, len(v)*16) // temporary byte slice for the encoding
+ e2 := NewEncoderBytes(&mksv, e.hh)
+ v2 := make([]bytesIntf, len(v))
+ var i, l uint {{/* put loop variables outside. seems currently needed for better perf */}}
+ var vp *bytesIntf
+ for k2 := range v {
+ l = uint(len(mksv))
+ e2.MustEncode(k2)
+ vp = &v2[i]
+ vp.v = mksv[l:]
+ vp.i = k2
+ i++
+ }
+ sort.Sort(bytesIntfSlice(v2))
+ for j := range v2 {
+ e.mapElemKey()
+ e.asis(v2[j].v)
+ e.mapElemValue()
+ e.encode(v[v2[j].i])
+ } {{else}}{{ $x := sorttype .MapKey true}}v2 := make([]{{ $x }}, len(v))
+ var i uint
+ for k := range v {
+ v2[i] = {{if eq $x .MapKey}}k{{else}}{{ $x }}(k){{end}}
+ i++
+ }
+ sort.Sort({{ sorttype .MapKey false}}(v2))
+ for _, k2 := range v2 {
+ e.mapElemKey()
+ {{if eq .MapKey "string"}} e.e.EncodeString(k2) {{else}}{{ $y := printf "%s(k2)" .MapKey }}{{if eq $x .MapKey }}{{ $y = "k2" }}{{end}}{{ encmd .MapKey $y }}{{end}}
+ e.mapElemValue()
+ {{ $y := printf "v[%s(k2)]" .MapKey }}{{if eq $x .MapKey }}{{ $y = "v[k2]" }}{{end}}{{ encmd .Elem $y }}
+ } {{end}}
+ } else {
+ for k2, v2 := range v {
+ e.mapElemKey()
+ {{if eq .MapKey "string"}} e.e.EncodeString(k2) {{else}}{{ encmd .MapKey "k2"}}{{end}}
+ e.mapElemValue()
+ {{ encmd .Elem "v2"}}
+ }
+ }
+ e.mapEnd()
+}
+{{end}}{{end}}{{end -}}
+
+// -- decode
+
+// -- -- fast path type switch
+func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool {
+ var changed bool
+ var containerLen int
+ switch v := iv.(type) {
+{{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+ case []{{ .Elem }}:
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}N(v, d)
+ case *[]{{ .Elem }}:
+ var v2 []{{ .Elem }}
+ if v2, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}Y(*v, d); changed {
+ *v = v2
+ }
+{{end}}{{end}}{{end -}}
+{{range .Values}}{{if not .Primitive}}{{if .MapKey }}{{/*
+// maps only change if nil, and in that case, there's no point copying
+*/ -}}
+ case map[{{ .MapKey }}]{{ .Elem }}:
+ containerLen = d.mapStart(d.d.ReadMapStart())
+ if containerLen != containerLenNil {
+ if containerLen != 0 {
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}L(v, containerLen, d)
+ }
+ d.mapEnd()
+ }
+ case *map[{{ .MapKey }}]{{ .Elem }}:
+ {{/*
+ containerLen = d.mapStart(d.d.ReadMapStart())
+ if containerLen == 0 {
+ d.mapEnd()
+ } else if containerLen == containerLenNil {
+ *v = nil
+ } else {
+ if *v == nil {
+ *v = make(map[{{ .MapKey }}]{{ .Elem }}, decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }}))
+ }
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}L(*v, containerLen, d)
+ }
+ // consider delegating fully to X - encoding *map is uncommon, so ok to pay small function call cost
+ */ -}}
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}X(v, d)
+{{end}}{{end}}{{end -}}
+ default:
+ _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
+ return false
+ }
+ return true
+}
+
+func fastpathDecodeSetZeroTypeSwitch(iv interface{}) bool {
+ switch v := iv.(type) {
+{{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+ case *[]{{ .Elem }}:
+ *v = nil
+{{end}}{{end}}{{end}}
+{{range .Values}}{{if not .Primitive}}{{if .MapKey -}}
+ case *map[{{ .MapKey }}]{{ .Elem }}:
+ *v = nil
+{{end}}{{end}}{{end}}
+ default:
+ _ = v // workaround https://github.com/golang/go/issues/12927 seen in go1.4
+ return false
+ }
+ return true
+}
+
+// -- -- fast path functions
+{{range .Values}}{{if not .Primitive}}{{if not .MapKey -}}
+{{/*
+Slices can change if they
+- did not come from an array
+- are addressable (from a ptr)
+- are settable (e.g. contained in an interface{})
+*/}}
+func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) {
+ {{/*
+ // seqTypeArray=true means that we are not getting a pointer, so no need to check that.
+ if f.seq != seqTypeArray && rv.Kind() == reflect.Ptr {
+ */ -}}
+ var v []{{ .Elem }}
+ switch rv.Kind() {
+ case reflect.Ptr:
+ vp := rv2i(rv).(*[]{{ .Elem }})
+ var changed bool
+ if v, changed = fastpathTV.{{ .MethodNamePfx "Dec" false }}Y(*vp, d); changed {
+ *vp = v
+ }
+ case reflect.Array:
+ rvGetSlice4Array(rv, &v)
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}N(v, d)
+ default:
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}N(rv2i(rv).([]{{ .Elem }}), d)
+ }
+}
+func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *[]{{ .Elem }}, d *Decoder) {
+ if v, changed := f.{{ .MethodNamePfx "Dec" false }}Y(*vp, d); changed { *vp = v }
+}
+func (fastpathT) {{ .MethodNamePfx "Dec" false }}Y(v []{{ .Elem }}, d *Decoder) (v2 []{{ .Elem }}, changed bool) {
+ {{ if eq .Elem "uint8" "byte" -}}
+ switch d.d.ContainerType() {
+ case valueTypeNil, valueTypeMap:
+ break
+ default:
+ v2 = d.decodeBytesInto(v[:len(v):len(v)])
+ changed = !(len(v2) > 0 && len(v2) == len(v) && &v2[0] == &v[0]) // not same slice
+ return
+ }
+ {{ end -}}
+ slh, containerLenS := d.decSliceHelperStart()
+ if slh.IsNil {
+ if v == nil { return }
+ return nil, true
+ }
+ if containerLenS == 0 {
+ if v == nil { v = []{{ .Elem }}{} } else if len(v) != 0 { v = v[:0] }
+ slh.End()
+ return v, true
+ }
+ hasLen := containerLenS > 0
+ var xlen int
+ if hasLen {
+ if containerLenS > cap(v) {
+ xlen = decInferLen(containerLenS, d.h.MaxInitLen, {{ .Size }})
+ if xlen <= cap(v) {
+ v = v[:uint(xlen)]
+ } else {
+ v = make([]{{ .Elem }}, uint(xlen))
+ }
+ changed = true
+ } else if containerLenS != len(v) {
+ v = v[:containerLenS]
+ changed = true
+ }
+ }
+ var j int
+ for j = 0; (hasLen && j < containerLenS) || !(hasLen || d.checkBreak()); j++ {
+ if j == 0 && len(v) == 0 { // means hasLen == false
+ xlen = decInferLen(containerLenS, d.h.MaxInitLen, {{ .Size }}) {{/* xlen = decDefSliceCap */}}
+ v = make([]{{ .Elem }}, uint(xlen))
+ changed = true
+ }
+ {{/* // if indefinite, etc, then expand the slice if necessary */ -}}
+ if j >= len(v) {
+ v = append(v, {{ zerocmd .Elem }})
+ changed = true
+ }
+ slh.ElemContainerState(j)
+ {{ if eq .Elem "interface{}" }}d.decode(&v[uint(j)]){{ else }}v[uint(j)] = {{ decmd .Elem false }}{{ end }}
+ }
+ if j < len(v) {
+ v = v[:uint(j)]
+ changed = true
+ } else if j == 0 && v == nil {
+ v = []{{ .Elem }}{}
+ changed = true
+ }
+ slh.End()
+ return v, changed
+}
+func (fastpathT) {{ .MethodNamePfx "Dec" false }}N(v []{{ .Elem }}, d *Decoder) {
+ {{ if eq .Elem "uint8" "byte" -}}
+ switch d.d.ContainerType() {
+ case valueTypeNil, valueTypeMap:
+ break
+ default:
+ v2 := d.decodeBytesInto(v[:len(v):len(v)])
+ if !(len(v2) > 0 && len(v2) == len(v) && &v2[0] == &v[0]) { // not same slice
+ copy(v, v2)
+ }
+ return
+ }
+ {{ end -}}
+ slh, containerLenS := d.decSliceHelperStart()
+ if slh.IsNil {
+ return
+ }
+ if containerLenS == 0 {
+ slh.End()
+ return
+ }
+ hasLen := containerLenS > 0
+ for j := 0; (hasLen && j < containerLenS) || !(hasLen || d.checkBreak()); j++ {
+ {{/* // if indefinite, etc, then expand the slice if necessary */ -}}
+ if j >= len(v) {
+ slh.arrayCannotExpand(hasLen, len(v), j, containerLenS)
+ return
+ }
+ slh.ElemContainerState(j)
+ {{ if eq .Elem "interface{}" -}}
+ d.decode(&v[uint(j)])
+ {{- else -}}
+ v[uint(j)] = {{ decmd .Elem false }}
+ {{- end }}
+ }
+ slh.End()
+}
+{{end}}{{end}}{{end -}}
+
+{{range .Values}}{{if not .Primitive}}{{if .MapKey -}}
+{{/*
+Maps can change if they are
+- addressable (from a ptr)
+- settable (e.g. contained in an interface{})
+
+Also, these methods are called by decodeValue directly, after handling a TryNil.
+Consequently, there's no need to check for containerLenNil here.
+*/ -}}
+func (d *Decoder) {{ .MethodNamePfx "fastpathDec" false }}R(f *codecFnInfo, rv reflect.Value) {
+ containerLen := d.mapStart(d.d.ReadMapStart())
+ {{/*
+ if containerLen == containerLenNil {
+ if rv.Kind() == reflect.Ptr {
+ *(rv2i(rv).(*map[{{ .MapKey }}]{{ .Elem }})) = nil
+ }
+ return
+ }
+ */ -}}
+ if rv.Kind() == reflect.Ptr {
+ vp, _ := rv2i(rv).(*map[{{ .MapKey }}]{{ .Elem }})
+ if *vp == nil {
+ *vp = make(map[{{ .MapKey }}]{{ .Elem }}, decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }}))
+ }
+ if containerLen != 0 {
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}L(*vp, containerLen, d)
+ }
+ } else if containerLen != 0 {
+ fastpathTV.{{ .MethodNamePfx "Dec" false }}L(rv2i(rv).(map[{{ .MapKey }}]{{ .Elem }}), containerLen, d)
+ }
+ d.mapEnd()
+}
+func (f fastpathT) {{ .MethodNamePfx "Dec" false }}X(vp *map[{{ .MapKey }}]{{ .Elem }}, d *Decoder) {
+ containerLen := d.mapStart(d.d.ReadMapStart())
+ if containerLen == containerLenNil {
+ *vp = nil
+ } else {
+ if *vp == nil {
+ *vp = make(map[{{ .MapKey }}]{{ .Elem }}, decInferLen(containerLen, d.h.MaxInitLen, {{ .Size }}))
+ }
+ if containerLen != 0 {
+ f.{{ .MethodNamePfx "Dec" false }}L(*vp, containerLen, d)
+ }
+ d.mapEnd()
+ }
+}
+func (fastpathT) {{ .MethodNamePfx "Dec" false }}L(v map[{{ .MapKey }}]{{ .Elem }}, containerLen int, d *Decoder) {
+ {{/* No need to check if containerLen == containerLenNil, as that is checked by R and L above */ -}}
+ if v == nil {
+ d.errorf("cannot decode into nil map[{{ .MapKey }}]{{ .Elem }} given stream length: %v", containerLen)
+ {{/* d.swallowMapContents(containerLen) */ -}}
+ return
+ }
+ {{if eq .Elem "interface{}" }}mapGet := v != nil && !d.h.MapValueReset && !d.h.InterfaceReset
+ {{else if eq .Elem "bytes" "[]byte" }}mapGet := v != nil && !d.h.MapValueReset
+ {{end -}}
+ var mk {{ .MapKey }}
+ var mv {{ .Elem }}
+ hasLen := containerLen > 0
+ for j := 0; (hasLen && j < containerLen) || !(hasLen || d.checkBreak()); j++ {
+ d.mapElemKey()
+ {{ if eq .MapKey "interface{}" }}mk = nil
+ d.decode(&mk)
+ if bv, bok := mk.([]byte); bok {
+ mk = d.stringZC(bv) {{/* // maps cannot have []byte as key. switch to string. */}}
+ }{{ else }}mk = {{ decmd .MapKey true }}{{ end }}
+ d.mapElemValue()
+ {{ if eq .Elem "interface{}" "[]byte" "bytes" -}}
+ if mapGet { mv = v[mk] } else { mv = nil }
+ {{ end -}}
+ {{ if eq .Elem "interface{}" -}}
+ d.decode(&mv)
+ {{ else if eq .Elem "[]byte" "bytes" -}}
+ mv = d.decodeBytesInto(mv)
+ {{ else -}}
+ mv = {{ decmd .Elem false }}
+ {{ end -}}
+ v[mk] = mv
+ }
+}
+{{end}}{{end}}{{end}}