blob: f07c2129207ec24bca63d4f0b805892a30919b37 [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/SkSpan.h"
#include "include/core/SkTypes.h"
#include "src/base/SkEnumBitMask.h"
#include "src/base/SkStringView.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCompiler.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/SkSLIRNode.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include "src/sksl/ir/SkSLProgramElement.h"
#include "src/sksl/ir/SkSLStatement.h"
#include "src/sksl/ir/SkSLSymbol.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 Context;
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>();
ModifierFlags flags = mutableDecl->modifierFlags();
flags &= ~ModifierFlag::kExport;
mutableDecl->setModifierFlags(flags);
mutableSym = mutableDecl->mutableNextOverload();
}
}
void Transform::RenamePrivateSymbols(Context& context,
Module& module,
ProgramUsage* usage,
ProgramKind kind) {
class SymbolRenamer : public ProgramWriter {
public:
SymbolRenamer(Context& context,
ProgramUsage* usage,
SymbolTable* symbolBase,
ProgramKind kind)
: fContext(context)
, fUsage(usage)
, fSymbolTableStack({symbolBase})
, fKind(kind) {}
static std::string FindShortNameForSymbol(const Symbol* sym,
const SymbolTable* symbolTable,
const 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()) {
return;
}
// Ensure that this variable is properly set up in the symbol table.
SymbolTable* symbols = fSymbolTableStack.back();
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(fContext, 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();
std::string shortName = FindShortNameForSymbol(funcDecl, symbols,
std::move(namePrefix));
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(fContext, 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.name(), '$') &&
!funcDecl.modifierFlags().isExport();
}
}
void minifyFunction(FunctionDefinition& def) {
// If the function is private, minify its name.
const FunctionDeclaration* funcDecl = &def.declaration();
if (this->functionNameCanBeMinifiedSafely(*funcDecl)) {
this->minifyFunctionName(funcDecl);
}
// Minify the names of each function parameter.
Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(),
&fSymbolTableStack);
for (Variable* param : funcDecl->parameters()) {
this->minifyVariableName(param);
}
}
void minifyPrototype(FunctionPrototype& proto) {
const FunctionDeclaration* funcDecl = &proto.declaration();
if (funcDecl->definition()) {
// This function is defined somewhere; this isn't just a loose prototype.
return;
}
// 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()) {
param->setName("");
}
}
bool visitProgramElement(ProgramElement& elem) override {
switch (elem.kind()) {
case ProgramElement::Kind::kFunction:
this->minifyFunction(elem.as<FunctionDefinition>());
return INHERITED::visitProgramElement(elem);
case ProgramElement::Kind::kFunctionPrototype:
this->minifyPrototype(elem.as<FunctionPrototype>());
return INHERITED::visitProgramElement(elem);
default:
return false;
}
}
bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(),
&fSymbolTableStack);
if (stmt->is<VarDeclaration>()) {
// Minify the variable's name.
VarDeclaration& decl = stmt->as<VarDeclaration>();
this->minifyVariableName(decl.var());
}
return INHERITED::visitStatementPtr(stmt);
}
Context& fContext;
ProgramUsage* fUsage;
std::vector<SymbolTable*> fSymbolTableStack;
ProgramKind fKind;
using INHERITED = ProgramWriter;
};
// Rename local variables and private functions.
SymbolRenamer renamer{context, usage, module.fSymbols.get(), kind};
for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
renamer.visitProgramElement(*pe);
}
// 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->modifierFlags().isExport()) {
strip_export_flag(context, funcDecl, module.fSymbols.get());
}
}
}
}
} // namespace SkSL