Improved symbol handling in whole-program serialization

Previously, a dehydrated SkSL program would include all symbols, even
module builtins. The rehydration API also required a vector of shared
elements, which made the API essentially impossible to use outside of
the very artificial situation in our test cases, where we retained the
original program so we could simply grab the elements from it. We
obviously cannot rely on the original program still being around in
order to successfully rehydrate it.

This CL eliminates the parent symbol tables, referring to module
builtins by name instead. This reduces the size of a small dehydrated
program by roughly 95%. We also encode the shared elements directly
into the output, which both simplifies the API and makes it work in
real-world cases.

Change-Id: I8e5dddf9316fe0886e6b97e7d29638fff8f9f499
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/505816
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
This commit is contained in:
Ethan Nicholas 2022-02-08 13:11:42 -05:00 committed by SkCQ
parent 654f09a5ad
commit b86ab9d097
11 changed files with 4829 additions and 4777 deletions

View File

@ -79,6 +79,16 @@ private:
Dehydrator* fDehydrator;
};
void Dehydrator::writeId(const Symbol* s) {
uint16_t id = this->symbolId(s);
if (id) {
this->writeU16(id);
} else {
this->writeU16(Rehydrator::kBuiltin_Symbol);
this->write(s->name());
}
}
void Dehydrator::write(Layout l) {
if (l == Layout()) {
this->writeCommand(Rehydrator::kDefaultLayout_Command);
@ -135,12 +145,13 @@ void Dehydrator::write(std::string s) {
}
void Dehydrator::write(const Symbol& s) {
uint16_t id = this->symbolId(&s, false);
uint16_t id = this->symbolId(&s);
if (id) {
this->writeCommand(Rehydrator::kSymbolRef_Command);
this->writeU16(id);
return;
}
this->allocSymbolId(&s);
switch (s.kind()) {
case Symbol::Kind::kFunctionDeclaration: {
const FunctionDeclaration& f = s.as<FunctionDeclaration>();
@ -246,7 +257,7 @@ void Dehydrator::write(const SymbolTable& symbols) {
if (!found) {
// we should only fail to find builtin types
SkASSERT(p.second->is<Type>() && p.second->as<Type>().isInBuiltinTypes());
this->writeU16(Rehydrator::kBuiltinType_Symbol);
this->writeU16(Rehydrator::kBuiltin_Symbol);
this->write(p.second->name());
}
}
@ -605,23 +616,26 @@ void Dehydrator::write(const std::vector<std::unique_ptr<ProgramElement>>& eleme
void Dehydrator::write(const Program& program) {
this->writeCommand(Rehydrator::kProgram_Command);
// Collect the symbol tables so we can write out the count
std::vector<SymbolTable*> symbolTables;
SymbolTable* symbols = program.fSymbols.get();
while (symbols) {
symbolTables.push_back(symbols);
symbols = symbols->fParent.get();
}
this->writeU8(symbolTables.size());
// Write the symbol tables from the root down
for (int i = symbolTables.size() - 1; i >= 0; --i) {
this->write(*symbolTables[i]);
}
this->writeU8((int)program.fConfig->fKind);
this->write(*program.fSymbols);
// Write the elements
this->write(program.fOwnedElements);
this->writeCommand(Rehydrator::kElements_Command);
for (const auto& e : program.fSharedElements) {
this->writeCommand(Rehydrator::kSharedFunction_Command);
const FunctionDefinition& f = e->as<FunctionDefinition>();
const FunctionDeclaration& decl = f.declaration();
this->writeU8(decl.parameters().size());
for (const Variable* param : decl.parameters()) {
this->write(*param);
}
this->write(f.declaration());
this->write(*e);
}
for (const auto& e : program.fOwnedElements) {
this->write(*e);
}
this->writeCommand(Rehydrator::kElementsComplete_Command);
// Write the inputs
struct KnownSkSLProgramInputs { bool useRTFlipUniform; };

View File

@ -88,21 +88,20 @@ private:
fBody.write32(i);
}
void writeId(const Symbol* s) {
if (!symbolId(s, false)) {
fSymbolMap.back()[s] = fNextId++;
}
this->writeU16(symbolId(s));
void allocSymbolId(const Symbol* s) {
SkASSERT(!symbolId(s));
fSymbolMap.back()[s] = fNextId++;
}
uint16_t symbolId(const Symbol* s, bool required = true) {
void writeId(const Symbol* s);
uint16_t symbolId(const Symbol* s) {
for (const auto& symbols : fSymbolMap) {
auto found = symbols.find(s);
if (found != symbols.end()) {
return found->second;
}
}
SkASSERT(!required);
return 0;
}

View File

@ -82,9 +82,10 @@ private:
std::shared_ptr<SymbolTable> fOldSymbols;
};
Rehydrator::Rehydrator(const Compiler& compiler, const uint8_t* src, size_t length,
Rehydrator::Rehydrator(Compiler& compiler, const uint8_t* src, size_t length,
std::shared_ptr<SymbolTable> symbols)
: fContext(compiler.fContext)
: fCompiler(compiler)
, fContext(compiler.fContext)
, fSymbolTable(symbols ? std::move(symbols) : compiler.makeGLSLRootSymbolTable())
SkDEBUGCODE(, fEnd(src + length)) {
SkASSERT(fSymbolTable);
@ -266,30 +267,26 @@ const Type* Rehydrator::type() {
return (const Type*) result;
}
std::unique_ptr<Program> Rehydrator::program(
const std::vector<const ProgramElement*>* sharedElements) {
std::unique_ptr<Program> Rehydrator::program() {
[[maybe_unused]] uint8_t command = this->readU8();
SkASSERT(command == kProgram_Command);
uint8_t symbolTableCount = this->readU8();
ProgramConfig* oldConfig = fContext->fConfig;
ModifiersPool* oldModifiersPool = fContext->fModifiersPool;
auto config = std::make_unique<ProgramConfig>();
config->fKind = (ProgramKind)this->readU8();
fContext->fConfig = config.get();
fSymbolTable = fCompiler.moduleForProgramKind(config->fKind).fSymbols;
auto modifiers = std::make_unique<ModifiersPool>();
fContext->fModifiersPool = modifiers.get();
for (int i = 0; i < symbolTableCount; ++i) {
this->symbolTable();
}
this->symbolTable();
std::vector<std::unique_ptr<ProgramElement>> elements = this->elements();
fContext->fConfig = oldConfig;
fContext->fModifiersPool = oldModifiersPool;
if (!sharedElements) {
sharedElements = &ThreadContext::SharedElements();
}
Program::Inputs inputs;
inputs.fUseFlipRTUniform = this->readU8();
return std::make_unique<Program>(nullptr, std::move(config), fContext, std::move(elements),
*sharedElements, std::move(modifiers), fSymbolTable, /*pool=*/nullptr, inputs);
/*sharedElements=*/std::vector<const ProgramElement*>(), std::move(modifiers),
fSymbolTable, /*pool=*/nullptr, inputs);
}
std::vector<std::unique_ptr<ProgramElement>> Rehydrator::elements() {
@ -339,6 +336,18 @@ std::unique_ptr<ProgramElement> Rehydrator::element() {
SkASSERT(type && type->is<Type>());
return std::make_unique<StructDefinition>(/*line=*/-1, type->as<Type>());
}
case Rehydrator::kSharedFunction_Command: {
int count = this->readU8();
for (int i = 0; i < count; ++i) {
[[maybe_unused]] const Symbol* param = this->symbol();
SkASSERT(param->is<Variable>());
}
[[maybe_unused]] const Symbol* decl = this->symbol();
SkASSERT(decl->is<FunctionDeclaration>());
std::unique_ptr<ProgramElement> result = this->element();
SkASSERT(result->is<FunctionDefinition>());
return result;
}
case Rehydrator::kElementsComplete_Command:
return nullptr;
default:
@ -529,9 +538,19 @@ std::unique_ptr<Expression> Rehydrator::expression() {
}
case Rehydrator::kFunctionCall_Command: {
const Type* type = this->type();
const FunctionDeclaration* f = this->symbolRef<FunctionDeclaration>(
Symbol::Kind::kFunctionDeclaration);
const Symbol* symbol = this->possiblyBuiltinSymbolRef();
ExpressionArray args = this->expressionArray();
const FunctionDeclaration* f;
if (symbol->is<FunctionDeclaration>()) {
f = &symbol->as<FunctionDeclaration>();
} else if (symbol->is<UnresolvedFunction>()) {
const UnresolvedFunction& unresolved = symbol->as<UnresolvedFunction>();
f = FunctionCall::FindBestFunctionForCall(*fContext, unresolved.functions(), args);
SkASSERT(f);
} else {
SkASSERT(false);
return nullptr;
}
return FunctionCall::Make(*fContext, /*line=*/-1, type, *f, std::move(args));
}
case Rehydrator::kIndex_Command: {
@ -607,7 +626,7 @@ std::shared_ptr<SymbolTable> Rehydrator::symbolTable() {
symbols.reserve(symbolCount);
for (int i = 0; i < symbolCount; ++i) {
int index = this->readU16();
if (index != kBuiltinType_Symbol) {
if (index != kBuiltin_Symbol) {
fSymbolTable->addWithoutOwnership(ownedSymbols[index]);
} else {
std::string_view name = this->readString();

View File

@ -34,7 +34,7 @@ class Type;
*/
class Rehydrator {
public:
static constexpr uint16_t kVersion = 6;
static constexpr uint16_t kVersion = 7;
enum Command {
// uint16 id, Type componentType, uint8 count
@ -117,6 +117,9 @@ public:
kReturn_Command,
// String name, Expression value
kSetting_Command,
// uint8_t parameterCount, Variable[] parameters, FunctionDeclaration decl,
// FunctionDefinition defn
kSharedFunction_Command,
// uint16 id, Type structType
kStructDefinition_Command,
// uint16 id, String name, uint8 fieldCount, (Modifiers, String, Type)[] fields
@ -149,7 +152,7 @@ public:
};
// src must remain in memory as long as the objects created from it do
Rehydrator(const Compiler& compiler, const uint8_t* src, size_t length,
Rehydrator(Compiler& compiler, const uint8_t* src, size_t length,
std::shared_ptr<SymbolTable> base = nullptr);
#ifdef SK_DEBUG
@ -162,15 +165,13 @@ public:
// Reads a collection of program elements and returns it
std::vector<std::unique_ptr<ProgramElement>> elements();
// Reads an entire program. If the sharedElements are not provided, they will be pulled from the
// current ThreadContext.
std::unique_ptr<Program> program(
const std::vector<const ProgramElement*>* sharedElements = nullptr);
// Reads an entire program.
std::unique_ptr<Program> program();
private:
// If this ID appears in a symbol table, it means the corresponding symbol isn't actually
// present in the file as it's a builtin type.
static constexpr uint16_t kBuiltinType_Symbol = 65535;
// If this ID appears in place of a symbol ID, it means the corresponding symbol isn't actually
// present in the file as it's a builtin. The string name of the symbol follows.
static constexpr uint16_t kBuiltin_Symbol = 65535;
int8_t readS8() {
SkASSERT(fIP < fEnd);
@ -224,6 +225,24 @@ private:
return (T*) fSymbols[result];
}
/**
* Reads either a symbol belonging to this program, or a named reference to a builtin symbol.
* This has to be a separate method from symbolRef() because builtin symbols can be const, and
* thus this method must have a const return, but there is at least one case in which we
* specifically require a non-const return value.
*/
const Symbol* possiblyBuiltinSymbolRef() {
uint16_t id = this->readU16();
if (id == kBuiltin_Symbol) {
std::string_view name = this->readString();
const Symbol* result = (*fSymbolTable)[name];
SkASSERTF(result, "symbol '%s' not found", std::string(name).c_str());
return result;
}
SkASSERT(fSymbols.size() > id);
return fSymbols[id];
}
Layout layout();
Modifiers modifiers();
@ -244,6 +263,7 @@ private:
ModifiersPool& modifiersPool() const { return *fContext->fModifiersPool; }
Compiler& fCompiler;
std::shared_ptr<Context> fContext;
std::shared_ptr<SymbolTable> fSymbolTable;
std::vector<const Symbol*> fSymbols;

View File

@ -1,4 +1,4 @@
static uint8_t SKSL_INCLUDE_sksl_frag[] = {6,0,96,0,
static uint8_t SKSL_INCLUDE_sksl_frag[] = {7,0,96,0,
12,115,107,95,70,114,97,103,67,111,111,114,100,
6,102,108,111,97,116,52,
12,115,107,95,67,108,111,99,107,119,105,115,101,
@ -7,52 +7,52 @@ static uint8_t SKSL_INCLUDE_sksl_frag[] = {6,0,96,0,
5,104,97,108,102,52,
16,115,107,95,76,97,115,116,70,114,97,103,67,111,108,111,114,
21,115,107,95,83,101,99,111,110,100,97,114,121,70,114,97,103,67,111,108,111,114,
50,1,5,0,
54,1,0,
51,1,5,0,
55,1,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,15,0,255,16,2,0,
51,2,0,15,0,0,
54,3,0,
52,2,0,15,0,0,
55,3,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,17,0,255,16,22,0,
51,4,0,35,0,0,
54,5,0,
52,4,0,35,0,0,
55,5,0,
37,
36,144,2,0,0,0,255,255,255,255,0,255,17,39,255,32,40,0,
51,6,0,53,0,0,
54,7,0,
52,6,0,53,0,0,
55,7,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,24,39,255,0,59,0,
49,6,0,0,
54,8,0,
50,6,0,0,
55,8,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,28,39,255,32,76,0,
49,6,0,0,5,0,
50,6,0,0,5,0,
1,0,
2,0,
0,0,
3,0,
4,0,
20,
56,
55,1,0,
49,2,0,0,
58,
56,
55,3,0,
49,4,0,0,
58,
56,
55,5,0,
49,6,0,0,
58,
56,
55,7,0,
49,6,0,0,
58,
56,
55,8,0,
49,6,0,0,
58,
57,
56,1,0,
50,2,0,0,
59,
57,
56,3,0,
50,4,0,0,
59,
57,
56,5,0,
50,6,0,0,
59,
57,
56,7,0,
50,6,0,0,
59,
57,
56,8,0,
50,6,0,0,
59,
21,};
static constexpr size_t SKSL_INCLUDE_sksl_frag_LENGTH = sizeof(SKSL_INCLUDE_sksl_frag);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {6,0,20,0,
static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {7,0,20,0,
12,115,107,95,70,114,97,103,67,111,111,114,100,
6,102,108,111,97,116,52,
50,1,1,0,
54,1,0,
51,1,1,0,
55,1,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,15,0,255,0,2,0,
51,2,0,15,0,0,1,0,
52,2,0,15,0,0,1,0,
0,0,
20,
56,
55,1,0,
49,2,0,0,
58,
57,
56,1,0,
50,2,0,0,
59,
21,};
static constexpr size_t SKSL_INCLUDE_sksl_rt_shader_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_shader);

View File

@ -1,4 +1,4 @@
static uint8_t SKSL_INCLUDE_sksl_vert[] = {6,0,82,0,
static uint8_t SKSL_INCLUDE_sksl_vert[] = {7,0,82,0,
12,115,107,95,80,101,114,86,101,114,116,101,120,
11,115,107,95,80,111,115,105,116,105,111,110,
6,102,108,111,97,116,52,
@ -8,42 +8,42 @@ static uint8_t SKSL_INCLUDE_sksl_vert[] = {6,0,82,0,
3,105,110,116,
13,115,107,95,73,110,115,116,97,110,99,101,73,68,
0,
50,1,6,0,
46,1,0,2,0,2,
51,1,6,0,
47,1,0,2,0,2,
37,
36,0,2,0,0,255,255,255,255,255,255,255,0,0,255,0,15,0,
51,2,0,27,0,
52,2,0,27,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,1,0,255,0,34,0,
51,3,0,47,0,1,
54,4,0,
52,3,0,47,0,1,
55,4,0,
37,
16,32,2,0,
49,1,0,0,
50,1,0,0,
23,4,0,0,
23,4,0,1,
54,5,0,
55,7,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,42,0,255,16,53,0,
51,6,0,65,0,0,
54,7,0,
52,8,0,65,0,0,
55,9,0,
37,
36,0,2,0,0,255,255,255,255,255,255,255,43,0,255,16,69,0,
49,6,0,0,4,0,
50,8,0,0,4,0,
5,0,
3,0,
2,0,
4,0,
20,
34,
49,4,0,2,0,83,0,0,
56,
55,5,0,
49,6,0,0,
58,
56,
55,7,0,
49,6,0,0,
58,
50,4,0,2,0,83,0,0,
57,
56,7,0,
50,8,0,0,
59,
57,
56,9,0,
50,8,0,0,
59,
21,};
static constexpr size_t SKSL_INCLUDE_sksl_vert_LENGTH = sizeof(SKSL_INCLUDE_sksl_vert);

View File

@ -46,6 +46,11 @@ public:
const FunctionDeclaration& function,
ExpressionArray arguments);
static const FunctionDeclaration* FindBestFunctionForCall(
const Context& context,
const std::vector<const FunctionDeclaration*>& functions,
const ExpressionArray& arguments);
const FunctionDeclaration& function() const {
return fFunction;
}
@ -69,11 +74,6 @@ private:
const FunctionDeclaration& function,
const ExpressionArray& arguments);
static const FunctionDeclaration* FindBestFunctionForCall(
const Context& context,
const std::vector<const FunctionDeclaration*>& functions,
const ExpressionArray& arguments);
const FunctionDeclaration& fFunction;
ExpressionArray fArguments;

View File

@ -237,7 +237,7 @@ static void test_rehydrate(skiatest::Reporter* r, const char* testFile) {
SkSL::Rehydrator rehydrator(compiler, (const uint8_t*) stream.str().data(),
stream.str().length());
std::unique_ptr<SkSL::Program> rehydrated = rehydrator.program(&program->fSharedElements);
std::unique_ptr<SkSL::Program> rehydrated = rehydrator.program();
REPORTER_ASSERT(r, rehydrated->description() == program->description(),
"Mismatch between original and dehydrated/rehydrated:\n-- Original:\n%s\n"
"-- Rehydrated:\n%s", program->description().c_str(),