Skip to content

Commit 347c842

Browse files
committed
Adding interning utility used in khi file format v6
1 parent 61ea8a5 commit 347c842

2 files changed

Lines changed: 495 additions & 0 deletions

File tree

pkg/model/khifile/v6/intern.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package khifilev6
16+
17+
import (
18+
"iter"
19+
"sort"
20+
"sync"
21+
"unsafe"
22+
23+
pb "github.com/GoogleCloudPlatform/khi/pkg/generated/khifile/v6"
24+
)
25+
26+
// InternStringRef represents a reference to an interned string.
27+
// This struct holds a reference to the pool and the ID of the string.
28+
type InternStringRef struct {
29+
pool *InternPool
30+
id uint32
31+
}
32+
33+
// Resolve returns the original string value.
34+
// It delegates to the pool to resolve the string from the stored ID.
35+
func (r *InternStringRef) Resolve() string {
36+
return r.pool.resolveStringFromID(r.id)
37+
}
38+
39+
// ToProto converts InternStringRef to its proto representation.
40+
func (r *InternStringRef) ToProto() *pb.InternString {
41+
id := r.id
42+
val := r.Resolve()
43+
return &pb.InternString{
44+
Id: &id,
45+
Value: &val,
46+
}
47+
}
48+
49+
// FieldPathSetRef represents a reference to an interned field path set.
50+
// This struct holds a reference to the pool and the ID of the field path set.
51+
type FieldPathSetRef struct {
52+
pool *InternPool
53+
id uint32
54+
}
55+
56+
// Resolve returns the original list of strings in the set.
57+
// It delegates to the pool to resolve the field path set and then resolves each string ID.
58+
func (r *FieldPathSetRef) Resolve() []string {
59+
ids := r.pool.resolveFieldSetFromID(r.id)
60+
res := make([]string, len(ids))
61+
for i, id := range ids {
62+
res[i] = r.pool.resolveStringFromID(id)
63+
}
64+
return res
65+
}
66+
67+
// ToProto converts FieldPathSetRef to its proto representation.
68+
func (r *FieldPathSetRef) ToProto() *pb.InternFieldPathSet {
69+
id := r.id
70+
names := r.pool.resolveFieldSetFromID(r.id)
71+
return &pb.InternFieldPathSet{
72+
Id: &id,
73+
FieldNames: names,
74+
}
75+
}
76+
77+
// InternPool manages interning of strings and field path sets to reduce memory usage.
78+
// It uses sync.Map for concurrent access and relies on IDGenerator for generating IDs.
79+
type InternPool struct {
80+
idGen *IDGenerator
81+
strToID sync.Map // map[string]uint32
82+
idToStr sync.Map // map[uint32]string
83+
84+
fieldSetToID sync.Map // map[string]uint32 (key is byte representation of []uint32)
85+
idToFieldSet sync.Map // map[uint32][]uint32
86+
}
87+
88+
// NewInternPool creates a new InternPool with the given IDGenerator.
89+
func NewInternPool(idGen *IDGenerator) *InternPool {
90+
return &InternPool{
91+
idGen: idGen,
92+
}
93+
}
94+
95+
// InternString returns a InternStringRef for the given string.
96+
// If the string is not already interned, it assigns a new ID from IDGenerator and stores it.
97+
func (p *InternPool) InternString(value string) *InternStringRef {
98+
if id, ok := p.strToID.Load(value); ok {
99+
return &InternStringRef{pool: p, id: id.(uint32)}
100+
}
101+
102+
id := p.idGen.New(IDString)
103+
p.idToStr.Store(id, value)
104+
105+
actual, loaded := p.strToID.LoadOrStore(value, id)
106+
if loaded {
107+
p.idToStr.Store(id, "")
108+
return &InternStringRef{pool: p, id: actual.(uint32)}
109+
}
110+
111+
return &InternStringRef{pool: p, id: id}
112+
}
113+
114+
// resolveStringFromID returns the string corresponding to the given ID.
115+
// It returns an empty string if the ID is not found.
116+
func (p *InternPool) resolveStringFromID(id uint32) string {
117+
if value, ok := p.idToStr.Load(id); ok {
118+
return value.(string)
119+
}
120+
return ""
121+
}
122+
123+
// InternFieldSet returns a FieldPathSetRef for the given list of strings.
124+
// It first interns each string to get its ID, and then interns the resulting list of IDs.
125+
// It uses unsafe string cast for fast lookup in fieldSetToID map without allocation.
126+
func (p *InternPool) InternFieldSet(fieldNames []string) *FieldPathSetRef {
127+
ids := make([]uint32, len(fieldNames))
128+
for i, name := range fieldNames {
129+
ids[i] = p.InternString(name).id
130+
}
131+
132+
// Zero-allocation lookup using unsafe string.
133+
keyLookup := fieldSetKey(ids)
134+
if id, ok := p.fieldSetToID.Load(keyLookup); ok {
135+
return &FieldPathSetRef{pool: p, id: id.(uint32)}
136+
}
137+
138+
id := p.idGen.New(IDFieldSet)
139+
140+
namesCopy := make([]uint32, len(ids))
141+
copy(namesCopy, ids)
142+
p.idToFieldSet.Store(id, namesCopy)
143+
keyStore := fieldSetKey(namesCopy)
144+
145+
actual, loaded := p.fieldSetToID.LoadOrStore(keyStore, id)
146+
if loaded {
147+
p.idToFieldSet.Store(id, []uint32{})
148+
return &FieldPathSetRef{pool: p, id: actual.(uint32)}
149+
}
150+
151+
return &FieldPathSetRef{pool: p, id: id}
152+
}
153+
154+
// resolveFieldSetFromID returns the field path set corresponding to the given ID.
155+
// It returns nil if the ID is not found.
156+
func (p *InternPool) resolveFieldSetFromID(id uint32) []uint32 {
157+
if value, ok := p.idToFieldSet.Load(id); ok {
158+
return value.([]uint32)
159+
}
160+
return nil
161+
}
162+
163+
// SortedStringRefs returns an iterator that yields InternStringRefs in the pool, sorted by their original string value.
164+
func (p *InternPool) SortedStringRefs() iter.Seq[*InternStringRef] {
165+
type entry struct {
166+
val string
167+
id uint32
168+
}
169+
var entries []entry
170+
171+
p.strToID.Range(func(key, value any) bool {
172+
entries = append(entries, entry{
173+
val: key.(string),
174+
id: value.(uint32),
175+
})
176+
return true
177+
})
178+
179+
sort.Slice(entries, func(i, j int) bool {
180+
return entries[i].val < entries[j].val
181+
})
182+
183+
return func(yield func(*InternStringRef) bool) {
184+
for _, e := range entries {
185+
if !yield(&InternStringRef{pool: p, id: e.id}) {
186+
return
187+
}
188+
}
189+
}
190+
}
191+
192+
// FieldSetRefs returns an iterator that yields FieldPathSetRefs in the pool, sorted by their ID.
193+
func (p *InternPool) FieldSetRefs() iter.Seq[*FieldPathSetRef] {
194+
type entry struct {
195+
id uint32
196+
}
197+
var entries []entry
198+
199+
p.fieldSetToID.Range(func(key, value any) bool {
200+
entries = append(entries, entry{
201+
id: value.(uint32),
202+
})
203+
return true
204+
})
205+
206+
// Sort by ID.
207+
sort.Slice(entries, func(i, j int) bool {
208+
return entries[i].id < entries[j].id
209+
})
210+
211+
return func(yield func(*FieldPathSetRef) bool) {
212+
for _, e := range entries {
213+
if !yield(&FieldPathSetRef{pool: p, id: e.id}) {
214+
return
215+
}
216+
}
217+
}
218+
}
219+
220+
// fieldSetKey casts a slice of uint32 to a string without copying.
221+
// The returned string shares memory with the slice. It is safe to use as a map key
222+
// ONLY if the slice is never modified.
223+
func fieldSetKey(ids []uint32) string {
224+
if len(ids) == 0 {
225+
return ""
226+
}
227+
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&ids[0])), len(ids)*4)
228+
return unsafe.String(&byteSlice[0], len(byteSlice))
229+
}

0 commit comments

Comments
 (0)