diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-02-22 11:44:57 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-02-22 11:44:57 -0800 |
| commit | 7eaaf1aa1fdac3fd7ff7b64800a965a2b2c17f66 (patch) | |
| tree | 0ce13473cf162c3982a0f6e28adebd3f8a89bc46 | |
| parent | 01c4134d33cea3a4f98fad9248584278fd4bc452 (diff) | |
Initial work on validating "constexpr"-ness in IR (#420)
* Initial work on validating "constexpr"-ness in IR
The underlying issue here is that certain operations in the target shading languages constrain their operands to be compile-time constants. A notable example is the optional texel offset parameter to the `Texture2D.Sample` operation.
When calling these operations in GLSL, the user is required to pass a "constant expression," and any variables in that expression must therefore be marked with the `const` qualifier (and themselves be initialized with constant expressions). Any GLSL output we generate must of course respect these rules.
When calling these operations in HLSL, the user is not so constrained. Instead, they can pass an arbitrary expression, which may involve ordinary variables with no particular markup, and then the compiler is responsible for determining if the actual value after simplification works out to be a constant. In some cases, the requirement that a value be constant might actually trigger things like loop unrolling. Also, it is okay to use a function parameter to determine such a constant expression, as long as the argument turns out to be a constant at all call sites.
The way we have decided to tackle these challenges in Slang is that we we propagate a notion of `constexpr`-ness through the IR. This is currently being tackled in `ir-constexpr.cpp` with a combination of forward and backward iterative dataflow:
* When the operands to an instruction are all `constexpr`, and the opcode is one we believe can be constant-folded, then we infer that the instruction *can* be evaluated as `constexpr`
* When instruction is required to be `constexpr`, then we infer that all of its operands are also required to be `constexpr`.
If this process ever infers that a function parameter is required to be `constexpr`, then we might have to continue propagation at all the call sites to that function.
If after all the propagation is done, there are any cases where an instruction is *required* to be `constexpr`, but it *can't* be `constexpr` (we weren't able to infer `constexpr`-ness for its operands), then we issue an error.
This implementation encodes the idea of `constexpr`-ness in the IR as part of the type system, using a simplified notion of rates. This change adds a `RateQualifiedType` that can represent `@R T`, and then introduces a `ConstExprRate` that can be used for `R`. Many accessors for the type information on IR nodes were updated to distinguish when one wants the "full" type of an IR value (which might include rate information) vs. just the "data" type.
A `constexpr` qualifier was added in the front-end, and is being used to decorate the texel offset parameter for `Texture2D.Sample`. Lowering from AST to IR looks for this qalifier and infers when a function parameter must be typed as `@ConstExpr T` instead of just `T`.
There are lots of limitations and gotchas in the implementation so far:
* The `@ConstExpr` rate is the only one added in this change, but it seems clear that the conceptual `ThreadGroup` rate that was added to represent `groupshared` should probably get folded into the representation.
* I'm not 100% pleased with how many places in the IR I have to special-case for rate-qualified types. At the same type, pulling out rate as a distinct field on `IRValue` would probably require that we pay attention to rate everywhere.
* I've added a test case to show that we can issue errors when users fail to provide a constant expression for the texel offset, but the actual error message isn't great because it doesn't indicate *why* a constant expression was required. Realistically the "initial IR" should contain a few more decorations we can use to relate error conditions back to the original code (even if this is in a side-band structure).
* I've added a test case that is supposed to show that we can back-propagate `constexpr`-ness to local variables, and I've manually confirmed that it works for Vulkan/SPIR-V output, but the level of Vulkan support in `render_test` today means I can't enable the test for check-in.
* While I'm attempting to propagate `@ConstExpr` information from callees to callers, I haven't implemented any logic to specialize callee functions based on values at call sites.
* In a similar vein, there is no handling of control-flow dependence in the current code. If we infer that a phi (block parameter) needs to be `@ConstExpr`, then it isn't actually enough to require that the inputs to the phi (arguments from predecessor blocks) are all `@ConstExpr` because we also need any control-flow decisions that pick which incoming edge we take to be `@ConstExpr` as well.
* As a practical matter, implicit propagation of `@ConstExpr` from a function body to a function parameter should only be allowed for functions that are "local" to a module. Any function that might be accessed from outside of a module should really have had its `@ConstExpr` parameter marked manually, and our pass should validate that they follow their own rules. Right now we have no kind of visibility (`public` vs `private`) system, so I'm kind of ignoring this issue.
While that is a lot of gaps, this is also just enough code to get the Falcor MultiPassPostProcess example working, so I'm inclined to get it checked in.
* Fixup: missing expected output for test
* Fixup: disable test that relies on [unroll] for now
28 files changed, 1181 insertions, 109 deletions
@@ -1237,12 +1237,13 @@ namespace slang #include "source/slang/dxc-support.cpp" #include "source/slang/emit.cpp" #include "source/slang/ir.cpp" -#include "source/slang/memory_pool.cpp" +#include "source/slang/ir-constexpr.cpp" #include "source/slang/ir-legalize-types.cpp" #include "source/slang/ir-ssa.cpp" #include "source/slang/legalize-types.cpp" #include "source/slang/lexer.cpp" #include "source/slang/mangle.cpp" +#include "source/slang/memory_pool.cpp" #include "source/slang/name.cpp" #include "source/slang/options.cpp" #include "source/slang/parameter-binding.cpp" diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp index eb6b755fc..6b6e4170e 100644 --- a/source/slang/bytecode.cpp +++ b/source/slang/bytecode.cpp @@ -319,7 +319,7 @@ void encodeOperand( bool opHasResult(IRValue* inst) { - auto type = inst->getType(); + auto type = inst->getDataType(); if (!type) return false; // As a bit of a hack right now, we need to check whether @@ -352,7 +352,7 @@ void generateBytecodeForInst( auto argCount = inst->getArgCount(); encodeUInt(context, inst->op); - encodeOperand(context, inst->getType()); + encodeOperand(context, inst->getDataType()); encodeUInt(context, argCount); for( UInt aa = 0; aa < argCount; ++aa ) { @@ -381,7 +381,7 @@ void generateBytecodeForInst( { auto ii = (IRConstant*) inst; encodeUInt(context, ii->op); - encodeOperand(context, ii->getType()); + encodeOperand(context, ii->getDataType()); // TODO: probably want distinct encodings // for signed vs. unsigned here. @@ -396,7 +396,7 @@ void generateBytecodeForInst( { auto cInst = (IRConstant*) inst; encodeUInt(context, cInst->op); - encodeOperand(context, cInst->getType()); + encodeOperand(context, cInst->getDataType()); static const UInt size = sizeof(IRFloatingPointValue); unsigned char buffer[size]; @@ -446,7 +446,7 @@ void generateBytecodeForInst( // We need to encode the type being stored, to make // our lives easier. - encodeOperand(context, inst->getArg(1)->getType()); + encodeOperand(context, inst->getArg(1)->getDataType()); encodeOperand(context, inst->getArg(0)); encodeOperand(context, inst->getArg(1)); } @@ -455,7 +455,7 @@ void generateBytecodeForInst( case kIROp_Load: { encodeUInt(context, inst->op); - encodeOperand(context, inst->getType()); + encodeOperand(context, inst->getDataType()); encodeOperand(context, inst->getArg(0)); encodeOperand(context, inst); } @@ -611,7 +611,7 @@ uint32_t getTypeIDForGlobalSymbol( BytecodeGenerationContext* context, IRValue* inst) { - auto type = inst->getType(); + auto type = inst->getDataType(); if(!type) return 0; @@ -836,7 +836,7 @@ BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( bcRegs[localID+1].op = ii->op; bcRegs[localID+1].previousVarIndexPlusOne = (uint32_t)localID+1; bcRegs[localID+1].typeID = getTypeID(context, - (ii->getType()->As<PtrType>())->getValueType()); + (ii->getDataType()->As<PtrType>())->getValueType()); } break; } diff --git a/source/slang/compiler.h b/source/slang/compiler.h index 845de5c81..fe3ee9533 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -448,6 +448,7 @@ namespace Slang RefPtr<Type> errorType; RefPtr<Type> initializerListType; RefPtr<Type> overloadedType; + RefPtr<Type> constExprRate; RefPtr<Type> irBasicBlockType; Dictionary<int, RefPtr<Type>> builtinTypes; @@ -467,6 +468,17 @@ namespace Slang Type* getOverloadedType(); Type* getErrorType(); + Type* getConstExprRate(); + RefPtr<RateQualifiedType> getRateQualifiedType( + Type* rate, + Type* valueType); + + RefPtr<RateQualifiedType> getConstExprType( + Type* valueType) + { + return getRateQualifiedType(getConstExprRate(), valueType); + } + // Should not be used in front-end code Type* getIRBasicBlockType(); diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index b395bd08b..d54eea94d 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -1,5 +1,9 @@ // Slang `core` library +// Modifier for variables that must resolve to compile-time constants +// as part of translation. +syntax constexpr : ConstExprModifier; + // A type that can be used as an operand for builtins interface __BuiltinType {} @@ -623,7 +627,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) { sb << ", int sampleIndex"; } - sb << ", int" << loadCoordCount << " offset"; + sb << ", constexpr int" << loadCoordCount << " offset"; sb << ");\n"; @@ -633,7 +637,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) { sb << ", int sampleIndex"; } - sb << ", int" << kBaseTextureTypes[tt].coordCount << " offset"; + sb << ", constexpr int" << kBaseTextureTypes[tt].coordCount << " offset"; sb << ", out uint status"; sb << ");\n"; } @@ -699,14 +703,14 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureOffset($$p, $2, $3)\")\n"; sb << "T Sample(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } sb << "T Sample(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; if( baseShape != TextureType::ShapeCube ) { - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; } sb << "float clamp);\n"; @@ -714,7 +718,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; if( baseShape != TextureType::ShapeCube ) { - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; } sb << "float clamp, out uint status);\n"; @@ -729,7 +733,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureOffset($$p, $2, $3, $4)\")\n"; sb << "T SampleBias(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, float bias, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } // `SampleCmp()` and `SampleCmpLevelZero` @@ -796,12 +800,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "T SampleCmp(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float compareValue, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; sb << "T SampleCmpLevelZero(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float compareValue, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } @@ -821,7 +825,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " gradX, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " gradY, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } // `SampleLevel` @@ -837,7 +841,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "T SampleLevel(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float level, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } } @@ -908,12 +912,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureGatherOffset($$p, $2, $3, " << componentIndex << ")\")\n"; sb << "vector<T, 4> Gather" << componentName << "(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; EMIT_LINE_DIRECTIVE(); sb << "vector<T, 4> Gather" << componentName << "(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; sb << "out uint status);\n"; EMIT_LINE_DIRECTIVE(); diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index 054174d3f..4d63bac5f 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -1,5 +1,9 @@ sb << "// Slang `core` library\n"; sb << "\n"; +sb << "// Modifier for variables that must resolve to compile-time constants\n"; +sb << "// as part of translation.\n"; +sb << "syntax constexpr : ConstExprModifier;\n"; +sb << "\n"; sb << "// A type that can be used as an operand for builtins\n"; sb << "interface __BuiltinType {}\n"; sb << "\n"; @@ -626,7 +630,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) { sb << ", int sampleIndex"; } - sb << ", int" << loadCoordCount << " offset"; + sb << ", constexpr int" << loadCoordCount << " offset"; sb << ");\n"; @@ -636,7 +640,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) { sb << ", int sampleIndex"; } - sb << ", int" << kBaseTextureTypes[tt].coordCount << " offset"; + sb << ", constexpr int" << kBaseTextureTypes[tt].coordCount << " offset"; sb << ", out uint status"; sb << ");\n"; } @@ -702,14 +706,14 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureOffset($p, $2, $3)\")\n"; sb << "T Sample(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } sb << "T Sample(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; if( baseShape != TextureType::ShapeCube ) { - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; } sb << "float clamp);\n"; @@ -717,7 +721,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; if( baseShape != TextureType::ShapeCube ) { - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; } sb << "float clamp, out uint status);\n"; @@ -732,7 +736,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureOffset($p, $2, $3, $4)\")\n"; sb << "T SampleBias(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, float bias, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } // `SampleCmp()` and `SampleCmpLevelZero` @@ -799,12 +803,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "T SampleCmp(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float compareValue, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; sb << "T SampleCmpLevelZero(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float compareValue, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } @@ -824,7 +828,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " gradX, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " gradY, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } // `SampleLevel` @@ -840,7 +844,7 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "T SampleLevel(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount + isArray << " location, "; sb << "float level, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; } } @@ -911,12 +915,12 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) sb << "__target_intrinsic(glsl, \"textureGatherOffset($p, $2, $3, " << componentIndex << ")\")\n"; sb << "vector<T, 4> Gather" << componentName << "(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset);\n"; EMIT_LINE_DIRECTIVE(); sb << "vector<T, 4> Gather" << componentName << "(SamplerState s, "; sb << "float" << kBaseTextureTypes[tt].coordCount << " location, "; - sb << "int" << kBaseTextureTypes[tt].coordCount << " offset, "; + sb << "constexpr int" << kBaseTextureTypes[tt].coordCount << " offset, "; sb << "out uint status);\n"; EMIT_LINE_DIRECTIVE(); diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 82aed2d69..06e553072 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -286,6 +286,8 @@ DIAGNOSTIC(40005, Error, topLevelModuleUsedWithoutSpecifyingBinding, "top level DIAGNOSTIC(49999, Error, unknownSystemValueSemantic, "unknown system-value semantic '$0'") +DIAGNOSTIC(40006, Error, needCompileTimeConstant, "expected a compile-time constant"); + // // 5xxxx - Target code generation. // diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index d2ed1c7c3..3fa50b928 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -1297,6 +1297,17 @@ struct EmitVisitor emitTypeImpl(arrayType->baseType, &arrayDeclarator); } + void visitRateQualifiedType(RateQualifiedType* type, TypeEmitArg const& arg) + { + emitTypeImpl(type->valueType, arg.declarator); + } + + void visitConstExprRate(ConstExprRate* /*rate*/, TypeEmitArg const& /*arg*/) + { + // This should never appear as a data type + SLANG_UNEXPECTED("Rates not expected during emit"); + } + void visitGroupSharedType(GroupSharedType* type, TypeEmitArg const& arg) { switch(getTarget(context)) @@ -4924,7 +4935,7 @@ emitDeclImpl(decl, nullptr); // Certain *types* will usually want to be folded in, // because they aren't allowed as types for temporary // variables. - auto type = inst->getType(); + auto type = inst->getDataType(); while (auto ptrType = type->As<PtrTypeBase>()) { @@ -4975,7 +4986,7 @@ emitDeclImpl(decl, nullptr); EmitContext* /*context*/, IRValue* inst) { - auto type = inst->getType(); + auto type = inst->getDataType(); if(type->As<UniformParameterGroupType>() && !type->As<ParameterBlockType>()) { @@ -5052,17 +5063,47 @@ emitDeclImpl(decl, nullptr); EmitType(type); } + void emitIRRateQualifiers( + EmitContext* ctx, + Type* rate) + { + if(!rate) return; + + if( auto constExprRate = rate->As<ConstExprRate>() ) + { + switch( getTarget(ctx) ) + { + case CodeGenTarget::GLSL: + emit("const "); + break; + + default: + break; + } + } + } + + void emitIRRateQualifiers( + EmitContext* ctx, + IRValue* value) + { + emitIRRateQualifiers(ctx, value->getRate()); + } + + void emitIRInstResultDecl( EmitContext* ctx, IRInst* inst) { - auto type = inst->getType(); + auto type = inst->getDataType(); if(!type) return; if (type->Equals(getSession()->getVoidType())) return; + emitIRRateQualifiers(ctx, inst); + emitIRType(ctx, type, getIRName(inst)); emit(" = "); } @@ -5651,13 +5692,13 @@ emitDeclImpl(decl, nullptr); { // Need to emit as cast for HLSL emit("("); - emitIRType(ctx, inst->getType()); + emitIRType(ctx, inst->getDataType()); emit(") "); emitIROperand(ctx, inst->getArg(0), mode); } else { - emitIRType(ctx, inst->getType()); + emitIRType(ctx, inst->getDataType()); emitIRArgs(ctx, inst, mode); } break; @@ -5667,12 +5708,12 @@ emitDeclImpl(decl, nullptr); if( getTarget(ctx) == CodeGenTarget::HLSL ) { emit("("); - emitIRType(ctx, inst->getType()); + emitIRType(ctx, inst->getDataType()); emit(")"); } else { - emitIRType(ctx, inst->getType()); + emitIRType(ctx, inst->getDataType()); } emit("("); emitIROperand(ctx, inst->getArg(0), mode); @@ -5769,7 +5810,7 @@ emitDeclImpl(decl, nullptr); case kIROp_Not: { - if (inst->getType()->Equals(getSession()->getBoolType())) + if (inst->getDataType()->Equals(getSession()->getBoolType())) { emit("!"); } @@ -5976,7 +6017,7 @@ emitDeclImpl(decl, nullptr); case kIROp_undefined: { - auto type = inst->getType(); + auto type = inst->getDataType(); emitIRType(ctx, type, getIRName(inst)); emit(";\n"); } @@ -5984,7 +6025,7 @@ emitDeclImpl(decl, nullptr); case kIROp_Var: { - auto ptrType = inst->getType(); + auto ptrType = inst->getDataType(); auto valType = ((PtrType*)ptrType)->getValueType(); auto name = getIRName(inst); @@ -6801,7 +6842,7 @@ emitDeclImpl(decl, nullptr); { for (auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam()) { - emitIRType(ctx, pp->getType(), getIRName(pp)); + emitIRType(ctx, pp->getDataType(), getIRName(pp)); emit(";\n"); } } @@ -6833,7 +6874,7 @@ emitDeclImpl(decl, nullptr); emit(", "); auto paramName = getIRName(pp); - auto paramType = pp->getType(); + auto paramType = pp->getDataType(); if (auto decor = pp->findDecoration<IRHighLevelDeclDecoration>()) { if (decor->decl) @@ -7496,7 +7537,7 @@ emitDeclImpl(decl, nullptr); EmitContext* ctx, IRVar* varDecl) { - auto allocatedType = varDecl->getType(); + auto allocatedType = varDecl->getDataType(); auto varType = allocatedType->getValueType(); // auto addressSpace = allocatedType->getAddressSpace(); @@ -7530,6 +7571,7 @@ emitDeclImpl(decl, nullptr); break; } #endif + emitIRRateQualifiers(ctx, varDecl); emitIRType(ctx, varType, getIRName(varDecl)); @@ -7590,7 +7632,7 @@ emitDeclImpl(decl, nullptr); EmitContext* ctx, IRGlobalVar* varDecl) { - auto allocatedType = varDecl->getType(); + auto allocatedType = varDecl->getDataType(); auto varType = allocatedType->getValueType(); String initFuncName; @@ -7712,7 +7754,7 @@ emitDeclImpl(decl, nullptr); EmitContext* ctx, IRGlobalConstant* valDecl) { - auto valType = valDecl->getType(); + auto valType = valDecl->getDataType(); emit("static const "); emitIRType(ctx, valType, getIRName(valDecl)); diff --git a/source/slang/ir-constexpr.cpp b/source/slang/ir-constexpr.cpp new file mode 100644 index 000000000..f5738c036 --- /dev/null +++ b/source/slang/ir-constexpr.cpp @@ -0,0 +1,531 @@ +// ir-constexpr.cpp +#include "ir-constexpr.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang { + +struct PropagateConstExprContext +{ + DiagnosticSink* sink; + + SharedIRBuilder sharedBuilder; + IRBuilder builder; + + List<IRGlobalValue*> workList; + HashSet<IRGlobalValue*> onWorkList; + + IRBuilder* getBuilder() { return &builder; } + + Session* getSession() { return sharedBuilder.session; } + + DiagnosticSink* getSink() { return sink; } +}; + +bool isConstExpr(Type* type) +{ + if( auto rateQualifiedType = type->As<RateQualifiedType>() ) + { + auto rate = rateQualifiedType->rate; + if(auto constExprRate = rate->As<ConstExprRate>()) + return true; + } + + return false; +} + +bool isConstExpr(IRValue* value) +{ + // Certain IR value ops are implicitly `constexpr` + // + // TODO: should we just go ahead and make that explicit + // in the type system? + switch(value->op) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_boolConst: + case kIROp_Func: + return true; + + default: + break; + } + + if(isConstExpr(value->getFullType())) + return true; + + return false; +} + +bool opCanBeConstExpr(IROp op) +{ + switch( op ) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_boolConst: + case kIROp_Add: + case kIROp_Sub: + case kIROp_Mul: + case kIROp_Div: + case kIROp_Mod: + case kIROp_Neg: + case kIROp_Construct: + case kIROp_makeVector: + case kIROp_makeArray: + case kIROp_makeMatrix: + // TODO: more cases + return true; + + default: + return false; + } +} + +bool opCanBeConstExpr(IRValue* value) +{ + // TODO: realistically need to special-case `call` + // operations here, so that we check whether the + // callee function is fixed/known, and if it is + // whether it has been decoared as constant-foldable + + return opCanBeConstExpr(value->op); +} + +void markConstExpr( + PropagateConstExprContext* context, + IRValue* value) +{ + Slang::markConstExpr(context->getSession(), value); +} + + +// Propagate `constexpr`-ness in a forward direction, from the +// operands of an instruction to the instruction itself. +bool propagateConstExprForward( + PropagateConstExprContext* context, + IRGlobalValueWithCode* code) +{ + bool anyChanges = false; + for(;;) + { + bool changedThisIteration = false; + for( auto bb = code->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) + { + // Instruction already `constexpr`? Then skip it. + if(isConstExpr(ii)) + continue; + + // Is the operation one that we can actually make be constexpr? + if(!opCanBeConstExpr(ii)) + continue; + + // Are all arguments `constexpr`? + bool allArgsConstExpr = true; + UInt argCount = ii->getArgCount(); + for( UInt aa = 0; aa < argCount; ++aa ) + { + auto arg = ii->getArg(aa); + + if( !isConstExpr(arg) ) + { + allArgsConstExpr = false; + break; + } + } + if(!allArgsConstExpr) + continue; + + // Seems like this operation can/should be made constexpr + markConstExpr(context, ii); + changedThisIteration = true; + } + } + + if( !changedThisIteration ) + return anyChanges; + + anyChanges = true; + } +} + +void maybeAddToWorkList( + PropagateConstExprContext* context, + IRGlobalValue* gv) +{ + if( !context->onWorkList.Contains(gv) ) + { + context->workList.Add(gv); + context->onWorkList.Add(gv); + } +} + +bool maybeMarkConstExpr( + PropagateConstExprContext* context, + IRValue* value) +{ + if(isConstExpr(value)) + return false; + + if(!opCanBeConstExpr(value)) + return false; + + markConstExpr(context, value); + + // TODO: we should only allow function parameters to be + // changed to be `constexpr` when we are compiling "application" + // code, and not library code. + // (Or eventually we'd have a rule that only non-`public` symbols + // can have this kind of propagation applied). + + if(value->op == kIROp_Param) + { + auto param = (IRParam*) value; + auto block = (IRBlock*) param->parent; + auto code = block->getParent(); + + if(block == code->getFirstBlock()) + { + // We've just changed a function parameter to + // be `constexpr`. We need to remember that + // fact so taht we can mark callers of this + // function as `constexpr` themselves. + + for( auto u = code->firstUse; u; u = u->nextUse ) + { + auto user = u->getUser(); + + switch( user->op ) + { + case kIROp_Call: + { + auto inst = (IRCall*) user; + auto caller = inst->getParentBlock()->getParent(); + maybeAddToWorkList(context, caller); + } + break; + + default: + break; + } + } + } + } + + return true; +} + +// Propagate `constexpr`-ness in a backward direction, from an instruction +// to its operands. +bool propagateConstExprBackward( + PropagateConstExprContext* context, + IRGlobalValueWithCode* code) +{ + SharedIRBuilder sharedBuilder; + sharedBuilder.module = code->parentModule; + sharedBuilder.session = sharedBuilder.module->session; + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilder; + builder.curFunc = code; + builder.curBlock = nullptr; + + bool anyChanges = false; + for(;;) + { + // Note: we are walking the list of blocks and the instructions + // in each block in reverse order, to maximize the chances that + // we propagate multiple changes in a each pass. + // + // TODO: this should probably all be done with a work list instead, + // but that requires being able to detect instructions vs. other + // values. + + bool changedThisIteration = false; + for( auto bb = code->getLastBlock(); bb; bb = bb->getPrevBlock() ) + { + for( auto ii = bb->getLastInst(); ii; ii = ii->getPrevInst() ) + { + if( isConstExpr(ii) ) + { + // If this instruction is `constexpr`, then its operands should be too. + UInt argCount = ii->getArgCount(); + for( UInt aa = 0; aa < argCount; ++aa ) + { + auto arg = ii->getArg(aa); + if(isConstExpr(arg)) + continue; + + if(!opCanBeConstExpr(arg)) + continue; + + if( maybeMarkConstExpr(context, arg) ) + { + changedThisIteration = true; + } + } + } + else if( ii->op == kIROp_Call ) + { + // A non-constexpr call might be calling a function with one or + // more constexpr parameters. We should check if we can resolve + // the callee for this call statically, and if so try to propagate + // constexpr from the parameters back to the arguments. + auto callInst = (IRCall*) ii; + + UInt operandCount = callInst->getArgCount(); + + UInt firstCallArg = 1; + UInt callArgCount = operandCount - firstCallArg; + + auto callee = callInst->getArg(0); + while( callee->op == kIROp_specialize ) + { + callee = ((IRSpecialize*) callee)->getArg(0); + } + if( callee->op == kIROp_Func ) + { + auto calleeFunc = (IRFunc*) callee; + auto calleeFuncType = calleeFunc->getType(); + + UInt callParamCount = calleeFuncType->getParamCount(); + SLANG_RELEASE_ASSERT(callParamCount == callArgCount); + + // If the callee has a definition, then we can read `constexpr` + // information off of the parameters of its first IR block. + if( auto calleeFirstBlock = calleeFunc->firstBlock ) + { + UInt paramCounter = 0; + for( auto pp = calleeFirstBlock->getFirstParam(); pp; pp = pp->getNextParam() ) + { + UInt paramIndex = paramCounter++; + + auto param = pp; + auto arg = callInst->getArg(firstCallArg + paramIndex); + + if( isConstExpr(param) ) + { + if( maybeMarkConstExpr(context, arg) ) + { + changedThisIteration = true; + } + } + } + } + else + { + // If we don't have the definition/body for the callee, + // then we have to glean `constexpr` information from its + // type instead. + auto calleeType = calleeFunc->getType(); + auto paramCount = calleeType->getParamCount(); + for( UInt pp = 0; pp < paramCount; ++pp ) + { + auto paramType = calleeType->getParamType(pp); + auto arg = callInst->getArg(firstCallArg + pp); + if( isConstExpr(paramType) ) + { + if( maybeMarkConstExpr(context, arg) ) + { + changedThisIteration = true; + } + } + } + } + + // TODO: this currently only works if the callee has a definition, + // because that is the only case where will generate IR values for + // its parameter list. Otherwise we'd need to pull this information + // from the function *type* for builtins. + if(!calleeFunc->firstBlock) + continue; + + } + } + } + + if( bb != code->getFirstBlock() ) + { + // A parameter in anything butr the first block is + // conceptually a phi node, which means its operands + // are the corresponding values from the terminating + // branch in a predecessor block. + + UInt paramCounter = 0; + for( auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam() ) + { + UInt paramIndex = paramCounter++; + + if(!isConstExpr(pp)) + continue; + + for(auto pred : bb->getPredecessors()) + { + auto terminator = pred->getLastInst(); + if(terminator->op != kIROp_unconditionalBranch) + continue; + + UInt operandIndex = paramIndex + 1; + SLANG_RELEASE_ASSERT(operandIndex < terminator->getArgCount()); + + auto operand = terminator->getArg(operandIndex); + if( maybeMarkConstExpr(context, operand) ) + { + changedThisIteration = true; + } + } + } + } + + } + + if( !changedThisIteration ) + return anyChanges; + + anyChanges = true; + } +} + +// Validate use of `constexpr` within a function (in particular, +// diagnose places where a value that must be contexpr depends +// on a value that cannot be) +void validateConstExpr( + PropagateConstExprContext* context, + IRGlobalValueWithCode* code) +{ + for( auto bb = code->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) + { + if(isConstExpr(ii)) + { + // For an instruction that must be `constexpr`, we need + // to ensure that its argumenst are all `constexpr` + + UInt argCount = ii->getArgCount(); + for( UInt aa = 0; aa < argCount; ++aa ) + { + auto arg = ii->getArg(aa); + + if( !isConstExpr(arg) ) + { + // Diagnose the failure. + + context->getSink()->diagnose(ii->sourceLoc, Diagnostics::needCompileTimeConstant); + + break; + } + } + } + } + } +} + +void propagateConstExpr( + IRModule* module, + DiagnosticSink* sink) +{ + auto session = module->session; + + PropagateConstExprContext context; + context.sink = sink; + context.sharedBuilder.module = module; + context.sharedBuilder.session = session; + context.builder.sharedBuilder = &context.sharedBuilder; + context.builder.curFunc = nullptr; + context.builder.curBlock = nullptr; + + + + // We need to propagate information both forward and backward. + // + // In the forward direction we need to check if all of the operands + // to an instruction are `constexpr` *and* if the operation is + // one that can conceptually be "promoted" to the constexpr rate. + // + // In the backward direction, if an instruction has already been + // marked as needing to be `constexpr`, then its operands had + // better be too. + // + // The backward direction needs to be interprocedural, because + // a parameter to a function might be `constexpr`, so that callers + // of that function would need to be marked too. If backwards + // propagation in any of the callers leads to some of their + // parameters being marked constexpr, then we would need to + // revisit their callers. + + // We will build an initial work list with all of the global values in it. + + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) + { + maybeAddToWorkList(&context, gv); + } + + // We will iterate applying propagation to one global value at a time + // until we run out. + while( context.workList.Count() ) + { + auto gv = context.workList[0]; + context.workList.FastRemoveAt(0); + context.onWorkList.Remove(gv); + + switch( gv->op ) + { + default: + break; + + case kIROp_Func: + case kIROp_global_var: + case kIROp_global_constant: + { + IRGlobalValueWithCode* code = (IRGlobalValueWithCode*) gv; + + for( ;;) + { + bool anyChange = false; + if( propagateConstExprForward(&context, code) ) + { + anyChange = true; + } + if( propagateConstExprBackward(&context, code) ) + { + anyChange = true; + } + if(!anyChange) + break; + } + } + break; + } + } + + // Okay, we've processed all our functions and found a steady state. + // Now we need to try and issue diagnostics for any IR values where + // we find that they are *required* to be `constexpr`, but *cannot* + // be, for some reason. + + for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() ) + { + switch( gv->op ) + { + default: + break; + + case kIROp_Func: + case kIROp_global_var: + case kIROp_global_constant: + { + IRGlobalValueWithCode* code = (IRGlobalValueWithCode*) gv; + validateConstExpr(&context, code); + } + break; + } + } + +} + +} diff --git a/source/slang/ir-constexpr.h b/source/slang/ir-constexpr.h new file mode 100644 index 000000000..04f2e59ec --- /dev/null +++ b/source/slang/ir-constexpr.h @@ -0,0 +1,12 @@ +// ir-constexpr.h +#pragma once + +namespace Slang +{ + class DiagnosticSink; + struct IRModule; + + void propagateConstExpr( + IRModule* module, + DiagnosticSink* sink); +} diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index aaebb9973..26cce054e 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -307,9 +307,9 @@ struct IRSwizzleSet : IRReturn // a stack allocation of some memory. struct IRVar : IRInst { - PtrType* getType() + PtrType* getDataType() { - return (PtrType*)type.Ptr(); + return (PtrType*) ((IRValue*) this)->getDataType(); } }; @@ -321,7 +321,10 @@ struct IRVar : IRInst /// blocks nested inside this value. struct IRGlobalVar : IRGlobalValueWithCode { - PtrType* getType() { return type.As<PtrType>(); } + PtrType* getDataType() + { + return (PtrType*) ((IRValue*) this)->getDataType(); + } }; /// @brief A global constant. @@ -733,6 +736,12 @@ void specializeGenerics( // +void markConstExpr( + Session* session, + IRValue* irValue); + +// + } #endif diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp index 2e69898e5..9cb32de8f 100644 --- a/source/slang/ir-legalize-types.cpp +++ b/source/slang/ir-legalize-types.cpp @@ -627,7 +627,7 @@ static LegalVal legalizeLocalVar( // Legalize the type for the variable's value auto legalValueType = legalizeType( context, - irLocalVar->getType()->getValueType()); + irLocalVar->getDataType()->getValueType()); RefPtr<VarLayout> varLayout = findVarLayout(irLocalVar); RefPtr<TypeLayout> typeLayout = varLayout ? varLayout->typeLayout : nullptr; @@ -809,7 +809,7 @@ static void legalizeFunc( // involve increasing the number of parameters for (auto pp = bb->getFirstParam(); pp; pp = pp->nextParam) { - auto legalParamType = legalizeType(context, pp->getType()); + auto legalParamType = legalizeType(context, pp->getFullType()); if (legalParamType.flavor != LegalType::Flavor::simple) { context->insertBeforeParam = pp; @@ -1053,7 +1053,7 @@ static void legalizeGlobalVar( // Legalize the type for the variable's value auto legalValueType = legalizeType( context, - irGlobalVar->getType()->getValueType()); + irGlobalVar->getDataType()->getValueType()); RefPtr<VarLayout> varLayout = findVarLayout(irGlobalVar); RefPtr<TypeLayout> typeLayout = varLayout ? varLayout->typeLayout : nullptr; @@ -1107,7 +1107,7 @@ static void legalizeGlobalConstant( // Legalize the type for the variable's value auto legalValueType = legalizeType( context, - irGlobalConstant->getType()); + irGlobalConstant->getFullType()); switch (legalValueType.flavor) { diff --git a/source/slang/ir-ssa.cpp b/source/slang/ir-ssa.cpp index 53eefd81f..b793f5320 100644 --- a/source/slang/ir-ssa.cpp +++ b/source/slang/ir-ssa.cpp @@ -207,7 +207,13 @@ PhiInfo* addPhi( IRVar* var) { auto builder = &blockInfo->builder; - IRParam* phi = builder->createParam(var->getType()->getValueType()); + + auto valueType = var->getDataType()->getValueType(); + if( auto rate = var->getRate() ) + { + valueType = context->sharedBuilder.getSession()->getRateQualifiedType(rate, valueType); + } + IRParam* phi = builder->createParam(valueType); RefPtr<PhiInfo> phiInfo = new PhiInfo(); context->phiInfos.Add(phi, phiInfo); @@ -451,7 +457,7 @@ IRValue* readVarRec( // a local lookup in the block had already failed, so // at this point we are dealing with an undefined value. - auto type = var->getType()->getValueType(); + auto type = var->getDataType()->getValueType(); val = blockInfo->builder.emitUndefined(type); } else if (!multiplePreds) diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 65f792577..090d0452b 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -1349,12 +1349,19 @@ namespace Slang IRInst* IRBuilder::emitLoad( IRValue* ptr) { + // Note: a `load` operation does not consider the rate + // (if any) attached to its operand (see the use of `getDataType` + // below). This means that a load from a rate-qualified + // variable will still conceptually execute (and return + // results) at the "default" rate of the parent function, + // unless a subsequent analysis pass constraints it. + RefPtr<Type> valueType; - if(auto ptrType = ptr->getType()->As<PtrTypeBase>()) + if(auto ptrType = ptr->getDataType()->As<PtrTypeBase>()) { valueType = ptrType->getValueType(); } - else if(auto ptrLikeType = ptr->getType()->As<PointerLikeType>()) + else if(auto ptrLikeType = ptr->getDataType()->As<PointerLikeType>()) { valueType = ptrLikeType->getElementType(); } @@ -1368,7 +1375,9 @@ namespace Slang // Ugly special case: the result of loading from `groupshared` // memory should not itself be `groupshared`. // - // TODO: Should this generalize to any "rate-qualified" type? + // TODO: This special case will go away once `GroupSharedType` + // is replaced by a `GroupSharedRate` that gets used together + // with `RateQualifiedType`. if(auto rateType = valueType->As<GroupSharedType>()) { valueType = rateType->valueType; @@ -2036,6 +2045,17 @@ namespace Slang dump(context, "@ThreadGroup "); dumpType(context, groupSharedType->valueType); } + else if(auto rateQualifiedType = type->As<RateQualifiedType>()) + { + dump(context, "@"); + dumpType(context, rateQualifiedType->rate); + dump(context, " "); + dumpType(context, rateQualifiedType->valueType); + } + else if(auto constExprRate = type->As<ConstExprRate>()) + { + dump(context, "ConstExpr"); + } else { // Need a default case here @@ -2117,7 +2137,7 @@ namespace Slang dumpIndent(context); dump(context, "param "); dumpID(context, pp); - dumpInstTypeClause(context, pp->getType()); + dumpInstTypeClause(context, pp->getFullType()); } context->indent -= 2; dump(context, ")"); @@ -2271,15 +2291,16 @@ namespace Slang dumpIndent(context); auto opInfo = &kIROpInfos[op]; - auto type = inst->getType(); + auto type = inst->getFullType(); + auto dataType = inst->getDataType(); - if (!type) + if (!dataType) { // No result, okay... } else { - auto basicType = type->As<BasicExpressionType>(); + auto basicType = dataType->As<BasicExpressionType>(); if (basicType && basicType->baseType == BaseType::Void) { // No result, okay... @@ -2439,7 +2460,7 @@ namespace Slang dumpIndent(context); dump(context, "ir_global_var "); dumpID(context, var); - dumpInstTypeClause(context, var->getType()); + dumpInstTypeClause(context, var->getFullType()); // TODO: deal with the case where a global // might have embedded initialization logic. @@ -2455,7 +2476,7 @@ namespace Slang dumpIndent(context); dump(context, "ir_global_constant "); dumpID(context, val); - dumpInstTypeClause(context, val->getType()); + dumpInstTypeClause(context, val->getFullType()); // TODO: deal with the case where a global // might have embedded initialization logic. @@ -2574,6 +2595,22 @@ namespace Slang // // + Type* IRValue::getRate() + { + if(auto rateQualifiedType = type->As<RateQualifiedType>()) + return rateQualifiedType->rate; + + return nullptr; + } + + Type* IRValue::getDataType() + { + if(auto rateQualifiedType = type->As<RateQualifiedType>()) + return rateQualifiedType->valueType; + + return type; + } + void IRValue::replaceUsesWith(IRValue* other) { // We will walk through the list of uses for the current @@ -3566,7 +3603,7 @@ namespace Slang // uses of the variable, and the exact logic there // will differ a bit between the pointer and non-pointer // cases. - auto paramType = pp->getType(); + auto paramType = pp->getDataType(); // Any initialization code we insert nees to be at the start // of the block: @@ -4167,7 +4204,15 @@ namespace Slang IRGlobalVar* originalVar, IROriginalValuesForClone const& originalValues) { - auto clonedVar = context->builder->createGlobalVar(context->maybeCloneType(originalVar->getType()->getValueType())); + auto clonedVar = context->builder->createGlobalVar( + context->maybeCloneType(originalVar->getDataType()->getValueType())); + + if(auto rate = originalVar->getRate() ) + { + clonedVar->type = context->builder->getSession()->getRateQualifiedType( + rate, clonedVar->type); + } + registerClonedValue(context, clonedVar, originalValues); auto mangledName = originalVar->mangledName; @@ -4196,7 +4241,7 @@ namespace Slang IRGlobalConstant* originalVal, IROriginalValuesForClone const& originalValues) { - auto clonedVal = context->builder->createGlobalConstant(context->maybeCloneType(originalVal->getType())); + auto clonedVal = context->builder->createGlobalConstant(context->maybeCloneType(originalVal->getFullType())); registerClonedValue(context, clonedVal, originalValues); auto mangledName = originalVal->mangledName; @@ -4290,7 +4335,7 @@ namespace Slang { IRParam* clonedParam = builder->emitParam( context->maybeCloneType( - originalParam->getType())); + originalParam->getFullType())); cloneDecorations(context, clonedParam, originalParam); registerClonedValue(context, clonedParam, originalParam); } @@ -5718,4 +5763,18 @@ namespace Slang } return globalParamSubst; } + + + void markConstExpr( + Session* session, + IRValue* irValue) + { + // We will take an IR value with type `T`, + // and turn it into one with type `@ConstExpr T`. + + // TODO: need to be careful if the value already has a rate + // qualifier set. + + irValue->type = session->getConstExprType(irValue->getDataType()); + } } diff --git a/source/slang/ir.h b/source/slang/ir.h index fdca73a83..3d4899824 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -157,7 +157,10 @@ struct IRValue : public IRObject // no value. RefPtr<Type> type; - Type* getType() { return type; } + Type* getFullType() { return type; } + + Type* getRate(); + Type* getDataType(); // Source location information for this value, if any SourceLoc sourceLoc; diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index b99e2713f..8c858f4f3 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -4,6 +4,7 @@ #include "../../slang.h" #include "ir.h" +#include "ir-constexpr.h" #include "ir-insts.h" #include "ir-ssa.h" #include "mangle.h" @@ -1262,7 +1263,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> // need to extract the value type from that pointer here. // IRValue* loweredBaseVal = getSimpleVal(context, loweredBase); - RefPtr<Type> loweredBaseType = loweredBaseVal->getType(); + RefPtr<Type> loweredBaseType = loweredBaseVal->getDataType(); if (loweredBaseType->As<PointerLikeType>() || loweredBaseType->As<PtrTypeBase>()) @@ -2580,7 +2581,7 @@ static LoweredValInfo maybeMoveMutableTemp( default: { IRValue* irVal = getSimpleVal(context, val); - auto type = irVal->getType(); + auto type = irVal->getDataType(); auto var = createVar(context, type); assign(context, var, LoweredValInfo::simple(irVal)); @@ -2660,7 +2661,7 @@ top: // Now apply the swizzle IRInst* irSwizzled = builder->emitSwizzleSet( - irLeftVal->getType(), + irLeftVal->getDataType(), irLeftVal, irRightVal, swizzleInfo->elementCount, @@ -3503,6 +3504,30 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> return false; } + bool isConstExprVar(Decl* decl) + { + if( decl->HasModifier<ConstExprModifier>() ) + { + return true; + } + else if(decl->HasModifier<HLSLStaticModifier>() && decl->HasModifier<ConstModifier>()) + { + return true; + } + + return false; + } + + RefPtr<Type> maybeGetConstExprType(Type* type, Decl* decl) + { + if(isConstExprVar(decl)) + { + return context->getSession()->getConstExprType(type); + } + + return type; + } + LoweredValInfo lowerFuncDecl(FunctionDeclBase* decl) { // Collect the parameter lists we will use for our new function. @@ -3563,11 +3588,11 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> for( auto paramInfo : parameterLists.params ) { RefPtr<Type> irParamType = lowerSimpleType(context, paramInfo.type); + switch( paramInfo.direction ) { case kParameterDirection_In: // Simple case of a by-value input parameter. - paramTypes.Add(irParamType); break; // If the parameter is declared `out` or `inout`, @@ -3575,18 +3600,26 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // the IR, but we will use a specialized pointer // type that encodes the parameter direction information. case kParameterDirection_Out: - paramTypes.Add( - context->getSession()->getOutType(irParamType)); + irParamType = context->getSession()->getOutType(irParamType); break; case kParameterDirection_InOut: - paramTypes.Add( - context->getSession()->getInOutType(irParamType)); + irParamType = context->getSession()->getInOutType(irParamType); break; default: SLANG_UNEXPECTED("unknown parameter direction"); break; } + + // If the parameter was explicitly marked as being a compile-time + // constant (`constexpr`), then attach that information to its + // IR-level type explicitly. + if( paramInfo.decl ) + { + irParamType = maybeGetConstExprType(irParamType, paramInfo.decl); + } + + paramTypes.Add(irParamType); } auto irResultType = lowerSimpleType(context, declForReturnType->ReturnType); @@ -3600,11 +3633,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // IRType* irParamType = irResultType; paramTypes.Add(irParamType); - subBuilder->emitParam(irParamType); - - // TODO: we need some way to wire this up to the `newValue` - // or whatever name we give for that parameter inside - // the setter body. // Instead, a setter always returns `void` // @@ -3660,11 +3688,11 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // // TODO: Is this the best representation we can use? - auto irPtrType = irParamType.As<PtrTypeBase>(); - - IRParam* irParamPtr = subBuilder->emitParam(irPtrType); + IRParam* irParamPtr = subBuilder->emitParam(irParamType); if(auto paramDecl = paramInfo.decl) + { subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl); + } paramVal = LoweredValInfo::ptr(irParamPtr); @@ -3682,27 +3710,51 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> case kParameterDirection_In: { // Simple case of a by-value input parameter. - // But note that HLSL allows an input parameter - // to be used as a local variable inside of a - // function body, so we need to introduce a temporary - // and then copy over to it... // - // TODO: we could skip this step if we knew - // the parameter was marked `const` or similar. - - paramTypes.Add(irParamType); - + // We start by declaring an IR parameter of the same type. + // + auto paramDecl = paramInfo.decl; IRParam* irParam = subBuilder->emitParam(irParamType); - if(auto paramDecl = paramInfo.decl) + if( paramDecl ) + { subBuilder->addHighLevelDeclDecoration(irParam, paramDecl); + } paramVal = LoweredValInfo::simple(irParam); - - auto irLocal = subBuilder->emitVar(irParamType); - auto localVal = LoweredValInfo::ptr(irLocal); - - assign(subContext, localVal, paramVal); - - paramVal = localVal; + // + // HLSL allows a function parameter to be used as a local + // variable in the function body (just like C/C++), so + // we need to support that case as well. + // + // However, if we notice that the parameter was marked + // `const`, then we can skip this step. + // + // TODO: we should consider having all parameter be implicitly + // immutable except in a specific "compatibility mode." + // + if(paramDecl && paramDecl->FindModifier<ConstModifier>()) + { + // This parameter was declared to be immutable, + // so there should be no assignment to it in the + // function body, and we don't need a temporary. + } + else + { + // The parameter migth get used as a temporary in + // the function body. We will allocate a mutable + // local variable for is value, and then assign + // from the parameter to the local at the start + // of the function. + // + auto irLocal = subBuilder->emitVar(irParamType); + auto localVal = LoweredValInfo::ptr(irLocal); + assign(subContext, localVal, paramVal); + // + // When code later in the body of the function refers + // to the parameter declaration, it will actually refer + // to the value stored in the local variable. + // + paramVal = localVal; + } } break; } @@ -3719,6 +3771,18 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> } } + if (auto setterDecl = dynamic_cast<SetterDecl*>(decl)) + { + // Add the IR parameter for the new value + IRType* irParamType = irResultType; + subBuilder->emitParam(irParamType); + + // TODO: we need some way to wire this up to the `newValue` + // or whatever name we give for that parameter inside + // the setter body. + } + + lowerStmt(subContext, decl->Body); // We need to carefully add a terminator instruction to the end @@ -4070,7 +4134,7 @@ LoweredValInfo maybeEmitSpecializeInst(IRGenContext* context, DeclRef<Decl> newDeclRef = DeclRef<Decl>(declRef.decl, lowedNewSubst); RefPtr<Type> type; - if (auto declType = val->getType()) + if (auto declType = val->getDataType()) { type = declType->Substitute(newDeclRef.substitutions).As<Type>(); } @@ -4225,6 +4289,10 @@ IRModule* generateIRForTranslationUnit( // TODO: Do basic constant folding and DCE + // Propagate `constexpr`-ness through the dataflow graph (and the + // call graph) based on constraints imposed by different instructions. + propagateConstExpr(module, &compileRequest->mSink); + // TODO: give error messages if any `undefined` or // `unreachable` instructions remain. diff --git a/source/slang/modifier-defs.h b/source/slang/modifier-defs.h index 3750f3373..725629d35 100644 --- a/source/slang/modifier-defs.h +++ b/source/slang/modifier-defs.h @@ -22,6 +22,7 @@ SIMPLE_MODIFIER(FromStdLib); SIMPLE_MODIFIER(Prefix); SIMPLE_MODIFIER(Postfix); SIMPLE_MODIFIER(Exported); +SIMPLE_MODIFIER(ConstExpr); #undef SIMPLE_MODIFIER diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 42e412438..f2e5587fa 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -747,6 +747,7 @@ Session::~Session() initializerListType = nullptr; overloadedType = nullptr; irBasicBlockType = nullptr; + constExprRate = nullptr; builtinTypes = decltype(builtinTypes)(); // destroy modules next diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 0154dd98f..3f2e19166 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -177,6 +177,7 @@ <ClInclude Include="diagnostics.h" /> <ClInclude Include="emit.h" /> <ClInclude Include="expr-defs.h" /> + <ClInclude Include="ir-constexpr.h" /> <ClInclude Include="ir-inst-defs.h" /> <ClInclude Include="ir-insts.h" /> <ClInclude Include="ir-ssa.h" /> @@ -218,6 +219,7 @@ <ClCompile Include="diagnostics.cpp" /> <ClCompile Include="dxc-support.cpp" /> <ClCompile Include="emit.cpp" /> + <ClCompile Include="ir-constexpr.cpp" /> <ClCompile Include="ir-legalize-types.cpp" /> <ClCompile Include="ir-ssa.cpp" /> <ClCompile Include="ir.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index e64721f6d..1d7e1e942 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -46,6 +46,7 @@ <ClInclude Include="legalize-types.h" /> <ClInclude Include="ir-ssa.h" /> <ClInclude Include="memory_pool.h" /> + <ClInclude Include="ir-constexpr.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="check.cpp" /> @@ -77,6 +78,7 @@ <ClCompile Include="legalize-types.cpp" /> <ClCompile Include="ir-ssa.cpp" /> <ClCompile Include="memory_pool.cpp" /> + <ClCompile Include="ir-constexpr.cpp" /> </ItemGroup> <ItemGroup> <CustomBuild Include="core.meta.slang" /> diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp index 62e467a44..ab472fe9f 100644 --- a/source/slang/syntax.cpp +++ b/source/slang/syntax.cpp @@ -231,6 +231,9 @@ void Type::accept(IValVisitor* visitor, void* extra) irBasicBlockType = new IRBasicBlockType(); irBasicBlockType->setSession(this); + + constExprRate = new ConstExprRate(); + constExprRate->setSession(this); } Type* Session::getBoolType() @@ -288,6 +291,22 @@ void Type::accept(IValVisitor* visitor, void* extra) return irBasicBlockType; } + Type* Session::getConstExprRate() + { + return constExprRate; + } + + RefPtr<RateQualifiedType> Session::getRateQualifiedType( + Type* rate, + Type* valueType) + { + RefPtr<RateQualifiedType> rateQualifiedType = new RateQualifiedType(); + rateQualifiedType->setSession(this); + rateQualifiedType->rate = rate; + rateQualifiedType->valueType = valueType; + return rateQualifiedType; + } + RefPtr<PtrType> Session::getPtrType( RefPtr<Type> valueType) { @@ -407,6 +426,88 @@ void Type::accept(IValVisitor* visitor, void* extra) return baseType->ToString() + "[]"; } + // RateQualifiedType + + Slang::String RateQualifiedType::ToString() + { + return "@" + rate->ToString() + " " + valueType->ToString(); + } + + bool RateQualifiedType::EqualsImpl(Type * type) + { + auto rateQualifiedType = type->As<RateQualifiedType>(); + if(!rateQualifiedType) + return false; + + return rate->Equals(rateQualifiedType->rate) + && valueType->Equals(rateQualifiedType->valueType); + } + + RefPtr<Val> RateQualifiedType::SubstituteImpl(SubstitutionSet subst, int* ioDiff) + { + int diff = 0; + auto substRate = rate->SubstituteImpl(subst, &diff).As<Type>(); + auto substValueType = valueType->SubstituteImpl(subst, &diff).As<Type>(); + if(!diff) + return this; + + (*ioDiff)++; + + return getSession()->getRateQualifiedType(substRate, substValueType); + } + + RefPtr<Type> RateQualifiedType::CreateCanonicalType() + { + RefPtr<Type> canRate = rate->GetCanonicalType(); + RefPtr<Type> canValueType = valueType->GetCanonicalType(); + + RefPtr<RateQualifiedType> canRateQualifiedType = new RateQualifiedType(); + canRateQualifiedType->setSession(session); + canRateQualifiedType->rate = canRate; + canRateQualifiedType->valueType = valueType; + return canRateQualifiedType; + } + + int RateQualifiedType::GetHashCode() + { + auto hash = (int)(typeid(this).hash_code()); + hash = combineHash(hash, rate->GetHashCode()); + hash = combineHash(hash, valueType->GetHashCode()); + return hash; + } + + // ConstExprRate + + Slang::String ConstExprRate::ToString() + { + return "ConstExpr"; + } + + bool ConstExprRate::EqualsImpl(Type * type) + { + auto constExprRate = type->As<ConstExprRate>(); + if(!constExprRate) + return false; + + return true; + } + + RefPtr<Val> ConstExprRate::SubstituteImpl(SubstitutionSet /*subst*/, int* /*ioDiff*/) + { + return this; + } + + RefPtr<Type> ConstExprRate::CreateCanonicalType() + { + return this; + } + + int ConstExprRate::GetHashCode() + { + auto hash = (int)(typeid(this).hash_code()); + return hash; + } + // GroupSharedType Slang::String GroupSharedType::ToString() diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h index 14e99caf9..70f062cdc 100644 --- a/source/slang/type-defs.h +++ b/source/slang/type-defs.h @@ -318,7 +318,46 @@ protected: ) END_SYNTAX_CLASS() +// A type that has a rate qualifier applied. Conceptually `@R T` where `R` +// represents a rate, and `T` represents a data type. +SYNTAX_CLASS(RateQualifiedType, Type) + + // The rate `R` at which the value is computed/stored + SYNTAX_FIELD(RefPtr<Type>, rate); + + // The underlying data type `T` of the value + SYNTAX_FIELD(RefPtr<Type>, valueType); + +RAW( + virtual Slang::String ToString() override; + +protected: + virtual bool EqualsImpl(Type * type) override; + virtual RefPtr<Type> CreateCanonicalType() override; + virtual RefPtr<Val> SubstituteImpl(SubstitutionSet subst, int* ioDiff) override; + virtual int GetHashCode() override; + ) +END_SYNTAX_CLASS() + +// A representation of the `ConstExpr` rate, to be used +// in defining `@ConstExpr T` for particular data types `T` +SYNTAX_CLASS(ConstExprRate, Type) + +RAW( + virtual Slang::String ToString() override; + +protected: + virtual bool EqualsImpl(Type * type) override; + virtual RefPtr<Type> CreateCanonicalType() override; + virtual RefPtr<Val> SubstituteImpl(SubstitutionSet subst, int* ioDiff) override; + virtual int GetHashCode() override; + ) +END_SYNTAX_CLASS() + // The effective type of a variable declared with `groupshared` storage qualifier. +// +// TODO: this should be converted to a `GroupSharedRate`, which then gets used +// in conjunction with `RateQualifiedType`. SYNTAX_CLASS(GroupSharedType, Type) SYNTAX_FIELD(RefPtr<Type>, valueType); diff --git a/tests/compute/constexpr.slang b/tests/compute/constexpr.slang new file mode 100644 index 000000000..c3f9b6a89 --- /dev/null +++ b/tests/compute/constexpr.slang @@ -0,0 +1,111 @@ +// constexpr.slang +//TEST(compute):COMPARE_COMPUTE_EX:-slang -gcompute +//DISABLED://TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -gcompute +//TEST_INPUT: Texture2D(size=4, content = one) : dxbinding(0),glbinding(0) +//TEST_INPUT: Sampler : dxbinding(0),glbinding(0) +//TEST_INPUT: ubuffer(data=[0 0], stride=4):dxbinding(1),glbinding(0),out + +// Note: Vulkan version of this test is disabled pending adding +// support for rendering tests to the harness. + +Texture2D tex; +SamplerState samp; +RWStructuredBuffer<float> outputBuffer; + +cbuffer Uniforms +{ + float4x4 modelViewProjection; +} + +struct AssembledVertex +{ + float3 position; + float3 color; + float2 uv; +}; + +struct CoarseVertex +{ + float3 color; + float2 uv; +}; + +struct Fragment +{ + float4 color; +}; + + +// Vertex Shader + +struct VertexStageInput +{ + AssembledVertex assembledVertex : A; +}; + +struct VertexStageOutput +{ + CoarseVertex coarseVertex : CoarseVertex; + float4 sv_position : SV_Position; +}; + +VertexStageOutput vertexMain(VertexStageInput input) +{ + VertexStageOutput output; + + float3 position = input.assembledVertex.position; + float3 color = input.assembledVertex.color; + + output.coarseVertex.color = color; + output.sv_position = mul(modelViewProjection, float4(position, 1.0)); + output.coarseVertex.uv = input.assembledVertex.uv; + return output; +} + +// Fragment Shader + +struct FragmentStageInput +{ + CoarseVertex coarseVertex : CoarseVertex; +}; + +struct FragmentStageOutput +{ + Fragment fragment : SV_Target; +}; + +FragmentStageOutput fragmentMain(FragmentStageInput input) +{ + // The texel offset argument to `Texture2D.Sample` is + // required to be `constexpr`. This test is going to + // check that we correctly propagate this constraint + // backward to the value `a`. + // + // Because the HLSL compiler(s) already do this kind + // of propagation, the only real way to test this + // will be to target Vulkan, where the standard + // GLSL compiler gives an error message rather than + // infer `const`-ness. + + uint a = 0; + constexpr uint b = 1; + + uint2 ab = uint2(a,b); + + FragmentStageOutput output; + + float3 color = input.coarseVertex.color; + float2 uv = input.coarseVertex.uv; + output.fragment.color = float4(color, 1.0); + + float4 val = float4(color, 1.0); + val = val - 16*tex.Sample(samp, uv, ab); + + outputBuffer[0] = 1; + + if(val.x < 0) + discard; + + outputBuffer[1] = 1; + return output; +}
\ No newline at end of file diff --git a/tests/compute/constexpr.slang.1.expected.txt b/tests/compute/constexpr.slang.1.expected.txt new file mode 100644 index 000000000..e0e43c4e8 --- /dev/null +++ b/tests/compute/constexpr.slang.1.expected.txt @@ -0,0 +1,2 @@ +3F800000 +0 diff --git a/tests/compute/constexpr.slang.expected.txt b/tests/compute/constexpr.slang.expected.txt new file mode 100644 index 000000000..e0e43c4e8 --- /dev/null +++ b/tests/compute/constexpr.slang.expected.txt @@ -0,0 +1,2 @@ +3F800000 +0 diff --git a/tests/diagnostics/constexpr-error.slang b/tests/diagnostics/constexpr-error.slang new file mode 100644 index 000000000..6006450a1 --- /dev/null +++ b/tests/diagnostics/constexpr-error.slang @@ -0,0 +1,45 @@ +//TEST:SIMPLE: + +// Failure to pass compile-time-constant data +// where it is expected. +// +// In this case, the place where compile-time-constant +// data is expected is the texel offset parameter to +// the `Texture2D.Sample` operation. + +Texture2D t; +SamplerState s; + +cbuffer U +{ + float2 uv; + uint2 offset; +}; + +float4 main() : SV_Target +{ + float4 result = 0.0f; + + // Okay, immediate constant + result += t.Sample(s, uv, uint2(0,0)); + + // Error: data passed through cbuffer isn't compile-time constant + result += t.Sample(s, uv, offset); + + // Error: data computed via conditional isn't compile-time cosntant + uint ii = 0; + if(uv.x > 0.0f) + { + ii = 1; + } + result += t.Sample(s, uv, uint2(ii)); + + // Error: data computed in loop isn't compile-time constant + // (and loop isn't unroll-able) + for(uint jj = 0; jj < uv.y; jj++) + { + result += t.Sample(s, uv, uint2(jj)); + } + + return result; +}
\ No newline at end of file diff --git a/tests/diagnostics/constexpr-error.slang.expected b/tests/diagnostics/constexpr-error.slang.expected new file mode 100644 index 000000000..c0b5e94d3 --- /dev/null +++ b/tests/diagnostics/constexpr-error.slang.expected @@ -0,0 +1,8 @@ +result code = -1 +standard error = { +tests/diagnostics/constexpr-error.slang(27): error 40006: expected a compile-time constant +tests/diagnostics/constexpr-error.slang(35): error 40006: expected a compile-time constant +tests/diagnostics/constexpr-error.slang(41): error 40006: expected a compile-time constant +} +standard output = { +} diff --git a/tests/hlsl/dxsdk/VarianceShadows11/2DQuadShaders.hlsl b/tests/hlsl/dxsdk/VarianceShadows11/2DQuadShaders.hlsl index 1804c3d3c..3b41d92b3 100644 --- a/tests/hlsl/dxsdk/VarianceShadows11/2DQuadShaders.hlsl +++ b/tests/hlsl/dxsdk/VarianceShadows11/2DQuadShaders.hlsl @@ -1,4 +1,9 @@ -//TEST:COMPARE_HLSL:-no-mangle -target dxbc-assembly -profile vs_4_0 -entry VSMain -profile ps_4_0 -entry PSBlurX -entry PSBlurY +//TEST_DISABLED:COMPARE_HLSL:-no-mangle -target dxbc-assembly -profile vs_4_0 -entry VSMain -profile ps_4_0 -entry PSBlurX -entry PSBlurY + +//SLANG: This test has been disabled because its semantic correctness +//around use of compile-time-constant expressions relies on processing +//the `[unroll]` attribute, and we don't yet support that. + //-------------------------------------------------------------------------------------- // File: Skinning10.fx // diff --git a/tests/ir/loop.slang.expected b/tests/ir/loop.slang.expected index 10c091e07..0ff22c2b5 100644 --- a/tests/ir/loop.slang.expected +++ b/tests/ir/loop.slang.expected @@ -19,12 +19,12 @@ block %1( let %6 : StructuredBuffer<vector<float,4>> = load(@_SV05input) let %7 : vector<float,4> = bufferLoad(%6, %2) store(%5, %7) - let %8 : uint = construct(1) + let %8 : @ConstExpr uint = construct(1) loop(%9, %10, %11, %8) block %9( param %12 : uint): - let %13 : uint = construct(64) + let %13 : @ConstExpr uint = construct(64) let %14 : bool = cmpLT(%12, %13) ifElse(%14, %15, %10, %15) @@ -40,7 +40,7 @@ block %15: unconditionalBranch(%11) block %11: - let %22 : uint = construct(1) + let %22 : @ConstExpr uint = construct(1) let %23 : uint = shl(%12, %22) unconditionalBranch(%9, %23) |
