Skip to content

Commit 8071f2a

Browse files
prattmicgopherbot
authored andcommitted
runtime: mapiter linkname compatibility layer
This CL reintroduces the various mapiter* linkname functions with a compatibility layer that is careful to maintain compatibility with users of the linkname. The wrappers are straightforward. Callers of these APIs get an extra layer of indirection, with their hiter containing a pointer to the real maps.Iter. These users will take a minor performance hit from the extra allocation, but this approach should have good long-term maintainability. Fixes #71408. Change-Id: I6a6a636c7574bbd670ff5243dfeb63dfba6dc611 Reviewed-on: https://go-review.googlesource.com/c/go/+/643899 Auto-Submit: Michael Pratt <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 78e6f2a commit 8071f2a

File tree

2 files changed

+211
-105
lines changed

2 files changed

+211
-105
lines changed

src/runtime/linkname_swiss.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build goexperiment.swissmap
6+
7+
package runtime
8+
9+
import (
10+
"internal/abi"
11+
"internal/runtime/maps"
12+
"internal/runtime/sys"
13+
"unsafe"
14+
)
15+
16+
// Legacy //go:linkname compatibility shims
17+
//
18+
// The functions below are unused by the toolchain, and exist only for
19+
// compatibility with existing //go:linkname use in the ecosystem (and in
20+
// map_noswiss.go for normal use via GOEXPERIMENT=noswissmap).
21+
22+
// linknameIter is the it argument to mapiterinit and mapiternext.
23+
//
24+
// Callers of mapiterinit allocate their own iter structure, which has the
25+
// layout of the pre-Go 1.24 hiter structure, shown here for posterity:
26+
//
27+
// type hiter struct {
28+
// key unsafe.Pointer
29+
// elem unsafe.Pointer
30+
// t *maptype
31+
// h *hmap
32+
// buckets unsafe.Pointer
33+
// bptr *bmap
34+
// overflow *[]*bmap
35+
// oldoverflow *[]*bmap
36+
// startBucket uintptr
37+
// offset uint8
38+
// wrapped bool
39+
// B uint8
40+
// i uint8
41+
// bucket uintptr
42+
// checkBucket uintptr
43+
// }
44+
//
45+
// Our structure must maintain compatibility with the old structure. This
46+
// means:
47+
//
48+
// - Our structure must be the same size or smaller than hiter. Otherwise we
49+
// may write outside the caller's hiter allocation.
50+
// - Our structure must have the same pointer layout as hiter, so that the GC
51+
// tracks pointers properly.
52+
//
53+
// Based on analysis of the "hall of shame" users of these linknames:
54+
//
55+
// - The key and elem fields must be kept up to date with the current key/elem.
56+
// Some users directly access the key and elem fields rather than calling
57+
// reflect.mapiterkey/reflect.mapiterelem.
58+
// - The t field must be non-nil after mapiterinit. gonum.org/v1/gonum uses
59+
// this to verify the iterator is initialized.
60+
// - github.com/segmentio/encoding and github.com/RomiChan/protobuf check if h
61+
// is non-nil, but the code has no effect. Thus the value of h does not
62+
// matter. See internal/runtime_reflect/map.go.
63+
type linknameIter struct {
64+
// Fields from hiter.
65+
key unsafe.Pointer
66+
elem unsafe.Pointer
67+
typ *abi.SwissMapType
68+
69+
// The real iterator.
70+
it *maps.Iter
71+
}
72+
73+
// mapiterinit is a compatibility wrapper for map iterator for users of
74+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
75+
// should use reflect or the maps package.
76+
//
77+
// mapiterinit should be an internal detail,
78+
// but widely used packages access it using linkname.
79+
// Notable members of the hall of shame include:
80+
// - github.com/bytedance/sonic
81+
// - github.com/goccy/go-json
82+
// - github.com/RomiChan/protobuf
83+
// - github.com/segmentio/encoding
84+
// - github.com/ugorji/go/codec
85+
// - github.com/wI2L/jettison
86+
//
87+
// Do not remove or change the type signature.
88+
// See go.dev/issue/67401.
89+
//
90+
//go:linkname mapiterinit
91+
func mapiterinit(t *abi.SwissMapType, m *maps.Map, it *linknameIter) {
92+
if raceenabled && m != nil {
93+
callerpc := sys.GetCallerPC()
94+
racereadpc(unsafe.Pointer(m), callerpc, abi.FuncPCABIInternal(mapiterinit))
95+
}
96+
97+
it.typ = t
98+
99+
it.it = new(maps.Iter)
100+
it.it.Init(t, m)
101+
it.it.Next()
102+
103+
it.key = it.it.Key()
104+
it.elem = it.it.Elem()
105+
}
106+
107+
// reflect_mapiterinit is a compatibility wrapper for map iterator for users of
108+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
109+
// should use reflect or the maps package.
110+
//
111+
// reflect_mapiterinit should be an internal detail,
112+
// but widely used packages access it using linkname.
113+
// Notable members of the hall of shame include:
114+
// - github.com/modern-go/reflect2
115+
// - gitee.com/quant1x/gox
116+
// - github.com/v2pro/plz
117+
// - github.com/wI2L/jettison
118+
//
119+
// Do not remove or change the type signature.
120+
// See go.dev/issue/67401.
121+
//
122+
//go:linkname reflect_mapiterinit reflect.mapiterinit
123+
func reflect_mapiterinit(t *abi.SwissMapType, m *maps.Map, it *linknameIter) {
124+
mapiterinit(t, m, it)
125+
}
126+
127+
// mapiternext is a compatibility wrapper for map iterator for users of
128+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
129+
// should use reflect or the maps package.
130+
//
131+
// mapiternext should be an internal detail,
132+
// but widely used packages access it using linkname.
133+
// Notable members of the hall of shame include:
134+
// - github.com/bytedance/sonic
135+
// - github.com/RomiChan/protobuf
136+
// - github.com/segmentio/encoding
137+
// - github.com/ugorji/go/codec
138+
// - gonum.org/v1/gonum
139+
//
140+
// Do not remove or change the type signature.
141+
// See go.dev/issue/67401.
142+
//
143+
//go:linkname mapiternext
144+
func mapiternext(it *linknameIter) {
145+
if raceenabled {
146+
callerpc := sys.GetCallerPC()
147+
racereadpc(unsafe.Pointer(it.it.Map()), callerpc, abi.FuncPCABIInternal(mapiternext))
148+
}
149+
150+
it.it.Next()
151+
152+
it.key = it.it.Key()
153+
it.elem = it.it.Elem()
154+
}
155+
156+
// reflect_mapiternext is a compatibility wrapper for map iterator for users of
157+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
158+
// should use reflect or the maps package.
159+
//
160+
// reflect_mapiternext is for package reflect,
161+
// but widely used packages access it using linkname.
162+
// Notable members of the hall of shame include:
163+
// - gitee.com/quant1x/gox
164+
// - github.com/modern-go/reflect2
165+
// - github.com/goccy/go-json
166+
// - github.com/v2pro/plz
167+
// - github.com/wI2L/jettison
168+
//
169+
// Do not remove or change the type signature.
170+
// See go.dev/issue/67401.
171+
//
172+
//go:linkname reflect_mapiternext reflect.mapiternext
173+
func reflect_mapiternext(it *linknameIter) {
174+
mapiternext(it)
175+
}
176+
177+
// reflect_mapiterkey is a compatibility wrapper for map iterator for users of
178+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
179+
// should use reflect or the maps package.
180+
//
181+
// reflect_mapiterkey should be an internal detail,
182+
// but widely used packages access it using linkname.
183+
// Notable members of the hall of shame include:
184+
// - github.com/goccy/go-json
185+
// - gonum.org/v1/gonum
186+
//
187+
// Do not remove or change the type signature.
188+
// See go.dev/issue/67401.
189+
//
190+
//go:linkname reflect_mapiterkey reflect.mapiterkey
191+
func reflect_mapiterkey(it *linknameIter) unsafe.Pointer {
192+
return it.it.Key()
193+
}
194+
195+
// reflect_mapiterelem is a compatibility wrapper for map iterator for users of
196+
// //go:linkname from before Go 1.24. It is not used by Go itself. New users
197+
// should use reflect or the maps package.
198+
//
199+
// reflect_mapiterelem should be an internal detail,
200+
// but widely used packages access it using linkname.
201+
// Notable members of the hall of shame include:
202+
// - github.com/goccy/go-json
203+
// - gonum.org/v1/gonum
204+
//
205+
// Do not remove or change the type signature.
206+
// See go.dev/issue/67401.
207+
//
208+
//go:linkname reflect_mapiterelem reflect.mapiterelem
209+
func reflect_mapiterelem(it *linknameIter) unsafe.Pointer {
210+
return it.it.Elem()
211+
}

