From c514ab35a3247a47e0c3cf6ef27349a0bd7ab4e1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 21 Aug 2026 22:29:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20console=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=AF=B9=E8=B1=A1=20(log/warn/error/info/deb?= =?UTF-8?q?ug/trace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/console.rs | 72 ++++++++++++++++++++ crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_kernel/src/builtin.rs | 14 ++++ crates/oxide_kernel/src/kernel.rs | 7 +- crates/oxide_vm/src/bindings/bind_console.rs | 36 ++++++++++ crates/oxide_vm/src/bindings/mod.rs | 6 ++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_builtins/src/console.rs create mode 100644 crates/oxide_vm/src/bindings/bind_console.rs diff --git a/crates/oxide_builtins/src/console.rs b/crates/oxide_builtins/src/console.rs new file mode 100644 index 00000000..6959483d --- /dev/null +++ b/crates/oxide_builtins/src/console.rs @@ -0,0 +1,72 @@ +//! Console 内置对象实现。 +//! +//! 单例全局对象,所有方法输出到 stderr(使用 eprintln!)。 +//! trace 除消息外额外打印调用栈(std::backtrace::Backtrace)。 + +use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_types::value::JsValue; + +fn multi_string(vm: &mut H, args: &[u8]) -> String { + let mut output = String::new(); + let mut first = true; + for i in 1..args.len() as usize { + if !first { + output.push(' '); + } + first = false; + let val = vm.reg(args[i]); + let part = if val.is_string() { + oxide_runtime_api::to_string(val) + } else { + let primitive = + oxide_runtime_api::to_primitive(val, oxide_runtime_api::ToPrimitiveHint::String, vm) + .unwrap_or_else(|_| JsValue::undefined()); + oxide_runtime_api::to_string(primitive) + }; + output.push_str(&part); + } + output +} + +/// `console.log(...args)`:输出消息到 stderr。 +pub fn log(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + eprintln!("{}", msg); + NativeResult::Ok(JsValue::undefined()) +} + +/// `console.warn(...args)`:输出警告到 stderr。 +pub fn warn(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + eprintln!("warn: {}", msg); + NativeResult::Ok(JsValue::undefined()) +} + +/// `console.error(...args)`:输出错误到 stderr。 +pub fn error(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + eprintln!("error: {}", msg); + NativeResult::Ok(JsValue::undefined()) +} + +/// `console.info(...args)`:输出信息到 stderr。 +pub fn info(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + eprintln!("info: {}", msg); + NativeResult::Ok(JsValue::undefined()) +} + +/// `console.debug(...args)`:输出调试消息到 stderr。 +pub fn debug(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + eprintln!("debug: {}", msg); + NativeResult::Ok(JsValue::undefined()) +} + +/// `console.trace(...args)`:输出跟踪消息及调用栈到 stderr。 +pub fn trace(vm: &mut H, args: &[u8]) -> NativeResult { + let msg = multi_string(vm, args); + let backtrace = std::backtrace::Backtrace::capture(); + eprintln!("trace: {}\n{:?}", msg, backtrace); + NativeResult::Ok(JsValue::undefined()) +} diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index 3968ab53..bd6e2bb4 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -17,6 +17,8 @@ pub mod bigint; pub mod boolean; /// builtins 层日志宏(`builtins_error`/`builtins_info` 等),target 为 `oxide::builtins`。 pub mod builtins_log; +/// Console 全局对象实现(log/warn/error/info/debug/trace 方法)。 +pub mod console; /// DataView 内置对象实现(对 ArrayBuffer 的定点读写视图)。 pub mod data_view; /// Date 内置对象实现(时间戳存取、get/set 系列与 to*String 系列)。 diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index e3867abb..e2f08274 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -294,6 +294,7 @@ pub struct BuiltinWorld { /// `AsyncDisposableStack.prototype`:异步资源栈原型(形状与同步栈一致)。 pub async_disposable_stack_proto: P, pub stub_objects: Vec>, + pub console_object: P, } fn intern_label(string_forge: &PermInterner, label: &str) -> u32 { @@ -614,6 +615,9 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { set_proto_if_changed(&world.temporal_object, obj_proto_val); set_proto_if_changed(&world.temporal_now_object, obj_proto_val); + // Console 对象(单例命名空间)继承 Object.prototype。 + set_proto_if_changed(&world.console_object, obj_proto_val); + // 迭代器原型链:%IteratorPrototype% → Object.prototype;各集合迭代器原型 // → %IteratorPrototype%(next/@@iterator 方法由绑定层安装到对应原型)。 set_proto_if_changed(&world.iterator_proto, obj_proto_val); @@ -768,6 +772,7 @@ impl BuiltinWorld { BuiltinId::PlainDateTimeProto => &self.plain_date_time_proto, BuiltinId::BigIntConstructor => &self.bigint_constructor, BuiltinId::BigIntProto => &self.bigint_proto, + BuiltinId::Console => &self.console_object, } } @@ -791,6 +796,8 @@ impl BuiltinWorld { let json_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let console_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let (date_proto, date_constructor) = make_named_pair(string_forge, shape_forge, labels, "Date"); let (set_proto, set_constructor) = make_named_pair(string_forge, shape_forge, labels, "Set"); let (map_proto, map_constructor) = make_named_pair(string_forge, shape_forge, labels, "Map"); @@ -941,6 +948,7 @@ impl BuiltinWorld { disposable_stack_proto, async_disposable_stack_proto, stub_objects, + console_object, }; wire_builtin_world_links(&world); kernel_info!("BuiltinWorld initialized"); @@ -1205,6 +1213,11 @@ impl BuiltinWorld { (current.bigint_proto.clone(), current.bigint_constructor.clone()) }; let stub_objects = if dirty.stubs { Vec::new() } else { current.stub_objects.clone() }; + let console_object = if dirty.console { + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())) + } else { + current.console_object.clone() + }; // 迭代器原型与资源栈原型依赖 Object.prototype(链到其上):object 家族重建时 // 一并重建,否则旧原型链指向已释放的 object_proto。 @@ -1345,6 +1358,7 @@ impl BuiltinWorld { disposable_stack_proto, async_disposable_stack_proto, stub_objects, + console_object, }; wire_builtin_world_links(&world); world diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 1d794435..9d03e000 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -225,7 +225,7 @@ pub struct KernelSession { /// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 /// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 /// builtin 家族。 -pub const NUM_BUILTINS: usize = 89; +pub const NUM_BUILTINS: usize = 90; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 /// @@ -324,6 +324,7 @@ pub enum BuiltinId { SymAsyncDispose = 86, SymDispose = 87, SuppressedErrorProto = 88, + Console = 89, } impl BuiltinId { @@ -420,6 +421,7 @@ impl BuiltinId { BuiltinId::SymAsyncDispose, BuiltinId::SymDispose, BuiltinId::SuppressedErrorProto, + BuiltinId::Console, ]; } @@ -480,6 +482,7 @@ pub struct BuiltinDirtySet { pub temporal: bool, pub stubs: bool, pub global: bool, + pub console: bool, } impl BuiltinDirtySet { @@ -504,6 +507,7 @@ impl BuiltinDirtySet { || self.typed_array_family || self.temporal || self.stubs + || self.console } /// 是否存在任何污染(builtin world 或 global object)。 @@ -683,6 +687,7 @@ impl KernelSession { || gen(BuiltinId::BigIntConstructor) != snap(BuiltinId::BigIntConstructor) || gen(BuiltinId::BigIntProto) != snap(BuiltinId::BigIntProto), global: BuiltinSnapshot::gen(&self.global_object) != snapshot.global_object_generation, + console: gen(BuiltinId::Console) != snap(BuiltinId::Console), } } diff --git a/crates/oxide_vm/src/bindings/bind_console.rs b/crates/oxide_vm/src/bindings/bind_console.rs new file mode 100644 index 00000000..57f032ef --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_console.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use crate::bindings::bind_global_value; +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +/// 把 Console 单例对象及其方法(log/warn/error/info/debug/trace)绑定到 global。 +pub fn bind_console(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let console_ptr = session.builtin_world().console_object.as_ptr() as *mut JsObject; + let console = unsafe { &mut *console_ptr }; + + let bw = session.builtin_world(); + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + + oxide_kernel::bind_methods!( + bw, + console, + sf, + sh, + ("log", oxide_builtins::console::log::, 1), + ("warn", oxide_builtins::console::warn::, 1), + ("error", oxide_builtins::console::error::, 1), + ("info", oxide_builtins::console::info::, 1), + ("debug", oxide_builtins::console::debug::, 1), + ("trace", oxide_builtins::console::trace::, 1), + ); + + bind_global_value( + core, + global, + "console", + JsValue::from_js_object(session.builtin_world().console_object.as_ptr() as *mut JsObject), + ); +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 45d6ccb4..6d9a938c 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -52,6 +52,8 @@ pub mod bind_symbol; pub mod bind_temporal; /// 各 TypedArray 构造器与共享原型的 native 方法绑定。 pub mod bind_typed_array; +/// Console 单例对象及其方法绑定。 +pub mod bind_console; use std::sync::Arc; @@ -1064,6 +1066,9 @@ pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession if dirty.map_or(true, |d| d.stubs) { bind_bigint::bind_bigint(core, session, global); } + if dirty.map_or(true, |d| d.console) { + bind_console::bind_console(core, session, global); + } } /// 完整初始化一个 session 的内置对象(全量绑定 + `globalThis` + 快照记录)。 @@ -1077,6 +1082,7 @@ pub fn init_kernel_builtins(core: &Arc, session: &mut KernelSession) bind_reflect::bind_reflect(core, session, global); bind_global::bind_global(core, session, global); bind_global_value(core, global, "globalThis", JsValue::from_js_object(global_ptr)); + bind_console::bind_console(core, session, global); crate::test262_host::bind_test262_host(core, session, global); session.record_snapshot(); }