walrus/
const_expr.rs

1//! Handling wasm constant values
2
3use crate::emit::EmitContext;
4use crate::ir::Value;
5use crate::parse::IndicesToIds;
6use crate::RefType;
7use crate::{FunctionId, GlobalId, Result};
8use anyhow::bail;
9
10/// A constant which is produced in WebAssembly, typically used in global
11/// initializers or element/data offsets.
12#[derive(Debug, Clone)]
13pub enum ConstExpr {
14    /// An immediate constant value
15    Value(Value),
16    /// A constant value referenced by the global specified
17    Global(GlobalId),
18    /// A null reference
19    RefNull(RefType),
20    /// A function initializer
21    RefFunc(FunctionId),
22    /// Extended constant expression (sequence of instructions)
23    Extended(Vec<ConstOp>),
24}
25
26/// Operations allowed in extended constant expressions
27#[derive(Debug, Copy, Clone)]
28pub enum ConstOp {
29    /// An i32 constant value
30    I32Const(i32),
31    /// An i64 constant value
32    I64Const(i64),
33    /// An f32 constant value
34    F32Const(f32),
35    /// An f64 constant value
36    F64Const(f64),
37    /// A v128 constant value
38    V128Const(u128),
39    /// Get the value of a global
40    GlobalGet(GlobalId),
41    /// A null reference
42    RefNull(RefType),
43    /// A function reference
44    RefFunc(FunctionId),
45    /// i32 addition
46    I32Add,
47    /// i32 subtraction
48    I32Sub,
49    /// i32 multiplication
50    I32Mul,
51    /// i64 addition
52    I64Add,
53    /// i64 subtraction
54    I64Sub,
55    /// i64 multiplication
56    I64Mul,
57}
58
59impl ConstExpr {
60    pub(crate) fn eval(init: &wasmparser::ConstExpr, ids: &IndicesToIds) -> Result<ConstExpr> {
61        use wasmparser::Operator::*;
62        let mut reader = init.get_operators_reader();
63        let mut ops = Vec::new();
64
65        loop {
66            let op = reader.read()?;
67            match op {
68                End => break,
69                I32Const { value } => ops.push(ConstOp::I32Const(value)),
70                I64Const { value } => ops.push(ConstOp::I64Const(value)),
71                F32Const { value } => ops.push(ConstOp::F32Const(f32::from_bits(value.bits()))),
72                F64Const { value } => ops.push(ConstOp::F64Const(f64::from_bits(value.bits()))),
73                V128Const { value } => ops.push(ConstOp::V128Const(v128_to_u128(&value))),
74                GlobalGet { global_index } => {
75                    ops.push(ConstOp::GlobalGet(ids.get_global(global_index)?))
76                }
77                RefNull { hty } => {
78                    let val_type = match hty {
79                        wasmparser::HeapType::Abstract { shared: _, ty } => match ty {
80                            wasmparser::AbstractHeapType::Func => RefType::Funcref,
81                            wasmparser::AbstractHeapType::Extern => RefType::Externref,
82                            other => bail!(
83                                "unsupported abstract heap type in constant expression: {other:?}"
84                            ),
85                        },
86                        wasmparser::HeapType::Concrete(_) => {
87                            bail!("unsupported concrete heap type in constant expression")
88                        }
89                    };
90                    ops.push(ConstOp::RefNull(val_type));
91                }
92                RefFunc { function_index } => {
93                    ops.push(ConstOp::RefFunc(ids.get_func(function_index)?))
94                }
95                I32Add => ops.push(ConstOp::I32Add),
96                I32Sub => ops.push(ConstOp::I32Sub),
97                I32Mul => ops.push(ConstOp::I32Mul),
98                I64Add => ops.push(ConstOp::I64Add),
99                I64Sub => ops.push(ConstOp::I64Sub),
100                I64Mul => ops.push(ConstOp::I64Mul),
101                _ => bail!("unsupported operation in constant expression: {:?}", op),
102            }
103        }
104
105        reader.finish()?;
106
107        // Optimize: if there's only one simple operation, use the simple form
108        if ops.len() == 1 {
109            match &ops[0] {
110                ConstOp::I32Const(v) => return Ok(ConstExpr::Value(Value::I32(*v))),
111                ConstOp::I64Const(v) => return Ok(ConstExpr::Value(Value::I64(*v))),
112                ConstOp::F32Const(v) => return Ok(ConstExpr::Value(Value::F32(*v))),
113                ConstOp::F64Const(v) => return Ok(ConstExpr::Value(Value::F64(*v))),
114                ConstOp::V128Const(v) => return Ok(ConstExpr::Value(Value::V128(*v))),
115                ConstOp::GlobalGet(g) => return Ok(ConstExpr::Global(*g)),
116                ConstOp::RefNull(ty) => return Ok(ConstExpr::RefNull(*ty)),
117                ConstOp::RefFunc(f) => return Ok(ConstExpr::RefFunc(*f)),
118                _ => {}
119            }
120        }
121
122        Ok(ConstExpr::Extended(ops))
123    }
124
125    pub(crate) fn to_wasmencoder_type(&self, cx: &EmitContext) -> wasm_encoder::ConstExpr {
126        use wasm_encoder::{Encode, Instruction};
127        match self {
128            ConstExpr::Value(v) => match v {
129                Value::I32(v) => wasm_encoder::ConstExpr::i32_const(*v),
130                Value::I64(v) => wasm_encoder::ConstExpr::i64_const(*v),
131                Value::F32(v) => wasm_encoder::ConstExpr::f32_const((*v).into()),
132                Value::F64(v) => wasm_encoder::ConstExpr::f64_const((*v).into()),
133                Value::V128(v) => wasm_encoder::ConstExpr::v128_const(*v as i128),
134            },
135            ConstExpr::Global(g) => {
136                wasm_encoder::ConstExpr::global_get(cx.indices.get_global_index(*g))
137            }
138            ConstExpr::RefNull(ty) => wasm_encoder::ConstExpr::ref_null(match ty {
139                RefType::Externref => wasm_encoder::HeapType::Abstract {
140                    shared: false,
141                    ty: wasm_encoder::AbstractHeapType::Extern,
142                },
143                RefType::Funcref => wasm_encoder::HeapType::Abstract {
144                    shared: false,
145                    ty: wasm_encoder::AbstractHeapType::Func,
146                },
147                RefType::Exnref => wasm_encoder::HeapType::Abstract {
148                    shared: false,
149                    ty: wasm_encoder::AbstractHeapType::Exn,
150                },
151            }),
152            ConstExpr::RefFunc(f) => {
153                wasm_encoder::ConstExpr::ref_func(cx.indices.get_func_index(*f))
154            }
155            ConstExpr::Extended(ops) => {
156                let mut bytes = Vec::new();
157                for op in ops {
158                    match op {
159                        ConstOp::I32Const(v) => Instruction::I32Const(*v).encode(&mut bytes),
160                        ConstOp::I64Const(v) => Instruction::I64Const(*v).encode(&mut bytes),
161                        ConstOp::F32Const(v) => {
162                            Instruction::F32Const((*v).into()).encode(&mut bytes)
163                        }
164                        ConstOp::F64Const(v) => {
165                            Instruction::F64Const((*v).into()).encode(&mut bytes)
166                        }
167                        ConstOp::V128Const(v) => {
168                            Instruction::V128Const(*v as i128).encode(&mut bytes)
169                        }
170                        ConstOp::GlobalGet(g) => {
171                            Instruction::GlobalGet(cx.indices.get_global_index(*g))
172                                .encode(&mut bytes)
173                        }
174                        ConstOp::RefNull(ty) => Instruction::RefNull(match ty {
175                            RefType::Externref => wasm_encoder::HeapType::Abstract {
176                                shared: false,
177                                ty: wasm_encoder::AbstractHeapType::Extern,
178                            },
179                            RefType::Funcref => wasm_encoder::HeapType::Abstract {
180                                shared: false,
181                                ty: wasm_encoder::AbstractHeapType::Func,
182                            },
183                            RefType::Exnref => wasm_encoder::HeapType::Abstract {
184                                shared: false,
185                                ty: wasm_encoder::AbstractHeapType::Exn,
186                            },
187                        })
188                        .encode(&mut bytes),
189                        ConstOp::RefFunc(f) => {
190                            Instruction::RefFunc(cx.indices.get_func_index(*f)).encode(&mut bytes)
191                        }
192                        ConstOp::I32Add => Instruction::I32Add.encode(&mut bytes),
193                        ConstOp::I32Sub => Instruction::I32Sub.encode(&mut bytes),
194                        ConstOp::I32Mul => Instruction::I32Mul.encode(&mut bytes),
195                        ConstOp::I64Add => Instruction::I64Add.encode(&mut bytes),
196                        ConstOp::I64Sub => Instruction::I64Sub.encode(&mut bytes),
197                        ConstOp::I64Mul => Instruction::I64Mul.encode(&mut bytes),
198                    }
199                }
200                // Don't add End instruction - wasm_encoder::ConstExpr::raw adds it automatically
201                wasm_encoder::ConstExpr::raw(bytes)
202            }
203        }
204    }
205}
206
207pub(crate) fn v128_to_u128(value: &wasmparser::V128) -> u128 {
208    let n = value.bytes();
209    (n[0] as u128)
210        | ((n[1] as u128) << 8)
211        | ((n[2] as u128) << 16)
212        | ((n[3] as u128) << 24)
213        | ((n[4] as u128) << 32)
214        | ((n[5] as u128) << 40)
215        | ((n[6] as u128) << 48)
216        | ((n[7] as u128) << 56)
217        | ((n[8] as u128) << 64)
218        | ((n[9] as u128) << 72)
219        | ((n[10] as u128) << 80)
220        | ((n[11] as u128) << 88)
221        | ((n[12] as u128) << 96)
222        | ((n[13] as u128) << 104)
223        | ((n[14] as u128) << 112)
224        | ((n[15] as u128) << 120)
225}