src/runtime/map_swiss.go

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -158,31 +158,6 @@ func mapdelete(t *abi.SwissMapType, m *maps.Map, key unsafe.Pointer) {
158158
m.Delete(t, key)
159159
}
160160

161-
// mapiterinit initializes the Iter struct used for ranging over maps.
162-
// The Iter struct pointed to by 'it' is allocated on the stack
163-
// by the compilers order pass or on the heap by reflect_mapiterinit.
164-
// Both need to have zeroed hiter since the struct contains pointers.
165-
//
166-
// mapiterinit should be an internal detail,
167-
// but widely used packages access it using linkname.
168-
// Notable members of the hall of shame include:
169-
// - github.com/bytedance/sonic
170-
// - github.com/goccy/go-json
171-
// - github.com/RomiChan/protobuf
172-
// - github.com/segmentio/encoding
173-
// - github.com/ugorji/go/codec
174-
// - github.com/wI2L/jettison
175-
//
176-
// Do not remove or change the type signature.
177-
// See go.dev/issue/67401.
178-
//
179-
// TODO go:linkname mapiterinit
180-
func mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
181-
// N.B. This is required by the builtin list in internal/goobj because
182-
// it is a builtin for old maps.
183-
throw("unreachable")
184-
}
185-
186161
// mapIterStart initializes the Iter struct used for ranging over maps and
187162
// performs the first step of iteration. The Iter struct pointed to by 'it' is
188163
// allocated on the stack by the compilers order pass or on the heap by
@@ -197,25 +172,6 @@ func mapIterStart(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
197172
it.Next()
198173
}
199174

