blob: 75b3d22a0ec9bbf29b3ea1b49678d753cc0699c9 [file] [log] [blame]
/*
* Copyright 2019 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/SkSLByteCodeGenerator.h"
namespace SkSL {
ByteCodeGenerator::ByteCodeGenerator(const Program* program, ErrorReporter* errors,
ByteCode* output)
: INHERITED(program, errors, nullptr)
, fOutput(output)
, fIntrinsics {
{ "cos", ByteCode::Instruction::kCos },
{ "dot", SpecialIntrinsic::kDot },
{ "inverse", SpecialIntrinsic::kInverse },
{ "print", ByteCode::Instruction::kPrint },
{ "sin", ByteCode::Instruction::kSin },
{ "sqrt", ByteCode::Instruction::kSqrt },
{ "tan", ByteCode::Instruction::kTan },
} {}
int ByteCodeGenerator::SlotCount(const Type& type) {
if (type.kind() == Type::kOther_Kind) {
return 0;
} else if (type.kind() == Type::kStruct_Kind) {
int slots = 0;
for (const auto& f : type.fields()) {
slots += SlotCount(*f.fType);
}
SkASSERT(slots <= 255);
return slots;
} else if (type.kind() == Type::kArray_Kind) {
int columns = type.columns();
SkASSERT(columns >= 0);
int slots = columns * SlotCount(type.componentType());
SkASSERT(slots <= 255);
return slots;
} else {
return type.columns() * type.rows();
}
}
static inline bool is_uniform(const SkSL::Variable& var) {
return var.fModifiers.fFlags & Modifiers::kUniform_Flag;
}
static inline bool is_in(const SkSL::Variable& var) {
return var.fModifiers.fFlags & Modifiers::kIn_Flag;
}
ByteCodeGenerator::Location ByteCodeGenerator::getLocation(const Variable& var) {
// given that we seldom have more than a couple of variables, linear search is probably the most
// efficient way to handle lookups
switch (var.fStorage) {
case Variable::kLocal_Storage: {
for (int i = fLocals.size() - 1; i >= 0; --i) {
if (fLocals[i] == &var) {
return ByteCode::Pointer{(uint16_t) (i + fParameterCount)};
}
}
int result = fLocals.size() + fParameterCount;
fLocals.push_back(&var);
for (int i = 0; i < SlotCount(var.fType) - 1; ++i) {
fLocals.push_back(nullptr);
}
SkASSERT(result <= ByteCode::kPointerMax);
return ByteCode::Pointer{(uint16_t) result};
}
case Variable::kParameter_Storage: {
int offset = 0;
for (const auto& p : fFunction->fDeclaration.fParameters) {
if (p == &var) {
SkASSERT(offset <= ByteCode::kPointerMax);
return ByteCode::Pointer{(uint16_t) offset};
}
offset += SlotCount(p->fType);
}
SkASSERT(false);
return ByteCode::Pointer{0};
}
case Variable::kGlobal_Storage: {
if (is_in(var)) {
// If you trip this assert, it means the program is using raw 'in' variables. You
// should either specialize the program (Compiler::specialize) to bake in the final
// values of the 'in' variables, or not use 'in' variables (maybe you meant to use
// 'uniform' instead?).
SkASSERT(false);
return ByteCode::Pointer{0};
}
bool isUniform = is_uniform(var);
int offset = isUniform ? fOutput->getGlobalSlotCount() : 0;
for (const auto& e : fProgram) {
if (e.fKind == ProgramElement::kVar_Kind) {
VarDeclarations& decl = (VarDeclarations&) e;
for (const auto& v : decl.fVars) {
const Variable* declVar = ((VarDeclaration&) *v).fVar;
if (declVar->fModifiers.fLayout.fBuiltin >= 0 || is_in(*declVar)) {
continue;
}
if (isUniform != is_uniform(*declVar)) {
continue;
}
if (declVar == &var) {
SkASSERT(offset <= ByteCode::kPointerMax);
return ByteCode::Pointer{(uint16_t) offset};
}
offset += SlotCount(declVar->fType);
}
}
}
SkASSERT(false);
return ByteCode::Pointer{0};
}
default:
SkASSERT(false);
return ByteCode::Pointer{0};
}
}
// A "simple" Swizzle is based on a variable (or a compound variable like a struct or array), and
// that references consecutive values, such that it can be implemented using normal load/store ops
// with an offset. Note that all single-component swizzles (of suitable base types) are simple.
static bool swizzle_is_simple(const Swizzle& s) {
switch (s.fBase->fKind) {
case Expression::kFieldAccess_Kind:
case Expression::kIndex_Kind:
case Expression::kVariableReference_Kind:
break;
default:
return false;
}
for (size_t i = 1; i < s.fComponents.size(); ++i) {
if (s.fComponents[i] != s.fComponents[i - 1] + 1) {
return false;
}
}
return true;
}
ByteCodeGenerator::Location ByteCodeGenerator::getLocation(const Expression& expr) {
switch (expr.fKind) {
case Expression::kFieldAccess_Kind: {
const FieldAccess& f = (const FieldAccess&) expr;
Location result = this->getLocation(*f.fBase);
int offset = 0;
for (int i = 0; i < f.fFieldIndex; ++i) {
offset += SlotCount(*f.fBase->fType.fields()[i].fType);
}
return result.offset(*this, offset);
}
case Expression::kIndex_Kind: {
const IndexExpression& idx = (const IndexExpression&) expr;
int stride = SlotCount(idx.fType);
int length = idx.fBase->fType.columns();
Location result = this->getLocation(*idx.fBase);
if (idx.fIndex->isConstant()) {
int64_t index = idx.fIndex->getConstantInt();
if (index < 0 || index >= length) {
fErrors.error(idx.fIndex->fOffset, "Array index out of bounds");
return result;
}
return result.offset(*this, index * stride);
} else {
ByteCode::Register index = this->next(1);
this->writeExpression(*idx.fIndex, index);
this->write(ByteCode::Instruction::kBoundsCheck);
this->write(index);
this->write(length);
ByteCode::Register imm = this->next(1);
this->write(ByteCode::Instruction::kImmediate);
this->write(imm);
this->write(ByteCode::Immediate{stride});
ByteCode::Register offset = this->next(1);
this->write(ByteCode::Instruction::kMultiplyI);
this->write(offset);
this->write(index);
this->write(imm);
return result.offset(*this, offset);
}
}
case Expression::kSwizzle_Kind: {
const Swizzle& s = (const Swizzle&) expr;
SkASSERT(swizzle_is_simple(s));
return this->getLocation(*s.fBase).offset(*this, s.fComponents[0]);
}
case Expression::kVariableReference_Kind: {
const Variable& var = ((const VariableReference&) expr).fVariable;
return this->getLocation(var);
}
default:
SkASSERT(false);
return ByteCode::Pointer{0};
}
}
Variable::Storage ByteCodeGenerator::getStorage(const Expression& expr) {
switch (expr.fKind) {
case Expression::kFieldAccess_Kind: {
const FieldAccess& f = (const FieldAccess&) expr;
return this->getStorage(*f.fBase);
}
case Expression::kIndex_Kind: {
const IndexExpression& idx = (const IndexExpression&) expr;
return this->getStorage(*idx.fBase);
}
case Expression::kSwizzle_Kind: {
const Swizzle& s = (const Swizzle&) expr;
return this->getStorage(*s.fBase);
}
case Expression::kVariableReference_Kind: {
const Variable& var = ((const VariableReference&) expr).fVariable;
return var.fStorage;
}
default:
SkASSERT(false);
return Variable::kLocal_Storage;
}
}
ByteCode::Instruction ByteCodeGenerator::getLoadInstruction(ByteCodeGenerator::Location location,
Variable::Storage storage) {
switch (storage) {
case Variable::kGlobal_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kLoadDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kLoad;
}
case Variable::kParameter_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kLoadParameterDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kLoadParameter;
}
case Variable::kLocal_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kLoadStackDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kLoadStack;
}
default:
break;
}
SkASSERT(false);
return ByteCode::Instruction::kNop;
}
ByteCode::Instruction ByteCodeGenerator::getStoreInstruction(ByteCodeGenerator::Location location,
Variable::Storage storage) {
switch (storage) {
case Variable::kGlobal_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kStoreDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kStore;
}
case Variable::kParameter_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kStoreParameterDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kStoreParameter;
}
case Variable::kLocal_Storage:
switch (location.fKind) {
case Location::kPointer_Kind: return ByteCode::Instruction::kStoreStackDirect;
case Location::kRegister_Kind: return ByteCode::Instruction::kStoreStack;
}
default:
break;
}
SkASSERT(false);
return ByteCode::Instruction::kNop;
}
class ByteCodeSimpleLValue : public ByteCodeGenerator::LValue {
public:
ByteCodeSimpleLValue(ByteCodeGenerator* generator, ByteCodeGenerator::Location location,
int count, ByteCode::Instruction load, ByteCode::Instruction store)
: INHERITED(*generator)
, fLocation(location)
, fCount(count)
, fLoad(load)
, fStore(store) {}
void load(ByteCode::Register result) override {
for (int i = 0; i < fCount; ++i) {
ByteCodeGenerator::Location final = fLocation.offset(fGenerator, i);
fGenerator.write(fLoad);
fGenerator.write(result + i);
fGenerator.write(final);
}
}
void store(ByteCode::Register src) override {
for (int i = 0; i < fCount; ++i) {
ByteCodeGenerator::Location final = fLocation.offset(fGenerator, i);
fGenerator.write(fStore);
fGenerator.write(final);
fGenerator.write(src + i);
}
}
private:
ByteCodeGenerator::Location fLocation;
int fCount;
ByteCode::Instruction fLoad;
ByteCode::Instruction fStore;
typedef ByteCodeGenerator::LValue INHERITED;
};
class ByteCodeSwizzleLValue : public ByteCodeGenerator::LValue {
public:
ByteCodeSwizzleLValue(ByteCodeGenerator* generator, const Swizzle* swizzle)
: INHERITED(*generator)
, fSwizzle(*swizzle) {}
void load(ByteCode::Register result) override {
fGenerator.writeSwizzle(fSwizzle, result);
}
void store(ByteCode::Register src) override {
ByteCodeGenerator::Location target = fGenerator.getLocation(*fSwizzle.fBase);
ByteCode::Instruction inst = fGenerator.getStoreInstruction(
target,
fGenerator.getStorage(*fSwizzle.fBase));
for (size_t i = 0; i < fSwizzle.fComponents.size(); ++i) {
ByteCodeGenerator::Location final = target.offset(fGenerator, fSwizzle.fComponents[i]);
fGenerator.write(inst);
fGenerator.write(final);
fGenerator.write(src + i);
}
}
private:
const Swizzle& fSwizzle;
typedef ByteCodeGenerator::LValue INHERITED;
};
class ByteCodeExternalValueLValue : public ByteCodeGenerator::LValue {
public:
ByteCodeExternalValueLValue(ByteCodeGenerator* generator, ExternalValue& value, int index)
: INHERITED(*generator)
, fIndex(index)
, fSlotCount(ByteCodeGenerator::SlotCount(value.type())) {
SkASSERT(fSlotCount <= 4);
}
void load(ByteCode::Register result) override {
fGenerator.write(ByteCode::Instruction::kReadExternal);
fGenerator.write(result);
fGenerator.write((uint8_t) fSlotCount);
fGenerator.write((uint8_t) fIndex);
}
void store(ByteCode::Register src) override {
fGenerator.write(ByteCode::Instruction::kWriteExternal);
fGenerator.write((uint8_t) fIndex);
fGenerator.write((uint8_t) fSlotCount);
fGenerator.write(src);
}
private:
typedef LValue INHERITED;
int fIndex;
int fSlotCount;
};
std::unique_ptr<ByteCodeGenerator::LValue> ByteCodeGenerator::getLValue(const Expression& expr) {
switch (expr.fKind) {
case Expression::kExternalValue_Kind: {
ExternalValue* value = ((ExternalValueReference&) expr).fValue;
int index = fOutput->fExternalValues.size();
fOutput->fExternalValues.push_back(value);
SkASSERT(index <= 255);
return std::unique_ptr<LValue>(new ByteCodeExternalValueLValue(this, *value, index));
}
case Expression::kFieldAccess_Kind:
case Expression::kIndex_Kind:
case Expression::kVariableReference_Kind: {
Location location = this->getLocation(expr);
Variable::Storage storage = this->getStorage(expr);
ByteCode::Instruction loadInst = this->getLoadInstruction(location, storage);
ByteCode::Instruction storeInst = this->getStoreInstruction(location, storage);
return std::unique_ptr<LValue>(new ByteCodeSimpleLValue(this, location,
SlotCount(expr.fType),
loadInst, storeInst));
}
case Expression::kSwizzle_Kind:
return std::unique_ptr<LValue>(new ByteCodeSwizzleLValue(this, &(Swizzle&) expr));
default:
ABORT("unsupported lvalue\n");
}
}
ByteCode::Register ByteCodeGenerator::next(int count) {
SkASSERT(fNextRegister + count <= ByteCode::kRegisterMax);
fNextRegister += count;
return ByteCode::Register{(uint16_t) (fNextRegister - count)};
}
static TypeCategory type_category(const Type& type) {
switch (type.kind()) {
case Type::Kind::kVector_Kind:
case Type::Kind::kMatrix_Kind:
return type_category(type.componentType());
default:
String name = type.displayName();
if (name == "bool") {
return TypeCategory::kBool;
} else if (name == "int" || name == "short") {
return TypeCategory::kSigned;
} else if (name == "uint" || name == "ushort") {
return TypeCategory::kUnsigned;
} else {
SkASSERT(name == "float" || name == "half");
return TypeCategory::kFloat;
}
ABORT("unsupported type: %s\n", name.c_str());
}
}
void ByteCodeGenerator::writeTypedInstruction(const Type& type, ByteCode::Instruction s,
ByteCode::Instruction u, ByteCode::Instruction f) {
switch (type_category(type)) {
case TypeCategory::kSigned:
this->write(s);
break;
case TypeCategory::kUnsigned:
this->write(u);
break;
case TypeCategory::kFloat: {
this->write(f);
break;
}
default:
SkASSERT(false);
}
}
void ByteCodeGenerator::writeBinaryInstruction(const Type& operandType,
ByteCode::Register left,
ByteCode::Register right,
ByteCode::Instruction s,
ByteCode::Instruction u,
ByteCode::Instruction f,
ByteCode::Register result) {
for (int i = 0; i < SlotCount(operandType); ++i) {
this->writeTypedInstruction(operandType, s, u, f);
this->write(result + i);
this->write(left + i);
this->write(right + i);
}
}
void ByteCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
ByteCode::Register result) {
if (b.fOperator == Token::Kind::EQ) {
std::unique_ptr<LValue> lvalue = this->getLValue(*b.fLeft);
this->writeExpression(*b.fRight, result);
lvalue->store(result);
return;
}
const Type& lType = b.fLeft->fType;
const Type& rType = b.fRight->fType;
bool lVecOrMtx = (lType.kind() == Type::kVector_Kind || lType.kind() == Type::kMatrix_Kind);
bool rVecOrMtx = (rType.kind() == Type::kVector_Kind || rType.kind() == Type::kMatrix_Kind);
const Type* operandType;
if (!lVecOrMtx && rVecOrMtx) {
operandType = &rType;
} else {
operandType = &lType;
}
Token::Kind op;
std::unique_ptr<LValue> lvalue;
ByteCode::Register left;
switch (b.fOperator) {
case Token::Kind::LOGICALAND:
case Token::Kind::LOGICALANDEQ:
case Token::Kind::LOGICALOR:
case Token::Kind::LOGICALOREQ:
left = result;
break;
default:
left = this->next(SlotCount(*operandType));
}
if (is_assignment(b.fOperator)) {
lvalue = this->getLValue(*b.fLeft);
lvalue->load(left);
op = remove_assignment(b.fOperator);
} else {
this->writeExpression(*b.fLeft, left);
op = b.fOperator;
if (!lVecOrMtx && rVecOrMtx) {
for (int i = 1; i < SlotCount(rType); ++i) {
this->write(ByteCode::Instruction::kCopy);
this->write(left + i);
this->write(left);
}
}
}
SkDEBUGCODE(TypeCategory tc = type_category(lType));
int count = std::max(SlotCount(lType), SlotCount(rType));
switch (op) {
case Token::Kind::LOGICALAND: {
SkASSERT(left.fIndex == result.fIndex);
this->write(ByteCode::Instruction::kMaskPush);
++fConditionCount;
this->write(left);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation falseLocation(this);
SkASSERT(SlotCount(b.fRight->fType) == 1);
ByteCode::Register right = this->next(1);
this->writeExpression(*b.fRight, right);
this->write(ByteCode::Instruction::kAnd);
this->write(result);
this->write(left);
this->write(right);
falseLocation.set();
--fConditionCount;
this->write(ByteCode::Instruction::kMaskPop);
return;
}
case Token::Kind::LOGICALOR: {
SkASSERT(left.fIndex == result.fIndex);
ByteCode::Register mask = this->next(1);
this->write(ByteCode::Instruction::kNot);
this->write(mask);
this->write(left);
this->write(ByteCode::Instruction::kMaskPush);
++fConditionCount;
this->write(mask);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation falseLocation(this);
SkASSERT(SlotCount(b.fRight->fType) == 1);
ByteCode::Register right = this->next(1);
this->writeExpression(*b.fRight, right);
this->write(ByteCode::Instruction::kOr);
this->write(result);
this->write(left);
this->write(right);
falseLocation.set();
--fConditionCount;
this->write(ByteCode::Instruction::kMaskPop);
return;
}
case Token::Kind::SHL:
case Token::Kind::SHR: {
SkASSERT(count == 1 && (tc == SkSL::TypeCategory::kSigned ||
tc == SkSL::TypeCategory::kUnsigned));
if (!b.fRight->isConstant()) {
fErrors.error(b.fRight->fOffset, "Shift amounts must be constant");
return;
}
int64_t shift = b.fRight->getConstantInt();
if (shift < 0 || shift > 31) {
fErrors.error(b.fRight->fOffset, "Shift amount out of range");
return;
}
if (op == Token::Kind::SHL) {
this->write(ByteCode::Instruction::kShiftLeft);
} else {
this->write(type_category(lType) == TypeCategory::kSigned
? ByteCode::Instruction::kShiftRightS
: ByteCode::Instruction::kShiftRightU);
}
this->write(result);
this->write(left);
this->write((uint8_t) shift);
return;
}
case Token::Kind::STAR:
// Special case for M*V, V*M, M*M (but not V*V!)
if (lType.columns() > 1 && rType.columns() > 1 &&
(lType.rows() > 1 || rType.rows() > 1)) {
ByteCode::Register right = this->next(SlotCount(rType));
this->writeExpression(*b.fRight, right);
int rCols = rType.columns(),
rRows = rType.rows(),
lCols = lType.columns(),
lRows = lType.rows();
// M*V treats the vector as a column
if (rType.kind() == Type::kVector_Kind) {
std::swap(rCols, rRows);
}
SkASSERT(lCols == rRows);
SkASSERT(SlotCount(b.fType) == lRows * rCols);
this->write(ByteCode::Instruction::kMatrixMultiply);
this->write(result);
this->write(left);
this->write(right);
this->write((uint8_t) lCols);
this->write((uint8_t) lRows);
this->write((uint8_t) rCols);
return;
}
default:
break;
}
ByteCode::Register right = this->next(SlotCount(*operandType));
this->writeExpression(*b.fRight, right);
if (lVecOrMtx && !rVecOrMtx) {
for (int i = 1; i < SlotCount(*operandType); ++i) {
this->write(ByteCode::Instruction::kCopy);
this->write(right + i);
this->write(right);
}
}
switch (op) {
case Token::Kind::EQEQ:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareEQI,
ByteCode::Instruction::kCompareEQI,
ByteCode::Instruction::kCompareEQF,
result);
// Collapse to a single bool
for (int i = 1; i < count; ++i) {
this->write(ByteCode::Instruction::kAnd);
this->write(result);
this->write(result);
this->write(result + i);
}
break;
case Token::Kind::GT:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareGTS,
ByteCode::Instruction::kCompareGTU,
ByteCode::Instruction::kCompareGTF,
result);
break;
case Token::Kind::GTEQ:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareGTEQS,
ByteCode::Instruction::kCompareGTEQU,
ByteCode::Instruction::kCompareGTEQF,
result);
break;
case Token::Kind::LT:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareLTS,
ByteCode::Instruction::kCompareLTU,
ByteCode::Instruction::kCompareLTF,
result);
break;
case Token::Kind::LTEQ:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareLTEQS,
ByteCode::Instruction::kCompareLTEQU,
ByteCode::Instruction::kCompareLTEQF,
result);
break;
case Token::Kind::MINUS:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kSubtractI,
ByteCode::Instruction::kSubtractI,
ByteCode::Instruction::kSubtractF,
result);
break;
case Token::Kind::NEQ:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kCompareNEQI,
ByteCode::Instruction::kCompareNEQI,
ByteCode::Instruction::kCompareNEQF,
result);
// Collapse to a single bool
for (int i = 1; i < count; ++i) {
this->write(ByteCode::Instruction::kOr);
this->write(result);
this->write(result);
this->write(result + i);
}
break;
case Token::Kind::PERCENT:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kRemainderS,
ByteCode::Instruction::kRemainderU,
ByteCode::Instruction::kRemainderF,
result);
break;
case Token::Kind::PLUS:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddF,
result);
break;
case Token::Kind::SLASH:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kDivideS,
ByteCode::Instruction::kDivideU,
ByteCode::Instruction::kDivideF,
result);
break;
case Token::Kind::STAR:
this->writeBinaryInstruction(*operandType, left, right,
ByteCode::Instruction::kMultiplyI,
ByteCode::Instruction::kMultiplyI,
ByteCode::Instruction::kMultiplyF,
result);
break;
case Token::Kind::LOGICALXOR: {
SkASSERT(tc == SkSL::TypeCategory::kBool);
this->write(ByteCode::Instruction::kXor);
this->write(result);
this->write(left);
this->write(right);
break;
}
case Token::Kind::BITWISEAND: {
SkASSERT(tc == SkSL::TypeCategory::kSigned || tc == SkSL::TypeCategory::kUnsigned);
this->write(ByteCode::Instruction::kAnd);
this->write(result);
this->write(left);
this->write(right);
break;
}
case Token::Kind::BITWISEOR: {
SkASSERT(tc == SkSL::TypeCategory::kSigned || tc == SkSL::TypeCategory::kUnsigned);
this->write(ByteCode::Instruction::kOr);
this->write(result);
this->write(left);
this->write(right);
break;
}
case Token::Kind::BITWISEXOR: {
SkASSERT(tc == SkSL::TypeCategory::kSigned || tc == SkSL::TypeCategory::kUnsigned);
this->write(ByteCode::Instruction::kXor);
this->write(result);
this->write(left);
this->write(right);
break;
}
default:
fErrors.error(b.fOffset, SkSL::String::printf("Unsupported binary operator '%s'",
Compiler::OperatorName(op)));
break;
}
if (lvalue) {
lvalue->store(result);
}
}
void ByteCodeGenerator::writeConstructor(const Constructor& c, ByteCode::Register result) {
if (c.fType.rows() > 1) {
if (c.fArguments.size() == 1) {
if (SlotCount(c.fArguments[0]->fType) == 1) {
ByteCode::Register v = this->next(1);
this->writeExpression(*c.fArguments[0], v);
this->write(ByteCode::Instruction::kScalarToMatrix);
this->write(result);
this->write(v);
this->write((uint8_t) c.fType.columns());
this->write((uint8_t) c.fType.rows());
return;
} else if (c.fArguments[0]->fType.rows() > 1) {
ByteCode::Register v = this->next(SlotCount(c.fArguments[0]->fType));
this->writeExpression(*c.fArguments[0], v);
this->write(ByteCode::Instruction::kMatrixToMatrix);
this->write(result);
this->write(v);
this->write((uint8_t) c.fArguments[0]->fType.columns());
this->write((uint8_t) c.fArguments[0]->fType.rows());
this->write((uint8_t) c.fType.columns());
this->write((uint8_t) c.fType.rows());
return;
}
}
int offset = 0;
for (const auto& arg : c.fArguments) {
this->writeExpression(*arg, ByteCode::Register{(uint16_t) (result.fIndex + offset)});
offset += SlotCount(arg->fType);
}
return;
}
if (c.fArguments.size() == 1 && c.fArguments[0]->fType.columns() == 1 &&
c.fType.columns() > 1) {
SkASSERT(SlotCount(c.fArguments[0]->fType) == 1);
ByteCode::Register v = result;
this->writeExpression(*c.fArguments[0], v);
for (int i = 1; i < c.fType.columns(); ++i) {
this->write(ByteCode::Instruction::kCopy);
this->write(v + i);
this->write(v);
}
return;
}
ByteCode::Instruction inst;
switch (type_category(c.fArguments[0]->fType)) {
case TypeCategory::kSigned:
if (type_category(c.fType) == TypeCategory::kFloat) {
inst = ByteCode::Instruction::kSignedToFloat;
} else {
inst = ByteCode::Instruction::kNop;
}
break;
case TypeCategory::kUnsigned:
if (type_category(c.fType) == TypeCategory::kFloat) {
inst = ByteCode::Instruction::kUnsignedToFloat;
} else {
inst = ByteCode::Instruction::kNop;
}
break;
case TypeCategory::kFloat:
if (type_category(c.fType) == TypeCategory::kSigned) {
inst = ByteCode::Instruction::kFloatToSigned;
} else if (type_category(c.fType) == TypeCategory::kUnsigned) {
inst = ByteCode::Instruction::kFloatToUnsigned;
} else {
inst = ByteCode::Instruction::kNop;
}
break;
default:
SkASSERT(false);
return;
}
ByteCode::Register values;
if (inst == ByteCode::Instruction::kNop) {
values = result;
} else {
values = this->next(SlotCount(c.fType));
}
ByteCode::Register v = values;
for (size_t i = 0; i < c.fArguments.size(); ++i) {
this->writeExpression(*c.fArguments[i], v);
v.fIndex += SlotCount(c.fArguments[i]->fType);
}
if (inst != ByteCode::Instruction::kNop) {
v = values;
ByteCode::Register target = result;
for (size_t i = 0; i < c.fArguments.size(); ++i) {
int count = SlotCount(c.fArguments[i]->fType);
for (int j = 0; j < count; ++j) {
this->write(inst);
this->write(target);
++target.fIndex;
this->write(v + j);
}
}
}
}
void ByteCodeGenerator::writeExternalFunctionCall(const ExternalFunctionCall& f,
ByteCode::Register result) {
int argumentCount = 0;
for (const auto& arg : f.fArguments) {
argumentCount += SlotCount(arg->fType);
}
ByteCode::Register args = this->next(argumentCount);
argumentCount = 0;
for (const auto& arg : f.fArguments) {
this->writeExpression(*arg, args + argumentCount);
argumentCount += SlotCount(arg->fType);
}
this->write(ByteCode::Instruction::kCallExternal);
this->write(result);
int index = fOutput->fExternalValues.size();
fOutput->fExternalValues.push_back(f.fFunction);
SkASSERT(index <= 255);
this->write((uint8_t) index);
SkASSERT(SlotCount(f.fType) <= 255);
this->write((uint8_t) SlotCount(f.fType));
this->write(args);
SkASSERT(argumentCount <= 255);
this->write((uint8_t) argumentCount);
}
void ByteCodeGenerator::writeExternalValue(const ExternalValueReference& e,
ByteCode::Register result) {
this->write(ByteCode::Instruction::kReadExternal);
this->write(result);
this->write((uint8_t) SlotCount(e.fValue->type()));
int index = fOutput->fExternalValues.size();
fOutput->fExternalValues.push_back(e.fValue);
SkASSERT(index <= 255);
this->write((uint8_t) index);
}
void ByteCodeGenerator::writeIntrinsicCall(const FunctionCall& c, Intrinsic intrinsic,
ByteCode::Register result) {
if (intrinsic.fIsSpecial) {
switch (intrinsic.fValue.fSpecial) {
case SpecialIntrinsic::kDot: {
SkASSERT(c.fArguments.size() == 2);
int count = SlotCount(c.fArguments[0]->fType);
ByteCode::Register left = this->next(count);
this->writeExpression(*c.fArguments[0], left);
ByteCode::Register right = this->next(count);
this->writeExpression(*c.fArguments[1], right);
ByteCode::Register product = this->next(count);
for (int i = 0; i < count; ++i) {
this->writeTypedInstruction(c.fType,
ByteCode::Instruction::kMultiplyI,
ByteCode::Instruction::kMultiplyI,
ByteCode::Instruction::kMultiplyF);
this->write(product + i);
this->write(left + i);
this->write(right + i);
}
ByteCode::Register total = product;
for (int i = 1; i < count; ++i) {
this->writeTypedInstruction(c.fType,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddF);
ByteCode::Register sum = i == count - 1 ? result : this->next(1);
this->write(sum);
this->write(total);
this->write(product + i);
total = sum;
}
break;
}
case SpecialIntrinsic::kInverse: {
SkASSERT(c.fArguments.size() == 1);
int count = SlotCount(c.fArguments[0]->fType);
ByteCode::Register arg = this->next(count);
this->writeExpression(*c.fArguments[0], arg);
switch (SlotCount(c.fArguments[0]->fType)) {
case 4: this->write(ByteCode::Instruction::kInverse2x2); break;
case 9: this->write(ByteCode::Instruction::kInverse3x3); break;
case 16: this->write(ByteCode::Instruction::kInverse4x4); break;
default: SkASSERT(false);
}
this->write(result);
this->write(arg);
break;
}
}
} else {
std::vector<ByteCode::Register> argRegs;
for (const auto& expr : c.fArguments) {
ByteCode::Register reg = this->next(SlotCount(expr->fType));
this->writeExpression(*expr, reg);
argRegs.push_back(reg);
}
this->write(intrinsic.fValue.fInstruction);
if (c.fType.fName != "void") {
this->write(result);
}
for (ByteCode::Register arg : argRegs) {
this->write(arg);
}
}
}
void ByteCodeGenerator::writeFunctionCall(const FunctionCall& c, ByteCode::Register result) {
auto found = fIntrinsics.find(c.fFunction.fName);
if (found != fIntrinsics.end()) {
return this->writeIntrinsicCall(c, found->second, result);
}
int argCount = c.fArguments.size();
std::vector<std::unique_ptr<LValue>> lvalues;
int parameterSlotCount = 0;
for (const auto& p : c.fFunction.fParameters) {
parameterSlotCount += SlotCount(p->fType);
}
ByteCode::Register argStart = this->next(parameterSlotCount);
ByteCode::Register nextArg = argStart;
for (int i = 0; i < argCount; ++i) {
const auto& param = c.fFunction.fParameters[i];
const auto& arg = c.fArguments[i];
if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
lvalues.emplace_back(this->getLValue(*arg));
lvalues.back()->load(nextArg);
} else {
this->writeExpression(*arg, nextArg);
}
nextArg.fIndex += SlotCount(arg->fType);
}
// Find the index of the function we're calling. We explicitly do not allow calls to functions
// before they're defined. This is an easy-to-understand rule that prevents recursion.
size_t idx;
for (idx = 0; idx < fFunctions.size(); ++idx) {
if (c.fFunction.matches(fFunctions[idx]->fDeclaration)) {
break;
}
}
if (idx > 255) {
fErrors.error(c.fOffset, "Function count limit exceeded");
return;
} else if (idx >= fOutput->fFunctions.size()) {
fErrors.error(c.fOffset, "Call to undefined function");
return;
}
this->write(ByteCode::Instruction::kCall);
this->write(result);
this->write((uint8_t) idx);
this->write(argStart);
nextArg = argStart;
auto lvalue = lvalues.begin();
for (int i = 0; i < argCount; ++i) {
const auto& param = c.fFunction.fParameters[i];
if (param->fModifiers.fFlags & Modifiers::kOut_Flag) {
(*(lvalue++))->store(nextArg);
}
nextArg.fIndex += SlotCount(param->fType);
}
}
void ByteCodeGenerator::incOrDec(Token::Kind op, Expression& operand, bool prefix,
ByteCode::Register result) {
SkASSERT(op == Token::Kind::PLUSPLUS || op == Token::Kind::MINUSMINUS);
std::unique_ptr<LValue> lvalue = this->getLValue(operand);
SkASSERT(SlotCount(operand.fType) == 1);
ByteCode::Register value;
if (prefix) {
value = this->next(1);
} else {
value = result;
}
lvalue->load(value);
ByteCode::Register one = this->next(1);
this->write(ByteCode::Instruction::kImmediate);
this->write(one);
if (type_category(operand.fType) == TypeCategory::kFloat) {
this->write(ByteCode::Immediate(1.0f));
} else {
this->write(ByteCode::Immediate((int32_t) 1));
}
if (op == Token::Kind::PLUSPLUS) {
this->writeTypedInstruction(operand.fType,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddI,
ByteCode::Instruction::kAddF);
} else {
this->writeTypedInstruction(operand.fType,
ByteCode::Instruction::kSubtractI,
ByteCode::Instruction::kSubtractI,
ByteCode::Instruction::kSubtractF);
}
if (prefix) {
this->write(result);
this->write(value);
this->write(one);
lvalue->store(result);
} else {
ByteCode::Register temp = this->next(1);
this->write(temp);
this->write(value);
this->write(one);
lvalue->store(temp);
}
}
void ByteCodeGenerator::writePostfixExpression(const PostfixExpression& p,
ByteCode::Register result) {
this->incOrDec(p.fOperator, *p.fOperand, false, result);
}
void ByteCodeGenerator::writePrefixExpression(const PrefixExpression& p,
ByteCode::Register result) {
switch (p.fOperator) {
case Token::Kind::PLUSPLUS:
case Token::Kind::MINUSMINUS: {
return this->incOrDec(p.fOperator, *p.fOperand, true, result);
}
case Token::Kind::MINUS: {
ByteCode::Register src = this->next(SlotCount(p.fType));
this->writeExpression(*p.fOperand, src);
for (int i = 0; i < SlotCount(p.fType); ++i) {
this->writeTypedInstruction(p.fType,
ByteCode::Instruction::kNegateS,
ByteCode::Instruction::kNegateS,
ByteCode::Instruction::kNegateF);
this->write(result + i);
this->write(src + i);
}
break;
}
case Token::Kind::LOGICALNOT:
case Token::Kind::BITWISENOT: {
ByteCode::Register src = this->next(SlotCount(p.fType));
this->writeExpression(*p.fOperand, src);
for (int i = 0; i < SlotCount(p.fType); ++i) {
this->write(ByteCode::Instruction::kNot);
this->write(result + i);
this->write(src + i);
}
break;
}
default:
SkASSERT(false);
}
}
void ByteCodeGenerator::writeSwizzle(const Swizzle& s, ByteCode::Register result) {
if (swizzle_is_simple(s)) {
this->writeVariableExpression(s, result);
return;
}
ByteCode::Register base = this->writeExpression(*s.fBase);
for (int i = 0; i < (int) s.fComponents.size(); ++i) {
this->write(ByteCode::Instruction::kCopy);
this->write(result + i);
this->write(base + s.fComponents[i]);
}
}
void ByteCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
ByteCode::Register result) {
int count = SlotCount(t.fType);
SkASSERT(count == SlotCount(t.fIfTrue->fType));
SkASSERT(count == SlotCount(t.fIfFalse->fType));
ByteCode::Register test = this->writeExpression(*t.fTest);
this->write(ByteCode::Instruction::kMaskPush);
++fConditionCount;
this->write(test);
ByteCode::Register ifTrue = this->writeExpression(*t.fIfTrue);
this->write(ByteCode::Instruction::kMaskNegate);
ByteCode::Register ifFalse = this->writeExpression(*t.fIfFalse);
--fConditionCount;
this->write(ByteCode::Instruction::kMaskPop);
for (int i = 0; i < count; ++i) {
this->write(ByteCode::Instruction::kSelect);
this->write(result + i);
this->write(test);
this->write(ifTrue + i);
this->write(ifFalse + i);
}
}
void ByteCodeGenerator::writeVariableExpression(const Expression& expr,
ByteCode::Register result) {
ByteCodeGenerator::Location location = this->getLocation(expr);
int count = SlotCount(expr.fType);
for (int i = 0; i < count; ++i) {
ByteCodeGenerator::Location final = location.offset(*this, i);
this->write(this->getLoadInstruction(location, this->getStorage(expr)));
this->write(result + i);
this->write(final);
}
}
void ByteCodeGenerator::writeExpression(const Expression& expr, ByteCode::Register result) {
switch (expr.fKind) {
case Expression::kBoolLiteral_Kind: {
this->write(ByteCode::Instruction::kImmediate);
this->write(result);
this->write(ByteCode::Immediate((int32_t) (((BoolLiteral&) expr).fValue ? -1 : 0)));
break;
}
case Expression::kBinary_Kind: {
this->writeBinaryExpression((BinaryExpression&) expr, result);
break;
}
case Expression::kConstructor_Kind: {
this->writeConstructor((Constructor&) expr, result);
break;
}
case Expression::kExternalFunctionCall_Kind:
this->writeExternalFunctionCall((ExternalFunctionCall&) expr, result);
break;
case Expression::kExternalValue_Kind:
this->writeExternalValue((ExternalValueReference&) expr, result);
break;
case Expression::kFloatLiteral_Kind: {
this->write(ByteCode::Instruction::kImmediate);
this->write(result);
this->write(ByteCode::Immediate((float) ((FloatLiteral&) expr).fValue));
break;
}
case Expression::kFunctionCall_Kind: {
this->writeFunctionCall((FunctionCall&) expr, result);
break;
}
case Expression::kIntLiteral_Kind: {
this->write(ByteCode::Instruction::kImmediate);
this->write(result);
this->write(ByteCode::Immediate((int32_t) ((IntLiteral&) expr).fValue));
break;
}
case Expression::kPostfix_Kind:
this->writePostfixExpression((PostfixExpression&) expr, result);
break;
case Expression::kPrefix_Kind:
this->writePrefixExpression((PrefixExpression&) expr, result);
break;
case Expression::kSwizzle_Kind:
this->writeSwizzle((Swizzle&) expr, result);
break;
case Expression::kTernary_Kind:
this->writeTernaryExpression((TernaryExpression&) expr, result);
break;
case Expression::kFieldAccess_Kind:
case Expression::kIndex_Kind:
case Expression::kVariableReference_Kind:
this->writeVariableExpression(expr, result);
break;
default:
#ifdef SK_DEBUG
ABORT("unsupported lvalue %s\n", expr.description().c_str());
#endif
break;
}
}
ByteCode::Register ByteCodeGenerator::writeExpression(const Expression& expr) {
ByteCode::Register result = this->next(SlotCount(expr.fType));
this->writeExpression(expr, result);
return result;
}
void ByteCodeGenerator::writeBlock(const Block& b) {
for (const auto& s : b.fStatements) {
this->writeStatement(*s);
}
}
void ByteCodeGenerator::writeDoStatement(const DoStatement& d) {
this->write(ByteCode::Instruction::kLoopBegin);
++fConditionCount;
SkASSERT(fCode->size() < ByteCode::kPointerMax);
ByteCode::Pointer start{(uint16_t) fCode->size()};
this->writeStatement(*d.fStatement);
ByteCode::Register test = this->writeExpression(*d.fTest);
this->write(ByteCode::Instruction::kLoopNext);
this->write(ByteCode::Instruction::kLoopMask);
this->write(test);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation endLocation(this);
this->write(ByteCode::Instruction::kBranch);
this->write(start);
endLocation.set();
--fConditionCount;
this->write(ByteCode::Instruction::kLoopEnd);
}
void ByteCodeGenerator::writeForStatement(const ForStatement& f) {
if (f.fInitializer) {
this->writeStatement(*f.fInitializer);
}
this->write(ByteCode::Instruction::kLoopBegin);
++fConditionCount;
ByteCode::Pointer start{(uint16_t) fCode->size()};
if (f.fTest) {
ByteCode::Register test = this->writeExpression(*f.fTest);
this->write(ByteCode::Instruction::kLoopMask);
this->write(test);
}
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation endLocation(this);
this->writeStatement(*f.fStatement);
this->write(ByteCode::Instruction::kLoopNext);
if (f.fNext) {
this->writeExpression(*f.fNext);
}
this->write(ByteCode::Instruction::kBranch);
this->write(start);
endLocation.set();
--fConditionCount;
this->write(ByteCode::Instruction::kLoopEnd);
}
void ByteCodeGenerator::writeIfStatement(const IfStatement& i) {
ByteCode::Register test = this->writeExpression(*i.fTest);
this->write(ByteCode::Instruction::kMaskPush);
++fConditionCount;
this->write(test);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation falseLocation(this);
this->writeStatement(*i.fIfTrue);
falseLocation.set();
if (i.fIfFalse) {
this->write(ByteCode::Instruction::kMaskNegate);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation endLocation(this);
this->writeStatement(*i.fIfFalse);
endLocation.set();
}
--fConditionCount;
this->write(ByteCode::Instruction::kMaskPop);
}
void ByteCodeGenerator::writeReturn(const ReturnStatement& r) {
if (fConditionCount) {
fErrors.error(r.fOffset, "return not allowed inside conditional or loop");
return;
}
if (r.fExpression) {
ByteCode::Register value = this->writeExpression(*r.fExpression);
this->write(ByteCode::Instruction::kReturnValue);
this->write(value);
}
else {
this->write(ByteCode::Instruction::kReturn);
}
}
void ByteCodeGenerator::writeVarDeclarations(const VarDeclarations& v) {
for (const auto& declStatement : v.fVars) {
const VarDeclaration& decl = (VarDeclaration&) *declStatement;
// we need to grab the location even if we don't use it, to ensure it
// has been allocated
ByteCodeGenerator::Location location = this->getLocation(*decl.fVar);
if (decl.fValue) {
ByteCode::Register src = this->writeExpression(*decl.fValue);
for (int i = 0; i < SlotCount(decl.fVar->fType); ++i) {
ByteCodeGenerator::Location final = location.offset(*this, i);
this->write(ByteCode::Instruction::kStoreStackDirect);
this->write(final);
this->write(src + i);
}
}
}
}
void ByteCodeGenerator::writeWhileStatement(const WhileStatement& w) {
this->write(ByteCode::Instruction::kLoopBegin);
++fConditionCount;
SkASSERT(fCode->size() < ByteCode::kPointerMax);
ByteCode::Pointer start{(uint16_t) fCode->size()};
ByteCode::Register test = this->writeExpression(*w.fTest);
this->write(ByteCode::Instruction::kLoopMask);
this->write(test);
this->write(ByteCode::Instruction::kBranchIfAllFalse);
DeferredLocation endLocation(this);
this->writeStatement(*w.fStatement);
this->write(ByteCode::Instruction::kLoopNext);
this->write(ByteCode::Instruction::kBranch);
this->write(start);
endLocation.set();
--fConditionCount;
this->write(ByteCode::Instruction::kLoopEnd);
}
void ByteCodeGenerator::writeStatement(const Statement& s) {
switch (s.fKind) {
case Statement::kBlock_Kind:
this->writeBlock((Block&) s);
break;
case Statement::kBreak_Kind:
this->write(ByteCode::Instruction::kBreak);
break;
case Statement::kContinue_Kind:
this->write(ByteCode::Instruction::kContinue);
break;
case Statement::kDo_Kind:
this->writeDoStatement((DoStatement&) s);
break;
case Statement::kExpression_Kind:
this->writeExpression(*((ExpressionStatement&) s).fExpression);
break;
case Statement::kFor_Kind:
this->writeForStatement((ForStatement&) s);
break;
case Statement::kIf_Kind:
this->writeIfStatement((IfStatement&) s);
break;
case Statement::kNop_Kind:
break;
case Statement::kReturn_Kind:
this->writeReturn((ReturnStatement&) s);
break;
case Statement::kVarDeclarations_Kind:
this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration);
break;
case Statement::kWhile_Kind:
this->writeWhileStatement((WhileStatement&) s);
break;
default:
ABORT("unsupported statement\n");
}
}
void ByteCodeGenerator::writeFunction(const FunctionDefinition& f) {
fFunction = &f;
std::unique_ptr<ByteCodeFunction> result(new ByteCodeFunction(&f.fDeclaration));
result->fReturnSlotCount = SlotCount(f.fDeclaration.fReturnType);
fParameterCount = 0;
fConditionCount = 0;
for (const auto& p : f.fDeclaration.fParameters) {
int count = SlotCount(p->fType);
bool isOut = ((p->fModifiers.fFlags & Modifiers::kOut_Flag) != 0);
result->fParameters.push_back(ByteCodeFunction::Parameter{count, isOut});
fParameterCount += count;
}
result->fParameterSlotCount = fParameterCount;
fCode = &result->fCode;
this->writeStatement(*f.fBody);
result->fStackSlotCount = fLocals.size();
if (f.fDeclaration.fReturnType.fName == "void") {
this->write(ByteCode::Instruction::kReturn);
} else {
this->write(ByteCode::Instruction::kAbort);
}
fOutput->fFunctions.push_back(std::move(result));
SkASSERT(fConditionCount == 0);
}
void ByteCodeGenerator::gatherUniforms(const Type& type, const String& name) {
if (type.kind() == Type::kOther_Kind) {
return;
} else if (type.kind() == Type::kStruct_Kind) {
for (const auto& f : type.fields()) {
this->gatherUniforms(*f.fType, name + "." + f.fName);
}
} else if (type.kind() == Type::kArray_Kind) {
for (int i = 0; i < type.columns(); ++i) {
this->gatherUniforms(type.componentType(), String::printf("%s[%d]", name.c_str(), i));
}
} else {
fOutput->fUniforms.push_back({ name, type_category(type), type.rows(), type.columns(),
fOutput->fUniformSlotCount });
fOutput->fUniformSlotCount += type.columns() * type.rows();
}
}
bool ByteCodeGenerator::generateCode() {
fOutput->fGlobalSlotCount = 0;
fOutput->fUniformSlotCount = 0;
for (const auto& pe : fProgram) {
if (pe.fKind == ProgramElement::kVar_Kind) {
VarDeclarations& decl = (VarDeclarations&) pe;
for (const auto& v : decl.fVars) {
const Variable* declVar = ((VarDeclaration&) *v).fVar;
if (declVar->fModifiers.fLayout.fBuiltin >= 0 || is_in(*declVar)) {
continue;
}
if (is_uniform(*declVar)) {
this->gatherUniforms(declVar->fType, declVar->fName);
} else {
fOutput->fGlobalSlotCount += SlotCount(declVar->fType);
}
}
}
}
for (const auto& pe : fProgram) {
if (pe.fKind == ProgramElement::kFunction_Kind) {
FunctionDefinition& f = (FunctionDefinition&) pe;
fFunctions.push_back(&f);
this->writeFunction(f);
}
}
return fErrors.errorCount() == 0;
}
} // namespace