From ad8e870997b490ea64ececd33955d63f0d3b265f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Tue, 9 Jun 2026 14:42:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BD=8D=E8=BF=90?= =?UTF-8?q?=E7=AE=97=E5=AD=97=E8=8A=82=E7=A0=81=E4=B8=8E=20ToInt32/ToUint3?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/emitter.rs | 24 +++- crates/oxide_compiler/src/opcode.rs | 41 +++++++ .../tests/compile_expr_tests.rs | 36 ++++++ crates/oxide_vm/src/coercion.rs | 17 +++ crates/oxide_vm/src/dispatch/bitwise.rs | 104 ++++++++++++++++++ crates/oxide_vm/src/dispatch/mod.rs | 1 + crates/oxide_vm/src/vm.rs | 52 +++++++++ crates/oxide_vm/tests/arithmetic_tests.rs | 50 +++++++++ crates/oxide_vm/tests/coercion_tests.rs | 23 ++++ 9 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_vm/src/dispatch/bitwise.rs diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 9af1e2a..35bb8c7 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -669,6 +669,12 @@ impl Compiler { BinaryOperator::Multiplication => OpCode::MUL, BinaryOperator::Division => OpCode::DIV, BinaryOperator::Remainder => OpCode::MOD, + BinaryOperator::BitwiseAnd => OpCode::BIT_AND, + BinaryOperator::BitwiseOR => OpCode::BIT_OR, + BinaryOperator::BitwiseXOR => OpCode::BIT_XOR, + BinaryOperator::ShiftLeft => OpCode::SHL, + BinaryOperator::ShiftRight => OpCode::SHR, + BinaryOperator::ShiftRightZeroFill => OpCode::USHR, BinaryOperator::Equality => OpCode::EQ, BinaryOperator::Inequality => OpCode::NEQ, BinaryOperator::LessThan => OpCode::LT, @@ -708,6 +714,11 @@ impl Compiler { ctx.emit(opcode::encode(OpCode::NOT, r, arg, 0)); Ok(r) } + UnaryOperator::BitwiseNot => { + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::BIT_NOT, r, arg, 0)); + Ok(r) + } UnaryOperator::UnaryPlus => { let r = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::UNARY_PLUS, r, arg, 0)); @@ -735,7 +746,6 @@ impl Compiler { } _ => Err("invalid delete target".into()), }, - _ => Err(format!("unsupported unary operator: {:?}", un.operator)), } } Expression::ConditionalExpression(cond) => { @@ -938,6 +948,12 @@ impl Compiler { || assign.operator == AssignmentOperator::Division || assign.operator == AssignmentOperator::Remainder || assign.operator == AssignmentOperator::Exponential + || assign.operator == AssignmentOperator::BitwiseAnd + || assign.operator == AssignmentOperator::BitwiseOR + || assign.operator == AssignmentOperator::BitwiseXOR + || assign.operator == AssignmentOperator::ShiftLeft + || assign.operator == AssignmentOperator::ShiftRight + || assign.operator == AssignmentOperator::ShiftRightZeroFill { let rhs = self.emit_expression(&assign.right, ctx)?; let name = id_ref.name.as_str(); @@ -949,6 +965,12 @@ impl Compiler { AssignmentOperator::Division => OpCode::COMPOUND_DIV, AssignmentOperator::Remainder => OpCode::COMPOUND_MOD, AssignmentOperator::Exponential => OpCode::COMPOUND_EXP, + AssignmentOperator::BitwiseAnd => OpCode::COMPOUND_AND, + AssignmentOperator::BitwiseOR => OpCode::COMPOUND_OR, + AssignmentOperator::BitwiseXOR => OpCode::COMPOUND_XOR, + AssignmentOperator::ShiftLeft => OpCode::COMPOUND_SHL, + AssignmentOperator::ShiftRight => OpCode::COMPOUND_SHR, + AssignmentOperator::ShiftRightZeroFill => OpCode::COMPOUND_USHR, _ => { return Err(format!( "compound assignment operator {:?} not supported", diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_compiler/src/opcode.rs index dd4d177..41788e7 100644 --- a/crates/oxide_compiler/src/opcode.rs +++ b/crates/oxide_compiler/src/opcode.rs @@ -122,6 +122,21 @@ pub enum OpCode { FORK = 0x70, JOIN = 0x71, + // -- Bitwise (0x80-0x8F) -- + BIT_AND = 0x80, + BIT_OR = 0x81, + BIT_XOR = 0x82, + SHL = 0x83, + SHR = 0x84, + USHR = 0x85, + BIT_NOT = 0x86, + COMPOUND_AND = 0x88, + COMPOUND_OR = 0x89, + COMPOUND_XOR = 0x8A, + COMPOUND_SHL = 0x8B, + COMPOUND_SHR = 0x8C, + COMPOUND_USHR = 0x8D, + // ── Misc (0xF0-0xFF) ── NOP = 0xF0, HALT = 0xF1, @@ -250,6 +265,19 @@ impl TryFrom for OpCode { 0x66 => Ok(OpCode::PROFILE_CALL), 0x70 => Ok(OpCode::FORK), 0x71 => Ok(OpCode::JOIN), + 0x80 => Ok(OpCode::BIT_AND), + 0x81 => Ok(OpCode::BIT_OR), + 0x82 => Ok(OpCode::BIT_XOR), + 0x83 => Ok(OpCode::SHL), + 0x84 => Ok(OpCode::SHR), + 0x85 => Ok(OpCode::USHR), + 0x86 => Ok(OpCode::BIT_NOT), + 0x88 => Ok(OpCode::COMPOUND_AND), + 0x89 => Ok(OpCode::COMPOUND_OR), + 0x8A => Ok(OpCode::COMPOUND_XOR), + 0x8B => Ok(OpCode::COMPOUND_SHL), + 0x8C => Ok(OpCode::COMPOUND_SHR), + 0x8D => Ok(OpCode::COMPOUND_USHR), 0xF0 => Ok(OpCode::NOP), 0xF1 => Ok(OpCode::HALT), 0xF2 => Ok(OpCode::TYPEOF), @@ -348,6 +376,19 @@ impl fmt::Display for OpCode { OpCode::PROFILE_CALL => "PROFILE_CALL", OpCode::FORK => "FORK", OpCode::JOIN => "JOIN", + OpCode::BIT_AND => "BIT_AND", + OpCode::BIT_OR => "BIT_OR", + OpCode::BIT_XOR => "BIT_XOR", + OpCode::SHL => "SHL", + OpCode::SHR => "SHR", + OpCode::USHR => "USHR", + OpCode::BIT_NOT => "BIT_NOT", + OpCode::COMPOUND_AND => "COMPOUND_AND", + OpCode::COMPOUND_OR => "COMPOUND_OR", + OpCode::COMPOUND_XOR => "COMPOUND_XOR", + OpCode::COMPOUND_SHL => "COMPOUND_SHL", + OpCode::COMPOUND_SHR => "COMPOUND_SHR", + OpCode::COMPOUND_USHR => "COMPOUND_USHR", OpCode::NOP => "NOP", OpCode::HALT => "HALT", OpCode::TYPEOF => "TYPEOF", diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 44c6cfe..b571a45 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -79,6 +79,12 @@ fn compile_binary_ops() { ("10 / 2", OpCode::DIV), ("7 % 3", OpCode::MOD), ("5 - 2", OpCode::SUB), + ("5 & 3", OpCode::BIT_AND), + ("5 | 2", OpCode::BIT_OR), + ("5 ^ 1", OpCode::BIT_XOR), + ("1 << 3", OpCode::SHL), + ("-8 >> 1", OpCode::SHR), + ("-1 >>> 0", OpCode::USHR), ]; for (src, expected_op) in tests { @@ -88,6 +94,15 @@ fn compile_binary_ops() { } } +#[test] +fn compile_bitwise_not() { + let module = compile_source("~0"); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::BIT_NOT), + "~0 should emit BIT_NOT opcode" + ); +} + #[test] fn compile_ternary_emits_jumps() { let module = compile_source("true ? 1 : 2"); @@ -206,6 +221,27 @@ fn compile_compound_exp() { ); } +#[test] +fn compile_compound_bitwise_ops() { + let tests = [ + ("var x=5; x&=3", OpCode::COMPOUND_AND), + ("var x=5; x|=2", OpCode::COMPOUND_OR), + ("var x=5; x^=1", OpCode::COMPOUND_XOR), + ("var x=1; x<<=5", OpCode::COMPOUND_SHL), + ("var x=-8; x>>=1", OpCode::COMPOUND_SHR), + ("var x=-1; x>>>=0", OpCode::COMPOUND_USHR), + ]; + + for (src, expected_op) in tests { + let module = compile_source(src); + assert!( + module.bytecode.iter().any(|&i| opcode::opcode(i) == expected_op), + "expected {:?} in '{src}'", + expected_op + ); + } +} + #[test] fn compile_compound_assign_no_error() { let result = std::panic::catch_unwind(|| { diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index bfb1d00..9dfed91 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -34,6 +34,23 @@ pub fn to_number(val: JsValue, string_forge: &StringForge) -> f64 { f64::NAN } +pub fn to_uint32(val: JsValue, string_forge: &StringForge) -> u32 { + let n = to_number(val, string_forge); + if n == 0.0 || !n.is_finite() { + return 0; + } + n.trunc().rem_euclid(4_294_967_296.0) as u32 +} + +pub fn to_int32(val: JsValue, string_forge: &StringForge) -> i32 { + let int = to_uint32(val, string_forge); + if int > i32::MAX as u32 { + (int as i64 - 4_294_967_296i64) as i32 + } else { + int as i32 + } +} + pub fn to_string(string_forge: &StringForge, val: JsValue) -> String { if val.is_int() { return val.as_int().to_string(); diff --git a/crates/oxide_vm/src/dispatch/bitwise.rs b/crates/oxide_vm/src/dispatch/bitwise.rs new file mode 100644 index 0000000..388f410 --- /dev/null +++ b/crates/oxide_vm/src/dispatch/bitwise.rs @@ -0,0 +1,104 @@ +use crate::coercion; +use crate::vm::Vm; +use oxide_types::value::JsValue; + +impl Vm { + #[inline(always)] + fn write_ushr_result(&mut self, rd: usize, result: u32) { + if result <= i32::MAX as u32 { + self.regs[rd] = JsValue::int(result as i32); + } else { + self.regs[rd] = JsValue::float(result as f64); + } + } + + #[inline(always)] + pub(crate) fn dispatch_bit_and(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) & coercion::to_int32(self.regs[b], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_bit_or(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) | coercion::to_int32(self.regs[b], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_bit_xor(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[a], sf) ^ coercion::to_int32(self.regs[b], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_shl(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_int32(self.regs[a], sf); + let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F; + self.regs[rd] = JsValue::int(lhs.wrapping_shl(shift)); + } + + #[inline(always)] + pub(crate) fn dispatch_shr(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_int32(self.regs[a], sf); + let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F; + self.regs[rd] = JsValue::int(lhs >> shift); + } + + #[inline(always)] + pub(crate) fn dispatch_ushr(&mut self, rd: usize, a: usize, b: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_uint32(self.regs[a], sf); + let shift = coercion::to_uint32(self.regs[b], sf) & 0x1F; + self.write_ushr_result(rd, lhs >> shift); + } + + #[inline(always)] + pub(crate) fn dispatch_bit_not(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(!coercion::to_int32(self.regs[a], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_bit_and(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) & coercion::to_int32(self.regs[a], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_bit_or(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) | coercion::to_int32(self.regs[a], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_bit_xor(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + self.regs[rd] = JsValue::int(coercion::to_int32(self.regs[rd], sf) ^ coercion::to_int32(self.regs[a], sf)); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_shl(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_int32(self.regs[rd], sf); + let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F; + self.regs[rd] = JsValue::int(lhs.wrapping_shl(shift)); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_shr(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_int32(self.regs[rd], sf); + let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F; + self.regs[rd] = JsValue::int(lhs >> shift); + } + + #[inline(always)] + pub(crate) fn dispatch_compound_ushr(&mut self, rd: usize, a: usize) { + let sf = self.kernel.string_forge().as_ref(); + let lhs = coercion::to_uint32(self.regs[rd], sf); + let shift = coercion::to_uint32(self.regs[a], sf) & 0x1F; + self.write_ushr_result(rd, lhs >> shift); + } +} diff --git a/crates/oxide_vm/src/dispatch/mod.rs b/crates/oxide_vm/src/dispatch/mod.rs index 50991eb..bd89bd3 100644 --- a/crates/oxide_vm/src/dispatch/mod.rs +++ b/crates/oxide_vm/src/dispatch/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod arithmetic; +pub(crate) mod bitwise; pub(crate) mod call; pub(crate) mod comparison; pub(crate) mod control_flow; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 6ba5ad3..37295f0 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -657,6 +657,34 @@ impl Vm { self.regs[rd] = JsValue::float(-v); } + OpCode::BIT_AND => { + self.dispatch_bit_and(rd, a, b); + } + + OpCode::BIT_OR => { + self.dispatch_bit_or(rd, a, b); + } + + OpCode::BIT_XOR => { + self.dispatch_bit_xor(rd, a, b); + } + + OpCode::SHL => { + self.dispatch_shl(rd, a, b); + } + + OpCode::SHR => { + self.dispatch_shr(rd, a, b); + } + + OpCode::USHR => { + self.dispatch_ushr(rd, a, b); + } + + OpCode::BIT_NOT => { + self.dispatch_bit_not(rd, a); + } + OpCode::EQ => { self.dispatch_eq(rd, a, b); } @@ -1277,6 +1305,30 @@ impl Vm { self.regs[rd] = JsValue::float(l.powf(r)); } + OpCode::COMPOUND_AND => { + self.dispatch_compound_bit_and(rd, a); + } + + OpCode::COMPOUND_OR => { + self.dispatch_compound_bit_or(rd, a); + } + + OpCode::COMPOUND_XOR => { + self.dispatch_compound_bit_xor(rd, a); + } + + OpCode::COMPOUND_SHL => { + self.dispatch_compound_shl(rd, a); + } + + OpCode::COMPOUND_SHR => { + self.dispatch_compound_shr(rd, a); + } + + OpCode::COMPOUND_USHR => { + self.dispatch_compound_ushr(rd, a); + } + OpCode::TYPEOF => { self.dispatch_typeof(rd, a); } diff --git a/crates/oxide_vm/tests/arithmetic_tests.rs b/crates/oxide_vm/tests/arithmetic_tests.rs index 9f569a1..404e7e1 100644 --- a/crates/oxide_vm/tests/arithmetic_tests.rs +++ b/crates/oxide_vm/tests/arithmetic_tests.rs @@ -103,3 +103,53 @@ fn eval_compound_add_expr_value() { fn eval_compound_undefined() { assert_eq!(eval("var x; x+=1; x"), "NaN"); } + +#[test] +fn eval_bitwise_direct_ops() { + assert_eq!(eval("5 & 3"), "1"); + assert_eq!(eval("5 | 2"), "7"); + assert_eq!(eval("5 ^ 1"), "4"); + assert_eq!(eval("1 << 3"), "8"); + assert_eq!(eval("-8 >> 1"), "-4"); + assert_eq!(eval("-1 >>> 0"), "4294967295"); + assert_eq!(eval("~0"), "-1"); +} + +#[test] +fn eval_bitwise_coercion() { + assert_eq!(eval("'3' | 0"), "3"); + assert_eq!(eval("'x' | 0"), "0"); + assert_eq!(eval("undefined | 0"), "0"); + assert_eq!(eval("NaN | 0"), "0"); + assert_eq!(eval("1.9 | 0"), "1"); + assert_eq!(eval("-1.9 | 0"), "-1"); +} + +#[test] +fn eval_shift_masking() { + assert_eq!(eval("1 << 32"), "1"); + assert_eq!(eval("1 << 33"), "2"); + assert_eq!(eval("8 >> 33"), "4"); +} + +#[test] +fn eval_ushr_boundaries() { + assert_eq!(eval("2147483647 >>> 0"), "2147483647"); + assert_eq!(eval("2147483648 >>> 0"), "2147483648"); + assert_eq!(eval("-1 >>> 1"), "2147483647"); +} + +#[test] +fn eval_compound_bitwise_ops() { + assert_eq!(eval("var x=5; x &= 3; x"), "1"); + assert_eq!(eval("var x=5; x |= 2; x"), "7"); + assert_eq!(eval("var x=5; x ^= 1; x"), "4"); + assert_eq!(eval("var x=1; x <<= 5; x"), "32"); + assert_eq!(eval("var x=-8; x >>= 1; x"), "-4"); + assert_eq!(eval("var x=-1; x >>>= 0; x"), "4294967295"); +} + +#[test] +fn eval_bitwise_in_control_flow() { + assert_eq!(eval("var x=3; if ((x & 1) == 1) { 7 } else { 9 }"), "7"); +} diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs index 4be136b..0232a2c 100644 --- a/crates/oxide_vm/tests/coercion_tests.rs +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -82,3 +82,26 @@ fn test_strict_equality_int_float_equal() { fn test_strict_equality_null_undefined() { assert!(!coercion::strict_equality(JsValue::null(), JsValue::undefined())); } + +#[test] +fn test_to_int32_and_to_uint32() { + let vm = Vm::new(); + let sf = vm.kernel().string_forge().as_ref(); + let string_three = { + let (idx, hash) = vm.kernel().string_forge().intern("3"); + JsValue::string(idx, hash) + }; + let string_bad = { + let (idx, hash) = vm.kernel().string_forge().intern("x"); + JsValue::string(idx, hash) + }; + + assert_eq!(coercion::to_int32(string_three, sf), 3); + assert_eq!(coercion::to_int32(string_bad, sf), 0); + assert_eq!(coercion::to_int32(JsValue::undefined(), sf), 0); + assert_eq!(coercion::to_int32(JsValue::float(1.9), sf), 1); + assert_eq!(coercion::to_int32(JsValue::float(-1.9), sf), -1); + assert_eq!(coercion::to_int32(JsValue::float(f64::NAN), sf), 0); + assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY), sf), 0); + assert_eq!(coercion::to_uint32(JsValue::int(-1), sf), 4_294_967_295); +}