xref: /aosp_15_r20/external/skia/src/sksl/lex/TransitionTable.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2021 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/lex/DFA.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/lex/TransitionTable.h"
10*c8dee2aaSAndroid Build Coastguard Worker 
11*c8dee2aaSAndroid Build Coastguard Worker #include <array>
12*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
13*c8dee2aaSAndroid Build Coastguard Worker #include <cassert>
14*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
15*c8dee2aaSAndroid Build Coastguard Worker #include <unordered_map>
16*c8dee2aaSAndroid Build Coastguard Worker #include <unordered_set>
17*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
18*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker namespace {
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker // The number of bits to use per entry in our compact transition table. This is customizable:
23*c8dee2aaSAndroid Build Coastguard Worker // - 1-bit: reasonable in theory. Doesn't actually pack many slices.
24*c8dee2aaSAndroid Build Coastguard Worker // - 2-bit: best fit for our data. Packs extremely well.
25*c8dee2aaSAndroid Build Coastguard Worker // - 4-bit: packs all but one slice, but doesn't save as much space overall.
26*c8dee2aaSAndroid Build Coastguard Worker // - 8-bit: way too large (an 8-bit LUT plus an 8-bit data table is as big as a 16-bit table)
27*c8dee2aaSAndroid Build Coastguard Worker // Other values don't divide cleanly into a byte and do not work.
28*c8dee2aaSAndroid Build Coastguard Worker constexpr int kNumBits = 2;
29*c8dee2aaSAndroid Build Coastguard Worker 
30*c8dee2aaSAndroid Build Coastguard Worker // These values are derived from kNumBits and shouldn't need to change.
31*c8dee2aaSAndroid Build Coastguard Worker constexpr int kNumValues = (1 << kNumBits) - 1;
32*c8dee2aaSAndroid Build Coastguard Worker constexpr int kDataPerByte = 8 / kNumBits;
33*c8dee2aaSAndroid Build Coastguard Worker 
34*c8dee2aaSAndroid Build Coastguard Worker enum IndexType {
35*c8dee2aaSAndroid Build Coastguard Worker     kCompactEntry = 0,
36*c8dee2aaSAndroid Build Coastguard Worker     kFullEntry,
37*c8dee2aaSAndroid Build Coastguard Worker };
38*c8dee2aaSAndroid Build Coastguard Worker struct IndexEntry {
39*c8dee2aaSAndroid Build Coastguard Worker     IndexType type;
40*c8dee2aaSAndroid Build Coastguard Worker     int pos;
41*c8dee2aaSAndroid Build Coastguard Worker };
42*c8dee2aaSAndroid Build Coastguard Worker struct CompactEntry {
43*c8dee2aaSAndroid Build Coastguard Worker     std::array<int, kNumValues> v = {};
44*c8dee2aaSAndroid Build Coastguard Worker     std::vector<int> data;
45*c8dee2aaSAndroid Build Coastguard Worker };
46*c8dee2aaSAndroid Build Coastguard Worker struct FullEntry {
47*c8dee2aaSAndroid Build Coastguard Worker     std::vector<int> data;
48*c8dee2aaSAndroid Build Coastguard Worker };
49*c8dee2aaSAndroid Build Coastguard Worker 
50*c8dee2aaSAndroid Build Coastguard Worker using TransitionSet = std::unordered_set<int>;
51*c8dee2aaSAndroid Build Coastguard Worker 
add_compact_entry(const TransitionSet & transitionSet,const std::vector<int> & data,std::vector<CompactEntry> * entries)52*c8dee2aaSAndroid Build Coastguard Worker static int add_compact_entry(const TransitionSet& transitionSet,
53*c8dee2aaSAndroid Build Coastguard Worker                              const std::vector<int>& data,
54*c8dee2aaSAndroid Build Coastguard Worker                              std::vector<CompactEntry>* entries) {
55*c8dee2aaSAndroid Build Coastguard Worker     // Create a compact entry with the unique values from the transition set, padded out with zeros
56*c8dee2aaSAndroid Build Coastguard Worker     // and sorted.
57*c8dee2aaSAndroid Build Coastguard Worker     CompactEntry result{};
58*c8dee2aaSAndroid Build Coastguard Worker     assert(transitionSet.size() <= result.v.size());
59*c8dee2aaSAndroid Build Coastguard Worker     std::copy(transitionSet.begin(), transitionSet.end(), result.v.begin());
60*c8dee2aaSAndroid Build Coastguard Worker     std::sort(result.v.rbegin(), result.v.rend());
61*c8dee2aaSAndroid Build Coastguard Worker 
62*c8dee2aaSAndroid Build Coastguard Worker     // Create a mapping from real values to small values.
63*c8dee2aaSAndroid Build Coastguard Worker     std::unordered_map<int, int> translationTable;
64*c8dee2aaSAndroid Build Coastguard Worker     for (size_t index = 0; index < result.v.size(); ++index) {
65*c8dee2aaSAndroid Build Coastguard Worker         translationTable[result.v[index]] = index;
66*c8dee2aaSAndroid Build Coastguard Worker     }
67*c8dee2aaSAndroid Build Coastguard Worker     translationTable[0] = result.v.size();
68*c8dee2aaSAndroid Build Coastguard Worker 
69*c8dee2aaSAndroid Build Coastguard Worker     // Convert the real values into small values.
70*c8dee2aaSAndroid Build Coastguard Worker     for (size_t index = 0; index < data.size(); ++index) {
71*c8dee2aaSAndroid Build Coastguard Worker         int value = data[index];
72*c8dee2aaSAndroid Build Coastguard Worker         assert(translationTable.find(value) != translationTable.end());
73*c8dee2aaSAndroid Build Coastguard Worker         result.data.push_back(translationTable[value]);
74*c8dee2aaSAndroid Build Coastguard Worker     }
75*c8dee2aaSAndroid Build Coastguard Worker 
76*c8dee2aaSAndroid Build Coastguard Worker     // Look for an existing entry that exactly matches this one.
77*c8dee2aaSAndroid Build Coastguard Worker     for (size_t index = 0; index < entries->size(); ++index) {
78*c8dee2aaSAndroid Build Coastguard Worker         if (entries->at(index).v == result.v && entries->at(index).data == result.data) {
79*c8dee2aaSAndroid Build Coastguard Worker             return index;
80*c8dee2aaSAndroid Build Coastguard Worker         }
81*c8dee2aaSAndroid Build Coastguard Worker     }
82*c8dee2aaSAndroid Build Coastguard Worker 
83*c8dee2aaSAndroid Build Coastguard Worker     // Add this as a new entry.
84*c8dee2aaSAndroid Build Coastguard Worker     entries->push_back(std::move(result));
85*c8dee2aaSAndroid Build Coastguard Worker     return (int)(entries->size() - 1);
86*c8dee2aaSAndroid Build Coastguard Worker }
87*c8dee2aaSAndroid Build Coastguard Worker 
add_full_entry(const TransitionSet & transitionMap,const std::vector<int> & data,std::vector<FullEntry> * entries)88*c8dee2aaSAndroid Build Coastguard Worker static int add_full_entry(const TransitionSet& transitionMap,
89*c8dee2aaSAndroid Build Coastguard Worker                           const std::vector<int>& data,
90*c8dee2aaSAndroid Build Coastguard Worker                           std::vector<FullEntry>* entries) {
91*c8dee2aaSAndroid Build Coastguard Worker     // Create a full entry with this data.
92*c8dee2aaSAndroid Build Coastguard Worker     FullEntry result{};
93*c8dee2aaSAndroid Build Coastguard Worker     result.data = std::vector<int>(data.begin(), data.end());
94*c8dee2aaSAndroid Build Coastguard Worker 
95*c8dee2aaSAndroid Build Coastguard Worker     // Look for an existing entry that exactly matches this one.
96*c8dee2aaSAndroid Build Coastguard Worker     for (size_t index = 0; index < entries->size(); ++index) {
97*c8dee2aaSAndroid Build Coastguard Worker         if (entries->at(index).data == result.data) {
98*c8dee2aaSAndroid Build Coastguard Worker             return index;
99*c8dee2aaSAndroid Build Coastguard Worker         }
100*c8dee2aaSAndroid Build Coastguard Worker     }
101*c8dee2aaSAndroid Build Coastguard Worker 
102*c8dee2aaSAndroid Build Coastguard Worker     // Add this as a new entry.
103*c8dee2aaSAndroid Build Coastguard Worker     entries->push_back(std::move(result));
104*c8dee2aaSAndroid Build Coastguard Worker     return (int)(entries->size() - 1);
105*c8dee2aaSAndroid Build Coastguard Worker }
106*c8dee2aaSAndroid Build Coastguard Worker 
107*c8dee2aaSAndroid Build Coastguard Worker }  // namespace
108*c8dee2aaSAndroid Build Coastguard Worker 
WriteTransitionTable(std::ofstream & out,const DFA & dfa,size_t states)109*c8dee2aaSAndroid Build Coastguard Worker void WriteTransitionTable(std::ofstream& out, const DFA& dfa, size_t states) {
110*c8dee2aaSAndroid Build Coastguard Worker     int numTransitions = dfa.fTransitions.size();
111*c8dee2aaSAndroid Build Coastguard Worker 
112*c8dee2aaSAndroid Build Coastguard Worker     // Assemble our compact and full data tables, and an index into them.
113*c8dee2aaSAndroid Build Coastguard Worker     std::vector<CompactEntry> compactEntries;
114*c8dee2aaSAndroid Build Coastguard Worker     std::vector<FullEntry> fullEntries;
115*c8dee2aaSAndroid Build Coastguard Worker     std::vector<IndexEntry> indices;
116*c8dee2aaSAndroid Build Coastguard Worker     for (size_t s = 0; s < states; ++s) {
117*c8dee2aaSAndroid Build Coastguard Worker         // Copy all the transitions for this state into a flat array, and into a histogram (counting
118*c8dee2aaSAndroid Build Coastguard Worker         // the number of unique state-transition values). Most states only transition to a few
119*c8dee2aaSAndroid Build Coastguard Worker         // possible new states.
120*c8dee2aaSAndroid Build Coastguard Worker         TransitionSet transitionSet;
121*c8dee2aaSAndroid Build Coastguard Worker         std::vector<int> data(numTransitions);
122*c8dee2aaSAndroid Build Coastguard Worker         for (int t = 0; t < numTransitions; ++t) {
123*c8dee2aaSAndroid Build Coastguard Worker             if ((size_t) t < dfa.fTransitions.size() && s < dfa.fTransitions[t].size()) {
124*c8dee2aaSAndroid Build Coastguard Worker                 int value = dfa.fTransitions[t][s];
125*c8dee2aaSAndroid Build Coastguard Worker                 assert(value >= 0 && value < (int)states);
126*c8dee2aaSAndroid Build Coastguard Worker                 data[t] = value;
127*c8dee2aaSAndroid Build Coastguard Worker                 transitionSet.insert(value);
128*c8dee2aaSAndroid Build Coastguard Worker             }
129*c8dee2aaSAndroid Build Coastguard Worker         }
130*c8dee2aaSAndroid Build Coastguard Worker 
131*c8dee2aaSAndroid Build Coastguard Worker         transitionSet.erase(0);
132*c8dee2aaSAndroid Build Coastguard Worker         if (transitionSet.size() <= kNumValues) {
133*c8dee2aaSAndroid Build Coastguard Worker             // This table only contained a small number of unique nonzero values.
134*c8dee2aaSAndroid Build Coastguard Worker             // Use a compact representation that squishes each value down to a few bits.
135*c8dee2aaSAndroid Build Coastguard Worker             int index = add_compact_entry(transitionSet, data, &compactEntries);
136*c8dee2aaSAndroid Build Coastguard Worker             indices.push_back(IndexEntry{kCompactEntry, index});
137*c8dee2aaSAndroid Build Coastguard Worker         } else {
138*c8dee2aaSAndroid Build Coastguard Worker             // This table contained a large number of values. We can't compact it.
139*c8dee2aaSAndroid Build Coastguard Worker             int index = add_full_entry(transitionSet, data, &fullEntries);
140*c8dee2aaSAndroid Build Coastguard Worker             indices.push_back(IndexEntry{kFullEntry, index});
141*c8dee2aaSAndroid Build Coastguard Worker         }
142*c8dee2aaSAndroid Build Coastguard Worker     }
143*c8dee2aaSAndroid Build Coastguard Worker 
144*c8dee2aaSAndroid Build Coastguard Worker     // Find the largest value for each compact-entry slot.
145*c8dee2aaSAndroid Build Coastguard Worker     int maxValue = 0;
146*c8dee2aaSAndroid Build Coastguard Worker     for (const CompactEntry& entry : compactEntries) {
147*c8dee2aaSAndroid Build Coastguard Worker         for (int index=0; index < kNumValues; ++index) {
148*c8dee2aaSAndroid Build Coastguard Worker             maxValue = std::max(maxValue, entry.v[index]);
149*c8dee2aaSAndroid Build Coastguard Worker         }
150*c8dee2aaSAndroid Build Coastguard Worker     }
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker     // Figure out how many bits we need to store our max value.
153*c8dee2aaSAndroid Build Coastguard Worker     int bitsPerValue = std::ceil(std::log2(maxValue));
154*c8dee2aaSAndroid Build Coastguard Worker     maxValue = (1 << bitsPerValue) - 1;
155*c8dee2aaSAndroid Build Coastguard Worker 
156*c8dee2aaSAndroid Build Coastguard Worker     // If we exceed 10 bits per value, three values would overflow 32 bits. If this happens, we'll
157*c8dee2aaSAndroid Build Coastguard Worker     // need to pack our values another way.
158*c8dee2aaSAndroid Build Coastguard Worker     assert(bitsPerValue <= 10);
159*c8dee2aaSAndroid Build Coastguard Worker 
160*c8dee2aaSAndroid Build Coastguard Worker     // Emit all the structs our transition table will use.
161*c8dee2aaSAndroid Build Coastguard Worker     out << "using IndexEntry = int16_t;\n"
162*c8dee2aaSAndroid Build Coastguard Worker         << "struct FullEntry {\n"
163*c8dee2aaSAndroid Build Coastguard Worker         << "    State data[" << numTransitions << "];\n"
164*c8dee2aaSAndroid Build Coastguard Worker         << "};\n";
165*c8dee2aaSAndroid Build Coastguard Worker 
166*c8dee2aaSAndroid Build Coastguard Worker     // Emit the compact-entry structure. We store all three values in `v`. If kNumBits were to
167*c8dee2aaSAndroid Build Coastguard Worker     // change, we would need to adjust the packing algorithm.
168*c8dee2aaSAndroid Build Coastguard Worker     static_assert(kNumBits == 2);
169*c8dee2aaSAndroid Build Coastguard Worker     out << "struct CompactEntry {\n"
170*c8dee2aaSAndroid Build Coastguard Worker         << "    uint32_t values;\n"
171*c8dee2aaSAndroid Build Coastguard Worker         << "    uint8_t data[" << std::ceil(float(numTransitions) / float(kDataPerByte)) << "];\n"
172*c8dee2aaSAndroid Build Coastguard Worker         << "};\n";
173*c8dee2aaSAndroid Build Coastguard Worker 
174*c8dee2aaSAndroid Build Coastguard Worker     // Emit the full-table data.
175*c8dee2aaSAndroid Build Coastguard Worker     out << "static constexpr FullEntry kFull[] = {\n";
176*c8dee2aaSAndroid Build Coastguard Worker     for (const FullEntry& entry : fullEntries) {
177*c8dee2aaSAndroid Build Coastguard Worker         out << "    {";
178*c8dee2aaSAndroid Build Coastguard Worker         for (int value : entry.data) {
179*c8dee2aaSAndroid Build Coastguard Worker             out << value << ", ";
180*c8dee2aaSAndroid Build Coastguard Worker         }
181*c8dee2aaSAndroid Build Coastguard Worker         out << "},\n";
182*c8dee2aaSAndroid Build Coastguard Worker     }
183*c8dee2aaSAndroid Build Coastguard Worker     out << "};\n";
184*c8dee2aaSAndroid Build Coastguard Worker 
185*c8dee2aaSAndroid Build Coastguard Worker     // Emit the compact-table data.
186*c8dee2aaSAndroid Build Coastguard Worker     out << "static constexpr CompactEntry kCompact[] = {\n";
187*c8dee2aaSAndroid Build Coastguard Worker     for (const CompactEntry& entry : compactEntries) {
188*c8dee2aaSAndroid Build Coastguard Worker         out << "    {";
189*c8dee2aaSAndroid Build Coastguard Worker 
190*c8dee2aaSAndroid Build Coastguard Worker         // We pack all three values into `v`. If kNumBits were to change, we would need to adjust
191*c8dee2aaSAndroid Build Coastguard Worker         // this packing algorithm.
192*c8dee2aaSAndroid Build Coastguard Worker         static_assert(kNumBits == 2);
193*c8dee2aaSAndroid Build Coastguard Worker         out << entry.v[0];
194*c8dee2aaSAndroid Build Coastguard Worker         if (entry.v[1]) {
195*c8dee2aaSAndroid Build Coastguard Worker             out << " | (" << entry.v[1] << " << " << bitsPerValue << ")";
196*c8dee2aaSAndroid Build Coastguard Worker         }
197*c8dee2aaSAndroid Build Coastguard Worker         if (entry.v[2]) {
198*c8dee2aaSAndroid Build Coastguard Worker             out << " | (" << entry.v[2] << " << " << (2 * bitsPerValue) << ")";
199*c8dee2aaSAndroid Build Coastguard Worker         }
200*c8dee2aaSAndroid Build Coastguard Worker         out << ", {";
201*c8dee2aaSAndroid Build Coastguard Worker 
202*c8dee2aaSAndroid Build Coastguard Worker         unsigned int shiftBits = 0, combinedBits = 0;
203*c8dee2aaSAndroid Build Coastguard Worker         for (int index = 0; index < numTransitions; index++) {
204*c8dee2aaSAndroid Build Coastguard Worker             combinedBits |= entry.data[index] << shiftBits;
205*c8dee2aaSAndroid Build Coastguard Worker             shiftBits += kNumBits;
206*c8dee2aaSAndroid Build Coastguard Worker             assert(shiftBits <= 8);
207*c8dee2aaSAndroid Build Coastguard Worker             if (shiftBits == 8) {
208*c8dee2aaSAndroid Build Coastguard Worker                 out << combinedBits << ", ";
209*c8dee2aaSAndroid Build Coastguard Worker                 shiftBits = 0;
210*c8dee2aaSAndroid Build Coastguard Worker                 combinedBits = 0;
211*c8dee2aaSAndroid Build Coastguard Worker             }
212*c8dee2aaSAndroid Build Coastguard Worker         }
213*c8dee2aaSAndroid Build Coastguard Worker         if (shiftBits > 0) {
214*c8dee2aaSAndroid Build Coastguard Worker             // Flush any partial values.
215*c8dee2aaSAndroid Build Coastguard Worker             out << combinedBits;
216*c8dee2aaSAndroid Build Coastguard Worker         }
217*c8dee2aaSAndroid Build Coastguard Worker         out << "}},\n";
218*c8dee2aaSAndroid Build Coastguard Worker     }
219*c8dee2aaSAndroid Build Coastguard Worker     out << "};\n"
220*c8dee2aaSAndroid Build Coastguard Worker         << "static constexpr IndexEntry kIndices[] = {\n";
221*c8dee2aaSAndroid Build Coastguard Worker     for (const IndexEntry& entry : indices) {
222*c8dee2aaSAndroid Build Coastguard Worker         if (entry.type == kFullEntry) {
223*c8dee2aaSAndroid Build Coastguard Worker             // Bit-not is used so that full entries start at -1 and go down from there.
224*c8dee2aaSAndroid Build Coastguard Worker             out << ~entry.pos << ", ";
225*c8dee2aaSAndroid Build Coastguard Worker         } else {
226*c8dee2aaSAndroid Build Coastguard Worker             // Compact entries start at 0 and go up from there.
227*c8dee2aaSAndroid Build Coastguard Worker             out << entry.pos << ", ";
228*c8dee2aaSAndroid Build Coastguard Worker         }
229*c8dee2aaSAndroid Build Coastguard Worker     }
230*c8dee2aaSAndroid Build Coastguard Worker     out << "};\n"
231*c8dee2aaSAndroid Build Coastguard Worker         << "static State get_transition(uint8_t transition, State state) {\n"
232*c8dee2aaSAndroid Build Coastguard Worker         << "    IndexEntry index = kIndices[state];\n"
233*c8dee2aaSAndroid Build Coastguard Worker         << "    if (index < 0) { return kFull[~index].data[transition]; }\n"
234*c8dee2aaSAndroid Build Coastguard Worker         << "    const CompactEntry& entry = kCompact[index];\n"
235*c8dee2aaSAndroid Build Coastguard Worker         << "    int v = entry.data[transition >> " << std::log2(kDataPerByte) << "];\n"
236*c8dee2aaSAndroid Build Coastguard Worker         << "    v >>= " << kNumBits << " * (transition & " << kDataPerByte - 1 << ");\n"
237*c8dee2aaSAndroid Build Coastguard Worker         << "    v &= " << kNumValues << ";\n"
238*c8dee2aaSAndroid Build Coastguard Worker         << "    v *= " << bitsPerValue << ";\n"
239*c8dee2aaSAndroid Build Coastguard Worker         << "    return (entry.values >> v) & " << maxValue << ";\n"
240*c8dee2aaSAndroid Build Coastguard Worker         << "}\n";
241*c8dee2aaSAndroid Build Coastguard Worker }
242