1use crate::runtime::host::{abi_arg, abi_result};
4use std::collections::BTreeMap;
5use wasmtime::{Caller, FuncType, Global, GlobalType, Linker, Mutability, Store, Val, ValType};
6
7use crate::runtime::StoreData;
8use crate::runtime::host::register_host_fn;
9use crate::runtime::intrinsic_types::intrinsic_types;
10use crate::runtime::number::{to_int32, to_uint32};
11use crate::runtime::prelude::MODULE_NAME;
12use crate::{
13 MangledName, NamespaceSymbol, PackageDeclaration, Param, Span, Type, ValueKind, ValueSymbol,
14};
15
16type UnaryFn = fn(f64) -> f64;
17type BinaryFn = fn(f64, f64) -> f64;
18
19const CONSTANTS: &[(&str, f64)] = &[
20 ("PI", std::f64::consts::PI),
21 ("E", std::f64::consts::E),
22 ("LN2", std::f64::consts::LN_2),
23 ("LN10", std::f64::consts::LN_10),
24 ("LOG2E", std::f64::consts::LOG2_E),
25 ("LOG10E", std::f64::consts::LOG10_E),
26 ("SQRT2", std::f64::consts::SQRT_2),
27 ("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2),
28];
29
30const UNARY: &[(&str, UnaryFn)] = &[
31 ("abs", f64::abs),
32 ("ceil", f64::ceil),
33 ("floor", f64::floor),
34 ("trunc", f64::trunc),
35 ("sqrt", f64::sqrt),
36 ("round", math_round),
37 ("sign", math_sign),
38 ("fround", math_fround),
39 ("clz32", math_clz32),
40 ("exp", f64::exp),
41 ("expm1", f64::exp_m1),
42 ("log", f64::ln),
43 ("log1p", f64::ln_1p),
44 ("log2", f64::log2),
45 ("log10", f64::log10),
46 ("cbrt", f64::cbrt),
47 ("sin", f64::sin),
48 ("cos", f64::cos),
49 ("tan", f64::tan),
50 ("asin", f64::asin),
51 ("acos", f64::acos),
52 ("atan", f64::atan),
53 ("sinh", f64::sinh),
54 ("cosh", f64::cosh),
55 ("tanh", f64::tanh),
56 ("asinh", f64::asinh),
57 ("acosh", f64::acosh),
58 ("atanh", f64::atanh),
59];
60
61const BINARY: &[(&str, BinaryFn)] = &[
62 ("imul", math_imul),
63 ("pow", crate::runtime::number::pow_js),
64 ("atan2", f64::atan2),
65];
66
67const VARIADIC: &[&str] = &["min", "max", "hypot"];
68
69pub(crate) fn math_key(name: &str) -> MangledName {
70 crate::mangle::extend(&crate::mangle::prelude("Math"), name)
71}
72
73pub(crate) fn namespace_symbol() -> NamespaceSymbol {
74 let math_prefix = crate::mangle::prelude("Math");
75 let mut math = NamespaceSymbol {
76 name: "Math".to_string(),
77 mangled_prefix: math_prefix.clone(),
78 declaration_span: Span::at(crate::FileId::MATH),
79 values: BTreeMap::new(),
80 types: BTreeMap::new(),
81 namespaces: BTreeMap::new(),
82 doc: None,
83 };
84
85 for (name, _) in CONSTANTS {
86 math.values.insert(
87 (*name).to_string(),
88 ValueSymbol {
89 name: (*name).to_string(),
90 mangled_name: math_key(name),
91 declaration_span: Span::at(crate::FileId::MATH),
92 kind: ValueKind::Const {
93 ty: Type::Number,
94 doc: None,
95 },
96 },
97 );
98 }
99 for (name, _) in UNARY {
100 math.values.insert(
101 (*name).to_string(),
102 ValueSymbol {
103 name: (*name).to_string(),
104 mangled_name: math_key(name),
105 declaration_span: Span::at(crate::FileId::MATH),
106 kind: ValueKind::Function {
107 generics: Vec::new(),
108 params: vec![Param::new("x", Type::Number)],
109 ret: Type::Number,
110 type_predicate: None,
111 doc: None,
112 },
113 },
114 );
115 }
116 math.values.insert(
117 "random".to_string(),
118 ValueSymbol {
119 name: "random".to_string(),
120 mangled_name: math_key("random"),
121 declaration_span: Span::at(crate::FileId::MATH),
122 kind: ValueKind::Function {
123 generics: Vec::new(),
124 params: Vec::new(),
125 ret: Type::Number,
126 type_predicate: None,
127 doc: crate::doc(
128 crate::FileId::MATH,
129 "/** Pseudo-random number in `[0, 1)`. Not suitable for cryptographic use — reach for `submilli:crypto.randomBytes` instead. */",
130 ),
131 },
132 },
133 );
134 for (name, _) in BINARY {
135 let params = match *name {
136 "pow" => vec![
137 Param::new("base", Type::Number),
138 Param::new("exponent", Type::Number),
139 ],
140 "atan2" => vec![Param::new("y", Type::Number), Param::new("x", Type::Number)],
141 "imul" => vec![Param::new("a", Type::Number), Param::new("b", Type::Number)],
142 _ => vec![Param::new("a", Type::Number), Param::new("b", Type::Number)],
143 };
144 math.values.insert(
145 (*name).to_string(),
146 ValueSymbol {
147 name: (*name).to_string(),
148 mangled_name: math_key(name),
149 declaration_span: Span::at(crate::FileId::MATH),
150 kind: ValueKind::Function {
151 generics: Vec::new(),
152 params,
153 ret: Type::Number,
154 type_predicate: None,
155 doc: None,
156 },
157 },
158 );
159 }
160 for name in VARIADIC {
161 math.values.insert(
162 (*name).to_string(),
163 ValueSymbol {
164 name: (*name).to_string(),
165 mangled_name: math_key(name),
166 declaration_span: Span::at(crate::FileId::MATH),
167 kind: ValueKind::Function {
168 generics: Vec::new(),
169 params: vec![Param::rest("values", Type::Array(Box::new(Type::Number)))],
170 ret: Type::Number,
171 type_predicate: None,
172 doc: None,
173 },
174 },
175 );
176 }
177
178 math
179}
180
181pub fn declare(defs: &mut PackageDeclaration) {
182 defs.namespaces
183 .insert("Math".to_string(), namespace_symbol());
184}
185
186pub(crate) fn install_constants(
187 linker: &mut Linker<StoreData>,
188 store: &mut Store<StoreData>,
189) -> wasmtime::Result<()> {
190 for (name, value) in CONSTANTS {
191 let gty = GlobalType::new(ValType::F64, Mutability::Var);
192 let global = Global::new(&mut *store, gty, Val::F64(value.to_bits()))?;
193 let mangled = math_key(name);
194 linker.define(&mut *store, MODULE_NAME, mangled.as_str(), global)?;
195 }
196 Ok(())
197}
198
199pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
200 let engine = linker.engine().clone();
201 let unary_ty = FuncType::new(&engine, [ValType::F64], [ValType::F64]);
202 let binary_ty = FuncType::new(&engine, [ValType::F64, ValType::F64], [ValType::F64]);
203 let array_ref = ValType::Ref(wasmtime::RefType::new(
204 false,
205 wasmtime::HeapType::ConcreteStruct(
206 crate::runtime::intrinsic_types::build_intrinsic_types(&engine)?.array,
207 ),
208 ));
209 let variadic_ty = FuncType::new(&engine, [array_ref], [ValType::F64]);
210
211 for (name, op) in UNARY {
212 let op = *op;
213 register_host_fn(
214 linker,
215 MODULE_NAME,
216 math_key(name),
217 unary_ty.clone(),
218 true,
219 move |_caller, params, results| -> wasmtime::Result<()> {
220 let x = read_f64(abi_arg(params, 0)?, "Math unary")?;
221 *abi_result(results, 0)? = Val::F64(op(x).to_bits());
222 Ok(())
223 },
224 )?;
225 }
226
227 for (name, op) in BINARY {
228 let op = *op;
229 register_host_fn(
230 linker,
231 MODULE_NAME,
232 math_key(name),
233 binary_ty.clone(),
234 true,
235 move |_caller, params, results| -> wasmtime::Result<()> {
236 let a = read_f64(abi_arg(params, 0)?, "Math binary")?;
237 let b = read_f64(abi_arg(params, 1)?, "Math binary")?;
238 *abi_result(results, 0)? = Val::F64(op(a, b).to_bits());
239 Ok(())
240 },
241 )?;
242 }
243
244 for name in VARIADIC {
245 let op = *name;
246 register_host_fn(
247 linker,
248 MODULE_NAME,
249 math_key(name),
250 variadic_ty.clone(),
251 true,
252 move |caller, params, results| -> wasmtime::Result<()> {
253 let values = read_variadic_numbers(caller, abi_arg(params, 0)?, op)?;
254 let result = match op {
255 "min" => math_min(&values),
256 "max" => math_max(&values),
257 "hypot" => math_hypot(&values),
258 _ => {
259 return Err(crate::runtime::host::invariant_trap(
260 "Math variadic: unknown operation",
261 ));
262 }
263 };
264 *abi_result(results, 0)? = Val::F64(result.to_bits());
265 Ok(())
266 },
267 )?;
268 }
269
270 register_host_fn(
271 linker,
272 MODULE_NAME,
273 math_key("random"),
274 FuncType::new(&engine, [], [ValType::F64]),
275 false,
276 |_caller, _params, results| -> wasmtime::Result<()> {
277 let mut bytes = [0u8; 8];
278 getrandom::getrandom(&mut bytes)
279 .map_err(|e| wasmtime::Error::msg(format!("Math.random getrandom failed: {e}")))?;
280 let raw = u64::from_le_bytes(bytes);
281 let mantissa = raw & ((1u64 << 52) - 1);
282 let bits = (0x3FFu64 << 52) | mantissa;
283 *abi_result(results, 0)? = Val::F64((f64::from_bits(bits) - 1.0).to_bits());
284 Ok(())
285 },
286 )?;
287
288 Ok(())
289}
290
291fn read_f64(val: &Val, name: &str) -> wasmtime::Result<f64> {
292 match val {
293 Val::F64(bits) => Ok(f64::from_bits(*bits)),
294 other => Err(crate::runtime::host::type_error(format!(
295 "{name} expects f64, got {other:?}"
296 ))),
297 }
298}
299
300fn read_variadic_numbers(
301 caller: &mut Caller<'_, StoreData>,
302 val: &Val,
303 name: &str,
304) -> wasmtime::Result<Vec<f64>> {
305 let elements = crate::runtime::prelude::collection::read_array_vals(caller, val)?;
306 let boxed_number = intrinsic_types(&mut *caller)?.boxed_number.clone();
307 let mut out = Vec::with_capacity(elements.len());
308 for element in elements {
309 let Val::AnyRef(Some(any)) = element else {
310 return Err(crate::runtime::host::type_error(format!(
311 "Math.{name} expects boxed number arguments"
312 )));
313 };
314 let st = any.as_struct(&mut *caller)?.ok_or_else(|| {
315 crate::runtime::host::type_error(format!("Math.{name}: expected boxed number"))
316 })?;
317 if !st.matches_ty(&*caller, &boxed_number)? {
318 return Err(crate::runtime::host::type_error(format!(
319 "Math.{name}: expected boxed number"
320 )));
321 }
322 out.push(read_f64(&st.field(&mut *caller, 1)?, name)?);
323 }
324 Ok(out)
325}
326
327fn math_round(x: f64) -> f64 {
328 if x.is_nan() || x == 0.0 || x.is_infinite() {
329 return x;
330 }
331 if (-0.5..0.0).contains(&x) || x == -0.5 {
332 return -0.0;
333 }
334 (x + 0.5).floor()
335}
336
337fn math_sign(x: f64) -> f64 {
338 if x > 0.0 {
339 1.0
340 } else if x < 0.0 {
341 -1.0
342 } else {
343 x
344 }
345}
346
347fn math_fround(x: f64) -> f64 {
348 (x as f32) as f64
349}
350
351fn math_clz32(x: f64) -> f64 {
352 f64::from(to_uint32(x).leading_zeros())
353}
354
355fn math_imul(a: f64, b: f64) -> f64 {
356 let a = to_int32(a);
357 let b = to_int32(b);
358 f64::from(a.wrapping_mul(b))
359}
360
361fn math_min(values: &[f64]) -> f64 {
362 let mut out = f64::INFINITY;
363 for &value in values {
364 if value.is_nan() {
365 return f64::NAN;
366 }
367 if value < out || (value == 0.0 && out == 0.0 && value.is_sign_negative()) {
368 out = value;
369 }
370 }
371 out
372}
373
374fn math_max(values: &[f64]) -> f64 {
375 let mut out = f64::NEG_INFINITY;
376 for &value in values {
377 if value.is_nan() {
378 return f64::NAN;
379 }
380 if value > out || (value == 0.0 && out == 0.0 && value.is_sign_positive()) {
381 out = value;
382 }
383 }
384 out
385}
386
387fn math_hypot(values: &[f64]) -> f64 {
388 if values.iter().any(|v| v.is_infinite()) {
389 return f64::INFINITY;
390 }
391 if values.iter().any(|v| v.is_nan()) {
392 return f64::NAN;
393 }
394 values.iter().fold(0.0, |acc, value| acc.hypot(*value))
395}