Skip to main content

qcode/value/
poison.rs

1//! First-class **poison** values (argpromote v2, `ARGPROMOTE_REGISTERS_V2.md`).
2//!
3//! A poison value is a typed placeholder for a datum whose concrete bits are
4//! undefined — the clobber slots of a register/external return pack, and the
5//! symbolic arguments of a pure-call emulation. Semantics:
6//!
7//! * **GVN never folds it.** Every poison is a distinct interned value (they are
8//!   never deduped), so two poisons of the same type are *not* congruent and a
9//!   poison is never congruent with a concrete value. See the congruence rule in
10//!   the GVN CSE sub-pass.
11//! * **DCE / `dead_signature` treat it as an ordinary pure value** — a
12//!   `store(R, poison)` dies iff `R` is unread; a poison-only pack slot prunes
13//!   through the normal unused-slot path. No special casing anywhere.
14//! * **Reading poison in the emulator is a hard error.** Propagating it as an
15//!   operand is fine; the trap fires only when its concrete value is demanded.
16//!
17//! Poison is engine-internal: it is minted mid-pipeline and is recomputable, so
18//! it is deliberately not rendered into textual qcode.
19
20use crate::{
21    context::Shared,
22    types::TypeId,
23    value::{
24        Value, ValueId,
25        util::base_ref::{BaseRef, WithShared},
26    },
27};
28use jstd::Identifier;
29
30#[derive(Identifier)]
31pub struct PoisonId(usize);
32
33/// A typed poison value stored in a [`Context`](crate::context::Context). Carries
34/// only its [`TypeId`] (hence its width); its bits are undefined.
35#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
36pub struct Poison {
37    /// The type (and thus byte width) of this poison value.
38    pub type_id: TypeId,
39}
40
41pub type PoisonRef<'str, 'ctx> = BaseRef<&'ctx Shared<'str>, PoisonId>;
42
43impl<'s, 'ctx: 's, 'str: 'ctx> WithShared<'s, 'ctx, 'str> for PoisonRef<'str, 'ctx> {
44    fn shared(&'s self) -> &'ctx Shared<'str> {
45        self.ctx
46    }
47}
48
49impl<'s, 'ctx: 's, 'str: 'ctx, Ctx> BaseRef<Ctx, PoisonId>
50where
51    Self: WithShared<'s, 'ctx, 'str>,
52{
53    fn inner(&'s self) -> &'ctx Poison {
54        &self.shared().values.poisons[self.id]
55    }
56
57    /// Returns the [`TypeId`] of this poison value.
58    pub fn type_id(&'s self) -> TypeId {
59        self.inner().type_id
60    }
61}
62
63impl std::fmt::Display for PoisonRef<'_, '_> {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        write!(f, "poison")
66    }
67}
68
69impl<'str, 'ctx> Value<'str, 'ctx> for PoisonRef<'str, 'ctx> {
70    fn id(&self) -> ValueId {
71        ValueId::Poison(self.id)
72    }
73
74    fn size(&self) -> usize {
75        self.ctx
76            .types
77            .size_of(self.ctx.values.poisons[self.id].type_id)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::testing::TestContext;
84    use crate::value::{Value, ValueId, ValueRef};
85
86    /// A poison value interns, carries its type/width, and round-trips through a
87    /// `ValueRef`.
88    #[test]
89    fn poison_interns_and_round_trips() {
90        let tc = TestContext::new();
91        let i32_ty = tc.ctx.shared.types.get_or_make_int(4);
92        let p = tc.ctx.get_poison(i32_ty);
93        assert!(p.is_poison());
94        assert_eq!(tc.ctx.type_of(p), i32_ty);
95        assert_eq!(tc.ctx.stored_type_of(p), Some(i32_ty));
96        match ValueRef::new(p, &tc.ctx) {
97            ValueRef::Poison(r) => {
98                assert_eq!(r.type_id(), i32_ty);
99                assert_eq!(r.size(), 4);
100            }
101            _ => panic!("expected a poison ref"),
102        }
103    }
104
105    /// Two poisons of the *same* type are distinct values (never deduped), so
106    /// GVN keeps them in separate congruence classes.
107    #[test]
108    fn same_type_poisons_are_distinct() {
109        let tc = TestContext::new();
110        let i64_ty = tc.ctx.shared.types.get_or_make_int(8);
111        let a = tc.ctx.get_poison(i64_ty);
112        let b = tc.ctx.get_poison(i64_ty);
113        assert_ne!(a, b, "each poison must be its own interned value");
114        assert!(matches!(a, ValueId::Poison(_)));
115        assert!(matches!(b, ValueId::Poison(_)));
116    }
117}