From 41b59f026bc66a467d3e70750f3d41c0a6e9e08c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Nov 2019 15:34:08 +0100 Subject: [PATCH 1/9] promotion tweaks --- promotion.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/promotion.md b/promotion.md index 6c8be92..180989e 100644 --- a/promotion.md +++ b/promotion.md @@ -1,8 +1,8 @@ # Const promotion -"Promotion" is the act of guaranteeing that code not written in a const context -(e.g. initalizer of a `const` or `static`, or an array length expression) will -be run at compile-time. +"Promotion" is the act of guaranteeing that code *not* written in an (explicit) +const context will be run at compile-time. Explicit const contexts include the +initializer of a `const` or `static`, or an array length expression. ## Promotion contexts @@ -57,11 +57,14 @@ actually put on the stack. In this way, lifetime extension is an "implicit promotion context": the user did not ask for the value to be promoted. On the other hand, when a user passes an expression to a function with -`#[rustc_args_required_const]`, they are explicitly asking for that expression +`#[rustc_args_required_const]`, the only way for this code to compile is to promote it. +In that sense, the user is explicitly asking for that expression to be evaluated at compile-time even though they have not written it in a `const` declaration. We call this an "explicit promotion context". -Currently, non-`Copy` array initialization is treated as an implicit context. +Currently, non-`Copy` array initialization is treated as an implicit context, +because the code could compile even without promotion (namely, if the result +type is `Copy`). The distinction between these two determines whether calls to arbitrary `const fn`s (those without `#[rustc_promotable]`) are promotable (see below). See @@ -119,6 +122,10 @@ limitation with the CTFE engine. While writing `let x = {expr}` outside of a const context, the user likely expects that `x` will live on the stack and be initialized at run-time. Although this is not (to my knowledge) guaranteed by the language, we do not wish to violate the user's expectations here. +(Constant-folding still applies: the optimizer may compute `x` at compile-time +and even inline it everywhere if it can show that this does not observably alter +program behavior. Promotion is very different from constant-folding as +promotion can introduce observable differences in behavior.) ### Single assignment @@ -137,11 +144,11 @@ resources for little benefit. ### Access to a `const` or `static` -When accessing a `const` in a promotable context, the restrictions on single -assignment and named locals do not apply to the body of the `const`. All other -restrictions, notably that the result of the `const` cannot be `Drop` or mutable -through a reference still apply. For instance, while the previous example was -not legal, the following would be: +When accessing a `const` in a promotable context, the initializer of that body +is not subject to any restrictions. However, the usual restrictions on the +*result* of that computation still apply: it cannot be `Drop`. + +For instance, while the previous example was not legal, the following would be: ```rust const BOOL: i32 = { @@ -152,8 +159,9 @@ const BOOL: i32 = { let x: &'static i32 = &BOOL; ``` -An access to a `static` is only promotable within the initializer of -another `static`. +An access to a `static` is only promotable within the initializer of another +`static`. This is for the same reason that `const` initializers +[cannot access statics](const.md#reading-statics). ### Panics From 1ca2c1b8051ddf71107c735d4c130d7093141ea2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Nov 2019 15:35:56 +0100 Subject: [PATCH 2/9] link --- promotion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/promotion.md b/promotion.md index 180989e..aae820c 100644 --- a/promotion.md +++ b/promotion.md @@ -146,7 +146,7 @@ resources for little benefit. When accessing a `const` in a promotable context, the initializer of that body is not subject to any restrictions. However, the usual restrictions on the -*result* of that computation still apply: it cannot be `Drop`. +*result* of that computation still apply: it [cannot be `Drop`](#drop). For instance, while the previous example was not legal, the following would be: From 7153ee9065ee621f4a1450d86ad90e3a49e7d169 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 16 Nov 2019 15:37:46 +0100 Subject: [PATCH 3/9] expand --- promotion.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/promotion.md b/promotion.md index aae820c..1ab9f13 100644 --- a/promotion.md +++ b/promotion.md @@ -122,10 +122,13 @@ limitation with the CTFE engine. While writing `let x = {expr}` outside of a const context, the user likely expects that `x` will live on the stack and be initialized at run-time. Although this is not (to my knowledge) guaranteed by the language, we do not wish to violate the user's expectations here. -(Constant-folding still applies: the optimizer may compute `x` at compile-time -and even inline it everywhere if it can show that this does not observably alter -program behavior. Promotion is very different from constant-folding as -promotion can introduce observable differences in behavior.) + +However, constant-folding still applies: the optimizer may compute `x` at +compile-time and even inline it everywhere if it can show that this does not +observably alter program behavior. Promotion is very different from +constant-folding as promotion can introduce observable differences in behavior +(if const-evaluation fails) and as it is *guaranteed* to happen in some cases +(and thus exploited by the borrow checker). ### Single assignment From 61db5dcf84f82e1827c8305ccc92b47344e9bd5a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 25 Nov 2019 15:43:09 +0100 Subject: [PATCH 4/9] expand on my referring to existing mutable statics is fine --- const.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/const.md b/const.md index 8dcd91b..24233a3 100644 --- a/const.md +++ b/const.md @@ -62,6 +62,11 @@ note the FIXME added [by this PR](https://github.com/rust-lang/rust/pull/63955): for untyped data in a constant, we currently just *make* it immutable, instead of checking properly. +Note that a constant *referring to* some already existing mutable memory is +fine: inlining that reference everywhere has the same behavior as computing a +new reference each time. In both cases, there exists exactly one instance of +the mutable memory that everything references. + ### 3. `Sync` Finally, the same constant reference is actually shared across threads. This is From e2ec55a310f8bcd91e71b197221c926895d9e437 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 25 Nov 2019 15:43:26 +0100 Subject: [PATCH 5/9] reorder promotion clauses and adjust const-use --- promotion.md | 107 +++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/promotion.md b/promotion.md index 1ab9f13..0e133df 100644 --- a/promotion.md +++ b/promotion.md @@ -115,56 +115,10 @@ expressions are actually eligible for promotion? We refer to eligible expressions as "promotable" and describe the restrictions on such expressions below. -### Named locals - -Promotable expressions cannot refer to named locals. This is not a technical -limitation with the CTFE engine. While writing `let x = {expr}` outside of a -const context, the user likely expects that `x` will live on the stack and be -initialized at run-time. Although this is not (to my knowledge) guaranteed by -the language, we do not wish to violate the user's expectations here. - -However, constant-folding still applies: the optimizer may compute `x` at -compile-time and even inline it everywhere if it can show that this does not -observably alter program behavior. Promotion is very different from -constant-folding as promotion can introduce observable differences in behavior -(if const-evaluation fails) and as it is *guaranteed* to happen in some cases -(and thus exploited by the borrow checker). - -### Single assignment - -We only promote temporaries that are assigned to exactly once. For example, the -lifetime of the temporary whose reference is assigned to `x` below will not be -extended. - -```rust -let x: &'static i32 = &if cfg!(windows) { 0 } else { 1 }; -``` - -Once again, this is not a fundamental limitation in the CTFE engine; we are -perfectly capable of evaluating such expressions at compile time. However, -determining the promotability of complex expressions would require more -resources for little benefit. - -### Access to a `const` or `static` - -When accessing a `const` in a promotable context, the initializer of that body -is not subject to any restrictions. However, the usual restrictions on the -*result* of that computation still apply: it [cannot be `Drop`](#drop). - -For instance, while the previous example was not legal, the following would be: - -```rust -const BOOL: i32 = { - let ret = if cfg!(windows) { 0 } else { 1 }; - ret -}; - -let x: &'static i32 = &BOOL; -``` - -An access to a `static` is only promotable within the initializer of another -`static`. This is for the same reason that `const` initializers -[cannot access statics](const.md#reading-statics). +First of all, expressions have to be [allowed in constants](const.md). The +restrictions described there are needed because we want `const` to behave the +same as copying the `const` initializer everywhere the constant is used; we need +the same property when promoting expressions. But we need more. ### Panics @@ -270,6 +224,59 @@ or `const` item and refer to that. *Dynamic check.* The Miri engine could dynamically check this by ensuring that the result of computing a promoted is a value that does not need dropping. +### Access to a `const` or `static` + +When accessing a `const` in a promotable context, its value anyway gets computed +at compile-time, so we do not have to check the initializer. However, the +restrictions described above still apply for the *result* of the promoted +computation: in particular, it must be a valid `const` (i.e., it cannot +introduce interior mutability) and it must not require dropping. + +For instance, while the previous example was not legal, the following would be: + +```rust +const BOOL: i32 = { + let ret = if cfg!(windows) { 0 } else { 1 }; + ret +}; + +let x: &'static i32 = &BOOL; +``` + +An access to a `static` is only promotable within the initializer of another +`static`. This is for the same reason that `const` initializers +[cannot access statics](const.md#reading-statics). + +### Named locals + +Promotable expressions cannot refer to named locals. This is not a technical +limitation with the CTFE engine. While writing `let x = {expr}` outside of a +const context, the user likely expects that `x` will live on the stack and be +initialized at run-time. Although this is not (to my knowledge) guaranteed by +the language, we do not wish to violate the user's expectations here. + +However, constant-folding still applies: the optimizer may compute `x` at +compile-time and even inline it everywhere if it can show that this does not +observably alter program behavior. Promotion is very different from +constant-folding as promotion can introduce observable differences in behavior +(if const-evaluation fails) and as it is *guaranteed* to happen in some cases +(and thus exploited by the borrow checker). + +### Single assignment + +We only promote temporaries that are assigned to exactly once. For example, the +lifetime of the temporary whose reference is assigned to `x` below will not be +extended. + +```rust +let x: &'static i32 = &if cfg!(windows) { 0 } else { 1 }; +``` + +Once again, this is not a fundamental limitation in the CTFE engine; we are +perfectly capable of evaluating such expressions at compile time. However, +determining the promotability of complex expressions would require more +resources for little benefit. + ## Open questions * There is a fourth kind of CTFE failure -- resource exhaustion. What do we do From 417088a8641e667bed68897cb403ad141f7a4744 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 25 Nov 2019 15:52:23 +0100 Subject: [PATCH 6/9] add interior-mutability-through-const-body example --- promotion.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/promotion.md b/promotion.md index 0e133df..2648f03 100644 --- a/promotion.md +++ b/promotion.md @@ -247,6 +247,21 @@ An access to a `static` is only promotable within the initializer of another `static`. This is for the same reason that `const` initializers [cannot access statics](const.md#reading-statics). +Crucially, however, the following is *not* legal: + +```rust +const X: Cell = Cell::new(5); // ok +const XREF: &Cell = &X; // not ok +fn main() { + let x: &'static _ = &X; // not ok +} +``` + +Just like allowing `XREF` would be a problem because, by the inlining semantics, +every user of `XREF` should get their own `Cell`; it would also be a problem to +promote here because if that code getes executed multiple times (e.g. inside a +loop), it should get a new `Cell` each time. + ### Named locals Promotable expressions cannot refer to named locals. This is not a technical From 4e7e22b2afd5e17345fff1e4713200f98005a5bb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 25 Nov 2019 22:59:33 +0100 Subject: [PATCH 7/9] less contrived example --- promotion.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/promotion.md b/promotion.md index 2648f03..5a8ccdb 100644 --- a/promotion.md +++ b/promotion.md @@ -232,15 +232,17 @@ restrictions described above still apply for the *result* of the promoted computation: in particular, it must be a valid `const` (i.e., it cannot introduce interior mutability) and it must not require dropping. -For instance, while the previous example was not legal, the following would be: +For instance the following would be legal even though calls to `do_it` are not +eligible for implicit promotion: ```rust -const BOOL: i32 = { - let ret = if cfg!(windows) { 0 } else { 1 }; +const fn do_it(x: i32) -> i32 { 2*x } +const ANSWER: i32 = { + let ret = do_it(21); ret }; -let x: &'static i32 = &BOOL; +let x: &'static i32 = &ANSWER; ``` An access to a `static` is only promotable within the initializer of another From 11d1592862703d94abc1535d9e8d1711196e8134 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 Nov 2019 10:45:20 +0100 Subject: [PATCH 8/9] Fix typo Co-Authored-By: ecstatic-morse --- promotion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/promotion.md b/promotion.md index 5a8ccdb..b09e1eb 100644 --- a/promotion.md +++ b/promotion.md @@ -261,7 +261,7 @@ fn main() { Just like allowing `XREF` would be a problem because, by the inlining semantics, every user of `XREF` should get their own `Cell`; it would also be a problem to -promote here because if that code getes executed multiple times (e.g. inside a +promote here because if that code gets executed multiple times (e.g. inside a loop), it should get a new `Cell` each time. ### Named locals From 45d867e37d559895413f2efbd155756ce530d124 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 Nov 2019 19:47:49 +0100 Subject: [PATCH 9/9] incorporate feedback --- promotion.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/promotion.md b/promotion.md index b09e1eb..bbc7275 100644 --- a/promotion.md +++ b/promotion.md @@ -226,8 +226,8 @@ the result of computing a promoted is a value that does not need dropping. ### Access to a `const` or `static` -When accessing a `const` in a promotable context, its value anyway gets computed -at compile-time, so we do not have to check the initializer. However, the +When accessing a `const` in a promotable context, its value gets computed +at compile-time anyway, so we do not have to check the initializer. However, the restrictions described above still apply for the *result* of the promoted computation: in particular, it must be a valid `const` (i.e., it cannot introduce interior mutability) and it must not require dropping. @@ -272,12 +272,13 @@ const context, the user likely expects that `x` will live on the stack and be initialized at run-time. Although this is not (to my knowledge) guaranteed by the language, we do not wish to violate the user's expectations here. -However, constant-folding still applies: the optimizer may compute `x` at +Note that constant-folding still applies: the optimizer may compute `x` at compile-time and even inline it everywhere if it can show that this does not observably alter program behavior. Promotion is very different from constant-folding as promotion can introduce observable differences in behavior (if const-evaluation fails) and as it is *guaranteed* to happen in some cases -(and thus exploited by the borrow checker). +(and thus exploited by the borrow checker). This is reflected in the fact that +promotion affects lifetimes, but constant folding does not. ### Single assignment