-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathstate_lists.go
More file actions
75 lines (66 loc) · 2.76 KB
/
Copy pathstate_lists.go
File metadata and controls
75 lines (66 loc) · 2.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package quamina
import (
"cmp"
"encoding/binary"
"slices"
"unsafe"
)
// internEntry bundles the list and DFA state into one map value so that
// cache hits require a single map lookup instead of two.
type internEntry struct {
states []*faState
dfaState *faState
}
// The idea is that in we are going to be computing the epsilon closures of NFA states, which
// will be slices of states. There will be duplicate slices and we want to deduplicate. There's
// probably a more idiomatic and efficient way to do this.
type stateLists struct {
entries map[string]internEntry
// Scratch space reused across intern() calls
sortBuf []*faState // reusable sorted buffer
keyBuf []byte // reusable key bytes buffer
}
func newStateLists() *stateLists {
return &stateLists{
entries: make(map[string]internEntry),
}
}
// intern turns a collection of states that may have dupes and, when deduped and
// considered as a set of states, may be identical to a previously-seen set of states.
// It returns a canonicalized set representation of the collection, a DFA state
// which either has already been computed for the set or is created and empty, and
// a boolean indicating whether the DFA state has already been computed or not.
func (sl *stateLists) intern(list []*faState) ([]*faState, *faState, bool) {
// Dedup by sorting then compacting adjacent duplicates. The set key is
// built from sorted pointers anyway, so sorting is not extra work; once
// sorted, duplicates are adjacent and Compact removes them in one linear
// pass. This avoids both a per-call dedup map and a per-faState
// generation field (the latter was removed to shrink steady-state memory).
sl.sortBuf = append(sl.sortBuf[:0], list...)
slices.SortFunc(sl.sortBuf, func(a, b *faState) int {
return cmp.Compare(uintptr(unsafe.Pointer(a)), uintptr(unsafe.Pointer(b)))
})
sl.sortBuf = slices.Compact(sl.sortBuf)
// Pre-size the key buffer and write pointers with PutUint64 instead of
// appending byte-by-byte, avoiding 8 append calls and bounds checks per state.
needed := len(sl.sortBuf) * 8
if cap(sl.keyBuf) < needed {
sl.keyBuf = make([]byte, needed)
} else {
sl.keyBuf = sl.keyBuf[:needed]
}
for i, state := range sl.sortBuf {
binary.LittleEndian.PutUint64(sl.keyBuf[i*8:], uint64(uintptr(unsafe.Pointer(state))))
}
// string(sl.keyBuf) in a map lookup is optimized by the compiler to avoid allocation
if entry, exists := sl.entries[string(sl.keyBuf)]; exists {
return entry.states, entry.dfaState, true
}
// cache miss: allocate owned copies for the map
key := string(sl.keyBuf)
stored := make([]*faState, len(sl.sortBuf))
copy(stored, sl.sortBuf)
dfaState := &faState{table: newSmallTable()}
sl.entries[key] = internEntry{states: stored, dfaState: dfaState}
return stored, dfaState, false
}