]> git.tdb.fi Git - libs/gl.git/commitdiff
Add a SPIR-V backend to the GLSL compiler
authorMikko Rasa <tdb@tdb.fi>
Fri, 9 Apr 2021 14:31:47 +0000 (17:31 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 9 Apr 2021 14:45:42 +0000 (17:45 +0300)
source/glsl/compiler.cpp
source/glsl/compiler.h
source/glsl/glsl_error.h
source/glsl/reflect.h
source/glsl/spirv.cpp [new file with mode: 0644]
source/glsl/spirv.h [new file with mode: 0644]
source/glsl/spirvconstants.h [new file with mode: 0644]
source/glsl/spirvwriter.cpp [new file with mode: 0644]
source/glsl/spirvwriter.h [new file with mode: 0644]

index b2c4f9ba2cbed88dee8b4e70f00d8a6c4afb5739..74eec856d86a154561669ab9a1745793236e945e 100644 (file)
@@ -12,6 +12,7 @@
 #include "output.h"
 #include "resolve.h"
 #include "resources.h"
+#include "spirv.h"
 #include "validate.h"
 
 #undef interface
@@ -156,6 +157,15 @@ string Compiler::get_stage_glsl(Stage::Type stage_type) const
        throw key_error(Stage::get_stage_name(stage_type));
 }
 
+vector<UInt32> Compiler::get_combined_spirv() const
+{
+       if(!compiled)
+               throw invalid_operation("Compiler::get_combined_spirv");
+       SpirVGenerator gen;
+       gen.apply(*module);
+       return gen.get_code();
+}
+
 const map<string, unsigned> &Compiler::get_vertex_attributes() const
 {
        if(!compiled)
@@ -392,6 +402,8 @@ void Compiler::finalize(Stage &stage, Mode mode)
                resolve(stage, RESOLVE_VARIABLES|RESOLVE_FUNCTIONS);
                PrecisionConverter().apply(stage);
        }
+       else if(mode==SPIRV)
+               StructOrganizer().apply(stage);
 
        // Collect bindings from all stages into the shared stage's maps
        module->shared.texture_bindings.insert(stage.texture_bindings.begin(), stage.texture_bindings.end());
index a1ab51f254a771566c337f7b392484a3984a4157..51b4f00a683059579157297c1ca9803e7aff9994 100644 (file)
@@ -17,7 +17,8 @@ public:
        enum Mode
        {
                MODULE,
-               PROGRAM
+               PROGRAM,
+               SPIRV
        };
 
 private:
@@ -88,6 +89,10 @@ public:
        GLSL suitable for OpenGL or an external GLSL compiler. */
        std::string get_stage_glsl(Stage::Type) const;
 
+       /** Returns a combined SPIR-V binary for all shader stages.  The result is
+       suitable for use with OpenGL or Vulkan. */
+       std::vector<UInt32> get_combined_spirv() const;
+
        /** Returns a map of vertex attribute locations.  If the target GLSL version
        supports interface layouts, the map is empty (locations are included in the
        GLSL soucre). */
index 6654770cd90695457de4425374aea968b7f37d2d..0b4b4b02d2ddff4641f41b5c59ce2df6c1d110f7 100644 (file)
@@ -38,6 +38,13 @@ public:
        virtual ~parse_error() throw() { }
 };
 
+class internal_error: public std::logic_error
+{
+public:
+       internal_error(const std::string &w): logic_error(w) { }
+       virtual ~internal_error() throw() { }
+};
+
 struct Diagnostic
 {
        enum Severity
index c2d7aa0483bd36e4411a55c2d5325158dfae1b92..8de915729ce50e8748c9e8011419d5bd4f7cf6b8 100644 (file)
@@ -53,7 +53,7 @@ private:
        virtual void visit(VariableDeclaration &);
 };
 
