-
Notifications
You must be signed in to change notification settings - Fork 151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add slangc flag to -zero-initialize
all variables
#3987
base: master
Are you sure you want to change the base?
Add slangc flag to -zero-initialize
all variables
#3987
Conversation
Adds `-zero-initialize` flag to set values to a __default() expression if they are missing a initExpr.
…on-to-zero-initialize-all-variables
-zero-initialize
all variables, #3516-zero-initialize
all variables
-zero-initialize
all variables-zero-initialize
all variables
source/slang/slang-check-decl.cpp
Outdated
@@ -1636,6 +1636,23 @@ namespace Slang | |||
} | |||
} | |||
|
|||
if (!varDecl->initExpr && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this logic should be in SemanticsDeclBodyVisitor::checkVarDeclCommon
instead.
There we are checking if a default ctor exists and calls it if so.
We should extend that logic by saying if default ctor does not exist, then initialize it with __default<T>()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will not be-able todo this logic in BodyVisitor since then when setting init's to struct fields we would have already parsed+setup the default ctor body logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be synthesizing ctor signature in header visitor, and synthesize the ctor body in body visitor, there we call the ctors of the field. Why isn't this possible? We shouln't be checking the init exprs in body visitor IMO.
You should write |
1. We must keep zero-initialize in SemanticsDeclHeaderVisitor. This is done because else a ctor will be initialized before we can set struct fields to `__default`. 2. IRDefaultCtorDecoration was added to track default ctor's with parent struct. 3. ParentAggTypeModifier was added to track ChildOfStruct->IRType for sharing data such as with functions. This is required to ensure we associate a lowered function with a lowered struct type
…on-to-zero-initialize-all-variables
This was done since decorations are checked for IR objects, storing auxillary info does not work here as a result if usable object.
…hub.com/ArielG-NV/slang into option-to-zero-initialize-all-variables
source/slang/slang-ast-modifier.h
Outdated
@@ -1585,6 +1585,24 @@ class DynamicUniformModifier : public Modifier | |||
SLANG_AST_CLASS(DynamicUniformModifier) | |||
}; | |||
|
|||
// Sharing IRInst data of AggType (Struct/Interface) with members. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be needed. In general we should never be creating AST nodes that references IR constructs.
source/slang/slang-ir.h
Outdated
IR_LEAF_ISA(StructType) | ||
|
||
IRFunc* defaultCtor = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be adding fields to IR insts like this. All references must be encoded as either a child or an operand.
And IRStruct should not be referencing ctors directly. You can use a decoration to associate a default ctor if necessary. But i don't think we should need this, because we should be generating all default ctor calls during IR lowering, and we should never need to have the concept of a ctor in the IR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a hole in our generics system that we don't have to address in this PR.
What we probably should do now is that if T doesn't have a default initializer from any constraints, then we don't call any default ctor even after specialization. However, we still want to make sure we zero initialize whatever type it became after specialization with defaultConstruct inst.
So when we lower the generic function to IR, we check if T has default ctor, if it has we call it. If not we emit a defaultConstruct inst.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do this, we don't need to keep track of the default ctor of each struct type in the IR in any explicit way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workaround added was not to handle generics, generics work as intended with __default as tested. I do see where this can be useful though (interfaces->struct to define an __init)
Passing a struct<->Ctor was done because of the cases like vector<...struct...>
. We don't exactly know what is inside the container, so we cannot simply call an 'invoke' to the ctor. Only __default has emitting logic to manage these code edge cases which is why a ctor was passed through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well,if you have:
struct MyContainer<T>
{
T obj;
}
Then the right answer is always do obj = __default<T>()
, which will eventually lower into defaultConstruct
.
If you have:
struct MyContainer<T:IDefaultInitializable> // or any other interface that leads to `__init()` to be found
{
T obj;
}
Then you lower it into:
obj = T();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In either case, you shouldn't need to keep track of this additional ctor info.
source/slang/slang-lower-to-ir.cpp
Outdated
{ | ||
for (auto ctor : decl->getMembersOfType<ConstructorDecl>()) | ||
{ | ||
auto parentStructTypeModifier = this->context->astBuilder->create<ParentStructTypeModifier>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simply maintain a side band dictionary in the ir lowering context to keep track of struct types and their ctors.
//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer | ||
RWStructuredBuffer<int> outputBuffer; | ||
|
||
__generic<T : __BuiltinArithmeticType> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For generics, we will need a IDefaultInitializable builtin interface that has a __init() requirement.
Then for any generic type that conforms to IDefaultInitializable, we can just call the interface method to initialize it. By default, we make all builtin types conform to this interface, and we can make all user defined struct types to automatically conform to the interface as we synthesize the default constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would solve the problem we have with vectors and remove the need for a workaround
we could define the vector IDefaultInitializable as the base type __default, which would resolve into a ctor eventually for all elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all builtin interfaces should inherit from IDefaultInitializable.
I am wondering if we can have tests for cases when "-zero-initialize" is not used. |
@jkwak-work Agreed that we need a test case to cover this. Basically, when -zero-initialize is not specified, then we should not be generating default ctors if a type does not have any member initializers, when we should not be generating assignments in default ctors for members that are not initialized. Then, if we don't synthesis such a default ctor, we won't be calling the default ctor at variable declaration sites. |
I will add the test case, I agree that there should be one for each possible senario:
|
-zero-initialize
all variables-zero-initialize
all variables
Since `IDefaultInitializable` is taking a considerabley larger amount of time than anticipated I am pushing some of the other fixes requested. I did not remove the "IRStruct storing a default Ctor" hack yet. mostly renamed/adjusted tests to work as intended added test to ensure we don't synthisize a junk `= 0` when not in `zero initialize` mode removed member in favor of sharedContext+dictionary.
tests/language-feature/zero-initialize/struct-no-zero-init.slang
Outdated
Show resolved
Hide resolved
source/slang/slang-check-decl.cpp
Outdated
@@ -1639,7 +1639,8 @@ namespace Slang | |||
} | |||
} | |||
|
|||
// We must keep zero-initialize in SemanticsDeclHeaderVisitor. | |||
// We must keep zero-initialize for struct fields in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I understand the comment here.
source/slang/slang-check-decl.cpp
Outdated
@@ -1636,6 +1636,23 @@ namespace Slang | |||
} | |||
} | |||
|
|||
if (!varDecl->initExpr && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be synthesizing ctor signature in header visitor, and synthesize the ctor body in body visitor, there we call the ctors of the field. Why isn't this possible? We shouln't be checking the init exprs in body visitor IMO.
source/slang/slang-lower-to-ir.cpp
Outdated
@@ -498,6 +498,9 @@ struct SharedIRGenContext | |||
Dictionary<Stmt*, IRBlock*> breakLabels; | |||
Dictionary<Stmt*, IRBlock*> continueLabels; | |||
|
|||
// A reference manager to a ctor's parent | |||
Dictionary<FunctionDeclBase*, IRType*> ctorBaseToParentStruct; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to get this with findResultTypeForConstructorDecl
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SemanticsVisitor logic is disjoint from IRGenContext and is not something which can work-without a fully visited SemanticsVisitor?
source/slang/slang-lower-to-ir.cpp
Outdated
if (parentStructTypeModifier && decl->members.getCount() == 0) | ||
as<IRStructType>(parentStructTypeModifier->getArg())->defaultCtor = as<IRFunc>(lowered.val); | ||
IRType** maybeParentStruct = this->context->shared->ctorBaseToParentStruct.tryGetValue(decl); | ||
if (maybeParentStruct && decl->members.getCount() == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(reference to previous comment - #3987 (comment))
…on-to-zero-initialize-all-variables
…ontainored-types)
1. IDefaultInitializer interface was added. If conforming, your type may be zero-initialized. To Conform a `__init()` is required 2. `[OnlyAutoInitIfForced]` was added. This attribute states that a default initializer should only be implicitly called if forced by the compiler (`zero-initialize` for example). This allows types which implicitly/explicitly conform to IDefaultInitialize to have optional auto-init behavior (which is Slang's default for user structs) to be disabled. * note about `[OnlyAutoInitIfForced]`. This is required for std-lib to not automatically resolve init-expressions for std-lib, but it has the added benifit of allowing user made structs/classes to control the default behavior of initializing
…on-to-zero-initialize-all-variables
@@ -1948,8 +1948,47 @@ namespace Slang | |||
checkVisibility(classDecl); | |||
} | |||
|
|||
static Expr* constructDefaultInitExprForVar(SemanticsVisitor* visitor, VarDeclBase* varDecl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make sense to be mayConstruct....
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the following name was suggested instead of maybe...
because if this function doesn't construct it is due to invalid types to avoid a language server crash.
We generally do not add maybe
if a language server crash is being avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what you meant here. Why is function name related to a crash. What is language server?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
I will add
maybe...
for simplicity if you think it would help the code. If someone else disagrees it will be an easy change -
To answer the other questions
What is language server?
Having no type is an error with Slang. Slangc will not lead to a position like this.
Slangc though is not the only way to use Slang, Slang has a language server. This is how the "Slang vscode extension" works.
The language server does not mirror how a regular compile works. Due to this, Slang code needs to be robust enough to avoid a crash if invalid data is given to a function. This is possible because Slang's language server works on incomplete, likely invalid input. A crash is an issue because then the code will not be able to send the 'errors' to the calling application.
Why is function name related to a crash
All code to avoid a language server failure are not addressed in function name from what I have seen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have never heard the term, "language server".
Do we have a better description or document about it somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MSVC has a good description:
JetBrains has a simpler but more lacking explanation:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you said about the language server, you were talking about a crash with slang extension for visual studio?
When you said, "Slang has a language server", i got an impression that we implemented something called a language server. But it seems like you meant to say, "slang uses a language server," is it the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ArielG-NV , can you answer to my questions on my previous comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I did not see the comment.
slangc is a compiler and a language server, the logic for the language server 'is the compiler'.
source\compiler-core\slang-language-server-protocol.cpp
- implements the protocol for use when slangc is compiled+used as a language server
DiagnosticSink::Flag::LanguageServer
- diagnose issues from the slang language server in a format useful to a language server protocol (LSP)
Because of this, Slang code needs to add additional checks to avoid failures specifically because a crash during diagnosis of incorrect code is fatal to a language server. A regular compile is different because we would still send a error for the user to interface with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting...
How do I test whether or not my change didn't break the language server?
Does slang-test have a test coverage on that?
Or do I need to install something more and run other tests?
source/slang/core.meta.slang
Outdated
[ForceInline] | ||
__init() | ||
{ | ||
this = __default<ThisType>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that cpp files checks the option like this,
this->getOptionSet().getBoolOption(CompilerOptionName::ZeroInitialize)
and change the behavior based on the command-line argument.
But this line doesn't check the option and it will always initialize the variables with zero, isn't it?
If it is the case, I am worried that Slang generated code will pay the cost of initialization even when they shouldn't be initialized.
If my understanding is incorrect, please let me know how it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stdlib structs do not set init expressions if they have an empty initExpr
.
This is due to the following code having !isFromStdLib
to ensure we don't default init (.*).meta.slang
types unless CompilerOptionName::ZeroInitialize
is enabled.
SemanticsDeclBodyVisitor::checkVarDeclCommon
else if(!varDeclBaseType || !varDeclBaseType->getDeclRef() || !isFromStdLib(varDeclBaseType->getDeclRef().getDecl()))
I don't think this affects raytracing types, but it seems it does not (falcor works)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I am following your explanation but it raised another question.
When slangc runs for the first time, it compiles stdlib and it is cached. And next time we run slangc, we don't recompile stdlib.
From this point, the run time compiler option will have no impact on how stdlib gets compiled, because the compilation doesn't happen anymore.
Does the implementation consider this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The part I don't understand is that you said,
stdlib structs do not set init expressions if they have an empty initExpr.
But it looks like you are setting initExpr here.
this = __default<ThisType>();
I think "__default()" is initExpr, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the implementation consider this case?
This has 2 parts:
- does IDefaultInitialize get added to meta.slang
stdlib will not automatically add IDefaultInitialize
if compiled without.
This cannot be worked around since a user does not have the ability to compile (.).meta.slang
- does it still work as expected
Yes. To simplify the answer I will use vec2 as an example: if we write int myVar
with the -zero-initialize flag
, the following code will compile int myVar = 0
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me change my question.
What does this line do?
this = __default<ThisType>();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int a = __default<ThisType>();
=> int a = 0;
float a = __default<ThisType>();
=> float a = 0.0f;
etc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it will initialized to zero even when -zero-initialize
is not set, is it the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it will initialized to zero even when -zero-initialize is not set, is it the case?
no, the init is not automatically called. It just exists for us to use.
Image if I make an interface thing{ __init(); }
--> this interface would not work with int
, which is incorrect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After talking on Slack for a bit, it turned out that @ArielG-NV was on an impression that we shouldn't be calling the constructors if the struct/class is defined in stdlib.
He thought that it is our design choice because he misinterpreted what Yong said on one of comments,
We probably want to exclude stdlib from this behaivor, though.
I don't believe that is what we want at all but I like to hear from @csyonghe if my understanding is correct.
@@ -51,19 +51,23 @@ namespace Slang | |||
|
|||
SubtypeWitness* SemanticsVisitor::isSubtype( | |||
Type* subType, | |||
Type* superType) | |||
Type* superType, | |||
bool subtypeInheritanceIsNotFullyResolved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to suggest to refactor the new boolean variable.
It appears that the behavior around the new boolean variable is that
- when it is true, "isSubtype()" behaves same as before.
- when it is false, "isSubtype()" behaves little differently.
If my observation is correct, you could make a new function like following without changing the existing function, "isSubtype()",
SubtypeWitness * XXX:isSubtypeWhenInheritanceIsNotFullyResolved(Type* subType, Type* superType)
{
auto result = isSubtype(subType, superType);
if (result == null_ptr)
{
do something here...
}
return result;
}
The reason why I suggest this is that "boolean" parameter is not a good thing to have in general.
It can be only true or false, which can be same as having two distinct functions instead of having a single function with a boolean parameter.
For more information, I like to suggest a very good book called, "Clean code", written by Martin C Robert.
His another book, "Agile Software Development, Principles, Patterns, and Practices", is also a fantastic book to checkout.
Likely best for future refactor, agreed. |
…hub.com/ArielG-NV/slang into option-to-zero-initialize-all-variables
I checked on the CI and realized I caused a failure, mistake was made not negating some code
tests/language-feature/zero-initialize/IDefaultExplicit-wrapper-type.slang
Outdated
Show resolved
Hide resolved
Update: jkwak-work found a flaw with how std types were treated. This is being addressed. |
Can you push the change so that I can see how it is changed? |
…hub.com/ArielG-NV/slang into option-to-zero-initialize-all-variables
pushed the changes |
previously defaultConstruct emitting was crashing due to having generics unresolved. By not resolving the default construct immediately, everything works.
With this change, if I don't use "-zero-initialize" option, the results are expected to be same as before.
Can you see why the result is changed with your code, and make sure everything works as expected? The test is not failing but SPIR-V code is different when I compared the outcome with the following command.
|
When I generated GLSL of the test, I see that "data1" is incorrectly unused with your code. |
Took a bit to check because I wanted to get a clean master first* There are differences. These of which I can explain, I will share the shaders for ease of reading: ### --> zero-init branch
#version 450
layout(row_major) uniform;
layout(row_major) buffer;
layout(std430, binding = 0) buffer StructuredBuffer_int_t_0 {
int _data[];
} outputBuffer_0;
struct DefaultStructNoInit_base_0
{
int data2_0;
};
DefaultStructNoInit_base_0 DefaultStructNoInit_base_x24init_0()
{
DefaultStructNoInit_base_0 _S1;
_S1.data2_0 = 2;
return _S1;
}
struct DefaultStructNoInit_0
{
DefaultStructNoInit_base_0 base_0;
int data3_0;
};
DefaultStructNoInit_0 DefaultStructNoInit_x24init_0()
{
DefaultStructNoInit_0 _S2;
_S2.base_0 = DefaultStructNoInit_base_x24init_0();
_S2.data3_0 = 2;
return _S2;
}
int data1_0;
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void main()
{
data1_0 = 2;
DefaultStructNoInit_0 noInit_0 = DefaultStructNoInit_x24init_0();
bool _S3;
if(noInit_0.base_0.data2_0 == 2)
{
_S3 = noInit_0.data3_0 == 2;
}
else
{
_S3 = false;
}
outputBuffer_0._data[0U] = int(_S3);
return;
}
### --> master
#version 450
layout(row_major) uniform;
layout(row_major) buffer;
layout(std430, binding = 0) buffer StructuredBuffer_int_t_0 {
int _data[];
} outputBuffer_0;
int data1_0;
struct DefaultStructNoInit_base_0
{
int data2_0;
};
DefaultStructNoInit_base_0 DefaultStructNoInit_base_x24init_0()
{
data1_0 = 2;
DefaultStructNoInit_base_0 _S1;
_S1.data2_0 = 2;
return _S1;
}
struct DefaultStructNoInit_0
{
DefaultStructNoInit_base_0 base_0;
int data3_0;
};
DefaultStructNoInit_0 DefaultStructNoInit_x24init_0()
{
DefaultStructNoInit_0 _S2;
DefaultStructNoInit_base_0 _S3 = DefaultStructNoInit_base_x24init_0();
_S2.base_0 = _S3;
_S2.data3_0 = 2;
return _S2;
}
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void main()
{
data1_0 = 2;
DefaultStructNoInit_0 noInit_0 = DefaultStructNoInit_x24init_0();
bool _S4;
if(data1_0 == 2)
{
_S4 = noInit_0.base_0.data2_0 == 2;
}
else
{
_S4 = false;
}
if(_S4)
{
_S4 = noInit_0.data3_0 == 2;
}
else
{
_S4 = false;
}
outputBuffer_0._data[0U] = int(_S4);
return;
} |
Differences;
|
why this happens: why this was added to this PR instead of it's own: |
This is correct behavior, master branch does things incorrectly currently. |
…hub.com/ArielG-NV/slang into option-to-zero-initialize-all-variables
I think your explanation makes sense. |
Adds
-zero-initialize
flag to set values to a __default() expression if they are missing an initExpr.Fixes #3516