midenc_hir/ir/verifier.rs
1use super::{Context, Report};
2
3/// The `OpVerifier` trait is expected to be implemented by all [crate::Op] impls as a prequisite.
4///
5/// The actual implementation is typically generated as part of deriving [crate::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 [crate::Op] impls
13/// that implement traits that imply constraints on the representation or behavior of that op.
14///
15/// For example, if some [crate::Op] derives an op trait like `SingleBlock`, this information is
16/// recorded in the underlying [crate::Operation] metadata, so that we can recover a trait object
17/// reference for the trait when needed. However, just deriving the trait is not sufficient to
18/// guarantee that the op actually adheres to the implicit constraints and behavior of that trait.
19/// For example, `SingleBlock` implies that the implementing op contains only regions that consist
20/// of a single [crate::Block]. This cannot be checked statically. The first step to addressing this
21/// though, is to 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 [crate::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]
94pub unsafe 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 [crate::Op] impls [OpVerifier] implementation, which will wrap verification in a
113/// const-evaluated check of the `VACUOUS` associated const. It can also be used directly, but the
114/// general idea behind all of this is that we don't need to directly touch any of this, it's all
115/// generated.
116///
117/// NOTE: Because this trait provides a default blanket impl for all `T`, you should avoid bringing
118/// it into scope unless absolutely needed. It is virtually always preferred to explicitly invoke
119/// this trait using turbofish syntax, so as to avoid conflict with the [Verify] trait, and to
120/// avoid polluting the namespace for all types in scope.
121pub trait Verifier<Trait: ?Sized> {
122 /// An implementation of `Verifier` sets this flag to true when its implementation is vacuous,
123 /// i.e. it always succeeds and is not dependent on runtime context.
124 ///
125 /// The default implementation of this trait sets this to `true`, since without a verifier for
126 /// the type, verification always succeeds. However, we can specialize on the presence of
127 /// a verifier and set this to `false`, which will result in all of the verification logic
128 /// being applied.
129 ///
130 /// ## Example Usage
131 ///
132 /// Shown below is an example of how one can use const eval to eliminate dead code branches
133 /// in verifier selection, so that the resulting implementation is specialized and able to
134 /// have more optimizations applied as a result.
135 ///
136 /// ```rust
137 /// use midenc_hir::{Context, Report, Verify, verifier::Verifier};
138 ///
139 /// /// This trait is a marker for a type that should never validate, i.e. verifying it always
140 /// /// returns an error.
141 /// trait Nope {}
142 /// impl<T: Nope> Verify<dyn Nope> for Any<T> {
143 /// fn verify(&self, context: &Context) -> Result<(), Report> {
144 /// Err(Report::msg("nope"))
145 /// }
146 /// }
147 ///
148 /// /// We can't impl the `Verify` trait for all T outside of `midenc_hir`, so we newtype all T
149 /// /// to do so, in effect, we can mimic the effect of implementing for all T using this type.
150 /// struct Any<T>(core::marker::PhantomData<T>);
151 /// impl<T> Any<T> {
152 /// fn new() -> Self {
153 /// Self(core::marker::PhantomData)
154 /// }
155 /// }
156 ///
157 /// /// This struct implements `Nope`, so it has an explicit verifier, which always fails
158 /// struct AlwaysRejected;
159 /// impl Nope for AlwaysRejected {}
160 ///
161 /// /// This struct doesn't implement `Nope`, so it gets a vacuous verifier, which always
162 /// /// succeeds.
163 /// struct AlwaysAccepted;
164 ///
165 /// /// Our vacuous verifier impl
166 /// #[inline(always)]
167 /// fn noop<T>(_: &Any<T>, _: &Context) -> Result<(), Report> { Ok(()) }
168 ///
169 /// /// This block uses const-eval to select the verifier for Any<AlwaysAccepted> statically
170 /// let always_accepted = const {
171 /// if <Any<AlwaysAccepted> as Verifier<dyn Nope>>::VACUOUS {
172 /// noop
173 /// } else {
174 /// <Any<AlwaysAccepted> as Verifier<dyn Nope>>::maybe_verify
175 /// }
176 /// };
177 ///
178 /// /// This block uses const-eval to select the verifier for Any<AlwaysRejected> statically
179 /// let always_rejected = const {
180 /// if <Any<AlwaysRejected> as Verifier<dyn Nope>>::VACUOUS {
181 /// noop
182 /// } else {
183 /// <Any<AlwaysRejected> as Verifier<dyn Nope>>::maybe_verify
184 /// }
185 /// };
186 ///
187 /// /// Verify that we got the correct impls. We can't verify that all of the abstraction was
188 /// /// eliminated, but from reviewing the assembly output, it appears that this is precisely
189 /// /// what happens.
190 /// let context = Context::default();
191 /// assert!(always_accepted(&Any::new(), &context).is_ok());
192 /// assert!(always_rejected(&Any::new(), &context).is_err());
193 /// ```
194 const VACUOUS: bool;
195
196 /// Checks if this verifier is applicable for the current item
197 fn should_verify(&self, context: &Context) -> bool;
198 /// Applies the verifier for this item, if [Verifier::should_verify] returns `true`
199 fn maybe_verify(&self, context: &Context) -> Result<(), Report>;
200 /// Applies the verifier for this item
201 fn verify(&self, context: &Context) -> Result<(), Report>;
202}
203
204/// The default blanket impl for all types and traits
205impl<T, Trait: ?Sized> Verifier<Trait> for T {
206 default const VACUOUS: bool = true;
207
208 #[inline(always)]
209 default fn should_verify(&self, _context: &Context) -> bool {
210 false
211 }
212
213 #[inline(always)]
214 default fn maybe_verify(&self, _context: &Context) -> Result<(), Report> {
215 Ok(())
216 }
217
218 #[inline(always)]
219 default fn verify(&self, _context: &Context) -> Result<(), Report> {
220 Ok(())
221 }
222}
223
224/// THe specialized impl for types which implement `Verify<Trait>`
225impl<T, Trait: ?Sized> Verifier<Trait> for T
226where
227 T: HasVerifier<Trait>,
228{
229 const VACUOUS: bool = false;
230
231 #[inline]
232 fn should_verify(&self, context: &Context) -> bool {
233 <T as Verify<Trait>>::should_verify(self, context)
234 }
235
236 #[inline(always)]
237 fn maybe_verify(&self, context: &Context) -> Result<(), Report> {
238 <T as Verify<Trait>>::maybe_verify(self, context)
239 }
240
241 #[inline]
242 fn verify(&self, context: &Context) -> Result<(), Report> {
243 <T as Verify<Trait>>::verify(self, context)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use core::hint::black_box;
250
251 use super::*;
252 use crate::{Operation, traits::SingleBlock};
253
254 struct Vacuous;
255
256 /// In this test, we're validating that a type that trivially verifies specializes as vacuous,
257 /// and that a type we know has a "real" verifier, specializes as _not_ vacuous
258 #[test]
259 fn verifier_specialization_concrete() {
260 assert!(black_box(<Vacuous as Verifier<dyn SingleBlock>>::VACUOUS));
261 assert!(black_box(!<Operation as Verifier<dyn SingleBlock>>::VACUOUS));
262 }
263}