blob: 6a639f15c30f7b0a6346127f873ee8cead54cf32 [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/sksl/ir/SkSLForStatement.h"
#include "include/core/SkTypes.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLDefines.h"
#include "src/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/analysis/SkSLProgramVisitor.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLNop.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
namespace SkSL {
static bool is_vardecl_block_initializer(const Statement* stmt) {
if (!stmt) {
return false;
}
if (!stmt->is<SkSL::Block>()) {
return false;
}
const SkSL::Block& b = stmt->as<SkSL::Block>();
if (b.isScope()) {
return false;
}
for (const auto& child : b.children()) {
if (!child->is<SkSL::VarDeclaration>()) {
return false;
}
}
return true;
}
static bool is_simple_initializer(const Statement* stmt) {
return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() ||
stmt->is<SkSL::ExpressionStatement>();
}
std::string ForStatement::description() const {
std::string result("for (");
if (this->initializer()) {
result += this->initializer()->description();
} else {
result += ";";
}
result += " ";
if (this->test()) {
result += this->test()->description();
}
result += "; ";
if (this->next()) {
result += this->next()->description();
}
result += ") " + this->statement()->description();
return result;
}
static void hoist_vardecl_symbols_into_outer_scope(const Context& context,
const Block& initBlock,
SymbolTable* innerSymbols,
SymbolTable* hoistedSymbols) {
class SymbolHoister : public ProgramVisitor {
public:
SymbolHoister(const Context& ctx, SymbolTable* innerSym, SymbolTable* hoistSym)
: fContext(ctx)
, fInnerSymbols(innerSym)
, fHoistedSymbols(hoistSym) {}
bool visitStatement(const Statement& stmt) override {
if (stmt.is<VarDeclaration>()) {
// Hoist the variable's symbol outside of the initializer block's symbol table, and
// into the outer symbol table. If the initializer's symbol table originally had
// ownership, transfer it. (If the variable was owned elsewhere, it can keep its
// current owner.)
Variable* var = stmt.as<VarDeclaration>().var();
fInnerSymbols->moveSymbolTo(fHoistedSymbols, var, fContext);
return false;
}
return ProgramVisitor::visitStatement(stmt);
}
const Context& fContext;
SymbolTable* fInnerSymbols;
SymbolTable* fHoistedSymbols;
};
SymbolHoister{context, innerSymbols, hoistedSymbols}.visitStatement(initBlock);
}
std::unique_ptr<Statement> ForStatement::Convert(const Context& context,
Position pos,
ForLoopPositions positions,
std::unique_ptr<Statement> initializer,
std::unique_ptr<Expression> test,
std::unique_ptr<Expression> next,
std::unique_ptr<Statement> statement,
std::unique_ptr<SymbolTable> symbolTable) {
bool isSimpleInitializer = is_simple_initializer(initializer.get());
bool isVardeclBlockInitializer = !isSimpleInitializer &&
is_vardecl_block_initializer(initializer.get());
if (!isSimpleInitializer && !isVardeclBlockInitializer) {
context.fErrors->error(initializer->fPosition, "invalid for loop initializer");
return nullptr;
}
if (test) {
test = context.fTypes.fBool->coerceExpression(std::move(test), context);
if (!test) {
return nullptr;
}
}
// The type of the next-expression doesn't matter, but it needs to be a complete expression.
// Report an error on intermediate expressions like FunctionReference or TypeReference.
if (next && next->isIncomplete(context)) {
return nullptr;
}
std::unique_ptr<LoopUnrollInfo> unrollInfo;
if (context.fConfig->strictES2Mode()) {
// In strict-ES2, loops must be unrollable or it's an error.
unrollInfo = Analysis::GetLoopUnrollInfo(context, pos, positions, initializer.get(), &test,
next.get(), statement.get(), context.fErrors);
if (!unrollInfo) {
return nullptr;
}
} else {
// In ES3, loops don't have to be unrollable, but we can use the unroll information for
// optimization purposes.
unrollInfo = Analysis::GetLoopUnrollInfo(context, pos, positions, initializer.get(), &test,
next.get(), statement.get(), /*errors=*/nullptr);
}
if (Analysis::DetectVarDeclarationWithoutScope(*statement, context.fErrors)) {
return nullptr;
}
if (isVardeclBlockInitializer) {
// If the initializer statement of a for loop contains multiple variables, this causes
// difficulties for several of our backends; e.g. Metal doesn't have a way to express arrays
// of different size in the same decl-stmt, because the array-size is part of the type. It's
// conceptually equivalent to synthesize a scope, declare the variables, and then emit a for
// statement with an empty init-stmt. (Note that we can't just do this transformation
// unilaterally for all for-statements, because the resulting for loop isn't ES2-compliant.)
std::unique_ptr<SymbolTable> hoistedSymbols = symbolTable->insertNewParent();
hoist_vardecl_symbols_into_outer_scope(context, initializer->as<Block>(),
symbolTable.get(), hoistedSymbols.get());
StatementArray scope;
scope.push_back(std::move(initializer));
scope.push_back(ForStatement::Make(context,
pos,
positions,
/*initializer=*/nullptr,
std::move(test),
std::move(next),
std::move(statement),
std::move(unrollInfo),
std::move(symbolTable)));
return Block::Make(pos,
std::move(scope),
Block::Kind::kBracedScope,
std::move(hoistedSymbols));
}
return ForStatement::Make(context,
pos,
positions,
std::move(initializer),
std::move(test),
std::move(next),
std::move(statement),
std::move(unrollInfo),
std::move(symbolTable));
}
std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context,
Position pos,
std::unique_ptr<Expression> test,
std::unique_ptr<Statement> statement) {
if (context.fConfig->strictES2Mode()) {
context.fErrors->error(pos, "while loops are not supported");
return nullptr;
}
return ForStatement::Convert(context,
pos,
ForLoopPositions(),
/*initializer=*/nullptr,
std::move(test),
/*next=*/nullptr,
std::move(statement),
/*symbolTable=*/nullptr);
}
std::unique_ptr<Statement> ForStatement::Make(const Context& context,
Position pos,
ForLoopPositions positions,
std::unique_ptr<Statement> initializer,
std::unique_ptr<Expression> test,
std::unique_ptr<Expression> next,
std::unique_ptr<Statement> statement,
std::unique_ptr<LoopUnrollInfo> unrollInfo,
std::unique_ptr<SymbolTable> symbolTable) {
SkASSERT(is_simple_initializer(initializer.get()) ||
is_vardecl_block_initializer(initializer.get()));
SkASSERT(!test || test->type().matches(*context.fTypes.fBool));
SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*statement));
SkASSERT(unrollInfo || !context.fConfig->strictES2Mode());
// Unrollable loops are easy to optimize because we know initializer, test and next don't have
// interesting side effects.
if (unrollInfo) {
// A zero-iteration unrollable loop can be replaced with Nop.
// An unrollable loop with an empty body can be replaced with Nop.
if (unrollInfo->fCount <= 0 || statement->isEmpty()) {
return Nop::Make();
}
}
return std::make_unique<ForStatement>(pos,
positions,
std::move(initializer),
std::move(test),
std::move(next),
std::move(statement),
std::move(unrollInfo),
std::move(symbolTable));
}
} // namespace SkSL