1use 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]
45pub 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}