feat: 二元幂运算 opcode 支持 number 与 BigInt

- 新增 EXP opcode(0x94)与 emit 层 Exponential 分支,二元 a ** b 不再 compile error
- dispatch_exp 与 dispatch_compound_exp 共享 exp_result:IEEE-754 powf 快路径 +
  BigInt 幂(负/超 u32 指数 RangeError)+ 混合 BigInt/Number TypeError
- 复合 **= 补 BigInt 语义(此前走 f64 近似),与二元 ** 行为一致
This commit is contained in:
2026-08-15 21:17:49 +08:00
parent b706b8c506
commit c4961fae29
5 changed files with 125 additions and 8 deletions
+4
View File
@@ -193,6 +193,10 @@ define_opcodes! {
COMPOUND_EXP = 0x0B => "COMPOUND_EXP",
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A)],
pure = false, jump = false, term = false, ic = false,
// 二元幂运算 `a ** b`(三寄存器,与 SUB/MUL 同族;0x00-0x0F 已满,值用空闲槽 0x94)。
EXP = 0x94 => "EXP",
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
pure = true, jump = false, term = false, ic = false,
MOV = 0x0C => "MOV", // rd=dst, a=src(寄存器复制,区间拆分搬值)
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::Slot(Slot::A)],
pure = true, jump = false, term = false, ic = false,
+1 -1
View File
@@ -57,6 +57,7 @@ impl Emitter {
BinaryOperator::Multiplication => OpCode::MUL,
BinaryOperator::Division => OpCode::DIV,
BinaryOperator::Remainder => OpCode::MOD,
BinaryOperator::Exponential => OpCode::EXP,
BinaryOperator::BitwiseAnd => OpCode::BIT_AND,
BinaryOperator::BitwiseOR => OpCode::BIT_OR,
BinaryOperator::BitwiseXOR => OpCode::BIT_XOR,
@@ -73,7 +74,6 @@ impl Emitter {
BinaryOperator::Instanceof => OpCode::INSTANCEOF,
BinaryOperator::StrictEquality => OpCode::STRICT_EQ,
BinaryOperator::StrictInequality => OpCode::STRICT_NEQ,
_ => return Err(format!("unsupported binary operator: {:?}", bin.operator)),
};
ctx.inst(Inst::new(op, Operand::Reg(left), Operand::Reg(left), Operand::Reg(right)));
Ok(left)
+66 -7
View File
@@ -462,18 +462,77 @@ impl Vm {
Ok(())
}
/// 幂运算核心:`base ** exponent` 的 Number/BigInt 双语义,二元 `**` 与复合
/// `**=` 共用(避免两套实现漂移)。int/double 快路径与混合转数值路径都经
/// IEEE-754 `powf`(`(-2) ** 0.5` → NaN、`0 ** 0` → 1、负底数整数指数等
/// 边界由 libm pow 保证与规范一致)。
///
/// # 边界与前提
/// - BigInt 指数为负或超 u32:抛 RangeError(BigInt::exponentiate 禁止负指数,
/// 超 u32 指数会分配不可控内存,同样拒绝)。
/// - 混合 BigInt/Number → TypeError(ToNumeric 类型不一致)。
/// - 对象操作数先 ToPrimitive(coerce 后判定,包装对象如 Object(2n) 参与 BigInt 运算)。
///
/// # 副作用
/// - 可能新建 BigInt 会话值(new_bigint)。
fn exp_result(&mut self, lv: JsValue, rv: JsValue) -> Result<JsValue, String> {
if lv.is_int() && rv.is_int() {
return Ok(JsValue::float((lv.as_int() as f64).powf(rv.as_int() as f64)));
}
if lv.is_double() && rv.is_double() {
return Ok(JsValue::float(lv.as_double().powf(rv.as_double())));
}
if lv.is_bigint() && rv.is_bigint() {
let base = self.bigint_value(lv).clone();
let exp = self.bigint_value(rv).clone();
return self.bigint_exp(&base, &exp);
}
let l = self.coerce_primitive_bounded(lv, false)?;
let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
let base = self.bigint_value(l).clone();
let exp = self.bigint_value(r).clone();
return self.bigint_exp(&base, &exp);
}
if l.is_bigint() != r.is_bigint() {
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 操作数混合必须抛
// TypeError(同 ADD/SUB 抛点)。
self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions")?;
return Ok(JsValue::undefined());
}
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
Ok(JsValue::float(ln.powf(rn)))
}
/// BigInt 幂:负指数或超 u32 抛 RangeError,否则 `base.pow(exp)`。
fn bigint_exp(&mut self, base: &num_bigint::BigInt, exp: &num_bigint::BigInt) -> Result<JsValue, String> {
let exp_u32 = match u32::try_from(exp) {
Ok(e) => e,
Err(_) => {
self.raise_error_kind("RangeError", "Exponent must be positive")?;
return Ok(JsValue::undefined());
}
};
Ok(self.new_bigint(base.pow(exp_u32)))
}
/// 二元幂运算:`regs[rd] = regs[a] ** regs[b]`。
#[inline(always)]
pub(crate) fn dispatch_exp(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("EXP rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
let lv = self.regs[a];
let rv = self.regs[b];
self.regs[rd] = self.exp_result(lv, rv)?;
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_compound_exp(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("COMPOUND_EXP rd={} r{}={:?}", rd, a, self.regs[a]);
let lv = self.regs[rd];
let rv = self.regs[a];
if lv.is_int() && rv.is_int() {
self.regs[rd] = JsValue::float((lv.as_int() as f64).powf(rv.as_int() as f64));
return Ok(());
}
let l = self.coerce_number_bounded(lv)?;
let r = self.coerce_number_bounded(rv)?;
self.regs[rd] = JsValue::float(l.powf(r));
self.regs[rd] = self.exp_result(lv, rv)?;
Ok(())
}
+4
View File
@@ -1470,6 +1470,10 @@ impl Vm {
binary_arith!(self, a, b, rd, %, true);
}
OpCode::EXP => {
self.dispatch_exp(rd, a, b)?;
}
OpCode::NEG => {
self.dispatch_neg(rd, a)?;
}
+50
View File
@@ -120,6 +120,56 @@ fn eval_compound_exp() {
assert_eq!(eval("var x=2; x**=3; x"), "8");
}
#[test]
fn eval_binary_exp() {
assert_eq!(eval("2 ** 3"), "8");
assert_eq!(eval("2 ** 10"), "1024");
assert_eq!(eval("2 ** -1"), "0.5");
assert_eq!(eval("4 ** 0.5"), "2");
}
#[test]
fn eval_exp_edge_cases() {
assert_eq!(eval("0 ** 0"), "1");
assert_eq!(eval("(-2) ** 3"), "-8");
assert_eq!(eval("(-2) ** 2"), "4");
assert_eq!(eval("(-2) ** 0.5"), "NaN");
assert_eq!(eval("NaN ** 0"), "1");
assert_eq!(eval("2 ** Infinity"), "Infinity");
assert_eq!(eval("2.5 ** 2"), "6.25");
}
#[test]
fn eval_exp_bigint() {
assert_eq!(eval("2n ** 3n"), "BigInt(8)");
assert_eq!(eval("2n ** 0n"), "BigInt(1)");
assert_eq!(eval("0n ** 0n"), "BigInt(1)");
}
#[test]
fn eval_exp_bigint_negative_exponent_throws() {
// BigInt::exponentiate 禁止负指数,抛 RangeError(规范 §6.1.6.1.19)。
let err = eval("2n ** -1n");
assert!(err.contains("RangeError"), "expected RangeError, got: {err}");
}
#[test]
fn eval_exp_mixed_bigint_number_throws() {
// ToNumeric 类型不一致:Number 与 BigInt 混合幂抛 TypeError(与 ADD/SUB 同)。
let err = eval("2 ** 2n");
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
let err2 = eval("2n ** 2");
assert!(err2.contains("TypeError"), "expected TypeError, got: {err2}");
}
#[test]
fn eval_exp_compound_bigint() {
// 复合 **= 与二元 ** 共享幂语义(BigInt 结果保持 BigInt,不再降 f64)。
assert_eq!(eval("var x = 2n; x **= 3n; x"), "BigInt(8)");
let err = eval("var x = 2n; x **= 3; x");
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
}
#[test]
fn eval_compound_add_expr_value() {
assert_eq!(eval("var x=5; x+=3"), "8");