diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2021-02-04 11:15:46 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-04 11:15:46 -0800 |
| commit | ef283b80b886c7a0fb34c20e07a43ccca3237ed8 (patch) | |
| tree | 1132d7b360ec1f4389383db559b5d075817aed2c | |
| parent | 4c66c17b2e5572c95da260ea4761f5804eb52853 (diff) | |
Change how function-scope static variables lower to IR (#1686)
This change pertains to `static` variables in function scope (including things like methods, initializers, property accessors, etc.). Note that it does *not* have anything to do with global-scope `static` variables or with `static const` variables (whether inside a function or not).
The old code generation strategy had a lot of "clever" code to deal with the problem of a `static` variable inside a generic function (or inside a function inside a generic type, etc.). Basically, if you had input code like:
int myFunc<T>(int newVal) {
static int state = 0;
int result = state;
state = newVal;
return result;
}
The language semantics are that `myFunc<float3>` should have a different `state` variable than `myFunc<int2>`. The way that the existing codegen handled that was to generate the `state` variable into its own dedicated `IRGeneric`. Something like:
generic myFunc_state<T0> { global_var g_ptr : int*; return g_ptr; }
generic myFunc<T1> {
func f(int newVal) {
let result : int = load(state<T>);
store(state<T1>, newVal);
return result;
}
}
The catch there is that you end up needing to generate an entire second `IRGeneric`, and then references to `state` need to explicitly use `specialize` to instantiate that generic using the same parameters as `myFunc` was passed (note how `T0` and `T1` are distinct IR generic parameters, despite both representing `T` here).
Things get even more complicated when you consider function-`static` variables with initialization logic, since we need to be sure we only perform that initialization once, but the initialization could refer to arguments of the outer function, and thus needs to be done inside the function body. To handle that case we emit an additional `bool` global if a function-`static` variable has an initializer, and that `bool` gets wrapped up in yet another generic.
That whole approach seems silly in retrospect, and a much simpler solution is possible: just emit the function-`static` variable immediately before the IR function it pertains to, which means it will be nested under the *same* IR generic if there is one (and at module scope if there isn't). The result is something like:
generic myFunc<T1> {
global_var state_ptr : int*;
func f(int newVal) {
let result : int = load(state_ptr);
store(state_ptr, newVal);
return result;
}
}
This change implements that simplification, and all the same tests pass (including whatever tests we had for function-`static` variables).
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 146 |
1 files changed, 23 insertions, 123 deletions
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 06e463d7d..f1a7c477b 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -5676,79 +5676,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> return false; } - IRInst* defaultSpecializeOuterGeneric( - IRInst* outerVal, - IRType* type, - GenericDecl* genericDecl) - { - auto builder = getBuilder(); - - // We need to specialize any generics that are further out... - auto specialiedOuterVal = defaultSpecializeOuterGenerics( - outerVal, - builder->getGenericKind(), - genericDecl); - - List<IRInst*> genericArgs; - - // Walk the parameters of the generic, and emit an argument for each, - // which will be a reference to binding for that parameter in the - // current scope. - // - // First we start with type and value parameters, - // in the order they were declared. - for (auto member : genericDecl->members) - { - if (auto typeParamDecl = as<GenericTypeParamDecl>(member)) - { - genericArgs.add(getSimpleVal(context, ensureDecl(context, typeParamDecl))); - } - else if (auto valDecl = as<GenericValueParamDecl>(member)) - { - genericArgs.add(getSimpleVal(context, ensureDecl(context, valDecl))); - } - } - // Then we emit constraint parameters, again in - // declaration order. - for (auto member : genericDecl->members) - { - if (auto constraintDecl = as<GenericTypeConstraintDecl>(member)) - { - genericArgs.add(getSimpleVal(context, ensureDecl(context, constraintDecl))); - } - } - - return builder->emitSpecializeInst(type, specialiedOuterVal, genericArgs.getCount(), genericArgs.getBuffer()); - } - - IRInst* defaultSpecializeOuterGenerics( - IRInst* val, - IRType* type, - Decl* decl) - { - if(!val) return nullptr; - - auto parentVal = val->getParent(); - while(parentVal) - { - if(as<IRGeneric>(parentVal)) - break; - parentVal = parentVal->getParent(); - } - if(!parentVal) - return val; - - for(auto pp = decl->parentDecl; pp; pp = pp->parentDecl) - { - if(auto genericAncestor = as<GenericDecl>(pp)) - { - return defaultSpecializeOuterGeneric(parentVal, type, genericAncestor); - } - } - - return val; - } - struct NestedContext { IRGenEnv subEnvStorage; @@ -5798,16 +5725,30 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> if(decl->hasModifier<ConstModifier>()) return lowerFunctionStaticConstVarDecl(decl); - // A global variable may need to be generic, if one - // of the outer declarations is generic. + // A function-scope `static` variable is effectively a global, + // and a simple solution here would be to try to emit this + // variable directly into the global scope. + // + // The one major wrinkle we need to deal with is the way that + // a function-scope `static` variable could be nested under + // a generic, leading to the situation that different instances + // of that same generic would need distinct storage for that + // variable declaration. + // + // We will handle that constraint by carefully nesting the + // IR global variable under the parent of its containing + // function. + // + auto parent = getBuilder()->insertIntoParent; + if(auto block = as<IRBlock>(parent)) + parent = block->getParent(); + NestedContext nestedContext(this); auto subBuilder = nestedContext.getBuilder(); auto subContext = nestedContext.getContext(); - subBuilder->setInsertInto(subBuilder->getModule()->getModuleInst()); - auto outerGeneric = emitOuterGenerics(subContext, decl, decl); + subBuilder->setInsertBefore(parent); IRType* subVarType = lowerType(subContext, decl->getType()); - IRGlobalValueWithCode* irGlobal = subBuilder->createGlobalVar(subVarType); addVarDecorations(subContext, irGlobal, decl); @@ -5816,46 +5757,14 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> subBuilder->addHighLevelDeclDecoration(irGlobal, decl); - // We are inside of a function, and that function might be generic, - // in which case the `static` variable will be lowered to another - // generic. Let's start with a terrible example: - // - // interface IHasCount { int getCount(); } - // int incrementCounter<T : IHasCount >(T val) { - // static int counter = 0; - // counter += val.getCount(); - // return counter; - // } - // - // In this case, `incrementCounter` will lower to a function - // nested in a generic, while `counter` will be lowered to - // a global variable nested in a *different* generic. - // The net result is something like this: - // - // int counter<T:IHasCount> = 0; - // - // int incrementCounter<T:IHasCount>(T val) { - // counter<T> += val.getCount(); - // return counter<T>; - // - // The references to `counter` inside of `incrementCounter` - // become references to `counter<T>`. - // - // At the IR level, this means that the value we install - // for `decl` needs to be a specialized reference to `irGlobal`, - // for any outer generics. - // - IRType* varType = lowerType(context, decl->getType()); - IRType* varPtrType = getBuilder()->getPtrType(varType); - auto irSpecializedGlobal = defaultSpecializeOuterGenerics(irGlobal, varPtrType, decl); - LoweredValInfo globalVal = LoweredValInfo::ptr(irSpecializedGlobal); + LoweredValInfo globalVal = LoweredValInfo::ptr(irGlobal); setValue(context, decl, globalVal); // A `static` variable with an initializer needs special handling, // at least if the initializer isn't a compile-time constant. if( auto initExpr = decl->initExpr ) { - // We must create an ordinary global `bool isInitialized = false` + // We must create another global `bool isInitialized = false` // to represent whether we've initialized this before. // Then emit code like: // @@ -5867,14 +5776,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // or not generating it in the first place. That is a bit // more complexity than I'm ready for at the moment. // - - // Of course, if we are under a generic, then the Boolean - // variable need to be generic as well! - NestedContext nestedBoolContext(this); - auto boolBuilder = nestedBoolContext.getBuilder(); - auto boolContext = nestedBoolContext.getContext(); - boolBuilder->setInsertInto(boolBuilder->getModule()->getModuleInst()); - emitOuterGenerics(boolContext, decl, decl); + auto boolBuilder = subBuilder; auto irBoolType = boolBuilder->getBoolType(); auto irBool = boolBuilder->createGlobalVar(irBoolType); @@ -5882,7 +5784,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> boolBuilder->setInsertInto(boolBuilder->createBlock()); boolBuilder->emitReturn(boolBuilder->getBoolValue(false)); - auto boolVal = LoweredValInfo::ptr(defaultSpecializeOuterGenerics(irBool, irBoolType, decl)); + auto boolVal = LoweredValInfo::ptr(irBool); // Okay, with our global Boolean created, we can move on to @@ -5904,8 +5806,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> builder->insertBlock(afterBlock); } - irGlobal->moveToEnd(); - finishOuterGenerics(subBuilder, irGlobal, outerGeneric); return globalVal; } |
