blob: 50bb88baf9735e93c27f59112ef21c97b6e0b3d8 [file] [log] [blame]
* Copyright 2022 Google LLC
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
#include "include/core/SkTypes.h"
#include "include/private/SkSLIRNode.h"
#include "include/private/SkSLModifiers.h"
#include "include/private/SkSLProgramElement.h"
#include "include/private/SkSLStatement.h"
#include "include/private/SkSLSymbol.h"
#include "src/base/SkStringView.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLModifiersPool.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLFunctionPrototype.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/transform/SkSLProgramWriter.h"
#include "src/sksl/transform/SkSLTransform.h"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace SkSL {
class ProgramUsage;
enum class ProgramKind : int8_t;
static void strip_export_flag(Context& context,
const FunctionDeclaration* funcDecl,
SymbolTable* symbols) {
// Remove `$export` from every overload of this function.
Symbol* mutableSym = symbols->findMutable(funcDecl->name());
while (mutableSym) {
FunctionDeclaration* mutableDecl = &mutableSym->as<FunctionDeclaration>();
Modifiers modifiers = mutableDecl->modifiers();
modifiers.fFlags &= ~Modifiers::kExport_Flag;
mutableSym = mutableDecl->mutableNextOverload();
void Transform::RenamePrivateSymbols(Context& context,
Module& module,
ProgramUsage* usage,
ProgramKind kind) {
class SymbolRenamer : public ProgramWriter {
SymbolRenamer(Context& context,
ProgramUsage* usage,
std::shared_ptr<SymbolTable> symbolBase,
ProgramKind kind)
: fContext(context)
, fUsage(usage)
, fSymbolTableStack({std::move(symbolBase)})
, fKind(kind) {}
static std::string FindShortNameForSymbol(const Symbol* sym,
const SymbolTable* symbolTable,
std::string namePrefix) {
static constexpr std::string_view kLetters[] = {
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
// Try any single-letter option.
for (std::string_view letter : kLetters) {
std::string name = namePrefix + std::string(letter);
if (symbolTable->find(name) == nullptr) {
return name;
// Try every two-letter option.
for (std::string_view letterA : kLetters) {
for (std::string_view letterB : kLetters) {
std::string name = namePrefix + std::string(letterA) + std::string(letterB);
if (symbolTable->find(name) == nullptr) {
return name;
// We struck out. Somehow, all 2700 two-letter names have been claimed.
SkDEBUGFAILF("Unable to find unique name for '%s'", std::string(sym->name()).c_str());
return std::string(sym->name());
void minifyVariableName(const Variable* var) {
// Some variables are associated with anonymous parameters--these don't have names and
// aren't present in the symbol table. Their names are already empty so there's no way
// to shrink them further.
if (var->name().empty()) {
// Ensure that this variable is properly set up in the symbol table.
SymbolTable* symbols = fSymbolTableStack.back().get();
Symbol* mutableSym = symbols->findMutable(var->name());
SkASSERTF(mutableSym != nullptr,
"symbol table missing '%.*s'", (int)var->name().size(), var->name().data());
SkASSERTF(mutableSym == var,
"wrong symbol found for '%.*s'", (int)var->name().size(), var->name().data());
// Look for a new name for this symbol.
// Note: we always rename _every_ variable, even ones with single-letter names. This is
// a safeguard: if we claimed a name like `i`, and then the program itself contained an
// `i` later on, in a nested SymbolTable, the two names would clash. By always renaming
// everything, we can ignore that problem.
std::string shortName = FindShortNameForSymbol(var, symbols, "");
SkASSERT(symbols->findMutable(shortName) == nullptr);
// Update the symbol's name.
const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
symbols->renameSymbol(mutableSym, *ownedName);
void minifyFunctionName(const FunctionDeclaration* funcDecl) {
// Look for a new name for this function.
std::string namePrefix = ProgramConfig::IsRuntimeEffect(fKind) ? "" : "$";
SymbolTable* symbols = fSymbolTableStack.back().get();
std::string shortName = FindShortNameForSymbol(funcDecl, symbols,
SkASSERT(symbols->findMutable(shortName) == nullptr);
if (shortName.size() < funcDecl->name().size()) {
// Update the function's name. (If the function has overloads, this will rename all
// of them at once.)
Symbol* mutableSym = symbols->findMutable(funcDecl->name());
const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
symbols->renameSymbol(mutableSym, *ownedName);
bool functionNameCanBeMinifiedSafely(const FunctionDeclaration& funcDecl) const {
if (ProgramConfig::IsRuntimeEffect(fKind)) {
// The only externally-accessible function in a runtime effect is main().
return !funcDecl.isMain();
} else {
// We will only minify $private_functions, and only ones not marked as $export.
return skstd::starts_with(, '$') &&
!(funcDecl.modifiers().fFlags & Modifiers::kExport_Flag);
void minifyFunction(FunctionDefinition& def) {
// If the function is private, minify its name.
const FunctionDeclaration* funcDecl = &def.declaration();
if (this->functionNameCanBeMinifiedSafely(*funcDecl)) {
// Minify the names of each function parameter.
Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(),
for (Variable* param : funcDecl->parameters()) {
void minifyPrototype(FunctionPrototype& proto) {
const FunctionDeclaration* funcDecl = &proto.declaration();
if (funcDecl->definition()) {
// This function is defined somewhere; this isn't just a loose prototype.
// Eliminate the names of each function parameter.
// The parameter names aren't in the symbol table's name lookup map at all.
// All we need to do is blank out their names.
for (Variable* param : funcDecl->parameters()) {
bool visitProgramElement(ProgramElement& elem) override {
switch (elem.kind()) {
case ProgramElement::Kind::kFunction:
return INHERITED::visitProgramElement(elem);
case ProgramElement::Kind::kFunctionPrototype:
return INHERITED::visitProgramElement(elem);
return false;
bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(),
if (stmt->is<VarDeclaration>()) {
// Minify the variable's name.
VarDeclaration& decl = stmt->as<VarDeclaration>();
return INHERITED::visitStatementPtr(stmt);
Context& fContext;
ProgramUsage* fUsage;
std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack;
ProgramKind fKind;
using INHERITED = ProgramWriter;
// Rename local variables and private functions.
SymbolRenamer renamer{context, usage, module.fSymbols, kind};
for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
// Strip off modifier `$export` from every function. (Only the minifier checks this flag, so we
// can remove it without affecting the meaning of the code.)
for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
if (pe->is<FunctionDefinition>()) {
const FunctionDeclaration* funcDecl = &pe->as<FunctionDefinition>().declaration();
if (funcDecl->modifiers().fFlags & Modifiers::kExport_Flag) {
strip_export_flag(context, funcDecl, module.fSymbols.get());
} // namespace SkSL