diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/bytecode.cpp | 16 | ||||
| -rw-r--r-- | source/slang/compiler.h | 12 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 28 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 28 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 72 | ||||
| -rw-r--r-- | source/slang/ir-constexpr.cpp | 531 | ||||
| -rw-r--r-- | source/slang/ir-constexpr.h | 12 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 15 | ||||
| -rw-r--r-- | source/slang/ir-legalize-types.cpp | 8 | ||||
| -rw-r--r-- | source/slang/ir-ssa.cpp | 10 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 85 | ||||
| -rw-r--r-- | source/slang/ir.h | 5 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 136 | ||||
| -rw-r--r-- | source/slang/modifier-defs.h | 1 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 1 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 2 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 101 | ||||
| -rw-r--r-- | source/slang/type-defs.h | 39 |
20 files changed, 1002 insertions, 104 deletions
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); |
