diff --git a/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs index 2318b62c..1c97ec62 100644 --- a/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs +++ b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs @@ -2,7 +2,10 @@ use crate::ecmascript::{ execution::{agent::JsError, Agent, JsResult}, - types::{bigint, Number, Value}, + types::{ + bigint::{self, BigInt}, + Number, Value, + }, }; use super::type_conversion::{to_primitive, PreferredType}; @@ -249,7 +252,7 @@ pub(crate) fn is_less_than( // 2. Return BigInt::lessThan(nx, ny). let nx = nx.to_bigint(agent)?; let ny = ny.to_bigint(agent)?; - return Ok(Some(bigint::less_than(agent, nx, ny))); + return Ok(Some(BigInt::less_than(agent, nx, ny))); } } diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index 25c1d1b2..80acf733 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -1,15 +1,191 @@ -mod abstract_operations; mod data; -use super::value::{BIGINT_DISCRIMINANT, SMALL_BIGINT_DISCRIMINANT}; -use crate::{heap::indexes::BigIntIndex, SmallInteger}; +use super::{ + value::{BIGINT_DISCRIMINANT, SMALL_BIGINT_DISCRIMINANT}, + Value, +}; +use crate::{ + ecmascript::execution::{agent::ExceptionType, Agent, JsResult}, + heap::{indexes::BigIntIndex, CreateHeapData, GetHeapData}, + SmallInteger, +}; -pub use abstract_operations::*; pub use data::BigIntHeapData; +/// [6.1.6.2 The BigInt Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type) +/// +/// The BigInt type represents an integer value. The value may be any size and +/// is not limited to a particular bit-width. Generally, where not otherwise +/// noted, operations are designed to return exact mathematically-based answers. +/// For binary operations, BigInts act as two's complement binary strings, with +/// negative numbers treated as having bits set infinitely to the left. #[derive(Clone, Copy)] #[repr(u8)] pub enum BigInt { BigInt(BigIntIndex) = BIGINT_DISCRIMINANT, SmallBigInt(SmallInteger) = SMALL_BIGINT_DISCRIMINANT, } + +impl BigInt { + /// ### [6.1.6.2.1 BigInt::unaryMinus ( x )](https://tc39.es/ecma262/#sec-numeric-types-bigint-unaryMinus) + /// + /// The abstract operation BigInt::unaryMinus takes argument x (a BigInt) + /// and returns a BigInt. + pub(crate) fn unary_minus(agent: &mut Agent, x: BigInt) -> BigInt { + // 1. If x is 0ℤ, return 0ℤ. + // NOTE: This is handled with the negation below. + + // 2. Return -x. + match x { + BigInt::SmallBigInt(x) => { + // We need to check if the negation will overflow. + if x.into_i64() != SmallInteger::MAX_BIGINT { + BigInt::SmallBigInt(-x) + } else { + agent.heap.create(BigIntHeapData { + data: -num_bigint_dig::BigInt::from(x.into_i64()), + }) + } + } + BigInt::BigInt(x_index) => { + let x_data = agent.heap.get(x_index); + agent.heap.create(BigIntHeapData { + data: -&x_data.data, + }) + } + } + } + + /// ### [6.1.6.2.2 BigInt::bitwiseNOT ( x )](https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseNOT) + /// + /// The abstract operation BigInt::bitwiseNOT takes argument x (a BigInt) + /// and returns a BigInt. It returns the one's complement of x. + pub(crate) fn bitwise_not(agent: &mut Agent, x: BigInt) -> BigInt { + // 1. Return -x - 1ℤ. + // NOTE: We can use the builtin bitwise not operations instead. + match x { + BigInt::SmallBigInt(x) => BigInt::SmallBigInt(!x), + BigInt::BigInt(x_index) => { + let x_data = agent.heap.get(x_index); + agent.heap.create(BigIntHeapData { + data: -&x_data.data, + }) + } + } + } + + /// ### [6.1.6.2.3 BigInt::exponentiate ( base, exponent )](https://tc39.es/ecma262/#sec-numeric-types-bigint-exponentiate) + /// + /// The abstract operation BigInt::exponentiate takes arguments base (a + /// BigInt) and exponent (a BigInt) and returns either a normal completion + /// containing a BigInt or a throw completion. + pub(crate) fn exponentiate( + agent: &mut Agent, + base: BigInt, + exponent: BigInt, + ) -> JsResult { + // 1. If exponent < 0ℤ, throw a RangeError exception. + if match exponent { + BigInt::SmallBigInt(x) if x.into_i64() < 0 => true, + BigInt::BigInt(x) => agent.heap.get(x).data < 0.into(), + _ => false, + } { + return Err( + agent.throw_exception(ExceptionType::RangeError, "exponent must be positive") + ); + } + + // TODO: 2. If base is 0ℤ and exponent is 0ℤ, return 1ℤ. + // TODO: 3. Return base raised to the power exponent. + // NOTE: The BigInt implementation does not support native + // exponentiation. + + Err(agent.throw_exception(ExceptionType::EvalError, "Unsupported operation.")) + } + + /// ### [6.1.6.2.4 BigInt::multiply ( x, y )](https://tc39.es/ecma262/#sec-numeric-types-bigint-multiply) + /// + /// The abstract operation BigInt::multiply takes arguments x (a BigInt) and + /// y (a BigInt) and returns a BigInt. + pub(crate) fn multiply(agent: &mut Agent, x: BigInt, y: BigInt) -> BigInt { + match (x, y) { + (BigInt::SmallBigInt(x), BigInt::SmallBigInt(y)) => { + let (x, y) = (x.into_i64() as i128, y.into_i64() as i128); + let result = x * y; + + if let Ok(result) = SmallInteger::try_from(result) { + BigInt::SmallBigInt(SmallInteger::try_from(result).unwrap()) + } else { + agent.heap.create(BigIntHeapData { + data: result.into(), + }) + } + } + (BigInt::SmallBigInt(x), BigInt::BigInt(y)) + | (BigInt::BigInt(y), BigInt::SmallBigInt(x)) => { + let x = x.into_i64(); + let y = agent.heap.get(y); + agent.heap.create(BigIntHeapData { data: x * &y.data }) + } + (BigInt::BigInt(x), BigInt::BigInt(y)) => { + let (x, y) = (agent.heap.get(x), agent.heap.get(y)); + agent.heap.create(BigIntHeapData { + data: &x.data * &y.data, + }) + } + } + } + + /// ### [6.1.6.2.12 BigInt::lessThan ( x, y )](https://tc39.es/ecma262/#sec-numeric-types-bigint-lessThan) + /// + /// The abstract operation BigInt::lessThan takes arguments x (a BigInt) and + /// y (a BigInt) and returns a Boolean. + pub(crate) fn less_than(agent: &mut Agent, x: BigInt, y: BigInt) -> bool { + // 1. If ℝ(x) < ℝ(y), return true; otherwise return false. + match (x, y) { + (BigInt::BigInt(_), BigInt::SmallBigInt(_)) => false, + (BigInt::SmallBigInt(_), BigInt::BigInt(_)) => true, + (BigInt::BigInt(b1), BigInt::BigInt(b2)) => { + let (b1, b2) = (agent.heap.get(b1), agent.heap.get(b2)); + b1.data < b2.data + } + (BigInt::SmallBigInt(b1), BigInt::SmallBigInt(b2)) => b1.into_i64() < b2.into_i64(), + } + } + + /// ### [6.1.6.2.13 BigInt::equal ( x, y )](https://tc39.es/ecma262/#sec-numeric-types-bigint-equal) + /// + /// The abstract operation BigInt::equal takes arguments x (a BigInt) and y (a + /// BigInt) and returns a Boolean. + pub(crate) fn equal(agent: &mut Agent, x: BigInt, y: BigInt) -> bool { + // 1. If ℝ(x) = ℝ(y), return true; otherwise return false. + match (x, y) { + (BigInt::BigInt(x), BigInt::BigInt(y)) => { + let (x, y) = (agent.heap.get(x), agent.heap.get(y)); + x.data == y.data + } + (BigInt::SmallBigInt(x), BigInt::SmallBigInt(y)) => x == y, + _ => false, + } + } +} + +impl TryFrom for BigInt { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::BigInt(x) => Ok(BigInt::BigInt(x)), + Value::SmallBigInt(x) => Ok(BigInt::SmallBigInt(x)), + _ => Err(()), + } + } +} + +impl From for Value { + fn from(value: BigInt) -> Value { + match value { + BigInt::BigInt(x) => Value::BigInt(x), + BigInt::SmallBigInt(x) => Value::SmallBigInt(x), + } + } +} diff --git a/nova_vm/src/ecmascript/types/language/bigint/abstract_operations.rs b/nova_vm/src/ecmascript/types/language/bigint/abstract_operations.rs deleted file mode 100644 index 9c7fbb2b..00000000 --- a/nova_vm/src/ecmascript/types/language/bigint/abstract_operations.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{ - ecmascript::{execution::Agent, types::BigInt}, - heap::GetHeapData, -}; - -/// ### [6.1.6.2.12 BigInt::lessThan ( x, y )](https://tc39.es/ecma262/#sec-numeric-types-bigint-lessThan) -/// -/// The abstract operation BigInt::lessThan takes arguments x (a BigInt) and -/// y (a BigInt) and returns a Boolean. It performs the following steps when -/// called: -pub(crate) fn less_than(agent: &mut Agent, x: BigInt, y: BigInt) -> bool { - // 1. If ℝ(x) < ℝ(y), return true; otherwise return false. - match (x, y) { - (BigInt::BigInt(_), BigInt::SmallBigInt(_)) => false, - (BigInt::SmallBigInt(_), BigInt::BigInt(_)) => true, - (BigInt::BigInt(b1), BigInt::BigInt(b2)) => { - let (b1, b2) = (agent.heap.get(b1), agent.heap.get(b2)); - b1.data < b2.data - } - (BigInt::SmallBigInt(b1), BigInt::SmallBigInt(b2)) => b1.into_i64() < b2.into_i64(), - } -} diff --git a/nova_vm/src/engine/small_integer.rs b/nova_vm/src/engine/small_integer.rs index 3af23ec8..d202ac60 100644 --- a/nova_vm/src/engine/small_integer.rs +++ b/nova_vm/src/engine/small_integer.rs @@ -46,6 +46,25 @@ impl SmallInteger { } } +impl std::ops::Neg for SmallInteger { + type Output = Self; + + /// ## Panics + /// - If the negation overflows. + fn neg(self) -> Self::Output { + Self::from_i64_unchecked(-self.into_i64()) + } +} + +impl std::ops::Not for SmallInteger { + type Output = Self; + fn not(self) -> Self::Output { + // NOTE: This is safe because the bitwise not of any number in the range + // will always be in the safe number range. + Self::from_i64_unchecked(!self.into_i64()) + } +} + impl TryFrom for SmallInteger { type Error = (); fn try_from(value: i64) -> Result { @@ -57,6 +76,17 @@ impl TryFrom for SmallInteger { } } +impl TryFrom for SmallInteger { + type Error = (); + fn try_from(value: i128) -> Result { + if (Self::MIN_BIGINT as i128..=Self::MAX_BIGINT as i128).contains(&value) { + Ok(Self::from_i64_unchecked(value as i64)) + } else { + Err(()) + } + } +} + impl TryFrom for SmallInteger { type Error = (); fn try_from(value: u64) -> Result { diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index f583e591..ac963a7e 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -34,7 +34,7 @@ use self::{ heap_constants::{ FIRST_CONSTRUCTOR_INDEX, LAST_BUILTIN_OBJECT_INDEX, LAST_WELL_KNOWN_SYMBOL_INDEX, }, - indexes::{BaseIndex, FunctionIndex, NumberIndex, ObjectIndex, StringIndex}, + indexes::{BaseIndex, BigIntIndex, FunctionIndex, NumberIndex, ObjectIndex, StringIndex}, math::initialize_math_object, number::initialize_number_heap, object::{initialize_object_heap, ObjectEntry, PropertyDescriptor}, @@ -46,8 +46,8 @@ use crate::ecmascript::{ builtins::{ArrayBufferHeapData, ArrayHeapData}, execution::{Environments, Realm, RealmIdentifier}, types::{ - BigIntHeapData, Function, FunctionHeapData, Number, NumberHeapData, Object, ObjectHeapData, - PropertyKey, String, StringHeapData, Value, + BigInt, BigIntHeapData, Function, FunctionHeapData, Number, NumberHeapData, Object, + ObjectHeapData, PropertyKey, String, StringHeapData, Value, }, }; use wtf8::{Wtf8, Wtf8Buf}; @@ -173,6 +173,13 @@ impl CreateHeapData for Heap<'_, '_> { } } +impl CreateHeapData for Heap<'_, '_> { + fn create(&mut self, data: BigIntHeapData) -> BigInt { + self.bigints.push(Some(data)); + BigInt::BigInt(BigIntIndex::last(&self.bigints)) + } +} + impl<'ctx, 'host> Heap<'ctx, 'host> { pub fn new() -> Heap<'ctx, 'host> { let mut heap = Heap {