| name | glslang |
| description | glslang SPIR-V Builder API for types, instructions, control flow, and decorations. |
glslang SPIR-V Usage
Located in src/ext/glslang/SPIRV. Headers:
#include "SPIRV/SpvBuilder.h"
#include "SPIRV/spvIR.h"
#include "SPIRV/GlslangToSpv.h"
#include "SPIRV/disassemble.h"
Code snippets follow glslang's own conventions (e.g. camelCase builder methods). LuisaCompute project style rules apply to project code, while src/ext/glslang is third-party code.
SpvBuilder Lifecycle
spv::Builder owns one SPIR-V module. Thread-safe internal IR.
spv::SpvBuildLogger logger;
spv::Builder builder(spv::Spv_1_5, 0, &logger);
builder.setSource(spv::SourceLanguage::GLSL, 450);
builder.setMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450);
builder.addCapability(spv::Capability::Shader);
std::vector<unsigned int> spirv;
builder.dump(spirv);
Module Setup
builder.setSource(spv::SourceLanguage::GLSL, 450);
builder.setEmitSpirvDebugInfo();
builder.setDebugMainSourceFile("shader.frag");
builder.setDebugSourceLocation(10, "shader.frag");
builder.addCapability(spv::Capability::Shader);
builder.addExtension("SPV_KHR_ray_tracing");
builder.setMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450);
spv::Id glsl450 = builder.import("GLSL.std.450");
Types (canonicalized)
spv::Id voidTy = builder.makeVoidType();
spv::Id boolTy = builder.makeBoolType();
spv::Id int32Ty = builder.makeIntType(32);
spv::Id uint32Ty = builder.makeUintType(32);
spv::Id uint64Ty = builder.makeUintType(64);
spv::Id floatTy = builder.makeFloatType(32);
spv::Id doubleTy = builder.makeFloatType(64);
spv::Id halfTy = builder.makeFloatType(16);
spv::Id bfloat16 = builder.makeBFloat16Type();
spv::Id float8e5 = builder.makeFloatE5M2Type();
spv::Id float8e4 = builder.makeFloatE4M3Type();
spv::Id vec4Ty = builder.makeVectorType(floatTy, 4);
spv::Id mat4x4Ty = builder.makeMatrixType(floatTy, 4, 4);
spv::Id arrTy = builder.makeArrayType(floatTy, builder.makeUintConstant(16), 0);
spv::Id runArrTy = builder.makeRuntimeArray(floatTy);
std::vector<spv::Id> members = {floatTy, int32Ty};
spv::Id structTy = builder.makeStructType(members, {}, "MyStruct", false);
spv::Id ptrTy = builder.makePointer(spv::StorageClass::Function, floatTy);
spv::Id fwdPtrTy = builder.makeForwardPointer(spv::StorageClass::PhysicalStorageBuffer);
spv::Id resolvedPtrTy = builder.makePointerFromForwardPointer(spv::StorageClass::PhysicalStorageBuffer, fwdPtrTy, floatTy);
spv::Id untypedPtr= builder.(spv::StorageClass::StorageBuffer);
spv::Id fnTy = builder.(voidTy, {floatTy, int32Ty});
spv::Id imgTy = builder.(floatTy, spv::Dim::Dim2D, , , , , spv::ImageFormat::Rgba32f, );
spv::Id sampledImgTy= builder.(imgTy, );
spv::Id samplerTy = builder.();
spv::Id asTy = builder.();
spv::Id rqTy = builder.();
spv::Id hoTy = builder.();
spv::Id coopMatTy = builder.(floatTy, scopeId, rowsId, colsId, useId);
spv::Id coopVecTy = builder.(floatTy, componentsId);
spv::Id tensorTy = builder.(floatTy, rankId);
std::vector<spv::IdImmediate> ops = {{, someId}};
spv::Id genericTy = builder.(spv::Op::OpType..., ops);
Type Queries
spv::Id typeId = builder.getTypeId(resultId);
spv::Op opCode = builder.getOpCode(id);
spv::Op cls = builder.getTypeClass(typeId);
bool isPtr = builder.isPointer(id);
bool isScalar = builder.isScalar(id);
bool isVec = builder.isVector(id);
bool isMat = builder.isMatrix(id);
bool isArray = builder.isArrayType(typeId);
bool isStruct = builder.isStructType(typeId);
bool isImage = builder.isImageType(typeId);
bool isSampler = builder.isSamplerType(typeId);
int width = builder.getScalarTypeWidth(typeId);
spv::Id scalar = builder.getScalarTypeId(typeId);
spv::Id contained = builder.getContainedTypeId(typeId);
spv::Id contained = builder.getContainedTypeId(typeId, n);
unsigned cols = builder.getNumColumns(id);
unsigned rows = builder.getNumRows(id);
unsigned comps= builder.getNumComponents(id);
Constants (deduplicated; spec constants not)
spv::Id t = builder.makeBoolConstant(true), f = builder.makeBoolConstant(false);
spv::Id i32 = builder.makeIntConstant(5), u32 = builder.makeUintConstant(7);
spv::Id i64 = builder.makeInt64Constant(9), u64 = builder.makeUint64Constant(11);
spv::Id i8 = builder.makeInt8Constant(1), u8 = builder.makeUint8Constant(2);
spv::Id i16 = builder.makeInt16Constant(3), u16 = builder.makeUint16Constant(4);
spv::Id f32 = builder.makeFloatConstant(1.0f), f64 = builder.makeDoubleConstant(2.0);
spv::Id f16 = builder.makeFloat16Constant(3.0f), bf16 = builder.makeBFloat16Constant(4.0f);
spv::Id fp = builder.makeFpConstant(floatTy, 1.5, false);
spv::Id null= builder.makeNullConstant(structTy);
spv::Id vec4 = builder.makeCompositeConstant(vec4Ty, {f32, f32, f32, f32});
spv::Id specI32 = builder.makeIntConstant(builder.makeIntType(32), 10, true);
spv::Id specVec = builder.makeCompositeConstant(vec4Ty, {f32, f32, f32, f32}, true);
Variables
spv::Id global = builder.createVariable(spv::Decoration::NoPrecision, spv::StorageClass::Private, floatTy, "g", builder.makeFloatConstant(0.0f));
spv::Id local = builder.createVariable(spv::Decoration::NoPrecision, spv::StorageClass::Function, floatTy, "l");
spv::Id untyped= builder.createUntypedVariable(spv::Decoration::NoPrecision, spv::StorageClass::StorageBuffer, "u", dataTypeId, initId);
spv::Id undef = builder.createUndefined(floatTy);
Functions
spv::Function* entry = builder.makeEntryPoint("main");
builder.addEntryPoint(spv::ExecutionModel::Fragment, entry, "main");
builder.addExecutionMode(entry, spv::ExecutionMode::OriginUpperLeft);
spv::Block* entryBlock = nullptr;
spv::Function* func = builder.makeFunctionEntry(
spv::Decoration::NoPrecision, floatTy, "myFunc", spv::LinkageType::Max,
{floatTy, int32Ty},
{{spv::Decoration::NoPrecision}, {spv::Decoration::NoPrecision}},
&entryBlock);
builder.enterFunction(func);
builder.setBuildPoint(entryBlock);
spv::Id p0 = func->getParamId(0);
spv::Id p1 = func->getParamId(1);
builder.makeReturn(false, resultId);
builder.leaveFunction();
Control Flow
If-Then-Else
spv::Builder::If ifBuilder(cond, spv::SelectionControlMask::MaskNone, builder);
ifBuilder.makeBeginElse();
ifBuilder.makeEndIf();
Switch
std::vector<int> caseValues = {0, 1}, valueToSegment = {0, 1};
int defaultSegment = 2, numSegments = 3;
std::vector<Block*> segmentBB;
builder.makeSwitch(selectorId, spv::SelectionControlMask::MaskNone, numSegments, caseValues, valueToSegment, defaultSegment, segmentBB);
builder.nextSwitchSegment(segmentBB, 0); builder.addSwitchBreak(false);
builder.nextSwitchSegment(segmentBB, 1); builder.addSwitchBreak(false);
builder.nextSwitchSegment(segmentBB, 2); builder.addSwitchBreak(false);
builder.endSwitch(segmentBB);
Loops
spv::Builder::LoopBlocks& loop = builder.makeNewLoop();
builder.setBuildPoint(&loop.head);
builder.createLoopMerge(&loop.merge, &loop.continue_target, spv::LoopControlMask::MaskNone, {});
builder.createConditionalBranch(cond, &loop.body, &loop.merge);
builder.setBuildPoint(&loop.body);
builder.createLoopContinue();
builder.setBuildPoint(&loop.continue_target);
builder.createBranch(false, &loop.head);
builder.setBuildPoint(&loop.merge);
builder.closeLoop();
Arithmetic & Logic
spv::Id neg = builder.createUnaryOp(spv::Op::OpSNegate, int32Ty, val);
spv::Id notb = builder.createUnaryOp(spv::Op::OpLogicalNot, boolTy, bval);
spv::Id add = builder.createBinOp(spv::Op::OpFAdd, floatTy, a, b);
spv::Id sub = builder.createBinOp(spv::Op::OpISub, int32Ty, a, b);
spv::Id mul = builder.createBinOp(spv::Op::OpIMul, int32Ty, a, b);
spv::Id div = builder.createBinOp(spv::Op::OpFDiv, floatTy, a, b);
spv::Id and_ = builder.createBinOp(spv::Op::OpBitwiseAnd, uint32Ty, a, b);
spv::Id fma = builder.createOp(spv::Op::OpExtInst, floatTy, {glsl450, GLSLstd450Fma, a, b, c});
spv::Id r = builder.createOp(spv::Op::OpVectorTimesMatrix, vec4Ty, {a, b, c});
std::vector<spv::IdImmediate> mixed = {{true, idOp}, {false, (unsigned)spv::MemoryAccessMask::Aligned}};
spv::Id r = builder.createOp(spv::Op::Op..., typeId, mixed);
spv::Id specAdd = builder.createSpecConstantOp(spv::Op::OpIAdd, int32Ty, {specA, specB}, {});
Memory Instructions
spv::Id loaded = builder.createLoad(ptrId, spv::Decoration::NoPrecision);
builder.createStore(valueId, ptrId);
builder.createStore(valueId, ptrId, spv::MemoryAccessMask::NonUniformPointerEXT, spv::Scope::Device, 4);
std::vector<spv::Id> indexes = {builder.makeUintConstant(0), builder.makeUintConstant(2)};
spv::Id chain = builder.createAccessChain(spv::StorageClass::Function, basePtr, indexes);
spv::Id elem = builder.createCompositeExtract(composite, elemType, 2);
spv::Id elem = builder.createCompositeExtract(composite, elemType, std::vector<unsigned>{0, 1});
spv::Id ins = builder.createCompositeInsert(newVal, composite, compositeType, 0);
spv::Id dynEl = builder.createVectorExtractDynamic(vec, elemType, indexId);
spv::Id dynVec= builder.createVectorInsertDynamic(vec, vecType, newElem, indexId);
spv::Id comp = builder.createCompositeConstruct(vec4Ty, {a, b, c, d});
spv::Id vec4 = builder.createConstructor(spv::Decoration::NoPrecision, {scalarId}, vec4Ty);
spv::Id mat = builder.createMatrixConstructor(spv::Decoration::NoPrecision, srcs, mat4x4Ty);
spv::Id swz = builder.createRvalueSwizzle(spv::Decoration::NoPrecision, vec4Ty, vec, {2, 1, 0, 3});
spv::Id lswz= builder.createLvalueSwizzle(vec4Ty, target, source, {2, 1, 0, 3});
builder.promoteScalar(spv::Decoration::NoPrecision, left, right);
spv::Id smeared = builder.(spv::Decoration::NoPrecision, scalarId, vec4Ty);
Access Chain Helper
Builder maintains one active access chain for l-value/r-value tracking:
builder.clearAccessChain();
builder.setAccessChainLValue(ptrId);
builder.setAccessChainRValue(valueId);
builder.accessChainPush(indexId, coherentFlags, alignment);
builder.accessChainPushSwizzle(channels, preSwizzleBaseType, coherentFlags, alignment);
builder.accessChainPushComponent(componentId, preSwizzleBaseType, coherentFlags, alignment);
spv::Id result = builder.accessChainLoad(precision, lvalNonUniform, rvalNonUniform, resultType, memAccess, scope, n);
builder.accessChainStore(valueId, spv::Decoration::NonUniform,
spv::MemoryAccessMask::MaskNone, spv::Scope::Max, 0);
spv::Id lval = builder.accessChainGetLValue();
spv::Id inferred = builder.accessChainGetInferredType();
bool canBeLvalue = builder.isSpvLvalue();
spv::Builder::AccessChain saved = builder.getAccessChain();
builder.setAccessChain(saved);
Texture Operations
spv::Builder::TextureParameters params = {};
params.sampler = sampledImageId;
params.coords = coordsId;
params.lod = lodId;
spv::Id tex = builder.createTextureCall(precision, resultType,
false, false, false, false, false,
params, spv::ImageOperandsMask::MaskNone);
Decorations & Names
builder.addName(id, "myVar");
builder.addMemberName(structTy, 0, "field0");
builder.addDecoration(id, spv::Decoration::Location, 0);
builder.addDecoration(id, spv::Decoration::Binding, 2);
builder.addDecoration(id, spv::Decoration::DescriptorSet, 0);
builder.addDecoration(id, spv::Decoration::NoContraction);
builder.addDecoration(id, spv::Decoration::RelaxedPrecision);
builder.addDecoration(id, spv::Decoration::BuiltIn, (int)spv::BuiltIn::Position);
builder.addMemberDecoration(structTy, 0, spv::Decoration::Offset, 0);
builder.addMemberDecoration(structTy, 1, spv::Decoration::Offset, 16);
builder.addDecoration(id, spv::Decoration::WorkgroupSize, std::vector<unsigned>{64, 1, 1});
builder.addDecorationId(id, spv::Decoration::ArrayStrideIdEXT, strideId);
builder.addLinkageDecoration(id, "myFunc", spv::LinkageType::Export);
Barriers
builder.createControlBarrier(spv::Scope::Workgroup, spv::Scope::Device,
spv::MemorySemanticsMask::UniformMemory | spv::MemorySemanticsMask::WorkgroupMemory);
builder.createMemoryBarrier(spv::Scope::Device, spv::MemorySemanticsMask::ImageMemory);
Debug Info
SPIR-V Standard (OpLine/OpSource)
builder.setEmitSpirvDebugInfo();
builder.setDebugMainSourceFile("shader.glsl");
builder.setDebugSourceLocation(42, "shader.glsl");
builder.setSourceText(sourceText);
NonSemantic Shader Debug Info
builder.setEmitNonSemanticShaderDebugInfo(true);
spv::Id debugType = builder.getDebugType(spirvTypeId);
builder.enterLexicalBlock(line, column);
builder.leaveLexicalBlock();
builder.setupFunctionDebugInfo(func, "myFunc", paramTypes, paramNames);
spv::Id dbgGlobal = builder.createDebugGlobalVariable(debugType, "globalVar", varId);
spv::Id dbgLocal = builder.createDebugLocalVariable(debugType, "localVar", argNumber);
spv::Id dbgDecl = builder.makeDebugDeclare(dbgLocal, ptrId);
spv::Id dbgVal = builder.makeDebugValue(dbgLocal, valueId);
Function Calls & Builtins
spv::Id result = builder.createFunctionCall(calleeFunc, {arg0, arg1, arg2});
spv::Id sqrtVal = builder.createBuiltinCall(floatTy, glsl450, GLSLstd450Sqrt, {val});
Post-Processing & Serialization
builder.postProcess(false);
builder.postProcessCFG();
builder.postProcessFeatures();
builder.postProcessSamplers();
std::vector<unsigned int> spirv;
builder.dump(spirv);
spv::Disassemble(std::cout, spirv);
glslang::OutputSpvBin(spirv, "out.spv");
glslang::OutputSpvHex(spirv, "out.h", "g_spv");
Both postProcessCFG() and Function::dump() traverse physical blocks with
inReadableOrder(), which assumes structured merge roles already nest. If an
outer selection merge is also an inner arm and then branches to the inner
merge, the physical graph exits the inner construct and re-enters it. The
traversal can initially mask that invalid topology by classifying the inner
merge as dead, replacing live code with OpUnreachable, and serializing it
before its dominator. Fix the producer's physical control-flow plan: preserve
the payload blocks but rotate the adjacent merge declarations so the inner
merge physically precedes the outer merge. Do not patch serialization order or
disable post-processing/validation around an invalid graph.
OpSwitch case literals are sized by the selector's OpTypeInt, not by the
generated operand-table class alone. A selector up to 32 bits uses one literal
word; a 64-bit selector uses two low-word-first literal words followed by one
target label ID. Disassemblers and binary walkers must resolve the selector
type and consume ceil(bit_width / 32) words per case before reading the label.
Never infer case boundaries by alternating one literal word and one ID.
Treat disassembly input as untrusted. Validate each instruction-local word
count before reading operands: reject zero, undersized, or module-truncated
instructions. When resolving an OpSwitch selector, also validate the mapped
defining instruction bounds and result ID; accept OpTypeInt only with its
exact four-word layout and a width of 8, 16, 32, or 64. Validate a directly
visited OpTypeInt before reading its width operand. The disassembler's fatal
path exits the process, so malformed-input regressions must run it in a child
process and assert the deterministic nonzero exit.
IR Classes (spvIR.h)
spv::Instruction* inst = new spv::Instruction(resultId, typeId, spv::Op::OpIAdd);
inst->addIdOperand(opA);
inst->addIdOperand(opB);
spv::Block* block = new spv::Block(blockId, *function);
block->addInstruction(std::unique_ptr<spv::Instruction>(inst));
block->addLocalVariable(std::unique_ptr<spv::Instruction>(varInst));
bool terminated = block->isTerminated();
spv::Function* func = new spv::Function(funcId, retType, funcType, firstParamId, linkage, name, module);
func->addBlock(block);
func->setReturnPrecision(spv::Decoration::RelaxedPrecision);
func->addParamPrecision(0, spv::Decoration::RelaxedPrecision);
spv::Module module;
module.addFunction(func);
module.mapInstruction(inst);
spv::Instruction* found = module.getInstruction(id);
spv::Id typeId = module.getTypeId(resultId);
Key Types
| Type | Purpose |
|---|
spv::Builder | SPIR-V module construction |
spv::Instruction | Single SPIR-V instruction |
spv::Block | Basic block |
spv::Function | SPIR-V function |
spv::Module | Module root, ID→instruction map |
spv::Builder::If | Structured if-then-else helper |
spv::Builder::LoopBlocks | Structured loop blocks |
spv::Builder::AccessChain | L-value/R-value access chain |
spv::Builder::TextureParameters | Texture op parameters |
spv::IdImmediate | Operand: ID or immediate |
glslang::SpvOptions | GlslangToSpv options |
GlslangToSpv Patterns
From TGlslangToSpvTraverser (src/ext/glslang/SPIRV/GlslangToSpv.cpp). Common pattern: clear access chain → traverse → load/store → set R-value.
visitSymbol
builder.clearAccessChain();
if (isRValue || rValueParameters.count(symbolId) ||
(!builder.isPointerType(builder.getTypeId(id)) && !builder.isUntypedPointer(id)))
builder.setAccessChainRValue(id);
else
builder.setAccessChainLValue(id);
spv::StorageClass sc = builder.getStorageClass(id);
if (builder.isGlobalVariable(id))
iOSet.insert(id);
builder.addExtension("SPV_GOOGLE_hlsl_functionality1");
builder.addDecorationId(id, spv::Decoration::HlslCounterBufferGOOGLE, counterId);
visitBinary (Assignment)
builder.clearAccessChain(); node->getLeft()->traverse(this);
auto lValue = builder.getAccessChain();
builder.clearAccessChain(); node->getRight()->traverse(this);
spv::Id rValue = accessChainLoad(node->getRight()->getType());
builder.setAccessChain(lValue);
multiTypeStore(node->getLeft()->getType(), rValue);
builder.clearAccessChain(); builder.setAccessChainRValue(rValue);
visitBinary (Array/Vector Index)
if (builder.isUintType(indexType) && builder.getScalarTypeWidth(indexType) < 32)
index = builder.createUnaryOp(spv::Op::OpUConvert, builder.makeUintType(32), index);
builder.accessChainPush(index, coherentFlags, alignment);
visitBinary (Swizzle)
builder.accessChainPushSwizzle(swizzle, convertGlslangToSpvType(node->getLeft()->getType()),
coherentFlags, alignment);
visitUnary (Inc/Dec)
spv::Id operand = builder.accessChainGetLValue();
spv::Id one = builder.makeIntConstant(1);
spv::Id result = builder.createBinOp(op, type, operand, one);
builder.accessChainStore(result, ...);
builder.clearAccessChain(); builder.setAccessChainRValue(result);
visitUnary (Builtin / NoResult / ArrayLength)
spv::Id result = builder.createBuiltinCall(resultType(), glsl450, opcode, {operand});
builder.createNoResultOp(spv::Op::OpKill);
builder.createNoResultOp(spv::Op::OpTerminateInvocation);
builder.createNoResultOp(spv::Op::OpDemoteToHelperInvocationEXT);
builder.createNoResultOp(spv::Op::OpAssumeTrueKHR, operand);
spv::Id len = builder.createArrayLength(builder.accessChainGetLValue(), member, bits);
len = builder.createUnaryOp(spv::Op::OpBitcast, builder.makeIntType(bits), len);
spv::Id lenKHR = builder.createCooperativeMatrixLengthKHR(typeId);
spv::Id lenNV = builder.createCooperativeMatrixLengthNV(typeId);
spv::Id lenVec = builder.getCooperativeVectorNumComponents(typeId);
spv::Id layout = builder.createOp(spv::Op::OpCreateTensorLayoutNV, resultType(), {});
spv::Id view = builder.createOp(spv::Op::OpCreateTensorViewNV, resultType(), {});
visitAggregate
builder.setBuildPoint(shaderEntry->getLastBlock());
builder.enterFunction(shaderEntry); builder.leaveFunction();
spv::Id result = builder.createFunctionCall(callee, arguments);
spv::Id c = builder.createConstructor(precision, arguments, resultType());
spv::Id m = builder.createMatrixConstructor(precision, arguments, resultType());
spv::Id r = builder.createBuiltinCall(resultType(), extInst, opcode, arguments);
spv::Builder::TextureParameters params = {sampledImageId, coordsId, };
spv::Id tex = builder.createTextureCall(precision, resultType(), sparse, fetch, proj, gather, noImplicit, params, mask);
spv::Id sampled = builder.createOp(spv::Op::OpSampledImage, resultType(), {imageId, samplerId});
spv::Id coop = builder.createCooperativeMatrixConversion(resultType(), arguments[0]);
spv::Id var = builder.createVariable(precision, spv::StorageClass::Function, type, name, init);
spv::Id loaded = builder.createLoad(ptrId, precision);
builder.createStore(valueId, ptrId);
builder.enterLexicalBlock(loc.line, loc.column); builder.leaveLexicalBlock();
visitSelection
spv::Id result = builder.createTriOp(spv::Op::OpSelect, resultType, cond, trueVal, falseVal);
if (builder.getSpvVersion() < spv::Spv_1_4 && builder.isVector(trueVal)) {
cond = builder.smearScalar(precision, cond,
builder.makeVectorType(builder.makeBoolType(),
builder.getNumComponents(trueVal)));
}
if (builder.getTypeId(trueVal) != resultType)
trueVal = builder.createUnaryOp(spv::Op::OpCopyLogical, resultType, trueVal);
if (builder.getTypeId(falseVal) != resultType)
falseVal = builder.createUnaryOp(spv::Op::OpCopyLogical, resultType, falseVal);
spv::Id result = builder.createTriOp(spv::Op::OpSelect, resultType, cond, trueVal, falseVal);
visitSwitch
std::vector<int> caseValues = {0,1,2}, valueToSegment = {0,1,2};
builder.makeSwitch(selectorId, spv::SelectionControlMask::MaskNone, 4, caseValues, valueToSegment, 3, segmentBB);
builder.nextSwitchSegment(segmentBB, 0); builder.addSwitchBreak(false);
builder.endSwitch(segmentBB);
visitLoop
spv::Builder::LoopBlocks& loop = builder.makeNewLoop();
builder.setBuildPoint(&loop.head);
builder.createLoopMerge(&loop.merge, &loop.continue_target, spv::LoopControlMask::MaskNone, {});
builder.createConditionalBranch(cond, &loop.body, &loop.merge);
builder.setBuildPoint(&loop.body); builder.createLoopContinue();
builder.setBuildPoint(&loop.continue_target); builder.createBranch(false, &loop.head);
builder.setBuildPoint(&loop.merge);
builder.closeLoop();
visitBranch
builder.makeReturn(false, returnValue);
builder.makeReturn(false);
builder.createLoopExit();
builder.createLoopContinue();
builder.makeStatementTerminator(spv::Op::OpKill, "post-discard");
builder.makeStatementTerminator(spv::Op::OpTerminateInvocation, "post-terminate");
builder.createNoResultOp(spv::Op::OpDemoteToHelperInvocationEXT);
builder.makeStatementTerminator(spv::Op::OpTerminateRayKHR, "post-terminate-ray");
builder.makeStatementTerminator(spv::Op::OpIgnoreIntersectionKHR, "post-ignore");
visitConstantUnion
spv::Id constantId = createSpvConstant(node);
builder.clearAccessChain();
builder.setAccessChainRValue(constantId);
visitVariableDecl
builder.setDebugSourceLocation(node->getLoc().line, node->getLoc().getFilename());
visitFunctions