diff --git a/CMakeLists.txt b/CMakeLists.txt index 5555b42df..c7eb3abf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,6 +276,7 @@ TESTSUITE ( aastep allowconnect-err and-or-not-synonyms arithmetic oslc-err-outputparamvararray oslc-err-paramdefault oslc-err-struct-array-init oslc-err-struct-ctr oslc-err-struct-dup oslc-err-struct-print + oslc-err-swizzle oslc-warn-commainit oslc-variadic-macro oslc-version @@ -296,6 +297,7 @@ TESTSUITE ( aastep allowconnect-err and-or-not-synonyms arithmetic struct-isomorphic-overload struct-layers struct-operator-overload struct-return struct-with-array struct-nested struct-nested-assign struct-nested-deep + swizzle ternary testshade-expr texture-alpha texture-blur texture-connected-options diff --git a/src/liboslcomp/ast.cpp b/src/liboslcomp/ast.cpp index 4bf8195f7..497d17a63 100644 --- a/src/liboslcomp/ast.cpp +++ b/src/liboslcomp/ast.cpp @@ -694,12 +694,12 @@ ASTindex::childname (size_t i) const ASTstructselect::ASTstructselect (OSLCompilerImpl *comp, ASTNode *expr, ustring field) - : ASTNode (structselect_node, comp, 0, expr), m_field(field), + : ASTfieldselect (structselect_node, comp, expr, field), m_structid(-1), m_fieldid(-1), m_fieldsym(NULL) { m_fieldsym = find_fieldsym (m_structid, m_fieldid); if (m_fieldsym) { - m_fieldname = m_fieldsym->name(); + m_fullname = m_fieldsym->name(); m_typespec = m_fieldsym->typespec(); } } @@ -712,12 +712,7 @@ ASTstructselect::ASTstructselect (OSLCompilerImpl *comp, ASTNode *expr, Symbol * ASTstructselect::find_fieldsym (int &structid, int &fieldid) { - if (! lvalue()->typespec().is_structure() && - ! lvalue()->typespec().is_structure_array()) { - error ("type '%s' does not have a member '%s'", - type_c_str(lvalue()->typespec()), m_field); - return NULL; - } + ASSERT (lvalue()->typespec().is_structure_based()); ustring structsymname; TypeSpec structtype; @@ -763,8 +758,7 @@ ASTstructselect::find_structsym (ASTNode *structnode, ustring &structname, // or array of structs) down to a symbol that represents the // particular field. In the process, we set structname and its // type structtype. - ASSERT (structnode->typespec().is_structure() || - structnode->typespec().is_structure_array()); + ASSERT (structnode->typespec().is_structure_based()); if (structnode->nodetype() == variable_ref_node) { // The structnode is a top-level struct variable ASTvariable_ref *var = (ASTvariable_ref *) structnode; @@ -811,6 +805,148 @@ ASTstructselect::print (std::ostream &out, int indentlevel) const +ASTswizzle::ASTswizzle (OSLCompilerImpl *comp, ASTNode *expr, ustring field) : +ASTfieldselect(swizzle_node, comp, expr, field) +{ + if (field.size() == 1) m_typespec = TypeDesc::TypeFloat; + else m_typespec = TypeDesc::TypeVector; +} + + + +size_t ASTswizzle::indices (ustring components, int *indexes, size_t N, bool consts) +{ + size_t i = 0; + do { + switch (components[i]) { + case 'r': + case 'x': indexes[i] = 0; break; + case 'g': + case 'y': indexes[i] = 1; break; + case 'b': + case 'z': indexes[i] = 2; break; + + case '0': + if (!consts) + return 0; + indexes[i] = const_offset; + break; + case '1': + if (!consts) + return 0; + indexes[i] = const_offset + 1; + break; + + case '\0': return i; + + default: return 0; + } + } while (++i < N); + + return i; +} + + + +const char * +ASTswizzle::childname (size_t i) const +{ + return type_c_str(m_typespec); +} + + +void +ASTswizzle::print (std::ostream &out, int indentlevel) const +{ + ASTNode::print (out, indentlevel); + indent (out, indentlevel+1); + out << "components " << field() << "\n"; +} + + +ASTNode* ASTfieldselect::create (OSLCompilerImpl *comp, ASTNode *expr, + ustring field, bool swizzle) +{ + if (!swizzle && expr->typespec().is_structure_based()) + return new ASTstructselect (comp, expr, field); + + const TypeSpec &type = expr->nodetype() != structselect_node ? expr->typespec() : + static_cast(expr)->fieldsym()->typespec(); + + if (type.aggregate() == TypeDesc::VEC3 && field.size() <= 3) { + // Early out swizle to native component ordering. + if (field == "rgb" || field == "xyz") + return expr; + + ASTindex* index = expr->nodetype() != index_node ? nullptr : static_cast(expr); + + int indexes[3]; + switch (ASTswizzle::indices (field, indexes, 3, true)) { + case 1: { + // c.0 && c.1 not allowed + ASSERT (indexes[0] >= 0); + if (!index) + return new ASTindex (comp, expr, new ASTliteral (comp, indexes[0])); + index->extend(new ASTliteral (comp, indexes[0])); + return index; + } + + case 3: { + bool allconst = true; + // Don't leak soon to be unused expr node + std::unique_ptr cleanup(index); + ASTNode* index0 = nullptr; + if (index) { + index0 = index->index().get(); + expr = index->lvalue().get(); + } + + ASTNode *args[3]; + for (int i = 0; i < 3; ++i) { + if (indexes[i] >= 0) { + allconst = false; + args[i] = new ASTliteral (comp, indexes[i]); + if (i == 0 && index) { + // Re-use expr by extending the ASTindex. + index->extend (args[i]); + args[0] = cleanup.release (); + } else { + args[i] = !index0 ? new ASTindex (comp, expr, args[i]) : + new ASTindex (comp, expr, index0, args[i]); + } + } else { + float cval = indexes[i] - ASTswizzle::const_offset; + ASSERT ((cval==0) || (cval==1)); + args[i] = new ASTliteral (comp, cval); + } + } + args[0]->append (args[1]); + args[1]->append (args[2]); + + if (allconst) { + // return a type constructor instead of a swizzle + ASSERT (!cleanup); + // initial expression will be unused + cleanup.reset (expr); + return new ASTtype_constructor (comp, type, args[0]); + } + return new ASTswizzle (comp, args[0], field); + } + + default: break; + } + } + + comp->error (comp->filename(), comp->lineno(), + "type '%s' does not have a member '%s'", + comp->type_c_str(expr->typespec()), field); + + // Don't leak expr node + delete expr; + return nullptr; +} + + const char * ASTconditional_statement::childname (size_t i) const { diff --git a/src/liboslcomp/ast.h b/src/liboslcomp/ast.h index de6965771..3d9f37c70 100644 --- a/src/liboslcomp/ast.h +++ b/src/liboslcomp/ast.h @@ -62,7 +62,7 @@ class ASTNode : public OIIO::RefCnt { unknown_node, shader_declaration_node, function_declaration_node, variable_declaration_node, compound_initializer_node, variable_ref_node, preincdec_node, postincdec_node, - index_node, structselect_node, + index_node, structselect_node, swizzle_node, conditional_statement_node, loop_statement_node, loopmod_statement_node, return_statement_node, binary_expression_node, unary_expression_node, @@ -580,6 +580,13 @@ class ASTindex : public ASTNode ustring destname, ustring srcname, Symbol *index); + /// Add another index: obj[current][extended] + /// + void extend (ASTNode *ext) { + ASSERT (nchildren() < 4); + m_children.emplace_back(ext); + } + ref lvalue () const { return child (0); } ref index () const { return child (1); } ref index2 () const { return child (2); } @@ -588,7 +595,28 @@ class ASTindex : public ASTNode -class ASTstructselect : public ASTNode +class ASTfieldselect : public ASTNode +{ +protected: + ASTfieldselect (NodeType type, OSLCompilerImpl *comp, ASTNode *expr, + ustring field) : + ASTNode(type, comp, Nothing, expr), m_field(field) {} + + ustring m_field; ///< Name of the field + ustring m_fullname; ///< Full name of variable and field + +public: + static ASTNode* create (OSLCompilerImpl *comp, ASTNode *expr, ustring field, + bool swizzle = false); + + ustring field () const { return m_field; } + ustring fullname () const { return m_fullname; } + ref lvalue () const { return child (0); } +}; + + + +class ASTstructselect : public ASTfieldselect { public: ASTstructselect (OSLCompilerImpl *comp, ASTNode *expr, ustring field); @@ -602,9 +630,7 @@ class ASTstructselect : public ASTNode /// field. void codegen_assign (Symbol *dest, Symbol *src); - ref lvalue () const { return child (0); } - ustring field () const { return m_field; } - ustring fieldname () const { return m_fieldname; } + ustring fieldname () const { return fullname(); } Symbol *fieldsym () const { return m_fieldsym; } private: @@ -613,15 +639,41 @@ class ASTstructselect : public ASTNode TypeSpec &structtype); Symbol *codegen_index (); - ustring m_field; ///< Name of the field int m_structid; ///< index of the structure int m_fieldid; ///< index of the field within the structure - ustring m_fieldname; ///< Name of the field variable Symbol *m_fieldsym; ///< Symbol of the field variable }; +class ASTswizzle : public ASTfieldselect +{ +public: + ASTswizzle (OSLCompilerImpl *comp, ASTNode *expr, ustring field); + + const char *nodetypename () const { return "swizzle"; } + const char *childname (size_t i) const; + void print (std::ostream &out, int indentlevel=0) const; + TypeSpec typecheck (TypeSpec expected); + Symbol *codegen (Symbol *dest = NULL); + + /// Get component indeces for a swizzle string. + /// consts allows '0' & '1' to be included in the string. + static size_t indices (ustring components, int *indexes, size_t N, bool consts); + + /// Offset if the component index is really a constant ('0' or '1'). + enum { const_offset = -2 }; + + /// Special code generation of assignment of src to proper components. + Symbol* codegen_assign (Symbol *src); + + size_t indices (int *indexes, size_t N, bool consts) const { + return indices (m_field, indexes, N, consts); + } +}; + + + class ASTconditional_statement : public ASTNode { public: diff --git a/src/liboslcomp/codegen.cpp b/src/liboslcomp/codegen.cpp index 39968d293..effaddc6a 100644 --- a/src/liboslcomp/codegen.cpp +++ b/src/liboslcomp/codegen.cpp @@ -488,6 +488,8 @@ ASTassign_expression::codegen (Symbol *dest) // Assigning to an individual component or array element index = (ASTindex *) var().get(); dest = NULL; + } else if (var()->nodetype() == swizzle_node) { + return static_cast(var().get())->codegen_assign(expr()->codegen (dest)); } else if (var()->nodetype() == structselect_node) { dest = var()->codegen(); } else { @@ -1294,6 +1296,78 @@ ASTindex::codegen_assign (Symbol *src, Symbol *ind, +Symbol * +ASTswizzle::codegen (Symbol *dest) +{ + // General case, construct a new triple with the swizzle indexes. + + if (dest == nullptr || ! equivalent (dest->typespec(), typespec())) + dest = m_compiler->make_temporary (typespec()); + + Symbol *syms[4] = { dest }; + + int nsym = 1; + for (ref arg = child(0); arg && nsym < 4; ++nsym, arg = arg->next()) { + syms[nsym] = arg->codegen(); + } + ASSERT (nsym == 4); + + // emit the constructor call + emitcode (typespec().string().c_str(), nsym, syms); + return dest; +} + + + +Symbol * +ASTswizzle::codegen_assign (Symbol *src) +{ + // Swizzle assignment. + + if (!m_is_lvalue) { + error ("Cannot assign to constant swizzle"); + return nullptr; + } + + // First child is an index, which holds the lvalue (this) to store to. + ASTindex* index = static_cast(child(0)); + Symbol *syms[4] = { index->lvalue().get()->codegen() }; + + int nsym = 0; + if (src->typespec().is_triple()) { + int idxs[3]; + if (indices(idxs, 3, false /*can't assign to constants*/) != 3) { + error ("Trying to assign to invalid swizzle"); + return nullptr; + } + // Iterate all of the indexes + for (; index && nsym < 3; ++nsym, index = static_cast(index->nextptr())) { + // tmp[I] = src[N] + ASSERT (index->nodetype() == index_node); + Symbol* sym = m_compiler->make_temporary (TypeDesc::TypeFloat); + emitcode ("compref", sym, src, m_compiler->make_constant (nsym)); + + // sym[0] is taken, so assign to syms[i+1] + ASSERT (idxs[nsym]+1 < int(sizeof(syms)/sizeof(syms[0]))); + syms[idxs[nsym]+1] = sym; + } + } else { + // Same assignment to all components + for (int n = m_typespec.aggregate(); nsym < n; ++nsym) + syms[nsym] = src; + } + + ASSERT (nsym == 3); + + // emit the constructor call + emitcode (typespec().string().c_str(), nsym+1, syms); + + // so transitive assignment will work + return syms[0]; +} + + + Symbol * ASTstructselect::codegen (Symbol *dest) { diff --git a/src/liboslcomp/oslgram.y b/src/liboslcomp/oslgram.y index af4842b96..daeebc1f5 100644 --- a/src/liboslcomp/oslgram.y +++ b/src/liboslcomp/oslgram.y @@ -83,7 +83,7 @@ static std::stack typespec_stack; // just for function_declaration // Define the terminal symbols. -%token IDENTIFIER STRING_LITERAL +%token IDENTIFIER STRING_LITERAL SWIZZLE_IDENTIFIER %token INT_LITERAL %token FLOAT_LITERAL %token COLORTYPE FLOATTYPE INTTYPE MATRIXTYPE @@ -778,9 +778,13 @@ id_or_field { $$ = new ASTvariable_ref (oslcompiler, ustring($1)); } + | variable_lvalue SWIZZLE_IDENTIFIER + { + $$ = ASTfieldselect::create (oslcompiler, $1, ustring($2)); + } | variable_lvalue '.' IDENTIFIER { - $$ = new ASTstructselect (oslcompiler, $1, ustring($3)); + $$ = ASTfieldselect::create (oslcompiler, $1, ustring($3)); } ; diff --git a/src/liboslcomp/osllex.l b/src/liboslcomp/osllex.l index 5f4ab1eaf..b9e1c6cae 100644 --- a/src/liboslcomp/osllex.l +++ b/src/liboslcomp/osllex.l @@ -80,6 +80,11 @@ STR \"(\\.|[^\\"\n])*\" /* " This extra quote fixes emacs syntax highlighting on this file */ /* Identifier: alphanumeric, may contain digits after the first character */ IDENT ({ALPHA}|[_])({ALPHA}|{DIGIT}|[_])* + /* Swizzle that starts with a constant value + Needs to include the . selector to take precedence over FLT */ +SWIZZLE (\.[01][01rgbxyz]{2}) + + /* C preprocessor (cpp) directives */ CPP ^[ \t]*#.*\n CPLUSCOMMENT \/\/.*\n @@ -196,6 +201,11 @@ void preprocess (const char *yytext); SETLINE; return IDENTIFIER; } +{SWIZZLE} { + yylval.s = ustring(yytext+1).c_str(); + SETLINE; + return SWIZZLE_IDENTIFIER; + } /* Literal values */ {INTEGER} { diff --git a/src/liboslcomp/typecheck.cpp b/src/liboslcomp/typecheck.cpp index 21419e4e1..98fd262fc 100644 --- a/src/liboslcomp/typecheck.cpp +++ b/src/liboslcomp/typecheck.cpp @@ -225,6 +225,22 @@ ASTindex::typecheck (TypeSpec expected) +TypeSpec +ASTswizzle::typecheck (TypeSpec expected) +{ + typecheck_children (); + + // Can't be an lvalue if any of the args are constant floats. + for (ref arg = child(0); arg; arg = arg->next()) { + if (!(m_is_lvalue = arg->is_lvalue())) + break; + } + + return m_typespec; +} + + + TypeSpec ASTstructselect::typecheck (TypeSpec expected) { diff --git a/testsuite/oslc-err-field/ref/out.txt b/testsuite/oslc-err-field/ref/out.txt index 617663258..260f3883c 100644 --- a/testsuite/oslc-err-field/ref/out.txt +++ b/testsuite/oslc-err-field/ref/out.txt @@ -1,3 +1,6 @@ -test.osl:8: error: type 'color' does not have a member 'nope' -test.osl:11: error: struct type 'custom' does not have a member 'bad' +test.osl:11: error: type 'color' does not have a member 'invalid' +test.osl:12: error: type 'color' does not have a member 'nope' +test.osl:15: error: struct type 'custom' does not have a member 'bad' +test.osl:18: error: type 'color' does not have a member 'sinvalid' +test.osl:19: error: type 'color' does not have a member 'error' FAILED test.osl diff --git a/testsuite/oslc-err-field/test.osl b/testsuite/oslc-err-field/test.osl index cad6fec5e..ce0f21154 100644 --- a/testsuite/oslc-err-field/test.osl +++ b/testsuite/oslc-err-field/test.osl @@ -1,12 +1,20 @@ // Test invalid field selections in function calls struct custom { float field; }; +struct A { color valid; }; +struct B { A a; }; +struct C { B b; }; shader test () { color c = color(4,3,2); + c.invalid = 45; printf("c.nope: %g\n", c.nope); custom cc = { 1 }; printf("cc.bad: %g\n", cc.bad); + + C nested; + nested.b.a.valid.sinvalid = color(1); + printf("nested.b.a.valid.error: %g\n", nested.b.a.valid.error); } diff --git a/testsuite/oslc-err-swizzle/ref/out.txt b/testsuite/oslc-err-swizzle/ref/out.txt new file mode 100644 index 000000000..f5bdb7406 --- /dev/null +++ b/testsuite/oslc-err-swizzle/ref/out.txt @@ -0,0 +1,3 @@ +test.osl:7: error: Can't assign via = to something that isn't an lvalue +test.osl:8: error: Can't assign via = to something that isn't an lvalue +FAILED test.osl diff --git a/testsuite/oslc-err-swizzle/run.py b/testsuite/oslc-err-swizzle/run.py new file mode 100755 index 000000000..d901e097a --- /dev/null +++ b/testsuite/oslc-err-swizzle/run.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +# command = oslc("test.osl") +# don't even need that -- it's automatic +failureok = 1 # this test is expected to have oslc errors diff --git a/testsuite/oslc-err-swizzle/test.osl b/testsuite/oslc-err-swizzle/test.osl new file mode 100644 index 000000000..1700c29eb --- /dev/null +++ b/testsuite/oslc-err-swizzle/test.osl @@ -0,0 +1,10 @@ +// Error on invalid swizzle operations + +shader test () +{ + color c; + + c.rg0 = color(1); + c.01r = color(1); + // c.0; +} diff --git a/testsuite/swizzle/ref/out.txt b/testsuite/swizzle/ref/out.txt new file mode 100644 index 000000000..d9435116c --- /dev/null +++ b/testsuite/swizzle/ref/out.txt @@ -0,0 +1,42 @@ +Compiled swizzle.osl -> swizzle.oso +c: 1 2 3 +c.brg = c.grb = c.bgr: 3 1 2 +c.rgb = c.gbr: 1 2 3 +c.r00: 1 0 0 +c.g01: 2 0 1 +c.b11: 3 1 1 +c.bg1: 3 2 1 +c.01g: 0 1 2 +c.101: 1 0 1 +c.rgb = c.brg: 3 1 2 +c.rgb = c.brg: 2 3 1 +c.rgb = c.brg: 1 2 3 +c.rgb = c.bgr: 3 2 1 +c.bgr = c.bgr: 3 2 1 +c.grb = c.bgr: 2 1 3 +c.ggr + c.rgr: 3 2 4 +c.r: 4 +c.g: 3 +c.b: 2 +v0.x: 6 +v0.y: 7 +v0.z: 8 +v0.z->x: 6 +c4[0].r: 1 +c4[0].g: 2 +c4[0].b: 3 +c4[1].r: -1 +c4[1].b: -3 +c4[1].g: -2 +c.rrr: 4 4 4 +c.rgb: 4 3 2 +c.rbg: 4 2 3 +c.ggg: 3 3 3 +c.grb: 3 4 2 +c.gbr: 3 2 4 +c.bbb: 2 2 2 +c.brg: 2 4 3 +c.ggr: 3 3 4 +ca[0].brg: 3 1 2 +c.rgb = 1: 1 1 1 + diff --git a/testsuite/swizzle/run.py b/testsuite/swizzle/run.py new file mode 100755 index 000000000..f61ba00ef --- /dev/null +++ b/testsuite/swizzle/run.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +command = testshade("swizzle") + diff --git a/testsuite/swizzle/swizzle.osl b/testsuite/swizzle/swizzle.osl new file mode 100644 index 000000000..828c1243f --- /dev/null +++ b/testsuite/swizzle/swizzle.osl @@ -0,0 +1,87 @@ +// Test swizzle statements + +#include "color4.h" + +shader swizzle () +{ + // Make sure floats don't generate a swizzle operation. + float noswizzle = 1.101; + noswizzle = 1.001; + + color c = color(1,2,3); + printf("c: %g\n", c); + + // transitive + // 1,2,3 -> 2,3,1 -> 3,1,2 + c.brg = c.grb = c.bgr; + printf("c.brg = c.grb = c.bgr: %g\n", c); + + c.rgb = c.gbr; + printf("c.rgb = c.gbr: %g\n", c); + + printf("c.r00: %g\n", c.r00); + printf("c.g01: %g\n", c.g01); + printf("c.b11: %g\n", c.b11); + printf("c.bg1: %g\n", c.bg1); + printf("c.01g: %g\n", c.01g); + printf("c.101: %g\n", c.101); + + c = c.brg; + printf("c.rgb = c.brg: %g\n", c); + + c = c.brg; + printf("c.rgb = c.brg: %g\n", c); + + c = c.brg; + printf("c.rgb = c.brg: %g\n", c); + + c.rgb = c.bgr; + printf("c.rgb = c.bgr: %g\n", c); + + c.bgr = c.bgr; + printf("c.bgr = c.bgr: %g\n", c); + + c.grb = c.bgr; + printf("c.grb = c.bgr: %g\n", c); + + printf("c.ggr + c.rgr: %g\n", c.ggr + c.rgr); + + c.gbr = c.ggr + c.rgr; + + printf("c.r: %g\n", c.r); + printf("c.g: %g\n", c.g); + printf("c.b: %g\n", c.b); + + vector v0 = vector(6,7,8); + printf("v0.x: %g\n", v0.x); + printf("v0.y: %g\n", v0.y); + printf("v0.z: %g\n", v0.z); + v0.z = v0.x; + printf("v0.z->x: %g\n", v0.r); + + color ca[2] = { color(-1,2,3), color(1,-2,-3) }; + color4 c4[2]; // FIXME: = { color4(color(1,2,3),4), color4(color(-1,-2,-3),-4) }; + + for (int i = 0; i < 2; ++i) { + ca[i].r = -ca[i].r; + c4[i].rgb = color(ca[i].x, ca[i].y, ca[i].z); + printf("c4[%d].r: %g\n", i, c4[i].rgb.r); + printf("c4[%d].%s: %g\n", i, i ? "b" : "g", i ? c4[i].rgb.b : c4[i].rgb.g); + printf("c4[%d].%s: %g\n", i, i ? "g" : "b", i ? c4[i].rgb.g : c4[i].rgb.b); + } + + printf("c.rrr: %g\n", c.rrr); + printf("c.rgb: %g\n", c.rgb); + printf("c.rbg: %g\n", c.rbg); + printf("c.ggg: %g\n", c.ggg); + printf("c.grb: %g\n", c.grb); + printf("c.gbr: %g\n", c.gbr); + printf("c.bbb: %g\n", c.bbb); + printf("c.brg: %g\n", c.brg); + printf("c.ggr: %g\n", c.ggr); + + printf("ca[0].brg: %g\n", ca[0].brg); + + c.rgb = 1; + printf("c.rgb = 1: %g\n", c); +}