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