midenc_hir/ir/verifier.rs
1use super::{Context, Report};
2
3/// The `OpVerifier` trait is expected to be implemented by all [Op] impls as a prequisite.
4///
5/// The actual implementation is typically generated as part of deriving [Op].
6pub trait OpVerifier {
7 fn verify(&self, context: &Context) -> Result<(), Report>;
8}
9
10/// The `Verify` trait represents verification logic associated with implementations of some trait.
11///
12/// This is specifically used for automatically deriving verification checks for [Op] impls that
13/// implement traits that imply constraints on the representation or behavior of that op.
14///
15/// For example, if some [Op] derives an op trait like `SingleBlock`, this information is recorded
16/// in the underlying [Operation] metadata, so that we can recover a trait object reference for the
17/// trait when needed. However, just deriving the trait is not sufficient to guarantee that the op
18/// actually adheres to the implicit constraints and behavior of that trait. For example,
19/// `SingleBlock` implies that the implementing op contains only regions that consist of a single
20/// [Block]. This cannot be checked statically. The first step to addressing this though, is to
21/// reify the implicit validation rules as explicit checks - hence this trait.
22///
23/// So we've established that some op traits, such as `SingleBlock` mentioned above, have implicit
24/// validation rules, and we can implement [Verify] to make the implicit validation rules of such
25/// traits explicit - but how do we ensure that when an op derives an op trait, that the [Verify]
26/// impl is also derived, _and_ that it is called when the op is verified?
27///
28/// The answer lies in the use of some tricky type-level code to accomplish the following goals:
29///
30/// * Do not emit useless checks for op traits that have no verification rules
31/// * Do not require storing data in each instance of an [Op] just to verify a trait
32/// * Do not require emitting a bunch of redundant type checks for information we know statically
33/// * Be able to automatically derive all of the verification machinery along with the op traits
34///
35/// The way this works is as follows:
36///
37/// * We `impl<T> Verify<dyn Trait> for T where T: Op` for every trait `Trait` with validation rules.
38/// * A blanket impl of [HasVerifier] exists for all `T: Verify<Trait>`. This is a marker trait used
39/// in conjunction with specialization. See the trait docs for more details on its purpose.
40/// * The [Verifier] trait provides a default vacuous impl for all `Trait` and `T` pairs. However,
41/// we also provided a specialized [Verifier] impl for all `T: Verify<Trait>` using the
42/// `HasVerifier` marker. The specialized impl applies the underlying `Verify` impl.
43/// * When deriving the op traits for an `Op` impl, we generate a hidden type that encodes all of
44/// the op traits implemented by the op. We then generate an `OpVerifier` impl for the op, which
45/// uses the hidden type we generated to reify the `Verifier` impl for each trait. The
46/// `OpVerifier` implementation uses const eval to strip out all of the vacuous verifier impls,
47/// leaving behind just the "real" verification rules specific to the traits implemented by that
48/// op.
49/// * The `OpVerifier` impl is object-safe, and is in fact a required super-trait of `Op` to ensure
50/// that verification is part of defining an `Op`, but also to ensure that `verify` is a method
51/// of `Op`, and that we can cast an `Operation` to `&dyn OpVerifier` and call `verify` on that.
52///
53/// As a result of all this, we end up with highly-specialized verifiers for each op, with no
54/// dynamic dispatch, and automatically maintained as part of the `Op` definition. When a new
55/// op trait is derived, the verifier for the op is automatically updated to verify the new trait.
56pub trait Verify<Trait: ?Sized> {
57 /// In cases where verification may be disabled via runtime configuration, or based on
58 /// dynamic properties of the type, this method can be overridden and used to signal to
59 /// the verification driver that verification should be skipped on this item.
60 #[inline(always)]
61 #[allow(unused_variables)]
62 fn should_verify(&self, context: &Context) -> bool {
63 true
64 }
65 /// Apply this verifier, but only if [Verify::should_verify] returns true.
66 #[inline]
67 fn maybe_verify(&self, context: &Context) -> Result<(), Report> {
68 if self.should_verify(context) {
69 self.verify(context)
70 } else {
71 Ok(())
72 }
73 }
74 /// Apply this verifier to the current item.
75 fn verify(&self, context: &Context) -> Result<(), Report>;
76}
77
78/// A marker trait used for verifier specialization.
79///
80/// # Safety
81///
82/// In order for the `#[rustc_unsafe_specialization_marker]` attribute to be used safely and
83/// correctly, the following rules must hold:
84///
85/// * No associated items
86/// * No impls with lifetime constraints, as specialization will ignore them
87///
88/// For our use case, which is specializing verification for a given type and trait combination,
89/// by optimizing out verification-related code for type combinations which have no verifier, these
90/// are easy rules to uphold.
91///
92/// However, we must ensure that we continue to uphold these rules moving forward.
93#[rustc_unsafe_specialization_marker]
94unsafe trait HasVerifier<Trait: ?Sized>: Verify<Trait> {}
95
96// While at first glance, it appears we would be using this to specialize on the fact that a type
97// _has_ a verifier, which is strictly-speaking true, the actual goal we're aiming to acheive is
98// to be able to identify the _absence_ of a verifier, so that we can eliminate the boilerplate for
99// verifying that trait. See `Verifier` for more information.
100unsafe impl<T, Trait: ?Sized> HasVerifier<Trait> for T where T: Verify<Trait> {}
101
102/// The `Verifier` trait is used to derive a verifier for a given trait and concrete type.
103///
104/// It does this by providing a default implementation for all combinations of `Trait` and `T`,
105/// which always succeeds, and then specializing that implementation for `T: HasVerifier<Trait>`.
106///
107/// This has the effect of making all traits "verifiable", but only actually doing any verification
108/// for types which implement `Verify<Trait>`.
109///
110/// We go a step further and actually set things up so that `rustc` can eliminate all of the dead
111/// code when verification is vacuous. This is done by using const eval in the hidden type generated
112/// for an [Op] impls [OpVerifier] implementation, which will wrap verification in a const-evaluated
113/// check of the `VACUOUS` associated const. It can also be used directly, but the general idea
114/// behind all of this is that we don't need to directly touch any of this, it's all generated.
115///
116/// NOTE: Because this trait provides a default blanket impl for all `T`, you should avoid bringing
117/// it into scope unless absolutely needed. It is virtually always preferred to explicitly invoke
118/// this trait using turbofish syntax, so as to avoid conflict with the [Verify] trait, and to
119/// avoid polluting the namespace for all types in scope.
120pub trait Verifier<Trait: ?Sized> {
121 /// An implementation of `Verifier` sets this flag to true when its implementation is vacuous,
122 /// i.e. it always succeeds and is not dependent on runtime context.
123 ///
124 /// The default implementation of this trait sets this to `true`, since without a verifier for
125 /// the type, verification always succeeds. However, we can specialize on the presence of
126 /// a verifier and set this to `false`, which will result in all of the verification logic
127 /// being applied.
128 ///
129 /// ## Example Usage
130 ///
131 /// Shown below is an example of how one can use const eval to eliminate dead code branches
132 /// in verifier selection, so that the resulting implementation is specialized and able to
133 /// have more optimizations applied as a result.
134 ///
135 /// ```rust
136 /// use midenc_hir::{Context, Report, Verify, verifier::Verifier};
137 ///
138 /// /// This trait is a marker for a type that should never validate, i.e. verifying it always
139 /// /// returns an error.
140 /// trait Nope {}
141 /// impl<T: Nope> Verify<dyn Nope> for Any<T> {
142 /// fn verify(&self, context: &Context) -> Result<(), Report> {
143 /// Err(Report::msg("nope"))
144 /// }
145 /// }
146 ///
147 /// /// We can't impl the `Verify` trait for all T outside of `midenc_hir`, so we newtype all T
148 /// /// to do so, in effect, we can mimic the effect of implementing for all T using this type.
149 /// struct Any<T>(core::marker::PhantomData<T>);
150 /// impl<T> Any<T> {
151 /// fn new() -> Self {
152 /// Self(core::marker::PhantomData)
153 /// }
154 /// }
155 ///
156 /// /// This struct implements `Nope`, so it has an explicit verifier, which always fails
157 /// struct AlwaysRejected;
158 /// impl Nope for AlwaysRejected {}
159 ///
160 /// /// This struct doesn't implement `Nope`, so it gets a vacuous verifier, which always
161 /// /// succeeds.
162 /// struct AlwaysAccepted;
163 ///
164 /// /// Our vacuous verifier impl
165 /// #[inline(always)]
166 /// fn noop<T>(_: &Any<T>, _: &Context) -> Result<(), Report> { Ok(()) }
167 ///
168 /// /// This block uses const-eval to select the verifier for Any<AlwaysAccepted> statically
169 /// let always_accepted = const {
170 /// if <Any<AlwaysAccepted> as Verifier<dyn Nope>>::VACUOUS {
171 /// noop
172 /// } else {
173 /// <Any<AlwaysAccepted> as Verifier<dyn Nope>>::maybe_verify
174 /// }
175 /// };
176 ///
177 /// /// This block uses const-eval to select the verifier for Any<AlwaysRejected> statically
178 /// let always_rejected = const {
179 /// if <Any<AlwaysRejected> as Verifier<dyn Nope>>::VACUOUS {
180 /// noop
181 /// } else {
182 /// <Any<AlwaysRejected> as Verifier<dyn Nope>>::maybe_verify
183 /// }
184 /// };
185 ///
186 /// /// Verify that we got the correct impls. We can't verify that all of the abstraction was
187 /// /// eliminated, but from reviewing the assembly output, it appears that this is precisely
188 /// /// what happens.
189 /// let context = Context::default();
190 /// assert!(always_accepted(&Any::new(), &context).is_ok());
191 /// assert!(always_rejected(&Any::new(), &context).is_err());
192 /// ```
193 const VACUOUS: bool;
194
195 /// Checks if this verifier is applicable for the current item
196 fn should_verify(&self, context: &Context) -> bool;
197 /// Applies the verifier for this item, if [Verifier::should_verify] returns `true`
198 fn maybe_verify(&self, context: &Context) -> Result<(), Report>;
199 /// Applies the verifier for this item
200 fn verify(&self, context: &Context) -> Result<(), Report>;
201}
202
203/// The default blanket impl for all types and traits
204impl<T, Trait: ?Sized> Verifier<Trait> for T {
205 default const VACUOUS: bool = true;
206
207 #[inline(always)]
208 default fn should_verify(&self, _context: &Context) -> bool {
209 false
210 }
211
212 #[inline(always)]
213 default fn maybe_verify(&self, _context: &Context) -> Result<(), Report> {
214 Ok(())
215 }
216
217 #[inline(always)]
218 default fn verify(&self, _context: &Context) -> Result<(), Report> {
219 Ok(())
220 }
221}
222
223/// THe specialized impl for types which implement `Verify<Trait>`
224impl<T, Trait: ?Sized> Verifier<Trait> for T
225where
226 T: HasVerifier<Trait>,
227{
228 const VACUOUS: bool = false;
229
230 #[inline]
231 fn should_verify(&self, context: &Context) -> bool {
232 <T as Verify<Trait>>::should_verify(self, context)
233 }
234
235 #[inline(always)]
236 fn maybe_verify(&self, context: &Context) -> Result<(), Report> {
237 <T as Verify<Trait>>::maybe_verify(self, context)
238 }
239
240 #[inline]
241 fn verify(&self, context: &Context) -> Result<(), Report> {
242 <T as Verify<Trait>>::verify(self, context)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use core::hint::black_box;
249
250 use super::*;
251 use crate::{traits::SingleBlock, Operation};
252
253 struct Vacuous;
254
255 /// In this test, we're validating that a type that trivially verifies specializes as vacuous,
256 /// and that a type we know has a "real" verifier, specializes as _not_ vacuous
257 #[test]
258 fn verifier_specialization_concrete() {
259 assert!(black_box(<Vacuous as Verifier<dyn SingleBlock>>::VACUOUS));
260 assert!(black_box(!<Operation as Verifier<dyn SingleBlock>>::VACUOUS));
261 }
262}