1use crate::runtime::host::{abi_arg, abi_result};
5use wasmtime::{
6 ArrayRef, ArrayRefPre, ArrayType, AsContextMut, Caller, Engine, FieldType, FuncType, HeapType,
7 Linker, Mutability, RefType, Rooted, StorageType, StructRef, StructRefPre, Val, ValType,
8};
9
10use crate::runtime::host::{
11 range_error, read_string_arg, register_host_fn, type_error, write_submilli_string,
12};
13use crate::runtime::intrinsic_types::intrinsic_types;
14use crate::runtime::{NUMBER_MODULE_NAME, StoreData, fuel};
15
16pub const BIGINT_MODULE_NAME: &str = "submilli:bigint";
17
18pub(super) fn integer_number(n: f64, name: &str) -> wasmtime::Result<num_bigint::BigInt> {
21 if !n.is_finite() {
22 return Err(range_error(format!(
23 "{name}: cannot convert non-finite number to bigint ({n})"
24 )));
25 }
26 if n.fract() != 0.0 {
27 return Err(range_error(format!(
28 "{name}: cannot convert non-integer number to bigint ({n})"
29 )));
30 }
31 <num_bigint::BigInt as num_traits::FromPrimitive>::from_f64(n).ok_or_else(|| {
32 crate::runtime::host::fatal_host_error("BigInt conversion refused a finite integer double")
33 })
34}
35
36pub(crate) fn limbs_array_type(engine: &Engine) -> ArrayType {
38 ArrayType::new(
39 engine,
40 FieldType::new(Mutability::Var, StorageType::ValType(ValType::I64)),
41 )
42}
43
44pub(crate) fn install(
45 linker: &mut Linker<StoreData>,
46 string_type: &ArrayType,
47) -> wasmtime::Result<()> {
48 let engine = linker.engine().clone();
49 let raw_string_param = ValType::Ref(RefType::new(false, HeapType::from(string_type.clone())));
50 let raw_string_result = raw_string_param.clone();
51 let limbs_array_ty = limbs_array_type(&engine);
52 let raw_bigint_result =
53 ValType::Ref(RefType::new(false, HeapType::from(limbs_array_ty.clone())));
54 let raw_bigint_param = raw_bigint_result.clone();
55
56 let from_string_ty = FuncType::new(
57 &engine,
58 [raw_string_param.clone()],
59 [ValType::I32, raw_bigint_result.clone()],
60 );
61 register_host_fn(
62 linker,
63 BIGINT_MODULE_NAME,
64 crate::mangle::host(BIGINT_MODULE_NAME, "fromString"),
65 from_string_ty,
66 true,
67 |caller, params, results| -> wasmtime::Result<()> {
68 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "bigint.fromString")?;
69 let parsed = parse_decimal(caller, &s, "bigint.fromString")?;
70 let (sign, magnitude) = parsed.into_parts();
71 let limbs = magnitude.to_u64_digits();
72 let arr = write_limbs(&mut *caller, &limbs)?;
73 *abi_result(results, 0)? = Val::I32(sign_to_i32(sign));
74 *abi_result(results, 1)? = Val::AnyRef(Some(arr.to_anyref()));
75 Ok(())
76 },
77 )?;
78
79 let from_number_ty = FuncType::new(
80 &engine,
81 [ValType::F64],
82 [ValType::I32, raw_bigint_result.clone()],
83 );
84 register_host_fn(
85 linker,
86 BIGINT_MODULE_NAME,
87 crate::mangle::host(BIGINT_MODULE_NAME, "fromNumber"),
88 from_number_ty,
89 true,
90 |caller, params, results| -> wasmtime::Result<()> {
91 let n = match *abi_arg(params, 0)? {
92 Val::F64(bits) => f64::from_bits(bits),
93 ref other => {
94 return Err(type_error(format!(
95 "bigint.fromNumber expects f64, got {other:?}"
96 )));
97 }
98 };
99 let parsed = integer_number(n, "bigint.fromNumber")?;
100 let (sign, magnitude) = parsed.into_parts();
101 let limbs = magnitude.to_u64_digits();
102 let arr = write_limbs(&mut *caller, &limbs)?;
103 *abi_result(results, 0)? = Val::I32(sign_to_i32(sign));
104 *abi_result(results, 1)? = Val::AnyRef(Some(arr.to_anyref()));
105 Ok(())
106 },
107 )?;
108
109 let to_string_ty = FuncType::new(
110 &engine,
111 [ValType::I32, raw_bigint_param.clone()],
112 [raw_string_result.clone()],
113 );
114 register_host_fn(
115 linker,
116 BIGINT_MODULE_NAME,
117 crate::mangle::host(BIGINT_MODULE_NAME, "toString"),
118 to_string_ty,
119 true,
120 |caller, params, results| -> wasmtime::Result<()> {
121 let value = read_bigint_arg(
122 &mut *caller,
123 abi_arg(params, 0)?,
124 abi_arg(params, 1)?,
125 "bigint.toString",
126 )?;
127 let formatted = format_bigint(caller, &value, 10)?;
128 let arr = write_submilli_string(&mut *caller, &formatted)?;
129 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
130 Ok(())
131 },
132 )?;
133
134 let to_string_radix_ty = FuncType::new(
135 &engine,
136 [ValType::I32, raw_bigint_param.clone(), ValType::F64],
137 [raw_string_result.clone()],
138 );
139 register_host_fn(
140 linker,
141 BIGINT_MODULE_NAME,
142 crate::mangle::host(BIGINT_MODULE_NAME, "toStringRadix"),
143 to_string_radix_ty,
144 true,
145 |caller, params, results| -> wasmtime::Result<()> {
146 let value = read_bigint_arg(
147 &mut *caller,
148 abi_arg(params, 0)?,
149 abi_arg(params, 1)?,
150 "bigint.toStringRadix",
151 )?;
152 let radix = match *abi_arg(params, 2)? {
153 Val::F64(bits) => f64::from_bits(bits),
154 ref other => {
155 return Err(type_error(format!(
156 "bigint.toStringRadix expects f64 radix, got {other:?}"
157 )));
158 }
159 };
160 let truncated = radix.trunc();
161 if radix.is_nan() || !(2.0..=36.0).contains(&truncated) {
162 return Err(range_error("toString radix must be between 2 and 36"));
163 }
164 let formatted = format_bigint(caller, &value, truncated as u32)?;
165 let arr = write_submilli_string(&mut *caller, &formatted)?;
166 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
167 Ok(())
168 },
169 )?;
170
171 let binop_ty = FuncType::new(
172 &engine,
173 [
174 ValType::I32,
175 raw_bigint_param.clone(),
176 ValType::I32,
177 raw_bigint_param.clone(),
178 ],
179 [ValType::I32, raw_bigint_result.clone()],
180 );
181 for (name, op, cost) in [
182 ("add", BinOp::Add, Cost::Linear),
183 ("sub", BinOp::Sub, Cost::Linear),
184 ("mul", BinOp::Mul, Cost::Product),
185 ] {
186 register_host_fn(
187 linker,
188 BIGINT_MODULE_NAME,
189 crate::mangle::host(BIGINT_MODULE_NAME, name),
190 binop_ty.clone(),
191 true,
192 move |caller, params, results| -> wasmtime::Result<()> {
193 run_binop(&mut *caller, params, results, name, cost, |a, b| {
194 Ok(match op {
195 BinOp::Add => a + b,
196 BinOp::Sub => a - b,
197 BinOp::Mul => a * b,
198 })
199 })
200 },
201 )?;
202 }
203 for (name, kind) in [("div", DivKind::Div), ("mod", DivKind::Rem)] {
204 register_host_fn(
205 linker,
206 BIGINT_MODULE_NAME,
207 crate::mangle::host(BIGINT_MODULE_NAME, name),
208 binop_ty.clone(),
209 true,
210 move |caller, params, results| -> wasmtime::Result<()> {
211 use num_traits::Zero;
212 let divisor = read_bigint_arg(
213 caller,
214 abi_arg(params, 2)?,
215 abi_arg(params, 3)?,
216 &format!("{name} rhs"),
217 )?;
218 if divisor.is_zero() {
219 return Err(range_error("Division by zero"));
220 }
221 let dividend =
222 read_bigint_arg(caller, ¶ms[0], ¶ms[1], &format!("{name} lhs"))?;
223 fuel::charge_host_fuel(&mut *caller, Cost::Product.of(÷nd, &divisor)?)?;
224 let result = match kind {
225 DivKind::Div => dividend / divisor,
226 DivKind::Rem => dividend % divisor,
227 };
228 write_binop_result(caller, results, result)
229 },
230 )?;
231 }
232
233 register_host_fn(
235 linker,
236 BIGINT_MODULE_NAME,
237 crate::mangle::host(BIGINT_MODULE_NAME, "pow"),
238 binop_ty.clone(),
239 true,
240 |caller, params, results| -> wasmtime::Result<()> {
241 run_binop(
242 &mut *caller,
243 params,
244 results,
245 "pow",
246 Cost::Pow,
247 |base, exp| {
248 use num_traits::{Signed, ToPrimitive};
249 if exp.is_negative() {
250 return Err(range_error("bigint.pow: exponent must be non-negative"));
251 }
252 let exp_u32 = exp.to_u32().ok_or_else(|| {
253 range_error("bigint.pow: exponent too large to fit in u32")
254 })?;
255 Ok(base.pow(exp_u32))
256 },
257 )
258 },
259 )?;
260
261 let cmp_ty = FuncType::new(
262 &engine,
263 [
264 ValType::I32,
265 raw_bigint_param.clone(),
266 ValType::I32,
267 raw_bigint_param.clone(),
268 ],
269 [ValType::I32],
270 );
271 register_host_fn(
272 linker,
273 BIGINT_MODULE_NAME,
274 crate::mangle::host(BIGINT_MODULE_NAME, "cmp"),
275 cmp_ty,
276 true,
277 |caller, params, results| -> wasmtime::Result<()> {
278 let a = read_bigint_arg(
279 &mut *caller,
280 abi_arg(params, 0)?,
281 abi_arg(params, 1)?,
282 "bigint.cmp lhs",
283 )?;
284 let b = read_bigint_arg(
285 &mut *caller,
286 abi_arg(params, 2)?,
287 abi_arg(params, 3)?,
288 "bigint.cmp rhs",
289 )?;
290 *abi_result(results, 0)? = Val::I32(match a.cmp(&b) {
291 std::cmp::Ordering::Less => -1,
292 std::cmp::Ordering::Equal => 0,
293 std::cmp::Ordering::Greater => 1,
294 });
295 Ok(())
296 },
297 )?;
298
299 let neg_ty = FuncType::new(
300 &engine,
301 [ValType::I32, raw_bigint_param.clone()],
302 [ValType::I32, raw_bigint_result.clone()],
303 );
304 register_host_fn(
305 linker,
306 BIGINT_MODULE_NAME,
307 crate::mangle::host(BIGINT_MODULE_NAME, "neg"),
308 neg_ty,
309 true,
310 |caller, params, results| -> wasmtime::Result<()> {
311 let sign = read_sign(abi_arg(params, 0)?)?;
312 let limbs = read_limbs_arg(&mut *caller, abi_arg(params, 1)?, "bigint.neg")?;
313 let arr = write_limbs(&mut *caller, &limbs)?;
314 *abi_result(results, 0)? = Val::I32(-sign);
315 *abi_result(results, 1)? = Val::AnyRef(Some(arr.to_anyref()));
316 Ok(())
317 },
318 )?;
319
320 let from_bigint_ty = FuncType::new(&engine, [ValType::I32, raw_bigint_param], [ValType::F64]);
321 register_host_fn(
322 linker,
323 NUMBER_MODULE_NAME,
324 crate::mangle::host(NUMBER_MODULE_NAME, "fromBigInt"),
325 from_bigint_ty,
326 true,
327 |caller, params, results| -> wasmtime::Result<()> {
328 use num_traits::ToPrimitive;
329 let value = read_bigint_arg(
330 &mut *caller,
331 abi_arg(params, 0)?,
332 abi_arg(params, 1)?,
333 "number.fromBigInt",
334 )?;
335 let n = value.to_f64().unwrap_or(f64::INFINITY);
336 *abi_result(results, 0)? = Val::F64(n.to_bits());
337 Ok(())
338 },
339 )?;
340
341 Ok(())
342}
343
344#[derive(Copy, Clone)]
345enum BinOp {
346 Add,
347 Sub,
348 Mul,
349}
350
351#[derive(Copy, Clone)]
352enum DivKind {
353 Div,
354 Rem,
355}
356
357pub(crate) const MAX_DECIMAL_INPUT_BYTES: usize = 65_536;
359const MAX_FORMAT_LIMBS: u64 = 4_096;
360
361pub(crate) fn parse_decimal(
362 caller: &mut Caller<'_, StoreData>,
363 text: &str,
364 operation: &str,
365) -> wasmtime::Result<num_bigint::BigInt> {
366 if text.len() > MAX_DECIMAL_INPUT_BYTES {
367 return Err(range_error(format!(
368 "{operation}: decimal input exceeds {MAX_DECIMAL_INPUT_BYTES} bytes; use a smaller integer"
369 )));
370 }
371 let trimmed = text.trim();
372 let digits = trimmed.len() as u64;
373 fuel::charge(
376 &mut *caller,
377 fuel::ELEM,
378 digits.saturating_mul(digits.div_ceil(19)),
379 )?;
380 trimmed.parse().map_err(|_| {
381 crate::runtime::host::syntax_error(format!(
382 "{operation}: invalid bigint literal: {trimmed:?}"
383 ))
384 })
385}
386
387pub(crate) fn format_bigint(
390 caller: &mut Caller<'_, StoreData>,
391 value: &num_bigint::BigInt,
392 radix: u32,
393) -> wasmtime::Result<String> {
394 if !(2..=36).contains(&radix) {
395 return Err(range_error("toString radix must be between 2 and 36"));
396 }
397 let limbs = value.bits().div_ceil(64);
398 if limbs > MAX_FORMAT_LIMBS {
399 return Err(range_error(format!(
400 "BigInt.toString: exceeds {MAX_FORMAT_LIMBS} limbs; format a smaller integer"
401 )));
402 }
403 let work = if radix.is_power_of_two() {
404 limbs.max(1)
405 } else {
406 limbs.max(1).saturating_mul(limbs.max(1))
407 };
408 fuel::charge(&mut *caller, fuel::ELEM, work)?;
409 Ok(value.to_str_radix(radix))
410}
411
412fn limbs_of(value: &num_bigint::BigInt) -> u64 {
414 value.magnitude().to_u64_digits().len() as u64
415}
416
417const MAX_POW_LIMBS: u64 = 1 << 16;
421
422#[derive(Copy, Clone)]
424enum Cost {
425 Linear,
427 Product,
429 Pow,
431}
432
433impl Cost {
434 fn of(self, a: &num_bigint::BigInt, b: &num_bigint::BigInt) -> wasmtime::Result<u64> {
437 let (la, lb) = (limbs_of(a), limbs_of(b));
438 Ok(match self {
439 Cost::Linear => fuel::ELEM.cost(la.max(lb)),
440 Cost::Product => fuel::bigint_product_cost(la, lb),
441 Cost::Pow => {
442 use num_traits::ToPrimitive;
443 match b.to_u32() {
446 Some(exp) => pow_cost(a, exp)?,
447 None => 0,
448 }
449 }
450 })
451 }
452}
453
454fn pow_cost(base: &num_bigint::BigInt, exp: u32) -> wasmtime::Result<u64> {
458 let bits = base.bits();
459 if bits <= 1 {
460 return Ok(fuel::ELEM.cost(1));
461 }
462 let result_limbs = bits.saturating_mul(u64::from(exp)).div_ceil(64);
463 if result_limbs > MAX_POW_LIMBS {
464 return Err(range_error("bigint.pow: result too large"));
465 }
466 Ok(fuel::bigint_product_cost(result_limbs, result_limbs))
467}
468
469fn run_binop(
470 caller: &mut Caller<'_, StoreData>,
471 params: &[Val],
472 results: &mut [Val],
473 op_name: &str,
474 cost: Cost,
475 op: impl FnOnce(num_bigint::BigInt, num_bigint::BigInt) -> wasmtime::Result<num_bigint::BigInt>,
476) -> wasmtime::Result<()> {
477 let a = read_bigint_arg(
478 caller,
479 abi_arg(params, 0)?,
480 abi_arg(params, 1)?,
481 &format!("{op_name} lhs"),
482 )?;
483 let b = read_bigint_arg(
484 caller,
485 abi_arg(params, 2)?,
486 abi_arg(params, 3)?,
487 &format!("{op_name} rhs"),
488 )?;
489 fuel::charge_host_fuel(&mut *caller, cost.of(&a, &b)?)?;
490 let r = op(a, b)?;
491 write_binop_result(caller, results, r)
492}
493
494fn write_binop_result(
495 caller: &mut Caller<'_, StoreData>,
496 results: &mut [Val],
497 value: num_bigint::BigInt,
498) -> wasmtime::Result<()> {
499 let (sign, magnitude) = value.into_parts();
500 let limbs = magnitude.to_u64_digits();
501 let arr = write_limbs(caller, &limbs)?;
502 *abi_result(results, 0)? = Val::I32(sign_to_i32(sign));
503 *abi_result(results, 1)? = Val::AnyRef(Some(arr.to_anyref()));
504 Ok(())
505}
506
507pub(crate) fn read_bigint_arg(
508 caller: &mut Caller<'_, StoreData>,
509 sign_val: &Val,
510 limbs_val: &Val,
511 name: &str,
512) -> wasmtime::Result<num_bigint::BigInt> {
513 let sign = read_sign(sign_val)?;
514 let limbs = read_limbs_arg(caller, limbs_val, name)?;
515 limbs_to_bigint(sign, &limbs)
516}
517
518pub(crate) fn limbs_to_bigint(sign: i32, limbs: &[u64]) -> wasmtime::Result<num_bigint::BigInt> {
522 let count = limbs
523 .len()
524 .checked_mul(2)
525 .ok_or_else(|| crate::runtime::host::fatal_host_error("BigInt limb count overflow"))?;
526 let mut u32s = Vec::new();
527 u32s.try_reserve_exact(count)
528 .map_err(crate::runtime::host::fatal_host_error)?;
529 for w in limbs {
530 u32s.push((*w & 0xFFFF_FFFF) as u32);
531 u32s.push((*w >> 32) as u32);
532 }
533 let magnitude = num_bigint::BigUint::from_slice(&u32s);
534 let signum = match sign {
535 v if v > 0 => num_bigint::Sign::Plus,
536 v if v < 0 => num_bigint::Sign::Minus,
537 _ => num_bigint::Sign::NoSign,
538 };
539 Ok(num_bigint::BigInt::from_biguint(signum, magnitude))
540}
541
542pub(crate) fn read_bigint_struct(
547 caller: &mut Caller<'_, StoreData>,
548 val: &Val,
549 name: &str,
550) -> wasmtime::Result<(i32, Vec<u64>)> {
551 let st: Rooted<StructRef> = match val {
552 Val::AnyRef(Some(any)) => any
553 .as_struct(&mut *caller)?
554 .ok_or_else(|| type_error(format!("{name}: receiver is not a struct")))?,
555 other => {
556 return Err(type_error(format!(
557 "{name}: expected (ref $bigint), got {other:?}"
558 )));
559 }
560 };
561 let sign = read_sign(&st.field(&mut *caller, 1)?)?;
562 let limbs_val = st.field(&mut *caller, 2)?;
563 let limbs = read_limbs_arg(caller, &limbs_val, name)?;
564 Ok((sign, limbs))
565}
566
567fn read_limbs_arg(
568 caller: &mut Caller<'_, StoreData>,
569 val: &Val,
570 name: &str,
571) -> wasmtime::Result<Vec<u64>> {
572 let arr = match val {
573 Val::AnyRef(Some(any)) => any.unwrap_array(&mut *caller)?,
574 Val::AnyRef(None) => {
575 return Err(type_error(format!("{name} limbs arg is null")));
576 }
577 other => {
578 return Err(type_error(format!(
579 "{name} expects (ref $rawBigInt), got {other:?}",
580 )));
581 }
582 };
583 let len = arr.len(&mut *caller)?;
584 fuel::charge(&mut *caller, fuel::COPY, u64::from(len).saturating_mul(8))?;
585 let mut out = Vec::new();
586 out.try_reserve_exact(len as usize)
587 .map_err(crate::runtime::host::fatal_host_error)?;
588 out.resize(len as usize, 0);
589 arr.copy_to_i64_slice(&*caller, &mut out)?;
590 Ok(out)
591}
592
593pub(crate) fn write_limbs(
594 mut ctx: impl AsContextMut<Data = StoreData>,
595 limbs: &[u64],
596) -> wasmtime::Result<Rooted<ArrayRef>> {
597 fuel::charge(&mut ctx, fuel::COPY, (limbs.len() as u64).saturating_mul(8))?;
598 let array_ty = limbs_array_type(ctx.as_context().engine());
599 let pre = ArrayRefPre::new(&mut ctx, array_ty);
600 ArrayRef::new_from_i64_slice(&mut ctx, &pre, limbs)
601}
602
603pub(crate) fn make_bigint_struct(
604 caller: &mut Caller<'_, StoreData>,
605 value: num_bigint::BigInt,
606) -> wasmtime::Result<Val> {
607 let (sign, magnitude) = value.into_parts();
608 let limbs = write_limbs(&mut *caller, &magnitude.to_u64_digits())?;
609 let intr = intrinsic_types(&mut *caller)?;
610 let vtable = crate::runtime::host::host_bigint_vtable(caller)?;
611 let pre = StructRefPre::new(&mut *caller, intr.bigint.clone());
612 let st = StructRef::new(
613 &mut *caller,
614 &pre,
615 &[
616 vtable,
617 Val::I32(sign_to_i32(sign)),
618 Val::AnyRef(Some(limbs.to_anyref())),
619 ],
620 )?;
621 Ok(Val::AnyRef(Some(st.to_anyref())))
622}
623
624pub(crate) fn sign_to_i32(sign: num_bigint::Sign) -> i32 {
625 match sign {
626 num_bigint::Sign::Minus => -1,
627 num_bigint::Sign::NoSign => 0,
628 num_bigint::Sign::Plus => 1,
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635
636 #[test]
642 fn division_and_modulo_are_registered_synchronously() {
643 let cfg = crate::runtime::RuntimeConfig::default();
644 let engine = cfg.engine().expect("engine");
645 let data = StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
646 let mut store = cfg.store_async(&engine, data).expect("store");
647 let mut linker = Linker::<StoreData>::new(&engine);
648 crate::runtime::host::install_host_functions(&mut linker).expect("install");
649
650 let zero = write_limbs(&mut store, &[]).expect("limbs");
653 let args = [
654 Val::I32(0),
655 Val::AnyRef(Some(zero.to_anyref())),
656 Val::I32(0),
657 Val::AnyRef(Some(zero.to_anyref())),
658 ];
659
660 for name in ["div", "mod"] {
661 let mangled = crate::mangle::host(BIGINT_MODULE_NAME, name);
662 let func = match linker
663 .get(&mut store, BIGINT_MODULE_NAME, mangled.as_str())
664 .unwrap_or_else(|e| panic!("{name} resolves: {e}"))
665 {
666 wasmtime::Extern::Func(func) => func,
667 other => panic!("expected a func for {name}, got {other:?}"),
668 };
669 let mut results = [Val::I32(0), Val::AnyRef(None)];
670 let err = func
671 .call(&mut store, &args, &mut results)
672 .expect_err("divide by zero");
673 assert!(
674 format!("{err}").contains("Division by zero"),
675 "{name} must run synchronously; got: {err}"
676 );
677 }
678 }
679}
680
681fn read_sign(value: &Val) -> wasmtime::Result<i32> {
682 match value {
683 Val::I32(sign @ -1..=1) => Ok(*sign),
684 _ => Err(crate::runtime::host::invariant_trap(
685 "bigint: invalid canonical sign",
686 )),
687 }
688}
689
690#[cfg(test)]
691mod sign_tests {
692 use super::*;
693 #[test]
694 fn malformed_signs_trap_before_negation() {
695 for value in [
696 Val::I32(i32::MIN),
697 Val::I32(i32::MAX),
698 Val::I32(-2),
699 Val::I32(2),
700 Val::F64(0),
701 ] {
702 assert!(read_sign(&value).unwrap_err().is::<wasmtime::Trap>());
703 }
704 for sign in [-1, 0, 1] {
705 assert_eq!(-read_sign(&Val::I32(sign)).unwrap(), -sign);
706 }
707 }
708}