blob: 2111613b4b8440a5257b2145128af999c581fc32 [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/SkSLFunctionDefinition.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTypes.h"
#include "src/base/SkSafeMath.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLDefines.h"
#include "src/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLOperator.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLString.h"
#include "src/sksl/SkSLThreadContext.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLFieldSymbol.h"
#include "src/sksl/ir/SkSLIRHelpers.h"
#include "src/sksl/ir/SkSLNop.h"
#include "src/sksl/ir/SkSLReturnStatement.h"
#include "src/sksl/ir/SkSLSymbol.h"
#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include "src/sksl/transform/SkSLProgramWriter.h"
#include <algorithm>
#include <cstddef>
#include <forward_list>
#include <string_view>
#include <type_traits>
namespace SkSL {
static void append_rtadjust_fixup_to_vertex_main(const Context& context,
const FunctionDeclaration& decl,
Block& body) {
// If this program uses RTAdjust...
ThreadContext::RTAdjustData& rtAdjust = ThreadContext::RTAdjustState();
if (rtAdjust.fVar || rtAdjust.fInterfaceBlock) {
// ...append a line to the end of the function body which fixes up sk_Position.
struct AppendRTAdjustFixupHelper : public IRHelpers {
AppendRTAdjustFixupHelper(const Context& ctx, ThreadContext::RTAdjustData& rt)
: IRHelpers(ctx)
, fRTAdjust(rt) {
fSkPositionField = &fContext.fSymbolTable->find(Compiler::POSITION_NAME)
->as<FieldSymbol>();
}
std::unique_ptr<Expression> Pos() const {
return Field(&fSkPositionField->owner(), fSkPositionField->fieldIndex());
}
std::unique_ptr<Expression> Adjust() const {
return fRTAdjust.fInterfaceBlock
? Field(fRTAdjust.fInterfaceBlock, fRTAdjust.fFieldIndex)
: Ref(fRTAdjust.fVar);
}
std::unique_ptr<Statement> makeFixupStmt() const {
// sk_Position = float4(sk_Position.xy * rtAdjust.xz + sk_Position.ww * rtAdjust.yw,
// 0,
// sk_Position.w);
return Assign(
Pos(),
CtorXYZW(Add(Mul(Swizzle(Pos(), {SwizzleComponent::X, SwizzleComponent::Y}),
Swizzle(Adjust(), {SwizzleComponent::X, SwizzleComponent::Z})),
Mul(Swizzle(Pos(), {SwizzleComponent::W, SwizzleComponent::W}),
Swizzle(Adjust(), {SwizzleComponent::Y, SwizzleComponent::W}))),
Float(0.0),
Swizzle(Pos(), {SwizzleComponent::W})));
}
const FieldSymbol* fSkPositionField;
ThreadContext::RTAdjustData& fRTAdjust;
};
AppendRTAdjustFixupHelper helper(context, rtAdjust);
body.children().push_back(helper.makeFixupStmt());
}
}
std::unique_ptr<FunctionDefinition> FunctionDefinition::Convert(const Context& context,
Position pos,
const FunctionDeclaration& function,
std::unique_ptr<Statement> body,
bool builtin) {
class Finalizer : public ProgramWriter {
public:
Finalizer(const Context& context, const FunctionDeclaration& function, Position pos)
: fContext(context)
, fFunction(function) {
// Function parameters count as local variables.
for (const Variable* var : function.parameters()) {
this->addLocalVariable(var, pos);
}
}
~Finalizer() override {
SkASSERT(fBreakableLevel == 0);
SkASSERT(fContinuableLevel == std::forward_list<int>{0});
}
void addLocalVariable(const Variable* var, Position pos) {
if (var->type().isOrContainsUnsizedArray()) {
fContext.fErrors->error(pos, "unsized arrays are not permitted here");
return;
}
// We count the number of slots used, but don't consider the precision of the base type.
// In practice, this reflects what GPUs actually do pretty well. (i.e., RelaxedPrecision
// math doesn't mean your variable takes less space.) We also don't attempt to reclaim
// slots at the end of a Block.
size_t prevSlotsUsed = fSlotsUsed;
fSlotsUsed = SkSafeMath::Add(fSlotsUsed, var->type().slotCount());
// To avoid overzealous error reporting, only trigger the error at the first
// place where the stack limit is exceeded.
if (prevSlotsUsed < kVariableSlotLimit && fSlotsUsed >= kVariableSlotLimit) {
fContext.fErrors->error(pos, "variable '" + std::string(var->name()) +
"' exceeds the stack size limit");
}
}
void fuseVariableDeclarationsWithInitialization(std::unique_ptr<Statement>& stmt) {
switch (stmt->kind()) {
case Statement::Kind::kNop:
case Statement::Kind::kBlock:
// Blocks and no-ops are inert; it is safe to fuse a variable declaration with
// its initialization across a nop or an open-brace, so we don't null out
// `fUninitializedVarDecl` here.
break;
case Statement::Kind::kVarDeclaration:
// Look for variable declarations without an initializer.
if (VarDeclaration& decl = stmt->as<VarDeclaration>(); !decl.value()) {
fUninitializedVarDecl = &decl;
break;
}
[[fallthrough]];
default:
// We found an intervening statement; it's not safe to fuse a declaration
// with an initializer if we encounter any other code.
fUninitializedVarDecl = nullptr;
break;
case Statement::Kind::kExpression: {
// We found an expression-statement. If there was a variable declaration
// immediately above it, it might be possible to fuse them.
if (fUninitializedVarDecl) {
VarDeclaration* vardecl = fUninitializedVarDecl;
fUninitializedVarDecl = nullptr;
std::unique_ptr<Expression>& nextExpr = stmt->as<ExpressionStatement>()
.expression();
// This statement must be a binary-expression...
if (!nextExpr->is<BinaryExpression>()) {
break;
}
// ... performing simple `var = expr` assignment...
BinaryExpression& binaryExpr = nextExpr->as<BinaryExpression>();
if (binaryExpr.getOperator().kind() != OperatorKind::EQ) {
break;
}
// ... directly into the variable (not a field/swizzle)...
Expression& leftExpr = *binaryExpr.left();
if (!leftExpr.is<VariableReference>()) {
break;
}
// ... and it must be the same variable as our vardecl.
VariableReference& varRef = leftExpr.as<VariableReference>();
if (varRef.variable() != vardecl->var()) {
break;
}
// The init-expression must not reference the variable.
// `int x; x = x = 0;` is legal SkSL, but `int x = x = 0;` is not.
if (Analysis::ContainsVariable(*binaryExpr.right(), *varRef.variable())) {
break;
}
// We found a match! Move the init-expression directly onto the vardecl, and
// turn the assignment into a no-op.
vardecl->value() = std::move(binaryExpr.right());
// Turn the expression-statement into a no-op.
stmt = Nop::Make();
}
break;
}
}
}
bool functionReturnsValue() const {
return !fFunction.returnType().isVoid();
}
bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
// We don't need to scan expressions.
return false;
}
bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
// When the optimizer is on, we look for variable declarations that are immediately
// followed by an initialization expression, and fuse them into one statement.
// (e.g.: `int i; i = 1;` can become `int i = 1;`)
if (fContext.fConfig->fSettings.fOptimize) {
this->fuseVariableDeclarationsWithInitialization(stmt);
}
// Perform error checking.
switch (stmt->kind()) {
case Statement::Kind::kVarDeclaration:
this->addLocalVariable(stmt->as<VarDeclaration>().var(), stmt->fPosition);
break;
case Statement::Kind::kReturn: {
// Early returns from a vertex main() function will bypass sk_Position
// normalization, so SkASSERT that we aren't doing that. If this becomes an
// issue, we can add normalization before each return statement.
if (ProgramConfig::IsVertex(fContext.fConfig->fKind) && fFunction.isMain()) {
fContext.fErrors->error(
stmt->fPosition,
"early returns from vertex programs are not supported");
}
// Verify that the return statement matches the function's return type.
ReturnStatement& returnStmt = stmt->as<ReturnStatement>();
if (returnStmt.expression()) {
if (this->functionReturnsValue()) {
// Coerce return expression to the function's return type.
returnStmt.setExpression(fFunction.returnType().coerceExpression(
std::move(returnStmt.expression()), fContext));
} else {
// Returning something from a function with a void return type.
fContext.fErrors->error(returnStmt.expression()->fPosition,
"may not return a value from a void function");
returnStmt.setExpression(nullptr);
}
} else {
if (this->functionReturnsValue()) {
// Returning nothing from a function with a non-void return type.
fContext.fErrors->error(returnStmt.fPosition,
"expected function to return '" +
fFunction.returnType().displayName() + "'");
}
}
break;
}
case Statement::Kind::kDo:
case Statement::Kind::kFor: {
++fBreakableLevel;
++fContinuableLevel.front();
bool result = INHERITED::visitStatementPtr(stmt);
--fContinuableLevel.front();
--fBreakableLevel;
return result;
}
case Statement::Kind::kSwitch: {
++fBreakableLevel;
fContinuableLevel.push_front(0);
bool result = INHERITED::visitStatementPtr(stmt);
fContinuableLevel.pop_front();
--fBreakableLevel;
return result;
}
case Statement::Kind::kBreak:
if (fBreakableLevel == 0) {
fContext.fErrors->error(stmt->fPosition,
"break statement must be inside a loop or switch");
}
break;
case Statement::Kind::kContinue:
if (fContinuableLevel.front() == 0) {
if (std::any_of(fContinuableLevel.begin(),
fContinuableLevel.end(),
[](int level) { return level > 0; })) {
fContext.fErrors->error(stmt->fPosition,
"continue statement cannot be used in a switch");
} else {
fContext.fErrors->error(stmt->fPosition,
"continue statement must be inside a loop");
}
}
break;
default:
break;
}
return INHERITED::visitStatementPtr(stmt);
}
private:
const Context& fContext;
const FunctionDeclaration& fFunction;
// how deeply nested we are in breakable constructs (for, do, switch).
int fBreakableLevel = 0;
// number of slots consumed by all variables declared in the function
size_t fSlotsUsed = 0;
// how deeply nested we are in continuable constructs (for, do).
// We keep a stack (via a forward_list) in order to disallow continue inside of switch.
std::forward_list<int> fContinuableLevel{0};
// We track uninitialized variable declarations, and if they are immediately assigned-to,
// we can move the assignment directly into the decl.
VarDeclaration* fUninitializedVarDecl = nullptr;
using INHERITED = ProgramWriter;
};
// We don't allow modules to define actual functions with intrinsic names. (Those should be
// reserved for actual intrinsics.)
if (function.isIntrinsic()) {
context.fErrors->error(function.fPosition,
SkSL::String::printf("Intrinsic function '%.*s' should not have "
"a definition",
(int)function.name().size(),
function.name().data()));
return nullptr;
}
// A function can't have more than one definition.
if (function.definition()) {
context.fErrors->error(function.fPosition,
SkSL::String::printf("function '%s' was already defined",
function.description().c_str()));
return nullptr;
}
// Run the function finalizer. This checks for illegal constructs and missing return statements,
// and also performs some simple code cleanup.
Finalizer(context, function, pos).visitStatementPtr(body);
if (function.isMain() && ProgramConfig::IsVertex(context.fConfig->fKind)) {
append_rtadjust_fixup_to_vertex_main(context, function, body->as<Block>());
}
if (Analysis::CanExitWithoutReturningValue(function, *body)) {
context.fErrors->error(body->fPosition, "function '" + std::string(function.name()) +
"' can exit without returning a value");
}
return std::make_unique<FunctionDefinition>(pos, &function, builtin, std::move(body));
}
} // namespace SkSL