1#![forbid(unsafe_code)]
2
3use sim_kernel::{AbiVersion, CapabilityName, Export, LibManifest, LibTarget, Symbol, Version};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub struct ManifestHash(pub [u8; 32]);
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct ShapeHash(pub [u8; 32]);
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct CodecHash(pub [u8; 32]);
16
17pub fn hash_manifest(manifest: &LibManifest) -> ManifestHash {
20 let mut hasher = CanonicalHasher::new("sim.manifest.v1");
21 hash_manifest_into(&mut hasher, manifest);
22 ManifestHash(hasher.finish())
23}
24
25#[cfg(feature = "shape")]
27pub fn hash_shape(symbol: &Symbol, shape: &dyn sim_shape::Shape) -> ShapeHash {
28 let mut hasher = CanonicalHasher::new("sim.shape.v1");
29 write_symbol(&mut hasher, symbol);
30 write_optional_u32(&mut hasher, shape.id().map(|id| id.0));
31 write_bool(&mut hasher, shape.is_effectful());
32 ShapeHash(hasher.finish())
33}
34
35#[cfg(any(
36 feature = "codec-lisp",
37 feature = "codec-json",
38 feature = "codec-binary",
39 feature = "codec-binary-base64",
40 feature = "codec-chat",
41 feature = "codec-algol"
42))]
43pub fn hash_codec(
46 cx: &mut sim_kernel::Cx,
47 codec: &sim_codec::CodecRuntime,
48) -> sim_kernel::Result<CodecHash> {
49 use sim_kernel::Expr;
50
51 fn shape_symbol(
52 cx: &mut sim_kernel::Cx,
53 value: &sim_kernel::Value,
54 ) -> sim_kernel::Result<Symbol> {
55 match value.object().as_expr(cx)? {
56 Expr::Symbol(symbol) => Ok(symbol),
57 other => Err(sim_kernel::Error::HostError(format!(
58 "shape ref did not lower to a symbol: {:?}",
59 other
60 ))),
61 }
62 }
63
64 let mut hasher = CanonicalHasher::new("sim.codec.v1");
65 write_symbol(&mut hasher, &codec.symbol);
66 write_bool(&mut hasher, codec.decoder.is_some());
67 write_bool(&mut hasher, codec.located_decoder.is_some());
68 write_bool(&mut hasher, codec.tree_decoder.is_some());
69 write_bool(&mut hasher, codec.encoder.is_some());
70 write_bool(&mut hasher, codec.located_encoder.is_some());
71 write_bool(&mut hasher, codec.tree_encoder.is_some());
72 write_str(&mut hasher, codec.default_decode.as_symbol_name());
73 write_symbol(&mut hasher, &shape_symbol(cx, &codec.expr_shape)?);
74 write_symbol(&mut hasher, &shape_symbol(cx, &codec.options_shape)?);
75 Ok(CodecHash(hasher.finish()))
76}
77
78#[derive(Clone, Copy)]
79struct CanonicalHasher {
80 state: [u64; 4],
81}
82
83impl CanonicalHasher {
84 fn new(domain: &'static str) -> Self {
85 let mut hasher = Self {
86 state: [
87 0x243f_6a88_85a3_08d3,
88 0x1319_8a2e_0370_7344,
89 0xa409_3822_299f_31d0,
90 0x082e_fa98_ec4e_6c89,
91 ],
92 };
93 hasher.write_bytes(domain.as_bytes());
94 hasher
95 }
96
97 fn write_bytes(&mut self, bytes: &[u8]) {
98 self.mix_len(bytes.len() as u64);
99 for (index, byte) in bytes.iter().enumerate() {
100 let lane = index & 3;
101 self.state[lane] ^= u64::from(*byte) + ((index as u64) << 8);
102 self.state[lane] = self.state[lane]
103 .rotate_left(13)
104 .wrapping_mul(0x1000_0000_01b3);
105 self.state[(lane + 1) & 3] ^= self.state[lane].rotate_right(7);
106 }
107 }
108
109 fn mix_len(&mut self, len: u64) {
110 for lane in 0..4 {
111 self.state[lane] ^= len.wrapping_mul((lane as u64) + 0x9e37_79b9);
112 self.state[lane] = self.state[lane].rotate_left(17);
113 }
114 }
115
116 fn finish(mut self) -> [u8; 32] {
117 self.mix_len(0xff);
118 for lane in 0..4 {
119 let other = self.state[(lane + 1) & 3];
120 self.state[lane] ^= other.rotate_left(11);
121 self.state[lane] = self.state[lane].wrapping_mul(0x9e37_79b1_85eb_ca87);
122 }
123
124 let mut out = [0u8; 32];
125 for (index, lane) in self.state.into_iter().enumerate() {
126 out[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes());
127 }
128 out
129 }
130}
131
132fn hash_manifest_into(hasher: &mut CanonicalHasher, manifest: &LibManifest) {
133 write_symbol(hasher, &manifest.id);
134 write_version(hasher, &manifest.version);
135 write_abi_version(hasher, manifest.abi);
136 write_lib_target(hasher, manifest.target.clone());
137
138 let mut requires = manifest.requires.clone();
139 requires.sort_by(|left, right| {
140 (
141 left.id.namespace.as_ref().map(|value| value.as_ref()),
142 left.id.name.as_ref(),
143 left.minimum_version.as_ref().map(|value| value.0.as_str()),
144 )
145 .cmp(&(
146 right.id.namespace.as_ref().map(|value| value.as_ref()),
147 right.id.name.as_ref(),
148 right.minimum_version.as_ref().map(|value| value.0.as_str()),
149 ))
150 });
151 write_usize(hasher, requires.len());
152 for dependency in &requires {
153 write_symbol(hasher, &dependency.id);
154 write_optional_version(hasher, dependency.minimum_version.as_ref());
155 }
156
157 let mut capabilities = manifest.capabilities.clone();
158 capabilities.sort_by(|left, right| left.as_str().cmp(right.as_str()));
159 write_usize(hasher, capabilities.len());
160 for capability in &capabilities {
161 write_capability(hasher, capability);
162 }
163
164 let mut exports = manifest.exports.clone();
165 exports.sort_by(|left, right| {
166 (left.kind(), left.symbol().as_qualified_str())
167 .cmp(&(right.kind(), right.symbol().as_qualified_str()))
168 });
169 write_usize(hasher, exports.len());
170 for export in &exports {
171 hash_export_into(hasher, export);
172 }
173}
174
175fn hash_export_into(hasher: &mut CanonicalHasher, export: &Export) {
176 write_str(hasher, export.kind());
177 write_symbol(hasher, export.symbol());
178}
179
180fn write_lib_target(hasher: &mut CanonicalHasher, target: LibTarget) {
181 write_str(hasher, &target.to_symbol().as_qualified_str());
182}
183
184fn write_symbol(hasher: &mut CanonicalHasher, symbol: &Symbol) {
185 write_optional_str(hasher, symbol.namespace.as_deref());
186 write_str(hasher, symbol.name.as_ref());
187}
188
189fn write_version(hasher: &mut CanonicalHasher, version: &Version) {
190 write_str(hasher, &version.0);
191}
192
193fn write_optional_version(hasher: &mut CanonicalHasher, version: Option<&Version>) {
194 match version {
195 Some(version) => {
196 write_bool(hasher, true);
197 write_version(hasher, version);
198 }
199 None => write_bool(hasher, false),
200 }
201}
202
203fn write_abi_version(hasher: &mut CanonicalHasher, abi: AbiVersion) {
204 write_u16(hasher, abi.major);
205 write_u16(hasher, abi.minor);
206}
207
208fn write_capability(hasher: &mut CanonicalHasher, capability: &CapabilityName) {
209 write_str(hasher, capability.as_str());
210}
211
212fn write_optional_str(hasher: &mut CanonicalHasher, value: Option<&str>) {
213 match value {
214 Some(value) => {
215 write_bool(hasher, true);
216 write_str(hasher, value);
217 }
218 None => write_bool(hasher, false),
219 }
220}
221
222#[cfg(feature = "shape")]
223fn write_optional_u32(hasher: &mut CanonicalHasher, value: Option<u32>) {
224 match value {
225 Some(value) => {
226 write_bool(hasher, true);
227 write_u32(hasher, value);
228 }
229 None => write_bool(hasher, false),
230 }
231}
232
233fn write_bool(hasher: &mut CanonicalHasher, value: bool) {
234 hasher.write_bytes(&[u8::from(value)]);
235}
236
237fn write_u16(hasher: &mut CanonicalHasher, value: u16) {
238 hasher.write_bytes(&value.to_le_bytes());
239}
240
241#[cfg(feature = "shape")]
242fn write_u32(hasher: &mut CanonicalHasher, value: u32) {
243 hasher.write_bytes(&value.to_le_bytes());
244}
245
246fn write_usize(hasher: &mut CanonicalHasher, value: usize) {
247 hasher.write_bytes(&(value as u64).to_le_bytes());
248}
249
250fn write_str(hasher: &mut CanonicalHasher, value: &str) {
251 hasher.write_bytes(value.as_bytes());
252}
253
254#[cfg(test)]
255mod tests {
256 use sim_kernel::{CapabilityName, Export, LibManifest, LibTarget, Symbol, Version};
257
258 use crate::compat::hash_manifest;
259
260 fn sample_manifest() -> LibManifest {
261 LibManifest {
262 id: Symbol::qualified("demo", "lib"),
263 version: Version("1.2.3".to_owned()),
264 abi: sim_kernel::AbiVersion { major: 1, minor: 0 },
265 target: LibTarget::CodecSource(Symbol::qualified("codec", "lisp")),
266 requires: vec![
267 sim_kernel::Dependency {
268 id: Symbol::qualified("z", "later"),
269 minimum_version: Some(Version("2.0.0".to_owned())),
270 },
271 sim_kernel::Dependency {
272 id: Symbol::qualified("a", "first"),
273 minimum_version: None,
274 },
275 ],
276 capabilities: vec![
277 CapabilityName::new("macro.expand"),
278 CapabilityName::new("read-construct"),
279 ],
280 exports: vec![
281 Export::Function {
282 symbol: Symbol::qualified("demo", "f"),
283 function_id: Some(sim_kernel::FunctionId(7)),
284 },
285 Export::Class {
286 symbol: Symbol::qualified("demo", "C"),
287 class_id: Some(sim_kernel::ClassId(222)),
288 },
289 ],
290 }
291 }
292
293 #[test]
294 fn manifest_hash_ignores_declaration_order_and_runtime_ids() {
295 let left = sample_manifest();
296 let mut right = sample_manifest();
297 right.requires.reverse();
298 right.capabilities.reverse();
299 right.exports.reverse();
300 right.exports = vec![
301 Export::Class {
302 symbol: Symbol::qualified("demo", "C"),
303 class_id: None,
304 },
305 Export::Function {
306 symbol: Symbol::qualified("demo", "f"),
307 function_id: None,
308 },
309 ];
310
311 assert_eq!(hash_manifest(&left), hash_manifest(&right));
312 }
313
314 #[test]
315 #[cfg(feature = "shape")]
316 fn shape_hash_uses_symbol_and_canonical_shape_flags() {
317 use std::sync::Arc;
318
319 use crate::compat::hash_shape;
320 use crate::shape::{AnyShape, EffectfulShape};
321
322 let plain = AnyShape;
323 let effectful = EffectfulShape::new(Arc::new(AnyShape));
324
325 assert_eq!(
326 hash_shape(&Symbol::qualified("demo", "shape"), &plain),
327 hash_shape(&Symbol::qualified("demo", "shape"), &AnyShape)
328 );
329 assert_ne!(
330 hash_shape(&Symbol::qualified("demo", "shape"), &plain),
331 hash_shape(&Symbol::qualified("demo", "shape"), &effectful)
332 );
333 }
334
335 #[test]
336 #[cfg(all(
337 feature = "shape",
338 any(
339 feature = "codec-lisp",
340 feature = "codec-json",
341 feature = "codec-binary",
342 feature = "codec-binary-base64",
343 feature = "codec-chat",
344 feature = "codec-algol"
345 )
346 ))]
347 fn codec_hash_ignores_runtime_ids_and_uses_surface_contract() {
348 use std::sync::Arc;
349
350 use sim_kernel::{DefaultFactory, EagerPolicy};
351
352 use crate::{
353 codec::{CodecDefaultDecode, CodecRuntime},
354 compat::hash_codec,
355 runtime::install_core_runtime,
356 };
357
358 let mut cx = sim_kernel::Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
359 install_core_runtime(&mut cx);
360 let expr_shape = cx
361 .registry()
362 .shape_by_symbol(&Symbol::qualified("core", "Expr"))
363 .cloned()
364 .unwrap();
365 let options_shape = cx
366 .registry()
367 .shape_by_symbol(&Symbol::qualified("core", "EncodeOptions"))
368 .cloned()
369 .unwrap();
370
371 let left = CodecRuntime {
372 id: sim_kernel::CodecId(1),
373 symbol: Symbol::qualified("codec", "demo"),
374 decoder: None,
375 located_decoder: None,
376 tree_decoder: None,
377 encoder: None,
378 located_encoder: None,
379 tree_encoder: None,
380 expr_shape: expr_shape.clone(),
381 options_shape: options_shape.clone(),
382 default_decode: CodecDefaultDecode::Datum,
383 };
384 let right = CodecRuntime {
385 id: sim_kernel::CodecId(99),
386 symbol: Symbol::qualified("codec", "demo"),
387 decoder: None,
388 located_decoder: None,
389 tree_decoder: None,
390 encoder: None,
391 located_encoder: None,
392 tree_encoder: None,
393 expr_shape,
394 options_shape,
395 default_decode: CodecDefaultDecode::Datum,
396 };
397
398 assert_eq!(
399 hash_codec(&mut cx, &left).unwrap(),
400 hash_codec(&mut cx, &right).unwrap()
401 );
402 }
403}