From 1890ea04c07547d6fdd4a468a8a7963cbb4811c0 Mon Sep 17 00:00:00 2001 From: Yong He Date: Mon, 23 Sep 2024 15:19:12 -0700 Subject: [PATCH 01/12] Design proposal for initialization. --- docs/proposals/004-initialization | 177 ++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 docs/proposals/004-initialization diff --git a/docs/proposals/004-initialization b/docs/proposals/004-initialization new file mode 100644 index 0000000000..08036e2f5e --- /dev/null +++ b/docs/proposals/004-initialization @@ -0,0 +1,177 @@ +SP #004: Initialization +================= + +This proposal documents the desired behavior of initialization related language semantics, including defualt constructor, initialization list and variable initialization. + +Status +------ + +Status: Design Review + +Implemtation: N/A + +Author: Yong He + +Reviewer: + +Background +---------- + +Slang has introduced several different syntax around initialization to provide syntactic compatibility with HLSL/C++. As the language evolve, there aree many corners where +the semantics around initialization are not well-defined, and causing confusion or leading to surprising behaviors. + +This proposal attempts to provide a design on where we want to language to be in turns of how initialization is handled in all different places. + +Related Work +------------ + +C++ has many different ways and syntax to initialize an object: through explicit constructor calls, initialization list, or implicitly in a member/variable declaration. +A variable in C++ can also be in an uninitialized state after its declaration. HLSL inherits most of these behvior from C++ by allowing variables to be uninitialized. + +On the other hand, languages like C# and Swift has a set of well defined rules to ensure every variable is initialized after its declaration. + +Proposed Approach +----------------- + +The high level direction for Slang is it needs to help ensuring all decls are properly initialized at its declaration site, while still providing a reasonable level +of backward compatibility with existing Slang/HLSL code. In this section, we document all concepts and rules to achieve this goal. + +## `IDefaultInitializable` interface + +The builtin `IDefaultInitializable` interface is defined as: +```csharp +interface IDefaultInitializable +{ + __init(); +} +``` + +Any type that conforms to `IDefaultInitializable` is treated as having the *default-initializable* property in the rest of the discussions. By default, all builtin +types, such as `int`, `float[]`, `float4` are default-initializable. The values for builtin numeric types after default-initialization are 0. +The default value for pointers is `nullptr`, and default value for `Optional` is `none`. + +## Default-Initializable Type + +A type X is default initializable if: +- It explicitly declares that `X` implements `IDefaultInitializable`. +- It explicitly provides a default constructor `X::__init()` that takes no arguments, in which case we treat the type as implementing `IDefaultInitializable` even if + this conformance isn't explicitly declared. +- It is a struct type where all its members are default-initializable. A member is considered default-initializable if the type of the member is default-initializable, + or if the member has an initialization expression that defines its default value. +- It is a sized-array type where the element type is default-initializable. +- It is a tuple type where all element types are default-initializable. +- It is an `Optional` type for any `T`. + +This means that an unsized-array type or an existential type, or any composite type that contains such types without providing an explicit default constructor +are not considered default-initializable. + + +## Variable Initialization + +If the type of a local variable is default-initializable, then its default initializer will be invoked at its declaration site implicitly to intialize its value: +```c++ +int x; // x will be default initialized to 0 because `int` is default-initializable. +// The above is equivalent to: +int x = int(); + +struct S { int x; int y; } +S s; // s will be default initialized to {0, 0} because `S` is default-initializable. +``` + +If a type is not default-initializable, and the declaration site does not provide an intial value for the variable, the compiler should generate an error: +```csharp +struct V { int[] arr; } + +V v; // error: `v` must be initialized at declaration. +``` + +For backward compatibility, we will introduce a compiler option to turn this error into a warning, but we may deprecate this option in the future. + +## Generic Type Parameter + +A generic type parameter is not considered default-initializable by-default. As a result, the following code should produce error: +```csharp +void foo() +{ + T t; // error, `t` is uninitialized. +} +``` + +## Automatic Synthesis of Default-Initializer + +If a `struct` type is determined to be default-initializable but a default constructor isn't explicitly provided by the user, the Slang compiler should +synthesize such a constructor for the type. The synthesis logic should be recursively invoke defualt initialization on all members. + +## Automatic Synthesis of `IDefaultInitializable` Conformance + +If a `struct` type provides an explicit default constructor, the compiler should automatically add `IDefaultIinitializable` to the conformance list of +the type, so it can be used for any generic parameters constrained on `IDefaultInitializble`. + +## Initialization List + +Slang allows initialization of a variable by assigning it with an initialization list. +Generally, Slang will always try to resolve initialization list coercion as if it is an explicit constructor invocation. +For example, given: +``` +S obj = {1,2}; +``` +Slang will try to convert the code into: +``` +S obj = S(1,2); +``` + +As a special case, an empty initializer list will translate into a default-initialization: +```csharp +S obj = {}; +// equivalent to: +S obj = S(); +``` + +If the above code passes type check, then it will be used as the way to initialize `obj`. + +If the above code does not pass type check, Slang continues to check if `S` meets the standard of a "legacy C-style struct` type. +A type is a "legacy C-Style struct" iff: +- It is a struct type. +- It is a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. +- It does not define any explicit constructors +- It does not define any initialization expressions on its members. +- All its members are legacy C-Style structs or arrays of legacy C-style structs. +In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: +``` +struct Inner { int x; int y; }; +struct Outer { Inner i; Inner j; } + +Outer o = {1, 2, 3, 4}; // Initializes `o` into `{ Inner{1,2}, Inner{3,4} }`. +``` + +### Synthesis of constructors for member initialization + +If a type already defines any explicit constructors, do not synthesize any constructors for initializer list call. An intializer list expression +for the type must exactly match one of the explicitly defined constructors. + +If the type doesn't provide any explicit constructors, the compiler need to synthesis the constructors for the calls that that the intializer +lists translate into, so that an initializer list expression can be used to initialize a variable of the type. + +For each visibilty level `V` in (`private`, `internal`, `public`), we will synthesize one constructor at that visiblity level. + +The signature for the synthesized initializer for type `T` is: +```csharp +V T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) +``` +where `V` is the visibilty level, and `(member0, member1, ... memberN)` is the set of members at or above visiblity level `V`, and `default(member0)` +is the value defined by the initialization expression in `member0` if it exist, or the default value of `member0`'s type. +If `member0`'s type is not default initializable and the the member doesn't provide an initial value, then the parameter will not have a default value. + +The body of the constructor will initialize each member with the value comming from the corresponding constructor argument if such argument exists, +otherwise the member will be initialized to its default value either defined by the init expr of the member, or the default value of the type if the +type is default-initializable. If the member type is not default-initializable and a default value isn't provided on the member, then such the constructor +synthesis will fail and the constructor will not be added to the type. Failure to synthesis a constructor is not an error, and an error will appear +if the user is trying to initialize a value of the type in question assuming such a constructor exist. + + +Alternatives Considered +----------------------- + +One important decision point is whether or not Slang should allow variables to be left in uninitialized state after its declaration as it is allowed in C++. +Our opinion is that this is not what we want to have in the long term and Slang should take the opportunity as a new language to not inherit from this +undesired C++ legacy behavior. \ No newline at end of file From 47ecaeb82e6f60102caad53c072af9f6524a749a Mon Sep 17 00:00:00 2001 From: Yong He Date: Tue, 24 Sep 2024 13:10:46 -0700 Subject: [PATCH 02/12] extension --- docs/proposals/{004-initialization => 004-initialization.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/proposals/{004-initialization => 004-initialization.md} (100%) diff --git a/docs/proposals/004-initialization b/docs/proposals/004-initialization.md similarity index 100% rename from docs/proposals/004-initialization rename to docs/proposals/004-initialization.md From 2061ca91b0fd7516b72783d194a15fd75690c394 Mon Sep 17 00:00:00 2001 From: Yong He Date: Tue, 24 Sep 2024 14:28:11 -0700 Subject: [PATCH 03/12] QA --- docs/proposals/004-initialization.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index 08036e2f5e..ee18ed77ef 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -1,7 +1,7 @@ SP #004: Initialization ================= -This proposal documents the desired behavior of initialization related language semantics, including defualt constructor, initialization list and variable initialization. +This proposal documents the desired behavior of initialization related language semantics, including default constructor, initialization list and variable initialization. Status ------ @@ -17,10 +17,10 @@ Reviewer: Background ---------- -Slang has introduced several different syntax around initialization to provide syntactic compatibility with HLSL/C++. As the language evolve, there aree many corners where +Slang has introduced several different syntax around initialization to provide syntactic compatibility with HLSL/C++. As the language evolve, there are many corners where the semantics around initialization are not well-defined, and causing confusion or leading to surprising behaviors. -This proposal attempts to provide a design on where we want to language to be in turns of how initialization is handled in all different places. +This proposal attempts to provide a design on where we want the language to be in terms of how initialization is handled in all different places. Related Work ------------ @@ -100,7 +100,7 @@ void foo() ## Automatic Synthesis of Default-Initializer If a `struct` type is determined to be default-initializable but a default constructor isn't explicitly provided by the user, the Slang compiler should -synthesize such a constructor for the type. The synthesis logic should be recursively invoke defualt initialization on all members. +synthesize such a constructor for the type. The synthesis logic should be recursively invoke default initializer on all members. ## Automatic Synthesis of `IDefaultInitializable` Conformance @@ -169,6 +169,20 @@ synthesis will fail and the constructor will not be added to the type. Failure t if the user is trying to initialize a value of the type in question assuming such a constructor exist. +Q&A +----------- + +## Should global static and groupshared variables be defualt initialized? + +It is difficult to efficiently initialized global variables safely and correctly in a general way on platforms such as Vulkan. +To avoid the performance issues, the current decision is to not to default initialized these global variables. + +## Should `out` parameters be default initialized? + +The source of an `out` parameter is either comming from a local variable that is already default-initialized, or from a +global variable where we can't default-initialize efficiently. For this reason, we should leave `out` parameter to not +be default initialized implicitly by the compiler. + Alternatives Considered ----------------------- From 3a1c156ac7c1e7342ad59c9ff2c8fe8763c41d5c Mon Sep 17 00:00:00 2001 From: Yong He Date: Tue, 24 Sep 2024 18:28:27 -0700 Subject: [PATCH 04/12] wording --- docs/proposals/004-initialization.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index ee18ed77ef..a61b36ee9b 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -33,10 +33,9 @@ On the other hand, languages like C# and Swift has a set of well defined rules t Proposed Approach ----------------- -The high level direction for Slang is it needs to help ensuring all decls are properly initialized at its declaration site, while still providing a reasonable level -of backward compatibility with existing Slang/HLSL code. In this section, we document all concepts and rules to achieve this goal. +The high level direction for Slang is it needs to help ensuring variables are properly initialized at its declaration site, while still providing a reasonable level of backward compatibility with existing Slang/HLSL code and avoid initialization behaviors that may have non-trivial performance impact. In this section, we document all concepts and rules to achieve this goal. -## `IDefaultInitializable` interface +### `IDefaultInitializable` interface The builtin `IDefaultInitializable` interface is defined as: ```csharp @@ -50,7 +49,7 @@ Any type that conforms to `IDefaultInitializable` is treated as having the *defa types, such as `int`, `float[]`, `float4` are default-initializable. The values for builtin numeric types after default-initialization are 0. The default value for pointers is `nullptr`, and default value for `Optional` is `none`. -## Default-Initializable Type +### Default-Initializable Type A type X is default initializable if: - It explicitly declares that `X` implements `IDefaultInitializable`. @@ -62,11 +61,11 @@ A type X is default initializable if: - It is a tuple type where all element types are default-initializable. - It is an `Optional` type for any `T`. -This means that an unsized-array type or an existential type, or any composite type that contains such types without providing an explicit default constructor -are not considered default-initializable. +This means that an unsized-array type or an existential type, or any composite type that contains such types without providing an explicit default constructor are not considered default-initializable. +Note that `void` type should be treated as default initializable, since `void` type can have only one value that is `()` (empty tuple). -## Variable Initialization +### Variable Initialization If the type of a local variable is default-initializable, then its default initializer will be invoked at its declaration site implicitly to intialize its value: ```c++ @@ -87,7 +86,7 @@ V v; // error: `v` must be initialized at declaration. For backward compatibility, we will introduce a compiler option to turn this error into a warning, but we may deprecate this option in the future. -## Generic Type Parameter +### Generic Type Parameter A generic type parameter is not considered default-initializable by-default. As a result, the following code should produce error: ```csharp @@ -97,17 +96,17 @@ void foo() } ``` -## Automatic Synthesis of Default-Initializer +### Automatic Synthesis of Default-Initializer If a `struct` type is determined to be default-initializable but a default constructor isn't explicitly provided by the user, the Slang compiler should synthesize such a constructor for the type. The synthesis logic should be recursively invoke default initializer on all members. -## Automatic Synthesis of `IDefaultInitializable` Conformance +### Automatic Synthesis of `IDefaultInitializable` Conformance If a `struct` type provides an explicit default constructor, the compiler should automatically add `IDefaultIinitializable` to the conformance list of the type, so it can be used for any generic parameters constrained on `IDefaultInitializble`. -## Initialization List +### Initialization List Slang allows initialization of a variable by assigning it with an initialization list. Generally, Slang will always try to resolve initialization list coercion as if it is an explicit constructor invocation. @@ -137,7 +136,8 @@ A type is a "legacy C-Style struct" iff: - It does not define any initialization expressions on its members. - All its members are legacy C-Style structs or arrays of legacy C-style structs. In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: -``` + +```csharp struct Inner { int x; int y; }; struct Outer { Inner i; Inner j; } @@ -172,12 +172,12 @@ if the user is trying to initialize a value of the type in question assuming suc Q&A ----------- -## Should global static and groupshared variables be defualt initialized? +### Should global static and groupshared variables be defualt initialized? It is difficult to efficiently initialized global variables safely and correctly in a general way on platforms such as Vulkan. To avoid the performance issues, the current decision is to not to default initialized these global variables. -## Should `out` parameters be default initialized? +### Should `out` parameters be default initialized? The source of an `out` parameter is either comming from a local variable that is already default-initialized, or from a global variable where we can't default-initialize efficiently. For this reason, we should leave `out` parameter to not From 6fa44952ca6748849d3f9581c258cba5c65c7094 Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 26 Sep 2024 11:41:19 -0700 Subject: [PATCH 05/12] Update design. --- docs/proposals/004-initialization.md | 159 +++++++++++++++++++-------- 1 file changed, 111 insertions(+), 48 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index a61b36ee9b..9c34da3f8a 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -12,7 +12,7 @@ Implemtation: N/A Author: Yong He -Reviewer: +Reviewer: Theresa Foley, Kai Zhang Background ---------- @@ -29,11 +29,13 @@ C++ has many different ways and syntax to initialize an object: through explicit A variable in C++ can also be in an uninitialized state after its declaration. HLSL inherits most of these behvior from C++ by allowing variables to be uninitialized. On the other hand, languages like C# and Swift has a set of well defined rules to ensure every variable is initialized after its declaration. +C++ allows using the initilization list syntax to initialize an object. The semantics of initialization lists depends on whether or not explicit constructors +are defined on the type. Proposed Approach ----------------- -The high level direction for Slang is it needs to help ensuring variables are properly initialized at its declaration site, while still providing a reasonable level of backward compatibility with existing Slang/HLSL code and avoid initialization behaviors that may have non-trivial performance impact. In this section, we document all concepts and rules to achieve this goal. +In this section, we document all concepts and rules related to initialization, constructors and initialization lists. ### `IDefaultInitializable` interface @@ -45,23 +47,17 @@ interface IDefaultInitializable } ``` -Any type that conforms to `IDefaultInitializable` is treated as having the *default-initializable* property in the rest of the discussions. By default, all builtin -types, such as `int`, `float[]`, `float4` are default-initializable. The values for builtin numeric types after default-initialization are 0. -The default value for pointers is `nullptr`, and default value for `Optional` is `none`. - +Any type that conforms to `IDefaultInitializable` is treated as having the *default-initializable* property in the rest of the discussions. ### Default-Initializable Type A type X is default initializable if: - It explicitly declares that `X` implements `IDefaultInitializable`. - It explicitly provides a default constructor `X::__init()` that takes no arguments, in which case we treat the type as implementing `IDefaultInitializable` even if this conformance isn't explicitly declared. -- It is a struct type where all its members are default-initializable. A member is considered default-initializable if the type of the member is default-initializable, - or if the member has an initialization expression that defines its default value. - It is a sized-array type where the element type is default-initializable. -- It is a tuple type where all element types are default-initializable. -- It is an `Optional` type for any `T`. This means that an unsized-array type or an existential type, or any composite type that contains such types without providing an explicit default constructor are not considered default-initializable. +Builtin types like `int`, `float`, `Ptr`, `Optional` and non-emtpy `Tuple` etc. are *not* default-initializable. Note that `void` type should be treated as default initializable, since `void` type can have only one value that is `()` (empty tuple). @@ -69,43 +65,64 @@ Note that `void` type should be treated as default initializable, since `void` t If the type of a local variable is default-initializable, then its default initializer will be invoked at its declaration site implicitly to intialize its value: ```c++ -int x; // x will be default initialized to 0 because `int` is default-initializable. +struct T : IDefaultInitializable { __init() { ... }} +T x; // x will be default initialized by the default constructor because `T` is default-initializable. // The above is equivalent to: -int x = int(); - -struct S { int x; int y; } -S s; // s will be default initialized to {0, 0} because `S` is default-initializable. +T x = T(); ``` -If a type is not default-initializable, and the declaration site does not provide an intial value for the variable, the compiler should generate an error: +If a type is not default-initializable, it will be left in an uninitialized state after its declaration: ```csharp -struct V { int[] arr; } - -V v; // error: `v` must be initialized at declaration. +struct S { int x; int y; } +S s; // s will not be default initialized, because `S` is not default-initializable. ``` - +Trying to use a variable without initializing it first is an error. For backward compatibility, we will introduce a compiler option to turn this error into a warning, but we may deprecate this option in the future. ### Generic Type Parameter -A generic type parameter is not considered default-initializable by-default. As a result, the following code should produce error: +A generic type parameter is not considered default-initializable by-default. As a result, the following code should leave `t` in an uninitialized state: ```csharp void foo() { - T t; // error, `t` is uninitialized. + T t; // `t` is uninitialized at declaration. } ``` -### Automatic Synthesis of Default-Initializer - -If a `struct` type is determined to be default-initializable but a default constructor isn't explicitly provided by the user, the Slang compiler should -synthesize such a constructor for the type. The synthesis logic should be recursively invoke default initializer on all members. - ### Automatic Synthesis of `IDefaultInitializable` Conformance If a `struct` type provides an explicit default constructor, the compiler should automatically add `IDefaultIinitializable` to the conformance list of the type, so it can be used for any generic parameters constrained on `IDefaultInitializble`. + +### Synthesis of constructors for member initialization + +If a type already defines any explicit constructors, do not synthesize any constructors for initializer list call. An intializer list expression +for the type must exactly match one of the explicitly defined constructors. + +If the type doesn't provide any explicit constructors, the compiler need to synthesize the constructors for the calls that that the intializer +lists translate into, so that an initializer list expression can be used to initialize a variable of the type. + +For each visibilty level `V` in (`private`, `internal`, `public`), we will synthesize one constructor at that visiblity level. + +The signature for the synthesized initializer for type `T` is: +```csharp +V T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) +``` +where `V` is the visibilty level, and `(member0, member1, ... memberN)` is the set of members at or above visiblity level `V`, and `default(member0)` +is the value defined by the initialization expression in `member0` if it exist, or the default value of `member0`'s type. +If `member0`'s type is not default initializable and the the member doesn't provide an initial value, then the parameter will not have a default value. + +The body of the constructor will initialize each member with the value comming from the corresponding constructor argument if such argument exists, +otherwise the member will be initialized to its default value either defined by the init expr of the member, or the default value of the type if the +type is default-initializable. If the member type is not default-initializable and a default value isn't provided on the member, then such the constructor +synthesis will fail and the constructor will not be added to the type. Failure to synthesis a constructor is not an error, and an error will appear +if the user is trying to initialize a value of the type in question assuming such a constructor exist. + +Note that if every member of a struct contains a default expression, the synthesized `__init` method can be called with 0 arguments. This should make +the type conform to `IDefaultInitializable` and behave as a default-initializable type. + + ### Initialization List Slang allows initialization of a variable by assigning it with an initialization list. @@ -119,7 +136,7 @@ Slang will try to convert the code into: S obj = S(1,2); ``` -As a special case, an empty initializer list will translate into a default-initialization: +Following the same logic, an empty initializer list will translate into a default-initialization: ```csharp S obj = {}; // equivalent to: @@ -132,7 +149,7 @@ If the above code does not pass type check, Slang continues to check if `S` meet A type is a "legacy C-Style struct" iff: - It is a struct type. - It is a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. -- It does not define any explicit constructors +- It does not contain any explicit constructors defined by the user. - It does not define any initialization expressions on its members. - All its members are legacy C-Style structs or arrays of legacy C-style structs. In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: @@ -141,32 +158,78 @@ In such case, we perform a legacy "read data" style consumption of the initializ struct Inner { int x; int y; }; struct Outer { Inner i; Inner j; } -Outer o = {1, 2, 3, 4}; // Initializes `o` into `{ Inner{1,2}, Inner{3,4} }`. +Outer o = {1, 2, 3}; // Initializes `o` into `{ Inner{1,2}, Inner{3,0} }`. ``` -### Synthesis of constructors for member initialization +If the type is not a legacy C-Style struct, Slang should produce an error. -If a type already defines any explicit constructors, do not synthesize any constructors for initializer list call. An intializer list expression -for the type must exactly match one of the explicitly defined constructors. +Examples +------------------- +```csharp +struct Empty {} +void test() +{ + Empty s0 = {}; // Works, `s` is considered initialized. + Empty s1; // `s1` is considered initialized bcause `Empty` is `IDefaultInitializable`. +} -If the type doesn't provide any explicit constructors, the compiler need to synthesis the constructors for the calls that that the intializer -lists translate into, so that an initializer list expression can be used to initialize a variable of the type. +struct CLike {int x; int y; } +void test1() +{ + CLike c0; // `c0` is uninitialized. + CLike c1 = {}; // initialized with legacy initializaer list logic, `c1` is now `{0,0}`. + CLike c2 = {1}; // initialized with legacy initializaer list logic, `c1` is now `{1,0}`. + CLike c3 = {1, 2}; // initilaized with ctor call `CLike(1,2)`, `c3` is now `{1,2}`. +} -For each visibilty level `V` in (`private`, `internal`, `public`), we will synthesize one constructor at that visiblity level. +struct ExplicitCtor { + int x; + int y; + __init(int x) {...} +} +void test2() +{ + ExplicitCtor e0; // `e0` is uninitialized. + ExplicitCtor e1 = {1}; // calls `__init`. + ExplicitCtor e2 = {1, 2}; // error, no ctor matches initializer list. +} -The signature for the synthesized initializer for type `T` is: -```csharp -V T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) -``` -where `V` is the visibilty level, and `(member0, member1, ... memberN)` is the set of members at or above visiblity level `V`, and `default(member0)` -is the value defined by the initialization expression in `member0` if it exist, or the default value of `member0`'s type. -If `member0`'s type is not default initializable and the the member doesn't provide an initial value, then the parameter will not have a default value. +struct DefaultMember { + int x = 0; + int y = 1; +} +void test3() +{ + DefaultMember m; // `m` is initialized to `{0, 1}`. + DefaultMember m1 = {1}; // calls `__init(1)`, initialized to `{1,1}`. + DefaultMember m2 = {1,2}; // calls `__init(1,2)`, initialized to `{1,2}`. +} -The body of the constructor will initialize each member with the value comming from the corresponding constructor argument if such argument exists, -otherwise the member will be initialized to its default value either defined by the init expr of the member, or the default value of the type if the -type is default-initializable. If the member type is not default-initializable and a default value isn't provided on the member, then such the constructor -synthesis will fail and the constructor will not be added to the type. Failure to synthesis a constructor is not an error, and an error will appear -if the user is trying to initialize a value of the type in question assuming such a constructor exist. +struct PartialInit { + // warning: not all members are initialized. + // members should either be all-uninitialized or all-initialized with + // default expr. + int x; + int y = 1; +} +void test4() +{ + PartialInit i; // `i` is not initialized. + PartialInit i1 = {2}; // calls `__init`, result is `{2,1}`. + PartialInit i2 = {2, 3}; // calls `__init`, result is {2, 3} +} + +struct PartialInit2 { + int x = 1; + int y; // warning: not all members are initialized. +} +void test5() +{ + PartialInit2 j; // `j` is not initialized. + PartialInit2 j1 = {2}; // error, no ctor match. + PartialInit2 j2 = {2, 3}; // calls `__init`, result is {2, 3} +} +``` Q&A From 48704f518d53ae6c06eecbf33a3120c1dab6f4c3 Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 26 Sep 2024 16:13:27 -0700 Subject: [PATCH 06/12] more revisions. --- docs/proposals/004-initialization.md | 167 ++++++++++++++++++--------- 1 file changed, 115 insertions(+), 52 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index 9c34da3f8a..1a22c5d626 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -37,45 +37,44 @@ Proposed Approach In this section, we document all concepts and rules related to initialization, constructors and initialization lists. -### `IDefaultInitializable` interface +### Default Initializable type -The builtin `IDefaultInitializable` interface is defined as: +A type is considered "default-initializable" if it provides a constructor that can take 0 arguments, so that it can be constructed with `T()`. + +### Variable Initialization + +Generally, a variable is considered uninitialized at its declaration site without an explicit value expression. +For example, ```csharp -interface IDefaultInitializable +void foo() { - __init(); + MyType t; // t is considered uninitialized. + var t1 : MyType; // same in modern syntax, t1 is uninitialized. } ``` -Any type that conforms to `IDefaultInitializable` is treated as having the *default-initializable* property in the rest of the discussions. -### Default-Initializable Type - -A type X is default initializable if: -- It explicitly declares that `X` implements `IDefaultInitializable`. -- It explicitly provides a default constructor `X::__init()` that takes no arguments, in which case we treat the type as implementing `IDefaultInitializable` even if - this conformance isn't explicitly declared. -- It is a sized-array type where the element type is default-initializable. - -This means that an unsized-array type or an existential type, or any composite type that contains such types without providing an explicit default constructor are not considered default-initializable. -Builtin types like `int`, `float`, `Ptr`, `Optional` and non-emtpy `Tuple` etc. are *not* default-initializable. - -Note that `void` type should be treated as default initializable, since `void` type can have only one value that is `()` (empty tuple). - -### Variable Initialization - -If the type of a local variable is default-initializable, then its default initializer will be invoked at its declaration site implicitly to intialize its value: -```c++ -struct T : IDefaultInitializable { __init() { ... }} -T x; // x will be default initialized by the default constructor because `T` is default-initializable. -// The above is equivalent to: -T x = T(); +However, the Slang language has been allowing implicit initialization of variables whose types are default initializable types. +For example, +```csharp +struct MyType1 { + int x; + __init() { x = 0; } +} +void foo() { + MyType t1; // `t1` is initialized with a call to `__init`. +} ``` -If a type is not default-initializable, it will be left in an uninitialized state after its declaration: +We would like to move away from this legacy behavior towards a consistent semantics of never implicitly initializing a variable. +To maintain backward compatibility, we will keep the legacy behavior, but remove the implicit initialization when the variable is defined +in modern syntax: ```csharp -struct S { int x; int y; } -S s; // s will not be default initialized, because `S` is not default-initializable. +void foo() { + var t1: MyType; // `t1` will no longer be initialized. +} ``` +We will also remove the default initilaization semantics for traditional syntax in modern Slang modules that comes with an explicit `module` declaration. + Trying to use a variable without initializing it first is an error. For backward compatibility, we will introduce a compiler option to turn this error into a warning, but we may deprecate this option in the future. @@ -89,12 +88,6 @@ void foo() } ``` -### Automatic Synthesis of `IDefaultInitializable` Conformance - -If a `struct` type provides an explicit default constructor, the compiler should automatically add `IDefaultIinitializable` to the conformance list of -the type, so it can be used for any generic parameters constrained on `IDefaultInitializble`. - - ### Synthesis of constructors for member initialization If a type already defines any explicit constructors, do not synthesize any constructors for initializer list call. An intializer list expression @@ -103,13 +96,13 @@ for the type must exactly match one of the explicitly defined constructors. If the type doesn't provide any explicit constructors, the compiler need to synthesize the constructors for the calls that that the intializer lists translate into, so that an initializer list expression can be used to initialize a variable of the type. -For each visibilty level `V` in (`private`, `internal`, `public`), we will synthesize one constructor at that visiblity level. +For each type, we will synthesize one `public` constructor: The signature for the synthesized initializer for type `T` is: ```csharp -V T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) +public T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) ``` -where `V` is the visibilty level, and `(member0, member1, ... memberN)` is the set of members at or above visiblity level `V`, and `default(member0)` +where `(member0, member1, ... memberN)` is the set of members of `public` visibility, and `default(member0)` is the value defined by the initialization expression in `member0` if it exist, or the default value of `member0`'s type. If `member0`'s type is not default initializable and the the member doesn't provide an initial value, then the parameter will not have a default value. @@ -119,20 +112,29 @@ type is default-initializable. If the member type is not default-initializable a synthesis will fail and the constructor will not be added to the type. Failure to synthesis a constructor is not an error, and an error will appear if the user is trying to initialize a value of the type in question assuming such a constructor exist. -Note that if every member of a struct contains a default expression, the synthesized `__init` method can be called with 0 arguments. This should make -the type conform to `IDefaultInitializable` and behave as a default-initializable type. +Note that if every member of a struct contains a default expression, the synthesized `__init` method can be called with 0 arguments. + +### Single argument constructor call + +Call to a constructor with a single argument is always treated as a syntactic sugar of type cast: +```csharp +int x = int(1.0f); // is treated as (int) 1.0f; +MyType y = MyType(arg); // is treated as (MyType)arg; +MyType x = MyType(y); // equivalent to `x = y`. +``` +The compiler will attempt to resolve all type casts using type coercion rules, if that failed, will fall back to resolve it as a constructor call. ### Initialization List Slang allows initialization of a variable by assigning it with an initialization list. Generally, Slang will always try to resolve initialization list coercion as if it is an explicit constructor invocation. For example, given: -``` +```csharp S obj = {1,2}; ``` Slang will try to convert the code into: -``` +```csharp S obj = S(1,2); ``` @@ -143,14 +145,27 @@ S obj = {}; S obj = S(); ``` +Note that initializer list of a single argument still translates directly into a constructor call and not a type cast. For example: +```csharp +void test() +{ + MyType t = {1}; + // translates to: + // MyType t = MyType.__init(1); + // which is not + // MyType t = MyType(t) + // or + // MyType t = (MyType)t; +} +``` + If the above code passes type check, then it will be used as the way to initialize `obj`. If the above code does not pass type check, Slang continues to check if `S` meets the standard of a "legacy C-style struct` type. -A type is a "legacy C-Style struct" iff: -- It is a struct type. -- It is a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. +A type is a "legacy C-Style struct" if all of the following conditions are met: +- It is a user-defined struct type or a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. - It does not contain any explicit constructors defined by the user. -- It does not define any initialization expressions on its members. +- All its members have higher or equal visibility than the type. - All its members are legacy C-Style structs or arrays of legacy C-style structs. In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: @@ -163,9 +178,13 @@ Outer o = {1, 2, 3}; // Initializes `o` into `{ Inner{1,2}, Inner{3,0} }`. If the type is not a legacy C-Style struct, Slang should produce an error. + Examples ------------------- ```csharp + +// Assume everything below is public unless explicitly declared. + struct Empty {} void test() { @@ -229,22 +248,66 @@ void test5() PartialInit2 j1 = {2}; // error, no ctor match. PartialInit2 j2 = {2, 3}; // calls `__init`, result is {2, 3} } + +public struct Visibility1 +{ + internal int x; + public int y = 0; +} +void test6() +{ + Visibility1 t = {0, 0}; // error, no matching ctor + Visibility1 t1 = {}; // error, no matching ctor +} + +public struct Visibility2 +{ + internal int x = 1; + public int y = 0; +} +void test7() +{ + Visibility2 t = {0, 0}; // error, no matching ctor + Visibility2 t1 = {}; // OK, initialized to {1,0} +} + +internal struct Visibility3 +{ + internal int x; + internal int y = 2; +} +internal void test7() +{ + Visibility3 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. + Visibility3 t1 = {1}; // OK, initialized to {1,2} via legacy logic. +} + +internal struct Visibility4 +{ + internal int x = 1; + internal int y = 2; +} +internal void test7() +{ + Visibility4 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. + Visibility4 t1 = {1}; // OK, initialized to {1,2} via legacy logic. + Visibility4 t2 = {}; // OK, initialized to {1,2} via ctor match. +} ``` Q&A ----------- -### Should global static and groupshared variables be defualt initialized? +### Should global static and groupshared variables be default initialized? -It is difficult to efficiently initialized global variables safely and correctly in a general way on platforms such as Vulkan. -To avoid the performance issues, the current decision is to not to default initialized these global variables. +Similar to local variables, all declarations are not default initialized at its declaration site. +In particular, it is difficult to efficiently initialized global variables safely and correctly in a general way on platforms such as Vulkan, +so implicit initialization for these variables can come with serious performance consequences. ### Should `out` parameters be default initialized? -The source of an `out` parameter is either comming from a local variable that is already default-initialized, or from a -global variable where we can't default-initialize efficiently. For this reason, we should leave `out` parameter to not -be default initialized implicitly by the compiler. +Following the same philosphy of not initializing any declarations, `out` parameters are also not default-initialized. Alternatives Considered ----------------------- From 6207aa29fd2846ab3089dc480799de942363c9f8 Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 26 Sep 2024 23:29:01 -0700 Subject: [PATCH 07/12] revise text. --- docs/proposals/004-initialization.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index 1a22c5d626..bb27d9cd52 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -112,7 +112,7 @@ type is default-initializable. If the member type is not default-initializable a synthesis will fail and the constructor will not be added to the type. Failure to synthesis a constructor is not an error, and an error will appear if the user is trying to initialize a value of the type in question assuming such a constructor exist. -Note that if every member of a struct contains a default expression, the synthesized `__init` method can be called with 0 arguments. +Note that if every member of a struct contains a default expression, the synthesized `__init` method can be called with 0 arguments, however, this will not cause a variable declaration to be implicitly initialized. Implicit initialization is a backward compatibility feature that only work for user-defined `__init()` methods. ### Single argument constructor call @@ -189,7 +189,7 @@ struct Empty {} void test() { Empty s0 = {}; // Works, `s` is considered initialized. - Empty s1; // `s1` is considered initialized bcause `Empty` is `IDefaultInitializable`. + Empty s1; // `s1` is considered uninitialized. } struct CLike {int x; int y; } @@ -219,9 +219,10 @@ struct DefaultMember { } void test3() { - DefaultMember m; // `m` is initialized to `{0, 1}`. - DefaultMember m1 = {1}; // calls `__init(1)`, initialized to `{1,1}`. - DefaultMember m2 = {1,2}; // calls `__init(1,2)`, initialized to `{1,2}`. + DefaultMember m; // `m` is uninitialized. + DefaultMember m1 = {}; // `m1` is initialized to `{0,1}`. + DefaultMember m2 = {1}; // calls `__init(1)`, initialized to `{1,1}`. + DefaultMember m3 = {1,2}; // calls `__init(1,2)`, initialized to `{1,2}`. } struct PartialInit { @@ -276,7 +277,7 @@ internal struct Visibility3 internal int x; internal int y = 2; } -internal void test7() +internal void test8() { Visibility3 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. Visibility3 t1 = {1}; // OK, initialized to {1,2} via legacy logic. @@ -287,7 +288,7 @@ internal struct Visibility4 internal int x = 1; internal int y = 2; } -internal void test7() +internal void test9() { Visibility4 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. Visibility4 t1 = {1}; // OK, initialized to {1,2} via legacy logic. From ecb8066109d5129ff481f3ab870cfddd151d48e0 Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 27 Sep 2024 09:40:48 -0700 Subject: [PATCH 08/12] rewording to be more accurate. --- docs/proposals/004-initialization.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index bb27d9cd52..f2dc53f522 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -46,9 +46,11 @@ A type is considered "default-initializable" if it provides a constructor that c Generally, a variable is considered uninitialized at its declaration site without an explicit value expression. For example, ```csharp +struct MyType { int x ; } + void foo() { - MyType t; // t is considered uninitialized. + MyType t; // t is uninitialized. var t1 : MyType; // same in modern syntax, t1 is uninitialized. } ``` @@ -165,7 +167,7 @@ If the above code does not pass type check, Slang continues to check if `S` meet A type is a "legacy C-Style struct" if all of the following conditions are met: - It is a user-defined struct type or a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. - It does not contain any explicit constructors defined by the user. -- All its members have higher or equal visibility than the type. +- All its members have the same visibility as the type itself. - All its members are legacy C-Style structs or arrays of legacy C-style structs. In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: From e1f43f82d8833495ff2b6cd3293991a600c549ff Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 27 Sep 2024 09:51:21 -0700 Subject: [PATCH 09/12] Fix wording and add explanation to examples. --- docs/proposals/004-initialization.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index f2dc53f522..0e3073554d 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -147,16 +147,16 @@ S obj = {}; S obj = S(); ``` -Note that initializer list of a single argument still translates directly into a constructor call and not a type cast. For example: +Note that initializer list of a single argument does not translate into a type cast, unlike the constructor call syntax. Initializing with a single element in the intializer list always translates directly into a constructor call. For example: ```csharp void test() { MyType t = {1}; - // translates to: + // translates to direct constructor call: // MyType t = MyType.__init(1); - // which is not + // which is NOT the same as: // MyType t = MyType(t) - // or + // or: // MyType t = (MyType)t; } ``` @@ -169,6 +169,7 @@ A type is a "legacy C-Style struct" if all of the following conditions are met: - It does not contain any explicit constructors defined by the user. - All its members have the same visibility as the type itself. - All its members are legacy C-Style structs or arrays of legacy C-style structs. +Note that C-Style structs are allowed to have member default values. In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: ```csharp @@ -190,7 +191,7 @@ Examples struct Empty {} void test() { - Empty s0 = {}; // Works, `s` is considered initialized. + Empty s0 = {}; // Works, `s` is considered initialized via ctor call. Empty s1; // `s1` is considered uninitialized. } @@ -222,7 +223,7 @@ struct DefaultMember { void test3() { DefaultMember m; // `m` is uninitialized. - DefaultMember m1 = {}; // `m1` is initialized to `{0,1}`. + DefaultMember m1 = {}; // calls `__init()`, initialized to `{0,1}`. DefaultMember m2 = {1}; // calls `__init(1)`, initialized to `{1,1}`. DefaultMember m3 = {1,2}; // calls `__init(1,2)`, initialized to `{1,2}`. } @@ -265,6 +266,10 @@ void test6() public struct Visibility2 { + // Visibility2 type contains members of different visibility, + // which disqualifies it from being considered as C-style struct. + // Therefore we will not attempt the legacy fallback logic for + // initializer-list syntax. internal int x = 1; public int y = 0; } @@ -276,6 +281,11 @@ void test7() internal struct Visibility3 { + // Visibility3 type is considered as C-style struct. + // Because all members have the same visibility as the type. + // Therefore we will attempt the legacy fallback logic for + // initializer-list syntax. + // Note that c-style structs can still have init exprs on members. internal int x; internal int y = 2; } @@ -287,6 +297,10 @@ internal void test8() internal struct Visibility4 { + // Visibility4 type is considered as C-style struct. + // And we still synthesize a ctor for member initialization. + // Because Visiblity4 has no public members, the synthesized + // ctor will take 0 arguments. internal int x = 1; internal int y = 2; } From 4783b0059f283a125c52adbe4d0edfeccf0e4343 Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 27 Sep 2024 10:09:22 -0700 Subject: [PATCH 10/12] clarify on zero initialization. --- docs/proposals/004-initialization.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index 0e3073554d..c3c30c14e3 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -312,6 +312,18 @@ internal void test9() } ``` +### Zero Initialization + +The Slang compiler supported an option to force zero-initialization of all local variables. +This is currently implemented by adding `IDefaultInitializable` conformance to all user +defined types. With the direction we are heading, we should remove this option in the future. +For now we can continue to provide this functionality but through an IR rewrite pass instead +of changing the frontend semantics. + +When users specifies `-zero-initialize`, we should still use the same front-end logic for +all the checking. After lowering to IR, we should insert a `store` after all `IRVar : T` to +initialize them to `defaultConstruct(T)`. + Q&A ----------- From 4a1d9183b94bfbced40c108523dd89888732d4d4 Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 27 Sep 2024 13:04:21 -0700 Subject: [PATCH 11/12] refine the rules and examples. --- docs/proposals/004-initialization.md | 108 +++++++++++++++++++++------ 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index c3c30c14e3..ee7d60ab42 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -98,16 +98,18 @@ for the type must exactly match one of the explicitly defined constructors. If the type doesn't provide any explicit constructors, the compiler need to synthesize the constructors for the calls that that the intializer lists translate into, so that an initializer list expression can be used to initialize a variable of the type. -For each type, we will synthesize one `public` constructor: +For each type, we will synthesize one constructor at the same visibility of the type itself: -The signature for the synthesized initializer for type `T` is: +The signature for the synthesized initializer for type `V struct T` is: ```csharp -public T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) +V T.__init(member0: typeof(member0) = default(member0), member1 : typeof(member1) = default(member1), ...) ``` -where `(member0, member1, ... memberN)` is the set of members of `public` visibility, and `default(member0)` +where `V` is a visibility modifier, `(member0, member1, ... memberN)` is the set of members that has visibility `V`, and `default(member0)` is the value defined by the initialization expression in `member0` if it exist, or the default value of `member0`'s type. If `member0`'s type is not default initializable and the the member doesn't provide an initial value, then the parameter will not have a default value. +The synthesized constructor will be marked as `[Synthesized]` by the compiler, so the call site can inject additional compatibility logic when calling a synthesized constructor. + The body of the constructor will initialize each member with the value comming from the corresponding constructor argument if such argument exists, otherwise the member will be initialized to its default value either defined by the init expr of the member, or the default value of the type if the type is default-initializable. If the member type is not default-initializable and a default value isn't provided on the member, then such the constructor @@ -163,24 +165,43 @@ void test() If the above code passes type check, then it will be used as the way to initialize `obj`. -If the above code does not pass type check, Slang continues to check if `S` meets the standard of a "legacy C-style struct` type. +If the above code does not pass type check, and if there is only one constructor for`MyType` that is synthesized as described in the previous section (and therefore marked as `[Synthesized]`, Slang continues to check if `S` meets the standard of a "legacy C-style struct` type. A type is a "legacy C-Style struct" if all of the following conditions are met: - It is a user-defined struct type or a basic scalar, vector or matrix type, e.g. `int`, `float4x4`. - It does not contain any explicit constructors defined by the user. - All its members have the same visibility as the type itself. - All its members are legacy C-Style structs or arrays of legacy C-style structs. Note that C-Style structs are allowed to have member default values. -In such case, we perform a legacy "read data" style consumption of the initializer list, so that the following behavior is valid: +In such case, we perform a legacy "read data" style consumption of the initializer list to synthesize the arguments to call the constructor, so that the following behavior is valid: ```csharp struct Inner { int x; int y; }; struct Outer { Inner i; Inner j; } -Outer o = {1, 2, 3}; // Initializes `o` into `{ Inner{1,2}, Inner{3,0} }`. +// Initializes `o` into `{ Inner{1,2}, Inner{3,0} }`, by synthesizing the +// arguments to call `Outer.__init(Inner(1,2), Inner(3, 0))`. +Outer o = {1, 2, 3}; ``` If the type is not a legacy C-Style struct, Slang should produce an error. +### Legacy HLSL syntax to cast from 0 + +HLSL allows a legacy syntax to cast from literal `0` to a struct type, for example: +```hlsl +MyStruct s { int x; } +void test() +{ + MyStruct s = (MyStruct)0; +} +``` + +Slang treats this as equivalent to a empty-initialization: +```csharp +MyStruct s = (MyStruct)0; +// is equivalent to +MyStruct s = {}; +``` Examples ------------------- @@ -188,26 +209,46 @@ Examples // Assume everything below is public unless explicitly declared. -struct Empty {} +struct Empty +{ + // compiler synthesizes: + // __init(); +} void test() { Empty s0 = {}; // Works, `s` is considered initialized via ctor call. Empty s1; // `s1` is considered uninitialized. } -struct CLike {int x; int y; } +struct CLike +{ + int x; int y; + // compiler synthesizes: + // __init(int x, int y); +} void test1() { CLike c0; // `c0` is uninitialized. - CLike c1 = {}; // initialized with legacy initializaer list logic, `c1` is now `{0,0}`. - CLike c2 = {1}; // initialized with legacy initializaer list logic, `c1` is now `{1,0}`. - CLike c3 = {1, 2}; // initilaized with ctor call `CLike(1,2)`, `c3` is now `{1,2}`. + + // case 1: initialized with synthesized ctor call using legacy logic to form arguments, + // and `c1` is now `{0,0}`. + // (we will refer to this scenario as "initialized with legacy logic" for + // the rest of the examples): + CLike c1 = {}; + + // case 2: initialized with legacy initializaer list logic, `c1` is now `{1,0}`: + CLike c2 = {1}; + + // case 3: initilaized with ctor call `CLike(1,2)`, `c3` is now `{1,2}`: + CLike c3 = {1, 2}; } -struct ExplicitCtor { +struct ExplicitCtor +{ int x; int y; __init(int x) {...} + // compiler does not synthesize any ctors. } void test2() { @@ -219,6 +260,8 @@ void test2() struct DefaultMember { int x = 0; int y = 1; + // compiler synthesizes: + // __init(int x = 0, int y = 1); } void test3() { @@ -234,6 +277,8 @@ struct PartialInit { // default expr. int x; int y = 1; + // compiler synthesizes: + // __init(int x, int y = 1); } void test4() { @@ -245,6 +290,8 @@ void test4() struct PartialInit2 { int x = 1; int y; // warning: not all members are initialized. + // compiler synthesizes: + // __init(int x, int y); } void test5() { @@ -257,11 +304,18 @@ public struct Visibility1 { internal int x; public int y = 0; + // the compiler does not synthesize any ctor. + // the compiler will try to synthesize: + // public __init(int y); + // but then it will find that `x` cannot be initialized. + // so this synthesis will fail and no ctor will be added + // to the type. } void test6() { Visibility1 t = {0, 0}; // error, no matching ctor Visibility1 t1 = {}; // error, no matching ctor + Visibility1 t2 = {1}; // error, no matching ctor } public struct Visibility2 @@ -272,11 +326,14 @@ public struct Visibility2 // initializer-list syntax. internal int x = 1; public int y = 0; + // compiler synthesizes: + // public __init(int y = 0); } void test7() { - Visibility2 t = {0, 0}; // error, no matching ctor - Visibility2 t1 = {}; // OK, initialized to {1,0} + Visibility2 t = {0, 0}; // error, no matching ctor. + Visibility2 t1 = {}; // OK, initialized to {1,0} via ctor call. + Visibility2 t2 = {1}; // OK, initialized to {1,1} via ctor call. } internal struct Visibility3 @@ -288,11 +345,14 @@ internal struct Visibility3 // Note that c-style structs can still have init exprs on members. internal int x; internal int y = 2; + // compiler synthesizes: + // internal __init(int x, int y = 2); } internal void test8() { - Visibility3 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. - Visibility3 t1 = {1}; // OK, initialized to {1,2} via legacy logic. + Visibility3 t = {0, 0}; // OK, initialized to {0,0} via ctor call. + Visibility3 t1 = {1}; // OK, initialized to {1,2} via ctor call. + Visibility3 t2 = {}; // OK, initialized to {0, 2} via legacy logic. } internal struct Visibility4 @@ -303,12 +363,14 @@ internal struct Visibility4 // ctor will take 0 arguments. internal int x = 1; internal int y = 2; + // compiler synthesizes: + // internal __init(int x = 1, int y = 2); } internal void test9() { - Visibility4 t = {0, 0}; // OK, initialized to {0,0} via legacy C-Style initialization. - Visibility4 t1 = {1}; // OK, initialized to {1,2} via legacy logic. - Visibility4 t2 = {}; // OK, initialized to {1,2} via ctor match. + Visibility4 t = {0, 0}; // OK, initialized to {0,0} via ctor call. + Visibility4 t1 = {3}; // OK, initialized to {3,2} via ctor call. + Visibility4 t2 = {}; // OK, initialized to {1,2} via ctor call. } ``` @@ -341,6 +403,6 @@ Following the same philosphy of not initializing any declarations, `out` paramet Alternatives Considered ----------------------- -One important decision point is whether or not Slang should allow variables to be left in uninitialized state after its declaration as it is allowed in C++. -Our opinion is that this is not what we want to have in the long term and Slang should take the opportunity as a new language to not inherit from this -undesired C++ legacy behavior. \ No newline at end of file +One important decision point is whether or not Slang should allow variables to be left in uninitialized state after its declaration as it is allowed in C++. In contrast, C# forces everything to be default initialized at its declaration site, which come at the cost of incurring the burden to developers to come up with a way to define the default value for each type. +Our opinion is we want to allow things as uninitialized, and to have the compiler validation checks to inform +the developer something is wrong if they try to use a variable in uninitialized state. We believe it is desirable to tell the developer what's wrong instead of using a heavyweight mechanism to ensure everything is initialized at declaration sites, which can have non-trivial performance consequences for GPU programs, especially when the variable is declared in groupshared memory. \ No newline at end of file From 63238b4dd5d75251d7de70abaa1b582c6b6286b8 Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 27 Sep 2024 13:05:09 -0700 Subject: [PATCH 12/12] update status. --- docs/proposals/004-initialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals/004-initialization.md b/docs/proposals/004-initialization.md index ee7d60ab42..4605cd0309 100644 --- a/docs/proposals/004-initialization.md +++ b/docs/proposals/004-initialization.md @@ -6,7 +6,7 @@ This proposal documents the desired behavior of initialization related language Status ------ -Status: Design Review +Status: Design Approved, implementation in-progress. Implemtation: N/A