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