-/** Determines the size and alignment of a variable, in bytes. */
+/** Determines the size and alignment of a variable or a type, in bytes. */
 class MemoryRequirementsCalculator: private NodeVisitor
 {
 public:
@@ -61,8 +61,9 @@ public:
        {
                unsigned size;
                unsigned alignment;
+               unsigned stride;
 
-               Result(unsigned s, unsigned a): size(s), alignment(a) { }
+               Result(unsigned s, unsigned a): size(s), alignment(a), stride(s+a-1-(s+a-1)%a) { }
        };
 private:
        unsigned r_size;
@@ -71,6 +72,7 @@ private:
 
 public:
        Result apply(VariableDeclaration &v) { v.visit(*this); return Result(r_size, r_alignment); }
+       Result apply(TypeDeclaration &t) { t.visit(*this); return Result(r_size, r_alignment); }
 
 private:
        virtual void visit(BasicTypeDeclaration &);
diff --git a/source/glsl/spirv.cpp b/source/glsl/spirv.cpp
new file mode 100644 (file)
index 0000000..799e31d
--- /dev/null
@@ -0,0 +1,1832 @@
+#include <msp/core/maputils.h>
+#include <msp/core/raii.h>
+#include "reflect.h"
+#include "spirv.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+namespace SL {
+
+const SpirVGenerator::BuiltinFunctionInfo SpirVGenerator::builtin_functions[] =
+{
+       { "radians", "f", "GLSL.std.450", GLSL450_RADIANS, { 1 }, 0 },
+       { "degrees", "f", "GLSL.std.450", GLSL450_DEGREES, { 1 }, 0 },
+       { "sin", "f", "GLSL.std.450", GLSL450_SIN, { 1 }, 0 },
+       { "cos", "f", "GLSL.std.450", GLSL450_COS, { 1 }, 0 },
+       { "tan", "f", "GLSL.std.450", GLSL450_TAN, { 1 }, 0 },
+       { "asin", "f", "GLSL.std.450", GLSL450_ASIN, { 1 }, 0 },
+       { "acos", "f", "GLSL.std.450", GLSL450_ACOS, { 1 }, 0 },
+       { "atan", "f", "GLSL.std.450", GLSL450_ATAN, { 1 }, 0 },
+       { "atan", "ff", "GLSL.std.450", GLSL450_ATAN2, { 1, 2 }, 0 },
+       { "sinh", "f", "GLSL.std.450", GLSL450_SINH, { 1 }, 0 },
+       { "cosh", "f", "GLSL.std.450", GLSL450_COSH, { 1 }, 0 },
+       { "tanh", "f", "GLSL.std.450", GLSL450_TANH, { 1 }, 0 },
+       { "asinh", "f", "GLSL.std.450", GLSL450_ASINH, { 1 }, 0 },
+       { "acosh", "f", "GLSL.std.450", GLSL450_ACOSH, { 1 }, 0 },
+       { "atanh", "f", "GLSL.std.450", GLSL450_ATANH, { 1 }, 0 },
+       { "pow", "ff", "GLSL.std.450", GLSL450_POW, { 1, 2 }, 0 },
+       { "exp", "f", "GLSL.std.450", GLSL450_EXP, { 1 }, 0 },
+       { "log", "f", "GLSL.std.450", GLSL450_LOG, { 1 }, 0 },
+       { "exp2", "f", "GLSL.std.450", GLSL450_EXP2, { 1 }, 0 },
+       { "log2", "f", "GLSL.std.450", GLSL450_LOG2, { 1 }, 0 },
+       { "sqrt", "f", "GLSL.std.450", GLSL450_SQRT, { 1 }, 0 },
+       { "inversesqrt", "f", "GLSL.std.450", GLSL450_INVERSE_SQRT, { 1 }, 0 },
+       { "abs", "f", "GLSL.std.450", GLSL450_F_ABS, { 1 }, 0 },
+       { "abs", "i", "GLSL.std.450", GLSL450_S_ABS, { 1 }, 0 },
+       { "sign", "f", "GLSL.std.450", GLSL450_F_SIGN, { 1 }, 0 },
+       { "sign", "i", "GLSL.std.450", GLSL450_S_SIGN, { 1 }, 0 },
+       { "floor", "f", "GLSL.std.450", GLSL450_FLOOR, { 1 }, 0 },
+       { "trunc", "f", "GLSL.std.450", GLSL450_TRUNC, { 1 }, 0 },
+       { "round", "f", "GLSL.std.450", GLSL450_ROUND, { 1 }, 0 },
+       { "roundEven", "f", "GLSL.std.450", GLSL450_ROUND_EVEN, { 1 }, 0 },
+       { "ceil", "f", "GLSL.std.450", GLSL450_CEIL, { 1 }, 0 },
+       { "fract", "f", "GLSL.std.450", GLSL450_FRACT, { 1 }, 0 },
+       { "mod", "f", "", OP_F_MOD, { 1, 2 }, 0 },
+       { "min", "ff", "GLSL.std.450", GLSL450_F_MIN, { 1, 2 }, 0 },
+       { "min", "ii", "GLSL.std.450", GLSL450_S_MIN, { 1, 2 }, 0 },
+       { "max", "ff", "GLSL.std.450", GLSL450_F_MAX, { 1, 2 }, 0 },
+       { "max", "ii", "GLSL.std.450", GLSL450_S_MAX, { 1, 2 }, 0 },
+       { "clamp", "fff", "GLSL.std.450", GLSL450_F_CLAMP, { 1, 2, 3 }, 0 },
+       { "clamp", "iii", "GLSL.std.450", GLSL450_S_CLAMP, { 1, 2, 3 }, 0 },
+       { "mix", "fff", "GLSL.std.450", GLSL450_F_MIX, { 1, 2, 3 }, 0 },
+       { "mix", "ffb", "", OP_SELECT, { 3, 2, 1 }, 0 },
+       { "mix", "iib", "", OP_SELECT, { 3, 2, 1 }, 0 },
+       { "step", "ff", "GLSL.std.450", GLSL450_F_STEP, { 1, 2 }, 0 },
+       { "smoothstep", "fff", "GLSL.std.450", GLSL450_F_SMOOTH_STEP, { 1, 2, 3 }, 0 },
+       { "isnan", "f", "", OP_IS_NAN, { 1 }, 0 },
+       { "isinf", "f", "", OP_IS_INF, { 1 }, 0 },
+       { "fma", "fff", "GLSL.std.450", GLSL450_F_FMA, { 1, 2, 3 }, 0 },
+       { "length", "f", "GLSL.std.450", GLSL450_LENGTH, { 1 }, 0 },
+       { "distance", "ff", "GLSL.std.450", GLSL450_DISTANCE, { 1, 2 }, 0 },
+       { "dot", "ff", "", OP_DOT, { 1, 2 }, 0 },
+       { "cross", "ff", "GLSL.std.450", GLSL450_CROSS, { 1, 2 }, 0 },
+       { "normalize", "f", "GLSL.std.450", GLSL450_NORMALIZE, { 1 }, 0 },
+       { "faceforward", "fff", "GLSL.std.450", GLSL450_FACE_FORWARD, { 1, 2, 3 }, 0 },
+       { "reflect", "ff", "GLSL.std.450", GLSL450_REFLECT, { 1, 2 }, 0 },
+       { "refract", "fff", "GLSL.std.450", GLSL450_REFRACT, { 1, 2, 3 }, 0 },
+       { "matrixCompMult", "ff", "", 0, { 0 }, &SpirVGenerator::visit_builtin_matrix_comp_mult },
+       { "outerProduct", "ff", "", OP_OUTER_PRODUCT, { 1, 2 }, 0 },
+       { "transpose", "f", "", OP_TRANSPOSE, { 1 }, 0 },
+       { "determinant", "f", "GLSL.std.450", GLSL450_DETERMINANT, { 1 }, 0 },
+       { "inverse", "f", "GLSL.std.450", GLSL450_MATRIX_INVERSE, { 1 }, 0 },
+       { "lessThan", "ff", "", OP_F_ORD_LESS_THAN, { 1, 2 }, 0 },
+       { "lessThan", "ii", "", OP_S_LESS_THAN, { 1, 2 }, 0 },
+       { "lessThanEqual", "ff", "", OP_F_ORD_LESS_THAN_EQUAL, { 1, 2 }, 0 },
+       { "lessThanEqual", "ii", "", OP_S_LESS_THAN_EQUAL, { 1, 2 }, 0 },
+       { "greaterThan", "ff", "", OP_F_ORD_GREATER_THAN, { 1, 2 }, 0 },
+       { "greaterThan", "ii", "", OP_S_GREATER_THAN, { 1, 2 }, 0 },
+       { "greaterThanEqual", "ff", "", OP_F_ORD_GREATER_THAN_EQUAL, { 1, 2 }, 0 },
+       { "greaterThanEqual", "ii", "", OP_S_GREATER_THAN_EQUAL, { 1, 2 }, 0 },
+       { "equal", "ff", "", OP_F_ORD_EQUAL, { 1, 2 }, 0 },
+       { "equal", "ii", "", OP_I_EQUAL, { 1, 2 }, 0 },
+       { "notEqual", "ff", "", OP_F_ORD_NOT_EQUAL, { 1, 2 }, 0 },
+       { "notEqual", "ii", "", OP_I_NOT_EQUAL, { 1, 2 }, 0 },
+       { "any", "b", "", OP_ANY, { 1 }, 0 },
+       { "all", "b", "", OP_ALL, { 1 }, 0 },
+       { "not", "b", "", OP_LOGICAL_NOT, { 1 }, 0 },
+       { "bitfieldExtract", "iii", "", OP_BIT_FIELD_S_EXTRACT, { 1, 2, 3 }, 0 },
+       { "bitfieldInsert", "iiii", "", OP_BIT_FIELD_INSERT, { 1, 2, 3, 4 }, 0 },
+       { "bitfieldReverse", "i", "", OP_BIT_REVERSE, { 1 }, 0 },
+       { "bitCount", "i", "", OP_BIT_COUNT, { 1 }, 0 },
+       { "findLSB", "i", "GLSL.std.450", GLSL450_FIND_I_LSB, { 1 }, 0 },
+       { "findMSB", "i", "GLSL.std.450", GLSL450_FIND_S_MSB, { 1 }, 0 },
+       { "textureSize", "", "", OP_IMAGE_QUERY_SIZE_LOD, { 1, 2 }, 0 },
+       { "texture", "", "", 0, { }, &SpirVGenerator::visit_builtin_texture },
+       { "textureLod", "", "", 0, { }, &SpirVGenerator::visit_builtin_texture },
+       { "texelFetch", "", "", 0, { }, &SpirVGenerator::visit_builtin_texel_fetch },
+       { "EmitVertex", "", "", OP_EMIT_VERTEX, { }, 0 },
+       { "EndPrimitive", "", "", OP_END_PRIMITIVE, { }, 0 },
+       { "dFdx", "f", "", OP_DP_DX, { 1 }, 0 },
+       { "dFdy", "f", "", OP_DP_DY, { 1 }, 0 },
+       { "dFdxFine", "f", "", OP_DP_DX_FINE, { 1 }, 0 },
+       { "dFdyFine", "f", "", OP_DP_DY_FINE, { 1 }, 0 },
+       { "dFdxCoarse", "f", "", OP_DP_DX_COARSE, { 1 }, 0 },
+       { "dFdyCoarse", "f", "", OP_DP_DY_COARSE, { 1 }, 0 },
+       { "fwidth", "f", "", OP_FWIDTH, { 1 }, 0 },
+       { "fwidthFine", "f", "", OP_FWIDTH_FINE, { 1 }, 0 },
+       { "fwidthCoarse", "f", "", OP_FWIDTH_COARSE, { 1 }, 0 },
+       { "interpolateAtCentroid", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
+       { "interpolateAtSample", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
+       { "interpolateAtOffset", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
+       { "", "", "", 0, { }, 0 }
+};
+
+SpirVGenerator::SpirVGenerator():
+       stage(0),
+       current_function(0),
+       writer(content),
+       next_id(1),
+       r_expression_result_id(0),
+       constant_expression(false),
+       spec_constant(false),
+       reachable(false),
+       composite_access(false),
+       r_composite_base_id(0),
+       r_composite_base(0),
+       assignment_source_id(0),
+       loop_merge_block_id(0),
+       loop_continue_target_id(0)
+{ }
+
+void SpirVGenerator::apply(Module &module)
+{
+       use_capability(CAP_SHADER);
+
+       for(list<Stage>::iterator i=module.stages.begin(); i!=module.stages.end(); ++i)
+       {
+               stage = &*i;
+               interface_layouts.clear();
+               i->content.visit(*this);
+       }
+
+       writer.finalize(next_id);
+}
+
+SpirVGenerator::StorageClass SpirVGenerator::get_interface_storage(const string &iface, bool block)
+{
+       if(iface=="in")
+               return STORAGE_INPUT;
+       else if(iface=="out")
+               return STORAGE_OUTPUT;
+       else if(iface=="uniform")
+               return (block ? STORAGE_UNIFORM : STORAGE_UNIFORM_CONSTANT);
+       else if(iface.empty())
+               return STORAGE_PRIVATE;
+       else
+               throw invalid_argument("SpirVGenerator::get_interface_storage");
+}
+
+SpirVGenerator::BuiltinSemantic SpirVGenerator::get_builtin_semantic(const string &name)
+{
+       if(name=="gl_Position")
+               return BUILTIN_POSITION;
+       else if(name=="gl_PointSize")
+               return BUILTIN_POINT_SIZE;
+       else if(name=="gl_ClipDistance")
+               return BUILTIN_CLIP_DISTANCE;
+       else if(name=="gl_VertexID")
+               return BUILTIN_VERTEX_ID;
+       else if(name=="gl_InstanceID")
+               return BUILTIN_INSTANCE_ID;
+       else if(name=="gl_PrimitiveID" || name=="gl_PrimitiveIDIn")
+               return BUILTIN_PRIMITIVE_ID;
+       else if(name=="gl_InvocationID")
+               return BUILTIN_INVOCATION_ID;
+       else if(name=="gl_Layer")
+               return BUILTIN_LAYER;
+       else if(name=="gl_FragCoord")
+               return BUILTIN_FRAG_COORD;
+       else if(name=="gl_PointCoord")
+               return BUILTIN_POINT_COORD;
+       else if(name=="gl_FrontFacing")
+               return BUILTIN_FRONT_FACING;
+       else if(name=="gl_SampleId")
+               return BUILTIN_SAMPLE_ID;
+       else if(name=="gl_SamplePosition")
+               return BUILTIN_SAMPLE_POSITION;
+       else if(name=="gl_FragDepth")
+               return BUILTIN_FRAG_DEPTH;
+       else
+               throw invalid_argument("SpirVGenerator::get_builtin_semantic");
+}
+
+void SpirVGenerator::use_capability(Capability cap)
+{
+       if(used_capabilities.count(cap))
+               return;
+
+       used_capabilities.insert(cap);
+       writer.write_op(content.capabilities, OP_CAPABILITY, cap);
+}
+
+SpirVGenerator::Id SpirVGenerator::import_extension(const string &name)
+{
+       Id &ext_id = imported_extension_ids[name];
+       if(!ext_id)
+       {
+               ext_id = next_id++;
+               writer.begin_op(content.extensions, OP_EXT_INST_IMPORT);
+               writer.write(ext_id);
+               writer.write_string(name);
+               writer.end_op(OP_EXT_INST_IMPORT);
+       }
+       return ext_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::get_id(Node &node) const
+{
+       return get_item(declared_ids, &node).id;
+}
+
+SpirVGenerator::Id SpirVGenerator::allocate_id(Node &node, Id type_id)
+{
+       Id id = next_id++;
+       insert_unique(declared_ids, &node, Declaration(id, type_id));
+       return id;
+}
+
+SpirVGenerator::Id SpirVGenerator::write_constant(Id type_id, Word value, bool spec)
+{
+       Id const_id = next_id++;
+       if(is_scalar_type(type_id, BasicTypeDeclaration::BOOL))
+       {
+               Opcode opcode = (value ? (spec ? OP_SPEC_CONSTANT_TRUE : OP_CONSTANT_TRUE) :
+                       (spec ? OP_SPEC_CONSTANT_FALSE : OP_CONSTANT_FALSE));
+               writer.write_op(content.globals, opcode, type_id, const_id);
+       }
+       else
+       {
+               Opcode opcode = (spec ? OP_SPEC_CONSTANT : OP_CONSTANT);
+               writer.write_op(content.globals, opcode, type_id, const_id, value);
+       }
+       return const_id;
+}
+
+SpirVGenerator::ConstantKey SpirVGenerator::get_constant_key(Id type_id, const Variant &value)
+{
+       if(value.check_type<bool>())
+               return ConstantKey(type_id, value.value<bool>());
+       else if(value.check_type<int>())
+               return ConstantKey(type_id, value.value<int>());
+       else if(value.check_type<float>())
+               return ConstantKey(type_id, value.value<float>());
+       else
+               throw invalid_argument("SpirVGenerator::get_constant_key");
+}
+
+SpirVGenerator::Id SpirVGenerator::get_constant_id(Id type_id, const Variant &value)
+{
+       ConstantKey key = get_constant_key(type_id, value);
+       Id &const_id = constant_ids[key];
+       if(!const_id)
+               const_id = write_constant(type_id, key.int_value, false);
+       return const_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::get_vector_constant_id(Id type_id, unsigned size, Id scalar_id)
+{
+       Id &const_id = constant_ids[get_constant_key(type_id, static_cast<int>(scalar_id))];
+       if(!const_id)
+       {
+               const_id = next_id++;
+               writer.begin_op(content.globals, OP_CONSTANT_COMPOSITE, 3+size);
+               writer.write(type_id);
+               writer.write(const_id);
+               for(unsigned i=0; i<size; ++i)
+                       writer.write(scalar_id);
+               writer.end_op(OP_CONSTANT_COMPOSITE);
+       }
+       return const_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::get_standard_type_id(BasicTypeDeclaration::Kind kind, unsigned size)
+{
+       Id base_id = (size>1 ? get_standard_type_id(kind, 1) : 0);
+       Id &type_id = standard_type_ids[TypeKey(base_id, (size>1 ? size : static_cast<unsigned>(kind)))];
+       if(!type_id)
+       {
+               type_id = next_id++;
+               if(size>1)
+                       writer.write_op(content.globals, OP_TYPE_VECTOR, type_id, base_id, size);
+               else if(kind==BasicTypeDeclaration::VOID)
+                       writer.write_op(content.globals, OP_TYPE_VOID, type_id);
+               else if(kind==BasicTypeDeclaration::BOOL)
+                       writer.write_op(content.globals, OP_TYPE_BOOL, type_id);
+               else if(kind==BasicTypeDeclaration::INT)
+                       writer.write_op(content.globals, OP_TYPE_INT, type_id, 32, 1);
+               else if(kind==BasicTypeDeclaration::FLOAT)
+                       writer.write_op(content.globals, OP_TYPE_FLOAT, type_id, 32);
+               else
+                       throw invalid_argument("SpirVGenerator::get_standard_type_id");
+       }
+       return type_id;
+}
+
+bool SpirVGenerator::is_scalar_type(Id type_id, BasicTypeDeclaration::Kind kind) const
+{
+       map<TypeKey, Id>::const_iterator i = standard_type_ids.find(TypeKey(0, kind));
+       return (i!=standard_type_ids.end() && i->second==type_id);
+}
+
+SpirVGenerator::Id SpirVGenerator::get_array_type_id(TypeDeclaration &base_type, Id size_id)
+{
+       Id base_type_id = get_id(base_type);
+       Id &array_type_id = array_type_ids[TypeKey(base_type_id, size_id)];
+       if(!array_type_id)
+       {
+               array_type_id = next_id++;
+               if(size_id)
+                       writer.write_op(content.globals, OP_TYPE_ARRAY, array_type_id, base_type_id, size_id);
+               else
+                       writer.write_op(content.globals, OP_TYPE_RUNTIME_ARRAY, array_type_id, base_type_id);
+
+               unsigned stride = MemoryRequirementsCalculator().apply(base_type).stride;
+               writer.write_op_decorate(array_type_id, DECO_ARRAY_STRIDE, stride);
+       }
+
+       return array_type_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::get_pointer_type_id(Id type_id, StorageClass storage)
+{
+       Id &ptr_type_id = pointer_type_ids[TypeKey(type_id, storage)];
+       if(!ptr_type_id)
+       {
+               ptr_type_id = next_id++;
+               writer.write_op(content.globals, OP_TYPE_POINTER, ptr_type_id, storage, type_id);
+       }
+       return ptr_type_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::get_variable_type_id(const VariableDeclaration &var)
+{
+       if(const BasicTypeDeclaration *basic = dynamic_cast<const BasicTypeDeclaration *>(var.type_declaration))
+               if(basic->kind==BasicTypeDeclaration::ARRAY)
+               {
+                       Id size_id = 0;
+                       if(var.array_size)
+                       {
+                               SetFlag set_const(constant_expression);
+                               r_expression_result_id = 0;
+                               var.array_size->visit(*this);
+                               size_id = r_expression_result_id;
+                       }
+                       else
+                               size_id = get_constant_id(get_standard_type_id(BasicTypeDeclaration::INT, 1), 1);
+                       return get_array_type_id(*basic->base_type, size_id);
+               }
+
+       return get_id(*var.type_declaration);
+}
+
+SpirVGenerator::Id SpirVGenerator::get_load_id(VariableDeclaration &var)
+{
+       Id &load_result_id = variable_load_ids[&var];
+       if(!load_result_id)
+       {
+               load_result_id = next_id++;
+               writer.write_op(content.function_body, OP_LOAD, get_variable_type_id(var), load_result_id, get_id(var));
+       }
+       return load_result_id;
+}
+
+void SpirVGenerator::prune_loads(Id min_id)
+{
+       for(map<const VariableDeclaration *, Id>::iterator i=variable_load_ids.begin(); i!=variable_load_ids.end(); )
+       {
+               if(i->second>=min_id)
+                       variable_load_ids.erase(i++);
+               else
+                       ++i;
+       }
+}
+
+SpirVGenerator::Id SpirVGenerator::begin_expression(Opcode opcode, Id type_id, unsigned n_args)
+{
+       bool has_result = (opcode==OP_FUNCTION_CALL || !is_scalar_type(type_id, BasicTypeDeclaration::VOID));
+       if(!constant_expression)
+       {
+               if(!current_function)
+                       throw internal_error("non-constant expression outside a function");
+
+               writer.begin_op(content.function_body, opcode, (n_args ? 1+has_result*2+n_args : 0));
+       }
+       else if(opcode==OP_COMPOSITE_CONSTRUCT)
+               writer.begin_op(content.function_body, OP_SPEC_CONSTANT_COMPOSITE, (n_args ? 1+has_result*2+n_args : 0));
+       else
+               writer.begin_op(content.function_body, OP_SPEC_CONSTANT_OP, (n_args ? 2+has_result*2+n_args : 0));
+
+       Id result_id = next_id++;
+       if(has_result)
+       {
+               writer.write(type_id);
+               writer.write(result_id);
+       }
+       if(constant_expression && opcode!=OP_COMPOSITE_CONSTRUCT)
+               writer.write(opcode);
+
+       return result_id;
+}
+
+void SpirVGenerator::end_expression(Opcode opcode)
+{
+       if(constant_expression)
+               opcode = (opcode==OP_COMPOSITE_CONSTRUCT ? OP_SPEC_CONSTANT_COMPOSITE : OP_SPEC_CONSTANT_OP);
+       writer.end_op(opcode);
+}
+
+SpirVGenerator::Id SpirVGenerator::write_expression(Opcode opcode, Id type_id, Id arg_id)
+{
+       Id result_id = begin_expression(opcode, type_id, 1);
+       writer.write(arg_id);
+       end_expression(opcode);
+       return result_id;
+}
+
+SpirVGenerator::Id SpirVGenerator::write_expression(Opcode opcode, Id type_id, Id left_id, Id right_id)
+{
+       Id result_id = begin_expression(opcode, type_id, 2);
+       writer.write(left_id);
+       writer.write(right_id);
+       end_expression(opcode);
+       return result_id;
+}
+
+void SpirVGenerator::write_deconstruct(Id elem_type_id, Id composite_id, Id *elem_ids, unsigned n_elems)
+{
+       for(unsigned i=0; i<n_elems; ++i)
+       {
+               elem_ids[i] = begin_expression(OP_COMPOSITE_EXTRACT, elem_type_id, 2);
+               writer.write(composite_id);
+               writer.write(i);
+               end_expression(OP_COMPOSITE_EXTRACT);
+       }
+}
+
+SpirVGenerator::Id SpirVGenerator::write_construct(Id type_id, const Id *elem_ids, unsigned n_elems)
+{
+       Id result_id = begin_expression(OP_COMPOSITE_CONSTRUCT, type_id, n_elems);
+       for(unsigned i=0; i<n_elems; ++i)
+               writer.write(elem_ids[i]);
+       end_expression(OP_COMPOSITE_CONSTRUCT);
+
+       return result_id;
+}
+
+BasicTypeDeclaration &SpirVGenerator::get_element_type(BasicTypeDeclaration &basic)
+{
+       if(basic.kind==BasicTypeDeclaration::BOOL || basic.kind==BasicTypeDeclaration::INT || basic.kind==BasicTypeDeclaration::FLOAT)
+               return basic;
+       else if((basic.kind==BasicTypeDeclaration::VECTOR || basic.kind==BasicTypeDeclaration::MATRIX) && basic.base_type)
+               return get_element_type(dynamic_cast<BasicTypeDeclaration &>(*basic.base_type));
+       else
+               throw invalid_argument("SpirVGenerator::get_element_type");
+}
+
+void SpirVGenerator::visit(Block &block)
+{
+       for(NodeList<Statement>::iterator i=block.body.begin(); i!=block.body.end(); ++i)
+               (*i)->visit(*this);
+}
+
+void SpirVGenerator::visit(Literal &literal)
+{
+       Id type_id = get_id(*literal.type);
+       if(spec_constant)
+               r_expression_result_id = write_constant(type_id, get_constant_key(type_id, literal.value).int_value, true);
+       else
+               r_expression_result_id = get_constant_id(type_id, literal.value);
+}
+
+void SpirVGenerator::visit(VariableReference &var)
+{
+       if(constant_expression || var.declaration->constant)
+       {
+               if(!var.declaration->constant)
+                       throw internal_error("reference to non-constant variable in constant context");
+
+               r_expression_result_id = get_id(*var.declaration);
+               return;
+       }
+       else if(!current_function)
+               throw internal_error("non-constant context outside a function");
+
+       if(composite_access)
+       {
+               r_composite_base = var.declaration;
+               r_expression_result_id = 0;
+       }
+       else if(assignment_source_id)
+       {
+               writer.write_op(content.function_body, OP_STORE, get_id(*var.declaration), assignment_source_id);
+               variable_load_ids[var.declaration] = assignment_source_id;
+               r_expression_result_id = assignment_source_id;
+       }
+       else
+               r_expression_result_id = get_load_id(*var.declaration);
+}
+
+void SpirVGenerator::visit(InterfaceBlockReference &iface)
+{
+       if(!composite_access || !current_function)
+               throw internal_error("invalid interface block reference");
+
+       r_composite_base = iface.declaration;
+       r_expression_result_id = 0;
+}
+
+void SpirVGenerator::generate_composite_access(TypeDeclaration &result_type)
+{
+       Opcode opcode;
+       Id result_type_id = get_id(result_type);
+       Id access_type_id = result_type_id;
+       if(r_composite_base)
+       {
+               if(constant_expression)
+                       throw internal_error("composite access through pointer in constant context");
+
+               Id int32_type_id = get_standard_type_id(BasicTypeDeclaration::INT, 1);
+               for(vector<unsigned>::iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
+                       *i = (*i<0x400000 ? get_constant_id(int32_type_id, static_cast<int>(*i)) : *i&0x3FFFFF);
+
+               /* Find the storage class of the base and obtain appropriate pointer type
+               for the result. */
+               const Declaration &base_decl = get_item(declared_ids, r_composite_base);
+               map<TypeKey, Id>::const_iterator i = pointer_type_ids.begin();
+               for(; (i!=pointer_type_ids.end() && i->second!=base_decl.type_id); ++i) ;
+               if(i==pointer_type_ids.end())
+                       throw internal_error("could not find storage class");
+               access_type_id = get_pointer_type_id(result_type_id, static_cast<StorageClass>(i->first.detail));
+
+               opcode = OP_ACCESS_CHAIN;
+       }
+       else if(assignment_source_id)
+               throw internal_error("assignment to temporary composite");
+       else
+       {
+               for(vector<unsigned>::iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
+                       for(map<ConstantKey, Id>::iterator j=constant_ids.begin(); (*i>=0x400000 && j!=constant_ids.end()); ++j)
+                               if(j->second==(*i&0x3FFFFF))
+                                       *i = j->first.int_value;
+
+               opcode = OP_COMPOSITE_EXTRACT;
+       }
+
+       Id access_id = begin_expression(opcode, access_type_id, 1+r_composite_chain.size());
+       writer.write(r_composite_base_id);
+       for(vector<unsigned>::const_iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
+               writer.write(*i);
+       end_expression(opcode);
+
+       if(r_composite_base)
+       {
+               if(assignment_source_id)
+               {
+                       writer.write_op(content.function_body, OP_STORE, access_id, assignment_source_id);
+                       r_expression_result_id = assignment_source_id;
+               }
+               else
+                       r_expression_result_id = write_expression(OP_LOAD, result_type_id, access_id);
+       }
+       else
+               r_expression_result_id = access_id;
+}
+
+void SpirVGenerator::visit_composite(Expression &base_expr, unsigned index, TypeDeclaration &type)
+{
+       if(!composite_access)
+       {
+               r_composite_base = 0;
+               r_composite_base_id = 0;
+               r_composite_chain.clear();
+       }
+
+       {
+               SetFlag set_composite(composite_access);
+               base_expr.visit(*this);
+       }
+
+       if(!r_composite_base_id)
+               r_composite_base_id = (r_composite_base ? get_id(*r_composite_base) : r_expression_result_id);
+
+       r_composite_chain.push_back(index);
+       if(!composite_access)
+               generate_composite_access(type);
+       else
+               r_expression_result_id = 0;
+}
+
+void SpirVGenerator::visit_isolated(Expression &expr)
+{
+       SetForScope<Id> clear_assign(assignment_source_id, 0);
+       SetFlag clear_composite(composite_access, false);
+       SetForScope<Node *> clear_base(r_composite_base, 0);
+       SetForScope<Id> clear_base_id(r_composite_base_id, 0);
+       vector<unsigned> saved_chain;
+       swap(saved_chain, r_composite_chain);
+       expr.visit(*this);
+       swap(saved_chain, r_composite_chain);
+}
+
+void SpirVGenerator::visit(MemberAccess &memacc)
+{
+       visit_composite(*memacc.left, memacc.index, *memacc.type);
+}
+
+void SpirVGenerator::visit(Swizzle &swizzle)
+{
+       if(swizzle.count==1)
+               visit_composite(*swizzle.left, swizzle.components[0], *swizzle.type);
+       else if(assignment_source_id)
+       {
+               const BasicTypeDeclaration &basic = dynamic_cast<const BasicTypeDeclaration &>(*swizzle.left->type);
+
+               unsigned mask = 0;
+               for(unsigned i=0; i<swizzle.count; ++i)
+                       mask |= 1<<swizzle.components[i];
+
+               visit_isolated(*swizzle.left);
+
+               Id combined_id = begin_expression(OP_VECTOR_SHUFFLE, get_id(*swizzle.left->type), 2+basic.size);
+               writer.write(r_expression_result_id);
+               writer.write(assignment_source_id);
+               for(unsigned i=0; i<basic.size; ++i)
+                       writer.write(i+((mask>>i)&1)*basic.size);
+               end_expression(OP_VECTOR_SHUFFLE);
+
+               SetForScope<Id> set_assign(assignment_source_id, combined_id);
+               swizzle.left->visit(*this);
+
+               r_expression_result_id = combined_id;
+       }
+       else
+       {
+               swizzle.left->visit(*this);
+               Id left_id = r_expression_result_id;
+
+               r_expression_result_id = begin_expression(OP_VECTOR_SHUFFLE, get_id(*swizzle.type), 2+swizzle.count);
+               writer.write(left_id);
+               writer.write(left_id);
+               for(unsigned i=0; i<swizzle.count; ++i)
+                       writer.write(swizzle.components[i]);
+               end_expression(OP_VECTOR_SHUFFLE);
+       }
+}
+
+void SpirVGenerator::visit(UnaryExpression &unary)
+{
+       unary.expression->visit(*this);
+
+       char oper = unary.oper->token[0];
+       char oper2 = unary.oper->token[1];
+       if(oper=='+' && !oper2)
+               return;
+
+       BasicTypeDeclaration &basic = dynamic_cast<BasicTypeDeclaration &>(*unary.expression->type);
+       BasicTypeDeclaration &elem = get_element_type(basic);
+
+       if(constant_expression && elem.kind!=BasicTypeDeclaration::BOOL && elem.kind!=BasicTypeDeclaration::INT)
+               /* SPIR-V allows constant operations on floating-point values only for
+               OpenGL kernels. */
+               throw internal_error("invalid operands for constant unary expression");
+
+       Id result_type_id = get_id(*unary.type);
+       Opcode opcode = OP_NOP;
+
+       if(oper=='!')
+               opcode = OP_LOGICAL_NOT;
+       else if(oper=='~')
+               opcode = OP_NOT;
+       else if(oper=='-' && !oper2)
+       {
+               opcode = (elem.kind==BasicTypeDeclaration::INT ? OP_S_NEGATE : OP_F_NEGATE);
+
+               if(basic.kind==BasicTypeDeclaration::MATRIX)
+               {
+                       Id column_type_id = get_id(*basic.base_type);
+                       unsigned n_columns = basic.size&0xFFFF;
+                       Id column_ids[4];
+                       write_deconstruct(column_type_id, r_expression_result_id, column_ids, n_columns);
+                       for(unsigned i=0; i<n_columns; ++i)
+                               column_ids[i] = write_expression(opcode, column_type_id, column_ids[i]);
+                       r_expression_result_id = write_construct(result_type_id, column_ids, n_columns);
+                       return;
+               }
+       }
+       else if((oper=='+' || oper=='-') && oper2==oper)
+       {
+               if(constant_expression)
+                       throw internal_error("increment or decrement in constant expression");
+
+               Id one_id = 0;
+               if(elem.kind==BasicTypeDeclaration::INT)
+               {
+                       opcode = (oper=='+' ? OP_I_ADD : OP_I_SUB);
+                       one_id = get_constant_id(get_id(elem), 1);
+               }
+               else if(elem.kind==BasicTypeDeclaration::FLOAT)
+               {
+                       opcode = (oper=='+' ? OP_F_ADD : OP_F_SUB);
+                       one_id = get_constant_id(get_id(elem), 1.0f);
+               }
+               else
+                       throw internal_error("invalid increment/decrement");
+
+               if(basic.kind==BasicTypeDeclaration::VECTOR)
+                       one_id = get_vector_constant_id(result_type_id, basic.size, one_id);
+
+               Id post_value_id = write_expression(opcode, result_type_id, r_expression_result_id, one_id);
+
+               SetForScope<Id> set_assign(assignment_source_id, post_value_id);
+               unary.expression->visit(*this);
+
+               r_expression_result_id = (unary.oper->type==Operator::POSTFIX ? r_expression_result_id : post_value_id);
+               return;
+       }
+
+       if(opcode==OP_NOP)
+               throw internal_error("unknown unary operator");
+
+       r_expression_result_id = write_expression(opcode, result_type_id, r_expression_result_id);
+}
+
+void SpirVGenerator::visit(BinaryExpression &binary)
+{
+       char oper = binary.oper->token[0];
+       if(oper=='[')
+       {
+               visit_isolated(*binary.right);
+               return visit_composite(*binary.left, 0x400000|r_expression_result_id, *binary.type);
+       }
+
+       if(assignment_source_id)
+               throw internal_error("invalid binary expression in assignment target");
+
+       BasicTypeDeclaration &basic_left = dynamic_cast<BasicTypeDeclaration &>(*binary.left->type);
+       BasicTypeDeclaration &basic_right = dynamic_cast<BasicTypeDeclaration &>(*binary.right->type);
+       // Expression resolver ensures that element types are the same
+       BasicTypeDeclaration &elem = get_element_type(basic_left);
+
+       if(constant_expression && elem.kind!=BasicTypeDeclaration::BOOL && elem.kind!=BasicTypeDeclaration::INT)
+               /* SPIR-V allows constant operations on floating-point values only for
+               OpenGL kernels. */
+               throw internal_error("invalid operands for constant binary expression");
+
+       binary.left->visit(*this);
+       Id left_id = r_expression_result_id;
+       binary.right->visit(*this);
+       Id right_id = r_expression_result_id;
+
+       Id result_type_id = get_id(*binary.type);
+       Opcode opcode = OP_NOP;
+       bool swap_operands = false;
+
+       char oper2 = binary.oper->token[1];
+       if((oper=='<' || oper=='>') && oper2!=oper)
+       {
+               if(basic_left.kind==BasicTypeDeclaration::INT)
+                       opcode = (oper=='<' ? (oper2=='=' ? OP_S_LESS_THAN_EQUAL : OP_S_LESS_THAN) :
+                               (oper2=='=' ? OP_S_GREATER_THAN_EQUAL : OP_S_GREATER_THAN));
+               else if(basic_left.kind==BasicTypeDeclaration::FLOAT)
+                       opcode = (oper=='<' ? (oper2=='=' ? OP_F_ORD_LESS_THAN_EQUAL : OP_F_ORD_LESS_THAN) :
+                               (oper2=='=' ? OP_F_ORD_GREATER_THAN_EQUAL : OP_F_ORD_GREATER_THAN));
+       }
+       else if((oper=='=' || oper=='!') && oper2=='=')
+       {
+               if(elem.kind==BasicTypeDeclaration::BOOL)
+                       opcode = (oper=='=' ? OP_LOGICAL_EQUAL : OP_LOGICAL_NOT_EQUAL);
+               else if(elem.kind==BasicTypeDeclaration::INT)
+                       opcode = (oper=='=' ? OP_I_EQUAL : OP_I_NOT_EQUAL);
+               else if(elem.kind==BasicTypeDeclaration::FLOAT)
+                       opcode = (oper=='=' ? OP_F_ORD_EQUAL : OP_F_ORD_NOT_EQUAL);
+
+               if(opcode!=OP_NOP && basic_left.base_type)
+               {
+                       /* The SPIR-V equality operations produce component-wise results, but
+                       GLSL operators return a single boolean.  Use the any/all operations to
+                       combine the results. */
+                       Opcode combine_op = (oper=='!' ? OP_ANY : OP_ALL);
+                       unsigned n_elems = basic_left.size&0xFFFF;
+                       Id bool_vec_type_id = get_standard_type_id(BasicTypeDeclaration::BOOL, n_elems);
+
+                       Id compare_id = 0;
+                       if(basic_left.kind==BasicTypeDeclaration::VECTOR)
+                               compare_id = write_expression(opcode, bool_vec_type_id, left_id, right_id);
+                       else if(basic_left.kind==BasicTypeDeclaration::MATRIX)
+                       {
+                               Id column_type_id = get_id(*basic_left.base_type);
+                               Id column_ids[8];
+                               write_deconstruct(column_type_id, left_id, column_ids, n_elems);
+                               write_deconstruct(column_type_id, right_id, column_ids+4, n_elems);
+
+                               Id column_bvec_type_id = get_standard_type_id(BasicTypeDeclaration::BOOL, basic_left.size>>16);
+                               for(unsigned i=0; i<n_elems; ++i)
+                               {
+                                       compare_id = write_expression(opcode, column_bvec_type_id, column_ids[i], column_ids[4+i]);
+                                       column_ids[i] = write_expression(combine_op, result_type_id, compare_id);;
+                               }
+
+                               compare_id = write_construct(bool_vec_type_id, column_ids, n_elems);
+                       }
+
+                       if(compare_id)
+                               r_expression_result_id = write_expression(combine_op, result_type_id, compare_id);
+                       return;
+               }
+       }
+       else if(oper2=='&' && elem.kind==BasicTypeDeclaration::BOOL)
+               opcode = OP_LOGICAL_AND;
+       else if(oper2=='|' && elem.kind==BasicTypeDeclaration::BOOL)
+               opcode = OP_LOGICAL_OR;
+       else if(oper2=='^' && elem.kind==BasicTypeDeclaration::BOOL)
+               opcode = OP_LOGICAL_NOT_EQUAL;
+       else if(oper=='&' && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_BITWISE_AND;
+       else if(oper=='|' && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_BITWISE_OR;
+       else if(oper=='^' && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_BITWISE_XOR;
+       else if(oper=='<' && oper2==oper && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_SHIFT_LEFT_LOGICAL;
+       else if(oper=='>' && oper2==oper && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_SHIFT_RIGHT_ARITHMETIC;
+       else if(oper=='%' && elem.kind==BasicTypeDeclaration::INT)
+               opcode = OP_S_MOD;
+       else if(oper=='+' || oper=='-' || oper=='*' || oper=='/')
+       {
+               Opcode elem_op = OP_NOP;
+               if(elem.kind==BasicTypeDeclaration::INT)
+                       elem_op = (oper=='+' ? OP_I_ADD : oper=='-' ? OP_I_SUB : oper=='*' ? OP_I_MUL : OP_S_DIV);
+               else if(elem.kind==BasicTypeDeclaration::FLOAT)
+                       elem_op = (oper=='+' ? OP_F_ADD : oper=='-' ? OP_F_SUB : oper=='*' ? OP_F_MUL : OP_F_DIV);
+
+               if(oper=='*' && (basic_left.base_type || basic_right.base_type) && elem.kind==BasicTypeDeclaration::FLOAT)
+               {
+                       /* Multiplication between floating-point vectors and matrices has
+                       dedicated operations. */
+                       if(basic_left.kind==BasicTypeDeclaration::MATRIX && basic_right.kind==BasicTypeDeclaration::MATRIX)
+                               opcode = OP_MATRIX_TIMES_MATRIX;
+                       else if(basic_left.kind==BasicTypeDeclaration::MATRIX || basic_right.kind==BasicTypeDeclaration::MATRIX)
+                       {
+                               if(basic_left.kind==BasicTypeDeclaration::VECTOR)
+                                       opcode = OP_VECTOR_TIMES_MATRIX;
+                               else if(basic_right.kind==BasicTypeDeclaration::VECTOR)
+                                       opcode = OP_MATRIX_TIMES_VECTOR;
+                               else
+                               {
+                                       opcode = OP_MATRIX_TIMES_SCALAR;
+                                       swap_operands = (basic_right.kind==BasicTypeDeclaration::MATRIX);
+                               }
+                       }
+                       else if(basic_left.kind==BasicTypeDeclaration::VECTOR && basic_right.kind==BasicTypeDeclaration::VECTOR)
+                               opcode = elem_op;
+                       else
+                       {
+                               opcode = OP_VECTOR_TIMES_SCALAR;
+                               swap_operands = (basic_right.kind==BasicTypeDeclaration::VECTOR);
+                       }
+               }
+               else if((basic_left.base_type!=0)!=(basic_right.base_type!=0))
+               {
+                       /* One operand is scalar and the other is a vector or a matrix.
+                       Expand the scalar to a vector of appropriate size. */
+                       Id &scalar_id = (basic_left.base_type ? right_id : left_id);
+                       BasicTypeDeclaration *vector_type = (basic_left.base_type ? &basic_left : &basic_right);
+                       if(vector_type->kind==BasicTypeDeclaration::MATRIX)
+                               vector_type = dynamic_cast<BasicTypeDeclaration *>(vector_type->base_type);
+                       Id vector_type_id = get_id(*vector_type);
+
+                       Id expanded_id = begin_expression(OP_COMPOSITE_CONSTRUCT, vector_type_id, vector_type->size);
+                       for(unsigned i=0; i<vector_type->size; ++i)
+                               writer.write(scalar_id);
+                       end_expression(OP_COMPOSITE_CONSTRUCT);
+
+                       scalar_id = expanded_id;
+
+                       if(basic_left.kind==BasicTypeDeclaration::MATRIX || basic_right.kind==BasicTypeDeclaration::MATRIX)
+                       {
+                               // Apply matrix operation column-wise.
+                               Id matrix_id = (basic_left.base_type ? left_id : right_id);
+
+                               Id column_ids[4];
+                               unsigned n_columns = (basic_left.base_type ? basic_left.size : basic_right.size)&0xFFFF;
+                               write_deconstruct(vector_type_id, matrix_id, column_ids, n_columns);
+
+                               for(unsigned i=0; i<n_columns; ++i)
+                                       column_ids[i] = write_expression(elem_op, vector_type_id, column_ids[i], expanded_id);
+
+                               r_expression_result_id = write_construct(result_type_id, column_ids, n_columns);
+                               return;
+                       }
+                       else
+                               opcode = elem_op;
+               }
+               else if(basic_left.kind==BasicTypeDeclaration::MATRIX && basic_right.kind==BasicTypeDeclaration::MATRIX)
+               {
+                       if(oper=='*')
+                               throw internal_error("non-float matrix multiplication");
+
+                       /* Other operations involving matrices need to be performed
+                       column-wise. */
+                       Id column_type_id = get_id(*basic_left.base_type);
+                       Id column_ids[8];
+
+                       unsigned n_columns = basic_left.size&0xFFFF;
+                       write_deconstruct(column_type_id, left_id, column_ids, n_columns);
+                       write_deconstruct(column_type_id, right_id, column_ids+4, n_columns);
+
+                       for(unsigned i=0; i<n_columns; ++i)
+                               column_ids[i] = write_expression(elem_op, column_type_id, column_ids[i], column_ids[4+i]);
+
+                       r_expression_result_id = write_construct(result_type_id, column_ids, n_columns);
+                       return;
+               }
+               else if(basic_left.kind==basic_right.kind)
+                       // Both operands are either scalars or vectors.
+                       opcode = elem_op;
+       }
+
+       if(opcode==OP_NOP)
+               throw internal_error("unknown binary operator");
+
+       if(swap_operands)
+               swap(left_id, right_id);
+
+       r_expression_result_id = write_expression(opcode, result_type_id, left_id, right_id);
+}
+
+void SpirVGenerator::visit(Assignment &assign)
+{
+       if(assign.oper->token[0]!='=')
+               visit(static_cast<BinaryExpression &>(assign));
+       else
+               assign.right->visit(*this);
+
+       SetForScope<Id> set_assign(assignment_source_id, r_expression_result_id);
+       assign.left->visit(*this);
+}
+
+void SpirVGenerator::visit(TernaryExpression &ternary)
+{
+       if(constant_expression)
+       {
+               ternary.condition->visit(*this);
+               Id condition_id = r_expression_result_id;
+               ternary.true_expr->visit(*this);
+               Id true_result_id = r_expression_result_id;
+               ternary.false_expr->visit(*this);
+               Id false_result_id = r_expression_result_id;
+
+               r_expression_result_id = begin_expression(OP_SELECT, get_id(*ternary.type), 3);
+               writer.write(condition_id);
+               writer.write(true_result_id);
+               writer.write(false_result_id);
+               end_expression(OP_SELECT);
+
+               return;
+       }
+
+       ternary.condition->visit(*this);
+       Id condition_id = r_expression_result_id;
+
+       Id true_label_id = next_id++;
+       Id false_label_id = next_id++;
+       Id merge_block_id = next_id++;
+       writer.write_op(content.function_body, OP_SELECTION_MERGE, merge_block_id, 0);  // Selection control (none)
+       writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, condition_id, true_label_id, false_label_id);
+
+       writer.write_op_label(true_label_id);
+       ternary.true_expr->visit(*this);
+       Id true_result_id = r_expression_result_id;
+       writer.write_op(content.function_body, OP_BRANCH, merge_block_id);
+
+       writer.write_op_label(false_label_id);
+       ternary.false_expr->visit(*this);
+       Id false_result_id = r_expression_result_id;
+
+       writer.write_op_label(merge_block_id);
+       r_expression_result_id = begin_expression(OP_PHI, get_id(*ternary.type), 4);
+       writer.write(true_result_id);
+       writer.write(true_label_id);
+       writer.write(false_result_id);
+       writer.write(false_label_id);
+       end_expression(OP_PHI);
+}
+
+void SpirVGenerator::visit(FunctionCall &call)
+{
+       if(constant_expression)
+               throw internal_error("function call in constant expression");
+       else if(assignment_source_id)
+               throw internal_error("assignment to function call");
+       else if(composite_access)
+               return visit_isolated(call);
+       else if(call.constructor && call.arguments.size()==1 && call.arguments[0]->type==call.type)
+               return call.arguments[0]->visit(*this);
+
+       vector<Id> argument_ids;
+       argument_ids.reserve(call.arguments.size());
+       for(NodeArray<Expression>::const_iterator i=call.arguments.begin(); i!=call.arguments.end(); ++i)
+       {
+               (*i)->visit(*this);
+               argument_ids.push_back(r_expression_result_id);
+       }
+
+       Id result_type_id = get_id(*call.type);
+
+       if(call.constructor)
+               visit_constructor(call, argument_ids);
+       else if(call.declaration->source==BUILTIN_SOURCE)
+       {
+               string arg_types;
+               for(NodeArray<Expression>::const_iterator i=call.arguments.begin(); i!=call.arguments.end(); ++i)
+                       if(BasicTypeDeclaration *basic_arg = dynamic_cast<BasicTypeDeclaration *>((*i)->type))
+                       {
+                               BasicTypeDeclaration &elem_arg = get_element_type(*basic_arg);
+                               switch(elem_arg.kind)
+                               {
+                               case BasicTypeDeclaration::BOOL: arg_types += 'b'; break;
+                               case BasicTypeDeclaration::INT: arg_types += 'i'; break;
+                               case BasicTypeDeclaration::FLOAT: arg_types += 'f'; break;
+                               default: arg_types += '?';
+                               }
+                       }
+
+               const BuiltinFunctionInfo *builtin_info;
+               for(builtin_info=builtin_functions; builtin_info->function[0]; ++builtin_info)
+                       if(builtin_info->function==call.name && (!builtin_info->arg_types[0] || builtin_info->arg_types==arg_types))
+                               break;
+
+               if(builtin_info->opcode)
+               {
+                       Opcode opcode;
+                       if(builtin_info->extension[0])
+                       {
+                               opcode = OP_EXT_INST;
+                               Id ext_id = import_extension(builtin_info->extension);
+
+                               r_expression_result_id = begin_expression(opcode, result_type_id);
+                               writer.write(ext_id);
+                               writer.write(builtin_info->opcode);
+                       }
+                       else
+                       {
+                               opcode = static_cast<Opcode>(builtin_info->opcode);
+                               r_expression_result_id = begin_expression(opcode, result_type_id);
+                       }
+
+                       for(unsigned i=0; i<call.arguments.size(); ++i)
+                       {
+                               if(!builtin_info->arg_order[i] || builtin_info->arg_order[i]>argument_ids.size())
+                                       throw internal_error("invalid builtin function info");
+                               writer.write(argument_ids[builtin_info->arg_order[i]-1]);
+                       }
+
+                       end_expression(opcode);
+               }
+               else if(builtin_info->handler)
+                       (this->*(builtin_info->handler))(call, argument_ids);
+               else
+                       throw internal_error("unknown builtin function "+call.name);
+       }
+       else
+       {
+               r_expression_result_id = begin_expression(OP_FUNCTION_CALL, result_type_id, 1+call.arguments.size());
+               writer.write(get_id(*call.declaration->definition));
+               for(vector<Id>::const_iterator i=argument_ids.begin(); i!=argument_ids.end(); ++i)
+                       writer.write(*i);
+               end_expression(OP_FUNCTION_CALL);
+
+               // Any global variables the called function uses might have changed value
+               set<Node *> dependencies = DependencyCollector().apply(*call.declaration->definition);
+               for(set<Node *>::const_iterator i=dependencies.begin(); i!=dependencies.end(); ++i)
+                       if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(*i))
+                               variable_load_ids.erase(var);
+       }
+}
+
+void SpirVGenerator::visit_constructor(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       Id result_type_id = get_id(*call.type);
+
+       BasicTypeDeclaration *basic = dynamic_cast<BasicTypeDeclaration *>(call.type);
+       if(!basic)
+       {
+               if(dynamic_cast<const StructDeclaration *>(call.type))
+                       r_expression_result_id = write_construct(result_type_id, &argument_ids[0], argument_ids.size());
+               else
+                       throw internal_error("unconstructable type "+call.name);
+               return;
+       }
+
+       BasicTypeDeclaration &elem = get_element_type(*basic);
+       BasicTypeDeclaration &basic_arg0 = dynamic_cast<BasicTypeDeclaration &>(*call.arguments[0]->type);
+       BasicTypeDeclaration &elem_arg0 = get_element_type(basic_arg0);
+
+       if(basic->kind==BasicTypeDeclaration::MATRIX)
+       {
+               Id col_type_id = get_id(*basic->base_type);
+               unsigned n_columns = basic->size&0xFFFF;
+               unsigned n_rows = basic->size>>16;
+
+               Id column_ids[4];
+               if(call.arguments.size()==1)
+               {
+                       // Construct diagonal matrix from a single scalar.
+                       Id zero_id = get_constant_id(get_id(elem), 0.0f);
+                       for(unsigned i=0; i<n_columns; ++i)
+                       {
+                               column_ids[i] = begin_expression(OP_COMPOSITE_CONSTRUCT, col_type_id, n_rows);;
+                               for(unsigned j=0; j<n_rows; ++j)
+                                       writer.write(j==i ? argument_ids[0] : zero_id);
+                               end_expression(OP_COMPOSITE_CONSTRUCT);
+                       }
+               }
+               else
+                       // Construct a matrix from column vectors
+                       copy(argument_ids.begin(), argument_ids.begin()+n_columns, column_ids);
+
+               r_expression_result_id = write_construct(result_type_id, column_ids, n_columns);
+       }
+       else if(basic->kind==BasicTypeDeclaration::VECTOR && (call.arguments.size()>1 || basic_arg0.kind!=BasicTypeDeclaration::VECTOR))
+       {
+               /* There's either a single scalar argument or multiple arguments
+               which make up the vector's components. */
+               if(call.arguments.size()==1)
+               {
+                       r_expression_result_id = begin_expression(OP_COMPOSITE_CONSTRUCT, result_type_id);
+                       for(unsigned i=0; i<basic->size; ++i)
+                               writer.write(argument_ids[0]);
+                       end_expression(OP_COMPOSITE_CONSTRUCT);
+               }
+               else
+                       r_expression_result_id = write_construct(result_type_id, &argument_ids[0], argument_ids.size());
+       }
+       else if(elem.kind==BasicTypeDeclaration::BOOL)
+       {
+               // Conversion to boolean is implemented as comparing against zero.
+               Id number_type_id = get_id(elem_arg0);
+               Id zero_id = (elem_arg0.kind==BasicTypeDeclaration::FLOAT ?
+                       get_constant_id(number_type_id, 0.0f) : get_constant_id(number_type_id, 0));
+               if(basic_arg0.kind==BasicTypeDeclaration::VECTOR)
+                       zero_id = get_vector_constant_id(get_id(basic_arg0), basic_arg0.size, zero_id);
+
+               Opcode opcode = (elem_arg0.kind==BasicTypeDeclaration::FLOAT ? OP_F_ORD_NOT_EQUAL : OP_I_NOT_EQUAL);
+               r_expression_result_id = write_expression(opcode, result_type_id, argument_ids[0], zero_id);
+       }
+       else if(elem_arg0.kind==BasicTypeDeclaration::BOOL)
+       {
+               /* Conversion from boolean is implemented as selecting from zero
+               or one. */
+               Id number_type_id = get_id(elem);
+               Id zero_id = (elem.kind==BasicTypeDeclaration::FLOAT ?
+                       get_constant_id(number_type_id, 0.0f) : get_constant_id(number_type_id, 0));
+               Id one_id = (elem.kind==BasicTypeDeclaration::FLOAT ?
+                       get_constant_id(number_type_id, 1.0f) : get_constant_id(number_type_id, 1));
+               if(basic->kind==BasicTypeDeclaration::VECTOR)
+               {
+                       zero_id = get_vector_constant_id(get_id(*basic), basic->size, zero_id);
+                       one_id = get_vector_constant_id(get_id(*basic), basic->size, one_id);
+               }
+
+               r_expression_result_id = begin_expression(OP_SELECT, result_type_id, 3);
+               writer.write(argument_ids[0]);
+               writer.write(zero_id);
+               writer.write(one_id);
+               end_expression(OP_SELECT);
+       }
+       else
+       {
+               // Scalar or vector conversion between types of equal size.
+               Opcode opcode;
+               if(elem.kind==BasicTypeDeclaration::INT && elem_arg0.kind==BasicTypeDeclaration::FLOAT)
+                       opcode = OP_CONVERT_F_TO_S;
+               else if(elem.kind==BasicTypeDeclaration::FLOAT && elem_arg0.kind==BasicTypeDeclaration::INT)
+                       opcode = OP_CONVERT_S_TO_F;
+               else
+                       throw internal_error("invalid conversion");
+
+               r_expression_result_id = write_expression(opcode, result_type_id, argument_ids[0]);
+       }
+}
+
+void SpirVGenerator::visit_builtin_matrix_comp_mult(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       if(argument_ids.size()!=2)
+               throw internal_error("invalid matrixCompMult call");
+
+       const BasicTypeDeclaration &basic_arg0 = dynamic_cast<const BasicTypeDeclaration &>(*call.arguments[0]->type);
+       Id column_type_id = get_id(*basic_arg0.base_type);
+       Id column_ids[8];
+
+       unsigned n_columns = basic_arg0.size&0xFFFF;
+       write_deconstruct(column_type_id, argument_ids[0], column_ids, n_columns);
+       write_deconstruct(column_type_id, argument_ids[1], column_ids+4, n_columns);
+
+       for(unsigned i=0; i<n_columns; ++i)
+               column_ids[i] = write_expression(OP_F_MUL, column_type_id, column_ids[i], column_ids[4+i]);
+
+       r_expression_result_id = write_construct(get_id(*call.type), column_ids, n_columns);
+}
+
+void SpirVGenerator::visit_builtin_texture(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       if(argument_ids.size()<2)
+               throw internal_error("invalid texture sampling call");
+
+       bool explicit_lod = (stage->type!=Stage::FRAGMENT || call.name=="textureLod");
+       Id lod_id = (!explicit_lod ? 0 : call.name=="textureLod" ? argument_ids.back() :
+               get_constant_id(get_standard_type_id(BasicTypeDeclaration::FLOAT, 1), 0.0f));
+
+       const ImageTypeDeclaration &image = dynamic_cast<const ImageTypeDeclaration &>(*call.arguments[0]->type);
+
+       Opcode opcode;
+       Id result_type_id = get_id(*call.type);
+       Id dref_id = 0;
+       if(image.shadow)
+       {
+               if(argument_ids.size()==2)
+               {
+                       const BasicTypeDeclaration &basic_arg1 = dynamic_cast<const BasicTypeDeclaration &>(*call.arguments[1]->type);
+                       dref_id = begin_expression(OP_COMPOSITE_EXTRACT, get_id(*basic_arg1.base_type), 2);
+                       writer.write(argument_ids.back());
+                       writer.write(basic_arg1.size-1);
+                       end_expression(OP_COMPOSITE_EXTRACT);
+               }
+               else
+                       dref_id = argument_ids[2];
+
+               opcode = (explicit_lod ? OP_IMAGE_SAMPLE_DREF_EXPLICIT_LOD : OP_IMAGE_SAMPLE_DREF_IMPLICIT_LOD);
+               r_expression_result_id = begin_expression(opcode, result_type_id, 3+explicit_lod*2);
+       }
+       else
+       {
+               opcode = (explicit_lod ? OP_IMAGE_SAMPLE_EXPLICIT_LOD : OP_IMAGE_SAMPLE_IMPLICIT_LOD);
+               r_expression_result_id = begin_expression(opcode, result_type_id, 2+explicit_lod*2);
+       }
+
+       for(unsigned i=0; i<2; ++i)
+               writer.write(argument_ids[i]);
+       if(dref_id)
+               writer.write(dref_id);
+       if(explicit_lod)
+       {
+               writer.write(2);  // Lod
+               writer.write(lod_id);
+       }
+
+       end_expression(opcode);
+}
+
+void SpirVGenerator::visit_builtin_texel_fetch(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       if(argument_ids.size()!=3)
+               throw internal_error("invalid texelFetch call");
+
+       r_expression_result_id = begin_expression(OP_IMAGE_FETCH, get_id(*call.type), 4);
+       for(unsigned i=0; i<2; ++i)
+               writer.write(argument_ids[i]);
+       writer.write(2);  // Lod
+       writer.write(argument_ids.back());
+       end_expression(OP_IMAGE_FETCH);
+}
+
+void SpirVGenerator::visit_builtin_interpolate(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       if(argument_ids.size()<1)
+               throw internal_error("invalid interpolate call");
+       const VariableReference *var = dynamic_cast<const VariableReference *>(call.arguments[0].get());
+       if(!var || !var->declaration || var->declaration->interface!="in")
+               throw internal_error("invalid interpolate call");
+
+       SpirVGlslStd450Opcode opcode;
+       if(call.name=="interpolateAtCentroid")
+               opcode = GLSL450_INTERPOLATE_AT_CENTROID;
+       else if(call.name=="interpolateAtSample")
+               opcode = GLSL450_INTERPOLATE_AT_SAMPLE;
+       else if(call.name=="interpolateAtOffset")
+               opcode = GLSL450_INTERPOLATE_AT_OFFSET;
+       else
+               throw internal_error("invalid interpolate call");
+
+       use_capability(CAP_INTERPOLATION_FUNCTION);
+
+       Id ext_id = import_extension("GLSL.std.450");
+       r_expression_result_id = begin_expression(OP_EXT_INST, get_id(*call.type));
+       writer.write(ext_id);
+       writer.write(opcode);
+       writer.write(get_id(*var->declaration));
+       for(vector<Id>::const_iterator i=argument_ids.begin(); ++i!=argument_ids.end(); )
+               writer.write(*i);
+       end_expression(OP_EXT_INST);
+}
+
+void SpirVGenerator::visit(ExpressionStatement &expr)
+{
+       expr.expression->visit(*this);
+}
+
+void SpirVGenerator::visit(InterfaceLayout &layout)
+{
+       interface_layouts.push_back(&layout);
+}
+
+bool SpirVGenerator::check_duplicate_type(TypeDeclaration &type)
+{
+       for(map<Node *, Declaration>::const_iterator i=declared_ids.begin(); i!=declared_ids.end(); ++i)
+               if(TypeDeclaration *type2 = dynamic_cast<TypeDeclaration *>(i->first))
+                       if(TypeComparer().apply(type, *type2))
+                       {
+                               insert_unique(declared_ids, &type, i->second);
+                               return true;
+                       }
+
+       return false;
+}
+
+bool SpirVGenerator::check_standard_type(BasicTypeDeclaration &basic)
+{
+       const BasicTypeDeclaration *elem = (basic.kind==BasicTypeDeclaration::VECTOR ?
+               dynamic_cast<const BasicTypeDeclaration *>(basic.base_type) : &basic);
+       if(!elem || elem->base_type)
+               return false;
+       if((elem->kind==BasicTypeDeclaration::INT || elem->kind==BasicTypeDeclaration::FLOAT) && elem->size!=32)
+               return false;
+
+       Id standard_id = get_standard_type_id(elem->kind, (basic.kind==BasicTypeDeclaration::VECTOR ? basic.size : 1));
+       insert_unique(declared_ids, &basic, Declaration(standard_id, 0));
+       writer.write_op_name(standard_id, basic.name);
+
+       return true;
+}
+
+void SpirVGenerator::visit(BasicTypeDeclaration &basic)
+{
+       if(check_standard_type(basic))
+               return;
+       if(check_duplicate_type(basic))
+               return;
+       // Alias types shouldn't exist at this point and arrays are handled elsewhere
+       if(basic.kind==BasicTypeDeclaration::ALIAS || basic.kind==BasicTypeDeclaration::ARRAY)
+               return;
+
+       Id type_id = allocate_id(basic, 0);
+       writer.write_op_name(type_id, basic.name);
+
+       switch(basic.kind)
+       {
+       case BasicTypeDeclaration::INT:
+               writer.write_op(content.globals, OP_TYPE_INT, type_id, basic.size, 1);
+               break;
+       case BasicTypeDeclaration::FLOAT:
+               writer.write_op(content.globals, OP_TYPE_FLOAT, type_id, basic.size);
+               break;
+       case BasicTypeDeclaration::VECTOR:
+               writer.write_op(content.globals, OP_TYPE_VECTOR, type_id, get_id(*basic.base_type), basic.size);
+               break;
+       case BasicTypeDeclaration::MATRIX:
+               writer.write_op(content.globals, OP_TYPE_MATRIX, type_id, get_id(*basic.base_type), basic.size&0xFFFF);
+               break;
+       default:
+               throw internal_error("unknown basic type");
+       }
+}
+
+void SpirVGenerator::visit(ImageTypeDeclaration &image)
+{
+       if(check_duplicate_type(image))
+               return;
+
+       Id type_id = allocate_id(image, 0);
+
+       Id image_id = (image.sampled ? next_id++ : type_id);
+       writer.begin_op(content.globals, OP_TYPE_IMAGE, 9);
+       writer.write(image_id);
+       writer.write(get_id(*image.base_type));
+       writer.write(image.dimensions-1);
+       writer.write(image.shadow);
+       writer.write(image.array);
+       writer.write(false);  // Multisample
+       writer.write(image.sampled ? 1 : 2);
+       writer.write(0);  // Format (unknown)
+       writer.end_op(OP_TYPE_IMAGE);
+
+       if(image.sampled)
+       {
+               writer.write_op_name(type_id, image.name);
+               writer.write_op(content.globals, OP_TYPE_SAMPLED_IMAGE, type_id, image_id);
+       }
+
+       if(image.dimensions==ImageTypeDeclaration::ONE)
+               use_capability(image.sampled ? CAP_SAMPLED_1D : CAP_IMAGE_1D);
+       else if(image.dimensions==ImageTypeDeclaration::CUBE && image.array)
+               use_capability(image.sampled ? CAP_SAMPLED_CUBE_ARRAY : CAP_IMAGE_CUBE_ARRAY);
+}
+
+void SpirVGenerator::visit(StructDeclaration &strct)
+{
+       if(check_duplicate_type(strct))
+               return;
+
+       Id type_id = allocate_id(strct, 0);
+       writer.write_op_name(type_id, strct.name);
+
+       if(strct.interface_block)
+               writer.write_op_decorate(type_id, DECO_BLOCK);
+
+       bool builtin = (strct.interface_block && !strct.interface_block->block_name.compare(0, 3, "gl_"));
+       vector<Id> member_type_ids;
+       member_type_ids.reserve(strct.members.body.size());
+       for(NodeList<Statement>::const_iterator i=strct.members.body.begin(); i!=strct.members.body.end(); ++i)
+       {
+               const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(i->get());
+               if(!var)
+                       continue;
+
+               unsigned index = member_type_ids.size();
+               member_type_ids.push_back(get_variable_type_id(*var));
+
+               writer.write_op_member_name(type_id, index, var->name);
+
+               if(builtin)
+               {
+                       BuiltinSemantic semantic = get_builtin_semantic(var->name);
+                       writer.write_op_member_decorate(type_id, index, DECO_BUILTIN, semantic);
+               }
+               else
+               {
+                       if(var->layout)
+                       {
+                               const vector<Layout::Qualifier> &qualifiers = var->layout->qualifiers;
+                               for(vector<Layout::Qualifier>::const_iterator j=qualifiers.begin(); j!=qualifiers.end(); ++j)
+                               {
+                                       if(j->name=="offset")
+                                               writer.write_op_member_decorate(type_id, index, DECO_OFFSET, j->value);
+                                       else if(j->name=="column_major")
+                                               writer.write_op_member_decorate(type_id, index, DECO_COL_MAJOR);
+                                       else if(j->name=="row_major")
+                                               writer.write_op_member_decorate(type_id, index, DECO_ROW_MAJOR);
+                               }
+                       }
+
+                       const BasicTypeDeclaration *basic = dynamic_cast<const BasicTypeDeclaration *>(var->type_declaration);
+                       while(basic && basic->kind==BasicTypeDeclaration::ARRAY)
+                               basic = dynamic_cast<const BasicTypeDeclaration *>(basic->base_type);
+                       if(basic && basic->kind==BasicTypeDeclaration::MATRIX)
+                       {
+                               unsigned stride = MemoryRequirementsCalculator().apply(*basic->base_type).stride;
+                               writer.write_op_member_decorate(type_id, index, DECO_MATRIX_STRIDE, stride);
+                       }
+               }
+       }
+
+       writer.begin_op(content.globals, OP_TYPE_STRUCT);
+       writer.write(type_id);
+       for(vector<Id>::const_iterator i=member_type_ids.begin(); i!=member_type_ids.end(); ++i)
+               writer.write(*i);
+       writer.end_op(OP_TYPE_STRUCT);
+}
+
+void SpirVGenerator::visit(VariableDeclaration &var)
+{
+       const vector<Layout::Qualifier> *layout_ql = (var.layout ? &var.layout->qualifiers : 0);
+
+       int spec_id = -1;
+       if(layout_ql)
+       {
+               for(vector<Layout::Qualifier>::const_iterator i=layout_ql->begin(); (spec_id<0 && i!=layout_ql->end()); ++i)
+                       if(i->name=="constant_id")
+                               spec_id = i->value;
+       }
+
+       Id type_id = get_variable_type_id(var);
+       Id var_id;
+
+       if(var.constant)
+       {
+               if(!var.init_expression)
+                       throw internal_error("const variable without initializer");
+
+               SetFlag set_const(constant_expression);
+               SetFlag set_spec(spec_constant, spec_id>=0);
+               r_expression_result_id = 0;
+               var.init_expression->visit(*this);
+               var_id = r_expression_result_id;
+               insert_unique(declared_ids, &var, Declaration(var_id, type_id));
+               writer.write_op_decorate(var_id, DECO_SPEC_ID, spec_id);
+
+               /* It's unclear what should be done if a specialization constant is
+               initialized with anything other than a literal.  GLSL doesn't seem to
+               prohibit that but SPIR-V says OpSpecConstantOp can't be updated via
+               specialization. */
+       }
+       else
+       {
+               StorageClass storage = (current_function ? STORAGE_FUNCTION : get_interface_storage(var.interface, false));
+               Id ptr_type_id = get_pointer_type_id(type_id, storage);
+               if(var.interface=="uniform")
+               {
+                       Id &uni_id = declared_uniform_ids["v"+var.name];
+                       if(uni_id)
+                       {
+                               insert_unique(declared_ids, &var, Declaration(uni_id, ptr_type_id));
+                               return;
+                       }
+
+                       uni_id = var_id = allocate_id(var, ptr_type_id);
+               }
+               else
+                       var_id = allocate_id(var, (var.constant ? type_id : ptr_type_id));
+
+               Id init_id = 0;
+               if(var.init_expression)
+               {
+                       SetFlag set_const(constant_expression, !current_function);
+                       r_expression_result_id = 0;
+                       var.init_expression->visit(*this);
+                       init_id = r_expression_result_id;
+               }
+
+               vector<Word> &target = (current_function ? content.locals : content.globals);
+               writer.begin_op(target, OP_VARIABLE, 4+(init_id && !current_function));
+               writer.write(ptr_type_id);
+               writer.write(var_id);
+               writer.write(storage);
+               if(init_id && !current_function)
+                       writer.write(init_id);
+               writer.end_op(OP_VARIABLE);
+
+               if(layout_ql)
+               {
+                       for(vector<Layout::Qualifier>::const_iterator i=layout_ql->begin(); i!=layout_ql->end(); ++i)
+                       {
+                               if(i->name=="location")
+                                       writer.write_op_decorate(var_id, DECO_LOCATION, i->value);
+                               else if(i->name=="set")
+                                       writer.write_op_decorate(var_id, DECO_DESCRIPTOR_SET, i->value);
+                               else if(i->name=="binding")
+                                       writer.write_op_decorate(var_id, DECO_BINDING, i->value);
+                       }
+               }
+
+               if(init_id && current_function)
+                       writer.write_op(content.function_body, OP_STORE, var_id, init_id);
+       }
+
+       writer.write_op_name(var_id, var.name);
+}
+
+void SpirVGenerator::visit(InterfaceBlock &iface)
+{
+       StorageClass storage = get_interface_storage(iface.interface, true);
+       Id type_id;
+       if(iface.array)
+               type_id = get_array_type_id(*iface.struct_declaration, 0);
+       else
+               type_id = get_id(*iface.struct_declaration);
+       Id ptr_type_id = get_pointer_type_id(type_id, storage);
+
+       Id block_id;
+       if(iface.interface=="uniform")
+       {
+               Id &uni_id = declared_uniform_ids["b"+iface.block_name];
+               if(uni_id)
+               {
+                       insert_unique(declared_ids, &iface, Declaration(uni_id, ptr_type_id));
+                       return;
+               }
+
+               uni_id = block_id = allocate_id(iface, ptr_type_id);
+       }
+       else
+               block_id = allocate_id(iface, ptr_type_id);
+       writer.write_op_name(block_id, iface.instance_name);
+
+       writer.write_op(content.globals, OP_VARIABLE, ptr_type_id, block_id, storage);
+
+       if(iface.layout)
+       {
+               const vector<Layout::Qualifier> &qualifiers = iface.layout->qualifiers;
+               for(vector<Layout::Qualifier>::const_iterator i=qualifiers.begin(); i!=qualifiers.end(); ++i)
+                       if(i->name=="binding")
+                               writer.write_op_decorate(block_id, DECO_BINDING, i->value);
+       }
+}
+
+void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
+{
+       writer.begin_op(content.entry_points, OP_ENTRY_POINT);
+       switch(stage->type)
+       {
+       case Stage::VERTEX: writer.write(0); break;
+       case Stage::GEOMETRY: writer.write(3); break;
+       case Stage::FRAGMENT: writer.write(4); break;
+       default: throw internal_error("unknown stage");
+       }
+       writer.write(func_id);
+       writer.write_string(func.name);
+
+       set<Node *> dependencies = DependencyCollector().apply(func);
+       for(set<Node *>::const_iterator i=dependencies.begin(); i!=dependencies.end(); ++i)
+       {
+               if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(*i))
+               {
+                       if(!var->interface.empty())
+                               writer.write(get_id(**i));
+               }
+               else if(dynamic_cast<InterfaceBlock *>(*i))
+                       writer.write(get_id(**i));
+       }
+
+       writer.end_op(OP_ENTRY_POINT);
+
+       if(stage->type==Stage::FRAGMENT)
+               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_ORIGIN_LOWER_LEFT);
+       else if(stage->type==Stage::GEOMETRY)
+               use_capability(CAP_GEOMETRY);
+
+       for(vector<const InterfaceLayout *>::const_iterator i=interface_layouts.begin(); i!=interface_layouts.end(); ++i)
+       {
+               const vector<Layout::Qualifier> &qualifiers = (*i)->layout.qualifiers;
+               for(vector<Layout::Qualifier>::const_iterator j=qualifiers.begin(); j!=qualifiers.end(); ++j)
+               {
+                       if(j->name=="point")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id,
+                                       ((*i)->interface=="in" ? EXEC_INPUT_POINTS : EXEC_OUTPUT_POINTS));
+                       else if(j->name=="lines")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_LINES);
+                       else if(j->name=="lines_adjacency")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_LINES_ADJACENCY);
+                       else if(j->name=="triangles")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_TRIANGLES);
+                       else if(j->name=="triangles_adjacency")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_TRIANGLES_ADJACENCY);
+                       else if(j->name=="line_strip")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_LINE_STRIP);
+                       else if(j->name=="triangle_strip")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_TRIANGLE_STRIP);
+                       else if(j->name=="max_vertices")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_VERTICES, j->value);
+               }
+       }
+}
+
+void SpirVGenerator::visit(FunctionDeclaration &func)
+{
+       if(func.source==BUILTIN_SOURCE || func.definition!=&func)
+               return;
+
+       Id return_type_id = get_id(*func.return_type_declaration);
+       vector<unsigned> param_type_ids;
+       param_type_ids.reserve(func.parameters.size());
+       for(NodeArray<VariableDeclaration>::const_iterator i=func.parameters.begin(); i!=func.parameters.end(); ++i)
+               param_type_ids.push_back(get_variable_type_id(**i));
+
+       string sig_with_return = func.return_type+func.signature;
+       Id &type_id = function_type_ids[sig_with_return];
+       if(!type_id)
+       {
+               type_id = next_id++;
+               writer.begin_op(content.globals, OP_TYPE_FUNCTION);
+               writer.write(type_id);
+               writer.write(return_type_id);
+               for(vector<unsigned>::const_iterator i=param_type_ids.begin(); i!=param_type_ids.end(); ++i)
+                       writer.write(*i);
+               writer.end_op(OP_TYPE_FUNCTION);
+
+               writer.write_op_name(type_id, sig_with_return);
+       }
+
+       Id func_id = allocate_id(func, type_id);
+       writer.write_op_name(func_id, func.name+func.signature);
+
+       if(func.name=="main")
+               visit_entry_point(func, func_id);
+
+       writer.begin_op(content.functions, OP_FUNCTION, 5);
+       writer.write(return_type_id);
+       writer.write(func_id);
+       writer.write(0);  // Function control flags (none)
+       writer.write(type_id);
+       writer.end_op(OP_FUNCTION);
+
+       for(unsigned i=0; i<func.parameters.size(); ++i)
+       {
+               Id param_id = allocate_id(*func.parameters[i], param_type_ids[i]);
+               writer.write_op(content.functions, OP_FUNCTION_PARAMETER, param_type_ids[i], param_id);
+               // TODO This is probably incorrect if the parameter is assigned to.
+               variable_load_ids[func.parameters[i].get()] = param_id;
+       }
+
+       writer.begin_function_body(next_id++);
+       SetForScope<FunctionDeclaration *> set_func(current_function, &func);
+       func.body.visit(*this);
+
+       if(writer.has_current_block())
+       {
+               if(!reachable)
+                       writer.write_op(content.function_body, OP_UNREACHABLE);
+               else
+               {
+                       const BasicTypeDeclaration *basic_return = dynamic_cast<const BasicTypeDeclaration *>(func.return_type_declaration);
+                       if(basic_return && basic_return->kind==BasicTypeDeclaration::VOID)
+                               writer.write_op(content.function_body, OP_RETURN);
+                       else
+                               throw internal_error("missing return in non-void function");
+               }
+       }
+       writer.end_function_body();
+       variable_load_ids.clear();
+}
+
+void SpirVGenerator::visit(Conditional &cond)
+{
+       cond.condition->visit(*this);
+
+       Id true_label_id = next_id++;
+       Id merge_block_id = next_id++;
+       Id false_label_id = (cond.else_body.body.empty() ? merge_block_id : next_id++);
+       writer.write_op(content.function_body, OP_SELECTION_MERGE, merge_block_id, 0);  // Selection control (none)
+       writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, r_expression_result_id, true_label_id, false_label_id);
+
+       writer.write_op_label(true_label_id);
+       cond.body.visit(*this);
+       if(writer.has_current_block())
+               writer.write_op(content.function_body, OP_BRANCH, merge_block_id);
+
+       bool reachable_if_true = reachable;
+
+       reachable = true;
+       if(!cond.else_body.body.empty())
+       {
+               writer.write_op_label(false_label_id);
+               cond.else_body.visit(*this);
+               reachable |= reachable_if_true;
+       }
+
+       writer.write_op_label(merge_block_id);
+       prune_loads(true_label_id);
+}
+
+void SpirVGenerator::visit(Iteration &iter)
+{
+       if(iter.init_statement)
+               iter.init_statement->visit(*this);
+
+       Id header_id = next_id++;
+       Id continue_id = next_id++;
+       Id merge_block_id = next_id++;
+
+       SetForScope<Id> set_merge(loop_merge_block_id, merge_block_id);
+       SetForScope<Id> set_continue(loop_continue_target_id, continue_id);
+
+       writer.write_op_label(header_id);
+       writer.write_op(content.function_body, OP_LOOP_MERGE, merge_block_id, continue_id, 0);  // Loop control (none)
+
+       Id body_id = next_id++;
+       if(iter.condition)
+       {
+               writer.write_op_label(next_id++);
+               iter.condition->visit(*this);
+               writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, r_expression_result_id, body_id, merge_block_id);
+       }
+
+       writer.write_op_label(body_id);
+       iter.body.visit(*this);
+
+       writer.write_op_label(continue_id);
+       if(iter.loop_expression)
+               iter.loop_expression->visit(*this);
+       writer.write_op(content.function_body, OP_BRANCH, header_id);
+
+       writer.write_op_label(merge_block_id);
+       prune_loads(header_id);
+       reachable = true;
+}
+
+void SpirVGenerator::visit(Return &ret)
+{
+       if(ret.expression)
+       {
+               ret.expression->visit(*this);
+               writer.write_op(content.function_body, OP_RETURN_VALUE, r_expression_result_id);
+       }
+       else
+               writer.write_op(content.function_body, OP_RETURN);
+       reachable = false;
+}
+
+void SpirVGenerator::visit(Jump &jump)
+{
+       if(jump.keyword=="discard")
+               writer.write_op(content.function_body, OP_KILL);
+       else if(jump.keyword=="break")
+               writer.write_op(content.function_body, OP_BRANCH, loop_merge_block_id);
+       else if(jump.keyword=="continue")
+               writer.write_op(content.function_body, OP_BRANCH, loop_continue_target_id);
+       else
+               throw internal_error("unknown jump");
+       reachable = false;
+}
+
+
+bool SpirVGenerator::TypeKey::operator<(const TypeKey &other) const
+{
+       if(type_id!=other.type_id)
+               return type_id<other.type_id;
+       return detail<other.detail;
+}
+
+
+bool SpirVGenerator::ConstantKey::operator<(const ConstantKey &other) const
+{
+       if(type_id!=other.type_id)
+               return type_id<other.type_id;
+       return int_value<other.int_value;
+}
+
+} // namespace SL
+} // namespace GL
+} // namespace Msp
diff --git a/source/glsl/spirv.h b/source/glsl/spirv.h
new file mode 100644 (file)
index 0000000..21f0ed5
--- /dev/null
@@ -0,0 +1,172 @@
+#ifndef MSP_GL_SL_SPIRV
+#define MSP_GL_SL_SPIRV
+
+#include <map>
+#include <string>
+#include <vector>
+#include "spirvconstants.h"
+#include "spirvwriter.h"
+#include "visitor.h"
+
+namespace Msp {
+namespace GL {
+namespace SL {
+
+/** Creates SPIR-V binary from a module. */
+class SpirVGenerator: private NodeVisitor
+{
+private:
+       typedef SpirVCapability Capability;
+       typedef SpirVStorageClass StorageClass;
+       typedef SpirVOpcode Opcode;
+       typedef SpirVDecoration Decoration;
+       typedef SpirVBuiltin BuiltinSemantic;
+       typedef SpirVContent::Word Word;
+       typedef SpirVWriter::Id Id;
+
+       struct BuiltinFunctionInfo
+       {
+               char function[22];
+               char arg_types[5];
+               char extension[13];
+               Word opcode;
+               UInt8 arg_order[4];
+               void (SpirVGenerator::*handler)(FunctionCall &, const std::vector<Id> &);
+       };
+
+       struct Declaration
+       {
+               Id id;
+               Id type_id;
+
+               Declaration(Id i, Id t): id(i), type_id(t) { }
+       };
+
+       struct TypeKey
+       {
+               Id type_id;
+               unsigned detail;
+
+               TypeKey(Id i, unsigned d): type_id(i), detail(d) { }
+
+               bool operator<(const TypeKey &) const;
+       };
+
+       struct ConstantKey
+       {
+               Id type_id;
+               union
+               {
+                       int int_value;
+                       float float_value;
+               };
+
+               ConstantKey(Id t, int i): type_id(t), int_value(i) { }
+               ConstantKey(Id t, float f): type_id(t), float_value(f) { }
+
+               bool operator<(const ConstantKey &) const;
+       };
+
+       Stage *stage;
+       FunctionDeclaration *current_function;
+       std::vector<const InterfaceLayout *> interface_layouts;
+       SpirVContent content;
+       SpirVWriter writer;
+       std::set<Capability> used_capabilities;
+       std::map<std::string, Id> imported_extension_ids;
+       std::map<Node *, Declaration> declared_ids;
+       std::map<std::string, Id> declared_uniform_ids;
+       std::map<TypeKey, Id> standard_type_ids;
+       std::map<TypeKey, Id> array_type_ids;
+       std::map<TypeKey, Id> pointer_type_ids;
+       std::map<std::string, Id> function_type_ids;
+       std::map<ConstantKey, Id> constant_ids;
+       std::map<const VariableDeclaration *, Id> variable_load_ids;
+       Id next_id;
+       Id r_expression_result_id;
+       bool constant_expression;
+       bool spec_constant;
+       bool reachable;
+       bool composite_access;
+       Id r_composite_base_id;
+       Node *r_composite_base;
+       std::vector<unsigned> r_composite_chain;
+       Id assignment_source_id;
+       Id loop_merge_block_id;
+       Id loop_continue_target_id;
+
+       static const BuiltinFunctionInfo builtin_functions[];
+
+public:
+       SpirVGenerator();
+
+       void apply(Module &);
+       const std::vector<Word> &get_code() const { return content.code; }
+
+private:
+       static StorageClass get_interface_storage(const std::string &, bool);
+       static SpirVBuiltin get_builtin_semantic(const std::string &);
+       void use_capability(Capability);
+       Id import_extension(const std::string &);
+       Id get_id(Node &) const;
+       Id allocate_id(Node &, Id);
+       Id write_constant(Id, Word, bool);
+       static ConstantKey get_constant_key(Id, const Variant &value);
+       Id get_constant_id(Id, const Variant &value);
+       Id get_vector_constant_id(Id, unsigned, Id);
+       Id get_standard_type_id(BasicTypeDeclaration::Kind, unsigned);
+       bool is_scalar_type(Id, BasicTypeDeclaration::Kind) const;
+       Id get_array_type_id(TypeDeclaration &, unsigned);
+       Id get_pointer_type_id(Id, StorageClass);
+       Id get_variable_type_id(const VariableDeclaration &);
+       Id get_load_id(VariableDeclaration &);
+       void prune_loads(Id);
+       Id begin_expression(Opcode, Id, unsigned = 0);
+       void end_expression(Opcode);
+       Id write_expression(Opcode, Id, Id);
+       Id write_expression(Opcode, Id, Id, Id);
+       void write_deconstruct(Id, Id, Id *, unsigned);
+       Id write_construct(Id, const Id *, unsigned);
+       static BasicTypeDeclaration &get_element_type(BasicTypeDeclaration &);
+
+       virtual void visit(Block &);
+       virtual void visit(Literal &);
+       virtual void visit(VariableReference &);
+       virtual void visit(InterfaceBlockReference &);
+       void generate_composite_access(TypeDeclaration &);
+       void visit_composite(Expression &, unsigned, TypeDeclaration &);
+       void visit_isolated(Expression &);
+       virtual void visit(MemberAccess &);
+       virtual void visit(Swizzle &);
+       virtual void visit(UnaryExpression &);
+       virtual void visit(BinaryExpression &);
+       virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
+       virtual void visit(FunctionCall &);
+       void visit_constructor(FunctionCall &, const std::vector<Id> &);
+       void visit_builtin_matrix_comp_mult(FunctionCall &, const std::vector<Id> &);
+       void visit_builtin_texture(FunctionCall &, const std::vector<Id> &);
+       void visit_builtin_texel_fetch(FunctionCall &, const std::vector<Id> &);
+       void visit_builtin_interpolate(FunctionCall &, const std::vector<Id> &);
+       virtual void visit(ExpressionStatement &);
+       virtual void visit(InterfaceLayout &);
+       bool check_duplicate_type(TypeDeclaration &);
+       bool check_standard_type(BasicTypeDeclaration &);
+       virtual void visit(BasicTypeDeclaration &);
+       virtual void visit(ImageTypeDeclaration &);
+       virtual void visit(StructDeclaration &);
+       virtual void visit(VariableDeclaration &);
+       virtual void visit(InterfaceBlock &);
+       void visit_entry_point(FunctionDeclaration &, Id);
+       virtual void visit(FunctionDeclaration &);
+       virtual void visit(Conditional &);
+       virtual void visit(Iteration &);
+       virtual void visit(Return &);
+       virtual void visit(Jump &);
+};
+
+} // namespace SL
+} // namespace GL
+} // namespace Msp
+
+#endif
diff --git a/source/glsl/spirvconstants.h b/source/glsl/spirvconstants.h
new file mode 100644 (file)
index 0000000..b981d19
--- /dev/null
@@ -0,0 +1,275 @@
+#ifndef MSP_GL_SL_SPIRVCONSTANTS_H_
+#define MSP_GL_SL_SPIRVCONSTANTS_H_
+
+namespace Msp {
+namespace GL {
+namespace SL {
+
+enum SpirVConstants
+{
+       SPIRV_MAGIC = 0x07230203,
+       SPIRV_MAGIC_REVERSED = 0x03022307
+};
+
+enum SpirVOpcode
+{
+       OP_NOP = 1,
+       OP_NAME = 5,
+       OP_MEMBER_NAME = 6,
+       OP_EXT_INST_IMPORT = 11,
+       OP_EXT_INST = 12,
+       OP_MEMORY_MODEL = 14,
+       OP_ENTRY_POINT = 15,
+       OP_EXECUTION_MODE = 16,
+       OP_CAPABILITY = 17,
+       OP_TYPE_VOID = 19,
+       OP_TYPE_BOOL = 20,
+       OP_TYPE_INT = 21,
+       OP_TYPE_FLOAT = 22,
+       OP_TYPE_VECTOR = 23,
+       OP_TYPE_MATRIX = 24,
+       OP_TYPE_IMAGE = 25,
+       OP_TYPE_SAMPLED_IMAGE = 27,
+       OP_TYPE_ARRAY = 28,
+       OP_TYPE_RUNTIME_ARRAY = 29,
+       OP_TYPE_STRUCT = 30,
+       OP_TYPE_POINTER = 32,
+       OP_TYPE_FUNCTION = 33,
+       OP_CONSTANT_TRUE = 41,
+       OP_CONSTANT_FALSE = 42,
+       OP_CONSTANT = 43,
+       OP_CONSTANT_COMPOSITE = 44,
+       OP_SPEC_CONSTANT_TRUE = 48,
+       OP_SPEC_CONSTANT_FALSE = 49,
+       OP_SPEC_CONSTANT = 50,
+       OP_SPEC_CONSTANT_COMPOSITE = 51,
+       OP_SPEC_CONSTANT_OP = 52,
+       OP_FUNCTION = 54,
+       OP_FUNCTION_PARAMETER = 55,
+       OP_FUNCTION_END = 56,
+       OP_FUNCTION_CALL = 57,
+       OP_VARIABLE = 59,
+       OP_LOAD = 61,
+       OP_STORE = 62,
+       OP_ACCESS_CHAIN = 65,
+       OP_DECORATE = 71,
+       OP_MEMBER_DECORATE = 72,
+       OP_VECTOR_SHUFFLE = 79,
+       OP_COMPOSITE_CONSTRUCT = 80,
+       OP_COMPOSITE_EXTRACT = 81,
+       OP_TRANSPOSE = 84,
+       OP_IMAGE_SAMPLE_IMPLICIT_LOD = 87,
+       OP_IMAGE_SAMPLE_EXPLICIT_LOD = 88,
+       OP_IMAGE_SAMPLE_DREF_IMPLICIT_LOD = 89,
+       OP_IMAGE_SAMPLE_DREF_EXPLICIT_LOD = 89,
+       OP_IMAGE_FETCH = 95,
+       OP_IMAGE_QUERY_SIZE_LOD = 103,
+       OP_CONVERT_F_TO_S = 110,
+       OP_CONVERT_S_TO_F = 111,
+       OP_S_NEGATE = 126,
+       OP_F_NEGATE = 127,
+       OP_I_ADD = 128,
+       OP_F_ADD = 129,
+       OP_I_SUB = 130,
+       OP_F_SUB = 131,
+       OP_I_MUL = 132,
+       OP_F_MUL = 133,
+       OP_S_DIV = 135,
+       OP_F_DIV = 136,
+       OP_S_MOD = 139,
+       OP_F_MOD = 141,
+       OP_VECTOR_TIMES_SCALAR = 142,
+       OP_MATRIX_TIMES_SCALAR = 143,
+       OP_VECTOR_TIMES_MATRIX = 144,
+       OP_MATRIX_TIMES_VECTOR = 145,
+       OP_MATRIX_TIMES_MATRIX = 146,
+       OP_OUTER_PRODUCT = 147,
+       OP_DOT = 148,
+       OP_ANY = 154,
+       OP_ALL = 155,
+       OP_IS_NAN = 156,
+       OP_IS_INF = 157,
+       OP_LOGICAL_EQUAL = 164,
+       OP_LOGICAL_NOT_EQUAL = 165,
+       OP_LOGICAL_OR = 166,
+       OP_LOGICAL_AND = 167,
+       OP_LOGICAL_NOT = 168,
+       OP_SELECT = 169,
+       OP_I_EQUAL = 170,
+       OP_I_NOT_EQUAL = 171,
+       OP_S_GREATER_THAN = 173,
+       OP_S_GREATER_THAN_EQUAL = 175,
+       OP_S_LESS_THAN = 177,
+       OP_S_LESS_THAN_EQUAL = 179,
+       OP_F_ORD_EQUAL = 180,
+       OP_F_ORD_NOT_EQUAL = 182,
+       OP_F_ORD_LESS_THAN = 184,
+       OP_F_ORD_GREATER_THAN = 186,
+       OP_F_ORD_LESS_THAN_EQUAL = 188,
+       OP_F_ORD_GREATER_THAN_EQUAL = 190,
+       OP_SHIFT_RIGHT_ARITHMETIC = 195,
+       OP_SHIFT_LEFT_LOGICAL = 196,
+       OP_BITWISE_OR = 197,
+       OP_BITWISE_XOR = 198,
+       OP_BITWISE_AND = 199,
+       OP_NOT = 200,
+       OP_BIT_FIELD_INSERT = 201,
+       OP_BIT_FIELD_S_EXTRACT = 202,
+       OP_BIT_REVERSE = 204,
+       OP_BIT_COUNT = 205,
+       OP_DP_DX = 207,
+       OP_DP_DY = 208,
+       OP_FWIDTH = 209,
+       OP_DP_DX_FINE = 210,
+       OP_DP_DY_FINE = 211,
+       OP_FWIDTH_FINE = 212,
+       OP_DP_DX_COARSE = 213,
+       OP_DP_DY_COARSE = 214,
+       OP_FWIDTH_COARSE = 215,
+       OP_EMIT_VERTEX = 218,
+       OP_END_PRIMITIVE = 219,
+       OP_PHI = 245,
+       OP_LOOP_MERGE = 246,
+       OP_SELECTION_MERGE = 247,
+       OP_LABEL = 248,
+       OP_BRANCH = 249,
+       OP_BRANCH_CONDITIONAL = 250,
+       OP_KILL = 252,
+       OP_RETURN = 253,
+       OP_RETURN_VALUE = 254,
+       OP_UNREACHABLE = 255
+};
+
+enum SpirVCapability
+{
+       CAP_SHADER = 1,
+       CAP_GEOMETRY = 2,
+       CAP_IMAGE_CUBE_ARRAY = 34,
+       CAP_SAMPLED_1D = 43,
+       CAP_IMAGE_1D = 44,
+       CAP_SAMPLED_CUBE_ARRAY = 45,
+       CAP_INTERPOLATION_FUNCTION = 52
+};
+
+enum SpirVExecutionMode
+{
+       EXEC_ORIGIN_LOWER_LEFT = 8,
+       EXEC_INPUT_POINTS = 19,
+       EXEC_INPUT_LINES = 20,
+       EXEC_INPUT_LINES_ADJACENCY = 21,
+       EXEC_TRIANGLES = 22,
+       EXEC_INPUT_TRIANGLES_ADJACENCY = 23,
+       EXEC_OUTPUT_VERTICES = 26,
+       EXEC_OUTPUT_POINTS = 27,
+       EXEC_OUTPUT_LINE_STRIP = 28,
+       EXEC_OUTPUT_TRIANGLE_STRIP = 29
+};
+
+enum SpirVStorageClass
+{
+       STORAGE_UNIFORM_CONSTANT = 0,
+       STORAGE_INPUT = 1,
+       STORAGE_UNIFORM = 2,
+       STORAGE_OUTPUT = 3,
+       STORAGE_PRIVATE = 6,
+       STORAGE_FUNCTION = 7
+};
+
+enum SpirVDecoration
+{
+       DECO_SPEC_ID = 1,
+       DECO_BLOCK = 2,
+       DECO_ROW_MAJOR = 4,
+       DECO_COL_MAJOR = 5,
+       DECO_ARRAY_STRIDE = 6,
+       DECO_MATRIX_STRIDE = 7,
+       DECO_BUILTIN = 11,
+       DECO_LOCATION = 30,
+       DECO_BINDING = 33,
+       DECO_DESCRIPTOR_SET = 34,
+       DECO_OFFSET = 35
+};
+
+enum SpirVBuiltin
+{
+       BUILTIN_POSITION = 0,
+       BUILTIN_POINT_SIZE = 1,
+       BUILTIN_CLIP_DISTANCE = 3,
+       BUILTIN_VERTEX_ID = 5,
+       BUILTIN_INSTANCE_ID = 6,
+       BUILTIN_PRIMITIVE_ID = 7,
+       BUILTIN_INVOCATION_ID = 8,
+       BUILTIN_LAYER = 9,
+       BUILTIN_FRAG_COORD = 15,
+       BUILTIN_POINT_COORD = 16,
+       BUILTIN_FRONT_FACING = 17,
+       BUILTIN_SAMPLE_ID = 18,
+       BUILTIN_SAMPLE_POSITION = 19,
+       BUILTIN_FRAG_DEPTH = 22
+};
+
+enum SpirVGlslStd450Opcode
+{
+       GLSL450_ROUND = 1,
+       GLSL450_ROUND_EVEN = 2,
+       GLSL450_TRUNC = 3,
+       GLSL450_F_ABS = 4,
+       GLSL450_S_ABS = 5,
+       GLSL450_F_SIGN = 6,
+       GLSL450_S_SIGN = 7,
+       GLSL450_FLOOR = 8,
+       GLSL450_CEIL = 9,
+       GLSL450_FRACT = 10,
+       GLSL450_RADIANS = 11,
+       GLSL450_DEGREES = 12,
+       GLSL450_SIN = 13,
+       GLSL450_COS = 14,
+       GLSL450_TAN = 15,
+       GLSL450_ASIN = 16,
+       GLSL450_ACOS = 17,
+       GLSL450_ATAN = 18,
+       GLSL450_SINH = 19,
+       GLSL450_COSH = 20,
+       GLSL450_TANH = 21,
+       GLSL450_ASINH = 22,
+       GLSL450_ACOSH = 23,
+       GLSL450_ATANH = 24,
+       GLSL450_ATAN2 = 25,
+       GLSL450_POW = 26,
+       GLSL450_EXP = 27,
+       GLSL450_LOG = 28,
+       GLSL450_EXP2 = 29,
+       GLSL450_LOG2 = 30,
+       GLSL450_SQRT = 31,
+       GLSL450_INVERSE_SQRT = 32,
+       GLSL450_DETERMINANT = 33,
+       GLSL450_MATRIX_INVERSE = 33,
+       GLSL450_F_MIN = 37,
+       GLSL450_S_MIN = 39,
+       GLSL450_F_MAX = 40,
+       GLSL450_S_MAX = 42,
+       GLSL450_F_CLAMP = 43,
+       GLSL450_S_CLAMP = 45,
+       GLSL450_F_MIX = 46,
+       GLSL450_F_STEP = 48,
+       GLSL450_F_SMOOTH_STEP = 49,
+       GLSL450_F_FMA = 50,
+       GLSL450_LENGTH = 66,
+       GLSL450_DISTANCE = 67,
+       GLSL450_CROSS = 68,
+       GLSL450_NORMALIZE = 69,
+       GLSL450_FACE_FORWARD = 70,
+       GLSL450_REFLECT = 71,
+       GLSL450_REFRACT = 72,
+       GLSL450_FIND_I_LSB = 73,
+       GLSL450_FIND_S_MSB = 74,
+       GLSL450_INTERPOLATE_AT_CENTROID = 76,
+       GLSL450_INTERPOLATE_AT_SAMPLE = 77,
+       GLSL450_INTERPOLATE_AT_OFFSET = 78
+};
+
+} // namespace SL
+} // namespace GL
+} // namespace Msp
+
+#endif
diff --git a/source/glsl/spirvwriter.cpp b/source/glsl/spirvwriter.cpp
new file mode 100644 (file)
index 0000000..77ddbdb
--- /dev/null
@@ -0,0 +1,213 @@
+#include "glsl_error.h"
+#include "spirvwriter.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+namespace SL {
+
+SpirVWriter::SpirVWriter(SpirVContent &c):
+       content(c),
+       op_target(0),
+       op_head_pos(0),
+       current_block_id(0)
+{ }
+
+void SpirVWriter::append(vector<Word> &target, const vector<Word> &source)
+{
+       target.insert(target.end(), source.begin(), source.end());
+}
+
+void SpirVWriter::write(Word word)
+{
+       if(!op_target)
+               throw logic_error("write without begin_op");
+       op_target->push_back(word);
+}
+
+void SpirVWriter::write_float(float value)
+{
+       union
+       {
+               float fv;
+               Word word;
+       };
+       fv = value;
+       write(word);
+}
+
+void SpirVWriter::write_string(const string &str)
+{
+       for(unsigned i=0; i<=str.size(); i+=4)
+       {
+               Word word = 0;
+               for(unsigned j=0; (j<4 && i+j<str.size()); ++j)
+                       word |= static_cast<unsigned char>(str[i+j])<<(j*8);
+               write(word);
+       }
+}
+
+void SpirVWriter::begin_op(vector<Word> &target, Opcode opcode, unsigned size)
+{
+       if(op_target)
+               throw logic_error("begin_op without end_op");
+       if(&target==&content.function_body && !current_block_id)
+               throw logic_error("no open block in function");
+
+       op_head_pos = target.size();
+       op_target = &target;
+       write(opcode | (size<<16));
+
+       if(opcode==OP_BRANCH || opcode==OP_BRANCH_CONDITIONAL || opcode==OP_KILL ||
+               opcode==OP_RETURN || opcode==OP_RETURN_VALUE || opcode==OP_UNREACHABLE)
+               current_block_id = 0;
+}
+
+void SpirVWriter::end_op(Opcode opcode)
+{
+       if(!op_target)
+               throw logic_error("end_op without begin_op");
+       Word &op_head = (*op_target)[op_head_pos];
+       if(opcode!=(op_head&0xFFFF))
+               throw logic_error("opcode mismatch");
+
+       unsigned words = op_target->size()-op_head_pos;
+       unsigned op_size = op_head>>16;
+       if(op_size)
+       {
+               if(words!=op_size)
+                       throw logic_error("incorred number of words written");
+       }
+       else
+               op_head |= (words<<16);
+
+       op_target = 0;
+       op_head_pos = 0;
+}
+
+void SpirVWriter::write_op(vector<Word> &target, Opcode opcode)
+{
+       begin_op(target, opcode, 1);
+       end_op(opcode);
+}
+
+void SpirVWriter::write_op(vector<Word> &target, Opcode opcode, Word arg0)
+{
+       begin_op(target, opcode, 2);
+       write(arg0);
+       end_op(opcode);
+}
+
+void SpirVWriter::write_op(vector<Word> &target, Opcode opcode, Word arg0, Word arg1)
+{
+       begin_op(target, opcode, 3);
+       write(arg0);
+       write(arg1);
+       end_op(opcode);
+}
+
+void SpirVWriter::write_op(vector<Word> &target, Opcode opcode, Word arg0, Word arg1, Word arg2)
+{
+       begin_op(target, opcode, 4);
+       write(arg0);
+       write(arg1);
+       write(arg2);
+       end_op(opcode);
+}
+
+void SpirVWriter::write_op_name(Id id, const string &name)
+{
+       begin_op(content.names, OP_NAME);
+       write(id);
+       write_string(name);
+       end_op(OP_NAME);
+}
+
+void SpirVWriter::write_op_member_name(Id id, unsigned index, const string &name)
+{
+       begin_op(content.names, OP_MEMBER_NAME);
+       write(id);
+       write(index);
+       write_string(name);
+       end_op(OP_MEMBER_NAME);
+}
+
+void SpirVWriter::write_op_decorate(Id id, Decoration decoration)
+{
+       write_op(content.decorations, OP_DECORATE, id, decoration);
+}
+
+void SpirVWriter::write_op_decorate(Id id, Decoration decoration, Word value)
+{
+       write_op(content.decorations, OP_DECORATE, id, decoration, value);
+}
+
+void SpirVWriter::write_op_member_decorate(Id id, unsigned index, Decoration decoration)
+{
+       write_op(content.decorations, OP_MEMBER_DECORATE, id, index, decoration);
+}
+
+void SpirVWriter::write_op_member_decorate(Id id, unsigned index, Decoration decoration, Word value)
+{
+       begin_op(content.decorations, OP_MEMBER_DECORATE, 5);
+       write(id);
+       write(index);
+       write(decoration);
+       write(value);
+       end_op(OP_MEMBER_DECORATE);
+}
+
+void SpirVWriter::write_op_label(Id label_id)
+{
+       if(current_block_id)
+               write_op(content.function_body, OP_BRANCH, label_id);
+       current_block_id = label_id;
+       write_op(content.function_body, OP_LABEL, label_id);
+}
+
+void SpirVWriter::begin_function_body(Id first_block_id)
+{
+       if(!content.function_body.empty() || current_block_id)
+               throw internal_error("begin_function without end_function");
+
+       current_block_id = first_block_id;
+       write_op(content.functions, OP_LABEL, first_block_id);
+}
+
+void SpirVWriter::end_function_body()
+{
+       if(content.function_body.empty())
+               throw internal_error("end_function without begin_function");
+       if(current_block_id)
+               throw internal_error("end_function with open block");
+
+       append(content.functions, content.locals);
+       append(content.functions, content.function_body);
+       write_op(content.functions, OP_FUNCTION_END);
+
+       content.locals.clear();
+       content.function_body.clear();
+}
+
+void SpirVWriter::finalize(Id id_bound)
+{
+       content.code.push_back(SPIRV_MAGIC);
+       content.code.push_back(0x00010500);
+       content.code.push_back(0);  // Generator
+       content.code.push_back(id_bound);
+       content.code.push_back(0);  // Reserved
+       append(content.code, content.capabilities);
+       append(content.code, content.extensions);
+       write_op(content.code, OP_MEMORY_MODEL, 0, 1);  // Logical, GLSL450
+       append(content.code, content.entry_points);
+       append(content.code, content.exec_modes);
+       append(content.code, content.names);
+       append(content.code, content.decorations);
+       append(content.code, content.globals);
+       append(content.code, content.functions);
+}
+
+} // namespace SL
+} // namespace GL
+} // namespace Msp
diff --git a/source/glsl/spirvwriter.h b/source/glsl/spirvwriter.h
new file mode 100644 (file)
index 0000000..94f44e8
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef MSP_GL_SL_SPIRVWRITER_H_
+#define MSP_GL_SL_SPIRVWRITER_H_
+
+#include <string>
+#include <vector>
+#include <msp/core/inttypes.h>
+#include "spirvconstants.h"
+
+namespace Msp {
+namespace GL {
+namespace SL {
+
+struct SpirVContent
+{
+       typedef UInt32 Word;
+
+       std::vector<Word> code;
+       std::vector<Word> capabilities;
+       std::vector<Word> extensions;
+       std::vector<Word> entry_points;
+       std::vector<Word> exec_modes;
+       std::vector<Word> names;
+       std::vector<Word> decorations;
+       std::vector<Word> globals;
+       std::vector<Word> functions;
+       std::vector<Word> locals;
+       std::vector<Word> function_body;
+};
+
+class SpirVWriter
+{
+public:
+       typedef SpirVOpcode Opcode;
+       typedef SpirVDecoration Decoration;
+       typedef SpirVContent::Word Word;
+       typedef Word Id;
+
+private:
+       SpirVContent &content;
+       std::vector<Word> *op_target;
+       unsigned op_head_pos;
+       Id current_block_id;
+
+public:
+       SpirVWriter(SpirVContent &);
+
+       void append(std::vector<Word> &, const std::vector<Word> &);
+       void write(Word);
+       void write_float(float);
+       void write_string(const std::string &);
+       void begin_op(std::vector<Word> &, Opcode, unsigned = 0);
+       void end_op(SpirVOpcode);
+       void write_op(std::vector<Word> &, Opcode);
+       void write_op(std::vector<Word> &, Opcode, Word);
+       void write_op(std::vector<Word> &, Opcode, Word, Word);
+       void write_op(std::vector<Word> &, Opcode, Word, Word, Word);
+       void write_op_name(Id, const std::string &);
+       void write_op_member_name(Id, unsigned, const std::string &);
+       void write_op_decorate(Id, Decoration);
+       void write_op_decorate(Id, Decoration, Word);
+       void write_op_member_decorate(Id, unsigned, Decoration);
+       void write_op_member_decorate(Id, unsigned, Decoration, Word);
+       void write_op_label(Id);
+       bool has_current_block() const { return current_block_id; }
+       void begin_function_body(Id);
+       void end_function_body();
+       void finalize(Id);
+};
+
+} // namespace SL
+} // namespace GL
+} // namespace Msp
+
+#endif