summaryrefslogtreecommitdiffstats
path: root/source/slang/lower-to-ir.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2018-02-22 11:44:57 -0800
committerGitHub <noreply@github.com>2018-02-22 11:44:57 -0800
commit7eaaf1aa1fdac3fd7ff7b64800a965a2b2c17f66 (patch)
tree0ce13473cf162c3982a0f6e28adebd3f8a89bc46 /source/slang/lower-to-ir.cpp
parent01c4134d33cea3a4f98fad9248584278fd4bc452 (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
Diffstat (limited to 'source/slang/lower-to-ir.cpp')
-rw-r--r--source/slang/lower-to-ir.cpp136
1 files changed, 102 insertions, 34 deletions
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.