arb_stylus/
middleware.rs

1use std::{collections::HashMap, sync::RwLock};
2
3// Use the wasmparser bundled with wasmer-compiler so the `Operator` type matches the
4// `MiddlewareReaderState` API. Other crates may pull a different wasmparser version
5// transitively; routing through wasmer's own export keeps the middleware sound.
6use wasmer_compiler::wasmparser::{BlockType, Operator, ValType};
7use wasmer_compiler::{FunctionMiddleware, MiddlewareReaderState, ModuleMiddleware};
8use wasmer_types::{
9    ExportIndex, FunctionIndex, FunctionType, GlobalIndex, GlobalInit, ImportIndex,
10    LocalFunctionIndex, MiddlewareError, ModuleInfo, SignatureIndex, Type,
11};
12
13use crate::meter::{STYLUS_ENTRY_POINT, STYLUS_INK_LEFT, STYLUS_INK_STATUS, STYLUS_STACK_LEFT};
14
15const SCRATCH_GLOBAL: &str = "stylus_scratch_global";
16
17fn mw_err(msg: impl Into<String>) -> MiddlewareError {
18    MiddlewareError::new("stylus", msg.into())
19}
20
21// ── StartMover ──────────────────────────────────────────────────────
22//
23// Renames the WASM start function to "stylus_start" so it doesn't run at
24// module instantiation, then drops all exports except the allowed whitelist.
25// Must run before the metering middleware.
26
27const STYLUS_START: &str = "stylus_start";
28
29#[derive(Debug)]
30pub struct StartMover {
31    debug: bool,
32}
33
34impl StartMover {
35    pub fn new(debug: bool) -> Self {
36        Self { debug }
37    }
38}
39
40impl ModuleMiddleware for StartMover {
41    fn transform_module_info(&self, info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
42        let exports_before = info.exports.len();
43
44        let had_start = if let Some(start) = info.start_function.take() {
45            if info.exports.contains_key(STYLUS_START) {
46                return Err(mw_err(format!("function {STYLUS_START} already exists")));
47            }
48            info.exports
49                .insert(STYLUS_START.to_owned(), ExportIndex::Function(start));
50            info.function_names.insert(start, STYLUS_START.to_owned());
51            true
52        } else {
53            false
54        };
55
56        if had_start && !self.debug {
57            return Err(mw_err("start functions not allowed"));
58        }
59
60        if !self.debug {
61            // Drop all exports except the whitelist (entry point, start, memory).
62            info.exports.retain(|name, export| match name.as_str() {
63                STYLUS_ENTRY_POINT => matches!(export, ExportIndex::Function(_)),
64                STYLUS_START => matches!(export, ExportIndex::Function(_)),
65                "memory" => matches!(export, ExportIndex::Memory(_)),
66                _ => false,
67            });
68            info.function_names.clear();
69        }
70        tracing::debug!(target: "stylus",
71            had_start, exports_before, exports_after = info.exports.len(),
72            "StartMover applied");
73        Ok(())
74    }
75
76    fn generate_function_middleware<'a>(
77        &self,
78        _: LocalFunctionIndex,
79    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
80        Box::new(NoopFunctionMiddleware)
81    }
82}
83
84#[derive(Debug)]
85struct NoopFunctionMiddleware;
86
87impl<'a> FunctionMiddleware<'a> for NoopFunctionMiddleware {
88    fn feed(
89        &mut self,
90        op: Operator<'a>,
91        state: &mut MiddlewareReaderState<'a>,
92    ) -> Result<(), MiddlewareError> {
93        state.push_operator(op);
94        Ok(())
95    }
96}
97
98// ── InkMeter ────────────────────────────────────────────────────────
99
100#[derive(Debug)]
101pub struct InkMeter {
102    header_cost: u64,
103    globals: RwLock<Option<[GlobalIndex; 2]>>,
104    sigs: RwLock<HashMap<u32, usize>>,
105}
106
107impl InkMeter {
108    pub fn new(header_cost: u64) -> Self {
109        Self {
110            header_cost,
111            globals: RwLock::new(None),
112            sigs: RwLock::new(HashMap::new()),
113        }
114    }
115
116    fn globals(&self) -> [GlobalIndex; 2] {
117        self.globals
118            .read()
119            .expect("ink globals lock poisoned")
120            .expect("missing ink globals")
121    }
122}
123
124impl ModuleMiddleware for InkMeter {
125    fn transform_module_info(&self, info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
126        let ink_ty = wasmer_types::GlobalType::new(Type::I64, wasmer_types::Mutability::Var);
127        let status_ty = wasmer_types::GlobalType::new(Type::I32, wasmer_types::Mutability::Var);
128
129        let ink_idx = info.globals.push(ink_ty);
130        let status_idx = info.globals.push(status_ty);
131        info.global_initializers.push(GlobalInit::I64Const(0));
132        info.global_initializers.push(GlobalInit::I32Const(0));
133
134        info.exports.insert(
135            STYLUS_INK_LEFT.to_string(),
136            wasmer_types::ExportIndex::Global(ink_idx),
137        );
138        info.exports.insert(
139            STYLUS_INK_STATUS.to_string(),
140            wasmer_types::ExportIndex::Global(status_idx),
141        );
142
143        let mut sig_map = self.sigs.write().expect("ink sigs lock poisoned");
144        for (sig_idx, sig) in info.signatures.iter() {
145            sig_map.insert(sig_idx.as_u32(), sig.params().len());
146        }
147
148        *self.globals.write().expect("ink globals lock poisoned") = Some([ink_idx, status_idx]);
149        Ok(())
150    }
151
152    fn generate_function_middleware<'a>(
153        &self,
154        _: LocalFunctionIndex,
155    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
156        let [ink, status] = self.globals();
157        let sigs = self.sigs.read().expect("ink sigs lock poisoned").clone();
158        Box::new(InkMeterFn {
159            ink_global: ink,
160            status_global: status,
161            block: vec![],
162            block_cost: 0,
163            header_cost: self.header_cost,
164            sigs,
165        })
166    }
167}
168
169#[derive(Debug)]
170struct InkMeterFn {
171    ink_global: GlobalIndex,
172    status_global: GlobalIndex,
173    block: Vec<Operator<'static>>,
174    block_cost: u64,
175    header_cost: u64,
176    sigs: HashMap<u32, usize>,
177}
178
179fn ends_basic_block(op: &Operator) -> bool {
180    use Operator::*;
181    matches!(
182        op,
183        End | Else
184            | Return
185            | Loop { .. }
186            | Br { .. }
187            | BrTable { .. }
188            | BrIf { .. }
189            | If { .. }
190            | Call { .. }
191            | CallIndirect { .. }
192    )
193}
194
195impl<'a> FunctionMiddleware<'a> for InkMeterFn {
196    fn feed(
197        &mut self,
198        op: Operator<'a>,
199        state: &mut MiddlewareReaderState<'a>,
200    ) -> Result<(), MiddlewareError> {
201        let end = ends_basic_block(&op);
202        let op_cost = opcode_ink_cost(&op, &self.sigs);
203        let mut cost = self.block_cost.saturating_add(op_cost);
204        self.block_cost = cost;
205
206        // SAFETY: wasmparser's `Operator<'a>` borrows from the WASM byte
207        // buffer for variants like `BrTable`. `FunctionMiddleware<'a>` is
208        // implemented on a `'static` struct, so the `'a` from `feed()`
209        // cannot be propagated into `self.block` — we transmute to
210        // `'static` for buffering and back to `'a` immediately before
211        // re-emitting via `push_operator` inside the same `feed()` call,
212        // which runs while the source bytes are still live.
213        let op_static = unsafe { std::mem::transmute::<Operator<'a>, Operator<'static>>(op) };
214        self.block.push(op_static);
215
216        if end {
217            let ink = self.ink_global.as_u32();
218            let status = self.status_global.as_u32();
219            cost = cost.saturating_add(self.header_cost);
220
221            state.push_operator(Operator::GlobalGet { global_index: ink });
222            state.push_operator(Operator::I64Const { value: cost as i64 });
223            state.push_operator(Operator::I64LtU);
224            state.push_operator(Operator::If {
225                blockty: BlockType::Empty,
226            });
227            state.push_operator(Operator::I32Const { value: 1 });
228            state.push_operator(Operator::GlobalSet {
229                global_index: status,
230            });
231            state.push_operator(Operator::Unreachable);
232            state.push_operator(Operator::End);
233
234            state.push_operator(Operator::GlobalGet { global_index: ink });
235            state.push_operator(Operator::I64Const { value: cost as i64 });
236            state.push_operator(Operator::I64Sub);
237            state.push_operator(Operator::GlobalSet { global_index: ink });
238
239            for buffered in self.block.drain(..) {
240                // SAFETY: reverses the `'a` -> `'static` transmute above
241                // within the same `feed()` call; the source WASM bytes
242                // owning any borrowed slice are still live.
243                let op_a =
244                    unsafe { std::mem::transmute::<Operator<'static>, Operator<'a>>(buffered) };
245                state.push_operator(op_a);
246            }
247            self.block_cost = 0;
248        }
249        Ok(())
250    }
251}
252
253// ── DynamicMeter ────────────────────────────────────────────────────
254
255#[derive(Debug)]
256pub struct DynamicMeter {
257    memory_fill_ink: u64,
258    memory_copy_ink: u64,
259    globals: RwLock<Option<[GlobalIndex; 3]>>,
260}
261
262impl DynamicMeter {
263    pub fn new(memory_fill_ink: u64, memory_copy_ink: u64) -> Self {
264        Self {
265            memory_fill_ink,
266            memory_copy_ink,
267            globals: RwLock::new(None),
268        }
269    }
270}
271
272impl ModuleMiddleware for DynamicMeter {
273    fn transform_module_info(&self, info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
274        let ink_idx = info
275            .exports
276            .get(STYLUS_INK_LEFT)
277            .and_then(|e| match e {
278                wasmer_types::ExportIndex::Global(g) => Some(*g),
279                _ => None,
280            })
281            .ok_or_else(|| mw_err("ink global not found"))?;
282
283        let status_idx = info
284            .exports
285            .get(STYLUS_INK_STATUS)
286            .and_then(|e| match e {
287                wasmer_types::ExportIndex::Global(g) => Some(*g),
288                _ => None,
289            })
290            .ok_or_else(|| mw_err("ink status global not found"))?;
291
292        let scratch_ty = wasmer_types::GlobalType::new(Type::I32, wasmer_types::Mutability::Var);
293        let scratch_idx = info.globals.push(scratch_ty);
294        info.global_initializers.push(GlobalInit::I32Const(0));
295        info.exports.insert(
296            SCRATCH_GLOBAL.to_string(),
297            wasmer_types::ExportIndex::Global(scratch_idx),
298        );
299
300        *self.globals.write().expect("dynamic meter lock poisoned") =
301            Some([ink_idx, status_idx, scratch_idx]);
302        Ok(())
303    }
304
305    fn generate_function_middleware<'a>(
306        &self,
307        _: LocalFunctionIndex,
308    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
309        let globals = self
310            .globals
311            .read()
312            .expect("dynamic meter lock poisoned")
313            .expect("missing dynamic globals");
314        Box::new(DynamicMeterFn {
315            memory_fill_ink: self.memory_fill_ink,
316            memory_copy_ink: self.memory_copy_ink,
317            globals,
318        })
319    }
320}
321
322#[derive(Debug)]
323struct DynamicMeterFn {
324    memory_fill_ink: u64,
325    memory_copy_ink: u64,
326    globals: [GlobalIndex; 3],
327}
328
329impl<'a> FunctionMiddleware<'a> for DynamicMeterFn {
330    fn feed(
331        &mut self,
332        op: Operator<'a>,
333        state: &mut MiddlewareReaderState<'a>,
334    ) -> Result<(), MiddlewareError> {
335        use Operator::*;
336
337        let [ink, status, scratch] = self.globals.map(|x| x.as_u32());
338        let blockty = BlockType::Empty;
339
340        let coefficient = match &op {
341            MemoryFill { .. } => Some(self.memory_fill_ink as i64),
342            MemoryCopy { .. } => Some(self.memory_copy_ink as i64),
343            _ => None,
344        };
345
346        if let Some(coeff) = coefficient {
347            // Stack has [dest, val/src, size]. Save size to scratch, compute cost,
348            // subtract from ink with overflow check, restore size.
349            state.extend([
350                GlobalSet {
351                    global_index: scratch,
352                },
353                GlobalGet { global_index: ink },
354                GlobalGet { global_index: ink },
355                GlobalGet {
356                    global_index: scratch,
357                },
358                I64ExtendI32U,
359                I64Const { value: coeff },
360                I64Mul,
361                I64Sub,
362                GlobalSet { global_index: ink },
363                GlobalGet { global_index: ink },
364                I64LtU,
365                If { blockty },
366                I32Const { value: 1 },
367                GlobalSet {
368                    global_index: status,
369                },
370                Unreachable,
371                End,
372                GlobalGet {
373                    global_index: scratch,
374                },
375            ]);
376        }
377
378        state.push_operator(op);
379        Ok(())
380    }
381}
382
383// ── DepthChecker ────────────────────────────────────────────────────
384
385type FuncMap = HashMap<FunctionIndex, FunctionType>;
386type SigMap = HashMap<SignatureIndex, FunctionType>;
387
388#[derive(Debug)]
389pub struct DepthChecker {
390    frame_limit: u32,
391    frame_contention: u16,
392    global: RwLock<Option<GlobalIndex>>,
393    funcs: RwLock<Option<FuncMap>>,
394    sigs: RwLock<Option<SigMap>>,
395}
396
397impl DepthChecker {
398    pub fn new(frame_limit: u32, frame_contention: u16) -> Self {
399        Self {
400            frame_limit,
401            frame_contention,
402            global: RwLock::new(None),
403            funcs: RwLock::new(None),
404            sigs: RwLock::new(None),
405        }
406    }
407}
408
409impl ModuleMiddleware for DepthChecker {
410    fn transform_module_info(&self, info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
411        let ty = wasmer_types::GlobalType::new(Type::I32, wasmer_types::Mutability::Var);
412        let idx = info.globals.push(ty);
413        info.global_initializers.push(GlobalInit::I32Const(0));
414        info.exports.insert(
415            STYLUS_STACK_LEFT.to_string(),
416            wasmer_types::ExportIndex::Global(idx),
417        );
418
419        let mut funcs = HashMap::new();
420        for (func_idx, sig_idx) in info.functions.iter() {
421            if let Some(sig) = info.signatures.get(*sig_idx) {
422                funcs.insert(func_idx, sig.clone());
423            }
424        }
425        let mut sigs = HashMap::new();
426        for (sig_idx, sig) in info.signatures.iter() {
427            sigs.insert(sig_idx, sig.clone());
428        }
429
430        *self.global.write().expect("depth checker lock poisoned") = Some(idx);
431        *self.funcs.write().expect("depth checker lock poisoned") = Some(funcs);
432        *self.sigs.write().expect("depth checker lock poisoned") = Some(sigs);
433        Ok(())
434    }
435
436    fn generate_function_middleware<'a>(
437        &self,
438        _: LocalFunctionIndex,
439    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
440        let g = self
441            .global
442            .read()
443            .expect("depth checker lock poisoned")
444            .expect("missing depth global");
445        let funcs = self
446            .funcs
447            .read()
448            .expect("depth checker lock poisoned")
449            .clone()
450            .expect("missing funcs");
451        let sigs = self
452            .sigs
453            .read()
454            .expect("depth checker lock poisoned")
455            .clone()
456            .expect("missing sigs");
457        Box::new(DepthCheckerFn {
458            global: g,
459            funcs,
460            sigs,
461            locals: None,
462            frame_limit: self.frame_limit,
463            frame_contention: self.frame_contention,
464            scopes: 1,
465            code: vec![],
466            done: false,
467        })
468    }
469}
470
471#[derive(Debug)]
472struct DepthCheckerFn {
473    global: GlobalIndex,
474    funcs: FuncMap,
475    sigs: SigMap,
476    locals: Option<usize>,
477    frame_limit: u32,
478    frame_contention: u16,
479    scopes: isize,
480    code: Vec<Operator<'static>>,
481    done: bool,
482}
483
484impl DepthCheckerFn {
485    #[rustfmt::skip]
486    fn worst_case_depth(&self) -> Result<u32, MiddlewareError> {
487        use Operator::*;
488
489        let mut worst: u32 = 0;
490        let mut stack: u32 = 0;
491
492        macro_rules! push {
493            ($count:expr_2021) => {{ stack += $count; worst = worst.max(stack); }};
494            () => { push!(1) };
495        }
496        macro_rules! pop {
497            ($count:expr_2021) => {{ stack = stack.saturating_sub($count); }};
498            () => { pop!(1) };
499        }
500        macro_rules! ins_and_outs {
501            ($ty:expr_2021) => {{
502                let ins = $ty.params().len() as u32;
503                let outs = $ty.results().len() as u32;
504                push!(outs);
505                pop!(ins);
506            }};
507        }
508        macro_rules! op {
509            ($first:ident $(,$opcode:ident)* $(,)?) => {
510                $first $(| $opcode)*
511            };
512        }
513        macro_rules! dot {
514            ($first:ident $(,$opcode:ident)* $(,)?) => {
515                $first { .. } $(| $opcode { .. })*
516            };
517        }
518        macro_rules! block_type {
519            ($ty:expr_2021) => {{
520                match $ty {
521                    BlockType::Empty => {}
522                    BlockType::Type(_) => push!(1),
523                    BlockType::FuncType(id) => {
524                        let index = SignatureIndex::from_u32(*id);
525                        let Some(ty) = self.sigs.get(&index) else {
526                            return Err(mw_err(format!("missing type for func {id}")));
527                        };
528                        ins_and_outs!(ty);
529                    }
530                }
531            }};
532        }
533
534        let mut scopes = vec![stack];
535
536        for op in &self.code {
537            match op {
538                Block { blockty } => {
539                    block_type!(blockty);
540                    scopes.push(stack);
541                }
542                Loop { blockty } => {
543                    block_type!(blockty);
544                    scopes.push(stack);
545                }
546                If { blockty } => {
547                    pop!();
548                    block_type!(blockty);
549                    scopes.push(stack);
550                }
551                Else => {
552                    stack = match scopes.last() {
553                        Some(scope) => *scope,
554                        None => return Err(mw_err("malformed if-else scope")),
555                    };
556                }
557                End => {
558                    stack = match scopes.pop() {
559                        Some(stack) => stack,
560                        None => return Err(mw_err("malformed scoping at end of block")),
561                    };
562                }
563
564                Call { function_index } => {
565                    let index = FunctionIndex::from_u32(*function_index);
566                    let Some(ty) = self.funcs.get(&index) else {
567                        return Err(mw_err(format!("missing type for func {function_index}")));
568                    };
569                    ins_and_outs!(ty)
570                }
571                CallIndirect { type_index, .. } => {
572                    let index = SignatureIndex::from_u32(*type_index);
573                    let Some(ty) = self.sigs.get(&index) else {
574                        return Err(mw_err(format!("missing type for signature {type_index}")));
575                    };
576                    ins_and_outs!(ty);
577                    pop!() // table index
578                }
579
580                MemoryFill { .. } | MemoryCopy { .. } => pop!(3), // 3 args, 0 returns
581
582                op!(
583                    Nop, Unreachable,
584                    I32Eqz, I64Eqz, I32Clz, I32Ctz, I32Popcnt, I64Clz, I64Ctz, I64Popcnt,
585                )
586                | dot!(
587                    Br, Return,
588                    LocalTee, MemoryGrow,
589                    I32Load, I64Load, F32Load, F64Load,
590                    I32Load8S, I32Load8U, I32Load16S, I32Load16U, I64Load8S, I64Load8U,
591                    I64Load16S, I64Load16U, I64Load32S, I64Load32U,
592                    I32WrapI64, I64ExtendI32S, I64ExtendI32U,
593                    I32Extend8S, I32Extend16S, I64Extend8S, I64Extend16S, I64Extend32S,
594                    F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt,
595                    F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, F64Nearest, F64Sqrt,
596                    I32TruncF32S, I32TruncF32U, I32TruncF64S, I32TruncF64U,
597                    I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U,
598                    F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64,
599                    F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32,
600                    I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64,
601                    I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U,
602                    I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U,
603                ) => {}
604
605                dot!(
606                    LocalGet, GlobalGet, MemorySize,
607                    I32Const, I64Const, F32Const, F64Const,
608                ) => push!(),
609
610                op!(
611                    Drop,
612                    I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU,
613                    I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU,
614                    F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge,
615                    F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge,
616                    I32Add, I32Sub, I32Mul, I32DivS, I32DivU, I32RemS, I32RemU,
617                    I64Add, I64Sub, I64Mul, I64DivS, I64DivU, I64RemS, I64RemU,
618                    I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr,
619                    I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr,
620                    F32Add, F32Sub, F32Mul, F32Div, F32Min, F32Max, F32Copysign,
621                    F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign,
622                )
623                | dot!(BrIf, BrTable, LocalSet, GlobalSet) => pop!(),
624
625                dot!(
626                    Select,
627                    I32Store, I64Store, F32Store, F64Store,
628                    I32Store8, I32Store16, I64Store8, I64Store16, I64Store32,
629                ) => pop!(2),
630
631                unsupported @ dot!(Try, Catch, Throw, Rethrow, ThrowRef, TryTable) => {
632                    return Err(mw_err(format!("exception-handling not supported {unsupported:?}")));
633                }
634                unsupported @ dot!(ReturnCall, ReturnCallIndirect) => {
635                    return Err(mw_err(format!("tail-call not supported {unsupported:?}")));
636                }
637                unsupported @ dot!(CallRef, ReturnCallRef) => {
638                    return Err(mw_err(format!("typed function references not supported {unsupported:?}")));
639                }
640                unsupported @ (dot!(Delegate) | op!(CatchAll)) => {
641                    return Err(mw_err(format!("exception-handling not supported {unsupported:?}")));
642                }
643                unsupported @ (op!(RefIsNull) | dot!(TypedSelect, RefNull, RefFunc, RefEq)) => {
644                    return Err(mw_err(format!("reference-types not supported {unsupported:?}")));
645                }
646                unsupported @ dot!(RefAsNonNull, BrOnNull, BrOnNonNull) => {
647                    return Err(mw_err(format!("typed function references not supported {unsupported:?}")));
648                }
649                unsupported @ dot!(
650                    MemoryInit, DataDrop, TableInit, ElemDrop,
651                    TableCopy, TableFill, TableGet, TableSet, TableGrow, TableSize
652                ) => {
653                    return Err(mw_err(format!("bulk-memory not fully supported {unsupported:?}")));
654                }
655                unsupported @ dot!(MemoryDiscard) => {
656                    return Err(mw_err(format!("memory discard not supported {unsupported:?}")));
657                }
658                unsupported @ dot!(
659                    StructNew, StructNewDefault, StructGet, StructGetS, StructGetU, StructSet,
660                    ArrayNew, ArrayNewDefault, ArrayNewFixed, ArrayNewData, ArrayNewElem,
661                    ArrayGet, ArrayGetS, ArrayGetU, ArraySet, ArrayLen, ArrayFill, ArrayCopy,
662                    ArrayInitData, ArrayInitElem,
663                    RefTestNonNull, RefTestNullable, RefCastNonNull, RefCastNullable,
664                    BrOnCast, BrOnCastFail, AnyConvertExtern, ExternConvertAny,
665                    RefI31, I31GetS, I31GetU
666                ) => {
667                    return Err(mw_err(format!("GC extension not supported {unsupported:?}")));
668                }
669                unsupported @ dot!(
670                    MemoryAtomicNotify, MemoryAtomicWait32, MemoryAtomicWait64, AtomicFence,
671                    I32AtomicLoad, I64AtomicLoad, I32AtomicLoad8U, I32AtomicLoad16U,
672                    I64AtomicLoad8U, I64AtomicLoad16U, I64AtomicLoad32U,
673                    I32AtomicStore, I64AtomicStore, I32AtomicStore8, I32AtomicStore16,
674                    I64AtomicStore8, I64AtomicStore16, I64AtomicStore32,
675                    I32AtomicRmwAdd, I64AtomicRmwAdd, I32AtomicRmw8AddU, I32AtomicRmw16AddU,
676                    I64AtomicRmw8AddU, I64AtomicRmw16AddU, I64AtomicRmw32AddU,
677                    I32AtomicRmwSub, I64AtomicRmwSub, I32AtomicRmw8SubU, I32AtomicRmw16SubU,
678                    I64AtomicRmw8SubU, I64AtomicRmw16SubU, I64AtomicRmw32SubU,
679                    I32AtomicRmwAnd, I64AtomicRmwAnd, I32AtomicRmw8AndU, I32AtomicRmw16AndU,
680                    I64AtomicRmw8AndU, I64AtomicRmw16AndU, I64AtomicRmw32AndU,
681                    I32AtomicRmwOr, I64AtomicRmwOr, I32AtomicRmw8OrU, I32AtomicRmw16OrU,
682                    I64AtomicRmw8OrU, I64AtomicRmw16OrU, I64AtomicRmw32OrU,
683                    I32AtomicRmwXor, I64AtomicRmwXor, I32AtomicRmw8XorU, I32AtomicRmw16XorU,
684                    I64AtomicRmw8XorU, I64AtomicRmw16XorU, I64AtomicRmw32XorU,
685                    I32AtomicRmwXchg, I64AtomicRmwXchg, I32AtomicRmw8XchgU, I32AtomicRmw16XchgU,
686                    I64AtomicRmw8XchgU, I64AtomicRmw16XchgU, I64AtomicRmw32XchgU,
687                    I32AtomicRmwCmpxchg, I64AtomicRmwCmpxchg, I32AtomicRmw8CmpxchgU,
688                    I32AtomicRmw16CmpxchgU, I64AtomicRmw8CmpxchgU, I64AtomicRmw16CmpxchgU,
689                    I64AtomicRmw32CmpxchgU
690                ) => {
691                    return Err(mw_err(format!("threads extension not supported {unsupported:?}")));
692                }
693                unsupported @ dot!(
694                    V128Load, V128Load8x8S, V128Load8x8U, V128Load16x4S, V128Load16x4U,
695                    V128Load32x2S, V128Load8Splat, V128Load16Splat, V128Load32Splat,
696                    V128Load64Splat, V128Load32Zero, V128Load64Zero, V128Load32x2U,
697                    V128Store, V128Load8Lane, V128Load16Lane, V128Load32Lane, V128Load64Lane,
698                    V128Store8Lane, V128Store16Lane, V128Store32Lane, V128Store64Lane, V128Const,
699                    I8x16Shuffle, I8x16ExtractLaneS, I8x16ExtractLaneU, I8x16ReplaceLane,
700                    I16x8ExtractLaneS, I16x8ExtractLaneU, I16x8ReplaceLane,
701                    I32x4ExtractLane, I32x4ReplaceLane, I64x2ExtractLane, I64x2ReplaceLane,
702                    F32x4ExtractLane, F32x4ReplaceLane, F64x2ExtractLane, F64x2ReplaceLane,
703                    I8x16Swizzle, I8x16Splat, I16x8Splat, I32x4Splat, I64x2Splat,
704                    F32x4Splat, F64x2Splat,
705                    I8x16Eq, I8x16Ne, I8x16LtS, I8x16LtU, I8x16GtS, I8x16GtU,
706                    I8x16LeS, I8x16LeU, I8x16GeS, I8x16GeU,
707                    I16x8Eq, I16x8Ne, I16x8LtS, I16x8LtU, I16x8GtS, I16x8GtU,
708                    I16x8LeS, I16x8LeU, I16x8GeS, I16x8GeU,
709                    I32x4Eq, I32x4Ne, I32x4LtS, I32x4LtU, I32x4GtS, I32x4GtU,
710                    I32x4LeS, I32x4LeU, I32x4GeS, I32x4GeU,
711                    I64x2Eq, I64x2Ne, I64x2LtS, I64x2GtS, I64x2LeS, I64x2GeS,
712                    F32x4Eq, F32x4Ne, F32x4Lt, F32x4Gt, F32x4Le, F32x4Ge,
713                    F64x2Eq, F64x2Ne, F64x2Lt, F64x2Gt, F64x2Le, F64x2Ge,
714                    V128Not, V128And, V128AndNot, V128Or, V128Xor, V128Bitselect, V128AnyTrue,
715                    I8x16Abs, I8x16Neg, I8x16Popcnt, I8x16AllTrue, I8x16Bitmask,
716                    I8x16NarrowI16x8S, I8x16NarrowI16x8U,
717                    I8x16Shl, I8x16ShrS, I8x16ShrU, I8x16Add, I8x16AddSatS, I8x16AddSatU,
718                    I8x16Sub, I8x16SubSatS, I8x16SubSatU, I8x16MinS, I8x16MinU,
719                    I8x16MaxS, I8x16MaxU, I8x16AvgrU,
720                    I16x8ExtAddPairwiseI8x16S, I16x8ExtAddPairwiseI8x16U, I16x8Abs, I16x8Neg,
721                    I16x8Q15MulrSatS, I16x8AllTrue, I16x8Bitmask,
722                    I16x8NarrowI32x4S, I16x8NarrowI32x4U,
723                    I16x8ExtendLowI8x16S, I16x8ExtendHighI8x16S,
724                    I16x8ExtendLowI8x16U, I16x8ExtendHighI8x16U,
725                    I16x8Shl, I16x8ShrS, I16x8ShrU, I16x8Add, I16x8AddSatS, I16x8AddSatU,
726                    I16x8Sub, I16x8SubSatS, I16x8SubSatU, I16x8Mul,
727                    I16x8MinS, I16x8MinU, I16x8MaxS, I16x8MaxU, I16x8AvgrU,
728                    I16x8ExtMulLowI8x16S, I16x8ExtMulHighI8x16S,
729                    I16x8ExtMulLowI8x16U, I16x8ExtMulHighI8x16U,
730                    I32x4ExtAddPairwiseI16x8U, I32x4Abs, I32x4Neg, I32x4AllTrue, I32x4Bitmask,
731                    I32x4ExtAddPairwiseI16x8S,
732                    I32x4ExtendLowI16x8S, I32x4ExtendHighI16x8S,
733                    I32x4ExtendLowI16x8U, I32x4ExtendHighI16x8U,
734                    I32x4Shl, I32x4ShrS, I32x4ShrU, I32x4Add, I32x4Sub, I32x4Mul,
735                    I32x4MinS, I32x4MinU, I32x4MaxS, I32x4MaxU, I32x4DotI16x8S,
736                    I32x4ExtMulLowI16x8S, I32x4ExtMulHighI16x8S,
737                    I32x4ExtMulLowI16x8U, I32x4ExtMulHighI16x8U,
738                    I64x2Abs, I64x2Neg, I64x2AllTrue, I64x2Bitmask,
739                    I64x2ExtendLowI32x4S, I64x2ExtendHighI32x4S,
740                    I64x2ExtendLowI32x4U, I64x2ExtendHighI32x4U,
741                    I64x2Shl, I64x2ShrS, I64x2ShrU, I64x2Add, I64x2Sub, I64x2Mul,
742                    I64x2ExtMulLowI32x4S, I64x2ExtMulHighI32x4S,
743                    I64x2ExtMulLowI32x4U, I64x2ExtMulHighI32x4U,
744                    F32x4Ceil, F32x4Floor, F32x4Trunc, F32x4Nearest,
745                    F32x4Abs, F32x4Neg, F32x4Sqrt, F32x4Add, F32x4Sub, F32x4Mul, F32x4Div,
746                    F32x4Min, F32x4Max, F32x4PMin, F32x4PMax,
747                    F64x2Ceil, F64x2Floor, F64x2Trunc, F64x2Nearest,
748                    F64x2Abs, F64x2Neg, F64x2Sqrt, F64x2Add, F64x2Sub, F64x2Mul, F64x2Div,
749                    F64x2Min, F64x2Max, F64x2PMin, F64x2PMax,
750                    I32x4TruncSatF32x4S, I32x4TruncSatF32x4U,
751                    F32x4ConvertI32x4S, F32x4ConvertI32x4U,
752                    I32x4TruncSatF64x2SZero, I32x4TruncSatF64x2UZero,
753                    F64x2ConvertLowI32x4S, F64x2ConvertLowI32x4U,
754                    F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4,
755                    I8x16RelaxedSwizzle,
756                    I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U,
757                    I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero,
758                    F32x4RelaxedMadd, F32x4RelaxedNmadd, F64x2RelaxedMadd, F64x2RelaxedNmadd,
759                    I8x16RelaxedLaneselect, I16x8RelaxedLaneselect,
760                    I32x4RelaxedLaneselect, I64x2RelaxedLaneselect,
761                    F32x4RelaxedMin, F32x4RelaxedMax, F64x2RelaxedMin, F64x2RelaxedMax,
762                    I16x8RelaxedQ15mulrS, I16x8RelaxedDotI8x16I7x16S,
763                    I32x4RelaxedDotI8x16I7x16AddS
764                ) => {
765                    return Err(mw_err(format!("SIMD extension not supported {unsupported:?}")));
766                }
767                // wasmparser >=0.245 made `Operator` non-exhaustive. Any future
768                // opcode the depth checker hasn't classified is rejected — same
769                // posture as the explicit unsupported-extension arms above.
770                unsupported => {
771                    return Err(mw_err(format!("unsupported opcode {unsupported:?}")));
772                }
773            };
774        }
775
776        if self.locals.is_none() {
777            return Err(mw_err("missing locals info"));
778        }
779
780        let contention = worst;
781        if contention > self.frame_contention.into() {
782            return Err(mw_err(format!(
783                "too many values on the stack at once: {contention} > {}",
784                self.frame_contention
785            )));
786        }
787
788        let locals = self.locals.unwrap_or_default();
789        Ok(worst + locals as u32 + 4)
790    }
791}
792
793impl<'a> FunctionMiddleware<'a> for DepthCheckerFn {
794    fn locals_info(&mut self, locals: &[ValType]) {
795        self.locals = Some(locals.len());
796    }
797
798    fn feed(
799        &mut self,
800        op: Operator<'a>,
801        state: &mut MiddlewareReaderState<'a>,
802    ) -> Result<(), MiddlewareError> {
803        if self.done {
804            return Err(mw_err("depth checker: feed called after finalization"));
805        }
806
807        match op {
808            Operator::Block { .. } | Operator::Loop { .. } | Operator::If { .. } => {
809                self.scopes += 1;
810            }
811            Operator::End => {
812                self.scopes -= 1;
813            }
814            _ => {}
815        }
816        if self.scopes < 0 {
817            return Err(mw_err("malformed scoping detected"));
818        }
819
820        let last = self.scopes == 0 && matches!(op, Operator::End);
821
822        // SAFETY: `Operator<'a>` borrows from the WASM byte buffer for
823        // some variants; `FunctionMiddleware<'a>` is implemented on a
824        // `'static` struct so `'a` cannot be threaded into `self.code`.
825        // The transmute is reversed inside this same `feed()` call
826        // before re-emission, while the source bytes are still live.
827        let op_static = unsafe { std::mem::transmute::<Operator<'a>, Operator<'static>>(op) };
828        self.code.push(op_static);
829
830        if !last {
831            return Ok(());
832        }
833
834        let size = self.worst_case_depth()?;
835        let g = self.global.as_u32();
836
837        if size > self.frame_limit {
838            return Err(mw_err(format!(
839                "frame too large: {size} > {}-word limit",
840                self.frame_limit
841            )));
842        }
843
844        // Insert an extraneous Return before the final End to match Arbitrator.
845        let mut code = std::mem::take(&mut self.code);
846        let Some(final_end) = code.pop() else {
847            return Err(mw_err("depth checker: missing final End operator"));
848        };
849
850        // Prologue: check and deduct depth budget
851        state.extend([
852            Operator::GlobalGet { global_index: g },
853            Operator::I32Const { value: size as i32 },
854            Operator::I32LeU,
855            Operator::If {
856                blockty: BlockType::Empty,
857            },
858            Operator::I32Const { value: 0 },
859            Operator::GlobalSet { global_index: g },
860            Operator::Unreachable,
861            Operator::End,
862            Operator::GlobalGet { global_index: g },
863            Operator::I32Const { value: size as i32 },
864            Operator::I32Sub,
865            Operator::GlobalSet { global_index: g },
866        ]);
867
868        code.push(Operator::Return);
869        code.push(final_end);
870
871        for op_s in code {
872            let is_return = matches!(op_s, Operator::Return);
873            if is_return {
874                state.extend([
875                    Operator::GlobalGet { global_index: g },
876                    Operator::I32Const { value: size as i32 },
877                    Operator::I32Add,
878                    Operator::GlobalSet { global_index: g },
879                ]);
880            }
881            // SAFETY: reverses the `'a` -> `'static` transmute earlier in
882            // this `feed()` call; source WASM bytes are still live.
883            let op_a = unsafe { std::mem::transmute::<Operator<'static>, Operator<'a>>(op_s) };
884            state.push_operator(op_a);
885        }
886
887        self.done = true;
888        Ok(())
889    }
890}
891
892// ── HeapBound ───────────────────────────────────────────────────────
893
894#[derive(Debug)]
895pub struct HeapBound {
896    globals: RwLock<Option<(GlobalIndex, Option<FunctionIndex>)>>,
897}
898
899impl HeapBound {
900    pub fn new() -> Self {
901        Self {
902            globals: RwLock::new(None),
903        }
904    }
905}
906
907impl ModuleMiddleware for HeapBound {
908    fn transform_module_info(&self, info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
909        let scratch_idx = info
910            .exports
911            .get(SCRATCH_GLOBAL)
912            .and_then(|e| match e {
913                wasmer_types::ExportIndex::Global(g) => Some(*g),
914                _ => None,
915            })
916            .ok_or_else(|| mw_err("scratch global not found"))?;
917
918        let pay_func = info.imports.iter().find_map(|(key, idx)| {
919            if key.field == "pay_for_memory_grow"
920                && let ImportIndex::Function(f) = idx
921            {
922                return Some(*f);
923            }
924            None
925        });
926
927        *self.globals.write().expect("heap bound lock poisoned") = Some((scratch_idx, pay_func));
928        Ok(())
929    }
930
931    fn generate_function_middleware<'a>(
932        &self,
933        _: LocalFunctionIndex,
934    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
935        let (scratch, pay_func) = self
936            .globals
937            .read()
938            .expect("heap bound lock poisoned")
939            .expect("missing heap globals");
940        Box::new(HeapBoundFn { scratch, pay_func })
941    }
942}
943
944#[derive(Debug)]
945struct HeapBoundFn {
946    scratch: GlobalIndex,
947    pay_func: Option<FunctionIndex>,
948}
949
950impl<'a> FunctionMiddleware<'a> for HeapBoundFn {
951    fn feed(
952        &mut self,
953        op: Operator<'a>,
954        state: &mut MiddlewareReaderState<'a>,
955    ) -> Result<(), MiddlewareError> {
956        if let (Operator::MemoryGrow { .. }, Some(pay)) = (&op, self.pay_func) {
957            let g = self.scratch.as_u32();
958            let f = pay.as_u32();
959            state.extend([
960                Operator::GlobalSet { global_index: g },
961                Operator::GlobalGet { global_index: g },
962                Operator::GlobalGet { global_index: g },
963                Operator::Call { function_index: f },
964            ]);
965        }
966        state.push_operator(op);
967        Ok(())
968    }
969}
970
971// ── Opcode ink costs ────────────────────────────────────────────────
972
973/// Per-opcode ink cost used by the ink meter middleware.
974#[rustfmt::skip]
975pub fn opcode_ink_cost(op: &Operator, sigs: &HashMap<u32, usize>) -> u64 {
976    use Operator::*;
977
978    macro_rules! op {
979        ($first:ident $(,$opcode:ident)*) => { $first $(| $opcode)* };
980    }
981    macro_rules! dot {
982        ($first:ident $(,$opcode:ident)*) => { $first { .. } $(| $opcode { .. })* };
983    }
984
985    match op {
986        op!(Unreachable, Return) => 1,
987        op!(Nop) | dot!(I32Const, I64Const) => 1,
988        op!(Drop) => 9,
989
990        dot!(Block, Loop) | op!(Else, End) => 1,
991        dot!(Br, BrIf, If) => 765,
992        dot!(Select) => 1250,
993        dot!(Call) => 3800,
994        dot!(LocalGet, LocalTee) => 75,
995        dot!(LocalSet) => 210,
996        dot!(GlobalGet) => 225,
997        dot!(GlobalSet) => 575,
998        dot!(I32Load, I32Load8S, I32Load8U, I32Load16S, I32Load16U) => 670,
999        dot!(I64Load, I64Load8S, I64Load8U, I64Load16S, I64Load16U, I64Load32S, I64Load32U) => 680,
1000        dot!(I32Store, I32Store8, I32Store16) => 825,
1001        dot!(I64Store, I64Store8, I64Store16, I64Store32) => 950,
1002        dot!(MemorySize) => 3000,
1003        dot!(MemoryGrow) => 8050,
1004
1005        op!(I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU) => 170,
1006        op!(I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU) => 225,
1007
1008        op!(I32Clz, I32Ctz) => 210,
1009        op!(I32Add, I32Sub) => 70,
1010        op!(I32Mul) => 160,
1011        op!(I32DivS, I32DivU, I32RemS, I32RemU) => 1120,
1012        op!(I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr) => 70,
1013
1014        op!(I64Clz, I64Ctz) => 210,
1015        op!(I64Add, I64Sub) => 100,
1016        op!(I64Mul) => 160,
1017        op!(I64DivS, I64DivU, I64RemS, I64RemU) => 1270,
1018        op!(I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr) => 100,
1019
1020        op!(I32Popcnt) => 2650,
1021        op!(I64Popcnt) => 6000,
1022
1023        op!(I32WrapI64, I64ExtendI32S, I64ExtendI32U) => 100,
1024        op!(I32Extend8S, I32Extend16S, I64Extend8S, I64Extend16S, I64Extend32S) => 100,
1025        dot!(MemoryCopy) => 950,
1026        dot!(MemoryFill) => 950,
1027
1028        BrTable { targets } => 2400 + 325 * targets.len() as u64,
1029        CallIndirect { type_index, .. } => {
1030            let params = sigs.get(type_index).copied().unwrap_or(0);
1031            13610 + 650 * params as u64
1032        },
1033
1034        _ => u64::MAX,
1035    }
1036}