1use crate::runtime::host::{abi_arg, abi_result};
23mod install;
24
25pub(crate) use install::declare_types;
26pub use install::{declare, install};
27
28use wasmtime::{
29 ArrayRef, ArrayRefPre, ArrayType, Caller, FieldType, Finality, Global, GlobalType, HeapType,
30 Mutability, RefType, Rooted, StorageType, Store, StructRef, StructRefPre, StructType, Val,
31 ValType,
32};
33
34use crate::runtime::StoreData;
35use crate::runtime::fuel;
36use crate::runtime::gc_singleton::{singleton_array, singleton_struct};
37use crate::runtime::host::{host_map_tombstone, host_object_vtable, write_submilli_array_struct};
38use crate::runtime::intrinsic_types::{IntrinsicTypes, intrinsic_types};
39use crate::runtime::prelude::closure::{self, Closure};
40use crate::runtime::prelude::collection::{
41 both_nan, decode_key, encode_key, is_null_key, probe_capacity, rehash_capacity,
42};
43use crate::runtime::prelude::collection::{is_a, object_field, read_array_vals, unbox_bool};
44use crate::runtime::prelude::iterator::{
45 IterKind, IteratorSource, build_iterator, iter_done, iter_yield, next_closure_type, shared_next,
46};
47use crate::runtime::prelude::keep::KeptValue;
48use crate::runtime::prelude::ledger::{
49 LedgerCursor, LedgerFields, forward_cleared, forward_rehashed,
50};
51use crate::runtime::prelude::vtable::dispatch_vtable_slot;
52
53const INITIAL_CAPACITY: i32 = 8;
56
57pub(crate) fn raw_index_array_type(engine: &wasmtime::Engine) -> wasmtime::Result<ArrayType> {
64 singleton_array(
65 engine,
66 Finality::Final,
67 FieldType::new(Mutability::Var, StorageType::ValType(ValType::I32)),
68 )
69}
70
71pub(crate) fn map_backing_struct(
77 engine: &wasmtime::Engine,
78 intr: &IntrinsicTypes,
79) -> wasmtime::Result<StructType> {
80 let imm = Mutability::Const;
81 let mutv = Mutability::Var;
82 let raw_index = raw_index_array_type(engine)?;
83 singleton_struct(
84 engine,
85 Finality::NonFinal,
86 Some(intr.object.clone()),
87 vec![
88 FieldType::new(
89 imm,
90 StorageType::ValType(ValType::Ref(RefType::new(
91 false,
92 intr.vtable.clone().into(),
93 ))),
94 ),
95 FieldType::new(
96 mutv,
97 StorageType::ValType(ValType::Ref(RefType::new(
98 false,
99 intr.raw_array.clone().into(),
100 ))),
101 ),
102 FieldType::new(
103 mutv,
104 StorageType::ValType(ValType::Ref(RefType::new(
105 false,
106 intr.raw_array.clone().into(),
107 ))),
108 ),
109 FieldType::new(mutv, StorageType::ValType(ValType::I32)),
110 FieldType::new(
111 mutv,
112 StorageType::ValType(ValType::Ref(RefType::new(false, raw_index.clone().into()))),
113 ),
114 FieldType::new(mutv, StorageType::ValType(ValType::I32)),
115 FieldType::new(
116 mutv,
117 StorageType::ValType(ValType::Ref(RefType::new(false, raw_index.clone().into()))),
118 ),
119 FieldType::new(
120 mutv,
121 StorageType::ValType(ValType::Ref(RefType::new(false, raw_index.into()))),
122 ),
123 FieldType::new(mutv, StorageType::ValType(ValType::I64)),
124 ],
125 )
126}
127
128pub(crate) fn build_tombstone(
133 store: &mut Store<StoreData>,
134 object_ty: StructType,
135 object_vtable: Global,
136) -> wasmtime::Result<Global> {
137 let vtable_val = object_vtable.get(&mut *store);
138 let pre = StructRefPre::new(&mut *store, object_ty);
139 let sentinel = StructRef::new(&mut *store, &pre, &[vtable_val])?;
140 let gty = GlobalType::new(
141 ValType::Ref(RefType::new(false, HeapType::Any)),
142 Mutability::Const,
143 );
144 Global::new(&mut *store, gty, Val::AnyRef(Some(sentinel.to_anyref())))
145}
146
147const F_KEYS: usize = 1;
149const F_VALUES: usize = 2;
150const F_SIZE: usize = 3;
151const F_ORDER: usize = 4;
152const F_ORDER_LEN: usize = 5;
153const F_HASHES: usize = 6;
154const F_ORDER_POSITIONS: usize = 7;
155
156fn backing(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<Rooted<StructRef>> {
158 let Val::AnyRef(Some(any)) = recv else {
159 return Err(wasmtime::Error::msg("Map method: receiver is null"));
160 };
161 any.as_struct(&mut *caller)?
162 .ok_or_else(|| wasmtime::Error::msg("Map method: receiver is not a $MapBacking"))
163}
164
165fn field_array(
167 caller: &mut Caller<'_, StoreData>,
168 b: &Rooted<StructRef>,
169 idx: usize,
170) -> wasmtime::Result<Rooted<ArrayRef>> {
171 match b.field(&mut *caller, idx)? {
172 Val::AnyRef(Some(a)) => a.unwrap_array(&mut *caller),
173 other => Err(wasmtime::Error::msg(format!(
174 "Map backing field {idx} is not an array: {other:?}"
175 ))),
176 }
177}
178
179fn field_i32(
181 caller: &mut Caller<'_, StoreData>,
182 b: &Rooted<StructRef>,
183 idx: usize,
184) -> wasmtime::Result<i32> {
185 match b.field(&mut *caller, idx)? {
186 Val::I32(n) => Ok(n),
187 other => Err(wasmtime::Error::msg(format!(
188 "Map backing field {idx} is not an i32: {other:?}"
189 ))),
190 }
191}
192
193fn is_tombstone(caller: &mut Caller<'_, StoreData>, slot: &Val) -> wasmtime::Result<bool> {
195 let tomb = host_map_tombstone(caller)?;
196 match (slot, &tomb) {
197 (Val::AnyRef(Some(a)), Val::AnyRef(Some(b))) => Ok(Rooted::ref_eq(&caller, a, b)?),
198 _ => Ok(false),
199 }
200}
201
202fn is_null(v: &Val) -> bool {
203 matches!(v, Val::AnyRef(None))
204}
205
206async fn hash(caller: &mut Caller<'_, StoreData>, key: &Val) -> wasmtime::Result<i32> {
208 if is_null_key(caller, key)? {
209 return Ok(0);
210 }
211 if super::undefined::is_undefined(caller, key)? {
212 return Ok(1);
213 }
214 match dispatch_vtable_slot(caller, key, 3, &[]).await? {
215 Val::I32(h) => Ok(h),
216 other => Err(wasmtime::Error::msg(format!(
217 "Map key hash returned {other:?}, expected i32"
218 ))),
219 }
220}
221
222async fn equals(
224 caller: &mut Caller<'_, StoreData>,
225 key: &Val,
226 slot: &Val,
227) -> wasmtime::Result<bool> {
228 let left_null = is_null_key(caller, key)?;
229 let right_null = is_null_key(caller, slot)?;
230 if left_null || right_null {
231 return Ok(left_null && right_null);
232 }
233 let left_undefined = super::undefined::is_undefined(caller, key)?;
234 let right_undefined = super::undefined::is_undefined(caller, slot)?;
235 if left_undefined || right_undefined {
236 return Ok(left_undefined && right_undefined);
237 }
238 if both_nan(caller, key, slot)? {
239 return Ok(true);
240 }
241 match dispatch_vtable_slot(caller, key, 2, &[*slot]).await? {
242 Val::I32(b) => Ok(b != 0),
243 other => Err(wasmtime::Error::msg(format!(
244 "Map key equals returned {other:?}, expected i32"
245 ))),
246 }
247}
248
249fn index_value(
250 caller: &mut Caller<'_, StoreData>,
251 array: &Rooted<ArrayRef>,
252 index: u32,
253) -> wasmtime::Result<i32> {
254 #[cfg(test)]
255 {
256 caller.data_mut().collection_index_reads += 1;
257 }
258 match array.get(&mut *caller, index)? {
259 Val::I32(value) => Ok(value),
260 _ => Err(crate::runtime::host::fatal_host_error(
261 "Invalid collection index/hash array",
262 )),
263 }
264}
265
266fn new_raw_array(caller: &mut Caller<'_, StoreData>, n: i32) -> wasmtime::Result<Rooted<ArrayRef>> {
268 let raw = intrinsic_types(&mut *caller)?.raw_array.clone();
269 let pre = ArrayRefPre::new(&mut *caller, raw);
270 let capacity =
271 u32::try_from(n).map_err(|_| wasmtime::Error::msg("Negative collection capacity"))?;
272 ArrayRef::new(&mut *caller, &pre, &Val::null_any_ref(), capacity)
273}
274
275fn new_index_array(
277 caller: &mut Caller<'_, StoreData>,
278 n: i32,
279) -> wasmtime::Result<Rooted<ArrayRef>> {
280 let ty = raw_index_array_type(caller.engine())?;
281 let pre = ArrayRefPre::new(&mut *caller, ty);
282 let capacity =
283 u32::try_from(n).map_err(|_| wasmtime::Error::msg("Negative collection capacity"))?;
284 ArrayRef::new(&mut *caller, &pre, &Val::I32(0), capacity)
285}
286
287pub(super) async fn get(
293 caller: &mut Caller<'_, StoreData>,
294 recv: &Val,
295 key: &Val,
296) -> wasmtime::Result<Val> {
297 let encoded_key = encode_key(caller, key)?;
298 let key = &encoded_key;
299 let b = backing(caller, recv)?;
300 let Some(index) = find_slot(caller, &b, key).await? else {
301 return super::undefined::value(caller);
302 };
303 field_array(caller, &b, F_VALUES)?.get(&mut *caller, index)
304}
305
306pub(super) async fn has(
308 caller: &mut Caller<'_, StoreData>,
309 recv: &Val,
310 key: &Val,
311) -> wasmtime::Result<bool> {
312 let encoded_key = encode_key(caller, key)?;
313 let b = backing(caller, recv)?;
314 Ok(find_slot(caller, &b, &encoded_key).await?.is_some())
315}
316
317async fn find_slot(
319 caller: &mut Caller<'_, StoreData>,
320 b: &Rooted<StructRef>,
321 key: &Val,
322) -> wasmtime::Result<Option<u32>> {
323 let key_hash = hash(caller, key).await?;
324 find_slot_hashed(caller, b, key, key_hash).await
325}
326
327async fn find_slot_hashed(
328 caller: &mut Caller<'_, StoreData>,
329 b: &Rooted<StructRef>,
330 key: &Val,
331 key_hash: i32,
332) -> wasmtime::Result<Option<u32>> {
333 let keys = field_array(caller, b, F_KEYS)?;
334 let cap = probe_capacity(keys.len(&mut *caller)?)?;
335 let hashes = field_array(caller, b, F_HASHES)?;
336 let mut i = key_hash & (cap - 1);
337 for _ in 0..cap {
338 fuel::charge(&mut *caller, fuel::ELEM, 1)?;
339 let slot = keys.get(&mut *caller, i as u32)?;
340 if is_null(&slot) {
341 return Ok(None);
342 }
343 if !is_tombstone(caller, &slot)?
344 && index_value(caller, &hashes, i as u32)? == key_hash
345 && equals(caller, key, &slot).await?
346 {
347 return Ok(Some(i as u32));
348 }
349 i = (i + 1) & (cap - 1);
350 }
351 Ok(None)
352}
353
354pub(super) async fn set(
358 caller: &mut Caller<'_, StoreData>,
359 recv: &Val,
360 key: &Val,
361 value: &Val,
362) -> wasmtime::Result<Val> {
363 let encoded_key = encode_key(caller, key)?;
364 let key = &encoded_key;
365 let b = backing(caller, recv)?;
366 let key_hash = hash(caller, key).await?;
367
368 let size = field_i32(caller, &b, F_SIZE)?;
369 let cap0 = field_array(caller, &b, F_KEYS)?.len(&mut *caller)?;
370 let order_len = field_i32(caller, &b, F_ORDER_LEN)?;
371 if let Some(capacity) = rehash_capacity(cap0, size, order_len)? {
372 if let Some(index) = find_slot_hashed(caller, &b, key, key_hash).await? {
373 let values = field_array(caller, &b, F_VALUES)?;
374 values.set(&mut *caller, index, *value)?;
375 return Ok(*recv);
376 }
377 rehash(caller, &b, capacity)?;
378 }
379
380 let keys = field_array(caller, &b, F_KEYS)?;
381 let values = field_array(caller, &b, F_VALUES)?;
382 let order = field_array(caller, &b, F_ORDER)?;
383 let order_len = field_i32(caller, &b, F_ORDER_LEN)?;
384 let cap = probe_capacity(keys.len(&mut *caller)?)?;
385
386 let hashes = field_array(caller, &b, F_HASHES)?;
387 let positions = field_array(caller, &b, F_ORDER_POSITIONS)?;
388 let mut i = key_hash & (cap - 1);
389 let mut first_tomb: i32 = -1;
390 for _ in 0..cap {
391 fuel::charge(&mut *caller, fuel::ELEM, 1)?;
392 let slot = keys.get(&mut *caller, i as u32)?;
393 if is_null(&slot) {
394 let ins = if first_tomb == -1 { i } else { first_tomb };
395 keys.set(&mut *caller, ins as u32, *key)?;
396 values.set(&mut *caller, ins as u32, *value)?;
397 order.set(&mut *caller, order_len as u32, Val::I32(ins))?;
398 hashes.set(&mut *caller, ins as u32, Val::I32(key_hash))?;
399 positions.set(&mut *caller, ins as u32, Val::I32(order_len))?;
400 b.set_field(&mut *caller, F_ORDER_LEN, Val::I32(order_len + 1))?;
401 b.set_field(&mut *caller, F_SIZE, Val::I32(size + 1))?;
402 return Ok(*recv);
403 }
404 if is_tombstone(caller, &slot)? {
405 if first_tomb == -1 {
406 first_tomb = i;
407 }
408 } else if index_value(caller, &hashes, i as u32)? == key_hash
409 && equals(caller, key, &slot).await?
410 {
411 values.set(&mut *caller, i as u32, *value)?;
412 return Ok(*recv);
413 }
414 i = (i + 1) & (cap - 1);
415 }
416 Err(wasmtime::Error::msg("Map insertion found no empty bucket"))
417}
418
419pub(super) async fn delete(
422 caller: &mut Caller<'_, StoreData>,
423 recv: &Val,
424 key: &Val,
425) -> wasmtime::Result<bool> {
426 let encoded_key = encode_key(caller, key)?;
427 let key = &encoded_key;
428 let b = backing(caller, recv)?;
429 let keys = field_array(caller, &b, F_KEYS)?;
430 let values = field_array(caller, &b, F_VALUES)?;
431 let order = field_array(caller, &b, F_ORDER)?;
432 let positions = field_array(caller, &b, F_ORDER_POSITIONS)?;
433 let Some(index) = find_slot(caller, &b, key).await? else {
434 return Ok(false);
435 };
436 let position = index_value(caller, &positions, index)?;
437 let position = u32::try_from(position).map_err(crate::runtime::host::fatal_host_error)?;
438 let size = field_i32(caller, &b, F_SIZE)?;
439 let remaining = size.checked_sub(1).filter(|n| *n >= 0).ok_or_else(|| {
440 crate::runtime::host::fatal_host_error("Invalid collection size on deletion")
441 })?;
442 let tomb = host_map_tombstone(caller)?;
443 keys.set(&mut *caller, index, tomb)?;
444 values.set(&mut *caller, index, Val::null_any_ref())?;
445 order.set(&mut *caller, position, Val::I32(-1))?;
446 b.set_field(&mut *caller, F_SIZE, Val::I32(remaining))?;
447 Ok(true)
448}
449
450pub(super) fn size(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<Val> {
453 let b = backing(caller, recv)?;
454 let n = field_i32(caller, &b, F_SIZE)?;
455 Ok(Val::F64((n as f64).to_bits()))
456}
457
458pub(super) fn clear(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<()> {
459 let b = backing(caller, recv)?;
460 let old_keys = field_array(caller, &b, F_KEYS)?;
461 let old_order = field_array(caller, &b, F_ORDER)?;
462 let old_order_len = field_i32(caller, &b, F_ORDER_LEN)?;
463 let keys = new_raw_array(caller, INITIAL_CAPACITY)?;
464 let values = new_raw_array(caller, INITIAL_CAPACITY)?;
465 let order = new_index_array(caller, INITIAL_CAPACITY)?;
466 let hashes = new_index_array(caller, INITIAL_CAPACITY)?;
467 let positions = new_index_array(caller, INITIAL_CAPACITY)?;
468 b.set_field(&mut *caller, F_KEYS, Val::AnyRef(Some(keys.to_anyref())))?;
469 b.set_field(
470 &mut *caller,
471 F_VALUES,
472 Val::AnyRef(Some(values.to_anyref())),
473 )?;
474 b.set_field(&mut *caller, F_SIZE, Val::I32(0))?;
475 b.set_field(&mut *caller, F_ORDER, Val::AnyRef(Some(order.to_anyref())))?;
476 b.set_field(&mut *caller, F_ORDER_LEN, Val::I32(0))?;
477 b.set_field(
478 &mut *caller,
479 F_HASHES,
480 Val::AnyRef(Some(hashes.to_anyref())),
481 )?;
482 b.set_field(
483 &mut *caller,
484 F_ORDER_POSITIONS,
485 Val::AnyRef(Some(positions.to_anyref())),
486 )?;
487 forward_cleared(caller, &old_keys, &old_order, old_order_len, &keys, &order)
488}
489
490fn rehash(
493 caller: &mut Caller<'_, StoreData>,
494 b: &Rooted<StructRef>,
495 new_cap: i32,
496) -> wasmtime::Result<()> {
497 let old_keys = field_array(caller, b, F_KEYS)?;
498 let old_vals = field_array(caller, b, F_VALUES)?;
499 let old_order = field_array(caller, b, F_ORDER)?;
500 let old_hashes = field_array(caller, b, F_HASHES)?;
501 let old_order_len = field_i32(caller, b, F_ORDER_LEN)?;
502 fuel::charge(
504 &mut *caller,
505 fuel::ELEM,
506 (new_cap as u64)
507 .saturating_mul(5)
508 .saturating_add(old_order_len as u64),
509 )?;
510
511 let new_keys = new_raw_array(caller, new_cap)?;
512 let new_vals = new_raw_array(caller, new_cap)?;
513 let new_order = new_index_array(caller, new_cap)?;
514 let new_hashes = new_index_array(caller, new_cap)?;
515 let new_positions = new_index_array(caller, new_cap)?;
516 let mut new_order_len: i32 = 0;
517
518 for o in 0..old_order_len {
519 let Val::I32(probe_idx) = old_order.get(&mut *caller, o as u32)? else {
520 return Err(wasmtime::Error::msg("Invalid collection ledger entry"));
521 };
522 if probe_idx == -1 {
523 continue;
524 }
525 let key = old_keys.get(&mut *caller, probe_idx as u32)?;
526 let val = old_vals.get(&mut *caller, probe_idx as u32)?;
527 let key_hash = index_value(caller, &old_hashes, probe_idx as u32)?;
528 let mut k = key_hash & (new_cap - 1);
529 let mut inserted = false;
530 for _ in 0..new_cap {
531 fuel::charge(&mut *caller, fuel::ELEM, 1)?;
532 if is_null(&new_keys.get(&mut *caller, k as u32)?) {
533 new_keys.set(&mut *caller, k as u32, key)?;
534 new_vals.set(&mut *caller, k as u32, val)?;
535 new_order.set(&mut *caller, new_order_len as u32, Val::I32(k))?;
536 new_hashes.set(&mut *caller, k as u32, Val::I32(key_hash))?;
537 new_positions.set(&mut *caller, k as u32, Val::I32(new_order_len))?;
538 new_order_len += 1;
539 inserted = true;
540 break;
541 }
542 k = (k + 1) & (new_cap - 1);
543 }
544 if !inserted {
545 return Err(wasmtime::Error::msg("Map rehash found no empty bucket"));
546 }
547 }
548
549 b.set_field(
550 &mut *caller,
551 F_KEYS,
552 Val::AnyRef(Some(new_keys.to_anyref())),
553 )?;
554 b.set_field(
555 &mut *caller,
556 F_VALUES,
557 Val::AnyRef(Some(new_vals.to_anyref())),
558 )?;
559 b.set_field(
560 &mut *caller,
561 F_ORDER,
562 Val::AnyRef(Some(new_order.to_anyref())),
563 )?;
564 b.set_field(&mut *caller, F_ORDER_LEN, Val::I32(new_order_len))?;
565 b.set_field(
566 &mut *caller,
567 F_HASHES,
568 Val::AnyRef(Some(new_hashes.to_anyref())),
569 )?;
570 b.set_field(
571 &mut *caller,
572 F_ORDER_POSITIONS,
573 Val::AnyRef(Some(new_positions.to_anyref())),
574 )?;
575 forward_rehashed(caller, &old_keys, &new_keys, &new_order)
576}
577
578const LEDGER: LedgerFields = LedgerFields {
584 entries: F_KEYS,
585 order: F_ORDER,
586 order_len: F_ORDER_LEN,
587};
588
589pub(super) async fn for_each(
593 caller: &mut Caller<'_, StoreData>,
594 recv: &Val,
595 f: &Closure,
596) -> wasmtime::Result<()> {
597 let b = backing(caller, recv)?;
598 let stored = LedgerCursor::start(caller, b, LEDGER)?.to_val(caller)?;
601 loop {
602 let mut cursor = LedgerCursor::from_val(caller, &stored)?;
603 let Some(bucket) = cursor.next_bucket(caller, LEDGER)? else {
604 return Ok(());
605 };
606 cursor.store(caller, &stored)?;
607 let (key, value) = map_entry(caller, &cursor, bucket)?;
608 f.call_dynamic(caller, &[value, key, *recv]).await?;
609 }
610}
611
612fn map_entry(
614 caller: &mut Caller<'_, StoreData>,
615 cursor: &LedgerCursor,
616 bucket: u32,
617) -> wasmtime::Result<(Val, Val)> {
618 let keys = cursor.current_array(caller, F_KEYS)?;
619 let values = cursor.current_array(caller, F_VALUES)?;
620 let key = keys.get(&mut *caller, bucket)?;
621 let key = decode_key(caller, key)?;
622 let value = values.get(&mut *caller, bucket)?;
623 Ok((key, value))
624}
625
626fn make_map_iterator(
629 caller: &mut Caller<'_, StoreData>,
630 recv: &Val,
631 kind: IterKind,
632) -> wasmtime::Result<Val> {
633 let b = backing(caller, recv)?;
634 let cursor = LedgerCursor::start(caller, b, LEDGER)?.to_val(caller)?;
635
636 let intr = intrinsic_types(&mut *caller)?;
637 let (next_ty, next_struct) = next_closure_type(caller.engine(), &intr)?;
638 let next = shared_next(
639 caller,
640 IteratorSource::Map,
641 kind,
642 next_ty,
643 move |caller, params, results| map_next_step(caller, params, results, kind),
644 )?;
645 build_iterator(caller, next_struct, next, cursor)
646}
647
648fn map_next_step(
651 caller: &mut Caller<'_, StoreData>,
652 params: &[Val],
653 results: &mut [Val],
654 kind: IterKind,
655) -> wasmtime::Result<()> {
656 let stored = abi_arg(params, 0)?;
657 let mut cursor = LedgerCursor::from_val(caller, stored)?;
658 let bucket = cursor.next_bucket(caller, LEDGER)?;
659 cursor.store(caller, stored)?;
660 let Some(bucket) = bucket else {
661 *abi_result(results, 0)? = iter_done(caller)?;
662 return Ok(());
663 };
664 let (key, value) = map_entry(caller, &cursor, bucket)?;
665 let yielded = match kind {
666 IterKind::Keys => key,
667 IterKind::Values => value,
668 IterKind::Entries => {
669 let pair = write_submilli_array_struct(caller, &[key, value])?;
670 Val::AnyRef(Some(pair.to_anyref()))
671 }
672 };
673 *abi_result(results, 0)? = iter_yield(caller, yielded)?;
674 Ok(())
675}
676
677pub(super) fn keys(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<Val> {
678 make_map_iterator(caller, recv, IterKind::Keys)
679}
680
681pub(super) fn values(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<Val> {
682 make_map_iterator(caller, recv, IterKind::Values)
683}
684
685pub(super) fn entries(caller: &mut Caller<'_, StoreData>, recv: &Val) -> wasmtime::Result<Val> {
688 make_map_iterator(caller, recv, IterKind::Entries)
689}
690
691pub(super) async fn construct(
701 caller: &mut Caller<'_, StoreData>,
702 init: &Val,
703) -> wasmtime::Result<Val> {
704 let coll = build_empty(caller)?;
705 if super::undefined::is_nullish(caller, init)? {
706 return Ok(coll);
707 }
708
709 let intr = intrinsic_types(&mut *caller)?;
710 if is_a(caller, init, &intr.array)? {
711 for entry in read_array_vals(caller, init)? {
712 let (k, v) = read_pair(caller, &entry)?;
713 set(caller, &coll, &k, &v).await?;
714 }
715 return Ok(coll);
716 }
717
718 let kept_iterator = KeptValue::new(caller)?;
720 let kept_result = KeptValue::new(caller)?;
721
722 let it = if is_a(caller, init, &map_backing_struct(caller.engine(), &intr)?)? {
723 entries(caller, init)?
724 } else if let Some(iter_method) = object_field(caller, init, "iterator")? {
725 let c = closure::read(caller, &iter_method, "Map ctor iterable")?;
726 c.call(caller, &[]).await?
727 } else {
728 *init
729 };
730 kept_iterator.set(caller, it)?;
731
732 let next = object_field(caller, &it, "next")?
733 .ok_or_else(|| wasmtime::Error::msg("Map ctor: initializer is not iterable"))?;
734 let next_closure = closure::read(caller, &next, "Map ctor iterator")?;
735 loop {
736 let result = next_closure.call(caller, &[]).await?;
737 kept_result.set(caller, result)?;
738 let done = object_field(caller, &result, "done")?
739 .ok_or_else(|| wasmtime::Error::msg("Map ctor: iterator result missing `done`"))?;
740 if unbox_bool(caller, &done)? {
741 break;
742 }
743 let value = object_field(caller, &result, "value")?
744 .ok_or_else(|| wasmtime::Error::msg("Map ctor: iterator result missing `value`"))?;
745 let (k, v) = read_pair(caller, &value)?;
746 set(caller, &coll, &k, &v).await?;
747 }
748 Ok(coll)
749}
750
751pub(crate) async fn string_map_from_pairs(
754 caller: &mut Caller<'_, StoreData>,
755 pairs: &[(String, String)],
756) -> wasmtime::Result<Val> {
757 host_string_map_from_pairs(caller, pairs)
758}
759
760pub(crate) fn host_string_map_from_pairs(
763 caller: &mut Caller<'_, StoreData>,
764 pairs: &[(String, String)],
765) -> wasmtime::Result<Val> {
766 let capacity = pairs
767 .len()
768 .checked_mul(2)
769 .and_then(usize::checked_next_power_of_two)
770 .and_then(|capacity| i32::try_from(capacity.max(INITIAL_CAPACITY as usize)).ok())
771 .ok_or_else(|| {
772 crate::runtime::host::range_error("host string map capacity limit exceeded")
773 })?;
774 let map = build_empty(caller)?;
775 let backing = backing(caller, &map)?;
776 if capacity > INITIAL_CAPACITY {
777 rehash(caller, &backing, capacity)?;
778 }
779 for (key, value) in pairs {
780 let _native = crate::runtime::limits::HostBytes::new(
781 &caller.data().tenant_limits,
782 (key.len() as u64).saturating_mul(8),
783 )?;
784 let mut units = Vec::new();
785 units
786 .try_reserve_exact(key.len())
787 .map_err(crate::runtime::host::fatal_host_error)?;
788 units.extend(key.encode_utf16());
789 let key = crate::runtime::host::write_submilli_string_struct(caller, key)?;
790 let value = crate::runtime::host::write_submilli_string_struct(caller, value)?;
791 insert_host_string(
792 caller,
793 &backing,
794 &units,
795 Val::AnyRef(Some(key.to_anyref())),
796 Val::AnyRef(Some(value.to_anyref())),
797 )?;
798 }
799 Ok(map)
800}
801
802fn insert_host_string(
803 caller: &mut Caller<'_, StoreData>,
804 backing: &Rooted<StructRef>,
805 units: &[u16],
806 key: Val,
807 value: Val,
808) -> wasmtime::Result<()> {
809 let keys = field_array(caller, backing, F_KEYS)?;
810 let values = field_array(caller, backing, F_VALUES)?;
811 let hashes = field_array(caller, backing, F_HASHES)?;
812 let capacity = probe_capacity(keys.len(&mut *caller)?)?;
813 let hash = super::vtable::string_hash(caller, &key)? as i32;
814 let mut bucket = hash & (capacity - 1);
815 for _ in 0..capacity {
816 fuel::charge(&mut *caller, fuel::ELEM, 1)?;
817 let slot = keys.get(&mut *caller, bucket as u32)?;
818 if is_null(&slot) {
819 let size = field_i32(caller, backing, F_SIZE)?;
820 let order = field_array(caller, backing, F_ORDER)?;
821 let positions = field_array(caller, backing, F_ORDER_POSITIONS)?;
822 keys.set(&mut *caller, bucket as u32, key)?;
823 values.set(&mut *caller, bucket as u32, value)?;
824 hashes.set(&mut *caller, bucket as u32, Val::I32(hash))?;
825 order.set(&mut *caller, size as u32, Val::I32(bucket))?;
826 positions.set(&mut *caller, bucket as u32, Val::I32(size))?;
827 backing.set_field(&mut *caller, F_SIZE, Val::I32(size + 1))?;
828 backing.set_field(&mut *caller, F_ORDER_LEN, Val::I32(size + 1))?;
829 return Ok(());
830 }
831 if index_value(caller, &hashes, bucket as u32)? == hash {
832 let existing = super::vtable::read_string_units(caller, &slot, "host map key")?;
833 fuel::charge(&mut *caller, fuel::SCAN, units.len() as u64)?;
834 if existing == units {
835 values.set(&mut *caller, bucket as u32, value)?;
836 return Ok(());
837 }
838 }
839 bucket = (bucket + 1) & (capacity - 1);
840 }
841 Err(crate::runtime::host::fatal_host_error(
842 "host string map has no empty bucket",
843 ))
844}
845
846pub(crate) fn string_entries(
849 caller: &mut Caller<'_, StoreData>,
850 map: &Val,
851) -> wasmtime::Result<Vec<(String, String)>> {
852 let b = backing(caller, map)?;
853 let keys = field_array(caller, &b, F_KEYS)?;
854 let values = field_array(caller, &b, F_VALUES)?;
855 let order = field_array(caller, &b, F_ORDER)?;
856 let order_len = field_i32(caller, &b, F_ORDER_LEN)?;
857 let mut out = Vec::new();
858 for o in 0..order_len {
859 let Val::I32(idx) = order.get(&mut *caller, o as u32)? else {
860 continue;
861 };
862 if idx == -1 {
863 continue;
864 }
865 let key = keys.get(&mut *caller, idx as u32)?;
866 let key = decode_key(caller, key)?;
867 let value = values.get(&mut *caller, idx as u32)?;
868 out.push((
869 crate::runtime::host::read_string_arg(caller, &key, "map entry key")?,
870 crate::runtime::host::read_string_arg(caller, &value, "map entry value")?,
871 ));
872 }
873 Ok(out)
874}
875
876fn build_empty(caller: &mut Caller<'_, StoreData>) -> wasmtime::Result<Val> {
878 let intr = intrinsic_types(&mut *caller)?;
879 let ty = map_backing_struct(caller.engine(), &intr)?;
880 let vtable = host_object_vtable(caller)?;
881 let keys = new_raw_array(caller, INITIAL_CAPACITY)?;
882 let values = new_raw_array(caller, INITIAL_CAPACITY)?;
883 let order = new_index_array(caller, INITIAL_CAPACITY)?;
884 let hashes = new_index_array(caller, INITIAL_CAPACITY)?;
885 let positions = new_index_array(caller, INITIAL_CAPACITY)?;
886 let pre = StructRefPre::new(&mut *caller, ty);
887 let st = StructRef::new(
888 &mut *caller,
889 &pre,
890 &[
891 vtable,
892 Val::AnyRef(Some(keys.to_anyref())),
893 Val::AnyRef(Some(values.to_anyref())),
894 Val::I32(0),
895 Val::AnyRef(Some(order.to_anyref())),
896 Val::I32(0),
897 Val::AnyRef(Some(hashes.to_anyref())),
898 Val::AnyRef(Some(positions.to_anyref())),
899 Val::I64(0),
900 ],
901 )?;
902 Ok(Val::AnyRef(Some(st.to_anyref())))
903}
904
905fn read_pair(caller: &mut Caller<'_, StoreData>, tuple: &Val) -> wasmtime::Result<(Val, Val)> {
907 let storage = crate::runtime::array_storage::ArrayStorage::read(caller, tuple)?;
908 if storage.len < 2 {
909 return Err(crate::runtime::host::type_error(
910 "Map ctor: entry needs a key and value",
911 ));
912 }
913 Ok((
914 storage.backing.get(&mut *caller, 0)?,
915 storage.backing.get(&mut *caller, 1)?,
916 ))
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922 use crate::codegen::intrinsics::declare_intrinsic_types;
923 use crate::runtime::intrinsic_types::build_intrinsic_types;
924 use wasm_encoder::{
925 ConstExpr, ExportKind, ExportSection, GlobalSection, GlobalType as EncGlobalType,
926 HeapType as EncHeapType, Module, RefType as EncRefType, TypeSection, ValType as EncValType,
927 };
928 use wasmtime::{Config, Engine};
929
930 #[test]
934 fn map_backing_matches_codegen() {
935 let mut config = Config::new();
936 config.wasm_gc(true);
937 config.wasm_function_references(true);
938 let engine = Engine::new(&config).unwrap();
939
940 let intr = build_intrinsic_types(&engine).unwrap();
941 let host = map_backing_struct(&engine, &intr).unwrap();
942
943 let mut module = Module::new();
944 let mut types = TypeSection::new();
945 let idx = declare_intrinsic_types(&mut types);
946 let backing_idx = idx.map;
947 module.section(&types);
948
949 let mut globals = GlobalSection::new();
950 globals.global(
951 EncGlobalType {
952 val_type: EncValType::Ref(EncRefType {
953 nullable: true,
954 heap_type: EncHeapType::Concrete(backing_idx),
955 }),
956 mutable: false,
957 shared: false,
958 },
959 &ConstExpr::ref_null(EncHeapType::Concrete(backing_idx)),
960 );
961 let mut exports = ExportSection::new();
962 exports.export("backing", ExportKind::Global, 0);
963 module.section(&globals);
964 module.section(&exports);
965
966 let module = wasmtime::Module::new(&engine, module.finish()).unwrap();
967 let recovered = module
968 .get_export("backing")
969 .unwrap()
970 .global()
971 .unwrap()
972 .content()
973 .as_ref()
974 .map(|r| r.heap_type().clone())
975 .unwrap()
976 .as_concrete_struct()
977 .unwrap()
978 .clone();
979 assert!(StructType::eq(&host, &recovered));
980 }
981}