200-
// mapiternext should be an internal detail,
201-
// but widely used packages access it using linkname.
202-
// Notable members of the hall of shame include:
203-
// - github.com/bytedance/sonic
204-
// - github.com/RomiChan/protobuf
205-
// - github.com/segmentio/encoding
206-
// - github.com/ugorji/go/codec
207-
// - gonum.org/v1/gonum
208-
//
209-
// Do not remove or change the type signature.
210-
// See go.dev/issue/67401.
211-
//
212-
// TODO go:linkname mapiternext
213-
func mapiternext(it *maps.Iter) {
214-
// N.B. This is required by the builtin list in internal/goobj because
215-
// it is a builtin for old maps.
216-
throw("unreachable")
217-
}
218-
219175
// mapIterNext performs the next step of iteration. Afterwards, the next
220176
// key/elem are in it.Key()/it.Elem().
221177
func mapIterNext(it *maps.Iter) {
@@ -324,67 +280,6 @@ func reflect_mapdelete_faststr(t *abi.SwissMapType, m *maps.Map, key string) {
324280
mapdelete_faststr(t, m, key)
325281
}
326282

327-
// reflect_mapiterinit is for package reflect,
328-
// but widely used packages access it using linkname.
329-
// Notable members of the hall of shame include:
330-
// - github.com/modern-go/reflect2
331-
// - gitee.com/quant1x/gox
332-
// - github.com/v2pro/plz
333-
// - github.com/wI2L/jettison
334-
//
335-
// Do not remove or change the type signature.
336-
// See go.dev/issue/67401.
337-
//
338-
// TODO go:linkname reflect_mapiterinit reflect.mapiterinit
339-
//func reflect_mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
340-
// mapiterinit(t, m, it)
341-
//}
342-
343-
// reflect_mapiternext is for package reflect,
344-
// but widely used packages access it using linkname.
345-
// Notable members of the hall of shame include:
346-
// - gitee.com/quant1x/gox
347-
// - github.com/modern-go/reflect2
348-
// - github.com/goccy/go-json
349-
// - github.com/v2pro/plz
350-
// - github.com/wI2L/jettison
351-
//
352-
// Do not remove or change the type signature.
353-
// See go.dev/issue/67401.
354-
//
355-
// TODO go:linkname reflect_mapiternext reflect.mapiternext
356-
//func reflect_mapiternext(it *maps.Iter) {
357-
// mapiternext(it)
358-
//}
359-
360-
// reflect_mapiterkey was for package reflect,
361-
// but widely used packages access it using linkname.
362-
// Notable members of the hall of shame include:
363-
// - github.com/goccy/go-json
364-
// - gonum.org/v1/gonum
365-
//
366-
// Do not remove or change the type signature.
367-
// See go.dev/issue/67401.
368-
//
369-
// TODO go:linkname reflect_mapiterkey reflect.mapiterkey
370-
//func reflect_mapiterkey(it *maps.Iter) unsafe.Pointer {
371-
// return it.Key()
372-
//}
373-
374-
// reflect_mapiterelem was for package reflect,
375-
// but widely used packages access it using linkname.
376-
// Notable members of the hall of shame include:
377-
// - github.com/goccy/go-json
378-
// - gonum.org/v1/gonum
379-
//
380-
// Do not remove or change the type signature.
381-
// See go.dev/issue/67401.
382-
//
383-
// TODO go:linkname reflect_mapiterelem reflect.mapiterelem
384-
//func reflect_mapiterelem(it *maps.Iter) unsafe.Pointer {
385-
// return it.Elem()
386-
//}
387-
388283
// reflect_maplen is for package reflect,
389284
// but widely used packages access it using linkname.
390285
// Notable members of the hall of shame include:

0 commit comments

Comments
 (0)