Major refactor of hecl parser using @hackyourlife architecture

This commit is contained in:
Jack Andersen 2017-11-20 23:33:28 -10:00
parent 3aae48d0bf
commit ddf7c983da
16 changed files with 980 additions and 1226 deletions

View File

@ -37,6 +37,11 @@ private:
return hecl::Format("vec3(%g,%g,%g)", vec.vec[0], vec.vec[1], vec.vec[2]);
}
std::string EmitVec3(const std::string& a, const std::string& b, const std::string& c) const
{
return hecl::Format("vec3(%s,%s,%s)", a.c_str(), b.c_str(), c.c_str());
}
std::string EmitTexGenSource2(TexGenSrc src, int uvIdx) const;
std::string EmitTexGenSource4(TexGenSrc src, int uvIdx) const;
};

View File

@ -33,6 +33,11 @@ private:
return hecl::Format("float3(%g,%g,%g)", vec.vec[0], vec.vec[1], vec.vec[2]);
}
std::string EmitVec3(const std::string& a, const std::string& b, const std::string& c) const
{
return hecl::Format("float3(%s,%s,%s)", a.c_str(), b.c_str(), c.c_str());
}
std::string EmitTexGenSource2(TexGenSrc src, int uvIdx) const;
std::string EmitTexGenSource4(TexGenSrc src, int uvIdx) const;
};

View File

@ -37,6 +37,11 @@ private:
return hecl::Format("float3(%g,%g,%g)", vec.vec[0], vec.vec[1], vec.vec[2]);
}
std::string EmitVec3(const std::string& a, const std::string& b, const std::string& c) const
{
return hecl::Format("float3(%s,%s,%s)", a.c_str(), b.c_str(), c.c_str());
}
std::string EmitTexGenSource2(TexGenSrc src, int uvIdx) const;
std::string EmitTexGenSource4(TexGenSrc src, int uvIdx) const;
};

View File

