blob: 747134af03f5d34346a431150bca8c6ad416a0b7 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "include/core/SkSpan.h"
#include "include/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLUnresolvedFunction.h"
namespace SkSL {
static SkSpan<const FunctionDeclaration* const> get_overload_set(
const Symbol& s,
const FunctionDeclaration*& scratchPtr) {
switch (s.kind()) {
case Symbol::Kind::kFunctionDeclaration:
scratchPtr = &s.as<FunctionDeclaration>();
return SkSpan(&scratchPtr, 1);
case Symbol::Kind::kUnresolvedFunction:
return SkSpan(s.as<UnresolvedFunction>().functions());
default:
return SkSpan<const FunctionDeclaration* const>{};
}
}
const Symbol* SymbolTable::operator[](std::string_view name) {
return this->lookup(this, /*encounteredModuleBoundary=*/false, MakeSymbolKey(name));
}
bool SymbolTable::isType(const SymbolKey& key) const {
const Symbol** symbolPPtr = fSymbols.find(key);
if (!symbolPPtr) {
// The symbol wasn't found; recurse into the parent symbol table.
return fParent && fParent->isType(key);
}
// We found a symbol with the given name; was it a type?
return (*symbolPPtr)->is<Type>();
}
bool SymbolTable::isType(std::string_view name) const {
return this->isType(MakeSymbolKey(name));
}
bool SymbolTable::isBuiltinType(std::string_view name) const {
if (!this->isBuiltin()) {
return fParent && fParent->isBuiltinType(name);
}
return this->isType(name);
}
const Symbol* SymbolTable::lookup(SymbolTable* writableSymbolTable,
bool encounteredModuleBoundary,
const SymbolKey& key) {
// Symbol-table lookup can cause new UnresolvedFunction nodes to be created; however, we don't
// want these to end up in the symbol tables of loaded modules (where they will outlive the
// Program associated with those UnresolvedFunction nodes). `writableSymbolTable` tracks the
// closest symbol table to the root which is not a built-in. `encounteredModuleBoundary` is set
// to true whenever we detect a module boundary; once this happens, we stop updating the
// `writableSymbolTable` to prevent making changes outside of our current Program.
if (!encounteredModuleBoundary) {
writableSymbolTable = this;
encounteredModuleBoundary |= fAtModuleBoundary;
}
const Symbol** symbolPPtr = fSymbols.find(key);
if (!symbolPPtr) {
// The symbol wasn't found; recurse into the parent symbol table.
return fParent ? fParent->lookup(writableSymbolTable, encounteredModuleBoundary, key)
: nullptr;
}
const Symbol* symbol = *symbolPPtr;
if (!fParent) {
// We found a symbol at the top level.
return symbol;
}
const FunctionDeclaration* scratchPtr;
SkSpan<const FunctionDeclaration* const> overloadSet = get_overload_set(*symbol, scratchPtr);
if (overloadSet.empty()) {
// We found a non-function-related symbol. Return it.
return symbol;
}
// We found a function-related symbol. We need to return the complete overload set.
return this->buildOverloadSet(writableSymbolTable, encounteredModuleBoundary,
key, symbol, overloadSet);
}
const Symbol* SymbolTable::buildOverloadSet(SymbolTable* writableSymbolTable,
bool encounteredModuleBoundary,
const SymbolKey& key,
const Symbol* symbol,
SkSpan<const FunctionDeclaration* const> overloadSet) {
// Scan the parent symbol table for a matching symbol.
const Symbol* overloadedSymbol = fParent->lookup(writableSymbolTable, encounteredModuleBoundary,
key);
if (!overloadedSymbol) {
return symbol;
}
const FunctionDeclaration* scratchPtr;
SkSpan<const FunctionDeclaration* const> parentOverloadSet = get_overload_set(*overloadedSymbol,
scratchPtr);
if (parentOverloadSet.empty()) {
// The parent symbol wasn't function-related. Return the symbol we found.
return symbol;
}
// We've found two distinct overload sets; we need to combine them. Start with the initial set.
std::vector<const FunctionDeclaration*> combinedOverloadSet{overloadSet.begin(),
overloadSet.end()};
// Now combine in any unique overloads from the parent overload set.
for (const FunctionDeclaration* prev : parentOverloadSet) {
bool matchFound = false;
for (const FunctionDeclaration* current : combinedOverloadSet) {
if (current->matches(*prev)) {
matchFound = true;
break;
}
}
if (!matchFound) {
combinedOverloadSet.push_back(prev);
}
}
SkASSERT(combinedOverloadSet.size() >= overloadSet.size());
if (combinedOverloadSet.size() == overloadSet.size()) {
// We didn't add anything to the overload set. Our existing symbol is fine.
return symbol;
}
// Add this combined overload set to the symbol table.
SkASSERT(combinedOverloadSet.size() > 1);
SkASSERT(writableSymbolTable);
return writableSymbolTable->takeOwnershipOfSymbol(
std::make_unique<UnresolvedFunction>(std::move(combinedOverloadSet)));
}
const std::string* SymbolTable::takeOwnershipOfString(std::string str) {
fOwnedStrings.push_front(std::move(str));
// Because fOwnedStrings is a linked list, pointers to elements are stable.
return &fOwnedStrings.front();
}
void SymbolTable::addWithoutOwnership(const Symbol* symbol) {
const std::string_view& name = symbol->name();
const Symbol*& refInSymbolTable = fSymbols[MakeSymbolKey(name)];
if (refInSymbolTable == nullptr) {
refInSymbolTable = symbol;
return;
}
if (!symbol->is<FunctionDeclaration>()) {
fContext.fErrors->error(symbol->fPosition, "symbol '" + std::string(name) +
"' was already defined");
return;
}
std::vector<const FunctionDeclaration*> functions;
if (refInSymbolTable->is<FunctionDeclaration>()) {
functions = {&refInSymbolTable->as<FunctionDeclaration>(),
&symbol->as<FunctionDeclaration>()};
refInSymbolTable = this->takeOwnershipOfSymbol(
std::make_unique<UnresolvedFunction>(std::move(functions)));
} else if (refInSymbolTable->is<UnresolvedFunction>()) {
functions = refInSymbolTable->as<UnresolvedFunction>().functions();
functions.push_back(&symbol->as<FunctionDeclaration>());
refInSymbolTable = this->takeOwnershipOfSymbol(
std::make_unique<UnresolvedFunction>(std::move(functions)));
}
}
const Type* SymbolTable::addArrayDimension(const Type* type, int arraySize) {
if (arraySize == 0) {
return type;
}
// If this is a builtin type, we add it as high as possible in the symbol table tree (at the
// module boundary), to enable additional reuse of the array-type.
if (type->isInBuiltinTypes() && fParent && !fAtModuleBoundary) {
return fParent->addArrayDimension(type, arraySize);
}
// Reuse an existing array type with this name if one already exists in our symbol table.
std::string arrayName = type->getArrayName(arraySize);
if (const Symbol* existingType = (*this)[arrayName]) {
return &existingType->as<Type>();
}
// Add a new array type to the symbol table.
const std::string* arrayNamePtr = this->takeOwnershipOfString(std::move(arrayName));
return this->add(Type::MakeArrayType(*arrayNamePtr, *type, arraySize));
}
} // namespace SkSL