From 7231ee7cf985da3c5ba52ddc078116304351638c Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 11 Oct 2019 12:38:36 -0700 Subject: [PATCH 1/9] Remove numbers from requirements --- promotion.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/promotion.md b/promotion.md index 5966bb8..40238e4 100644 --- a/promotion.md +++ b/promotion.md @@ -20,7 +20,7 @@ That's why we have to be very conservative with what can and cannot be promoted. ## Rules -### 1. Panics +### Panics Promotion is not allowed to throw away side effects. This includes panicking. Let us look at what happens when we promote `&(0_usize - 1)` in a debug build: @@ -56,7 +56,7 @@ could not panic!) at run-time leads to a compile-time CTFE error. *Dynamic check.* The Miri engine already dynamically detects panics, but the main point of promoteds is ruling them out statically. -### 2. Const safety +### Const safety We have explained what happens when evaluating a promoted panics, but what about other kinds of failure -- what about hitting an unsupported operation or @@ -105,7 +105,7 @@ For this reason, only `const fn` that were explicitly marked with the *Dynamic check.* The Miri engine already dynamically detects const safety violations, but the main point of promoteds is ruling them out statically. -### 3. Drop +### Drop Expressions returning "needs drop" types can never be promoted. If such an expression were promoted, the `Drop` impl would never get called on the value, From 4b4e01f4d766234ed29119712ba5a841fcb571c8 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 11 Oct 2019 12:38:59 -0700 Subject: [PATCH 2/9] Define promotion in terms of "context" and "promotability" --- promotion.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/promotion.md b/promotion.md index 40238e4..2b08961 100644 --- a/promotion.md +++ b/promotion.md @@ -1,6 +1,16 @@ # Const promotion -["(Implicit) Promotion"][promotion-rfc] is a mechanism that affects code like `&3`: +"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 contexts + +There are a few different contexts where promotion is beneficial. + +### Lifetime extension + +"Lifetime extension" is a mechanism that affects code like `&3`: Instead of putting it on the stack, the `3` is allocated in global static memory and a reference with lifetime `'static` is provided. This is essentially an automatic transformation turning `&EXPR` into @@ -10,15 +20,111 @@ Note that promotion happens on the MIR, not on surface-level syntax. This is relevant when discussing e.g. handling of panics caused by overflowing arithmetic. +Lifetime extension is described in [RFC 1414][promotion-rfc]. The RFC uses the +word "promotion" to refer exclusively to lifetime extension, since this was the +first context where promotion was done. + +[promotion-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md + +### Non-`Copy` array initialization + +Another promotion context was introduced in [RFC +2203](https://github.com/rust-rfcs/const-eval/blob/master/const.md). In this +case, we try to promote the initializer in expressions like `[Vec::new(); 32]`, +which allows non-`Copy` types to be used as array initializers. + +### `#[rustc_args_required_const(...)]` + +Additionally, some platform intrinsics require certain operations to be +immediates (known at compile-time). We use the `#[rustc_args_required_const]` +attribute, introduced in +[rust-lang/rust#48018](https://github.com/rust-lang/rust/pull/48018), to +specify these parameters and (aggressively, see below) try to promote the +corresponding arguments. + +### Implicit and explicit contexts + On top of what applies to [consts](const.md), promoteds suffer from the additional issue that *the user did not ask for them to be evaluated at compile-time*. Thus, if CTFE fails but the code would have worked fine at run-time, we broke the user's code for no good reason. Even if we are sure we found an error in the user's code, we are only allowed to [emit a warning, not a hard error][warn-rfc]. That's why we have to be very conservative with what can and cannot be promoted. -[promotion-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md +For example, users might be surprised to learn that whenever they take a +reference to a temporary, that temporary may be promoted away and never +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 +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. + +The distinction between these two determines whether calls to arbitrary `const +fn`s (those without `#[rustc_promotable]`) are promotable (see below). See +[rust-rfcs/const-eval#19](https://github.com/rust-rfcs/const-eval/issues/19) +for a thorough discussion of this. At present, this is the only difference +between implicit and explicit contexts. The requirements for promotion in an +implicit context are a superset of the ones in an explicit context. + [warn-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md -## Rules +### Lifetime extension in `const` and `static` + +We defined above that promotion guarantees that code in a non-const context +will be executed at compile-time. However, lifetime extension is useful +*inside* `const`s and `static`s as well (unlike the other promotion contexts). +Strictly speaking, lifetime extension in a const-context is not promotion; it +does not create `promoted`s in the MIR. However the same rules for +promotability apply inside a const-context as outside. + +It's an open question whether lifetime extension within a `const` or `static` +should be an implicit or explicit context. Currently it is treated as an +implicit one. + +## Promotability + +We have defined when it is desirable to promote expressions but have not yet +defined which expressions can actually be promoted. We refer to such +expressions as "promotable". + +### 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. + +### 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` + +Accesses to `const`s are always promotable, regardless of the body of the +`const`. For instance, while the previous example was not legal, the +following would be: + +```rust +const NOT_WINDOWS: i32 = if cfg!(windows) { 0 } else { 1 }; +let x: &'static i32 = &NOT_WINDOWS; +``` + +However, an access to a `static` is only promotable within the initializer of +another `static`. ### Panics From 10543e96a09791c236748bc70e20623fc1702d42 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 11 Oct 2019 14:47:43 -0700 Subject: [PATCH 3/9] Fix error regarding explicit promotion in `const`s --- promotion.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/promotion.md b/promotion.md index 2b08961..4743abd 100644 --- a/promotion.md +++ b/promotion.md @@ -79,9 +79,9 @@ Strictly speaking, lifetime extension in a const-context is not promotion; it does not create `promoted`s in the MIR. However the same rules for promotability apply inside a const-context as outside. -It's an open question whether lifetime extension within a `const` or `static` -should be an implicit or explicit context. Currently it is treated as an -implicit one. +All contexts are treated as explicit ones when determining promotability within +a `const` or `static` initializer because the user has requested that their +code run at compile-time anyway. ## Promotability From 0e030609261ff358ba341fdc87cd268b32edd485 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 11 Oct 2019 15:00:15 -0700 Subject: [PATCH 4/9] Fix link to RFC 2203 --- promotion.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/promotion.md b/promotion.md index 4743abd..8b23535 100644 --- a/promotion.md +++ b/promotion.md @@ -28,10 +28,11 @@ first context where promotion was done. ### Non-`Copy` array initialization -Another promotion context was introduced in [RFC -2203](https://github.com/rust-rfcs/const-eval/blob/master/const.md). In this -case, we try to promote the initializer in expressions like `[Vec::new(); 32]`, -which allows non-`Copy` types to be used as array initializers. +Another promotion context was introduced in [RFC 2203][]. In this case, we try +to promote the initializer in expressions like `[Vec::new(); 32]`, which allows +non-`Copy` types to be used as array initializers. + +[RFC 2203]: https://github.com/rust-lang/rfcs/blob/master/text/2203-const-repeat-expr.md ### `#[rustc_args_required_const(...)]` From fbeb1166dec065daf5e36075b85a66872126285c Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 11 Oct 2019 15:46:05 -0700 Subject: [PATCH 5/9] Better explain the motivation of RFC 2203 This is useful inside a const-context as well. --- promotion.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/promotion.md b/promotion.md index 8b23535..5c11c8f 100644 --- a/promotion.md +++ b/promotion.md @@ -28,9 +28,10 @@ first context where promotion was done. ### Non-`Copy` array initialization -Another promotion context was introduced in [RFC 2203][]. In this case, we try -to promote the initializer in expressions like `[Vec::new(); 32]`, which allows -non-`Copy` types to be used as array initializers. +Another promotion context, the initializer of an array expression, was +introduced in [RFC 2203][]. Here, promotion allows arrays of +non-`Copy` types to be initialized idiomatically, for example +`[Option::>::None; 32]`. [RFC 2203]: https://github.com/rust-lang/rfcs/blob/master/text/2203-const-repeat-expr.md @@ -71,18 +72,16 @@ implicit context are a superset of the ones in an explicit context. [warn-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md -### Lifetime extension in `const` and `static` +### Promotion contexts in `const` and `static` We defined above that promotion guarantees that code in a non-const context -will be executed at compile-time. However, lifetime extension is useful -*inside* `const`s and `static`s as well (unlike the other promotion contexts). -Strictly speaking, lifetime extension in a const-context is not promotion; it -does not create `promoted`s in the MIR. However the same rules for -promotability apply inside a const-context as outside. - -All contexts are treated as explicit ones when determining promotability within -a `const` or `static` initializer because the user has requested that their -code run at compile-time anyway. +will be executed at compile-time. However, lifetime extension and non-`Copy` +array initialziation are useful features *inside* `const`s and `static`s as +well. Strictly speaking, the transformation used to enable these features +inside a const-context is not promotion; no `promoted`s are created in the MIR. +However the same rules for promotability are used with one modification: +Because the user has already requested that this code run at compile time, all +contexts are treated as explicit. ## Promotability From 3d2f41a77cccc7a7638c87793384db74abef7314 Mon Sep 17 00:00:00 2001 From: ecstatic-morse Date: Fri, 11 Oct 2019 16:54:51 -0700 Subject: [PATCH 6/9] Fix typos --- promotion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/promotion.md b/promotion.md index 5c11c8f..c5346b3 100644 --- a/promotion.md +++ b/promotion.md @@ -37,7 +37,7 @@ non-`Copy` types to be initialized idiomatically, for example ### `#[rustc_args_required_const(...)]` -Additionally, some platform intrinsics require certain operations to be +Additionally, some platform intrinsics require certain parameters to be immediates (known at compile-time). We use the `#[rustc_args_required_const]` attribute, introduced in [rust-lang/rust#48018](https://github.com/rust-lang/rust/pull/48018), to @@ -76,7 +76,7 @@ implicit context are a superset of the ones in an explicit context. We defined above that promotion guarantees that code in a non-const context will be executed at compile-time. However, lifetime extension and non-`Copy` -array initialziation are useful features *inside* `const`s and `static`s as +array initialization are useful features *inside* `const`s and `static`s as well. Strictly speaking, the transformation used to enable these features inside a const-context is not promotion; no `promoted`s are created in the MIR. However the same rules for promotability are used with one modification: From 14b0ce17be9245c76f88ec796c1eb1f56d0d8fe5 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 21 Oct 2019 11:39:36 -0700 Subject: [PATCH 7/9] Reword description of promotability --- promotion.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/promotion.md b/promotion.md index c5346b3..d12c9f5 100644 --- a/promotion.md +++ b/promotion.md @@ -85,9 +85,10 @@ contexts are treated as explicit. ## Promotability -We have defined when it is desirable to promote expressions but have not yet -defined which expressions can actually be promoted. We refer to such -expressions as "promotable". +We have described the circumstances where promotion is desirable, but what +expressions are actually eligible for promotion? We refer to eligible +expressions as "promotable" and describe the restrictions on such expressions +below. ### Named locals From 7d5752d60c8002bc497ed5337a3d17e058830b24 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 21 Oct 2019 11:42:47 -0700 Subject: [PATCH 8/9] Clarify rules around promotablility of `const`s --- promotion.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/promotion.md b/promotion.md index d12c9f5..c81f4dc 100644 --- a/promotion.md +++ b/promotion.md @@ -115,16 +115,22 @@ resources for little benefit. ### Access to a `const` or `static` -Accesses to `const`s are always promotable, regardless of the body of the -`const`. For instance, while the previous example was not legal, the -following would be: +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: ```rust -const NOT_WINDOWS: i32 = if cfg!(windows) { 0 } else { 1 }; -let x: &'static i32 = &NOT_WINDOWS; +const BOOL: i32 = { + let ret = if cfg!(windows) { 0 } else { 1 }; + ret +}; + +let x: &'static i32 = &BOOL; ``` -However, an access to a `static` is only promotable within the initializer of +An access to a `static` is only promotable within the initializer of another `static`. ### Panics From 3ccf570fd85b6ec85c72c52ab8ce66daacb659aa Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 21 Oct 2019 11:47:11 -0700 Subject: [PATCH 9/9] Merge sections on lifetime extension in consts and statics --- promotion.md | 84 +++++++++++++++++++--------------------------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/promotion.md b/promotion.md index c81f4dc..7c5e7bb 100644 --- a/promotion.md +++ b/promotion.md @@ -72,16 +72,38 @@ implicit context are a superset of the ones in an explicit context. [warn-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md -### Promotion contexts in `const` and `static` +### Promotion contexts inside `const` and `static` + +Lifetime extension is also responsible for making code like this work: + +```rust +const FOO: &'static i32 = { + let x = &13; + x +}; +``` We defined above that promotion guarantees that code in a non-const context -will be executed at compile-time. However, lifetime extension and non-`Copy` -array initialization are useful features *inside* `const`s and `static`s as -well. Strictly speaking, the transformation used to enable these features -inside a const-context is not promotion; no `promoted`s are created in the MIR. -However the same rules for promotability are used with one modification: -Because the user has already requested that this code run at compile time, all -contexts are treated as explicit. +will be executed at compile-time. The above example illustrates that lifetime +extension and non-`Copy` array initialization are useful features *inside* +`const`s and `static`s as well. Strictly speaking, the transformation used to +enable these features inside a const-context is not promotion; no `promoted`s +are created in the MIR. However the same rules for promotability are used with +one modification: Because the user has already requested that this code run at +compile time, all contexts are treated as explicit. + +Notice that some code involving `&` *looks* like it relies on lifetime +extension but actually does not: + +```rust +const EMPTY_BYTES: &Vec = &Vec::new(); // Ok without lifetime extension +``` + +As we have seen above, `Vec::new()` does not get promoted. And yet this +compiles. Why that? The reason is that the reference obtains the lifetime of +the "enclosing scope", similar to how `let x = &mut x;` creates a reference +whose lifetime lasts for the enclosing scope. This is decided during MIR +building already, and does not involve lifetime extension. ## Promotability @@ -237,52 +259,6 @@ 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. -## `&` in `const` and `static` - -Promotion is also responsible for making code like this work: - -```rust -const FOO: &'static i32 = { - let x = &13; - x -}; -``` - -However, since this is in explicit const context, we are less strict about -promotion in this situation: all function calls are promoted, not just -`#[rustc_promotable]` functions: - -```rust -const fn bar() -> i32 { 42 } - -const FOO: &'static i32 = { - let x = &bar(); // this gets promoted - x -}; -``` - -However, we still do not promote *everything*; e.g., drop-checking still applies: - -```rust -const DROP: &'static Vec = { - let x = &Vec::new(); //~ ERROR: temporary value dropped while borrowed - x -}; -``` - -Notice that some code involving `&` *looks* like it relies on promotion but -actually does not: - -```rust -const EMPTY_BYTES: &Vec = &Vec::new(); // Ok without promotion -``` - -As we have seen above, `Vec::new()` does not get promoted. And yet this -compiles. Why that? The reason is that the reference obtains the lifetime of -the "enclosing scope", similar to how `let x = &mut x;` creates a reference -whose lifetime lasts for the enclosing scope. This is decided during MIR -building already, and does not involve promotion. - ## Open questions * There is a fourth kind of CTFE failure -- resource exhaustion. What do we do