Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explain the new valtree system for type level constants. #1097

Merged
merged 3 commits into from
Sep 13, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions src/const-eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,62 @@ Additionally constant evaluation can be used to reduce the workload or binary
size at runtime by precomputing complex operations at compiletime and only
storing the result.

All uses of constant evaluation can either be categorized as "influencing the type system"
(array lengths, enum variant discriminants, const generic parameters), or as solely being
Copy link
Contributor

@lcnr lcnr Mar 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do discriminants influence the type system in any way?

I think ValTrees are fine here because enum variants have to be plain integers, but I don't think that this is strictly necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do discriminants influence the type system in any way?

as tags are computed from discriminants, yes, the tag is part of the representation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, how does the tag feel into the type system? It feels like I am missing something here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... You can technically transmute an enum into a layout equivalent datastructure and then use the discriminant to change an array length. But in a less whacky (and unsound) way, if you put a non-c-like enum into a const generic, the tag will end up in the ValTree and thus in the parameters of the const generic.

Enums are encoded by adding their tag as a field before all variant fields. Alternatively we could encode enums by having an Option<VariantIdx> on ValTree::Branches. There is no strong reason to prefer either, except that enums are rare right now, so we'd be pessimizing non-enum aggregates with an unused None field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, yeah 🤔 if we encode the discriminant in the val tree we need to do it this way. Using the VariantIdx might be cleaner in the long run, but I don't really think it matters too much.

Copy link
Member

@RalfJung RalfJung Apr 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "affect the type system" is the wrong terminology... it's more about "does the value semantically matter to the compiler, or does the compiler just treat it as an opaque blobs (and the actual value is entirely irrelevant)". Discriminant values semantically matter -- the compiler computes on them to ensure uniqueness, determine layout niches, things like that.

Patterns, at least the subset of them that are considered for exhaustiveness checking, are another example of values that semantically matter, and that hence do/should use valtrees.

done to precompute expressions to be used at runtime.

Constant evaluation can be done by calling the `const_eval_*` functions of `TyCtxt`.
They're the wrappers of the `const_eval` query.

* `const_eval_global_id_for_typeck` evaluates a constant to a valtree,
so the result value can be further inspected by the compiler.
* `const_eval_global_id` evaluate a constant to an "opaque blob" containing its final value;
this is only useful for codegen backends and the CTFE evaluator engine itself.
* `eval_static_initializer` specifically computes the initial values of a static.
Statics are special; all other functions do not represent statics correctly
and have thus assertions preventing their use on statics.

The `const_eval_*` functions use a [`ParamEnv`](./param_env.html) of environment
in which the constant is evaluated (e.g. the function within which the constant is used)
and a [`GlobalId`]. The `GlobalId` is made up of an `Instance` referring to a constant
or static or of an `Instance` of a function and an index into the function's `Promoted` table.

Constant evaluation returns a [`EvalToConstValueResult`] with either the error, or a
representation of the constant. `static` initializers are always represented as
[`miri`](./miri.html) virtual memory allocations (via [`ConstValue::ByRef`]).
Constant evaluation returns an [`EvalToValTreeResult`] for type system constants or
[`EvalToConstValueResult`] with either the error, or a representation of the constant.

Constants for the type system are encoded in "valtree representation". The `ValTree` datastructure
allows us to represent

* arrays,
* many structs,
* tuples,
* enums and,
* most primitives.

The basic rule for
being permitted in the type system is that every value must be uniquely represented. In other
words: a specific value must only be representable in one specific way. For example: there is only
one way to represent an array of two integers as a `ValTree`:
`ValTree::Branch(&[ValTree::Leaf(first_int), ValTree;:Leaf(second_int)])`.
Even though theoretically a `[u32; 2]` could be encoded in a `u64` and thus just be a
`ValTree::Leaf(bits_of_two_u32)`, that is not a legal construction of `ValTree`
(and is very complex to do, so it is unlikely anyone is tempted to do so).

These rules also mean that some values are not representable. There can be no `union`s in type
level constants, as it is not clear how they should be represented, because their active variant
is unknown. Similarly there is no way to represent pointers, as addresses are unknown at
lcnr marked this conversation as resolved.
Show resolved Hide resolved
lcnr marked this conversation as resolved.
Show resolved Hide resolved
compile-time and thus we cannot make any assumptions about them. References on the other hand
*can* be represented, as equality for references is defined as equality on their value, so we
ignore their address and just look at the backing value. We must make sure that the pointer value
of the references are not observable. We thus encode `&42` exactly like `42`. Any conversion from
lcnr marked this conversation as resolved.
Show resolved Hide resolved
valtree back to codegen constants must reintroduce an actual indirection. At codegen time the
addresses may be deduplicated between multiple uses or not, entirely depending on arbitrary
optimization choices.

As a consequence, all decoding of `ValTree` must happen by matching on the type first and making
decisions depending on that. The value itself gives no useful information without the type that
belongs to it.

Other constants get represented as [`ConstValue::Scalar`]
or [`ConstValue::Slice`] if possible. This means that the `const_eval_*`
functions cannot be used to create miri-pointers to the evaluated constant.
Expand All @@ -42,4 +87,5 @@ If you need the value of a constant inside Miri, you need to directly work with
[`ConstValue::Slice`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/interpret/value/enum.ConstValue.html#variant.Slice
[`ConstValue::ByRef`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/interpret/value/enum.ConstValue.html#variant.ByRef
[`EvalToConstValueResult`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/interpret/error/type.EvalToConstValueResult.html
[`EvalToValTreeResult`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/interpret/error/type.EvalToValTreeResult.html
[`const_to_op`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.InterpCx.html#method.const_to_op