From fd9eddf06d38dfcbec53f9d576b36cdfc1b4c2ae Mon Sep 17 00:00:00 2001 From: xunilrj Date: Sun, 27 Oct 2024 13:19:37 +0000 Subject: [PATCH 1/5] RFC for const generics --- README.md | 1 + rfcs/0014-const-generics.md | 136 ++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 rfcs/0014-const-generics.md diff --git a/README.md b/README.md index 6bb6c72..69b13af 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,4 @@ | [0011](rfcs/0011-references.md) | References | | [0012](rfcs/0012-expressive-diagnostics.md) | Expressive Diagnostics | | [0013](rfcs/0013-changes-lifecycle.md) | Changes Lifecycle | +| [0014](rfcs/0014-const-generics.md) | Const Generics | diff --git a/rfcs/0014-const-generics.md b/rfcs/0014-const-generics.md new file mode 100644 index 0000000..894f62c --- /dev/null +++ b/rfcs/0014-const-generics.md @@ -0,0 +1,136 @@ +- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [FuelLabs/sway-rfcs#0000](https://github.com/FuelLabs/sway-rfcs/pull/001) +- Sway Issue: [FueLabs/sway#0000](https://github.com/FuelLabs/sway/issues/001) + +# Summary + +[summary]: #summary + +Allows constant values as generic arguments. + +# Motivation + +[motivation]: #motivation + +Some types have constants, specifically unsigned integers, as their definition (e.g. arrays and string arrays). Without const generics it is impossible to have `impl` items for all instances of these types. + +# Guide-level explanation + +[guide-level-explanation]: #guide-level-explanation + +`const generics` refer to the language syntax where generic parameters are defined as constant values, instead of types. + +A simple example would be: + +```sway +fn id(array: [u64; SIZE]) -> [u64; SIZE] { + array +} +``` + +This also allows `impl` items such as + +```sway +impl AbiEncode for str[N] { + ... +} +``` + +This constant can be infered or explicitly specified. When infered, the syntax is no different than just using the item: + +```sway + id([1u8]) +``` + +In the example above, the Sway compiler will infer `SIZE` to be one, because `id` parameter will be infered to be `[u8; 1]`. + +For the cases where the compiler cannot infer this value, or this value comes from a expression, it is possible to do: + +```sway + id::<1>([1u8]); + id::<{1 + 1}>([1u8, 2u8]); +``` + +When the value is not a literal, but an expression, it needs to enclosed by curly braces. This will fail, if the expression cannot be evaluated as `const`. + +# Reference-level explanation + +[reference-level-explanation]: #reference-level-explanation + +This new syntax has three forms: const generics declaration, call and reference. + +const generics declarations can appear anywhere a generic argument declaration is valid: + +1. Function/method declaration; +2. Struct/Enum declaration; +3. `impl` declarations. + +const generics call can appears everywhere a declaration that contains a generic argument call be referenced: + +1. Function/method reference; +1. Struct/Enum reference; +1. Fully qualified call paths. + +const generics references can appear anywhere a reference to a const variable can appear. For semantic purposes there is no difference from the reference of a const generics and a "normal" const. + +Different from type generics, const generics cannot appear at: + +1. constraints; +1. where types are expected. + +By the nature of `impl` declarations, it is possible to specialize for some specific types. For example: + +```sway +impl SomeStruct { + fn f() { + } +} +``` + +## Const Value Specialization + +In the example above, `f` only is available when the generic argument of SomeStruct is know to be bool. By the nature of const generics, Sway will not support specialization by value. This mean that the example below will not be supported: + +```sway +impl SomeIntegerStruct<1> { + ... +} + +impl SomeBoolStruct { + ... +} +``` + +The main reason for forbidding this option is that apart from `bool`, which only needs two values, all other constants would demand complex syntax to guarantee the completeness and uniqueness of all implementations, the same way that `match` expressions do. + +## Monomorphization + +As other generic arguments, `const generics` monormophize functions, which means that a new "TyFunctionDecl", for example, will be created for which value that is instantiated. + +# Drawbacks + +[drawbacks]: #drawbacks + +No reasons + +# Rationale and alternatives + +[rationale-and-alternatives]: #rationale-and-alternatives + +# Prior art + +[prior-art]: #prior-art + +https://doc.rust-lang.org/reference/items/generics.html#const-generics + +# Unresolved questions + +[unresolved-questions]: #unresolved-questions + + +# Future possibilities + +[future-possibilities]: #future-possibilities + +Sway does not have a distinction between `const` and `non-const` functions. Which means that we will need to deny function calls in `const generics expressions`, or we will need to evaluate them and fail when they are not `const`. This can impact compilation time. From 2da5f99c7ce12561875e19d2dd07ad48a9122479 Mon Sep 17 00:00:00 2001 From: xunilrj Date: Thu, 5 Dec 2024 21:00:02 +0000 Subject: [PATCH 2/5] implementation roadmap --- rfcs/0014-const-generics.md | 122 +++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 23 deletions(-) diff --git a/rfcs/0014-const-generics.md b/rfcs/0014-const-generics.md index 894f62c..691e992 100644 --- a/rfcs/0014-const-generics.md +++ b/rfcs/0014-const-generics.md @@ -23,7 +23,7 @@ Some types have constants, specifically unsigned integers, as their definition ( A simple example would be: -```sway +```rust fn id(array: [u64; SIZE]) -> [u64; SIZE] { array } @@ -31,7 +31,7 @@ fn id(array: [u64; SIZE]) -> [u64; SIZE] { This also allows `impl` items such as -```sway +```rust impl AbiEncode for str[N] { ... } @@ -39,60 +39,105 @@ impl AbiEncode for str[N] { This constant can be infered or explicitly specified. When infered, the syntax is no different than just using the item: -```sway - id([1u8]) +```rust +id([1u8]) ``` In the example above, the Sway compiler will infer `SIZE` to be one, because `id` parameter will be infered to be `[u8; 1]`. For the cases where the compiler cannot infer this value, or this value comes from a expression, it is possible to do: -```sway - id::<1>([1u8]); - id::<{1 + 1}>([1u8, 2u8]); +```rust +id::<1>([1u8]); +id::<{1 + 1}>([1u8, 2u8]); ``` -When the value is not a literal, but an expression, it needs to enclosed by curly braces. This will fail, if the expression cannot be evaluated as `const`. +When the value is not a literal, but an expression, it is named "const generic expression" and it needs to be enclosed by curly braces. This will fail, if the expression cannot be evaluated as `const`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This new syntax has three forms: const generics declaration, call and reference. +This new syntax has three forms: declarations, instantiations and references. -const generics declarations can appear anywhere a generic argument declaration is valid: +"const generics declarations" can appear anywhere all others generic arguments declarations are valid: 1. Function/method declaration; -2. Struct/Enum declaration; -3. `impl` declarations. +1. Struct/Enum declaration; +1. `impl` declarations. -const generics call can appears everywhere a declaration that contains a generic argument call be referenced: +```rust +// 1 +fn id(...) { ... } + +// 2 +struct SpecialArray { + inner: [u8; N], +} + +//3 +impl AbiEncode for str[N] { + ... +} +``` + +"const generics instantiations" can appear anywhere all others generic argument instantiations are valid: 1. Function/method reference; 1. Struct/Enum reference; 1. Fully qualified call paths. -const generics references can appear anywhere a reference to a const variable can appear. For semantic purposes there is no difference from the reference of a const generics and a "normal" const. +```rust +// 1 +id::<1>([1u8]); +special_array.do_something::<1>(); + +// 2 +SpecialArray::<1>::new(); + +// 3 + as SpecialArrayTrait::<1>>::f(); +``` + +"const generics references" can appear anywhere any other identifier appear. For semantic purposes there is no difference from the reference of a "const generics" and a "normal" const. + +```rust +fn f() { + __log(I); +} +``` Different from type generics, const generics cannot appear at: 1. constraints; 1. where types are expected. +```rust +// 1 - INVALID +fn f() where I > 10 { + ... +} + +// 2 - INVALID +fn f() -> Vec { + Vec::::new() +} +``` + +## Const Value Specialization + By the nature of `impl` declarations, it is possible to specialize for some specific types. For example: -```sway +```rust impl SomeStruct { fn f() { } } ``` -## Const Value Specialization - -In the example above, `f` only is available when the generic argument of SomeStruct is know to be bool. By the nature of const generics, Sway will not support specialization by value. This mean that the example below will not be supported: +In the example above, `f` only is available when the generic argument of SomeStruct is know to be `bool`. This will not be supported for "const generics", which means that the example below will not be supported: -```sway +```rust impl SomeIntegerStruct<1> { ... } @@ -102,17 +147,47 @@ impl SomeBoolStruct { } ``` -The main reason for forbidding this option is that apart from `bool`, which only needs two values, all other constants would demand complex syntax to guarantee the completeness and uniqueness of all implementations, the same way that `match` expressions do. +The main reason for forbidding this is that apart from `bool`, which only needs two values, all other constants would demand complex syntax to guarantee the completeness and uniqueness of all implementations, the same way that `match` expressions do. ## Monomorphization -As other generic arguments, `const generics` monormophize functions, which means that a new "TyFunctionDecl", for example, will be created for which value that is instantiated. +As other generic arguments, `const generics` monormorphize functions, which means that a new "TyFunctionDecl", for example, will be created for which value that is instantiated. + +Prevention of code bloat will be responsability of the optimizer. + +# Implementation Roadmap + +1. Creation of the feature flag `const-generics`; +1. Implementation of "const generics references"; +```rust +fn f() { __log(I); } +``` +3. The compiler will be able to encode arrays of any size; That means being able to implement the following in the "core" lib and using arrays of any size as configurables; +```rust +impl AbiEncode for [T; N] { ... } +``` +4. Being able to `encode` arrays of any size; +```rust +fn f(s: [T; N]) -> raw_slice { + <[T; N] as AbiEncode>::abi_encode(...); +} +f::<1>([1]) +``` +5. Inference of the example above +```rust +f([1]); +core::encode([1]); +``` +6. Struct/enum support for const generics +7. Function/method declaration; +8. `impl` declarations. +9. Function/method reference; # Drawbacks [drawbacks]: #drawbacks -No reasons +None # Rationale and alternatives @@ -122,12 +197,13 @@ No reasons [prior-art]: #prior-art -https://doc.rust-lang.org/reference/items/generics.html#const-generics +This RFC is partially based on Rust's own const generic system: https://doc.rust-lang.org/reference/items/generics.html#const-generics # Unresolved questions [unresolved-questions]: #unresolved-questions +None # Future possibilities From 481ffd163180c2737a40081788e1a4d1c8fdaa57 Mon Sep 17 00:00:00 2001 From: xunilrj Date: Mon, 20 Jan 2025 17:01:42 -0300 Subject: [PATCH 3/5] more details about monomorphization, method call selection and type system --- rfcs/0014-const-generics.md | 117 +++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 14 deletions(-) diff --git a/rfcs/0014-const-generics.md b/rfcs/0014-const-generics.md index 691e992..7cf6fda 100644 --- a/rfcs/0014-const-generics.md +++ b/rfcs/0014-const-generics.md @@ -13,7 +13,7 @@ Allows constant values as generic arguments. [motivation]: #motivation -Some types have constants, specifically unsigned integers, as their definition (e.g. arrays and string arrays). Without const generics it is impossible to have `impl` items for all instances of these types. +Some types have constants, specifically unsigned integers, as their definition (e.g. arrays and string arrays). Without `const generics` it is impossible to have `impl` items for all instances of these types. # Guide-level explanation @@ -37,7 +37,7 @@ impl AbiEncode for str[N] { } ``` -This constant can be infered or explicitly specified. When infered, the syntax is no different than just using the item: +This constant can be inferred or explicitly specified. When inferred, the syntax is no different than just using the item: ```rust id([1u8]) @@ -45,14 +45,14 @@ id([1u8]) In the example above, the Sway compiler will infer `SIZE` to be one, because `id` parameter will be infered to be `[u8; 1]`. -For the cases where the compiler cannot infer this value, or this value comes from a expression, it is possible to do: +For the cases where the compiler cannot infer this value, or this value comes from an expression, it is possible to do: ```rust id::<1>([1u8]); id::<{1 + 1}>([1u8, 2u8]); ``` -When the value is not a literal, but an expression, it is named "const generic expression" and it needs to be enclosed by curly braces. This will fail, if the expression cannot be evaluated as `const`. +When the value is not a literal, but an expression, it is named "const generic expression" and it needs to be enclosed by curly braces. This will fail if the expression cannot be evaluated as `const`. # Reference-level explanation @@ -60,7 +60,7 @@ When the value is not a literal, but an expression, it is named "const generic e This new syntax has three forms: declarations, instantiations and references. -"const generics declarations" can appear anywhere all others generic arguments declarations are valid: +"const generics declarations" can appear anywhere all other generic arguments declarations are valid: 1. Function/method declaration; 1. Struct/Enum declaration; @@ -81,7 +81,7 @@ impl AbiEncode for str[N] { } ``` -"const generics instantiations" can appear anywhere all others generic argument instantiations are valid: +"const generics instantiations" can appear anywhere all other generic argument instantiations are valid: 1. Function/method reference; 1. Struct/Enum reference; @@ -99,7 +99,7 @@ SpecialArray::<1>::new(); as SpecialArrayTrait::<1>>::f(); ``` -"const generics references" can appear anywhere any other identifier appear. For semantic purposes there is no difference from the reference of a "const generics" and a "normal" const. +"const generics references" can appear anywhere any other identifier appears. For semantic purposes, there is no difference between the reference of a "const generics" and a "normal" const. ```rust fn f() { @@ -131,11 +131,11 @@ By the nature of `impl` declarations, it is possible to specialize for some spec ```rust impl SomeStruct { fn f() { - } + } } ``` -In the example above, `f` only is available when the generic argument of SomeStruct is know to be `bool`. This will not be supported for "const generics", which means that the example below will not be supported: +In the example above, `f` only is available when the generic argument of `SomeStruct` is known to be `bool`. This will not be supported for "const generics", which means that the example below will not be supported: ```rust impl SomeIntegerStruct<1> { @@ -151,9 +151,98 @@ The main reason for forbidding this is that apart from `bool`, which only needs ## Monomorphization -As other generic arguments, `const generics` monormorphize functions, which means that a new "TyFunctionDecl", for example, will be created for which value that is instantiated. +As with other generic arguments, `const generics` monormorphize functions, which means that a new "TyFunctionDecl", for example, will be created for which value that is instantiated. -Prevention of code bloat will be responsability of the optimizer. +Prevention of code bloat will be the responsability of the optimizer. + +Monomorphization for const generics has one extra complexity to support arbitrary expressions it is needed to "solve" an equation. For example, +if a variable is typed as `[u64; 1]` and a method with its `self` type as `[u64; N + 1]` is called, the monomorphization process needs to know that `N` needs to be valued as `0` and if the variable is `[u64; 2]`, `N` will be `1`. + +## Type Engine changes + +By the nature of `const generics`, it will be possible to write expressions inside types. Initially, only simple references will be +supported, but at some point, more complex expressions will be needed, for example: + +```sway +fn len(a: [T; N]) { ... } + +fn bigger(a: [u64; N]) -> [u64; N + 1] { + [0; N + 1] +} +``` + +This poses the challenge of having an expression tree inside the type system. Currently `sway` already +has three expression trees: `Expr`, `ExpressionKind` and `TyExpressionVariant`. This demands a new one, +given that the first two allow much more complex expressions than what `const generics` wants to support. + +The last one is only create after some `TypeInfo` already exist, and thus cannot be used. + +At the same time, the parser should be able to parse any expression and return a friendly error that such expression, +is not supported. So the parser will parser the `const generic` expression as `ExpressionKind`, and will lower it +to the type system expression enum. And will return a `TypeInfo::ErrorRecovery` instead of the expression. + +These expressions will also increase the complexity of all types related algorithms such as: + +1. Unification +2. PartialEq +3. Hash + +In the simplest case, it is very clear how to unify `TypeInfo::Array(..., Length::Literal(1))` and `TypeInfo::Array(..., Length::Expression("N"))`. +But more complex cases such as `TypeInfo::Array(..., Length::Expression("N"))` and `TypeInfo::Array(..., Length::Expression("N + 1"))`, is not clear +if these types are unifiable, equal or simply different. + +## Method call search algorithm + +When a method is called, the algorithm that searches which method is called uses the method `TraitMap::get_impls`. +Currently, this method does an `O(1)` search to find all methods applicable to a type. For example: + +```sway +impl [u64; 1] { + fn len_for_size_one(&self) { ... } +} +``` + +would create a map with something like + +``` +Placeholder -> [...] +... +[u64; 1] -> [..., len_for_size_one,...] +... +``` + +The algorithms first create a `TypeRootFilter`, which is very similar to `TypeInfo`. And uses this `enum` to search the hash table. +After that, it "generalizes" the filter and searches for `TypeRootFilter::Placeholder`. + +To fully support `const generics` and `const value specialization`, the compiler will now keep generalizing the +searched type until it hits `Placeholder`. For example, searching for `[u64; 1]` will actually search for: + +1. [u64; 1]; +1. [u64; Placeholder]; +1. Placeholder; + +The initial implementation will do this generalization only for `const generics`, but it also makes sense to +generalize this with other types such as `Vec`. + +1. Vec; +1. Vec; +1. Placeholder; + +This gets more complex as the number of `generics` and `const generics` increases. For example: + +```sway +struct VecWithSmallVecOptimization { ... } +``` + +Searching for this type would search: + +1. VecWithSmallVecOptimization +1. VecWithSmallVecOptimization +1. VecWithSmallVecOptimization +1. VecWithSmallVecOptimization +1. Placeholder + +More research is needed to understand if this change can potentially change the semantics of any program written in `sway`. # Implementation Roadmap @@ -162,14 +251,14 @@ Prevention of code bloat will be responsability of the optimizer. ```rust fn f() { __log(I); } ``` -3. The compiler will be able to encode arrays of any size; That means being able to implement the following in the "core" lib and using arrays of any size as configurables; +3. The compiler will be able to encode arrays of any size; Which means being able to implement the following in the "core" lib and using arrays of any size as "configurables"; ```rust impl AbiEncode for [T; N] { ... } ``` 4. Being able to `encode` arrays of any size; ```rust fn f(s: [T; N]) -> raw_slice { - <[T; N] as AbiEncode>::abi_encode(...); + <[T; N] as AbiEncode>::abi_encode(...); } f::<1>([1]) ``` @@ -203,7 +292,7 @@ This RFC is partially based on Rust's own const generic system: https://doc.rust [unresolved-questions]: #unresolved-questions -None +1. What is the impact of changing the "method called algorithm"? # Future possibilities From 20b1248989dd710cb93bc1e5b7fd90595ced538d Mon Sep 17 00:00:00 2001 From: xunilrj Date: Tue, 21 Jan 2025 08:38:43 -0300 Subject: [PATCH 4/5] more adjustments --- rfcs/0014-const-generics.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/rfcs/0014-const-generics.md b/rfcs/0014-const-generics.md index 7cf6fda..8770f6d 100644 --- a/rfcs/0014-const-generics.md +++ b/rfcs/0014-const-generics.md @@ -173,13 +173,13 @@ fn bigger(a: [u64; N]) -> [u64; N + 1] { This poses the challenge of having an expression tree inside the type system. Currently `sway` already has three expression trees: `Expr`, `ExpressionKind` and `TyExpressionVariant`. This demands a new one, -given that the first two allow much more complex expressions than what `const generics` wants to support. - -The last one is only create after some `TypeInfo` already exist, and thus cannot be used. +given that the first two allow much more complex expressions than what `const generics` wants to support; +and last one is only create after some `TypeInfo` already exist, and thus cannot be used. At the same time, the parser should be able to parse any expression and return a friendly error that such expression, -is not supported. So the parser will parser the `const generic` expression as `ExpressionKind`, and will lower it -to the type system expression enum. And will return a `TypeInfo::ErrorRecovery` instead of the expression. +is not supported. + +So, in case of a unsupported expression the parser will parser the `const generic` expression as it does for normal `Expr`and will lower it to the type system expression enum, but in the place of the unsupported expression will return a `TypeInfo::ErrorRecovery`. These expressions will also increase the complexity of all types related algorithms such as: @@ -224,8 +224,8 @@ searched type until it hits `Placeholder`. For example, searching for `[u64; 1]` The initial implementation will do this generalization only for `const generics`, but it also makes sense to generalize this with other types such as `Vec`. -1. Vec; -1. Vec; +1. Vec\; +1. Vec\; 1. Placeholder; This gets more complex as the number of `generics` and `const generics` increases. For example: @@ -236,10 +236,10 @@ struct VecWithSmallVecOptimization { ... } Searching for this type would search: -1. VecWithSmallVecOptimization -1. VecWithSmallVecOptimization -1. VecWithSmallVecOptimization -1. VecWithSmallVecOptimization +1. VecWithSmallVecOptimization\ +1. VecWithSmallVecOptimization\ +1. VecWithSmallVecOptimization\ +1. VecWithSmallVecOptimization\ 1. Placeholder More research is needed to understand if this change can potentially change the semantics of any program written in `sway`. @@ -286,7 +286,7 @@ None [prior-art]: #prior-art -This RFC is partially based on Rust's own const generic system: https://doc.rust-lang.org/reference/items/generics.html#const-generics +This RFC is partially based on Rust's own const generic system: https://doc.rust-lang.org/reference/items/generics.html#const-generics, https://blog.rust-lang.org/inside-rust/2021/09/06/Splitting-const-generics.html, https://rust-lang.github.io/rfcs/2000-const-generics.html, https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html # Unresolved questions @@ -297,5 +297,3 @@ This RFC is partially based on Rust's own const generic system: https://doc.rust # Future possibilities [future-possibilities]: #future-possibilities - -Sway does not have a distinction between `const` and `non-const` functions. Which means that we will need to deny function calls in `const generics expressions`, or we will need to evaluate them and fail when they are not `const`. This can impact compilation time. From de7209d03fdc9169c5058c586d6ab4b93aa39702 Mon Sep 17 00:00:00 2001 From: xunilrj Date: Tue, 21 Jan 2025 08:40:45 -0300 Subject: [PATCH 5/5] RFC metadata --- README.md | 3 ++- rfcs/{0014-const-generics.md => 0015-const-generics.md} | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename rfcs/{0014-const-generics.md => 0015-const-generics.md} (97%) diff --git a/README.md b/README.md index 69b13af..0b9db9c 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,5 @@ | [0011](rfcs/0011-references.md) | References | | [0012](rfcs/0012-expressive-diagnostics.md) | Expressive Diagnostics | | [0013](rfcs/0013-changes-lifecycle.md) | Changes Lifecycle | -| [0014](rfcs/0014-const-generics.md) | Const Generics | +| [0014](rfcs/0014-abi-errors.md) | Abi Errors | +| [0014](rfcs/0015-const-generics.md) | Const Generics | diff --git a/rfcs/0014-const-generics.md b/rfcs/0015-const-generics.md similarity index 97% rename from rfcs/0014-const-generics.md rename to rfcs/0015-const-generics.md index 8770f6d..7cde0f2 100644 --- a/rfcs/0014-const-generics.md +++ b/rfcs/0015-const-generics.md @@ -1,6 +1,6 @@ -- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- RFC PR: [FuelLabs/sway-rfcs#0000](https://github.com/FuelLabs/sway-rfcs/pull/001) +- Feature Name: const_generics +- Start Date: 2024-10-27 +- RFC PR: [FuelLabs/sway-rfcs#42](https://github.com/FuelLabs/sway-rfcs/pull/42) - Sway Issue: [FueLabs/sway#0000](https://github.com/FuelLabs/sway/issues/001) # Summary