@ -103,6 +103,7 @@ private:
}
virtual std::string EmitVec3(const atVec4f& vec) const=0;
virtual std::string EmitVec3(const std::string& a, const std::string& b, const std::string& c) const=0;
std::string EmitVal(float val) const
{

View File

@ -9,11 +9,11 @@
#include <hecl/hecl.hpp>
#include <boo/graphicsdev/IGraphicsDataFactory.hpp>
namespace hecl
{
namespace Frontend
namespace hecl::Frontend
{
using namespace std::literals;
struct SourceLocation
{
int line = -1;
@ -32,85 +32,250 @@ public:
void reset(std::string_view name, std::string_view source) {m_name = name; m_source = source;}
void reset(std::string_view name) {m_name = name; m_source.clear();}
void setBackend(std::string_view backend) {m_backend = backend;}
void reportScannerErr(const SourceLocation& l, const char* format, ...);
void reportParserErr(const SourceLocation& l, const char* format, ...);
void reportLexerErr(const SourceLocation& l, const char* format, ...);
void reportCompileErr(const SourceLocation& l, const char* format, ...);
void reportBackendErr(const SourceLocation& l, const char* format, ...);
std::string_view getName() const {return m_name;}
std::string_view getSource() const {return m_source;}
};
class Parser
struct Token
{
public:
enum class TokenType
enum class Kind
{
None,
SourceBegin,
SourceEnd,
NumLiteral,
VectorSwizzle,
EvalGroupStart,
EvalGroupEnd,
FunctionStart,
FunctionEnd,
FunctionArgDelim,
ArithmeticOp,
Eof,
Lf,
Plus,
Minus,
Times,
Div,
Lpar,
Rpar,
Comma,
Period,
Ident,
Number
};
private:
static std::string_view KindToStr(Kind k)
{
switch (k)
{
case Kind::None: return "none"sv;
case Kind::Eof: return "eof"sv;
case Kind::Lf: return "lf"sv;
case Kind::Plus: return "+"sv;
case Kind::Minus: return "-"sv;
case Kind::Times: return "*"sv;
case Kind::Div: return "/"sv;
case Kind::Lpar: return "("sv;
case Kind::Rpar: return ")"sv;
case Kind::Comma: return ","sv;
case Kind::Period: return "."sv;
case Kind::Ident: return "ident"sv;
case Kind::Number: return "number"sv;
}
}
Kind kind = Kind::None;
std::string str;
SourceLocation loc;
Token() = default;
Token(Kind kind, const SourceLocation& loc)
: kind(kind), loc(loc) {}
Token(Kind kind, std::string&& str, const SourceLocation& loc)
: kind(kind), str(std::move(str)), loc(loc) {}
std::string toString() const
{
if (str.empty())
return hecl::Format("%d:%d: %s", loc.line, loc.col, KindToStr(kind).data());
else
return hecl::Format("%d:%d: %s (%s)", loc.line, loc.col, KindToStr(kind).data(), str.c_str());
}
};
class Scanner
{
friend class Parser;
static constexpr char LF = '\n';
static constexpr char COMMENT = '#';
Diagnostics& m_diag;
std::string_view m_source;
std::string_view::const_iterator m_sourceIt;
std::vector<TokenType> m_parenStack;
bool m_reset = false;
void skipWhitespace(std::string_view::const_iterator& it);
public:
struct Token
{
TokenType m_type;
SourceLocation m_location;
std::string m_tokenString;
int m_tokenInt = 0;
float m_tokenFloat = 0.0;
Token() : m_type(TokenType::None) {}
Token(TokenType type, SourceLocation loc) : m_type(type), m_location(loc) {}
const char* typeString() const
{
switch (m_type)
{
case TokenType::None:
return "None";
case TokenType::SourceBegin:
return "SourceBegin";
case TokenType::SourceEnd:
return "SourceEnd";
case TokenType::NumLiteral:
return "NumLiteral";
case TokenType::VectorSwizzle:
return "VectorSwizzle";
case TokenType::EvalGroupStart:
return "EvalGroupStart";
case TokenType::EvalGroupEnd:
return "EvalGroupEnd";
case TokenType::FunctionStart:
return "FunctionStart";
case TokenType::FunctionEnd:
return "FunctionEnd";
case TokenType::FunctionArgDelim:
return "FunctionArgDelim";
case TokenType::ArithmeticOp:
return "ArithmeticOp";
default: break;
}
return nullptr;
}
};
void reset(std::string_view source);
Token consumeToken();
SourceLocation getLocation() const;
char ch;
SourceLocation loc;
int lfcol;
Parser(Diagnostics& diag) : m_diag(diag) {}
std::string lastLine;
std::string currentLine;
Token::Kind CharToTokenKind(char ch)
{
switch (ch)
{
case '(': return Token::Kind::Lpar;
case ')': return Token::Kind::Rpar;
case ',': return Token::Kind::Comma;
case '.': return Token::Kind::Period;
case '+': return Token::Kind::Plus;
case '-': return Token::Kind::Minus;
case '*': return Token::Kind::Times;
case '/': return Token::Kind::Div;
default: return Token::Kind::None;
}
}
template <class... Args>
void error(const SourceLocation& loc, const char* s, Args&&... args)
{
m_diag.reportScannerErr(loc, s, args...);
}
static bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
static bool isStartIdent(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c == '_');
}
static bool isMidIdent(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c == '_') || isDigit(c);
}
int _read();
bool read();
static char chr(char c) { return (c < 32 || c > 127) ? '.' : c; }
public:
Scanner(Diagnostics& diag) : m_diag(diag) {}
void reset(std::string_view in)
{
m_source = in;
m_sourceIt = in.cbegin();
ch = 0;
loc.line = 1;
loc.col = 0;
lfcol = 0;
lastLine = std::string();
currentLine = std::string();
read();
}
Token next();
};
struct IRNode
{
friend struct IR;
enum class Op
{
Add, Sub, Mul, Div
};
enum class Kind
{
None, Call, Imm, Binop, Swizzle
};
Kind kind = Kind::None;
std::string str;
float val;
Op op;
std::unique_ptr<IRNode> left;
std::unique_ptr<IRNode> right;
std::list<IRNode> children;
SourceLocation loc;
static std::string_view OpToStr(Op op)
{
switch (op)
{
case Op::Add: return "+"sv;
case Op::Sub: return "-"sv;
case Op::Mul: return "*"sv;
case Op::Div: return "/"sv;
}
}
static std::string_view KindToStr(Kind k)
{
switch (k)
{
case Kind::None: return "none"sv;
case Kind::Call: return "call"sv;
case Kind::Imm: return "imm"sv;
case Kind::Binop: return "binop"sv;
case Kind::Swizzle: return "swizzle"sv;
}
}
IRNode() = default;
IRNode(Kind kind, std::string&& str, const SourceLocation& loc)
: kind(kind), str(std::move(str)), loc(loc) {}
IRNode(Kind kind, float val, const SourceLocation& loc)
: kind(kind), val(val), loc(loc) {}
IRNode(Kind kind, std::string&& str, std::list<IRNode>&& children, const SourceLocation& loc)
: kind(kind), str(std::move(str)), children(std::move(children)), loc(loc) {}
IRNode(Op op, IRNode&& left, IRNode&& right, const SourceLocation& loc)
: kind(Kind::Binop), op(op), left(new IRNode(std::move(left))), right(new IRNode(std::move(right))), loc(loc) {}
IRNode(Kind kind, std::string&& str, IRNode&& node, const SourceLocation& loc)
: kind(kind), str(std::move(str)), left(new IRNode(std::move(node))), loc(loc) {}
std::string toString() const { return fmt(0); }
private:
static std::string rep(int n, std::string_view s);
std::string fmt(int level) const;
std::string describe() const;
};
class Parser
{
Scanner m_scanner;
Token t;
Token la;
Token::Kind sym;
void scan()
{
t = la;
la = m_scanner.next();
sym = la.kind;
}
template <class... Args>
void error(const char* s, Args&&... args)
{
m_scanner.m_diag.reportParserErr(la.loc, s, args...);
}
void check(Token::Kind expected);
IRNode call();
static bool imm(const IRNode& a, const IRNode& b);
IRNode expr();
IRNode sum();
IRNode factor();
IRNode value();
public:
Parser(Diagnostics& diag)
: m_scanner(diag) {}
void reset(std::string_view in) { la = Token(); m_scanner.reset(in); }
std::list<IRNode> parse();
};
using BigDNA = athena::io::DNA<athena::BigEndian>;
@ -175,116 +340,14 @@ struct IR : BigDNA
Value<atUint16> m_instIdx;
} m_swizzle;
Instruction(OpType type, const SourceLocation& loc) : m_op(type), m_loc(loc) {}
int getChildCount() const
{
switch (m_op)
{
case OpType::Call:
return m_call.m_argInstIdxs.size();
case OpType::Arithmetic:
return 2;
case OpType::Swizzle:
return 1;
default:
LogModule.report(logvisor::Fatal, "invalid op type");
}
return -1;
}
const IR::Instruction& getChildInst(const IR& ir, size_t idx) const
{
switch (m_op)
{
case OpType::Call:
return ir.m_instructions.at(m_call.m_argInstIdxs.at(idx));
case OpType::Arithmetic:
if (idx > 1)
LogModule.report(logvisor::Fatal, "arithmetic child idx must be 0 or 1");
return ir.m_instructions.at(m_arithmetic.m_instIdxs[idx]);
case OpType::Swizzle:
if (idx > 0)
LogModule.report(logvisor::Fatal, "swizzle child idx must be 0");
return ir.m_instructions.at(m_swizzle.m_instIdx);
default:
LogModule.report(logvisor::Fatal, "invalid op type");
}
return *this;
}
const atVec4f& getImmVec() const
{
if (m_op != OpType::LoadImm)
LogModule.report(logvisor::Fatal, "invalid op type");
return m_loadImm.m_immVec;
}
void read(athena::io::IStreamReader& reader)
{
m_op = OpType(reader.readUByte());
m_target = reader.readUint16Big();
switch (m_op)
{
default: break;
case OpType::Call:
m_call.read(reader);
break;
case OpType::LoadImm:
m_loadImm.read(reader);
break;
case OpType::Arithmetic:
m_arithmetic.read(reader);
break;
case OpType::Swizzle:
m_swizzle.read(reader);
break;
}
}
void write(athena::io::IStreamWriter& writer) const
{
writer.writeUByte(m_op);
writer.writeUint16Big(m_target);
switch (m_op)
{
default: break;
case OpType::Call:
m_call.write(writer);
break;
case OpType::LoadImm:
m_loadImm.write(writer);
break;
case OpType::Arithmetic:
m_arithmetic.write(writer);
break;
case OpType::Swizzle:
m_swizzle.write(writer);
break;
}
}
size_t binarySize(size_t sz) const
{
sz += 3;
switch (m_op)
{
default: break;
case OpType::Call:
sz = m_call.binarySize(sz);
break;
case OpType::LoadImm:
sz = m_loadImm.binarySize(sz);
break;
case OpType::Arithmetic:
sz = m_arithmetic.binarySize(sz);
break;
case OpType::Swizzle:
sz = m_swizzle.binarySize(sz);
break;
}
return sz;
}
Instruction(OpType type, RegID target, const SourceLocation& loc)
: m_op(type), m_target(target), m_loc(loc) {}
int getChildCount() const;
const IR::Instruction& getChildInst(const IR& ir, size_t idx) const;
const atVec4f& getImmVec() const;
void read(athena::io::IStreamReader& reader);
void write(athena::io::IStreamWriter& writer) const;
size_t binarySize(size_t sz) const;
Instruction(athena::io::IStreamReader& reader) {read(reader);}
};
@ -297,122 +360,23 @@ struct IR : BigDNA
boo::BlendFactor m_blendDst = boo::BlendFactor::Zero;
bool m_doAlpha = false;
void read(athena::io::IStreamReader& reader)
{
m_hash = reader.readUint64Big();
m_regCount = reader.readUint16Big();
atUint16 instCount = reader.readUint16Big();
m_instructions.clear();
m_instructions.reserve(instCount);
for (atUint16 i=0 ; i<instCount ; ++i)
m_instructions.emplace_back(reader);
/* Pre-resolve blending mode */
const IR::Instruction& rootCall = m_instructions.back();
m_doAlpha = false;
if (!rootCall.m_call.m_name.compare("HECLOpaque"))
{
m_blendSrc = boo::BlendFactor::One;
m_blendDst = boo::BlendFactor::Zero;
}
else if (!rootCall.m_call.m_name.compare("HECLAlpha"))
{
m_blendSrc = boo::BlendFactor::SrcAlpha;
m_blendDst = boo::BlendFactor::InvSrcAlpha;
m_doAlpha = true;
}
else if (!rootCall.m_call.m_name.compare("HECLAdditive"))
{
m_blendSrc = boo::BlendFactor::SrcAlpha;
m_blendDst = boo::BlendFactor::One;
m_doAlpha = true;
}
}
void write(athena::io::IStreamWriter& writer) const
{
writer.writeUint64Big(m_hash);
writer.writeUint16Big(m_regCount);
writer.writeUint16Big(m_instructions.size());
for (const Instruction& inst : m_instructions)
inst.write(writer);
}
size_t binarySize(size_t sz) const
{
sz += 12;
for (const Instruction& inst : m_instructions)
sz = inst.binarySize(sz);
return sz;
}
};
class Lexer
{
friend class OperationNode;
Diagnostics& m_diag;
/* Intermediate tree-node for organizing tokens into operations */
struct OperationNode
{
Parser::Token m_tok;
OperationNode* m_prev = nullptr;
OperationNode* m_next = nullptr;
OperationNode* m_sub = nullptr;
OperationNode() {}
OperationNode(Parser::Token&& tok) : m_tok(std::move(tok)) {}
};
/* Pool of nodes to keep ownership (forward_list so pointers aren't invalidated) */
std::forward_list<OperationNode> m_pool;
/* Final lexed root function (IR comes from this) */
OperationNode* m_root = nullptr;
/* Helper for relinking operator precedence */
void ReconnectArithmetic(OperationNode* sn, OperationNode** lastSub, OperationNode** newSub) const;
/* Recursive IR compile funcs */
void RecursiveFuncCompile(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const;
void RecursiveGroupCompile(IR& ir, const Lexer::OperationNode* groupNode, IR::RegID target) const;
void EmitVec3(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const;
void EmitVec4(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const;
void EmitArithmetic(IR& ir, const Lexer::OperationNode* arithNode, IR::RegID target) const;
void EmitVectorSwizzle(IR& ir, const Lexer::OperationNode* swizNode, IR::RegID target) const;
static void PrintChain(const Lexer::OperationNode* begin, const Lexer::OperationNode* end);
static void PrintTree(const Lexer::OperationNode* node, int indent=0);
public:
void reset();
void consumeAllTokens(Parser& parser);
IR compileIR(atUint64 hash) const;
Lexer(Diagnostics& diag) : m_diag(diag) {}
static atInt8 swizzleCompIdx(char aChar);
int addInstruction(const IRNode& n, IR::RegID target);
void read(athena::io::IStreamReader& reader);
void write(athena::io::IStreamWriter& writer) const;
size_t binarySize(size_t sz) const;
};
class Frontend
{
Diagnostics m_diag;
Parser m_parser;
Lexer m_lexer;
public:
IR compileSource(std::string_view source, std::string_view diagName)
{
Hash hash(source);
m_diag.reset(diagName, source);
m_parser.reset(source);
m_lexer.consumeAllTokens(m_parser);
return m_lexer.compileIR(hash.val64());
}
IR compileSource(std::string_view source, std::string_view diagName);
Diagnostics& getDiagnostics() { return m_diag; }
Frontend() : m_parser(m_diag), m_lexer(m_diag) {}
Frontend() : m_parser(m_diag) {}
};
}
}
#endif // HECLFRONTEND_HPP

View File

@ -578,6 +578,8 @@ struct GLSLBackendFactory : IShaderBackendFactory
std::string vertSource = r.readString();
std::string fragSource = r.readString();
printf("%s\n%s\n", vertSource.c_str(), fragSource.c_str());
if (r.hasError())
return false;

View File

@ -83,7 +83,7 @@ unsigned GX::RecursiveTraceTexGen(const IR& ir, Diagnostics& diag, const IR::Ins
if (inst.getChildCount() < 1)
diag.reportBackendErr(inst.m_loc, "TexCoordGen UV(layerIdx) requires one argument");
const IR::Instruction& idxInst = inst.getChildInst(ir, 0);
const atVec4f& idxImm = idxInst.getImmVec();
auto& idxImm = idxInst.getImmVec();
return addTexCoordGen(diag, inst.m_loc, TexGenSrc(TG_TEX0 + unsigned(idxImm.vec[0])), mtx, normalize, pmtx);
}
else if (!tcgName.compare("Normal"))
@ -125,7 +125,7 @@ GX::TraceResult GX::RecursiveTraceColor(const IR& ir, Diagnostics& diag, const I
diag.reportBackendErr(inst.m_loc, "Texture(map, texgen) requires 2 arguments");
const IR::Instruction& mapInst = inst.getChildInst(ir, 0);
const atVec4f& mapImm = mapInst.getImmVec();
auto& mapImm = mapInst.getImmVec();
newStage.m_texMapIdx = unsigned(mapImm.vec[0]);
newStage.m_color[0] = swizzleAlpha ? CC_TEXA : CC_TEXC;

View File

@ -52,7 +52,7 @@ unsigned ProgrammableCommon::RecursiveTraceTexGen(const IR& ir, Diagnostics& dia
if (inst.getChildCount() < 1)
diag.reportBackendErr(inst.m_loc, "TexCoordGen UV(layerIdx) requires one argument");
const IR::Instruction& idxInst = inst.getChildInst(ir, 0);
const atVec4f& idxImm = idxInst.getImmVec();
auto& idxImm = idxInst.getImmVec();
return addTexCoordGen(TexGenSrc::UV, idxImm.vec[0], mtx, normalize);
}
else if (!tcgName.compare("Normal"))
@ -91,7 +91,7 @@ std::string ProgrammableCommon::RecursiveTraceColor(const IR& ir, Diagnostics& d
diag.reportBackendErr(inst.m_loc, "Texture(map, texgen) requires 2 arguments");
const IR::Instruction& mapInst = inst.getChildInst(ir, 0);
const atVec4f& mapImm = mapInst.getImmVec();
auto& mapImm = mapInst.getImmVec();
unsigned mapIdx = unsigned(mapImm.vec[0]);
const IR::Instruction& tcgInst = inst.getChildInst(ir, 1);
@ -111,6 +111,17 @@ std::string ProgrammableCommon::RecursiveTraceColor(const IR& ir, Diagnostics& d
m_lighting = true;
return toSwizzle ? EmitLightingRaw() : EmitLightingRGB();
}
else if (!name.compare("vec3"))
{
if (inst.getChildCount() < 3)
diag.reportBackendErr(inst.m_loc, "vec3(r,g,b) requires 3 arguments");
const IR::Instruction& aInst = inst.getChildInst(ir, 0);
const IR::Instruction& bInst = inst.getChildInst(ir, 1);
const IR::Instruction& cInst = inst.getChildInst(ir, 2);
return EmitVec3(RecursiveTraceAlpha(ir, diag, aInst, false),
RecursiveTraceAlpha(ir, diag, bInst, false),
RecursiveTraceAlpha(ir, diag, cInst, false));
}
else
diag.reportBackendErr(inst.m_loc, "unable to interpret '%s'", name.c_str());
break;

View File

@ -1,6 +1,7 @@
set(FRONTEND_SOURCES
Parser.cpp
Lexer.cpp
Diagnostics.cpp)
Scanner.cpp
Diagnostics.cpp
Frontend.cpp)
hecl_add_list(Frontend FRONTEND_SOURCES)

View File

@ -12,9 +12,7 @@
#define BOLD "\x1b[1m"
#define NORMAL "\x1b[0m"
namespace hecl
{
namespace Frontend
namespace hecl::Frontend
{
std::string Diagnostics::sourceDiagString(const SourceLocation& l, bool ansi) const
@ -43,6 +41,28 @@ std::string Diagnostics::sourceDiagString(const SourceLocation& l, bool ansi) co
return retval;
}
void Diagnostics::reportScannerErr(const SourceLocation& l, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char* result = nullptr;
#ifdef _WIN32
int length = _vscprintf(fmt, ap);
result = (char*)malloc(length);
vsnprintf(result, length, fmt, ap);
#else
vasprintf(&result, fmt, ap);
#endif
va_end(ap);
if (logvisor::XtermColor)
LogModule.report(logvisor::Fatal, CYAN "[Scanner]" NORMAL " %s " YELLOW "@%d:%d " NORMAL "\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, true).c_str());
else
LogModule.report(logvisor::Fatal, "[Scanner] %s @%d:%d\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, false).c_str());
free(result);
}
void Diagnostics::reportParserErr(const SourceLocation& l, const char* fmt, ...)
{
va_list ap;
@ -65,50 +85,6 @@ void Diagnostics::reportParserErr(const SourceLocation& l, const char* fmt, ...)
free(result);
}
void Diagnostics::reportLexerErr(const SourceLocation& l, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char* result = nullptr;
#ifdef _WIN32
int length = _vscprintf(fmt, ap);
result = (char*)malloc(length);
vsnprintf(result, length, fmt, ap);
#else
vasprintf(&result, fmt, ap);
#endif
va_end(ap);
if (logvisor::XtermColor)
LogModule.report(logvisor::Fatal, CYAN "[Lexer]" NORMAL " %s " YELLOW "@%d:%d " NORMAL "\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, true).c_str());
else
LogModule.report(logvisor::Fatal, "[Lexer] %s @%d:%d\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, false).c_str());
free(result);
}
void Diagnostics::reportCompileErr(const SourceLocation& l, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char* result = nullptr;
#ifdef _WIN32
int length = _vscprintf(fmt, ap);
result = (char*)malloc(length);
vsnprintf(result, length, fmt, ap);
#else
vasprintf(&result, fmt, ap);
#endif
va_end(ap);
if (logvisor::XtermColor)
LogModule.report(logvisor::Fatal, CYAN "[Compiler]" NORMAL " %s " YELLOW "@%d:%d " NORMAL "\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, true).c_str());
else
LogModule.report(logvisor::Fatal, "[Compiler] %s @%d:%d\n%s\n%s",
m_name.c_str(), l.line, l.col, result, sourceDiagString(l, false).c_str());
free(result);
}
void Diagnostics::reportBackendErr(const SourceLocation& l, const char* fmt, ...)
{
va_list ap;
@ -132,4 +108,3 @@ void Diagnostics::reportBackendErr(const SourceLocation& l, const char* fmt, ...
}
}
}

View File

@ -0,0 +1,294 @@
#include "hecl/Frontend.hpp"
namespace hecl::Frontend
{
int IR::Instruction::getChildCount() const
{
switch (m_op)
{
case OpType::Call:
return m_call.m_argInstIdxs.size();
case OpType::Arithmetic:
return 2;
case OpType::Swizzle:
return 1;
default:
LogModule.report(logvisor::Fatal, "invalid op type");
}
return -1;
}
const IR::Instruction& IR::Instruction::getChildInst(const IR& ir, size_t idx) const
{
switch (m_op)
{
case OpType::Call:
return ir.m_instructions.at(m_call.m_argInstIdxs.at(idx));
case OpType::Arithmetic:
if (idx > 1)
LogModule.report(logvisor::Fatal, "arithmetic child idx must be 0 or 1");
return ir.m_instructions.at(m_arithmetic.m_instIdxs[idx]);
case OpType::Swizzle:
if (idx > 0)
LogModule.report(logvisor::Fatal, "swizzle child idx must be 0");
return ir.m_instructions.at(m_swizzle.m_instIdx);
default:
LogModule.report(logvisor::Fatal, "invalid op type");
}
return *this;
}
const atVec4f& IR::Instruction::getImmVec() const
{
if (m_op != OpType::LoadImm)
LogModule.report(logvisor::Fatal, "invalid op type");
return m_loadImm.m_immVec;
}
void IR::Instruction::read(athena::io::IStreamReader& reader)
{
m_op = OpType(reader.readUByte());
m_target = reader.readUint16Big();
switch (m_op)
{
default: break;
case OpType::Call:
m_call.read(reader);
break;
case OpType::LoadImm:
m_loadImm.read(reader);
break;
case OpType::Arithmetic:
m_arithmetic.read(reader);
break;
case OpType::Swizzle:
m_swizzle.read(reader);
break;
}
}
void IR::Instruction::write(athena::io::IStreamWriter& writer) const
{
writer.writeUByte(m_op);
writer.writeUint16Big(m_target);
switch (m_op)
{
default: break;
case OpType::Call:
m_call.write(writer);
break;
case OpType::LoadImm:
m_loadImm.write(writer);
break;
case OpType::Arithmetic:
m_arithmetic.write(writer);
break;
case OpType::Swizzle:
m_swizzle.write(writer);
break;
}
}
size_t IR::Instruction::binarySize(size_t sz) const
{
sz += 3;
switch (m_op)
{
default: break;
case OpType::Call:
sz = m_call.binarySize(sz);
break;
case OpType::LoadImm:
sz = m_loadImm.binarySize(sz);
break;
case OpType::Arithmetic:
sz = m_arithmetic.binarySize(sz);
break;
case OpType::Swizzle:
sz = m_swizzle.binarySize(sz);
break;
}
return sz;
}
atInt8 IR::swizzleCompIdx(char aChar)
{
switch (aChar)
{
case 'x':
case 'r':
return 0;
case 'y':
case 'g':
return 1;
case 'z':
case 'b':
return 2;
case 'w':
case 'a':
return 3;
default:
break;
}
return -1;
}
int IR::addInstruction(const IRNode& n, IR::RegID target)
{
if (n.kind == IRNode::Kind::None)
return -1;
switch (n.kind)
{
case IRNode::Kind::Call:
{
if (!n.str.compare("vec3") && n.children.size() >= 3)
{
atVec4f vec = {};
auto it = n.children.cbegin();
int i;
for (i=0 ; i<3 ; ++i, ++it)
{
if (it->kind != IRNode::Kind::Imm)
break;
vec.vec[i] = it->val;
}
if (i == 3)
{
m_instructions.emplace_back(OpType::LoadImm, target, n.loc);
Instruction::LoadImm& inst = m_instructions.back().m_loadImm;
inst.m_immVec = vec;
return m_instructions.size() - 1;
}
}
else if (!n.str.compare("vec4") && n.children.size() >= 4)
{
atVec4f vec = {};
auto it = n.children.cbegin();
int i;
for (i=0 ; i<4 ; ++i, ++it)
{
if (it->kind != IRNode::Kind::Imm)
break;
vec.vec[i] = it->val;
}
if (i == 4)
{
m_instructions.emplace_back(OpType::LoadImm, target, n.loc);
Instruction::LoadImm& inst = m_instructions.back().m_loadImm;
inst.m_immVec = vec;
return m_instructions.size() - 1;
}
}
std::vector<atUint16> argInstIdxs;
argInstIdxs.reserve(n.children.size());
IR::RegID tgt = target;
for (auto& c : n.children)
argInstIdxs.push_back(addInstruction(c, tgt++));
m_instructions.emplace_back(OpType::Call, target, n.loc);
Instruction::Call& inst = m_instructions.back().m_call;
inst.m_name = n.str;
inst.m_argInstCount = atUint16(argInstIdxs.size());
inst.m_argInstIdxs = argInstIdxs;
return m_instructions.size() - 1;
}
case IRNode::Kind::Imm:
{
m_instructions.emplace_back(OpType::LoadImm, target, n.loc);
Instruction::LoadImm& inst = m_instructions.back().m_loadImm;
inst.m_immVec.vec[0] = n.val;
inst.m_immVec.vec[1] = n.val;
inst.m_immVec.vec[2] = n.val;
inst.m_immVec.vec[3] = n.val;
return m_instructions.size() - 1;
}
case IRNode::Kind::Binop:
{
atUint16 left = addInstruction(*n.left, target);
atUint16 right = addInstruction(*n.right, target + 1);
m_instructions.emplace_back(OpType::Arithmetic, target, n.loc);
Instruction::Arithmetic& inst = m_instructions.back().m_arithmetic;
inst.m_op = Instruction::ArithmeticOpType(int(n.op) + 1);
inst.m_instIdxs[0] = left;
inst.m_instIdxs[1] = right;
return m_instructions.size() - 1;
}
case IRNode::Kind::Swizzle:
{
atUint16 left = addInstruction(*n.left, target);
m_instructions.emplace_back(OpType::Swizzle, target, n.loc);
Instruction::Swizzle& inst = m_instructions.back().m_swizzle;
for (int i=0 ; i<n.str.size() && i<4 ; ++i)
inst.m_idxs[i] = swizzleCompIdx(n.str[i]);
inst.m_instIdx = left;
return m_instructions.size() - 1;
}
default:
return -1;
}
}
void IR::read(athena::io::IStreamReader& reader)
{
m_hash = reader.readUint64Big();
m_regCount = reader.readUint16Big();
atUint16 instCount = reader.readUint16Big();
m_instructions.clear();
m_instructions.reserve(instCount);
for (atUint16 i=0 ; i<instCount ; ++i)
m_instructions.emplace_back(reader);
/* Pre-resolve blending mode */
const IR::Instruction& rootCall = m_instructions.back();
m_doAlpha = false;
if (!rootCall.m_call.m_name.compare("HECLOpaque"))
{
m_blendSrc = boo::BlendFactor::One;
m_blendDst = boo::BlendFactor::Zero;
}
else if (!rootCall.m_call.m_name.compare("HECLAlpha"))
{
m_blendSrc = boo::BlendFactor::SrcAlpha;
m_blendDst = boo::BlendFactor::InvSrcAlpha;
m_doAlpha = true;
}
else if (!rootCall.m_call.m_name.compare("HECLAdditive"))
{
m_blendSrc = boo::BlendFactor::SrcAlpha;
m_blendDst = boo::BlendFactor::One;
m_doAlpha = true;
}
}
void IR::write(athena::io::IStreamWriter& writer) const
{
writer.writeUint64Big(m_hash);
writer.writeUint16Big(m_regCount);
writer.writeUint16Big(m_instructions.size());
for (const Instruction& inst : m_instructions)
inst.write(writer);
}
size_t IR::binarySize(size_t sz) const
{
sz += 12;
for (const Instruction& inst : m_instructions)
sz = inst.binarySize(sz);
return sz;
}
IR Frontend::compileSource(std::string_view source, std::string_view diagName)
{
Hash hash(source);
m_diag.reset(diagName, source);
m_parser.reset(source);
auto insts = m_parser.parse();
IR ir;
ir.m_hash = hash.val64();
for (auto& inst : insts)
ir.addInstruction(inst, 0);
return ir;
}
}

View File

@ -1,722 +0,0 @@
#include "hecl/hecl.hpp"
#include "hecl/Frontend.hpp"
/* Combined lexer and semantic analysis system */
namespace hecl
{
namespace Frontend
{
static IR::Instruction::ArithmeticOpType ArithType(int aChar)
{
switch (aChar)
{
case '+':
return IR::Instruction::ArithmeticOpType::Add;
case '-':
return IR::Instruction::ArithmeticOpType::Subtract;
case '*':
return IR::Instruction::ArithmeticOpType::Multiply;
case '/':
return IR::Instruction::ArithmeticOpType::Divide;
default:
return IR::Instruction::ArithmeticOpType::None;
}
}
void Lexer::ReconnectArithmetic(OperationNode* sn, OperationNode** lastSub, OperationNode** newSub) const
{
sn->m_sub = sn->m_prev;
sn->m_prev = nullptr;
sn->m_sub->m_prev = nullptr;
sn->m_sub->m_next = sn->m_next;
sn->m_next = sn->m_next->m_next;
sn->m_sub->m_next->m_prev = sn->m_sub;
sn->m_sub->m_next->m_next = nullptr;
if (*lastSub)
{
(*lastSub)->m_next = sn;
sn->m_prev = *lastSub;
}
*lastSub = sn;
if (!*newSub)
*newSub = sn;
}
void Lexer::PrintChain(const Lexer::OperationNode* begin, const Lexer::OperationNode* end)
{
for (const Lexer::OperationNode* n = begin ; n != end ; n = n->m_next)
{
printf("%3d %s %s\n", n->m_tok.m_location.col, n->m_tok.typeString(),
n->m_tok.m_tokenString.c_str());
}
}
void Lexer::PrintTree(const Lexer::OperationNode* node, int indent)
{
for (const Lexer::OperationNode* n = node ; n ; n = n->m_next)
{
for (int i=0 ; i<indent ; ++i)
printf(" ");
printf("%3d %s %s %c %g\n", n->m_tok.m_location.col, n->m_tok.typeString(),
n->m_tok.m_tokenString.c_str(), n->m_tok.m_tokenInt, n->m_tok.m_tokenFloat);
if (n->m_sub)
PrintTree(n->m_sub, indent + 1);
}
}
void Lexer::reset()
{
m_root = nullptr;
m_pool.clear();
}
void Lexer::consumeAllTokens(Parser& parser)
{
reset();
Parser::Token firstTok = parser.consumeToken();
if (firstTok.m_type != Parser::TokenType::SourceBegin)
{
m_diag.reportLexerErr(firstTok.m_location, "expected start token");
return;
}
m_pool.emplace_front(std::move(firstTok));
Lexer::OperationNode* firstNode = &m_pool.front();
Lexer::OperationNode* lastNode = firstNode;
/* Build linked-list of nodes parsed in-order */
{
std::vector<SourceLocation> funcStack;
std::vector<SourceLocation> groupStack;
while (lastNode->m_tok.m_type != Parser::TokenType::SourceEnd)
{
Parser::Token tok = parser.consumeToken();
switch (tok.m_type)
{
case Parser::TokenType::EvalGroupStart:
groupStack.push_back(tok.m_location);
break;
case Parser::TokenType::EvalGroupEnd:
if (groupStack.empty())
{
m_diag.reportLexerErr(tok.m_location, "unbalanced group detected");
return;
}
groupStack.pop_back();
break;
case Parser::TokenType::FunctionStart:
funcStack.push_back(tok.m_location);
break;
case Parser::TokenType::FunctionEnd:
if (funcStack.empty())
{
m_diag.reportLexerErr(tok.m_location, "unbalanced function detected");
return;
}
funcStack.pop_back();
break;
case Parser::TokenType::SourceEnd:
case Parser::TokenType::NumLiteral:
case Parser::TokenType::VectorSwizzle:
case Parser::TokenType::FunctionArgDelim:
case Parser::TokenType::ArithmeticOp:
break;
default:
m_diag.reportLexerErr(tok.m_location, "invalid token");
return;
}
m_pool.emplace_front(std::move(tok));
lastNode->m_next = &m_pool.front();
m_pool.front().m_prev = lastNode;
lastNode = &m_pool.front();
}
/* Ensure functions and groups are balanced */
if (funcStack.size())
{
m_diag.reportLexerErr(funcStack.back(), "unclosed function detected");
return;
}
if (groupStack.size())
{
m_diag.reportLexerErr(groupStack.back(), "unclosed group detected");
return;
}
}
/* Ensure first non-start node is a function */
if (firstNode->m_next->m_tok.m_type != Parser::TokenType::FunctionStart)
{
m_diag.reportLexerErr(firstNode->m_tok.m_location, "expected root function");
return;
}
/* Organize marked function args into implicit groups */
for (Lexer::OperationNode* n = firstNode ; n != lastNode ; n = n->m_next)
{
if (n->m_tok.m_type == Parser::TokenType::FunctionStart)
{
if (n->m_next->m_tok.m_type != Parser::TokenType::FunctionEnd)
{
if (n->m_next->m_tok.m_type == Parser::TokenType::FunctionArgDelim)
{
m_diag.reportLexerErr(n->m_next->m_tok.m_location, "empty function arg");
return;
}
m_pool.emplace_front(
Parser::Token(Parser::TokenType::EvalGroupStart, n->m_next->m_tok.m_location));
Lexer::OperationNode* grp = &m_pool.front();
grp->m_next = n->m_next;
grp->m_prev = n;
n->m_next->m_prev = grp;
n->m_next = grp;
}
}
else if (n->m_tok.m_type == Parser::TokenType::FunctionEnd)
{
if (n->m_prev->m_tok.m_type != Parser::TokenType::FunctionStart)
{
m_pool.emplace_front(
Parser::Token(Parser::TokenType::EvalGroupEnd, n->m_tok.m_location));
Lexer::OperationNode* grp = &m_pool.front();
grp->m_next = n;
grp->m_prev = n->m_prev;
n->m_prev->m_next = grp;
n->m_prev = grp;
}
}
else if (n->m_tok.m_type == Parser::TokenType::FunctionArgDelim)
{
if (n->m_next->m_tok.m_type == Parser::TokenType::FunctionArgDelim ||
n->m_next->m_tok.m_type == Parser::TokenType::FunctionEnd)
{
m_diag.reportLexerErr(n->m_next->m_tok.m_location, "empty function arg");
return;
}
m_pool.emplace_front(
Parser::Token(Parser::TokenType::EvalGroupEnd, n->m_tok.m_location));
Lexer::OperationNode* egrp = &m_pool.front();
m_pool.emplace_front(
Parser::Token(Parser::TokenType::EvalGroupStart, n->m_next->m_tok.m_location));
Lexer::OperationNode* sgrp = &m_pool.front();
egrp->m_next = sgrp;
sgrp->m_prev = egrp;
sgrp->m_next = n->m_next;
egrp->m_prev = n->m_prev;
n->m_next->m_prev = sgrp;
n->m_prev->m_next = egrp;
}
}
/* Organize marked groups into tree-hierarchy */
{
std::vector<Lexer::OperationNode*> groupStack;
for (Lexer::OperationNode* n = firstNode ; n != lastNode ; n = n->m_next)
{
if (n->m_tok.m_type == Parser::TokenType::EvalGroupStart)
groupStack.push_back(n);
else if (n->m_tok.m_type == Parser::TokenType::EvalGroupEnd)
{
Lexer::OperationNode* start = groupStack.back();
groupStack.pop_back();
if (n->m_prev == start)
{
m_diag.reportLexerErr(start->m_tok.m_location, "empty group");
return;
}
start->m_sub = start->m_next;
start->m_next = n->m_next;
if (n->m_next)
n->m_next->m_prev = start;
n->m_prev->m_next = nullptr;
}
}
}
/* Organize functions into tree-hierarchy */
for (Lexer::OperationNode& n : m_pool)
{
if (n.m_tok.m_type == Parser::TokenType::FunctionStart)
{
for (Lexer::OperationNode* sn = n.m_next ; sn ; sn = sn->m_next)
{
if (sn->m_tok.m_type == Parser::TokenType::FunctionEnd)
{
n.m_sub = n.m_next;
if (n.m_next == sn)
n.m_sub = nullptr;
n.m_next = sn->m_next;
if (sn->m_next)
sn->m_next->m_prev = &n;
if (n.m_sub)
n.m_sub->m_prev = nullptr;
if (sn->m_prev)
sn->m_prev->m_next = nullptr;
break;
}
}
}
}
/* Organize vector swizzles into tree-hierarchy */
for (Lexer::OperationNode& n : m_pool)
{
if (n.m_tok.m_type == Parser::TokenType::VectorSwizzle)
{
if (n.m_prev->m_tok.m_type != Parser::TokenType::FunctionStart)
{
m_diag.reportLexerErr(n.m_tok.m_location,
"vector swizzles may only follow functions");
return;
}
Lexer::OperationNode* func = n.m_prev;
n.m_sub = func;
n.m_prev = func->m_prev;
if (func->m_prev)
{
if (func->m_prev->m_sub == func)
func->m_prev->m_sub = &n;
else
func->m_prev->m_next = &n;
}
func->m_next = nullptr;
func->m_prev = nullptr;
}
}
/* Ensure evaluation groups have proper arithmetic usage */
for (Lexer::OperationNode& n : m_pool)
{
if (n.m_tok.m_type == Parser::TokenType::EvalGroupStart)
{
int idx = 0;
for (Lexer::OperationNode* sn = n.m_sub ; sn ; sn = sn->m_next, ++idx)
{
if ((sn->m_tok.m_type == Parser::TokenType::ArithmeticOp && !(idx & 1)) ||
(sn->m_tok.m_type != Parser::TokenType::ArithmeticOp && (idx & 1)))
{
m_diag.reportLexerErr(sn->m_tok.m_location, "improper arithmetic expression");
return;
}
}
}
}
/* Organize arithmetic usage into tree-hierarchy */
for (Lexer::OperationNode& n : m_pool)
{
if (n.m_tok.m_type == Parser::TokenType::EvalGroupStart)
{
Lexer::OperationNode* newSub = nullptr;
Lexer::OperationNode* lastSub = nullptr;
for (Lexer::OperationNode* sn = n.m_sub ; sn ; sn = sn->m_next)
{
if (sn->m_tok.m_type == Parser::TokenType::ArithmeticOp)
{
IR::Instruction::ArithmeticOpType op = ArithType(sn->m_tok.m_tokenInt);
if (op == IR::Instruction::ArithmeticOpType::Multiply ||
op == IR::Instruction::ArithmeticOpType::Divide)
ReconnectArithmetic(sn, &lastSub, &newSub);
}
}
for (Lexer::OperationNode* sn = n.m_sub ; sn ; sn = sn->m_next)
{
if (sn->m_tok.m_type == Parser::TokenType::ArithmeticOp)
{
IR::Instruction::ArithmeticOpType op = ArithType(sn->m_tok.m_tokenInt);
if (op == IR::Instruction::ArithmeticOpType::Add ||
op == IR::Instruction::ArithmeticOpType::Subtract)
ReconnectArithmetic(sn, &lastSub, &newSub);
}
}
if (newSub)
n.m_sub = newSub;
}
}
if (hecl::VerbosityLevel > 1)
{
printf("%s\n", m_diag.getSource().data());
PrintTree(firstNode);
printf("\n");
}
/* Done! */
m_root = firstNode->m_next;
}
void Lexer::EmitVec3(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const
{
/* Optimization case: if empty call, emit zero imm load */
const Lexer::OperationNode* gn = funcNode->m_sub;
if (!gn)
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, funcNode->m_tok.m_location);
return;
}
/* Optimization case: if all numeric literals, emit vector imm load */
bool opt = true;
const Parser::Token* imms[3];
for (int i=0 ; i<3 ; ++i)
{
if (!gn->m_sub || gn->m_sub->m_tok.m_type != Parser::TokenType::NumLiteral)
{
opt = false;
break;
}
imms[i] = &gn->m_sub->m_tok;
gn = gn->m_next;
}
if (opt)
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, funcNode->m_tok.m_location);
atVec4f& vec = ir.m_instructions.back().m_loadImm.m_immVec;
vec.vec[0] = imms[0]->m_tokenFloat;
vec.vec[1] = imms[1]->m_tokenFloat;
vec.vec[2] = imms[2]->m_tokenFloat;
vec.vec[3] = 1.0;
return;
}
/* Otherwise treat as normal function */
RecursiveFuncCompile(ir, funcNode, target);
}
void Lexer::EmitVec4(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const
{
/* Optimization case: if empty call, emit zero imm load */
const Lexer::OperationNode* gn = funcNode->m_sub;
if (!gn)
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, funcNode->m_tok.m_location);
return;
}
/* Optimization case: if all numeric literals, emit vector imm load */
bool opt = true;
const Parser::Token* imms[4];
for (int i=0 ; i<4 ; ++i)
{
if (!gn || !gn->m_sub || gn->m_sub->m_tok.m_type != Parser::TokenType::NumLiteral)
{
opt = false;
break;
}
imms[i] = &gn->m_sub->m_tok;
gn = gn->m_next;
}
if (opt)
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, funcNode->m_tok.m_location);
atVec4f& vec = ir.m_instructions.back().m_loadImm.m_immVec;
vec.vec[0] = imms[0]->m_tokenFloat;
vec.vec[1] = imms[1]->m_tokenFloat;
vec.vec[2] = imms[2]->m_tokenFloat;
vec.vec[3] = imms[3]->m_tokenFloat;
return;
}
/* Otherwise treat as normal function */
RecursiveFuncCompile(ir, funcNode, target);
}
void Lexer::EmitArithmetic(IR& ir, const Lexer::OperationNode* arithNode, IR::RegID target) const
{
/* Evaluate operands */
atVec4f* opt[2] = {nullptr};
size_t instCount = ir.m_instructions.size();
const Lexer::OperationNode* on = arithNode->m_sub;
IR::RegID tgt = target;
size_t argInsts[2];
for (int i=0 ; i<2 ; ++i, ++tgt)
{
const Parser::Token& tok = on->m_tok;
switch (tok.m_type)
{
case Parser::TokenType::FunctionStart:
if (!tok.m_tokenString.compare("vec3"))
EmitVec3(ir, on, tgt);
else if (!tok.m_tokenString.compare("vec4"))
EmitVec4(ir, on, tgt);
else
RecursiveFuncCompile(ir, on, tgt);
break;
case Parser::TokenType::EvalGroupStart:
RecursiveGroupCompile(ir, on, tgt);
break;
case Parser::TokenType::NumLiteral:
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, arithNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = tgt;
inst.m_loadImm.m_immVec.vec[0] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[1] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[2] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[3] = tok.m_tokenFloat;
break;
}
case Parser::TokenType::VectorSwizzle:
EmitVectorSwizzle(ir, on, tgt);
break;
default:
m_diag.reportCompileErr(tok.m_location, "invalid lexer node for IR");
break;
};
argInsts[i] = ir.m_instructions.size() - 1;
if (ir.m_instructions.back().m_op == IR::OpType::LoadImm)
opt[i] = &ir.m_instructions.back().m_loadImm.m_immVec;
on = on->m_next;
}
/* Optimization case: if both operands imm load, pre-evalulate */
if (opt[0] && opt[1] && (ir.m_instructions.size() - instCount == 2))
{
atVec4f eval;
switch (ArithType(arithNode->m_tok.m_tokenInt))
{
case IR::Instruction::ArithmeticOpType::Add:
eval.vec[0] = opt[0]->vec[0] + opt[1]->vec[0];
eval.vec[1] = opt[0]->vec[1] + opt[1]->vec[1];
eval.vec[2] = opt[0]->vec[2] + opt[1]->vec[2];
eval.vec[3] = opt[0]->vec[3] + opt[1]->vec[3];
break;
case IR::Instruction::ArithmeticOpType::Subtract:
eval.vec[0] = opt[0]->vec[0] - opt[1]->vec[0];
eval.vec[1] = opt[0]->vec[1] - opt[1]->vec[1];
eval.vec[2] = opt[0]->vec[2] - opt[1]->vec[2];
eval.vec[3] = opt[0]->vec[3] - opt[1]->vec[3];
break;
case IR::Instruction::ArithmeticOpType::Multiply:
eval.vec[0] = opt[0]->vec[0] * opt[1]->vec[0];
eval.vec[1] = opt[0]->vec[1] * opt[1]->vec[1];
eval.vec[2] = opt[0]->vec[2] * opt[1]->vec[2];
eval.vec[3] = opt[0]->vec[3] * opt[1]->vec[3];
break;
case IR::Instruction::ArithmeticOpType::Divide:
eval.vec[0] = opt[0]->vec[0] / opt[1]->vec[0];
eval.vec[1] = opt[0]->vec[1] / opt[1]->vec[1];
eval.vec[2] = opt[0]->vec[2] / opt[1]->vec[2];
eval.vec[3] = opt[0]->vec[3] / opt[1]->vec[3];
break;
default:
m_diag.reportCompileErr(arithNode->m_tok.m_location, "invalid arithmetic type");
break;
}
ir.m_instructions.pop_back();
ir.m_instructions.pop_back();
ir.m_instructions.emplace_back(IR::OpType::LoadImm, arithNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = target;
inst.m_loadImm.m_immVec = eval;
}
else
{
ir.m_instructions.emplace_back(IR::OpType::Arithmetic, arithNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = target;
inst.m_arithmetic.m_instIdxs[0] = argInsts[0];
inst.m_arithmetic.m_instIdxs[1] = argInsts[1];
inst.m_arithmetic.m_op = ArithType(arithNode->m_tok.m_tokenInt);
if (tgt > ir.m_regCount)
ir.m_regCount = tgt;
}
}
static int SwizzleCompIdx(char aChar, Diagnostics& diag, const SourceLocation& loc)
{
switch (aChar)
{
case 'x':
case 'r':
return 0;
case 'y':
case 'g':
return 1;
case 'z':
case 'b':
return 2;
case 'w':
case 'a':
return 3;
default:
diag.reportCompileErr(loc, "invalid swizzle char %c", aChar);
}
return -1;
}
void Lexer::EmitVectorSwizzle(IR& ir, const Lexer::OperationNode* swizNode, IR::RegID target) const
{
const std::string& str = swizNode->m_tok.m_tokenString;
if (str.size() != 1 && str.size() != 3 && str.size() != 4)
m_diag.reportCompileErr(swizNode->m_tok.m_location, "%d component swizzles not supported", int(str.size()));
size_t instCount = ir.m_instructions.size();
const Lexer::OperationNode* on = swizNode->m_sub;
const Parser::Token& tok = on->m_tok;
switch (tok.m_type)
{
case Parser::TokenType::FunctionStart:
if (!tok.m_tokenString.compare("vec3"))
EmitVec3(ir, on, target);
else if (!tok.m_tokenString.compare("vec4"))
EmitVec4(ir, on, target);
else
RecursiveFuncCompile(ir, on, target);
break;
case Parser::TokenType::EvalGroupStart:
RecursiveGroupCompile(ir, on, target);
break;
case Parser::TokenType::NumLiteral:
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, swizNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = target;
inst.m_loadImm.m_immVec.vec[0] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[1] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[2] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[3] = tok.m_tokenFloat;
break;
}
case Parser::TokenType::VectorSwizzle:
EmitVectorSwizzle(ir, on, target);
break;
default:
m_diag.reportCompileErr(tok.m_location, "invalid lexer node for IR");
break;
};
/* Optimization case: if operand imm load, pre-evalulate */
if (ir.m_instructions.back().m_op == IR::OpType::LoadImm && (ir.m_instructions.size() - instCount == 1))
{
atVec4f* opt = &ir.m_instructions.back().m_loadImm.m_immVec;
const SourceLocation& loc = ir.m_instructions.back().m_loc;
atVec4f eval = {};
switch (str.size())
{
case 1:
eval.vec[0] = opt->vec[SwizzleCompIdx(str[0], m_diag, loc)];
eval.vec[1] = eval.vec[0];
eval.vec[2] = eval.vec[0];
eval.vec[3] = eval.vec[0];
break;
case 3:
eval.vec[0] = opt->vec[SwizzleCompIdx(str[0], m_diag, loc)];
eval.vec[1] = opt->vec[SwizzleCompIdx(str[1], m_diag, loc)];
eval.vec[2] = opt->vec[SwizzleCompIdx(str[2], m_diag, loc)];
eval.vec[3] = 1.0;
break;
case 4:
eval.vec[0] = opt->vec[SwizzleCompIdx(str[0], m_diag, loc)];
eval.vec[1] = opt->vec[SwizzleCompIdx(str[1], m_diag, loc)];
eval.vec[2] = opt->vec[SwizzleCompIdx(str[2], m_diag, loc)];
eval.vec[3] = opt->vec[SwizzleCompIdx(str[3], m_diag, loc)];
break;
default:
break;
}
ir.m_instructions.pop_back();
ir.m_instructions.emplace_back(IR::OpType::LoadImm, swizNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = target;
inst.m_loadImm.m_immVec = eval;
}
else
{
ir.m_instructions.emplace_back(IR::OpType::Swizzle, swizNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_swizzle.m_instIdx = ir.m_instructions.size() - 2;
inst.m_target = target;
for (int i=0 ; i<str.size() ; ++i)
inst.m_swizzle.m_idxs[i] = SwizzleCompIdx(str[i], m_diag, swizNode->m_tok.m_location);
}
}
void Lexer::RecursiveGroupCompile(IR& ir, const Lexer::OperationNode* groupNode, IR::RegID target) const
{
IR::RegID tgt = target;
for (const Lexer::OperationNode* sn = groupNode->m_sub ; sn ; sn = sn->m_next, ++tgt)
{
const Parser::Token& tok = sn->m_tok;
switch (tok.m_type)
{
case Parser::TokenType::FunctionStart:
if (!tok.m_tokenString.compare("vec3"))
EmitVec3(ir, sn, tgt);
else if (!tok.m_tokenString.compare("vec4"))
EmitVec4(ir, sn, tgt);
else
RecursiveFuncCompile(ir, sn, tgt);
break;
case Parser::TokenType::EvalGroupStart:
RecursiveGroupCompile(ir, sn, tgt);
break;
case Parser::TokenType::NumLiteral:
{
ir.m_instructions.emplace_back(IR::OpType::LoadImm, tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_target = tgt;
inst.m_loadImm.m_immVec.vec[0] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[1] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[2] = tok.m_tokenFloat;
inst.m_loadImm.m_immVec.vec[3] = tok.m_tokenFloat;
break;
}
case Parser::TokenType::ArithmeticOp:
EmitArithmetic(ir, sn, tgt);
break;
case Parser::TokenType::VectorSwizzle:
EmitVectorSwizzle(ir, sn, tgt);
break;
default:
m_diag.reportCompileErr(tok.m_location, "invalid lexer node for IR");
break;
};
}
if (tgt > ir.m_regCount)
ir.m_regCount = tgt;
}
void Lexer::RecursiveFuncCompile(IR& ir, const Lexer::OperationNode* funcNode, IR::RegID target) const
{
IR::RegID tgt = target;
std::vector<atUint16> instIdxs;
for (const Lexer::OperationNode* gn = funcNode->m_sub ; gn ; gn = gn->m_next, ++tgt)
{
RecursiveGroupCompile(ir, gn, tgt);
instIdxs.push_back(ir.m_instructions.size() - 1);
}
ir.m_instructions.emplace_back(IR::OpType::Call, funcNode->m_tok.m_location);
IR::Instruction& inst = ir.m_instructions.back();
inst.m_call.m_name = funcNode->m_tok.m_tokenString;
inst.m_call.m_argInstCount = instIdxs.size();
inst.m_call.m_argInstIdxs = std::move(instIdxs);
inst.m_target = target;
if (tgt > ir.m_regCount)
ir.m_regCount = tgt;
}
IR Lexer::compileIR(atUint64 hash) const
{
if (!m_root)
m_diag.reportCompileErr(SourceLocation(), "unable to compile HECL-IR for invalid source");
IR ir;
ir.m_hash = hash;
RecursiveFuncCompile(ir, m_root, 0);
return ir;
}
}
}

View File

@ -3,191 +3,277 @@
/* Syntatical token parsing system */
namespace hecl
{
namespace Frontend
namespace hecl::Frontend
{
void Parser::skipWhitespace(std::string_view::const_iterator& it)
{
while (it != m_source.cend())
{
while (it != m_source.cend() && isspace(*it))
++it;
/*
* hecl = { lf } call { lf { lf } call } { lf } .
* call = ident "(" [ expr { "," expr } ] ")" .
* expr = sum { ("+" | "-") sum } .
* sum = factor { ("*" | "/") factor } .
* factor = value | "(" expr ")" .
* value = ( call [ "." ident ] )
* | [ "-" ] number
* .
*/
/* Skip comment line */
if (it != m_source.cend() && *it == '#')
std::string IRNode::rep(int n, std::string_view s)
{
while (it != m_source.cend() && *it != '\n')
++it;
if (it != m_source.cend() && *it == '\n')
++it;
continue;
std::string buf;
buf.reserve(n * s.size());
for (int i = 0; i < n; i++)
buf.append(s);
return buf;
}
std::string IRNode::fmt(int level) const
{
std::string buf;
auto indent = rep(level, "\t"sv);
switch (kind)
{
case Kind::Call:
buf.append(indent);
buf.append("Call "sv).append(str);
if (!children.empty())
{
buf.append(" {\n"sv);
for (const IRNode& n : children)
{
buf.append(n.fmt(level + 1));
buf.append("\n"sv);
}
buf.append(indent);
buf.append("}"sv);
}
break;
}
}
void Parser::reset(std::string_view source)
{
m_source = source;
m_sourceIt = m_source.cbegin();
m_parenStack.clear();
m_reset = true;
}
Parser::Token Parser::consumeToken()
{
if (m_source.empty())
return Parser::Token(TokenType::None, SourceLocation());
/* If parser has just been reset, emit begin token */
if (m_reset)
{
m_reset = false;
return Parser::Token(TokenType::SourceBegin, getLocation());
}
/* Skip whitespace */
skipWhitespace(m_sourceIt);
/* Check for source end */
if (m_sourceIt == m_source.cend())
return Parser::Token(TokenType::SourceEnd, getLocation());
/* Check for numeric literal */
{
char* strEnd;
float val = strtof(&*m_sourceIt, &strEnd);
if (&*m_sourceIt != strEnd)
{
Parser::Token tok(TokenType::NumLiteral, getLocation());
tok.m_tokenFloat = val;
m_sourceIt += (strEnd - &*m_sourceIt);
return tok;
}
}
/* Check for swizzle op */
if (*m_sourceIt == '.')
{
int count = 0;
std::string_view::const_iterator tmp = m_sourceIt + 1;
if (tmp != m_source.cend())
{
for (int i=0 ; i<4 ; ++i)
{
std::string_view::const_iterator tmp2 = tmp + i;
if (tmp2 == m_source.cend())
case Kind::Imm:
buf.append(indent);
buf.append("Imm "sv).append(hecl::Format("%f", val));
break;
char ch = tolower(*tmp2);
if (ch >= 'w' && ch <= 'z')
++count;
else if (ch == 'r' || ch == 'g' || ch == 'b' || ch == 'a')
++count;
case Kind::Binop:
buf.append(indent);
buf.append("Binop "sv).append(OpToStr(op)).append(" {\n"sv);
buf.append(left->fmt(level + 1)).append("\n"sv);
buf.append(right->fmt(level + 1)).append("\n"sv);
buf.append(indent).append("}"sv);
break;
case Kind::Swizzle:
buf.append(indent);
buf.append("Swizzle \""sv).append(str);
buf.append("\" {\n"sv);
buf.append(left->fmt(level + 1)).append("\n"sv);
buf.append(indent).append("}"sv);
break;
default:
break;
}
return buf;
}
std::string IRNode::describe() const
{
std::vector<std::string> l;
l.push_back("kind="s + KindToStr(kind).data());
if (!str.empty())
l.push_back("str="s + str);
if (kind == Kind::Binop)
{
l.push_back("op="s + OpToStr(op).data());
l.push_back("left="s + left->toString());
l.push_back("right="s + right->toString());
}
if (kind == Kind::Swizzle)
l.push_back("node="s + left->toString());
if (kind == Kind::Call)
{
std::string str = "children=["s;
for (auto it = children.begin(); it != children.end(); ++it)
{
str += it->toString();
if (&*it != &children.back())
str += ';';
}
str += ']';
l.push_back(str);
}
std::string str = "IRNode["s;
for (auto it = l.begin(); it != l.end(); ++it)
{
str += *it;
if (&*it != &l.back())
str += ';';
}
str += ']';
return str;
}
void Parser::check(Token::Kind expected)
{
if (sym == expected)
scan();
else
error("expected %s, was %s", Token::KindToStr(expected).data(), Token::KindToStr(sym).data());
}
IRNode Parser::call()
{
check(Token::Kind::Ident);
std::string name = t.str;
std::list<IRNode> args;
check(Token::Kind::Lpar);
if (sym == Token::Kind::Lpar || sym == Token::Kind::Ident ||
sym == Token::Kind::Number || sym == Token::Kind::Minus)
{
args.push_back(expr());
while (sym == Token::Kind::Comma)
{
scan();
args.push_back(expr());
}
}
if (sym != Token::Kind::Rpar)
error("expected expr|rpar, was %s", Token::KindToStr(sym).data());
else
scan();
return IRNode(IRNode::Kind::Call, std::move(name), std::move(args), t.loc);
}
bool Parser::imm(const IRNode& a, const IRNode& b)
{
return a.kind == IRNode::Kind::Imm && b.kind == IRNode::Kind::Imm;
}
IRNode Parser::expr()
{
IRNode node = sum();
while (sym == Token::Kind::Plus || sym == Token::Kind::Minus)
{
scan();
Token::Kind op = t.kind;
IRNode right = sum();
switch (op)
{
case Token::Kind::Plus:
if (imm(node, right)) // constant folding
return IRNode(IRNode::Kind::Imm, node.val + right.val, t.loc);
else
node = IRNode(IRNode::Op::Add, std::move(node), std::move(right), t.loc);
break;
case Token::Kind::Minus:
if (imm(node, right)) // constant folding
node = IRNode(IRNode::Kind::Imm, node.val - right.val, t.loc);
else
node = IRNode(IRNode::Op::Sub, std::move(node), std::move(right), t.loc);
break;
default:
break;
}
}
if (count)
{
Parser::Token tok(TokenType::VectorSwizzle, getLocation());
for (int i=0 ; i<count ; ++i)
{
std::string_view::const_iterator tmp2 = tmp + i;
tok.m_tokenString += tolower(*tmp2);
return node;
}
m_sourceIt = tmp + count;
return tok;
IRNode Parser::sum()
{
IRNode node = factor();
while (sym == Token::Kind::Times || sym == Token::Kind::Div)
{
scan();
Token::Kind op = t.kind;
IRNode right = factor();
switch (op)
{
case Token::Kind::Times:
if (imm(node, right)) // constant folding
node = IRNode(IRNode::Kind::Imm, node.val * right.val, t.loc);
else
node = IRNode(IRNode::Op::Mul, std::move(node), std::move(right), t.loc);
break;
case Token::Kind::Div:
if (imm(node, right)) // constant folding
node = IRNode(IRNode::Kind::Imm, node.val / right.val, t.loc);
else
node = IRNode(IRNode::Op::Div, std::move(node), std::move(right), t.loc);
break;
default:
break;
}
}
return node;
}
IRNode Parser::factor()
{
if (sym == Token::Kind::Lpar)
{
scan();
IRNode node = expr();
check(Token::Kind::Rpar);
return node;
} else
return value();
}
IRNode Parser::value()
{
if (sym == Token::Kind::Number || sym == Token::Kind::Minus)
{
scan();
bool neg = false;
if (t.kind == Token::Kind::Minus)
{
neg = true;
check(Token::Kind::Number);
}
float val = strtof(((neg ? "-"s : ""s) + t.str).c_str(), nullptr);
return IRNode(IRNode::Kind::Imm, val, t.loc);
}
else if (sym == Token::Kind::Ident)
{
IRNode call = Parser::call();
if (sym == Token::Kind::Period)
{
scan();
check(Token::Kind::Ident);
return IRNode(IRNode::Kind::Swizzle, std::string(t.str), std::move(call), t.loc);
}
return call;
}
else
{
error("expected number|call, was %s", Token::KindToStr(sym).data());
return IRNode();
}
}
/* Check for arithmetic op */
if (*m_sourceIt == '+' || *m_sourceIt == '-' || *m_sourceIt == '*' || *m_sourceIt == '/')
std::list<IRNode> Parser::parse()
{
Parser::Token tok(TokenType::ArithmeticOp, getLocation());
tok.m_tokenInt = *m_sourceIt;
++m_sourceIt;
return tok;
std::list<IRNode> result;
scan();
while (sym == Token::Kind::Lf)
scan();
result.push_back(call());
while (sym == Token::Kind::Lf)
{
while (sym == Token::Kind::Lf)
scan();
if (sym != Token::Kind::Eof)
result.push_back(call());
}
while (sym == Token::Kind::Lf)
scan();
check(Token::Kind::Eof);
/* Check for parenthesis end (group or function call) */
if (*m_sourceIt == ')')
{
if (m_parenStack.empty())
{
m_diag.reportParserErr(getLocation(), "unexpected ')' while parsing");
return Parser::Token(TokenType::None, SourceLocation());
}
Parser::Token tok(m_parenStack.back(), getLocation());
++m_sourceIt;
m_parenStack.pop_back();
return tok;
}
if (hecl::VerbosityLevel > 1)
for (auto& res : result)
printf("%s\n", res.toString().c_str());
/* Check for group start */
if (*m_sourceIt == '(')
{
m_parenStack.push_back(TokenType::EvalGroupEnd);
Parser::Token tok(TokenType::EvalGroupStart, getLocation());
++m_sourceIt;
return tok;
}
/* Check for function start */
if (isalpha(*m_sourceIt) || *m_sourceIt == '_')
{
std::string_view::const_iterator tmp = m_sourceIt + 1;
while (tmp != m_source.cend() && (isalnum(*tmp) || *tmp == '_') && *tmp != '(')
++tmp;
std::string_view::const_iterator nameEnd = tmp;
skipWhitespace(tmp);
if (*tmp == '(')
{
Parser::Token tok(TokenType::FunctionStart, getLocation());
tok.m_tokenString.assign(m_sourceIt, nameEnd);
m_sourceIt = tmp + 1;
m_parenStack.push_back(TokenType::FunctionEnd);
return tok;
}
}
/* Check for function arg delimitation */
if (*m_sourceIt == ',')
{
if (m_parenStack.empty() || m_parenStack.back() != TokenType::FunctionEnd)
{
m_diag.reportParserErr(getLocation(), "unexpected ',' while parsing");
return Parser::Token(TokenType::None, SourceLocation());
}
Parser::Token tok(TokenType::FunctionArgDelim, getLocation());
++m_sourceIt;
return tok;
}
/* Error condition if reached */
m_diag.reportParserErr(getLocation(), "unexpected token while parsing");
return Parser::Token(TokenType::None, SourceLocation());
}
SourceLocation Parser::getLocation() const
{
if (m_source.empty())
return SourceLocation();
std::string_view::const_iterator it = m_source.cbegin();
int line = 0;
int col = 0;
for (; it != m_sourceIt ; ++it)
{
++col;
if (*it == '\n')
{
++line;
col = 0;
}
}
return {line+1, col+1};
return result;
}
}
}

View File

@ -0,0 +1,122 @@
#include "hecl/Frontend.hpp"
namespace hecl::Frontend
{
int Scanner::_read()
{
if (m_sourceIt == m_source.end())
return -1;
return *m_sourceIt++;
}
bool Scanner::read()
{
if (ch == EOF)
return false;
if (ch == LF)
{
lastLine = std::move(currentLine);
currentLine = std::string();
}
int c = _read();
ch = char(c);
if (ch == LF)
{
loc.line++;
lfcol = loc.col;
loc.col = 0;
}
else if (c != EOF)
{
currentLine += ch;
loc.col++;
}
return c != EOF;
}
Token Scanner::next()
{
if (ch == EOF)
return Token(Token::Kind::Eof, loc);
char c = ch;
int tline = loc.line;
int tcol = loc.col;
int tlfcol = lfcol;
read();
// skip comments and newlines
while (c != EOF && (c == COMMENT || isspace(c)))
{
if (c == COMMENT)
{
while (c != LF && c != EOF)
{
tline = loc.line;
tcol = loc.col;
tlfcol = lfcol;
c = ch;
read();
}
}
while (c != EOF && isspace(c))
{
if (c == LF)
return Token(Token::Kind::Lf, {tline - 1, tlfcol + 1});
tline = loc.line;
tcol = loc.col;
tlfcol = lfcol;
c = ch;
read();
}
}
Token::Kind kind = CharToTokenKind(c);
if (kind != Token::Kind::None)
return Token(kind, {tline, tcol});
if (ch == EOF)
return Token(Token::Kind::Eof, {tline, tcol});
// ident or number
if (isDigit(c))
{
std::string buf;
buf += c;
while (isDigit(ch))
{
buf += ch;
read();
}
if (ch == '.')
{ // float
buf += ch;
read();
while (isDigit(ch))
{
buf += ch;
read();
}
}
return Token(Token::Kind::Number, std::move(buf), {tline, tcol});
}
if (isStartIdent(c))
{
std::string buf;
buf += c;
while (isMidIdent(ch))
{
buf += ch;
read();
}
return Token(Token::Kind::Ident, std::move(buf), {tline, tcol});
}
error({tline, tcol}, "unexpected character '%c' (X'%02X')", chr(c), int(c));
return Token(Token::Kind::None, {tline, tcol});
}
}

View File

@ -544,9 +544,12 @@ ShaderCacheManager::buildExtendedShader(const ShaderTag& tag, const hecl::Fronte
if (search != m_pipelineLookup.cend())
return search->second;
if (tag.valSizeT() == 3543211830293577851)
printf("");
std::shared_ptr<ShaderPipelines> ret = std::make_shared<ShaderPipelines>();
ShaderCachedData foundData = lookupData(tag);
if (foundData)
if (false && foundData)
{
factory.commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) -> bool
{

View File

@ -42,6 +42,8 @@ struct HECLApplicationCallback : boo::IApplicationCallback
int appMain(boo::IApplication* app)
{
hecl::VerbosityLevel = 2;
/* Setup boo window */
m_mainWindow = app->newWindow(_S("HECL Test"), 1);
m_mainWindow->setCallback(&m_windowCb);
@ -88,7 +90,7 @@ struct HECLApplicationCallback : boo::IApplicationCallback
/* Compile HECL shader */
static std::string testShader = "HECLOpaque(Texture(0, UV(0)))";
//static std::string testShader = "HECLOpaque(vec4(1.0,1.0,1.0,1.0))";
//static std::string testShader = "HECLOpaque(vec3(1.0,1.0,1.0),1.0)";
hecl::Runtime::ShaderTag testShaderTag(testShader, 0, 1, 0, 0, 0, boo::Primitive::TriStrips,
hecl::Backend::ReflectionType::None, false, false, false);
std::shared_ptr<hecl::Runtime::ShaderPipelines> testShaderObj =