Skip to main content

presolve_compiler/
production_constant_pool.rs

1//! K5 immutable constant pooling for production-only executable products.
2
3use std::collections::BTreeMap;
4
5use crate::{ConstantPoolEntryId, ProductionOptimizationPolicyV1};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct ProductionConstantCandidate {
9    pub consumer_id: String,
10    pub type_tag: String,
11    pub canonical_bytes: Vec<u8>,
12    pub immutable: bool,
13    pub identity_observable: bool,
14    pub snapshot_or_public_artifact: bool,
15}
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct ProductionConstantPoolEntry {
18    pub id: ConstantPoolEntryId,
19    pub ordinal: usize,
20    pub type_tag: String,
21    pub canonical_bytes: Vec<u8>,
22}
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct ConstantPoolConsumer {
25    pub consumer_id: String,
26    pub entry_id: ConstantPoolEntryId,
27}
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum ConstantPoolingDecision {
30    Pooled,
31    Inline,
32    Excluded,
33}
34#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct ConstantPoolingReport {
36    pub decisions: Vec<(String, ConstantPoolingDecision)>,
37}
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct ProductionConstantPool {
40    pub entries: Vec<ProductionConstantPoolEntry>,
41    pub consumers: Vec<ConstantPoolConsumer>,
42}
43
44#[must_use]
45///
46/// # Panics
47///
48/// Panics only if an internally validated canonical type tag becomes invalid.
49pub fn pool_production_constants(
50    candidates: &[ProductionConstantCandidate],
51) -> (ProductionConstantPool, ConstantPoolingReport) {
52    let mut groups = BTreeMap::<(String, Vec<u8>), Vec<&ProductionConstantCandidate>>::new();
53    let mut decisions = Vec::new();
54    for candidate in candidates {
55        if !candidate.immutable
56            || candidate.identity_observable
57            || candidate.snapshot_or_public_artifact
58            || ConstantPoolEntryId::for_canonical_value(
59                &candidate.type_tag,
60                &candidate.canonical_bytes,
61            )
62            .is_none()
63        {
64            decisions.push((
65                candidate.consumer_id.clone(),
66                ConstantPoolingDecision::Excluded,
67            ));
68        } else {
69            groups
70                .entry((
71                    candidate.type_tag.clone(),
72                    candidate.canonical_bytes.clone(),
73                ))
74                .or_default()
75                .push(candidate);
76        }
77    }
78    let mut pending = Vec::new();
79    for ((type_tag, bytes), users) in groups {
80        let count = users.len();
81        let short = bytes.len() <= ProductionOptimizationPolicyV1::INLINE_LITERAL_MAX_UTF8_BYTES;
82        let pooled_smaller =
83            bytes.len().saturating_mul(count) > bytes.len() + count.saturating_mul(4);
84        if count >= ProductionOptimizationPolicyV1::CONSTANT_POOL_MIN_REUSE_COUNT
85            && (!short || pooled_smaller)
86        {
87            pending.push((
88                ConstantPoolEntryId::for_canonical_value(&type_tag, &bytes)
89                    .expect("validated type tag"),
90                type_tag,
91                bytes,
92                users,
93            ));
94        } else {
95            decisions.extend(
96                users
97                    .into_iter()
98                    .map(|user| (user.consumer_id.clone(), ConstantPoolingDecision::Inline)),
99            );
100        }
101    }
102    pending.sort_by(|a, b| a.0.cmp(&b.0));
103    let mut entries = Vec::new();
104    let mut consumers = Vec::new();
105    for (ordinal, (id, type_tag, canonical_bytes, users)) in pending.into_iter().enumerate() {
106        entries.push(ProductionConstantPoolEntry {
107            id: id.clone(),
108            ordinal,
109            type_tag,
110            canonical_bytes,
111        });
112        for user in users {
113            consumers.push(ConstantPoolConsumer {
114                consumer_id: user.consumer_id.clone(),
115                entry_id: id.clone(),
116            });
117            decisions.push((user.consumer_id.clone(), ConstantPoolingDecision::Pooled));
118        }
119    }
120    consumers.sort_by(|a, b| a.consumer_id.cmp(&b.consumer_id));
121    decisions.sort_by(|a, b| a.0.cmp(&b.0));
122    (
123        ProductionConstantPool { entries, consumers },
124        ConstantPoolingReport { decisions },
125    )
126}
127
128#[cfg(test)]
129mod tests {
130    use super::{pool_production_constants, ConstantPoolingDecision, ProductionConstantCandidate};
131    fn candidate(id: &str, ty: &str, value: &[u8]) -> ProductionConstantCandidate {
132        ProductionConstantCandidate {
133            consumer_id: id.to_string(),
134            type_tag: ty.to_string(),
135            canonical_bytes: value.to_vec(),
136            immutable: true,
137            identity_observable: false,
138            snapshot_or_public_artifact: false,
139        }
140    }
141    #[test]
142    fn k5_pools_only_safe_reused_constants_in_canonical_order() {
143        let long = b"this canonical constant is longer than twenty-four bytes";
144        let (pool, report) = pool_production_constants(&[
145            candidate("z", "string", long),
146            candidate("a", "string", long),
147            candidate("number", "number", b"1"),
148            candidate("number2", "number", b"1"),
149        ]);
150        assert_eq!(pool.entries.len(), 1);
151        assert_eq!(pool.entries[0].ordinal, 0);
152        assert_eq!(pool.consumers.len(), 2);
153        assert!(report
154            .decisions
155            .iter()
156            .any(|(id, decision)| id == "number" && *decision == ConstantPoolingDecision::Inline));
157    }
158    #[test]
159    fn k5_excludes_mutable_identity_and_public_values() {
160        let mut mutable = candidate("mutable", "string", b"same-long-value-for-pooling");
161        mutable.immutable = false;
162        let mut identity = candidate("identity", "string", b"same-long-value-for-pooling");
163        identity.identity_observable = true;
164        let (pool, report) = pool_production_constants(&[mutable, identity]);
165        assert!(pool.entries.is_empty());
166        assert!(report
167            .decisions
168            .iter()
169            .all(|(_, decision)| *decision == ConstantPoolingDecision::Excluded));
170    }
171}