diff --git a/BENCHMARK_BASELINE.md b/BENCHMARK_BASELINE.md new file mode 100644 index 0000000..4fe890d --- /dev/null +++ b/BENCHMARK_BASELINE.md @@ -0,0 +1,7 @@ +Test Name Wall(ms) Comp(ms) Exec(ms) Instrs GC Ct IC Rate Sess Objs Sess B +------------------------------------------------------------------------------------------------------------------------ +array 0.19 0.000 0.193 291 0 0.0% 0 0 +call 0.06 0.000 0.058 157 0 0.0% 0 0 +coercion 0.03 0.000 0.027 163 0 0.0% 0 0 +gc 0.26 0.000 0.254 343 0 100.0% 0 0 +string 0.05 0.000 0.049 115 0 100.0% 0 0 diff --git a/Cargo.lock b/Cargo.lock index 96e123d..0f441c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -136,6 +142,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "castaway" version = "0.2.4" @@ -178,6 +190,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.6.1" @@ -259,6 +298,51 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -284,6 +368,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "dashmap" version = "6.2.1" @@ -298,6 +388,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "dragonbox_ecma" version = "0.1.12" @@ -379,6 +475,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "futures-core" version = "0.3.32" @@ -428,6 +530,17 @@ dependencies = [ "wasip3", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -444,7 +557,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -454,6 +567,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -462,6 +577,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "home" version = "0.5.12" @@ -513,12 +634,32 @@ dependencies = [ "serde_core", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -576,6 +717,15 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +[[package]] +name = "lru" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "matchers" version = "0.2.0" @@ -637,6 +787,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.46" @@ -667,6 +823,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "owo-colors" version = "4.3.0" @@ -889,6 +1051,24 @@ dependencies = [ "serde_json", ] +[[package]] +name = "oxide_builtins" +version = "0.1.0" +dependencies = [ + "chrono", + "fast-float", + "indexmap", + "memchr", + "oxide_kernel", + "oxide_log", + "oxide_runtime_api", + "oxide_types", + "regex", + "ryu", + "serde_json", + "unicode-normalization", +] + [[package]] name = "oxide_bytecode" version = "0.1.0" @@ -908,14 +1088,17 @@ dependencies = [ "oxide_types", "oxide_vm", "rustyline", + "serde", + "serde_json", ] [[package]] name = "oxide_code_cache" version = "0.1.0" dependencies = [ - "dashmap", + "lru", "oxide_bytecode", + "oxide_log", ] [[package]] @@ -924,6 +1107,7 @@ version = "0.1.0" dependencies = [ "oxide_bytecode", "oxide_code_cache", + "oxide_log", "oxide_parser", "rustc-hash", "serde", @@ -939,10 +1123,18 @@ dependencies = [ "hashbrown 0.14.5", "oxide_bytecode", "oxide_code_cache", + "oxide_log", "oxide_parser", "oxide_types", "rustc-hash", +] + +[[package]] +name = "oxide_log" +version = "0.1.0" +dependencies = [ "tracing", + "tracing-appender", "tracing-subscriber", ] @@ -958,6 +1150,15 @@ dependencies = [ "oxc_syntax", ] +[[package]] +name = "oxide_runtime_api" +version = "0.1.0" +dependencies = [ + "oxide_kernel", + "oxide_types", + "ryu", +] + [[package]] name = "oxide_test262" version = "0.1.0" @@ -988,20 +1189,24 @@ version = "0.1.0" dependencies = [ "bumpalo", "chrono", + "criterion", "fast-float", "indexmap", "memchr", + "oxide_builtins", "oxide_bytecode", "oxide_compiler", "oxide_kernel", + "oxide_log", "oxide_parser", + "oxide_runtime_api", "oxide_types", "proptest", "regex", + "rustc-hash", "ryu", "serde_json", "smallvec", - "tracing", "unicode-normalization", ] @@ -1073,6 +1278,40 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1452,6 +1691,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.117" @@ -1516,6 +1761,46 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -1542,6 +1827,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -1789,6 +2087,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 0001a24..350b97b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,13 @@ members = [ "crates/oxide_parser", "crates/oxide_bytecode", "crates/oxide_code_cache", + "crates/oxide_log", "crates/oxide_compiler", "crates/oxide_types", "crates/oxide_vm", "crates/oxide_kernel", + "crates/oxide_runtime_api", + "crates/oxide_builtins", "crates/oxide_api", "crates/oxide_cli", "crates/oxide_test262", @@ -42,11 +45,13 @@ ryu = "1" memchr = "2" regex = "1" indexmap = "2" +lru = "0.18" smallvec = { version = "1", features = ["union"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } unicode-normalization = "0.1" -tracing = "0.1.44" -tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +tracing = { version = "0.1.44", features = ["max_level_trace", "release_max_level_info"] } +tracing-subscriber = { version = "0.3.23", features = ["env-filter", "registry"] } +tracing-appender = "0.2.5" [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/benchmark_baseline.json b/benchmark_baseline.json new file mode 100644 index 0000000..1c37c9d --- /dev/null +++ b/benchmark_baseline.json @@ -0,0 +1,92 @@ +[ + { + "test_name": "array", + "wall_time_us": 193, + "session_objects": 0, + "session_bytes": 0, + "epoch_objects": 1, + "epoch_bytes": 0, + "gc_trigger_count": 0, + "gc_bytes_freed": 0, + "gc_objects_scanned": 0, + "gc_collection_us": 0, + "instruction_count": 291, + "compile_time_us": 0, + "exec_time_us": 193, + "ic_hit_rate": 0.0, + "ic_hits": 0, + "ic_misses": 0 + }, + { + "test_name": "call", + "wall_time_us": 59, + "session_objects": 0, + "session_bytes": 0, + "epoch_objects": 2, + "epoch_bytes": 0, + "gc_trigger_count": 0, + "gc_bytes_freed": 0, + "gc_objects_scanned": 0, + "gc_collection_us": 0, + "instruction_count": 157, + "compile_time_us": 0, + "exec_time_us": 58, + "ic_hit_rate": 0.0, + "ic_hits": 0, + "ic_misses": 0 + }, + { + "test_name": "coercion", + "wall_time_us": 28, + "session_objects": 0, + "session_bytes": 0, + "epoch_objects": 0, + "epoch_bytes": 0, + "gc_trigger_count": 0, + "gc_bytes_freed": 0, + "gc_objects_scanned": 0, + "gc_collection_us": 0, + "instruction_count": 163, + "compile_time_us": 0, + "exec_time_us": 27, + "ic_hit_rate": 0.0, + "ic_hits": 0, + "ic_misses": 0 + }, + { + "test_name": "gc", + "wall_time_us": 255, + "session_objects": 0, + "session_bytes": 0, + "epoch_objects": 0, + "epoch_bytes": 0, + "gc_trigger_count": 0, + "gc_bytes_freed": 0, + "gc_objects_scanned": 0, + "gc_collection_us": 0, + "instruction_count": 343, + "compile_time_us": 0, + "exec_time_us": 254, + "ic_hit_rate": 1.0, + "ic_hits": 135, + "ic_misses": 0 + }, + { + "test_name": "string", + "wall_time_us": 50, + "session_objects": 0, + "session_bytes": 0, + "epoch_objects": 0, + "epoch_bytes": 0, + "gc_trigger_count": 0, + "gc_bytes_freed": 0, + "gc_objects_scanned": 0, + "gc_collection_us": 0, + "instruction_count": 115, + "compile_time_us": 0, + "exec_time_us": 49, + "ic_hit_rate": 1.0, + "ic_hits": 216, + "ic_misses": 0 + } +] \ No newline at end of file diff --git a/crates/oxide_builtins/Cargo.toml b/crates/oxide_builtins/Cargo.toml new file mode 100644 index 0000000..dea8897 --- /dev/null +++ b/crates/oxide_builtins/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "oxide_builtins" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - Generic builtin JS functions parameterized over VmHost" + +[dependencies] +oxide_runtime_api = { path = "../oxide_runtime_api" } +oxide_types = { path = "../oxide_types" } +oxide_kernel = { path = "../oxide_kernel" } +oxide_log = { path = "../oxide_log" } +fast-float.workspace = true +ryu.workspace = true +memchr.workspace = true +regex.workspace = true +serde_json.workspace = true +chrono.workspace = true +unicode-normalization.workspace = true +indexmap.workspace = true diff --git a/crates/oxide_vm/src/builtins/array.rs b/crates/oxide_builtins/src/array.rs similarity index 62% rename from crates/oxide_vm/src/builtins/array.rs rename to crates/oxide_builtins/src/array.rs index 9671747..66c123e 100644 --- a/crates/oxide_vm/src/builtins/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -2,15 +2,19 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, MAX_DENSE_PROPS}; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; + +use crate::builtins_debug; +use crate::builtins_error; macro_rules! array_ptr { ($vm:expr, $args:expr) => {{ match get_this_array_ref($vm, $vm.reg($args[0])) { Ok(ptr) => ptr, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array method: invalid receiver"); + return NativeResult::Err(err); + } } }}; } @@ -23,11 +27,11 @@ macro_rules! array_ptr_len { }}; } -fn array_type_error(vm: &mut Vm, msg: &str) -> JsValue { - crate::builtins::error::create_type_error(vm, msg) +fn array_type_error(vm: &mut H, msg: &str) -> JsValue { + crate::error::create_type_error(vm, msg) } -fn get_this_array_ref(vm: &mut Vm, val: JsValue) -> Result<*mut JsObject, JsValue> { +fn get_this_array_ref(vm: &mut H, val: JsValue) -> Result<*mut JsObject, JsValue> { if !val.is_object() { return Err(array_type_error(vm, "Array method called on incompatible receiver")); } @@ -42,7 +46,7 @@ fn get_this_array_ref(vm: &mut Vm, val: JsValue) -> Result<*mut JsObject, JsValu Ok(ptr) } -fn require_callback(vm: &mut Vm, callback_val: JsValue) -> Result { +fn require_callback(vm: &mut H, callback_val: JsValue) -> Result { if !callback_val.is_object() { return Err(array_type_error(vm, "callback is not a function")); } @@ -53,7 +57,7 @@ fn require_callback(vm: &mut Vm, callback_val: JsValue) -> Result *mut JsObject { +fn create_new_array(vm: &mut H, n: usize) -> *mut JsObject { let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; vm.alloc_object(JsObject::new_array( EMPTY_SHAPE_ID, @@ -63,25 +67,27 @@ fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject { )) } -fn array_length_arg(vm: &mut Vm, value: JsValue) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn array_length_arg(vm: &mut H, value: JsValue) -> Result { + let n = oxide_runtime_api::to_number(value); if !n.is_finite() || n < 0.0 || n.fract() != 0.0 || n > MAX_DENSE_PROPS as f64 { - return Err(crate::builtins::error::create_range_error(vm, "Invalid array length")); + return Err(crate::error::create_range_error(vm, "Invalid array length")); } Ok(n as usize) } -fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue]) -> NativeResult { +fn invoke_native_callback( + vm: &mut H, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue], +) -> NativeResult { if !callback_val.is_object() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function")); + return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function")); } let cb_ptr = callback_val.as_js_object_ptr(); if cb_ptr.is_null() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function")); + return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function")); } let cb = unsafe { &*cb_ptr }; if !cb.is_function() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "callback is not a function")); + return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function")); } match vm.call_function_sync(callback_val, this_val, cb_args) { Ok(value) => NativeResult::Ok(value), @@ -89,31 +95,31 @@ fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, } } -fn callback_error_from_text(vm: &mut Vm, err: &str) -> JsValue { +fn callback_error_from_text(vm: &mut H, err: &str) -> JsValue { let err = err.strip_prefix("uncaught ").unwrap_or(err); if let Some(msg) = err.strip_prefix("TypeError: ") { - return crate::builtins::error::create_type_error(vm, msg); + return crate::error::create_type_error(vm, msg); } if let Some(msg) = err.strip_prefix("ReferenceError: ") { - return crate::builtins::error::create_reference_error(vm, msg); + return crate::error::create_reference_error(vm, msg); } if let Some(msg) = err.strip_prefix("RangeError: ") { - return crate::builtins::error::create_range_error(vm, msg); + return crate::error::create_range_error(vm, msg); } if let Some(msg) = err.strip_prefix("SyntaxError: ") { - return crate::builtins::error::create_syntax_error(vm, msg); + return crate::error::create_syntax_error(vm, msg); } if let Some(msg) = err.strip_prefix("Error: ") { - return crate::builtins::error::create_error(vm, msg); + return crate::error::create_error(vm, msg); } - crate::builtins::error::create_error(vm, err) + crate::error::create_error(vm, err) } -fn unexpected_tail_call_error(vm: &mut Vm) -> NativeResult { - NativeResult::Err(crate::builtins::error::create_type_error(vm, "unexpected tail call in array callback")) +fn unexpected_tail_call_error(vm: &mut H) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, "unexpected tail call in array callback")) } -pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let proto_val = JsValue::from_js_object(proto); @@ -142,7 +148,7 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(arr)) } -pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_is_array(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } @@ -157,7 +163,8 @@ pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(unsafe { &*ptr }.is_array())) } -pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_push(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.push called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let mut len = unsafe { &*arr_ptr }.prop_count(); for &arg_reg in args.iter().skip(1) { @@ -169,7 +176,8 @@ pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(len as i32)) } -pub fn array_pop(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_pop(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.pop called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count(); @@ -181,17 +189,18 @@ pub fn array_pop(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(last) } -pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_slice(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.slice called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let start = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (oxide_runtime_api::to_number(vm.reg(args[1])) as i32) as usize } else { 0 }; let end = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (oxide_runtime_api::to_number(vm.reg(args[2])) as i32) as usize } else { n }; @@ -209,13 +218,14 @@ pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_splice(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.splice called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let n = arr.prop_count() as usize; let start = if args.len() > 1 { - let v = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(vm.reg(args[1])); let s = v as i32; if s < 0 { (n as i32 + s).max(0) as usize @@ -227,7 +237,7 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { }; let delete_count = if args.len() > 2 { - let v = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(vm.reg(args[2])); (v as usize).min(n - start) } else { n - start @@ -271,7 +281,8 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(removed_arr)) } -pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_concat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.concat called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; @@ -306,32 +317,33 @@ pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_join(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_join(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.join called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let sep = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + oxide_runtime_api::to_string(vm.reg(args[1])) } else { ",".to_string() }; - let sf = vm.kernel_core().string_forge().as_ref(); let parts: Vec = (0..n) .map(|i| { let v = arr.get_prop_at(i); if v.is_undefined() || v.is_null() { String::new() } else { - coercion::to_string(sf, v) + oxide_runtime_api::to_string(v) } }) .collect(); let joined = parts.join(&sep); - let result_val = vm.intern(&joined); + let result_val = vm.new_string(&joined); NativeResult::Ok(result_val) } -pub fn array_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.indexOf called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; @@ -340,14 +352,15 @@ pub fn array_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } let target = vm.reg(args[1]); for i in 0..n { - if coercion::strict_equality(arr.get_prop_at(i), target) { + if oxide_runtime_api::strict_equality(arr.get_prop_at(i), target) { return NativeResult::Ok(JsValue::int(i as i32)); } } NativeResult::Ok(JsValue::int(-1)) } -pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.includes called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; @@ -364,14 +377,15 @@ pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::bool(true)); } } - if coercion::strict_equality(elem, target) { + if oxide_runtime_api::strict_equality(elem, target) { return NativeResult::Ok(JsValue::bool(true)); } } NativeResult::Ok(JsValue::bool(false)) } -pub fn array_reverse(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.reverse called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let n = arr.prop_count() as usize; @@ -387,21 +401,22 @@ pub fn array_reverse(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[0])) } -pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.flat called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let depth = if args.len() > 1 { - let n = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()); + let n = oxide_runtime_api::to_number(vm.reg(args[1])); if !n.is_finite() { - vm.kernel_core.config.max_call_depth + vm.kernel_core().config.max_call_depth } else { (n as i32).max(1) as usize } } else { 1 } - .min(vm.kernel_core.config.max_call_depth); + .min(vm.kernel_core().config.max_call_depth); fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec { let mut out = Vec::new(); @@ -443,35 +458,51 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_for_each(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.forEach called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.forEach: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.forEach: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in 0..n { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(_) => {} - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.forEach: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.forEach: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::undefined()) } -pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_map(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.map called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.map: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.map: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let new_arr = create_new_array(vm, n); @@ -481,8 +512,14 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(mapped) => unsafe { (*new_arr).set_prop_at(i, mapped); }, - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.map: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.map: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } unsafe { @@ -491,14 +528,19 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_filter(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.filter called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.filter: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.filter: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let mut kept: Vec = Vec::new(); @@ -506,13 +548,18 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if oxide_runtime_api::to_boolean(result_val) { kept.push(elem); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.filter: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.filter: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } let new_arr = create_new_array(vm, kept.len()); @@ -525,17 +572,23 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_reduce(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.reduce called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if n == 0 && args.len() < 3 { + builtins_error!("Array.prototype.reduce: invalid receiver"); return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value")); } if args.len() < 2 { + builtins_error!("Array.prototype.reduce: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.reduce: invalid receiver"); + return NativeResult::Err(err); + } }; let has_initial = args.len() > 2; let mut accumulator; @@ -557,99 +610,140 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult { &[accumulator, elem, JsValue::int(i as i32), vm.reg(args[0])], ) { NativeResult::Ok(result) => accumulator = result, - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.reduce: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.reduce: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(accumulator) } -pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_find(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.find called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.find: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.find: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in 0..n { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if oxide_runtime_api::to_boolean(result_val) { return NativeResult::Ok(elem); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.find: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.find: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::undefined()) } -pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_some(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.some called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.some: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.some: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in 0..n { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if oxide_runtime_api::to_boolean(result_val) { return NativeResult::Ok(JsValue::bool(true)); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.some: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.some: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::bool(false)) } -pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_every(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.every called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.every: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.every: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in 0..n { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if !coercion::to_boolean(result_val, sf) { + if !oxide_runtime_api::to_boolean(result_val) { return NativeResult::Ok(JsValue::bool(false)); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.every: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.every: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::bool(true)) } -pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.flatMap called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.flatMap: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.flatMap: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; let mut flat: Vec = Vec::new(); @@ -672,8 +766,14 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { } flat.push(result); } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.flatMap: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.flatMap: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } let new_arr = create_new_array(vm, flat.len()); @@ -686,7 +786,8 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_shift(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.shift called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count(); @@ -702,7 +803,8 @@ pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(first) } -pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_unshift(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.unshift called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count(); @@ -719,18 +821,19 @@ pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(new_len as i32)) } -pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_fill(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.fill called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count() as usize; let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let start = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32).max(0) as usize + (oxide_runtime_api::to_number(vm.reg(args[2])) as i32).max(0) as usize } else { 0 }; let end = if args.len() > 3 { - let e = coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as i32; + let e = oxide_runtime_api::to_number(vm.reg(args[3])) as i32; (e as usize).min(len) } else { len @@ -741,7 +844,8 @@ pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[0])) } -pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_copy_within(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.copyWithin called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count() as usize; @@ -749,17 +853,17 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { return NativeResult::Ok(vm.reg(args[0])); } let target = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (oxide_runtime_api::to_number(vm.reg(args[1])) as i32) as usize } else { 0 }; let start = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (oxide_runtime_api::to_number(vm.reg(args[2])) as i32) as usize } else { 0 }; let end = if args.len() > 3 { - (coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as i32 as usize).min(len) + (oxide_runtime_api::to_number(vm.reg(args[3])) as i32 as usize).min(len) } else { len }; @@ -775,12 +879,13 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[0])) } -pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_at(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.at called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let len = arr.prop_count() as i32; let mut index = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32 + oxide_runtime_api::to_number(vm.reg(args[1])) as i32 } else { 0 }; @@ -793,85 +898,114 @@ pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(arr.get_prop_at(index)) } -pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let len = arr.prop_count() as i32; let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; for i in (0..len).rev() { - if coercion::strict_equality(arr.get_prop_at(i), search) { + if oxide_runtime_api::strict_equality(arr.get_prop_at(i), search) { return NativeResult::Ok(JsValue::int(i)); } } NativeResult::Ok(JsValue::int(-1)) } -pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_find_index(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.findIndex called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.findIndex: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.findIndex: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in 0..n { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(r) => { - if coercion::to_boolean(r, vm.kernel_core().string_forge().as_ref()) { + if oxide_runtime_api::to_boolean(r) { return NativeResult::Ok(JsValue::int(i as i32)); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.findIndex: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.findIndex: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::int(-1)) } -pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_find_last(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.findLast called with {} args", args.len()); let (arr_ptr, n) = { let (arr_ptr, len) = array_ptr_len!(vm, args); (arr_ptr, len as i32) }; if args.len() < 2 { + builtins_error!("Array.prototype.findLast: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.findLast: invalid receiver"); + return NativeResult::Err(err); + } }; let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; for i in (0..n).rev() { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), vm.reg(args[0])]) { NativeResult::Ok(r) => { - if coercion::to_boolean(r, vm.kernel_core().string_forge().as_ref()) { + if oxide_runtime_api::to_boolean(r) { return NativeResult::Ok(elem); } } - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.findLast: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.findLast: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(JsValue::undefined()) } -pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_reduce_right(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.reduceRight called with {} args", args.len()); let (arr_ptr, n) = array_ptr_len!(vm, args); if args.len() < 2 { + builtins_error!("Array.prototype.reduceRight: invalid receiver"); return NativeResult::Err(array_type_error(vm, "callback is not a function")); } let callback_val = match require_callback(vm, vm.reg(args[1])) { Ok(callback) => callback, - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.reduceRight: invalid receiver"); + return NativeResult::Err(err); + } }; let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 { (vm.reg(args[2]), n as i32 - 1) } else { if n == 0 { + builtins_error!("Array.prototype.reduceRight: invalid receiver"); return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value")); } (unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2) @@ -885,14 +1019,21 @@ pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult { &[acc, elem, JsValue::int(i), vm.reg(args[0])], ) { NativeResult::Ok(r) => acc = r, - NativeResult::Err(err) => return NativeResult::Err(err), - NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm), + NativeResult::Err(err) => { + builtins_error!("Array.prototype.reduceRight: invalid receiver"); + return NativeResult::Err(err); + } + NativeResult::TailCall { .. } => { + builtins_error!("Array.prototype.reduceRight: invalid receiver"); + return unexpected_tail_call_error(vm); + } } } NativeResult::Ok(acc) } -pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_sort(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.sort called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); let len = unsafe { (*arr_ptr).prop_count() as usize }; let mut vals: Vec = (0..len).map(|i| unsafe { (*arr_ptr).get_prop_at(i) }).collect(); @@ -903,7 +1044,10 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { match require_callback(vm, candidate) { Ok(callback) => Some(callback), - Err(err) => return NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.sort: invalid receiver"); + return NativeResult::Err(err); + } } } } else { @@ -917,7 +1061,7 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { if let Some(callback) = comparator { match invoke_native_callback(vm, callback, JsValue::undefined(), &[*a, *b]) { NativeResult::Ok(result) => { - let n = coercion::to_number(result, vm.kernel_core().string_forge().as_ref()); + let n = oxide_runtime_api::to_number(result); if n.is_nan() || n == 0.0 { std::cmp::Ordering::Equal } else if n < 0.0 { @@ -931,18 +1075,18 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { std::cmp::Ordering::Equal } NativeResult::TailCall { .. } => { - sort_error = - Some(crate::builtins::error::create_type_error(vm, "unexpected tail call in array callback")); + sort_error = Some(crate::error::create_type_error(vm, "unexpected tail call in array callback")); std::cmp::Ordering::Equal } } } else { - let sa = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *a); - let sb = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *b); + let sa = oxide_runtime_api::to_string(*a); + let sb = oxide_runtime_api::to_string(*b); sa.cmp(&sb) } }); if let Some(err) = sort_error { + builtins_error!("Array.prototype.sort: invalid receiver"); return NativeResult::Err(err); } let arr = unsafe { &mut *arr_ptr }; @@ -952,10 +1096,14 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[0])) } -pub fn array_values(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_values(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.values called with {} args", args.len()); let this_val = vm.reg(args[0]); - match crate::builtins::iterator::make_iterator_for_value(vm, this_val) { + match crate::iterator::make_iterator_for_value(vm, this_val) { Ok(iterator) => NativeResult::Ok(iterator), - Err(err) => NativeResult::Err(err), + Err(err) => { + builtins_error!("Array.prototype.values: invalid receiver"); + NativeResult::Err(err) + } } } diff --git a/crates/oxide_vm/src/builtins/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs similarity index 67% rename from crates/oxide_vm/src/builtins/array_buffer.rs rename to crates/oxide_builtins/src/array_buffer.rs index 93e7102..3b928ba 100644 --- a/crates/oxide_vm/src/builtins/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -2,9 +2,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30; @@ -17,23 +15,23 @@ macro_rules! native_try { }; } -fn type_error(vm: &mut Vm, msg: &str) -> NativeResult { - NativeResult::Err(crate::builtins::error::create_type_error(vm, msg)) +fn type_error(vm: &mut H, msg: &str) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, msg)) } -fn to_index(vm: &mut Vm, value: JsValue) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn to_index(vm: &mut H, value: JsValue) -> Result { + let n = oxide_runtime_api::to_number(value); if n.is_nan() { return Ok(0); } if !n.is_finite() || n < 0.0 || n > MAX_ARRAY_BUFFER_LENGTH as f64 { - return Err(crate::builtins::error::create_range_error(vm, "invalid ArrayBuffer length")); + return Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); } Ok(n.trunc() as usize) } -fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn normalize_index(_vm: &mut H, value: JsValue, len: usize) -> usize { + let n = oxide_runtime_api::to_number(value); if n.is_nan() { return 0; } @@ -45,12 +43,12 @@ fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { } } -fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; +fn set_named_prop(vm: &mut H, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } -pub(crate) fn new_array_buffer(vm: &mut Vm, data: Vec) -> *mut JsObject { +pub(crate) fn new_array_buffer(vm: &mut H, data: Vec) -> *mut JsObject { let proto = vm.session().builtin_world().array_buffer_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; @@ -76,7 +74,7 @@ fn array_buffer_vec_ptr(obj: &JsObject) -> Option<*mut Vec> { obj.native_fn().map(|ptr| ptr.as_ptr() as *mut Vec) } -pub(crate) fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) { +pub fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) { let Some(ptr) = array_buffer_vec_ptr(old_obj) else { return; }; @@ -88,7 +86,7 @@ pub(crate) fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObje new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) })); } -pub(crate) fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { +pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { let Some(data_ptr) = array_buffer_vec_ptr(obj) else { return 0; }; @@ -102,35 +100,29 @@ pub(crate) fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { bytes as u64 } -pub(crate) fn array_buffer_data_ptr(vm: &mut Vm, this_val: JsValue) -> Result<*mut Vec, JsValue> { +pub(crate) fn array_buffer_data_ptr(vm: &mut H, this_val: JsValue) -> Result<*mut Vec, JsValue> { if !this_val.is_object() { - return Err(crate::builtins::error::create_type_error( - vm, - "ArrayBuffer method called on incompatible receiver", - )); + return Err(crate::error::create_type_error(vm, "ArrayBuffer method called on incompatible receiver")); } let obj_ptr = this_val.as_js_object_ptr(); if obj_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); } let obj = unsafe { &*obj_ptr }; if !obj.is_array_buffer_obj() { - return Err(crate::builtins::error::create_type_error( - vm, - "ArrayBuffer method called on incompatible receiver", - )); + return Err(crate::error::create_type_error(vm, "ArrayBuffer method called on incompatible receiver")); } let Some(data_ptr) = obj.native_fn() else { - return Err(crate::builtins::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); }; let data_ptr = data_ptr.as_ptr() as *mut Vec; if data_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); } Ok(data_ptr) } -pub fn array_buffer_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_buffer_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let length = if args.len() > 1 { match to_index(vm, vm.reg(args[1])) { Ok(length) => length, @@ -142,13 +134,13 @@ pub fn array_buffer_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, vec![0; length]))) } -pub fn array_buffer_byte_length(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_buffer_byte_length(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let data_ptr = native_try!(array_buffer_data_ptr(vm, this_val)); NativeResult::Ok(JsValue::int(unsafe { (*data_ptr).len() } as i32)) } -pub fn array_buffer_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let data_ptr = native_try!(array_buffer_data_ptr(vm, this_val)); let data = unsafe { &*data_ptr }; @@ -159,7 +151,7 @@ pub fn array_buffer_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, data[start..end].to_vec()))) } -pub fn array_buffer_is_view(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_buffer_is_view(vm: &mut H, args: &[u8]) -> NativeResult { let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; if !value.is_object() { return NativeResult::Ok(JsValue::bool(false)); @@ -172,10 +164,10 @@ pub fn array_buffer_is_view(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(obj.is_data_view_obj() || obj.is_typed_array_obj())) } -pub fn array_buffer_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn array_buffer_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if array_buffer_data_ptr(vm, this_val).is_err() { return type_error(vm, "ArrayBuffer.prototype.toString called on incompatible receiver"); } - NativeResult::Ok(vm.intern("[object ArrayBuffer]")) + NativeResult::Ok(vm.new_string("[object ArrayBuffer]")) } diff --git a/crates/oxide_vm/src/builtins/boolean.rs b/crates/oxide_builtins/src/boolean.rs similarity index 72% rename from crates/oxide_vm/src/builtins/boolean.rs rename to crates/oxide_builtins/src/boolean.rs index 56f4164..c2e4162 100644 --- a/crates/oxide_vm/src/builtins/boolean.rs +++ b/crates/oxide_builtins/src/boolean.rs @@ -1,10 +1,9 @@ use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn boolean_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let bool_val = if args.len() > 1 { let arg = vm.reg(args[1]); @@ -13,12 +12,8 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else if arg.is_bool() { arg.as_bool() } else if arg.is_string() { - let s = vm - .kernel_core() - .string_forge() - .lookup(arg.as_string_index()) - .unwrap_or_default(); - !s.is_empty() + // SAFETY: arg is a string value. + !unsafe { (*arg.as_string_ptr()).is_empty() } } else if arg.is_object() { true } else if arg.is_int() { @@ -55,20 +50,20 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn boolean_prototype_value_of(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if this_val.is_bool() { return NativeResult::Ok(this_val); } if !this_val.is_object() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Boolean.prototype.valueOf called on non-Boolean object", )); } let obj = unsafe { &*this_val.as_js_object_ptr() }; if !obj.is_boolean_obj() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Boolean.prototype.valueOf called on non-Boolean object", )); @@ -77,24 +72,24 @@ pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(val) } -pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn boolean_prototype_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if this_val.is_bool() { return if this_val.as_bool() { - NativeResult::Ok(vm.intern("true")) + NativeResult::Ok(vm.new_string("true")) } else { - NativeResult::Ok(vm.intern("false")) + NativeResult::Ok(vm.new_string("false")) }; } if !this_val.is_object() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Boolean.prototype.toString called on non-Boolean object", )); } let obj = unsafe { &*this_val.as_js_object_ptr() }; if !obj.is_boolean_obj() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Boolean.prototype.toString called on non-Boolean object", )); @@ -106,8 +101,8 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { !val.is_null() && !val.is_undefined() }; if is_true { - NativeResult::Ok(vm.intern("true")) + NativeResult::Ok(vm.new_string("true")) } else { - NativeResult::Ok(vm.intern("false")) + NativeResult::Ok(vm.new_string("false")) } } diff --git a/crates/oxide_builtins/src/builtins_log.rs b/crates/oxide_builtins/src/builtins_log.rs new file mode 100644 index 0000000..f19c853 --- /dev/null +++ b/crates/oxide_builtins/src/builtins_log.rs @@ -0,0 +1,34 @@ +#[macro_export] +macro_rules! builtins_error { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::builtins", oxide_log::tracing::Level::ERROR, $($arg)*) + }; +} + +#[macro_export] +macro_rules! builtins_warn { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::builtins", oxide_log::tracing::Level::WARN, $($arg)*) + }; +} + +#[macro_export] +macro_rules! builtins_info { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::builtins", oxide_log::tracing::Level::INFO, $($arg)*) + }; +} + +#[macro_export] +macro_rules! builtins_debug { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::builtins", oxide_log::tracing::Level::DEBUG, $($arg)*) + }; +} + +#[macro_export] +macro_rules! builtins_trace { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::builtins", oxide_log::tracing::Level::TRACE, $($arg)*) + }; +} diff --git a/crates/oxide_vm/src/builtins/data_view.rs b/crates/oxide_builtins/src/data_view.rs similarity index 65% rename from crates/oxide_vm/src/builtins/data_view.rs rename to crates/oxide_builtins/src/data_view.rs index c642a50..2550048 100644 --- a/crates/oxide_vm/src/builtins/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -2,10 +2,9 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; -use crate::builtins::array_buffer::array_buffer_data_ptr; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use crate::array_buffer::array_buffer_data_ptr; + +use oxide_runtime_api::{NativeResult, VmHost}; #[derive(Clone, Copy)] pub(crate) struct DataViewData { @@ -23,73 +22,69 @@ macro_rules! native_try { }; } -fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn to_index(vm: &mut H, value: JsValue, msg: &str) -> Result { + let n = oxide_runtime_api::to_number(value); if n.is_nan() { return Ok(0); } if !n.is_finite() || n < 0.0 { - return Err(crate::builtins::error::create_range_error(vm, msg)); + return Err(crate::error::create_range_error(vm, msg)); } Ok(n.trunc() as usize) } -fn is_little_endian(vm: &mut Vm, args: &[u8], idx: usize) -> bool { +fn is_little_endian(vm: &mut H, args: &[u8], idx: usize) -> bool { args.get(idx) - .map(|reg| coercion::to_boolean(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) + .map(|reg| oxide_runtime_api::to_boolean(vm.reg(*reg))) .unwrap_or(false) } -fn numeric_arg(vm: &mut Vm, args: &[u8], idx: usize) -> f64 { +fn numeric_arg(vm: &mut H, args: &[u8], idx: usize) -> f64 { args.get(idx) - .map(|reg| coercion::to_number(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) + .map(|reg| oxide_runtime_api::to_number(vm.reg(*reg))) .unwrap_or(0.0) } -fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; +fn set_named_prop(vm: &mut H, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } -fn get_data_view_data(vm: &mut Vm, this_val: JsValue) -> Result { +fn get_data_view_data(vm: &mut H, this_val: JsValue) -> Result { if !this_val.is_object() { - return Err(crate::builtins::error::create_type_error( - vm, - "DataView method called on incompatible receiver", - )); + return Err(crate::error::create_type_error(vm, "DataView method called on incompatible receiver")); } let obj_ptr = this_val.as_js_object_ptr(); if obj_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + return Err(crate::error::create_type_error(vm, "DataView internal state invalid")); } let obj = unsafe { &*obj_ptr }; if !obj.is_data_view_obj() { - return Err(crate::builtins::error::create_type_error( - vm, - "DataView method called on incompatible receiver", - )); + return Err(crate::error::create_type_error(vm, "DataView method called on incompatible receiver")); } let Some(data_ptr) = obj.native_fn() else { - return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + return Err(crate::error::create_type_error(vm, "DataView internal state invalid")); }; let data_ptr = data_ptr.as_ptr() as *const DataViewData; if data_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "DataView internal state invalid")); + return Err(crate::error::create_type_error(vm, "DataView internal state invalid")); } Ok(unsafe { *data_ptr }) } -fn checked_absolute_offset(vm: &mut Vm, view: DataViewData, offset: usize, width: usize) -> Result { +fn checked_absolute_offset( + vm: &mut H, view: DataViewData, offset: usize, width: usize, +) -> Result { let Some(end) = offset.checked_add(width) else { - return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); }; if end > view.byte_length { - return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); } Ok(view.byte_offset + offset) } -fn read_bytes(vm: &mut Vm, args: &[u8]) -> Result<[u8; N], JsValue> { +fn read_bytes(vm: &mut H, args: &[u8]) -> Result<[u8; N], JsValue> { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = get_data_view_data(vm, this_val)?; let offset = if args.len() > 1 { @@ -101,14 +96,14 @@ fn read_bytes(vm: &mut Vm, args: &[u8]) -> Result<[u8; N], JsVal let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; let buffer = unsafe { &*buffer_ptr }; if abs + N > buffer.len() { - return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); } let mut out = [0u8; N]; out.copy_from_slice(&buffer[abs..abs + N]); Ok(out) } -fn write_bytes(vm: &mut Vm, args: &[u8], bytes: [u8; N]) -> Result<(), JsValue> { +fn write_bytes(vm: &mut H, args: &[u8], bytes: [u8; N]) -> Result<(), JsValue> { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = get_data_view_data(vm, this_val)?; let offset = if args.len() > 1 { @@ -120,15 +115,15 @@ fn write_bytes(vm: &mut Vm, args: &[u8], bytes: [u8; N]) -> Resu let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?; let buffer = unsafe { &mut *buffer_ptr }; if abs + N > buffer.len() { - return Err(crate::builtins::error::create_range_error(vm, "DataView offset out of bounds")); + return Err(crate::error::create_range_error(vm, "DataView offset out of bounds")); } buffer[abs..abs + N].copy_from_slice(&bytes); Ok(()) } -pub fn data_view_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_constructor(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "DataView requires an ArrayBuffer")); + return NativeResult::Err(crate::error::create_type_error(vm, "DataView requires an ArrayBuffer")); } let buffer = vm.reg(args[1]); let buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer)); @@ -139,7 +134,7 @@ pub fn data_view_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { 0 }; if byte_offset > buffer_len { - return NativeResult::Err(crate::builtins::error::create_range_error(vm, "DataView byteOffset out of bounds")); + return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteOffset out of bounds")); } let default_len = buffer_len - byte_offset; let byte_length = if args.len() > 3 { @@ -148,7 +143,7 @@ pub fn data_view_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { default_len }; if byte_offset + byte_length > buffer_len { - return NativeResult::Err(crate::builtins::error::create_range_error(vm, "DataView byteLength out of bounds")); + return NativeResult::Err(crate::error::create_range_error(vm, "DataView byteLength out of bounds")); } let proto = vm.session().builtin_world().data_view_proto.as_ptr() as *mut JsObject; @@ -187,7 +182,7 @@ fn data_view_data_ptr(obj: &JsObject) -> Option<*mut DataViewData> { obj.native_fn().map(|ptr| ptr.as_ptr() as *mut DataViewData) } -pub(crate) fn data_view_native_edges(obj: &JsObject) -> Vec { +pub fn data_view_native_edges(obj: &JsObject) -> Vec { let Some(ptr) = data_view_data_ptr(obj) else { return Vec::new(); }; @@ -202,7 +197,7 @@ pub(crate) fn data_view_native_edges(obj: &JsObject) -> Vec { } } -pub(crate) fn clone_data_view_native_with_rewrite(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F) +pub fn clone_data_view_native_with_rewrite(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -218,7 +213,7 @@ where new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned as *const ()) })); } -pub(crate) fn rewrite_data_view_native(obj: &mut JsObject, mut rewrite: F) +pub fn rewrite_data_view_native(obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -233,7 +228,7 @@ where } } -pub(crate) fn drop_data_view_native(obj: &mut JsObject) -> u64 { +pub fn drop_data_view_native(obj: &mut JsObject) -> u64 { let Some(ptr) = data_view_data_ptr(obj) else { return 0; }; @@ -245,18 +240,18 @@ pub(crate) fn drop_data_view_native(obj: &mut JsObject) -> u64 { std::mem::size_of::() as u64 } -pub fn data_view_get_int8(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<1>(vm, args)); +pub fn data_view_get_int8(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<1, H>(vm, args)); NativeResult::Ok(JsValue::int(i8::from_ne_bytes(bytes) as i32)) } -pub fn data_view_get_uint8(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<1>(vm, args)); +pub fn data_view_get_uint8(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<1, H>(vm, args)); NativeResult::Ok(JsValue::int(bytes[0] as i32)) } -pub fn data_view_get_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<2>(vm, args)); +pub fn data_view_get_int16(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<2, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { i16::from_le_bytes(bytes) } else { @@ -265,8 +260,8 @@ pub fn data_view_get_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(n as i32)) } -pub fn data_view_get_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<2>(vm, args)); +pub fn data_view_get_uint16(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<2, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { u16::from_le_bytes(bytes) } else { @@ -275,8 +270,8 @@ pub fn data_view_get_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(n as i32)) } -pub fn data_view_get_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<4>(vm, args)); +pub fn data_view_get_int32(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { i32::from_le_bytes(bytes) } else { @@ -285,8 +280,8 @@ pub fn data_view_get_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(n)) } -pub fn data_view_get_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<4>(vm, args)); +pub fn data_view_get_uint32(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { u32::from_le_bytes(bytes) } else { @@ -295,8 +290,8 @@ pub fn data_view_get_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(n as f64)) } -pub fn data_view_get_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<4>(vm, args)); +pub fn data_view_get_float32(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<4, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { f32::from_le_bytes(bytes) } else { @@ -305,8 +300,8 @@ pub fn data_view_get_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(n as f64)) } -pub fn data_view_get_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<8>(vm, args)); +pub fn data_view_get_float64(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { f64::from_le_bytes(bytes) } else { @@ -315,8 +310,8 @@ pub fn data_view_get_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(n)) } -pub fn data_view_get_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<8>(vm, args)); +pub fn data_view_get_big_int64(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { i64::from_le_bytes(bytes) } else { @@ -325,8 +320,8 @@ pub fn data_view_get_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(n as f64)) } -pub fn data_view_get_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { - let bytes = native_try!(read_bytes::<8>(vm, args)); +pub fn data_view_get_big_uint64(vm: &mut H, args: &[u8]) -> NativeResult { + let bytes = native_try!(read_bytes::<8, H>(vm, args)); let n = if is_little_endian(vm, args, 2) { u64::from_le_bytes(bytes) } else { @@ -335,19 +330,19 @@ pub fn data_view_get_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(n as f64)) } -pub fn data_view_set_int8(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_int8(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i32 as u8 as i8; native_try!(write_bytes(vm, args, value.to_ne_bytes())); NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_uint8(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_uint8(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i32 as u8; native_try!(write_bytes(vm, args, [value])); NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_int16(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i32 as u16 as i16; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -358,7 +353,7 @@ pub fn data_view_set_int16(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_uint16(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i32 as u16; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -369,7 +364,7 @@ pub fn data_view_set_uint16(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_int32(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i32; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -380,7 +375,7 @@ pub fn data_view_set_int32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_uint32(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as u32; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -391,7 +386,7 @@ pub fn data_view_set_uint32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_float32(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as f32; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -402,7 +397,7 @@ pub fn data_view_set_float32(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_float64(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2); let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -413,7 +408,7 @@ pub fn data_view_set_float64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_big_int64(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as i64; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -424,7 +419,7 @@ pub fn data_view_set_big_int64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_set_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_set_big_uint64(vm: &mut H, args: &[u8]) -> NativeResult { let value = numeric_arg(vm, args, 2) as u64; let bytes = if is_little_endian(vm, args, 3) { value.to_le_bytes() @@ -435,8 +430,8 @@ pub fn data_view_set_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn data_view_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn data_view_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); native_try!(get_data_view_data(vm, this_val)); - NativeResult::Ok(vm.intern("[object DataView]")) + NativeResult::Ok(vm.new_string("[object DataView]")) } diff --git a/crates/oxide_vm/src/builtins/date.rs b/crates/oxide_builtins/src/date.rs similarity index 77% rename from crates/oxide_vm/src/builtins/date.rs rename to crates/oxide_builtins/src/date.rs index 55f5ced..d8112c7 100644 --- a/crates/oxide_vm/src/builtins/date.rs +++ b/crates/oxide_builtins/src/date.rs @@ -4,9 +4,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; fn get_timestamp(obj: &JsObject) -> f64 { let v = obj.get_prop_at(0); @@ -21,9 +19,9 @@ fn set_timestamp(obj: &mut JsObject, ms: f64) { obj.set_prop_at(0, JsValue::float(ms)); } -fn ensure_date(vm: &mut Vm, obj: &JsObject) -> Result<(), JsValue> { +fn ensure_date(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> { if !obj.is_date_obj() { - return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); + return Err(crate::error::create_type_error(vm, "called on incompatible receiver")); } Ok(()) } @@ -37,28 +35,28 @@ macro_rules! native_try { }; } -fn date_this_mut(vm: &mut Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> { +fn date_this_mut(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> { let raw = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !raw.is_object() { - return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); + return Err(crate::error::create_type_error(vm, "called on incompatible receiver")); } let obj_ptr = raw.as_js_object_ptr(); if obj_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); + return Err(crate::error::create_type_error(vm, "called on incompatible receiver")); } let obj = unsafe { &*obj_ptr }; ensure_date(vm, obj)?; Ok(obj_ptr) } -fn date_this(vm: &mut Vm, args: &[u8]) -> Result<*const JsObject, JsValue> { +fn date_this(vm: &mut H, args: &[u8]) -> Result<*const JsObject, JsValue> { let raw = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !raw.is_object() { - return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); + return Err(crate::error::create_type_error(vm, "called on incompatible receiver")); } let obj_ptr = raw.as_js_object_ptr(); if obj_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver")); + return Err(crate::error::create_type_error(vm, "called on incompatible receiver")); } let obj = unsafe { &*obj_ptr }; ensure_date(vm, obj)?; @@ -79,7 +77,7 @@ fn naive_from_ms(ms: f64) -> Option { dt_from_ms(ms).map(|dt| dt.naive_utc()) } -pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let is_ctor_call = this_val.is_object() && { let ptr = this_val.as_js_object_ptr(); @@ -104,30 +102,30 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let timestamp = if args.len() < 2 { Utc::now().timestamp_millis() as f64 } else if args.len() > 2 { - let y = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; - let m = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as u32; + let y = oxide_runtime_api::to_number(vm.reg(args[1])) as i32; + let m = oxide_runtime_api::to_number(vm.reg(args[2])) as u32; let d = if args.len() > 3 { - coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[3])) as u32 } else { 1 }; let h = if args.len() > 4 { - coercion::to_number(vm.reg(args[4]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[4])) as u32 } else { 0 }; let min = if args.len() > 5 { - coercion::to_number(vm.reg(args[5]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[5])) as u32 } else { 0 }; let sec = if args.len() > 6 { - coercion::to_number(vm.reg(args[6]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[6])) as u32 } else { 0 }; let ms = if args.len() > 7 { - coercion::to_number(vm.reg(args[7]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[7])) as u32 } else { 0 }; @@ -141,11 +139,8 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { let val = vm.reg(args[1]); if val.is_string() { - let s = vm - .kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"]; let mut ts = f64::NAN; for fmt in &formats { @@ -191,7 +186,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { "Invalid Date".to_string() }; - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let mut obj = JsObject::new_empty( @@ -205,11 +200,11 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(ptr)) } -pub fn date_now(_vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn date_now(_vm: &mut H, _args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(Utc::now().timestamp_millis() as f64)) } -pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_parse(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } @@ -217,11 +212,8 @@ pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { if !val.is_string() { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let s = vm - .kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"]; let mut ts = f64::NAN; for fmt in &formats { @@ -240,13 +232,13 @@ pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(ts)) } -pub fn date_utc(_vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn date_utc(_vm: &mut H, _args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(f64::NAN)) } macro_rules! make_getter { ($name:ident, $f:expr, $df:expr) => { - pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult { + pub fn $name(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -262,7 +254,7 @@ macro_rules! make_getter { macro_rules! make_utc_getter { ($name:ident, $f:expr, $df:expr) => { - pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult { + pub fn $name(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -303,7 +295,7 @@ make_utc_getter!( f64::NAN ); -pub fn date_get_timezone_offset(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_get_timezone_offset(vm: &mut H, args: &[u8]) -> NativeResult { let _obj = unsafe { &*native_try!(date_this(vm, args)) }; let offset_min = local_offset_minutes(); NativeResult::Ok(JsValue::float(offset_min as f64)) @@ -313,18 +305,18 @@ fn local_offset_minutes() -> i32 { chrono::Local::now().offset().local_minus_utc() / 60 } -fn get_opt_arg(vm: &Vm, args: &[u8], idx: usize, default: u32) -> u32 { +fn get_opt_arg(vm: &H, args: &[u8], idx: usize, default: u32) -> u32 { if args.len() > idx { - coercion::to_number(vm.reg(args[idx]), vm.kernel_core().string_forge().as_ref()) as u32 + oxide_runtime_api::to_number(vm.reg(args[idx])) as u32 } else { default } } -pub fn date_set_time(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_time(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let val = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) + oxide_runtime_api::to_number(vm.reg(args[1])) } else { f64::NAN }; @@ -332,9 +324,9 @@ pub fn date_set_time(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(val)) } -pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_full_year(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -355,9 +347,9 @@ pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_month(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_month(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -373,9 +365,9 @@ pub fn date_set_month(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_date(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_date(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -390,9 +382,9 @@ pub fn date_set_date(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_hours(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -415,9 +407,9 @@ pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_minutes(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -438,9 +430,9 @@ pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_seconds(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -459,9 +451,9 @@ pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult { set_timestamp(obj, ts); NativeResult::Ok(JsValue::float(ts)) } -pub fn date_set_milliseconds(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_set_milliseconds(vm: &mut H, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = oxide_runtime_api::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -477,24 +469,24 @@ pub fn date_set_milliseconds(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(ts)) } -fn date_to_string_inner(vm: &mut Vm, args: &[u8], format_str: &str, invalid: &str) -> NativeResult { +fn date_to_string_inner(vm: &mut H, args: &[u8], format_str: &str, invalid: &str) -> NativeResult { let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { - return NativeResult::Ok(vm.intern(invalid)); + return NativeResult::Ok(vm.new_string(invalid)); } let s = match dt_from_ms(ms) { Some(dt) => dt.format(format_str).to_string(), None => invalid.to_string(), }; - NativeResult::Ok(vm.intern(&s)) + NativeResult::Ok(vm.new_string(&s)) } -pub fn date_to_iso_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_iso_string(vm: &mut H, args: &[u8]) -> NativeResult { date_to_string_inner(vm, args, "%Y-%m-%dT%H:%M:%S%.3fZ", "Invalid Date") } -pub fn date_to_json(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_json(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -504,26 +496,26 @@ pub fn date_to_json(vm: &mut Vm, args: &[u8]) -> NativeResult { Some(dt) => dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(), None => return NativeResult::Ok(JsValue::null()), }; - NativeResult::Ok(vm.intern(&s)) + NativeResult::Ok(vm.new_string(&s)) } -pub fn date_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_string(vm: &mut H, args: &[u8]) -> NativeResult { date_to_string_inner(vm, args, "%a %b %d %Y %H:%M:%S %Z %z", "Invalid Date") } -pub fn date_to_date_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_date_string(vm: &mut H, args: &[u8]) -> NativeResult { date_to_string_inner(vm, args, "%a %b %d %Y", "Invalid Date") } -pub fn date_to_time_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_time_string(vm: &mut H, args: &[u8]) -> NativeResult { date_to_string_inner(vm, args, "%H:%M:%S %Z", "Invalid Date") } -pub fn date_to_utc_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_to_utc_string(vm: &mut H, args: &[u8]) -> NativeResult { date_to_string_inner(vm, args, "%a, %d %b %Y %H:%M:%S GMT", "Invalid Date") } -pub fn date_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn date_value_of(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); NativeResult::Ok(JsValue::float(ms)) diff --git a/crates/oxide_builtins/src/error.rs b/crates/oxide_builtins/src/error.rs new file mode 100644 index 0000000..79a9555 --- /dev/null +++ b/crates/oxide_builtins/src/error.rs @@ -0,0 +1,154 @@ +use std::sync::Arc; + +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_runtime_api::{to_string, NativeResult, VmHost}; +use oxide_types::mem::P; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +fn create_error_object(host: &mut H, error_proto_ptr: *mut JsObject, name: &str, message: &str) -> JsValue { + let obj = host + .epoch() + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_proto_ptr))); + + let sf = Arc::clone(host.kernel_core().perm_interner()); + let sh = Arc::clone(host.kernel_core().shape_forge()); + + let si_message = sf.intern("message").0; + let sh_message = sh.make_shape(EMPTY_SHAPE_ID, si_message); + let msg_val = host.new_string(message); + unsafe { + (*obj).set_shape_id(sh_message); + (*obj).push_prop(msg_val); + } + + let si_name = sf.intern("name").0; + let sh_name = sh.make_shape(sh_message, si_name); + let name_val = host.new_string(name); + unsafe { + (*obj).set_shape_id(sh_name); + (*obj).push_prop(name_val); + } + + JsValue::from_js_object(obj) +} + +pub fn create_kind_error(host: &mut H, kind: &str, msg: &str) -> JsValue { + let proto_ptr = match kind { + "TypeError" => P::as_ptr(&host.session().builtin_world().type_error_proto) as *mut JsObject, + "RangeError" => P::as_ptr(&host.session().builtin_world().range_error_proto) as *mut JsObject, + "ReferenceError" => P::as_ptr(&host.session().builtin_world().reference_error_proto) as *mut JsObject, + "SyntaxError" => P::as_ptr(&host.session().builtin_world().syntax_error_proto) as *mut JsObject, + "URIError" => P::as_ptr(&host.session().builtin_world().uri_error_proto) as *mut JsObject, + "EvalError" => P::as_ptr(&host.session().builtin_world().eval_error_proto) as *mut JsObject, + _ => P::as_ptr(&host.session().builtin_world().error_proto) as *mut JsObject, + }; + create_error_object(host, proto_ptr, kind, msg) +} + +pub fn create_type_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "TypeError", msg) +} + +pub fn create_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "Error", msg) +} + +pub fn create_reference_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "ReferenceError", msg) +} + +pub fn create_range_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "RangeError", msg) +} + +pub fn create_syntax_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "SyntaxError", msg) +} + +pub fn create_uri_error(host: &mut H, msg: &str) -> JsValue { + create_kind_error(host, "URIError", msg) +} + +fn get_msg(host: &mut H, args: &[u8]) -> String { + if args.len() > 1 { + to_string(host.reg(args[1])) + } else { + String::new() + } +} + +pub fn error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "Error", &msg)) +} + +pub fn type_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().type_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "TypeError", &msg)) +} + +pub fn reference_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().reference_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "ReferenceError", &msg)) +} + +pub fn range_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().range_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "RangeError", &msg)) +} + +pub fn syntax_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().syntax_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "SyntaxError", &msg)) +} + +pub fn uri_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().uri_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "URIError", &msg)) +} + +pub fn eval_error_constructor(host: &mut H, args: &[u8]) -> NativeResult { + let msg = get_msg(host, args); + let proto_ptr = P::as_ptr(&host.session().builtin_world().eval_error_proto) as *mut JsObject; + NativeResult::Ok(create_error_object(host, proto_ptr, "EvalError", &msg)) +} + +pub fn error_to_string(host: &mut H, args: &[u8]) -> NativeResult { + let this_val = host.reg(args[0]); + let (name, msg) = if this_val.is_object() { + let obj = unsafe { &*this_val.as_js_object_ptr() }; + let name_val = obj + .hash_props_vec() + .and_then(|v| v.first()) + .copied() + .unwrap_or(JsValue::undefined()); + let msg_val = obj + .hash_props_vec() + .and_then(|v| v.get(1)) + .copied() + .unwrap_or(JsValue::undefined()); + ( + host.lookup_str(name_val).unwrap_or_else(|| "Error".to_string()), + host.lookup_str(msg_val).unwrap_or_default(), + ) + } else { + ("Error".to_string(), String::new()) + }; + let result = oxide_runtime_api::format_error_message(&name, &msg); + NativeResult::Ok(host.new_string(&result)) +} + +pub fn error_stack_getter(host: &mut H, _args: &[u8]) -> NativeResult { + let names = host.call_stack_function_names(); + let mut lines: Vec = names.iter().rev().map(|n| format!(" at {} (native)", n)).collect(); + lines.push(" at (native)".to_string()); + let result = lines.join("\n"); + NativeResult::Ok(host.new_string(&result)) +} diff --git a/crates/oxide_vm/src/builtins/function.rs b/crates/oxide_builtins/src/function.rs similarity index 78% rename from crates/oxide_vm/src/builtins/function.rs rename to crates/oxide_builtins/src/function.rs index dd1470b..341256d 100644 --- a/crates/oxide_vm/src/builtins/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -2,10 +2,9 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::value::JsValue; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -fn invoke_target(vm: &mut Vm, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult { +fn invoke_target(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult { let args: Vec = arg_regs.iter().map(|&r| vm.reg(r)).collect(); NativeResult::TailCall { callee: target_val, @@ -14,7 +13,7 @@ fn invoke_target(vm: &mut Vm, target_val: JsValue, this_val: JsValue, arg_regs: } } -fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult { +fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; let bound_target = wrapper @@ -31,7 +30,7 @@ fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult { invoke_target(vm, bound_target, bound_this, &arg_regs) } -pub fn function_call(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn function_call(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(JsValue::undefined()); } @@ -41,7 +40,7 @@ pub fn function_call(vm: &mut Vm, args: &[u8]) -> NativeResult { invoke_target(vm, target_val, this_val, &arg_regs) } -pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(JsValue::undefined()); } @@ -81,7 +80,7 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult { invoke_target(vm, target_val, this_val, &arg_regs) } -pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(JsValue::undefined()); } @@ -95,7 +94,7 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult { unsafe { (*wrapper).set_function(true); // SAFETY: bind_dispatcher is a NativeFn fn-item; valid to store as NativeFnPtr. - (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher as *const ()))); + (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher:: as *const ()))); (*wrapper).set_native_arg_count(0); (*wrapper).ensure_hash_props().push(target_val); (*wrapper).ensure_hash_props().push(bound_this); @@ -104,10 +103,10 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(wrapper)) } -pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn function_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !this_val.is_object() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Function.prototype.toString called on non-function", )); @@ -115,26 +114,20 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let func = unsafe { &*this_val.as_js_object_ptr() }; if !func.is_function() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Function.prototype.toString called on non-function", )); } - let name_si = vm.kernel_core().string_forge().intern("name").0; + let name_si = vm.kernel_core().perm_interner().intern("name").0; let name = vm .resolve_property(func, name_si) - .and_then(|v| { - if v.is_string() { - vm.kernel_core().string_forge().lookup(v.as_string_index()) - } else { - None - } - }) + .and_then(|v| vm.lookup_str(v)) .unwrap_or_else(|| { let sub_idx = func.sub_module_index(); - if sub_idx > 0 && (sub_idx as usize) <= vm.sub_modules.len() { - vm.sub_modules[sub_idx as usize - 1].function_name.clone().unwrap_or_default() + if sub_idx > 0 { + vm.sub_module_function_name((sub_idx - 1) as u16) } else { String::new() } @@ -146,6 +139,5 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { format!("function {name}() {{ {body} }}") }; - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_vm/src/builtins/global.rs b/crates/oxide_builtins/src/global.rs similarity index 66% rename from crates/oxide_vm/src/builtins/global.rs rename to crates/oxide_builtins/src/global.rs index c7c0ace..4c24a76 100644 --- a/crates/oxide_vm/src/builtins/global.rs +++ b/crates/oxide_builtins/src/global.rs @@ -1,15 +1,13 @@ +use oxide_runtime_api::VmHost; mod annex_b; mod uri; -use crate::coercion; -use crate::vm::Vm; - pub use annex_b::{js_escape, js_unescape}; pub use uri::{decode_uri, decode_uri_component, encode_uri, encode_uri_component}; -fn string_arg(vm: &mut Vm, args: &[u8]) -> String { +fn string_arg(vm: &mut H, args: &[u8]) -> String { if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + oxide_runtime_api::to_string(vm.reg(args[1])) } else { "undefined".to_string() } diff --git a/crates/oxide_vm/src/builtins/global/annex_b.rs b/crates/oxide_builtins/src/global/annex_b.rs similarity index 91% rename from crates/oxide_vm/src/builtins/global/annex_b.rs rename to crates/oxide_builtins/src/global/annex_b.rs index 4aea2a7..9cbc9d5 100644 --- a/crates/oxide_vm/src/builtins/global/annex_b.rs +++ b/crates/oxide_builtins/src/global/annex_b.rs @@ -1,5 +1,4 @@ -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; const ESCAPE_SAFE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./"; @@ -85,14 +84,14 @@ fn unescape_string(input: &str) -> String { out } -pub fn js_escape(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn js_escape(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&escape_string(&input))) + NativeResult::Ok(vm.new_string(&escape_string(&input))) } -pub fn js_unescape(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn js_unescape(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&unescape_string(&input))) + NativeResult::Ok(vm.new_string(&unescape_string(&input))) } #[cfg(test)] diff --git a/crates/oxide_vm/src/builtins/global/uri.rs b/crates/oxide_builtins/src/global/uri.rs similarity index 84% rename from crates/oxide_vm/src/builtins/global/uri.rs rename to crates/oxide_builtins/src/global/uri.rs index d2291da..77240b2 100644 --- a/crates/oxide_vm/src/builtins/global/uri.rs +++ b/crates/oxide_builtins/src/global/uri.rs @@ -1,5 +1,4 @@ -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; const URI_UNESCAPED: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()"; const URI_RESERVED: &str = ";/?:@&=+$,#"; @@ -82,32 +81,32 @@ fn decode_uri_string(input: &str, preserve_reserved: bool) -> Result Ok(out) } -fn uri_error(vm: &mut Vm) -> NativeResult { - NativeResult::Err(crate::builtins::error::create_uri_error(vm, URI_ERROR_MESSAGE)) +fn uri_error(vm: &mut H) -> NativeResult { + NativeResult::Err(crate::error::create_uri_error(vm, URI_ERROR_MESSAGE)) } -pub fn encode_uri(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn encode_uri(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&encode_uri_string(&input, URI_SAFE))) + NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_SAFE))) } -pub fn encode_uri_component(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn encode_uri_component(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&encode_uri_string(&input, URI_UNESCAPED))) + NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_UNESCAPED))) } -pub fn decode_uri(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn decode_uri(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); match decode_uri_string(&input, true) { - Ok(decoded) => NativeResult::Ok(vm.intern(&decoded)), + Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)), Err(()) => uri_error(vm), } } -pub fn decode_uri_component(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn decode_uri_component(vm: &mut H, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); match decode_uri_string(&input, false) { - Ok(decoded) => NativeResult::Ok(vm.intern(&decoded)), + Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)), Err(()) => uri_error(vm), } } diff --git a/crates/oxide_vm/src/builtins/iterator.rs b/crates/oxide_builtins/src/iterator.rs similarity index 64% rename from crates/oxide_vm/src/builtins/iterator.rs rename to crates/oxide_builtins/src/iterator.rs index 81b5039..ffe9884 100644 --- a/crates/oxide_vm/src/builtins/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -2,18 +2,16 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; const INNER_PROP: &str = "__inner__"; const INDEX_PROP: &str = "__index__"; -pub fn iterator_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult { - NativeResult::Err(crate::builtins::error::create_type_error(vm, "Iterator is not a constructor")) +pub fn iterator_constructor(vm: &mut H, _args: &[u8]) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, "Iterator is not a constructor")) } -pub fn iterator_from(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn iterator_from(vm: &mut H, args: &[u8]) -> NativeResult { let iterable = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; match make_iterator_for_value(vm, iterable) { Ok(iterator) => NativeResult::Ok(iterator), @@ -21,46 +19,38 @@ pub fn iterator_from(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub(crate) fn make_iterator_for_value(vm: &mut Vm, value: JsValue) -> Result { +pub fn make_iterator_for_value(vm: &mut H, value: JsValue) -> Result { let inner = get_iterator(vm, value)?; let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let wrapper = vm .epoch() .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); - let inner_si = vm.kernel_core().string_forge().intern(INNER_PROP).0; - let index_si = vm.kernel_core().string_forge().intern(INDEX_PROP).0; - let next_si = vm.kernel_core().string_forge().intern("next").0; + let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0; + let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0; + let next_si = vm.kernel_core().perm_interner().intern("next").0; let wrapper_obj = unsafe { &mut *wrapper }; vm.set_or_create_prop_value(wrapper_obj, inner_si, inner); vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0)); - let next_fn = make_native_function(vm, "next", iterator_wrapper_next as *const (), 0); + let next_fn = make_native_function(vm, "next", iterator_wrapper_next:: as *const (), 0); vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn); Ok(JsValue::from_js_object(wrapper)) } -pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if !this_val.is_object() { - return NativeResult::Err(crate::builtins::error::create_type_error( - vm, - "Iterator wrapper next called on non-object", - )); + return NativeResult::Err(crate::error::create_type_error(vm, "Iterator wrapper next called on non-object")); } let wrapper = unsafe { &mut *this_val.as_js_object_ptr() }; - let inner_si = vm.kernel_core().string_forge().intern(INNER_PROP).0; - let index_si = vm.kernel_core().string_forge().intern(INDEX_PROP).0; + let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0; + let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0; let inner = match vm.ordinary_get(wrapper, inner_si, this_val) { Ok(inner) if !inner.is_undefined() => inner, - _ => { - return NativeResult::Err(crate::builtins::error::create_type_error( - vm, - "Iterator wrapper has no inner iterator", - )) - } + _ => return NativeResult::Err(crate::error::create_type_error(vm, "Iterator wrapper has no inner iterator")), }; if let Some(result) = next_array_like(vm, wrapper, inner, index_si) { @@ -69,28 +59,28 @@ pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult { if inner.is_object() { let inner_obj = unsafe { &*inner.as_js_object_ptr() }; - let next_si = vm.kernel_core().string_forge().intern("next").0; + let next_si = vm.kernel_core().perm_interner().intern("next").0; let next = match vm.ordinary_get(inner_obj, next_si, inner) { Ok(next) => next, - Err(err) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, &err)), + Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)), }; return match vm.call_function_sync(next, inner, &[]) { Ok(result) => NativeResult::Ok(result), - Err(err) => NativeResult::Err(crate::builtins::error::create_type_error(vm, &err)), + Err(err) => NativeResult::Err(crate::error::create_type_error(vm, &err)), }; } - NativeResult::Err(crate::builtins::error::create_type_error(vm, "value is not iterable")) + NativeResult::Err(crate::error::create_type_error(vm, "value is not iterable")) } -fn get_iterator(vm: &mut Vm, value: JsValue) -> Result { +fn get_iterator(vm: &mut H, value: JsValue) -> Result { if value.is_string() || is_array_value(value) { return Ok(value); } if value.is_object() { let obj = unsafe { &*value.as_js_object_ptr() }; - let next_si = vm.kernel_core().string_forge().intern("next").0; + let next_si = vm.kernel_core().perm_interner().intern("next").0; if let Ok(next) = vm.ordinary_get(obj, next_si, value) { if is_callable(next) { return Ok(value); @@ -98,10 +88,10 @@ fn get_iterator(vm: &mut Vm, value: JsValue) -> Result { } } - Err(crate::builtins::error::create_type_error(vm, "value is not iterable")) + Err(crate::error::create_type_error(vm, "value is not iterable")) } -fn next_array_like(vm: &mut Vm, wrapper: &mut JsObject, inner: JsValue, index_si: u32) -> Option { +fn next_array_like(vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32) -> Option { if is_array_value(inner) { let index = current_index(vm, wrapper, index_si); let arr = unsafe { &*inner.as_js_object_ptr() }; @@ -115,11 +105,11 @@ fn next_array_like(vm: &mut Vm, wrapper: &mut JsObject, inner: JsValue, index_si if inner.is_string() { let index = current_index(vm, wrapper, index_si); - let source = coercion::to_string(vm.kernel_core().string_forge().as_ref(), inner); + let source = oxide_runtime_api::to_string(inner); let mut chars = source.chars(); if let Some(ch) = chars.nth(index) { vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32)); - let value = vm.intern(&ch.to_string()); + let value = vm.new_string(&ch.to_string()); return Some(make_iter_result(vm, value, false)); } return Some(make_iter_result(vm, JsValue::undefined(), true)); @@ -128,7 +118,7 @@ fn next_array_like(vm: &mut Vm, wrapper: &mut JsObject, inner: JsValue, index_si None } -fn current_index(vm: &mut Vm, wrapper: &JsObject, index_si: u32) -> usize { +fn current_index(vm: &mut H, wrapper: &JsObject, index_si: u32) -> usize { match vm.ordinary_get(wrapper, index_si, JsValue::undefined()) { Ok(value) if value.is_int() => value.as_int().max(0) as usize, Ok(value) if value.is_double() => value.as_double().max(0.0) as usize, @@ -136,20 +126,20 @@ fn current_index(vm: &mut Vm, wrapper: &JsObject, index_si: u32) -> usize { } } -fn make_iter_result(vm: &mut Vm, value: JsValue, done: bool) -> JsValue { +fn make_iter_result(vm: &mut H, value: JsValue, done: bool) -> JsValue { let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let obj = vm .epoch() .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); - let value_si = vm.kernel_core().string_forge().intern("value").0; - let done_si = vm.kernel_core().string_forge().intern("done").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let done_si = vm.kernel_core().perm_interner().intern("done").0; let obj_ref = unsafe { &mut *obj }; vm.set_or_create_prop_value(obj_ref, value_si, value); vm.set_or_create_prop_value(obj_ref, done_si, JsValue::bool(done)); JsValue::from_js_object(obj) } -fn make_native_function(vm: &mut Vm, name: &str, native_fn: *const (), arg_count: u8) -> JsValue { +fn make_native_function(vm: &mut H, name: &str, native_fn: *const (), arg_count: u8) -> JsValue { let function_proto = vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject; let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto)); func.set_function(true); @@ -157,8 +147,8 @@ fn make_native_function(vm: &mut Vm, name: &str, native_fn: *const (), arg_count func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) })); func.set_native_arg_count(arg_count); let func = vm.alloc_object(func); - let name_si = vm.kernel_core().string_forge().intern("name").0; - let value = vm.intern(name); + let name_si = vm.kernel_core().perm_interner().intern("name").0; + let value = vm.new_string(name); let func_ref = unsafe { &mut *func }; vm.set_or_create_prop_value(func_ref, name_si, value); JsValue::from_js_object(func) diff --git a/crates/oxide_vm/src/builtins/json.rs b/crates/oxide_builtins/src/json.rs similarity index 77% rename from crates/oxide_vm/src/builtins/json.rs rename to crates/oxide_builtins/src/json.rs index 515d2dc..b19df61 100644 --- a/crates/oxide_vm/src/builtins/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -5,35 +5,32 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::builtins::object::walk_own_keys; -use crate::native::NativeResult; -use crate::vm::Vm; +use crate::object::walk_own_keys; -pub fn json_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { +use oxide_runtime_api::{NativeResult, VmHost}; + +pub fn json_parse(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { - return NativeResult::Err(crate::builtins::error::create_syntax_error(vm, "JSON.parse requires 1 argument")); + return NativeResult::Err(crate::error::create_syntax_error(vm, "JSON.parse requires 1 argument")); } let val = vm.reg(args[1]); if !val.is_string() { - return NativeResult::Err(crate::builtins::error::create_syntax_error( - vm, - "JSON.parse: argument is not a string", - )); + return NativeResult::Err(crate::error::create_syntax_error(vm, "JSON.parse: argument is not a string")); } let text = { - let si = val.as_string_index(); - vm.kernel_core().string_forge().lookup(si).unwrap_or_default() + // SAFETY: val is a string value. + unsafe { (*val.as_string_ptr()).data.clone() } }; let parsed: serde_json::Value = match serde_json::from_str(&text) { Ok(v) => v, - Err(e) => return NativeResult::Err(crate::builtins::error::create_syntax_error(vm, &format!("{}", e))), + Err(e) => return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("{}", e))), }; NativeResult::Ok(value_to_jsvalue(vm, &parsed)) } -fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { +fn value_to_jsvalue(vm: &mut H, val: &serde_json::Value) -> JsValue { match val { serde_json::Value::Null => JsValue::null(), serde_json::Value::Bool(b) => JsValue::bool(*b), @@ -44,7 +41,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { JsValue::float(0.0) } } - serde_json::Value::String(s) => vm.intern(s), + serde_json::Value::String(s) => vm.new_string(s), serde_json::Value::Array(arr) => { let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let n = arr.len(); @@ -66,7 +63,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)); for (key, val) in map { - let si = vm.kernel_core().string_forge().intern(key).0; + let si = vm.kernel_core().perm_interner().intern(key).0; let jsv = value_to_jsvalue(vm, val); let new_shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si); obj.set_shape_id(new_shape); @@ -78,7 +75,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { } } -pub fn json_stringify(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn json_stringify(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); } @@ -89,12 +86,14 @@ pub fn json_stringify(vm: &mut Vm, args: &[u8]) -> NativeResult { let mut visited = HashSet::new(); let mut output = String::new(); if jsvalue_to_json(vm, value, &mut visited, &mut output).is_err() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "cyclic object value")); + return NativeResult::Err(crate::error::create_type_error(vm, "cyclic object value")); }; - NativeResult::Ok(vm.intern(&output)) + NativeResult::Ok(vm.new_string(&output)) } -fn jsvalue_to_json(vm: &Vm, val: JsValue, visited: &mut HashSet<*const u8>, out: &mut String) -> Result<(), ()> { +fn jsvalue_to_json( + vm: &H, val: JsValue, visited: &mut HashSet<*const u8>, out: &mut String, +) -> Result<(), ()> { if val.is_null() { out.push_str("null"); } else if val.is_undefined() { @@ -113,8 +112,8 @@ fn jsvalue_to_json(vm: &Vm, val: JsValue, visited: &mut HashSet<*const u8>, out: out.push_str(buf.format(n)); } } else if val.is_string() { - let si = val.as_string_index(); - let s = vm.kernel_core().string_forge().lookup(si).unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; stringify_string(&s, out); } else if val.is_object() { let obj_ptr = val.as_js_object_ptr(); @@ -163,7 +162,9 @@ fn stringify_string(s: &str, out: &mut String) { out.push('"'); } -fn stringify_object(vm: &Vm, obj: &JsObject, visited: &mut HashSet<*const u8>, out: &mut String) -> Result<(), ()> { +fn stringify_object( + vm: &H, obj: &JsObject, visited: &mut HashSet<*const u8>, out: &mut String, +) -> Result<(), ()> { out.push('{'); let keys = walk_own_keys(vm, obj); let mut first = true; @@ -184,8 +185,8 @@ fn stringify_object(vm: &Vm, obj: &JsObject, visited: &mut HashSet<*const u8>, o out.push(','); } first = false; - let name = vm.kernel_core().string_forge().lookup(si).unwrap_or_default(); - stringify_string(&name, out); + let name = vm.kernel_core().perm_interner().lookup(si).unwrap_or_default(); + stringify_string(name, out); out.push(':'); jsvalue_to_json(vm, val, visited, out)?; } @@ -193,7 +194,9 @@ fn stringify_object(vm: &Vm, obj: &JsObject, visited: &mut HashSet<*const u8>, o Ok(()) } -fn stringify_array(vm: &Vm, obj: &JsObject, visited: &mut HashSet<*const u8>, out: &mut String) -> Result<(), ()> { +fn stringify_array( + vm: &H, obj: &JsObject, visited: &mut HashSet<*const u8>, out: &mut String, +) -> Result<(), ()> { out.push('['); let len = obj.prop_vec_len(); for i in 0..len { diff --git a/crates/oxide_vm/src/builtins/mod.rs b/crates/oxide_builtins/src/lib.rs similarity index 93% rename from crates/oxide_vm/src/builtins/mod.rs rename to crates/oxide_builtins/src/lib.rs index 3098714..b23061c 100644 --- a/crates/oxide_vm/src/builtins/mod.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -1,6 +1,7 @@ pub mod array; pub mod array_buffer; pub mod boolean; +pub mod builtins_log; pub mod data_view; pub mod date; pub mod error; diff --git a/crates/oxide_vm/src/builtins/map.rs b/crates/oxide_builtins/src/map.rs similarity index 80% rename from crates/oxide_vm/src/builtins/map.rs rename to crates/oxide_builtins/src/map.rs index b679350..49c8674 100644 --- a/crates/oxide_vm/src/builtins/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -2,9 +2,9 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::builtins::set::SetKey; -use crate::native::NativeResult; -use crate::vm::Vm; +use crate::set::SetKey; + +use oxide_runtime_api::{NativeResult, VmHost}; macro_rules! native_try { ($expr:expr) => { @@ -27,20 +27,20 @@ type MapInner = indexmap::IndexMap; /// is created in `new_map_inner()` and is never freed until the process exits (intentional /// leak — lifetime is tied to the epoch). Only one live `*mut` alias exists at a time /// per Map object because native calls are single-threaded. -fn get_map_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut MapInner, JsValue> { +fn get_map_inner(vm: &mut H, this_val: JsValue) -> Result<*mut MapInner, JsValue> { if !this_val.is_object() { - return Err(crate::builtins::error::create_type_error(vm, "called on non-Map object")); + return Err(crate::error::create_type_error(vm, "called on non-Map object")); } let map_ptr = this_val.as_js_object_ptr(); if map_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "Map internal state invalid")); + return Err(crate::error::create_type_error(vm, "Map internal state invalid")); } // SAFETY: map_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the // current Epoch. It remains valid for the duration of this call (epoch is not reset // during native execution). let map_obj = unsafe { &*map_ptr }; if !map_obj.is_map() { - return Err(crate::builtins::error::create_type_error( + return Err(crate::error::create_type_error( vm, "Map.prototype method called on incompatible receiver", )); @@ -50,7 +50,7 @@ fn get_map_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut MapInner, JsValu // Alignment: IndexMap requires at most 8-byte alignment; the global allocator satisfies this. let inner_ptr = map_obj.native_data() as *mut MapInner; if inner_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "Map internal state invalid")); + return Err(crate::error::create_type_error(vm, "Map internal state invalid")); } Ok(inner_ptr) } @@ -59,7 +59,7 @@ fn new_map_inner() -> *mut MapInner { Box::into_raw(Box::new(MapInner::new())) } -fn alloc_map(vm: &mut Vm) -> *mut JsObject { +fn alloc_map(vm: &mut H) -> *mut JsObject { let map_proto = vm.session().builtin_world().map_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(map_proto)); obj.set_map(true); @@ -68,7 +68,7 @@ fn alloc_map(vm: &mut Vm) -> *mut JsObject { vm.alloc_object(obj) } -pub(crate) fn map_native_edges(obj: &JsObject) -> Vec { +pub fn map_native_edges(obj: &JsObject) -> Vec { if !obj.is_map() { return Vec::new(); } @@ -80,12 +80,12 @@ pub(crate) fn map_native_edges(obj: &JsObject) -> Vec { (*inner) .iter() .flat_map(|(key, value)| [key.0, *value]) - .filter(|value| value.is_object()) + .filter(|value: &JsValue| value.is_object()) .collect() } } -pub(crate) fn clone_map_native_with_rewrite(src: &JsObject, dst: &mut JsObject, mut rewrite: F) +pub fn clone_map_native_with_rewrite(src: &JsObject, dst: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -108,7 +108,7 @@ where dst.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); } -pub(crate) fn rewrite_map_native(obj: &mut JsObject, mut rewrite: F) +pub fn rewrite_map_native(obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -130,7 +130,7 @@ where } } -pub(crate) fn drop_map_native(obj: &mut JsObject) -> u64 { +pub fn drop_map_native(obj: &mut JsObject) -> u64 { if !obj.is_map() { return 0; } @@ -139,7 +139,7 @@ pub(crate) fn drop_map_native(obj: &mut JsObject) -> u64 { return 0; } unsafe { - let boxed = Box::from_raw(inner); + let boxed: Box = Box::from_raw(inner); let bytes = std::mem::size_of::() + boxed.capacity() * std::mem::size_of::<(SetKey, JsValue)>(); drop(boxed); obj.set_native_data(std::ptr::null_mut()); @@ -147,12 +147,12 @@ pub(crate) fn drop_map_native(obj: &mut JsObject) -> u64 { } } -pub fn map_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn map_constructor(vm: &mut H, _args: &[u8]) -> NativeResult { let map_obj = alloc_map(vm); NativeResult::Ok(JsValue::from_js_object(map_obj)) } -pub fn map_set(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_set(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); let key = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -163,7 +163,7 @@ pub fn map_set(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -pub fn map_get(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_get(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); let key = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -171,7 +171,7 @@ pub fn map_get(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(found.unwrap_or(JsValue::undefined())) } -pub fn map_has(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_has(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); let key = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -179,7 +179,7 @@ pub fn map_has(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(found)) } -pub fn map_delete(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_delete(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); let key = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -187,7 +187,7 @@ pub fn map_delete(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(removed.is_some())) } -pub fn map_clear(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_clear(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); unsafe { @@ -196,7 +196,7 @@ pub fn map_clear(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn map_size(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn map_size(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_map_inner(vm, this_val)); NativeResult::Ok(JsValue::float(unsafe { (*inner).len() } as f64)) diff --git a/crates/oxide_vm/src/builtins/math.rs b/crates/oxide_builtins/src/math.rs similarity index 73% rename from crates/oxide_vm/src/builtins/math.rs rename to crates/oxide_builtins/src/math.rs index acd3840..2386687 100644 --- a/crates/oxide_vm/src/builtins/math.rs +++ b/crates/oxide_builtins/src/math.rs @@ -1,14 +1,12 @@ use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -fn num(vm: &Vm, reg: u8) -> f64 { - coercion::to_number(vm.reg(reg), vm.kernel_core().string_forge().as_ref()) +fn num(vm: &H, reg: u8) -> f64 { + oxide_runtime_api::to_number(vm.reg(reg)) } -fn arg1(vm: &Vm, args: &[u8]) -> f64 { +fn arg1(vm: &H, args: &[u8]) -> f64 { if args.len() < 2 { f64::NAN } else { @@ -16,14 +14,14 @@ fn arg1(vm: &Vm, args: &[u8]) -> f64 { } } -fn arg2(vm: &Vm, args: &[u8]) -> (f64, f64) { +fn arg2(vm: &H, args: &[u8]) -> (f64, f64) { ( if args.len() > 1 { num(vm, args[1]) } else { f64::NAN }, if args.len() > 2 { num(vm, args[2]) } else { f64::NAN }, ) } -pub fn math_abs(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_abs(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } @@ -37,7 +35,7 @@ pub fn math_abs(vm: &mut Vm, args: &[u8]) -> NativeResult { macro_rules! math_unary { ($name:ident, $op:ident) => { - pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult { + pub fn $name(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(arg1(vm, args).$op())) } }; @@ -67,18 +65,18 @@ math_unary!(math_tan, tan); math_unary!(math_tanh, tanh); math_unary!(math_trunc, trunc); -pub fn math_atan2(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_atan2(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); NativeResult::Ok(JsValue::float(a.atan2(b))) } -pub fn math_round(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_round(vm: &mut H, args: &[u8]) -> NativeResult { let x = arg1(vm, args); let r = if x < 0.0 { (x - 0.5).ceil() } else { (x + 0.5).floor() }; NativeResult::Ok(JsValue::float(r)) } -pub fn math_sign(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_sign(vm: &mut H, args: &[u8]) -> NativeResult { let x = arg1(vm, args); if x.is_nan() { NativeResult::Ok(JsValue::float(f64::NAN)) @@ -91,31 +89,31 @@ pub fn math_sign(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn math_clz32(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_clz32(vm: &mut H, args: &[u8]) -> NativeResult { let n = arg1(vm, args) as u32; NativeResult::Ok(JsValue::int(n.leading_zeros() as i32)) } -pub fn math_fround(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_fround(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(arg1(vm, args) as f32 as f64)) } -pub fn math_hypot(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_hypot(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); NativeResult::Ok(JsValue::float(a.hypot(b))) } -pub fn math_imul(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_imul(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); NativeResult::Ok(JsValue::int((a as i32).wrapping_mul(b as i32))) } -pub fn math_pow(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_pow(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); NativeResult::Ok(JsValue::float(a.powf(b))) } -pub fn math_max(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_max(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NEG_INFINITY)); } @@ -132,7 +130,7 @@ pub fn math_max(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(m)) } -pub fn math_min(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn math_min(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::INFINITY)); } @@ -149,7 +147,7 @@ pub fn math_min(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(m)) } -pub fn math_random(vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn math_random(vm: &mut H, _args: &[u8]) -> NativeResult { vm.step_rng(); NativeResult::Ok(JsValue::float(vm.math_rng_value())) } diff --git a/crates/oxide_vm/src/builtins/number.rs b/crates/oxide_builtins/src/number.rs similarity index 63% rename from crates/oxide_vm/src/builtins/number.rs rename to crates/oxide_builtins/src/number.rs index e96fe66..fb0998a 100644 --- a/crates/oxide_vm/src/builtins/number.rs +++ b/crates/oxide_builtins/src/number.rs @@ -1,15 +1,9 @@ use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) - } else { - 0.0 - }; +pub fn number_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + let n = if args.len() > 1 { oxide_runtime_api::to_number(vm.reg(args[1])) } else { 0.0 }; let number_proto = vm.session().builtin_world().number_proto.as_ptr() as *mut oxide_types::object::JsObject; let is_ctor = if let Some(this_reg) = args.first().copied() { let this_val = vm.reg(this_reg); @@ -48,7 +42,7 @@ pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn number_is_nan(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_is_nan(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } @@ -60,7 +54,7 @@ pub fn number_is_nan(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn number_is_finite(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_is_finite(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } @@ -71,16 +65,15 @@ pub fn number_is_finite(vm: &mut Vm, args: &[u8]) -> NativeResult { if val.is_double() { return NativeResult::Ok(JsValue::bool(val.as_double().is_finite())); } - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = oxide_runtime_api::to_number(val); NativeResult::Ok(JsValue::bool(n.is_finite())) } -pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_parse_int(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let sf = vm.kernel_core().string_forge().as_ref(); - let s = coercion::to_string(sf, vm.reg(args[1])); + let s = oxide_runtime_api::to_string(vm.reg(args[1])); let s = s.trim(); if s.is_empty() { @@ -88,7 +81,7 @@ pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult { } let radix = if args.len() > 2 { - let r = coercion::to_number(vm.reg(args[2]), sf) as i32; + let r = oxide_runtime_api::to_number(vm.reg(args[2])) as i32; if r == 0 { 10 } else { @@ -117,12 +110,11 @@ pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::float(f64::NAN)) } -pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_parse_float(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let sf = vm.kernel_core().string_forge().as_ref(); - let s = coercion::to_string(sf, vm.reg(args[1])); + let s = oxide_runtime_api::to_string(vm.reg(args[1])); let s = s.trim(); if s.is_empty() { @@ -135,10 +127,10 @@ pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); +pub fn number_to_string(vm: &mut H, args: &[u8]) -> NativeResult { + let n = oxide_runtime_api::to_number(vm.reg(args[0])); let radix = if args.len() > 1 { - let r = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as u32; + let r = oxide_runtime_api::to_number(vm.reg(args[1])) as u32; r.clamp(2, 36) } else { 10u32 @@ -146,22 +138,22 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { if radix == 10 { if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { if n.is_sign_positive() { - return NativeResult::Ok(vm.intern("Infinity")); + return NativeResult::Ok(vm.new_string("Infinity")); } - return NativeResult::Ok(vm.intern("-Infinity")); + return NativeResult::Ok(vm.new_string("-Infinity")); } if n.fract() == 0.0 && n >= i32::MIN as f64 && n <= i32::MAX as f64 { - return NativeResult::Ok(vm.intern(&(n as i64).to_string())); + return NativeResult::Ok(vm.new_string(&(n as i64).to_string())); } let mut buf = ryu::Buffer::new(); - NativeResult::Ok(vm.intern(buf.format(n))) + NativeResult::Ok(vm.new_string(buf.format(n))) } else { if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } let nn = n as i64; let mut result = String::new(); @@ -182,73 +174,73 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { if nn < 0 { result.insert(0, '-'); } - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } } -pub fn number_to_fixed(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); +pub fn number_to_fixed(vm: &mut H, args: &[u8]) -> NativeResult { + let n = oxide_runtime_api::to_number(vm.reg(args[0])); let digits = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(20); let formatted = format!("{:.digits$}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } -pub fn number_is_integer(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_is_integer(vm: &mut H, args: &[u8]) -> NativeResult { let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] }); - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = oxide_runtime_api::to_number(val); NativeResult::Ok(JsValue::bool(n.trunc() == n && n.is_finite())) } -pub fn number_is_safe_integer(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_is_safe_integer(vm: &mut H, args: &[u8]) -> NativeResult { let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] }); - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = oxide_runtime_api::to_number(val); let safe = n.trunc() == n && n.is_finite() && n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64; NativeResult::Ok(JsValue::bool(safe)) } -pub fn number_to_exponential(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); +pub fn number_to_exponential(vm: &mut H, args: &[u8]) -> NativeResult { + let n = oxide_runtime_api::to_number(vm.reg(args[0])); let digits = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(100); if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { - return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); + return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); } let formatted = format!("{:.digits$e}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } -pub fn number_to_precision(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); +pub fn number_to_precision(vm: &mut H, args: &[u8]) -> NativeResult { + let n = oxide_runtime_api::to_number(vm.reg(args[0])); let precision = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(21); if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { - return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); + return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); } let formatted = format!("{:.precision$}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } -pub fn number_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn number_value_of(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); if this_val.is_int() || this_val.is_double() { return NativeResult::Ok(this_val); @@ -262,7 +254,7 @@ pub fn number_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } } } - NativeResult::Err(crate::builtins::error::create_type_error( + NativeResult::Err(crate::error::create_type_error( vm, "Number.prototype.valueOf called on incompatible receiver", )) diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_builtins/src/object.rs similarity index 76% rename from crates/oxide_vm/src/builtins/object.rs rename to crates/oxide_builtins/src/object.rs index a2445d8..8758769 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1,14 +1,12 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use oxide_kernel::string_forge::StringForge; +use oxide_kernel::string_forge::PermInterner; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::is_private_name_key; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> { +pub(crate) fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { let mut keys: Vec<(u32, u32)> = Vec::new(); let shape_id = obj.shape_id(); let mut pos: u32 = 0; @@ -38,12 +36,12 @@ pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> { keys } -pub fn object_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn object_constructor(vm: &mut H, _args: &[u8]) -> NativeResult { let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); NativeResult::Ok(JsValue::from_js_object(obj)) } -pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_keys(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Err(JsValue::undefined()); } @@ -62,7 +60,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { let keys = walk_own_keys(vm, obj); key_names = keys .iter() - .map(|(si, _offset)| vm.kernel_core().string_forge().lookup(*si).unwrap_or_default()) + .map(|(si, _offset)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) .collect(); } @@ -75,7 +73,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, k) in key_names.iter().enumerate() { - let str_val = vm.intern(k); + let str_val = vm.new_string(k); unsafe { (*arr).set_prop_at(i, str_val); } @@ -86,7 +84,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(arr)) } -pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_create(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); } @@ -98,14 +96,14 @@ pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(obj)) } -pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_assign(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Object.assign requires a target")); + return NativeResult::Err(crate::error::create_type_error(vm, "Object.assign requires a target")); } let target_val = vm.reg(args[1]); - let target_val = match coercion::to_object(target_val, vm) { + let target_val = match oxide_runtime_api::to_object(target_val, vm) { Ok(val) => val, - Err(msg) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, &msg)), + Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), }; let target_ptr = target_val.as_js_object_ptr(); if target_ptr.is_null() { @@ -133,13 +131,13 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(target_val) } -pub fn object_is(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_is(vm: &mut H, args: &[u8]) -> NativeResult { let lhs = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let rhs = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; - NativeResult::Ok(JsValue::bool(coercion::same_value(lhs, rhs))) + NativeResult::Ok(JsValue::bool(oxide_runtime_api::same_value(lhs, rhs))) } -pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 4 { return NativeResult::Err(JsValue::undefined()); } @@ -159,15 +157,15 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { if desc_ptr.is_null() { return NativeResult::Err(JsValue::undefined()); } - let prop_name_str = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); - let si = vm.kernel_core().string_forge().intern(&prop_name_str).0; + let prop_name_str = oxide_runtime_api::to_string(vm.reg(args[2])); + let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0; - let value_si = vm.kernel_core().string_forge().intern("value").0; - let get_si = vm.kernel_core().string_forge().intern("get").0; - let set_si = vm.kernel_core().string_forge().intern("set").0; - let writable_si = vm.kernel_core().string_forge().intern("writable").0; - let enumerable_si = vm.kernel_core().string_forge().intern("enumerable").0; - let configurable_si = vm.kernel_core().string_forge().intern("configurable").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let get_si = vm.kernel_core().perm_interner().intern("get").0; + let set_si = vm.kernel_core().perm_interner().intern("set").0; + let writable_si = vm.kernel_core().perm_interner().intern("writable").0; + let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0; + let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0; let desc = unsafe { &*desc_ptr }; let value_field = own_field(vm, desc, value_si); @@ -175,16 +173,16 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { let set_field = own_field(vm, desc, set_si); let writable_field = own_field(vm, desc, writable_si); let enumerable = own_field(vm, desc, enumerable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) + .map(oxide_runtime_api::to_boolean) .unwrap_or(false); let configurable = own_field(vm, desc, configurable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) + .map(oxide_runtime_api::to_boolean) .unwrap_or(false); let has_data = value_field.is_some() || writable_field.is_some(); let has_accessor = get_field.is_some() || set_field.is_some(); if has_data && has_accessor { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Invalid property descriptor: cannot mix data and accessor fields", )); @@ -195,7 +193,7 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { let get = get_field.unwrap_or(JsValue::undefined()); let set = set_field.unwrap_or(JsValue::undefined()); if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "accessor descriptor get/set must be callable or undefined", )); @@ -204,24 +202,22 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { .define_accessor_property(obj, si, get, set, PropAttributes::new(false, enumerable, configurable)) .is_err() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Cannot define property")); + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot define property")); } } else { let value = value_field.unwrap_or(JsValue::undefined()); - let writable = writable_field - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let writable = writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false); if vm .define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable)) .is_err() { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Cannot define property")); + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot define property")); } } NativeResult::Ok(obj_val) } -pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { return NativeResult::Err(JsValue::undefined()); } @@ -233,8 +229,8 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes if obj_ptr.is_null() { return NativeResult::Ok(JsValue::undefined()); } - let prop_name_str = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); - let si = vm.kernel_core().string_forge().intern(&prop_name_str).0; + let prop_name_str = oxide_runtime_api::to_string(vm.reg(args[2])); + let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0; let (found_value, found_meta, found) = { let obj = unsafe { &*obj_ptr }; @@ -257,7 +253,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes return NativeResult::Ok(JsValue::undefined()); } - let sf_ptr = vm.kernel_core().string_forge().as_ref() as *const StringForge; + let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner; let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge; let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let desc = vm @@ -283,7 +279,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes NativeResult::Ok(JsValue::from_js_object(desc)) } -fn own_field(vm: &Vm, obj: &JsObject, prop_si: u32) -> Option { +fn own_field(vm: &H, obj: &JsObject, prop_si: u32) -> Option { vm.kernel_core() .shape_forge() .lookup_position(obj.shape_id(), prop_si) @@ -306,26 +302,17 @@ fn push_desc_prop(obj: &mut JsObject, shape_forge: &ShapeForge, prop_si: u32, va obj.push_prop(val); } -fn require_obj_arg(vm: &mut Vm, args: &[u8], fn_name: &str) -> Result<*mut JsObject, JsValue> { +fn require_obj_arg(vm: &mut H, args: &[u8], fn_name: &str) -> Result<*mut JsObject, JsValue> { if args.len() < 2 { - return Err(crate::builtins::error::create_type_error( - vm, - &format!("Object.{fn_name} called on non-object"), - )); + return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); } let val = vm.reg(args[1]); if !val.is_object() { - return Err(crate::builtins::error::create_type_error( - vm, - &format!("Object.{fn_name} called on non-object"), - )); + return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); } let ptr = val.as_js_object_ptr(); if ptr.is_null() { - return Err(crate::builtins::error::create_type_error( - vm, - &format!("Object.{fn_name} called on non-object"), - )); + return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object"))); } Ok(ptr) } @@ -339,7 +326,7 @@ macro_rules! native_try { }; } -pub fn object_freeze(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); } @@ -350,7 +337,7 @@ pub fn object_freeze(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(val) } -pub fn object_seal(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); } @@ -361,7 +348,7 @@ pub fn object_seal(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(val) } -pub fn object_prevent_extensions(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_prevent_extensions(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); } @@ -372,7 +359,7 @@ pub fn object_prevent_extensions(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(val) } -pub fn object_is_frozen(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_is_frozen(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(true)); } @@ -383,7 +370,7 @@ pub fn object_is_frozen(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(false)) } -pub fn object_is_sealed(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_is_sealed(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(true)); } @@ -394,19 +381,19 @@ pub fn object_is_sealed(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(false)) } -pub fn object_is_extensible(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult { native_try!(require_obj_arg(vm, args, "isExtensible")); NativeResult::Ok(JsValue::bool(true)) } -pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_get_own_property_names(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames")); let key_names: Vec = { let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); keys.iter() - .map(|(si, _)| vm.kernel_core().string_forge().lookup(*si).unwrap_or_default()) + .map(|(si, _)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) .collect() }; @@ -419,7 +406,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, k) in key_names.iter().enumerate() { - let str_val = vm.intern(k); + let str_val = vm.new_string(k); unsafe { (*arr).set_prop_at(i, str_val); } @@ -430,7 +417,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(arr)) } -pub fn object_define_properties(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_define_properties(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { return NativeResult::Ok(vm.reg(args[1])); } @@ -442,7 +429,7 @@ pub fn object_define_properties(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[1])) } -pub fn object_from_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_from_entries(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::null()); } @@ -457,12 +444,12 @@ pub fn object_from_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(obj_ptr)) } -pub fn object_get_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf")); NativeResult::Ok(unsafe { (*obj_ptr).proto() }) } -pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_has_own(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 3 { return NativeResult::Ok(JsValue::bool(false)); } @@ -472,13 +459,13 @@ pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) } -pub fn object_proto_has_own_property(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } let this_val = vm.reg(args[0]); if !this_val.is_object() || this_val.as_js_object_ptr().is_null() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Object.prototype.hasOwnProperty called on non-object", )); @@ -488,13 +475,13 @@ pub fn object_proto_has_own_property(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some())) } -pub fn object_proto_property_is_enumerable(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_proto_property_is_enumerable(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); } let this_val = vm.reg(args[0]); if !this_val.is_object() || this_val.as_js_object_ptr().is_null() { - return NativeResult::Err(crate::builtins::error::create_type_error( + return NativeResult::Err(crate::error::create_type_error( vm, "Object.prototype.propertyIsEnumerable called on non-object", )); @@ -511,7 +498,7 @@ pub fn object_proto_property_is_enumerable(vm: &mut Vm, args: &[u8]) -> NativeRe NativeResult::Ok(JsValue::bool(enumerable)) } -pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_entries(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "entries")); let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); @@ -524,8 +511,8 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, (si, offset)) in keys.iter().enumerate() { - let key_str = vm.kernel_core().string_forge().lookup(*si).unwrap_or_default(); - let key_val = vm.intern(&key_str); + let key_str = vm.kernel_core().perm_interner().lookup(*si).unwrap_or_default(); + let key_val = vm.new_string(key_str); let val = obj.get_prop_at(*offset); let pair = vm.alloc_object(JsObject::new_array( EMPTY_SHAPE_ID, @@ -546,7 +533,7 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(arr)) } -pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn object_values(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "values")); let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); diff --git a/crates/oxide_vm/src/builtins/reflect.rs b/crates/oxide_builtins/src/reflect.rs similarity index 78% rename from crates/oxide_vm/src/builtins/reflect.rs rename to crates/oxide_builtins/src/reflect.rs index 9d8ada1..c775db5 100644 --- a/crates/oxide_vm/src/builtins/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -2,12 +2,11 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::value::JsValue; -use crate::builtins::object::walk_own_keys; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use crate::object::walk_own_keys; -pub fn reflect_apply(vm: &mut Vm, args: &[u8]) -> NativeResult { +use oxide_runtime_api::{NativeResult, VmHost}; + +pub fn reflect_apply(vm: &mut H, args: &[u8]) -> NativeResult { let target = arg(vm, args, 1); let this_arg = arg(vm, args, 2); let arg_list = arg(vm, args, 3); @@ -23,11 +22,11 @@ pub fn reflect_apply(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn reflect_construct(vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn reflect_construct(vm: &mut H, _args: &[u8]) -> NativeResult { type_error(vm, "Reflect.construct not yet supported") } -pub fn reflect_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_define_property(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.defineProperty target is not an object"); @@ -39,22 +38,22 @@ pub fn reflect_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { let key_si = vm.property_key_si(arg(vm, args, 2)); let desc = unsafe { &*desc_ptr }; - let value_si = vm.kernel_core().string_forge().intern("value").0; - let get_si = vm.kernel_core().string_forge().intern("get").0; - let set_si = vm.kernel_core().string_forge().intern("set").0; - let writable_si = vm.kernel_core().string_forge().intern("writable").0; - let enumerable_si = vm.kernel_core().string_forge().intern("enumerable").0; - let configurable_si = vm.kernel_core().string_forge().intern("configurable").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let get_si = vm.kernel_core().perm_interner().intern("get").0; + let set_si = vm.kernel_core().perm_interner().intern("set").0; + let writable_si = vm.kernel_core().perm_interner().intern("writable").0; + let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0; + let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0; let value_field = own_field(vm, desc, value_si); let get_field = own_field(vm, desc, get_si); let set_field = own_field(vm, desc, set_si); let writable_field = own_field(vm, desc, writable_si); let enumerable = own_field(vm, desc, enumerable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) + .map(oxide_runtime_api::to_boolean) .unwrap_or(false); let configurable = own_field(vm, desc, configurable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) + .map(oxide_runtime_api::to_boolean) .unwrap_or(false); let has_data = value_field.is_some() || writable_field.is_some(); @@ -73,15 +72,13 @@ pub fn reflect_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.define_accessor_property(target, key_si, get, set, PropAttributes::new(false, enumerable, configurable)) } else { let value = value_field.unwrap_or(JsValue::undefined()); - let writable = writable_field - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let writable = writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false); vm.define_data_property(target, key_si, value, PropAttributes::new(writable, enumerable, configurable)) }; NativeResult::Ok(JsValue::bool(result.is_ok())) } -pub fn reflect_delete_property(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_delete_property(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.deleteProperty target is not an object"); @@ -91,7 +88,7 @@ pub fn reflect_delete_property(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(delete_own_property(vm, target, key_si))) } -pub fn reflect_get(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_get(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.get target is not an object"); @@ -104,15 +101,15 @@ pub fn reflect_get(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn reflect_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(_) = object_ptr(target_val) else { return type_error(vm, "Reflect.getOwnPropertyDescriptor target is not an object"); }; - crate::builtins::object::object_get_own_property_descriptor(vm, args) + crate::object::object_get_own_property_descriptor(vm, args) } -pub fn reflect_get_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.getPrototypeOf target is not an object"); @@ -120,7 +117,7 @@ pub fn reflect_get_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(unsafe { &*target_ptr }.proto()) } -pub fn reflect_has(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_has(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.has target is not an object"); @@ -129,7 +126,7 @@ pub fn reflect_has(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(vm.resolve_property(unsafe { &*target_ptr }, key_si).is_some())) } -pub fn reflect_is_extensible(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.isExtensible target is not an object"); @@ -137,7 +134,7 @@ pub fn reflect_is_extensible(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(unsafe { &*target_ptr }.is_extensible())) } -pub fn reflect_own_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.ownKeys target is not an object"); @@ -145,12 +142,12 @@ pub fn reflect_own_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { let target = unsafe { &*target_ptr }; let key_names: Vec = walk_own_keys(vm, target) .into_iter() - .map(|(si, _)| vm.kernel_core().string_forge().lookup(si).unwrap_or_default()) + .map(|(si, _)| vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string()) .collect(); NativeResult::Ok(make_string_array(vm, &key_names)) } -pub fn reflect_prevent_extensions(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_prevent_extensions(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.preventExtensions target is not an object"); @@ -159,7 +156,7 @@ pub fn reflect_prevent_extensions(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(true)) } -pub fn reflect_set(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_set(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.set target is not an object"); @@ -174,7 +171,7 @@ pub fn reflect_set(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(vm.ordinary_set(target, key_si, value, receiver).is_ok())) } -pub fn reflect_set_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn reflect_set_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.setPrototypeOf target is not an object"); @@ -186,7 +183,7 @@ pub fn reflect_set_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(unsafe { &mut *target_ptr }.set_proto(proto).is_ok())) } -fn arg(vm: &Vm, args: &[u8], idx: usize) -> JsValue { +fn arg(vm: &H, args: &[u8], idx: usize) -> JsValue { args.get(idx).map(|reg| vm.reg(*reg)).unwrap_or_else(JsValue::undefined) } @@ -202,8 +199,8 @@ fn is_callable(value: JsValue) -> bool { object_ptr(value).is_some_and(|ptr| unsafe { &*ptr }.is_function()) } -fn type_error(vm: &mut Vm, message: &str) -> NativeResult { - NativeResult::Err(crate::builtins::error::create_type_error(vm, message)) +fn type_error(vm: &mut H, message: &str) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, message)) } fn array_like_elements(value: JsValue) -> Option> { @@ -212,14 +209,14 @@ fn array_like_elements(value: JsValue) -> Option> { Some((0..obj.prop_count() as usize).map(|idx| obj.get_prop_at(idx)).collect()) } -fn own_field(vm: &Vm, obj: &JsObject, prop_si: u32) -> Option { +fn own_field(vm: &H, obj: &JsObject, prop_si: u32) -> Option { vm.kernel_core() .shape_forge() .lookup_position(obj.shape_id(), prop_si) .and_then(|pos| (obj.prop_vec_len() > pos as usize).then(|| obj.get_prop_at(pos))) } -fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { +fn make_string_array(vm: &mut H, parts: &[String]) -> JsValue { let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.alloc_object(JsObject::new_array( EMPTY_SHAPE_ID, @@ -228,14 +225,14 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { vm.epoch().bump(), )); for (idx, part) in parts.iter().enumerate() { - let value = vm.intern(part); + let value = vm.new_string(part); unsafe { &mut *arr }.set_prop_at(idx, value); } unsafe { &mut *arr }.set_prop_count(parts.len()); JsValue::from_js_object(arr) } -fn delete_own_property(vm: &mut Vm, obj: &mut JsObject, key_si: u32) -> bool { +fn delete_own_property(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool { let keys = walk_own_keys(vm, obj); let Some((_, delete_pos)) = keys.iter().find(|(si, _)| *si == key_si).copied() else { return true; diff --git a/crates/oxide_vm/src/builtins/regexp.rs b/crates/oxide_builtins/src/regexp.rs similarity index 71% rename from crates/oxide_vm/src/builtins/regexp.rs rename to crates/oxide_builtins/src/regexp.rs index 1c32d52..948b2b9 100644 --- a/crates/oxide_vm/src/builtins/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -2,27 +2,19 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; -fn get_regexp_ptr(vm: &mut Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> { +fn get_regexp_ptr(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> { let this_val = vm.reg(args[0]); if !this_val.is_object() { - return Err(crate::builtins::error::create_type_error( - vm, - "RegExp.prototype method called on non-object", - )); + return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-object")); } let ptr = this_val.as_js_object_ptr(); if ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "null object")); + return Err(crate::error::create_type_error(vm, "null object")); } if !unsafe { &*ptr }.is_regexp_obj() { - return Err(crate::builtins::error::create_type_error( - vm, - "RegExp.prototype method called on non-RegExp object", - )); + return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-RegExp object")); } Ok(ptr) } @@ -42,8 +34,8 @@ fn parse_flags(flags: &str) -> (bool, bool, bool) { (global, ignore_case, multi_line) } -fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &Vm) { - let si = vm.kernel_core().string_forge().intern(name).0; +fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &H) { + let si = vm.kernel_core().perm_interner().intern(name).0; let shape_id = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si); obj.set_shape_id(shape_id); obj.ensure_hash_props().push(val); @@ -67,15 +59,15 @@ fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) { } } -pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let (pattern, flags) = if args.len() < 2 { (String::new(), String::new()) } else if args.len() < 3 { - let pat = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])); + let pat = oxide_runtime_api::to_string(vm.reg(args[1])); (pat, String::new()) } else { - let pat = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])); - let fl = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); + let pat = oxide_runtime_api::to_string(vm.reg(args[1])); + let fl = oxide_runtime_api::to_string(vm.reg(args[2])); (pat, fl) }; @@ -101,7 +93,7 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) })); } Err(e) => { - return NativeResult::Err(crate::builtins::error::create_syntax_error( + return NativeResult::Err(crate::error::create_syntax_error( vm, &format!("Invalid regular expression: {e}"), )); @@ -109,8 +101,8 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } set_prop(&mut obj, "lastIndex", JsValue::int(0), vm); - set_prop(&mut obj, "source", vm.intern(&pattern), vm); - set_prop(&mut obj, "flags", vm.intern(&flags), vm); + set_prop(&mut obj, "source", vm.new_string(&pattern), vm); + set_prop(&mut obj, "flags", vm.new_string(&flags), vm); set_prop(&mut obj, "global", JsValue::bool(global), vm); set_prop(&mut obj, "ignoreCase", JsValue::bool(ignore_case), vm); set_prop(&mut obj, "multiline", JsValue::bool(multi_line), vm); @@ -123,7 +115,7 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(obj_ptr)) } -pub(crate) fn drop_regexp_native(obj: &mut JsObject) -> u64 { +pub fn drop_regexp_native(obj: &mut JsObject) -> u64 { if !obj.is_regexp_obj() { return 0; } @@ -139,7 +131,7 @@ pub(crate) fn drop_regexp_native(obj: &mut JsObject) -> u64 { std::mem::size_of::() as u64 } -pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn regexp_test(vm: &mut H, args: &[u8]) -> NativeResult { let re_ptr = match get_regexp_ptr(vm, args) { Ok(ptr) => ptr, Err(err) => return NativeResult::Err(err), @@ -148,18 +140,15 @@ pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult { let fn_ptr = match re.native_fn() { None => { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "invalid RegExp")); + return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp")); } Some(p) => p, }; // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; - let haystack = coercion::to_string( - vm.kernel_core().string_forge().as_ref(), - vm.reg(if args.len() > 1 { args[1] } else { args[0] }), - ); - let last_index = coercion::to_number(get_prop(re, 0), vm.kernel_core().string_forge().as_ref()) as usize; + let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); + let last_index = oxide_runtime_api::to_number(get_prop(re, 0)) as usize; let is_global = get_prop(re, 3).as_bool(); if is_global { @@ -170,7 +159,7 @@ pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn regexp_exec(vm: &mut H, args: &[u8]) -> NativeResult { let re_ptr = match get_regexp_ptr(vm, args) { Ok(ptr) => ptr, Err(err) => return NativeResult::Err(err), @@ -180,7 +169,7 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; match re.native_fn() { None => { - return NativeResult::Err(crate::builtins::error::create_type_error(vm, "invalid RegExp")); + return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp")); } Some(p) => p, } @@ -188,14 +177,11 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; - let haystack = coercion::to_string( - vm.kernel_core().string_forge().as_ref(), - vm.reg(if args.len() > 1 { args[1] } else { args[0] }), - ); + let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); let (last_index, is_global) = { let re = unsafe { &*re_ptr }; - let li = coercion::to_number(get_prop(re, 0), vm.kernel_core().string_forge().as_ref()) as usize; + let li = oxide_runtime_api::to_number(get_prop(re, 0)) as usize; let g = get_prop(re, 3).as_bool(); (li, g) }; @@ -216,20 +202,20 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { ); let full_match = &haystack[m.start()..m.end()]; - let full_si = vm.kernel_core().string_forge().intern(full_match).0; - match_obj.ensure_hash_props().push(JsValue::string(full_si, 0)); + let full_val = vm.new_string(full_match); + match_obj.ensure_hash_props().push(full_val); let captures = regex.captures(&haystack[m.start()..m.end()]); if let Some(caps) = &captures { for i in 1..caps.len() { let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or(""); - let si = vm.kernel_core().string_forge().intern(cap_str).0; - match_obj.ensure_hash_props().push(JsValue::string(si, 0)); + let cap_val = vm.new_string(cap_str); + match_obj.ensure_hash_props().push(cap_val); } } set_prop(&mut match_obj, "index", JsValue::int(m.start() as i32), vm); - let haystack_val = vm.intern(&haystack); + let haystack_val = vm.new_string(&haystack); set_prop(&mut match_obj, "input", haystack_val, vm); set_prop(&mut match_obj, "groups", JsValue::undefined(), vm); @@ -247,7 +233,7 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn regexp_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let re_ptr = match get_regexp_ptr(vm, args) { Ok(ptr) => ptr, Err(err) => return NativeResult::Err(err), @@ -262,6 +248,5 @@ pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.lookup_str(val).unwrap_or_default() }; let result = format!("/{}/{}", source, flags); - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_vm/src/builtins/set.rs b/crates/oxide_builtins/src/set.rs similarity index 80% rename from crates/oxide_vm/src/builtins/set.rs rename to crates/oxide_builtins/src/set.rs index 05f8e3b..ce4519d 100644 --- a/crates/oxide_vm/src/builtins/set.rs +++ b/crates/oxide_builtins/src/set.rs @@ -4,8 +4,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::native::NativeResult; -use crate::vm::Vm; +use oxide_runtime_api::{NativeResult, VmHost}; macro_rules! native_try { ($expr:expr) => { @@ -68,29 +67,26 @@ type SetInner = indexmap::IndexSet; /// native builtin is executing. The `Box` is allocated in `new_set_inner()` /// and is never freed during the Set's lifetime. Only one live `*mut` alias exists per /// Set object at a time because native calls are single-threaded. -fn get_set_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut SetInner, JsValue> { +fn get_set_inner(vm: &mut H, this_val: JsValue) -> Result<*mut SetInner, JsValue> { if !this_val.is_object() { - return Err(crate::builtins::error::create_type_error(vm, "called on non-Set object")); + return Err(crate::error::create_type_error(vm, "called on non-Set object")); } let set_ptr = this_val.as_js_object_ptr(); if set_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "Set internal state invalid")); + return Err(crate::error::create_type_error(vm, "Set internal state invalid")); } // SAFETY: set_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the // current Epoch. Remains valid for the duration of this call. let set_obj = unsafe { &*set_ptr }; if !set_obj.is_set() { - return Err(crate::builtins::error::create_type_error( - vm, - "Set.prototype.add called on incompatible receiver", - )); + return Err(crate::error::create_type_error(vm, "Set.prototype.add called on incompatible receiver")); } // SAFETY: native_data holds the raw pointer written by `alloc_set`. // The pointer is a valid, heap-allocated `Box>`. // Alignment: IndexSet requires at most 8-byte alignment; the global allocator satisfies this. let inner_ptr = set_obj.native_data() as *mut SetInner; if inner_ptr.is_null() { - return Err(crate::builtins::error::create_type_error(vm, "Set internal state invalid")); + return Err(crate::error::create_type_error(vm, "Set internal state invalid")); } Ok(inner_ptr) } @@ -99,7 +95,7 @@ fn new_set_inner() -> *mut SetInner { Box::into_raw(Box::new(SetInner::new())) } -fn alloc_set(vm: &mut Vm) -> *mut JsObject { +fn alloc_set(vm: &mut H) -> *mut JsObject { let set_proto = vm.session().builtin_world().set_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(set_proto)); obj.set_set(true); @@ -108,7 +104,7 @@ fn alloc_set(vm: &mut Vm) -> *mut JsObject { vm.alloc_object(obj) } -pub(crate) fn set_native_edges(obj: &JsObject) -> Vec { +pub fn set_native_edges(obj: &JsObject) -> Vec { if !obj.is_set() { return Vec::new(); } @@ -116,10 +112,16 @@ pub(crate) fn set_native_edges(obj: &JsObject) -> Vec { if inner.is_null() { return Vec::new(); } - unsafe { (*inner).iter().map(|key| key.0).filter(|value| value.is_object()).collect() } + unsafe { + (*inner) + .iter() + .map(|key| key.0) + .filter(|value: &JsValue| value.is_object()) + .collect() + } } -pub(crate) fn clone_set_native_with_rewrite(src: &JsObject, dst: &mut JsObject, mut rewrite: F) +pub fn clone_set_native_with_rewrite(src: &JsObject, dst: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -141,7 +143,7 @@ where dst.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); } -pub(crate) fn rewrite_set_native(obj: &mut JsObject, mut rewrite: F) +pub fn rewrite_set_native(obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -162,7 +164,7 @@ where } } -pub(crate) fn drop_set_native(obj: &mut JsObject) -> u64 { +pub fn drop_set_native(obj: &mut JsObject) -> u64 { if !obj.is_set() { return 0; } @@ -171,7 +173,7 @@ pub(crate) fn drop_set_native(obj: &mut JsObject) -> u64 { return 0; } unsafe { - let boxed = Box::from_raw(inner); + let boxed: Box = Box::from_raw(inner); let bytes = std::mem::size_of::() + boxed.capacity() * std::mem::size_of::(); drop(boxed); obj.set_native_data(std::ptr::null_mut()); @@ -179,12 +181,12 @@ pub(crate) fn drop_set_native(obj: &mut JsObject) -> u64 { } } -pub fn set_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult { +pub fn set_constructor(vm: &mut H, _args: &[u8]) -> NativeResult { let set_obj = alloc_set(vm); NativeResult::Ok(JsValue::from_js_object(set_obj)) } -pub fn set_add(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn set_add(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_set_inner(vm, this_val)); let val = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -194,7 +196,7 @@ pub fn set_add(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -pub fn set_has(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn set_has(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_set_inner(vm, this_val)); let val = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -202,7 +204,7 @@ pub fn set_has(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(found)) } -pub fn set_delete(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn set_delete(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_set_inner(vm, this_val)); let val = vm.reg(if args.len() > 1 { args[1] } else { 0 }); @@ -210,7 +212,7 @@ pub fn set_delete(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::bool(removed)) } -pub fn set_clear(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn set_clear(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_set_inner(vm, this_val)); unsafe { @@ -219,7 +221,7 @@ pub fn set_clear(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn set_size(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn set_size(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let inner = native_try!(get_set_inner(vm, this_val)); NativeResult::Ok(JsValue::float(unsafe { (*inner).len() } as f64)) diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_builtins/src/string.rs similarity index 65% rename from crates/oxide_vm/src/builtins/string.rs rename to crates/oxide_builtins/src/string.rs index 862b384..8d98349 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -2,29 +2,32 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::JsObject; use oxide_types::value::JsValue; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; use memchr::memchr; +use oxide_runtime_api::{NativeResult, VmHost}; -fn this_string(vm: &Vm, args: &[u8]) -> String { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[0])) +use crate::builtins_debug; +use crate::builtins_error; + +fn this_string(vm: &H, args: &[u8]) -> String { + oxide_runtime_api::to_string(vm.reg(args[0])) } -pub fn string_from_char_code(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_from_char_code(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.fromCharCode called with {} args", args.len()); let mut out = String::new(); for &arg_reg in args.iter().skip(1) { - let code = coercion::to_uint32(vm.reg(arg_reg), vm.kernel_core().string_forge().as_ref()) & 0xFFFF; + let code = oxide_runtime_api::to_uint32(vm.reg(arg_reg)) & 0xFFFF; if let Some(ch) = char::from_u32(code) { out.push(ch); } else { out.push('\u{FFFD}'); } } - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } -pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_value_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.valueOf called with {} args", args.len()); let this_val = vm.reg(args[0]); if this_val.is_string() { return NativeResult::Ok(this_val); @@ -38,19 +41,20 @@ pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } } } - NativeResult::Err(crate::builtins::error::create_type_error( + builtins_error!("String.prototype.valueOf: invalid receiver"); + NativeResult::Err(crate::error::create_type_error( vm, "String.prototype.valueOf called on non-String object", )) } -pub fn string_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let s = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + oxide_runtime_api::to_string(vm.reg(args[1])) } else { String::new() }; - let str_val = vm.intern(&s); + let str_val = vm.new_string(&s); let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject; let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); @@ -76,7 +80,7 @@ pub fn string_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -pub fn string_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); if this_val.is_string() { return NativeResult::Ok(this_val); @@ -90,13 +94,13 @@ pub fn string_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { } } } - NativeResult::Err(crate::builtins::error::create_type_error( + NativeResult::Err(crate::error::create_type_error( vm, "String.prototype.toString called on non-String object", )) } -fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { +fn make_string_array(vm: &mut H, parts: &[String]) -> JsValue { let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let n = parts.len(); let arr = @@ -104,7 +108,7 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { .alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); unsafe { for (i, s) in parts.iter().enumerate() { - let sv = vm.intern(s); + let sv = vm.new_string(s); (*arr).set_prop_at(i, sv); } (*arr).set_prop_count(n); @@ -112,8 +116,8 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { JsValue::from_js_object(arr) } -fn as_string(vm: &Vm, val: JsValue) -> String { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), val) +fn as_string(_vm: &H, val: JsValue) -> String { + oxide_runtime_api::to_string(val) } fn char_len(s: &str) -> usize { @@ -137,7 +141,7 @@ fn take_chars(s: &str, count: usize) -> String { s.chars().take(count).collect() } -fn is_regexp_obj(val: JsValue, vm: &Vm) -> bool { +fn is_regexp_obj(val: JsValue, vm: &H) -> bool { if !val.is_object() { return false; } @@ -158,7 +162,8 @@ fn is_regexp_obj(val: JsValue, vm: &Vm) -> bool { std::ptr::eq(proto_ptr, rp) } -pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_index_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.indexOf called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s); if args.len() < 2 { @@ -166,7 +171,7 @@ pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (oxide_runtime_api::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; @@ -189,7 +194,8 @@ pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(-1)) } -pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_includes(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.includes called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s); if args.len() < 2 { @@ -197,7 +203,7 @@ pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (oxide_runtime_api::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; @@ -214,23 +220,25 @@ pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { } } -pub fn string_char_at(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_char_at(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.charAt called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { if s.is_empty() { - return NativeResult::Ok(vm.intern("")); + return NativeResult::Ok(vm.new_string("")); } - return NativeResult::Ok(vm.intern(&take_chars(&s, 1))); + return NativeResult::Ok(vm.new_string(&take_chars(&s, 1))); } - let idx = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let idx = oxide_runtime_api::to_number(vm.reg(args[1])) as i32; if idx < 0 || idx as usize >= char_len(&s) { - return NativeResult::Ok(vm.intern("")); + return NativeResult::Ok(vm.new_string("")); } let ch = s.chars().nth(idx as usize).map(|c| c.to_string()).unwrap_or_default(); - NativeResult::Ok(vm.intern(&ch)) + NativeResult::Ok(vm.new_string(&ch)) } -pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_char_code_at(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.charCodeAt called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { if s.is_empty() { @@ -238,27 +246,28 @@ pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult { } return NativeResult::Ok(JsValue::int(s.chars().next().unwrap() as i32)); } - let idx = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let idx = oxide_runtime_api::to_number(vm.reg(args[1])) as i32; if idx < 0 || idx as usize >= char_len(&s) { return NativeResult::Ok(JsValue::float(f64::NAN)); } NativeResult::Ok(JsValue::int(s.chars().nth(idx as usize).unwrap() as i32)) } -pub fn string_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_concat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.concat called with {} args", args.len()); let mut result = this_string(vm, args); - let sf = vm.kernel_core().string_forge().as_ref(); for &arg_reg in args.iter().skip(1) { - result.push_str(&coercion::to_string(sf, vm.reg(arg_reg))); + result.push_str(&oxide_runtime_api::to_string(vm.reg(arg_reg))); } - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } -pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_slice(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.slice called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s) as i32; let start = if args.len() > 1 { - let v = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let v = oxide_runtime_api::to_number(vm.reg(args[1])) as i32; if v < 0 { (n + v).max(0) } else { @@ -268,7 +277,7 @@ pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { 0 }; let end = if args.len() > 2 { - let v = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32; + let v = oxide_runtime_api::to_number(vm.reg(args[2])) as i32; if v < 0 { (n + v).max(0) } else { @@ -280,23 +289,20 @@ pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { let start = start as usize; let end = end as usize; let result = if start < end { char_slice(&s, start, end) } else { "" }; - NativeResult::Ok(vm.intern(result)) + NativeResult::Ok(vm.new_string(result)) } -pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_substring(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.substring called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s) as i32; let mut start = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) - .max(0) - .min(n) + (oxide_runtime_api::to_number(vm.reg(args[1])) as i32).max(0).min(n) } else { 0 }; let mut end = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) - .max(0) - .min(n) + (oxide_runtime_api::to_number(vm.reg(args[2])) as i32).max(0).min(n) } else { n }; @@ -304,81 +310,90 @@ pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult { std::mem::swap(&mut start, &mut end); } let result = char_slice(&s, start as usize, end as usize); - NativeResult::Ok(vm.intern(result)) + NativeResult::Ok(vm.new_string(result)) } -pub fn string_to_upper_case(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_to_upper_case(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.toUpperCase called with {} args", args.len()); let s = this_string(vm, args); - NativeResult::Ok(vm.intern(&s.to_uppercase())) + NativeResult::Ok(vm.new_string(&s.to_uppercase())) } -pub fn string_to_lower_case(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_to_lower_case(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.toLowerCase called with {} args", args.len()); let s = this_string(vm, args); - NativeResult::Ok(vm.intern(&s.to_lowercase())) + NativeResult::Ok(vm.new_string(&s.to_lowercase())) } -pub fn string_trim(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_trim(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trim called with {} args", args.len()); let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim())) + NativeResult::Ok(vm.new_string(s.trim())) } -pub fn string_repeat(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_repeat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.repeat called with {} args", args.len()); let s = this_string(vm, args); let n = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize).min(10000) + (oxide_runtime_api::to_number(vm.reg(args[1])) as usize).min(10000) } else { 1 }; - NativeResult::Ok(vm.intern(&s.repeat(n))) + NativeResult::Ok(vm.new_string(&s.repeat(n))) } -pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_pad_start(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.padStart called with {} args", args.len()); let s = this_string(vm, args); let s_len = char_len(&s); let target = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { s_len }; if target > 10000 { - return NativeResult::Err(crate::builtins::error::create_range_error(vm, "Invalid string length")); + builtins_error!("String.prototype.padStart: invalid receiver"); + return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length")); } let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() }; if s_len >= target || pad.is_empty() { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let needed = target - s_len; let pad_len = char_len(&pad).max(1); let reps = needed.div_ceil(pad_len); let mut out = take_chars(&pad.repeat(reps), needed); out.push_str(&s); - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } -pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_pad_end(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.padEnd called with {} args", args.len()); let s = this_string(vm, args); let s_len = char_len(&s); let target = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { s_len }; if target > 10000 { - return NativeResult::Err(crate::builtins::error::create_range_error(vm, "Invalid string length")); + builtins_error!("String.prototype.padEnd: invalid receiver"); + return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length")); } let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() }; if s_len >= target || pad.is_empty() { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let needed = target - s_len; let pad_len = char_len(&pad).max(1); let reps = needed.div_ceil(pad_len); let mut out = s; out.push_str(&take_chars(&pad.repeat(reps), needed)); - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } -pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_starts_with(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.startsWith called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s); if args.len() < 2 { @@ -386,14 +401,15 @@ pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (oxide_runtime_api::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; NativeResult::Ok(JsValue::bool(s[byte_index_at_char(&s, pos)..].starts_with(&search))) } -pub fn string_ends_with(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_ends_with(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.endsWith called with {} args", args.len()); let s = this_string(vm, args); let n = char_len(&s); if args.len() < 2 { @@ -401,14 +417,15 @@ pub fn string_ends_with(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let end_pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (oxide_runtime_api::to_number(vm.reg(args[2])) as usize).min(n) } else { n }; NativeResult::Ok(JsValue::bool(s[..byte_index_at_char(&s, end_pos)].ends_with(&search))) } -pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_split(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.split called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { let parts = vec![s.clone()]; @@ -416,7 +433,7 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult { } let sep_val = vm.reg(args[1]); let limit = if args.len() > 2 { - coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[2])) as usize } else { usize::MAX }; @@ -447,10 +464,11 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(make_string_array(vm, &parts)) } -pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_replace(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.replace called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let pattern_val = vm.reg(args[1]); let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; @@ -460,20 +478,21 @@ pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, - None => return NativeResult::Ok(vm.intern(&s)), + None => return NativeResult::Ok(vm.new_string(&s)), }; // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let result = regex.replace_all(&s, replacement.as_str()).to_string(); - return NativeResult::Ok(vm.intern(&result)); + return NativeResult::Ok(vm.new_string(&result)); } let pattern = as_string(vm, pattern_val); let result = s.replacen(&pattern, &replacement, 1); - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } -pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_match_fn(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.match called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { return NativeResult::Ok(JsValue::undefined()); @@ -500,7 +519,8 @@ pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_search(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.search called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { return NativeResult::Ok(JsValue::int(-1)); @@ -529,20 +549,23 @@ pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(-1)) } -pub fn string_trim_start(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_trim_start(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trimStart called with {} args", args.len()); let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim_start())) + NativeResult::Ok(vm.new_string(s.trim_start())) } -pub fn string_trim_end(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_trim_end(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.trimEnd called with {} args", args.len()); let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim_end())) + NativeResult::Ok(vm.new_string(s.trim_end())) } -pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_code_point_at(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.codePointAt called with {} args", args.len()); let s = this_string(vm, args); let pos = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + oxide_runtime_api::to_number(vm.reg(args[1])) as usize } else { 0 }; @@ -561,7 +584,8 @@ pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::int(c as i32)) } -pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_normalize(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.normalize called with {} args", args.len()); use unicode_normalization::UnicodeNormalization; let s = this_string(vm, args); let form = if args.len() > 1 { as_string(vm, vm.reg(args[1])) } else { "NFC".to_string() }; @@ -571,12 +595,14 @@ pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult { "NFKD" => s.nfkd().collect(), _ => s.nfc().collect(), }; - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } -pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.matchAll called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { + builtins_error!("String.prototype.matchAll: invalid receiver"); return NativeResult::Err(JsValue::undefined()); } let pattern_val = vm.reg(args[1]); @@ -595,10 +621,11 @@ pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn string_replace_all(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("String.prototype.replaceAll called with {} args", args.len()); let s = this_string(vm, args); if args.len() < 2 { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let pattern_val = vm.reg(args[1]); let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; @@ -607,14 +634,14 @@ pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, - None => return NativeResult::Ok(vm.intern(&s)), + None => return NativeResult::Ok(vm.new_string(&s)), }; // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let result = regex.replace_all(&s, replacement.as_str()).to_string(); - return NativeResult::Ok(vm.intern(&result)); + return NativeResult::Ok(vm.new_string(&result)); } let pattern = as_string(vm, pattern_val); let result = s.replace(&pattern, &replacement); - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_builtins/src/stubs.rs b/crates/oxide_builtins/src/stubs.rs new file mode 100644 index 0000000..ccc267d --- /dev/null +++ b/crates/oxide_builtins/src/stubs.rs @@ -0,0 +1,31 @@ +use oxide_runtime_api::{NativeResult, VmHost}; + +fn stub_error(vm: &mut H, name: &str) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, &format!("{name} is not implemented"))) +} + +// Architecturally deferred — these features are excluded from the supported language subset by design. +pub fn proxy_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "Proxy") +} +pub fn bigint_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "BigInt") +} +pub fn weakmap_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "WeakMap") +} +pub fn weakset_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "WeakSet") +} +pub fn weakref_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "WeakRef") +} +pub fn finalization_registry_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "FinalizationRegistry") +} +pub fn shared_array_buffer_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "SharedArrayBuffer") +} +pub fn atomics_stub(vm: &mut H, _args: &[u8]) -> NativeResult { + stub_error(vm, "Atomics") +} diff --git a/crates/oxide_builtins/src/symbol.rs b/crates/oxide_builtins/src/symbol.rs new file mode 100644 index 0000000..7501423 --- /dev/null +++ b/crates/oxide_builtins/src/symbol.rs @@ -0,0 +1,74 @@ +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use oxide_runtime_api::{NativeResult, VmHost}; + +pub fn symbol_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if this_val.is_object() { + let ptr = this_val.as_js_object_ptr(); + if !ptr.is_null() { + let obj = unsafe { &*ptr }; + let proto = obj.proto(); + if proto.is_object() { + let proto_ptr = proto.as_js_object_ptr(); + if !proto_ptr.is_null() { + let sp = vm.session().builtin_world().symbol_proto.as_ptr() as *mut JsObject; + if std::ptr::eq(proto_ptr, sp) { + return NativeResult::Err(crate::error::create_type_error(vm, "Symbol is not a constructor")); + } + } + } + } + } + + let description = if args.len() > 1 { + oxide_runtime_api::to_string(vm.reg(args[1])) + } else { + String::new() + }; + + let idx = vm.symbol_intern(description); + NativeResult::Ok(JsValue::symbol(idx)) +} + +pub fn symbol_to_string(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(args[0]); + if !this_val.is_symbol() { + return NativeResult::Err(crate::error::create_type_error(vm, "Symbol.prototype.toString requires a Symbol")); + } + + let idx = this_val.as_symbol_index(); + let desc = vm.symbol_description(idx).unwrap_or("").to_string(); + let result = format!("Symbol({})", desc); + NativeResult::Ok(vm.new_string(&result)) +} + +pub fn symbol_for(vm: &mut H, args: &[u8]) -> NativeResult { + let key = if args.len() > 1 { + oxide_runtime_api::to_string(vm.reg(args[1])) + } else { + "undefined".to_string() + }; + + if let Some(idx) = vm.symbol_lookup_global(&key) { + return NativeResult::Ok(JsValue::symbol(idx)); + } + + let idx = vm.symbol_intern(key.clone()); + vm.symbol_register_global(key, idx); + NativeResult::Ok(JsValue::symbol(idx)) +} + +pub fn symbol_key_for(vm: &mut H, args: &[u8]) -> NativeResult { + let sym = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if !sym.is_symbol() { + return NativeResult::Err(crate::error::create_type_error(vm, "is not a symbol")); + } + + let idx = sym.as_symbol_index(); + match vm.symbol_key_for_id(idx) { + Some(key) => NativeResult::Ok(vm.new_string(&key)), + None => NativeResult::Ok(JsValue::undefined()), + } +} diff --git a/crates/oxide_vm/src/builtins/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs similarity index 87% rename from crates/oxide_vm/src/builtins/typed_array.rs rename to crates/oxide_builtins/src/typed_array.rs index b1b9b0e..bfc80da 100644 --- a/crates/oxide_vm/src/builtins/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -2,10 +2,9 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes, TypedArrayKind}; use oxide_types::value::JsValue; -use crate::builtins::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; -use crate::coercion; -use crate::native::NativeResult; -use crate::vm::Vm; +use crate::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; + +use oxide_runtime_api::{NativeResult, VmHost}; #[derive(Clone, Copy)] pub(crate) struct TypedArrayData { @@ -24,16 +23,16 @@ macro_rules! native_try { }; } -fn type_error(vm: &mut Vm, msg: &str) -> JsValue { - crate::builtins::error::create_type_error(vm, msg) +fn type_error(vm: &mut H, msg: &str) -> JsValue { + crate::error::create_type_error(vm, msg) } -fn range_error(vm: &mut Vm, msg: &str) -> JsValue { - crate::builtins::error::create_range_error(vm, msg) +fn range_error(vm: &mut H, msg: &str) -> JsValue { + crate::error::create_range_error(vm, msg) } -fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn to_index(vm: &mut H, value: JsValue, msg: &str) -> Result { + let n = oxide_runtime_api::to_number(value); if n.is_nan() { return Ok(0); } @@ -43,8 +42,8 @@ fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { Ok(n.trunc() as usize) } -fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn normalize_index(_vm: &mut H, value: JsValue, len: usize) -> usize { + let n = oxide_runtime_api::to_number(value); if n.is_nan() { return 0; } @@ -56,12 +55,12 @@ fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { } } -fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; +fn set_named_prop(vm: &mut H, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } -fn typed_array_proto_ptr(vm: &mut Vm, kind: TypedArrayKind) -> *mut JsObject { +fn typed_array_proto_ptr(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { let world = vm.session().builtin_world(); match kind { TypedArrayKind::Int8 => world.int8array_proto.as_ptr() as *mut JsObject, @@ -78,8 +77,8 @@ fn typed_array_proto_ptr(vm: &mut Vm, kind: TypedArrayKind) -> *mut JsObject { } } -fn create_typed_array( - vm: &mut Vm, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, +fn create_typed_array( + vm: &mut H, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize, ) -> *mut JsObject { let proto = typed_array_proto_ptr(vm, kind); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); @@ -127,7 +126,7 @@ fn typed_array_data_ptr(obj: &JsObject) -> Option<*mut TypedArrayData> { obj.native_fn().map(|ptr| ptr.as_ptr() as *mut TypedArrayData) } -pub(crate) fn typed_array_native_edges(obj: &JsObject) -> Vec { +pub fn typed_array_native_edges(obj: &JsObject) -> Vec { let Some(ptr) = typed_array_data_ptr(obj) else { return Vec::new(); }; @@ -142,7 +141,7 @@ pub(crate) fn typed_array_native_edges(obj: &JsObject) -> Vec { } } -pub(crate) fn clone_typed_array_native_with_rewrite(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F) +pub fn clone_typed_array_native_with_rewrite(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -158,7 +157,7 @@ where new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned as *const ()) })); } -pub(crate) fn rewrite_typed_array_native(obj: &mut JsObject, mut rewrite: F) +pub fn rewrite_typed_array_native(obj: &mut JsObject, mut rewrite: F) where F: FnMut(JsValue) -> JsValue, { @@ -173,7 +172,7 @@ where } } -pub(crate) fn drop_typed_array_native(obj: &mut JsObject) -> u64 { +pub fn drop_typed_array_native(obj: &mut JsObject) -> u64 { let Some(ptr) = typed_array_data_ptr(obj) else { return 0; }; @@ -185,7 +184,7 @@ pub(crate) fn drop_typed_array_native(obj: &mut JsObject) -> u64 { std::mem::size_of::() as u64 } -pub(crate) fn get_typed_array_data(vm: &mut Vm, this_val: JsValue) -> Result { +pub(crate) fn get_typed_array_data(vm: &mut H, this_val: JsValue) -> Result { if !this_val.is_object() { return Err(type_error(vm, "TypedArray method called on incompatible receiver")); } @@ -236,8 +235,8 @@ fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { } } -fn numeric_value(vm: &mut Vm, value: JsValue) -> f64 { - coercion::to_number(value, vm.kernel_core().string_forge().as_ref()) +fn numeric_value(_vm: &mut H, value: JsValue) -> f64 { + oxide_runtime_api::to_number(value) } fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f64) { @@ -256,7 +255,7 @@ fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f } } -fn collect_array_like(vm: &mut Vm, value: JsValue) -> Result, JsValue> { +fn collect_array_like(vm: &mut H, value: JsValue) -> Result, JsValue> { if !value.is_object() { return Err(type_error(vm, "TypedArray source must be array-like")); } @@ -280,7 +279,7 @@ fn collect_array_like(vm: &mut Vm, value: JsValue) -> Result, JsVal Err(type_error(vm, "TypedArray source must be Array, ArrayBuffer, or TypedArray")) } -fn typed_array_new(vm: &mut Vm, args: &[u8], kind: TypedArrayKind) -> NativeResult { +fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> NativeResult { let first = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::int(0) }; let bpe = kind.bytes_per_element(); @@ -342,7 +341,7 @@ fn typed_array_new(vm: &mut Vm, args: &[u8], kind: TypedArrayKind) -> NativeResu macro_rules! typed_array_ctor { ($name:ident, $kind:ident) => { - pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult { + pub fn $name(vm: &mut H, args: &[u8]) -> NativeResult { typed_array_new(vm, args, TypedArrayKind::$kind) } }; @@ -360,7 +359,7 @@ typed_array_ctor!(float64array_constructor, Float64); typed_array_ctor!(bigint64array_constructor, BigInt64); typed_array_ctor!(biguint64array_constructor, BigUint64); -pub fn typed_array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_at(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); let raw_index = if args.len() > 1 { @@ -377,7 +376,7 @@ pub fn typed_array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(read_element(view.kind, buffer, absolute_byte_offset(view, idx as usize))) } -pub fn typed_array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_fill(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; @@ -400,7 +399,7 @@ pub fn typed_array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(this_val) } -pub fn typed_array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_slice(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); let start = if args.len() > 1 { @@ -425,7 +424,7 @@ pub fn typed_array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, view.kind, buffer, 0, count))) } -pub fn typed_array_subarray(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_subarray(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); let start = if args.len() > 1 { @@ -449,7 +448,7 @@ pub fn typed_array_subarray(vm: &mut Vm, args: &[u8]) -> NativeResult { ))) } -pub fn typed_array_set(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let view = native_try!(get_typed_array_data(vm, this_val)); if args.len() < 2 { @@ -474,8 +473,8 @@ pub fn typed_array_set(vm: &mut Vm, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -pub fn typed_array_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { +pub fn typed_array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); native_try!(get_typed_array_data(vm, this_val)); - NativeResult::Ok(vm.intern("[object TypedArray]")) + NativeResult::Ok(vm.new_string("[object TypedArray]")) } diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index ac4cd0e..eb44518 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -10,8 +10,6 @@ pub enum Constant { Boolean(bool), Null, Undefined, - BytecodeFunc(u32), - RegExp(String, String), } pub struct CompiledModule { @@ -22,13 +20,13 @@ pub struct CompiledModule { pub param_base: u8, pub builtin_reg_map: Vec<(String, u8)>, pub sub_modules: Vec, - /// True when this module is an arrow function body (D-01). + /// True when this module is an arrow function body. /// Arrow functions capture lexical `this` from the enclosing scope. pub is_arrow: bool, /// Index into `constants` holding the captured `this` JsValue. /// 0 means "not captured - use standard this binding". pub captured_this_const_idx: u16, - /// Function name inferred from assignment context (D-04). + /// Function name inferred from assignment context. /// Set at the VariableDeclaration / ObjectProperty assignment site. pub function_name: Option, /// True when this bytecode function is a class constructor. @@ -92,10 +90,7 @@ impl fmt::Display for CompiledModule { writeln!(f, "; n_registers = {}", self.n_registers)?; writeln!(f, "; constants:")?; for (i, c) in self.constants.iter().enumerate() { - match c { - Constant::BytecodeFunc(idx) => writeln!(f, "; [{i}] = BytecodeFunc(sub_module[{idx}])")?, - other => writeln!(f, "; [{i}] = {other:?}")?, - } + writeln!(f, "; [{i}] = {c:?}")?; } writeln!(f)?; for (offset, &instr) in self.bytecode.iter().enumerate() { diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 8fb1fb2..8fbbbbf 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -1,170 +1,196 @@ use std::fmt; -/// OpCode for register-based bytecode VM. +/// Generate the `OpCode` enum plus its `TryFrom` and `Display` impls from a +/// single table. Each row is `VARIANT = 0xXX => "DISPLAY"` — the byte value is +/// the enum discriminant, the `TryFrom` match arm, and the `Display` name come +/// from the same row, so the three tables can never drift. A new opcode is one +/// row. /// -/// Organized in groups of 16 for readability. Implemented opcodes have -/// emitter support in the compiler; placeholder opcodes are reserved -/// for future phases (IC, profiling, parallelization). -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(non_camel_case_types)] -pub enum OpCode { +/// Grouping comments (`// -- Arithmetic --`) are stripped by the lexer before +/// macro expansion, so they can be interleaved freely between rows. +macro_rules! define_opcodes { + ( $( $name:ident = $val:literal => $disp:literal ),+ $(,)? ) => { + /// OpCode for register-based bytecode VM. + /// + /// Organized in groups of 16 for readability. Implemented opcodes have + /// emitter support in the compiler; placeholder opcodes are reserved + /// for future phases (IC, profiling, parallelization). + #[repr(u8)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[allow(non_camel_case_types)] + pub enum OpCode { + $( $name = $val, )+ + } + + impl TryFrom for OpCode { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + $( $val => Ok(OpCode::$name), )+ + _ => Err(()), + } + } + } + + impl fmt::Display for OpCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + $( OpCode::$name => $disp, )+ + }; + write!(f, "{name}") + } + } + }; +} + +define_opcodes! { // ── Arithmetic (0x00-0x0F) ── - ADD = 0x00, - SUB = 0x01, - MUL = 0x02, - DIV = 0x03, - MOD = 0x04, - NEG = 0x05, - COMPOUND_ADD = 0x06, - COMPOUND_SUB = 0x07, - COMPOUND_MUL = 0x08, - COMPOUND_DIV = 0x09, - COMPOUND_MOD = 0x0A, - COMPOUND_EXP = 0x0B, + ADD = 0x00 => "ADD", + SUB = 0x01 => "SUB", + MUL = 0x02 => "MUL", + DIV = 0x03 => "DIV", + MOD = 0x04 => "MOD", + NEG = 0x05 => "NEG", + COMPOUND_ADD = 0x06 => "COMPOUND_ADD", + COMPOUND_SUB = 0x07 => "COMPOUND_SUB", + COMPOUND_MUL = 0x08 => "COMPOUND_MUL", + COMPOUND_DIV = 0x09 => "COMPOUND_DIV", + COMPOUND_MOD = 0x0A => "COMPOUND_MOD", + COMPOUND_EXP = 0x0B => "COMPOUND_EXP", // -- Comparison (0x10-0x1F) -- - EQ = 0x10, - NEQ = 0x11, - LT = 0x12, - GT = 0x13, - LTE = 0x14, - GTE = 0x15, - IN = 0x16, - NOT = 0x17, - AND = 0x18, - OR = 0x19, - STRICT_EQ = 0x1A, - STRICT_NEQ = 0x1C, - UNARY_PLUS = 0x1D, + EQ = 0x10 => "EQ", + NEQ = 0x11 => "NEQ", + LT = 0x12 => "LT", + GT = 0x13 => "GT", + LTE = 0x14 => "LTE", + GTE = 0x15 => "GTE", + IN = 0x16 => "IN", + NOT = 0x17 => "NOT", + AND = 0x18 => "AND", + OR = 0x19 => "OR", + STRICT_EQ = 0x1A => "STRICT_EQ", + STRICT_NEQ = 0x1C => "STRICT_NEQ", + UNARY_PLUS = 0x1D => "UNARY_PLUS", // -- Control Flow (0x20-0x2F) -- - JMP = 0x20, - JMP_IF_FALSE = 0x21, - JMP_IF_TRUE = 0x22, - FOR_OF_INIT = 0x23, - FOR_OF_NEXT = 0x24, + JMP = 0x20 => "JMP", + JMP_IF_FALSE = 0x21 => "JMP_IF_FALSE", + JMP_IF_TRUE = 0x22 => "JMP_IF_TRUE", + FOR_OF_INIT = 0x23 => "FOR_OF_INIT", + FOR_OF_NEXT = 0x24 => "FOR_OF_NEXT", // -- Update (0x25-0x28) -- - INC_PRE = 0x25, - INC_POST = 0x26, - DEC_PRE = 0x27, - DEC_POST = 0x28, + INC_PRE = 0x25 => "INC_PRE", + INC_POST = 0x26 => "INC_POST", + DEC_PRE = 0x27 => "DEC_PRE", + DEC_POST = 0x28 => "DEC_POST", - FOR_IN_INIT = 0x29, - FOR_IN_NEXT = 0x2A, - FOR_IN_DONE = 0x2B, - SWITCH_TABLE = 0x2C, - FOR_IN_CLEANUP = 0x2D, + FOR_IN_INIT = 0x29 => "FOR_IN_INIT", + FOR_IN_NEXT = 0x2A => "FOR_IN_NEXT", + FOR_IN_DONE = 0x2B => "FOR_IN_DONE", + SWITCH_TABLE = 0x2C => "SWITCH_TABLE", + FOR_IN_CLEANUP = 0x2D => "FOR_IN_CLEANUP", // -- Exception (0x2E-0x2F, 0x33-0x35) -- - THROW = 0x2E, - TRY_BEGIN = 0x2F, - TRY_END = 0x33, - TRY_FINALLY_BEGIN = 0x34, - TRY_FINALLY_END = 0x35, - FOR_OF_DONE = 0x36, - FOR_OF_CLOSE = 0x37, + THROW = 0x2E => "THROW", + TRY_BEGIN = 0x2F => "TRY_BEGIN", + TRY_END = 0x33 => "TRY_END", + TRY_FINALLY_BEGIN = 0x34 => "TRY_FINALLY_BEGIN", + TRY_FINALLY_END = 0x35 => "TRY_FINALLY_END", + FOR_OF_DONE = 0x36 => "FOR_OF_DONE", + FOR_OF_CLOSE = 0x37 => "FOR_OF_CLOSE", // -- Template Literal (0x38) -- - TEMPLATE_STR = 0x38, + TEMPLATE_STR = 0x38 => "TEMPLATE_STR", // -- Small Language Features (0x39-0x3B) -- - DELETE_PROP_STATIC = 0x39, - DELETE_PROP_DYNAMIC = 0x3A, - INSTANCEOF = 0x3B, - REST_OBJECT = 0x3E, + DELETE_PROP_STATIC = 0x39 => "DELETE_PROP_STATIC", + DELETE_PROP_DYNAMIC = 0x3A => "DELETE_PROP_DYNAMIC", + INSTANCEOF = 0x3B => "INSTANCEOF", + REST_OBJECT = 0x3E => "REST_OBJECT", // -- Variable (0x30-0x32) -- - LOAD_VAR = 0x30, - STORE_VAR = 0x31, - LOAD_CONST = 0x32, + LOAD_VAR = 0x30 => "LOAD_VAR", + STORE_VAR = 0x31 => "STORE_VAR", + LOAD_CONST = 0x32 => "LOAD_CONST", // -- Call (0x40-0x4F) -- - CALL = 0x40, - RETURN = 0x41, - CALL_NATIVE = 0x42, - NEW_EXPRESSION = 0x43, - SUPER_CALL = 0x44, - SUPER_GET_PROP = 0x45, - SUPER_STATIC_GET_PROP = 0x46, - SET_HOME_OBJECT = 0x47, - DEFINE_ACCESSOR = 0x48, + CALL = 0x40 => "CALL", + RETURN = 0x41 => "RETURN", + CALL_NATIVE = 0x42 => "CALL_NATIVE", + NEW_EXPRESSION = 0x43 => "NEW_EXPRESSION", + SUPER_CALL = 0x44 => "SUPER_CALL", + SUPER_GET_PROP = 0x45 => "SUPER_GET_PROP", + SUPER_STATIC_GET_PROP = 0x46 => "SUPER_STATIC_GET_PROP", + SET_HOME_OBJECT = 0x47 => "SET_HOME_OBJECT", + DEFINE_ACCESSOR = 0x48 => "DEFINE_ACCESSOR", + CREATE_CLOSURE = 0x4A => "CREATE_CLOSURE", + CREATE_REGEXP = 0x4B => "CREATE_REGEXP", // ── Object Property (0x50-0x5F) ── - IC_GET_PROP = 0x50, - IC_SET_PROP = 0x51, - GET_PROP = 0x52, - GET_PROP_DYNAMIC = 0x53, - SET_PROP = 0x54, - SET_PROP_DYNAMIC = 0x55, - NEW_OBJECT = 0x56, - NEW_ARRAY = 0x57, - SET_ELEM = 0x58, + IC_GET_PROP = 0x50 => "IC_GET_PROP", + IC_SET_PROP = 0x51 => "IC_SET_PROP", + GET_PROP = 0x52 => "GET_PROP", + GET_PROP_DYNAMIC = 0x53 => "GET_PROP_DYNAMIC", + SET_PROP = 0x54 => "SET_PROP", + SET_PROP_DYNAMIC = 0x55 => "SET_PROP_DYNAMIC", + NEW_OBJECT = 0x56 => "NEW_OBJECT", + NEW_ARRAY = 0x57 => "NEW_ARRAY", + SET_ELEM = 0x58 => "SET_ELEM", // -- Member Update (0x59-0x62) -- - MEMBER_INC = 0x59, - MEMBER_DEC = 0x5A, - DYN_MEMBER_INC = 0x5B, - DYN_MEMBER_DEC = 0x5C, - COMPOUND_MEMBER_ADD = 0x5D, - COMPOUND_MEMBER_SUB = 0x5E, - COMPOUND_MEMBER_MUL = 0x5F, - COMPOUND_MEMBER_DIV = 0x60, - COMPOUND_MEMBER_MOD = 0x61, - COMPOUND_MEMBER_EXP = 0x62, + MEMBER_INC = 0x59 => "MEMBER_INC", + MEMBER_DEC = 0x5A => "MEMBER_DEC", + DYN_MEMBER_INC = 0x5B => "DYN_MEMBER_INC", + DYN_MEMBER_DEC = 0x5C => "DYN_MEMBER_DEC", + COMPOUND_MEMBER_ADD = 0x5D => "COMPOUND_MEMBER_ADD", + COMPOUND_MEMBER_SUB = 0x5E => "COMPOUND_MEMBER_SUB", + COMPOUND_MEMBER_MUL = 0x5F => "COMPOUND_MEMBER_MUL", + COMPOUND_MEMBER_DIV = 0x60 => "COMPOUND_MEMBER_DIV", + COMPOUND_MEMBER_MOD = 0x61 => "COMPOUND_MEMBER_MOD", + COMPOUND_MEMBER_EXP = 0x62 => "COMPOUND_MEMBER_EXP", // -- Profiling - placeholders (0x63-0x6F) -- - PROFILE_TYPE = 0x63, - PROFILE_SHAPE = 0x64, - PROFILE_BRANCH = 0x65, - PROFILE_CALL = 0x66, + PROFILE_TYPE = 0x63 => "PROFILE_TYPE", + PROFILE_SHAPE = 0x64 => "PROFILE_SHAPE", + PROFILE_BRANCH = 0x65 => "PROFILE_BRANCH", + PROFILE_CALL = 0x66 => "PROFILE_CALL", // ── Parallel — placeholders (0x70-0x7F) ── - FORK = 0x70, - JOIN = 0x71, - GET_PRIVATE = 0x72, - SET_PRIVATE = 0x73, - INIT_PRIVATE = 0x74, - PRIVATE_BRAND_IN = 0x75, + FORK = 0x70 => "FORK", + JOIN = 0x71 => "JOIN", + GET_PRIVATE = 0x72 => "GET_PRIVATE", + SET_PRIVATE = 0x73 => "SET_PRIVATE", + INIT_PRIVATE = 0x74 => "INIT_PRIVATE", + PRIVATE_BRAND_IN = 0x75 => "PRIVATE_BRAND_IN", // -- 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, - NULLISH = 0x8E, - JMP_IF_NULLISH = 0x8F, + BIT_AND = 0x80 => "BIT_AND", + BIT_OR = 0x81 => "BIT_OR", + BIT_XOR = 0x82 => "BIT_XOR", + SHL = 0x83 => "SHL", + SHR = 0x84 => "SHR", + USHR = 0x85 => "USHR", + BIT_NOT = 0x86 => "BIT_NOT", + COMPOUND_AND = 0x88 => "COMPOUND_AND", + COMPOUND_OR = 0x89 => "COMPOUND_OR", + COMPOUND_XOR = 0x8A => "COMPOUND_XOR", + COMPOUND_SHL = 0x8B => "COMPOUND_SHL", + COMPOUND_SHR = 0x8C => "COMPOUND_SHR", + COMPOUND_USHR = 0x8D => "COMPOUND_USHR", + NULLISH = 0x8E => "NULLISH", + JMP_IF_NULLISH = 0x8F => "JMP_IF_NULLISH", // ── Misc (0xF0-0xFF) ── - NOP = 0xF0, - HALT = 0xF1, - TYPEOF = 0xF2, - VOID = 0xF3, + NOP = 0xF0 => "NOP", + HALT = 0xF1 => "HALT", + TYPEOF = 0xF2 => "TYPEOF", + VOID = 0xF3 => "VOID", } impl OpCode { - pub fn is_implemented(&self) -> bool { - !matches!( - self, - OpCode::PROFILE_TYPE - | OpCode::PROFILE_SHAPE - | OpCode::PROFILE_BRANCH - | OpCode::PROFILE_CALL - | OpCode::FORK - | OpCode::JOIN - ) - } - pub fn has_ic_ext_words(&self) -> bool { matches!( self, @@ -182,246 +208,6 @@ impl OpCode { } } -impl TryFrom for OpCode { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(OpCode::ADD), - 0x01 => Ok(OpCode::SUB), - 0x02 => Ok(OpCode::MUL), - 0x03 => Ok(OpCode::DIV), - 0x04 => Ok(OpCode::MOD), - 0x05 => Ok(OpCode::NEG), - 0x06 => Ok(OpCode::COMPOUND_ADD), - 0x07 => Ok(OpCode::COMPOUND_SUB), - 0x08 => Ok(OpCode::COMPOUND_MUL), - 0x09 => Ok(OpCode::COMPOUND_DIV), - 0x0A => Ok(OpCode::COMPOUND_MOD), - 0x0B => Ok(OpCode::COMPOUND_EXP), - 0x10 => Ok(OpCode::EQ), - 0x11 => Ok(OpCode::NEQ), - 0x12 => Ok(OpCode::LT), - 0x13 => Ok(OpCode::GT), - 0x14 => Ok(OpCode::LTE), - 0x15 => Ok(OpCode::GTE), - 0x16 => Ok(OpCode::IN), - 0x17 => Ok(OpCode::NOT), - 0x18 => Ok(OpCode::AND), - 0x19 => Ok(OpCode::OR), - 0x1A => Ok(OpCode::STRICT_EQ), - 0x1C => Ok(OpCode::STRICT_NEQ), - 0x1D => Ok(OpCode::UNARY_PLUS), - 0x20 => Ok(OpCode::JMP), - 0x21 => Ok(OpCode::JMP_IF_FALSE), - 0x22 => Ok(OpCode::JMP_IF_TRUE), - 0x23 => Ok(OpCode::FOR_OF_INIT), - 0x24 => Ok(OpCode::FOR_OF_NEXT), - 0x25 => Ok(OpCode::INC_PRE), - 0x26 => Ok(OpCode::INC_POST), - 0x27 => Ok(OpCode::DEC_PRE), - 0x28 => Ok(OpCode::DEC_POST), - 0x29 => Ok(OpCode::FOR_IN_INIT), - 0x2A => Ok(OpCode::FOR_IN_NEXT), - 0x2B => Ok(OpCode::FOR_IN_DONE), - 0x2C => Ok(OpCode::SWITCH_TABLE), - 0x2D => Ok(OpCode::FOR_IN_CLEANUP), - 0x2E => Ok(OpCode::THROW), - 0x2F => Ok(OpCode::TRY_BEGIN), - 0x30 => Ok(OpCode::LOAD_VAR), - 0x31 => Ok(OpCode::STORE_VAR), - 0x32 => Ok(OpCode::LOAD_CONST), - 0x33 => Ok(OpCode::TRY_END), - 0x34 => Ok(OpCode::TRY_FINALLY_BEGIN), - 0x35 => Ok(OpCode::TRY_FINALLY_END), - 0x36 => Ok(OpCode::FOR_OF_DONE), - 0x37 => Ok(OpCode::FOR_OF_CLOSE), - 0x38 => Ok(OpCode::TEMPLATE_STR), - 0x39 => Ok(OpCode::DELETE_PROP_STATIC), - 0x3A => Ok(OpCode::DELETE_PROP_DYNAMIC), - 0x3B => Ok(OpCode::INSTANCEOF), - 0x3E => Ok(OpCode::REST_OBJECT), - 0x40 => Ok(OpCode::CALL), - 0x41 => Ok(OpCode::RETURN), - 0x42 => Ok(OpCode::CALL_NATIVE), - 0x43 => Ok(OpCode::NEW_EXPRESSION), - 0x44 => Ok(OpCode::SUPER_CALL), - 0x45 => Ok(OpCode::SUPER_GET_PROP), - 0x46 => Ok(OpCode::SUPER_STATIC_GET_PROP), - 0x47 => Ok(OpCode::SET_HOME_OBJECT), - 0x48 => Ok(OpCode::DEFINE_ACCESSOR), - 0x50 => Ok(OpCode::IC_GET_PROP), - 0x51 => Ok(OpCode::IC_SET_PROP), - 0x52 => Ok(OpCode::GET_PROP), - 0x53 => Ok(OpCode::GET_PROP_DYNAMIC), - 0x54 => Ok(OpCode::SET_PROP), - 0x55 => Ok(OpCode::SET_PROP_DYNAMIC), - 0x56 => Ok(OpCode::NEW_OBJECT), - 0x57 => Ok(OpCode::NEW_ARRAY), - 0x58 => Ok(OpCode::SET_ELEM), - 0x59 => Ok(OpCode::MEMBER_INC), - 0x5A => Ok(OpCode::MEMBER_DEC), - 0x5B => Ok(OpCode::DYN_MEMBER_INC), - 0x5C => Ok(OpCode::DYN_MEMBER_DEC), - 0x5D => Ok(OpCode::COMPOUND_MEMBER_ADD), - 0x5E => Ok(OpCode::COMPOUND_MEMBER_SUB), - 0x5F => Ok(OpCode::COMPOUND_MEMBER_MUL), - 0x60 => Ok(OpCode::COMPOUND_MEMBER_DIV), - 0x61 => Ok(OpCode::COMPOUND_MEMBER_MOD), - 0x62 => Ok(OpCode::COMPOUND_MEMBER_EXP), - 0x63 => Ok(OpCode::PROFILE_TYPE), - 0x64 => Ok(OpCode::PROFILE_SHAPE), - 0x65 => Ok(OpCode::PROFILE_BRANCH), - 0x66 => Ok(OpCode::PROFILE_CALL), - 0x70 => Ok(OpCode::FORK), - 0x71 => Ok(OpCode::JOIN), - 0x72 => Ok(OpCode::GET_PRIVATE), - 0x73 => Ok(OpCode::SET_PRIVATE), - 0x74 => Ok(OpCode::INIT_PRIVATE), - 0x75 => Ok(OpCode::PRIVATE_BRAND_IN), - 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), - 0x8E => Ok(OpCode::NULLISH), - 0x8F => Ok(OpCode::JMP_IF_NULLISH), - 0xF0 => Ok(OpCode::NOP), - 0xF1 => Ok(OpCode::HALT), - 0xF2 => Ok(OpCode::TYPEOF), - 0xF3 => Ok(OpCode::VOID), - _ => Err(()), - } - } -} - -impl fmt::Display for OpCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = match self { - OpCode::ADD => "ADD", - OpCode::SUB => "SUB", - OpCode::MUL => "MUL", - OpCode::DIV => "DIV", - OpCode::MOD => "MOD", - OpCode::NEG => "NEG", - OpCode::COMPOUND_ADD => "COMPOUND_ADD", - OpCode::COMPOUND_SUB => "COMPOUND_SUB", - OpCode::COMPOUND_MUL => "COMPOUND_MUL", - OpCode::COMPOUND_DIV => "COMPOUND_DIV", - OpCode::COMPOUND_MOD => "COMPOUND_MOD", - OpCode::COMPOUND_EXP => "COMPOUND_EXP", - OpCode::EQ => "EQ", - OpCode::NEQ => "NEQ", - OpCode::LT => "LT", - OpCode::GT => "GT", - OpCode::LTE => "LTE", - OpCode::GTE => "GTE", - OpCode::IN => "IN", - OpCode::NOT => "NOT", - OpCode::AND => "AND", - OpCode::OR => "OR", - OpCode::STRICT_EQ => "STRICT_EQ", - OpCode::STRICT_NEQ => "STRICT_NEQ", - OpCode::UNARY_PLUS => "UNARY_PLUS", - OpCode::JMP => "JMP", - OpCode::JMP_IF_FALSE => "JMP_IF_FALSE", - OpCode::JMP_IF_TRUE => "JMP_IF_TRUE", - OpCode::FOR_OF_INIT => "FOR_OF_INIT", - OpCode::FOR_OF_NEXT => "FOR_OF_NEXT", - OpCode::INC_PRE => "INC_PRE", - OpCode::INC_POST => "INC_POST", - OpCode::DEC_PRE => "DEC_PRE", - OpCode::DEC_POST => "DEC_POST", - OpCode::FOR_IN_INIT => "FOR_IN_INIT", - OpCode::FOR_IN_NEXT => "FOR_IN_NEXT", - OpCode::FOR_IN_DONE => "FOR_IN_DONE", - OpCode::SWITCH_TABLE => "SWITCH_TABLE", - OpCode::FOR_IN_CLEANUP => "FOR_IN_CLEANUP", - OpCode::THROW => "THROW", - OpCode::TRY_BEGIN => "TRY_BEGIN", - OpCode::TRY_END => "TRY_END", - OpCode::TRY_FINALLY_BEGIN => "TRY_FINALLY_BEGIN", - OpCode::TRY_FINALLY_END => "TRY_FINALLY_END", - OpCode::FOR_OF_DONE => "FOR_OF_DONE", - OpCode::FOR_OF_CLOSE => "FOR_OF_CLOSE", - OpCode::TEMPLATE_STR => "TEMPLATE_STR", - OpCode::DELETE_PROP_STATIC => "DELETE_PROP_STATIC", - OpCode::DELETE_PROP_DYNAMIC => "DELETE_PROP_DYNAMIC", - OpCode::INSTANCEOF => "INSTANCEOF", - OpCode::REST_OBJECT => "REST_OBJECT", - OpCode::LOAD_VAR => "LOAD_VAR", - OpCode::STORE_VAR => "STORE_VAR", - OpCode::LOAD_CONST => "LOAD_CONST", - OpCode::CALL => "CALL", - OpCode::RETURN => "RETURN", - OpCode::CALL_NATIVE => "CALL_NATIVE", - OpCode::NEW_EXPRESSION => "NEW_EXPRESSION", - OpCode::SUPER_CALL => "SUPER_CALL", - OpCode::SUPER_GET_PROP => "SUPER_GET_PROP", - OpCode::SUPER_STATIC_GET_PROP => "SUPER_STATIC_GET_PROP", - OpCode::SET_HOME_OBJECT => "SET_HOME_OBJECT", - OpCode::DEFINE_ACCESSOR => "DEFINE_ACCESSOR", - OpCode::IC_GET_PROP => "IC_GET_PROP", - OpCode::IC_SET_PROP => "IC_SET_PROP", - OpCode::GET_PROP => "GET_PROP", - OpCode::GET_PROP_DYNAMIC => "GET_PROP_DYNAMIC", - OpCode::SET_PROP => "SET_PROP", - OpCode::SET_PROP_DYNAMIC => "SET_PROP_DYNAMIC", - OpCode::NEW_OBJECT => "NEW_OBJECT", - OpCode::NEW_ARRAY => "NEW_ARRAY", - OpCode::SET_ELEM => "SET_ELEM", - OpCode::MEMBER_INC => "MEMBER_INC", - OpCode::MEMBER_DEC => "MEMBER_DEC", - OpCode::DYN_MEMBER_INC => "DYN_MEMBER_INC", - OpCode::DYN_MEMBER_DEC => "DYN_MEMBER_DEC", - OpCode::COMPOUND_MEMBER_ADD => "COMPOUND_MEMBER_ADD", - OpCode::COMPOUND_MEMBER_SUB => "COMPOUND_MEMBER_SUB", - OpCode::COMPOUND_MEMBER_MUL => "COMPOUND_MEMBER_MUL", - OpCode::COMPOUND_MEMBER_DIV => "COMPOUND_MEMBER_DIV", - OpCode::COMPOUND_MEMBER_MOD => "COMPOUND_MEMBER_MOD", - OpCode::COMPOUND_MEMBER_EXP => "COMPOUND_MEMBER_EXP", - OpCode::PROFILE_TYPE => "PROFILE_TYPE", - OpCode::PROFILE_SHAPE => "PROFILE_SHAPE", - OpCode::PROFILE_BRANCH => "PROFILE_BRANCH", - OpCode::PROFILE_CALL => "PROFILE_CALL", - OpCode::FORK => "FORK", - OpCode::JOIN => "JOIN", - OpCode::GET_PRIVATE => "GET_PRIVATE", - OpCode::SET_PRIVATE => "SET_PRIVATE", - OpCode::INIT_PRIVATE => "INIT_PRIVATE", - OpCode::PRIVATE_BRAND_IN => "PRIVATE_BRAND_IN", - 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::NULLISH => "NULLISH", - OpCode::JMP_IF_NULLISH => "JMP_IF_NULLISH", - OpCode::NOP => "NOP", - OpCode::HALT => "HALT", - OpCode::TYPEOF => "TYPEOF", - OpCode::VOID => "VOID", - }; - write!(f, "{name}") - } -} - /// 4-byte instruction. /// /// Layout: `[opcode: u8] [rd: u8] [a: u8] [b: u8]` @@ -493,3 +279,34 @@ pub fn encode_try_finally_begin(offset: i16) -> Instr { let hi = ((offset as u16 >> 8) & 0xFF) as u8; encode(OpCode::TRY_FINALLY_BEGIN, 0, lo, hi) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn opcode_table_is_byte_identical() { + for byte in 0u8..=u8::MAX { + if let Ok(op) = OpCode::try_from(byte) { + assert_eq!(op as u8, byte, "discriminant mismatch for 0x{byte:02X}"); + assert!(!op.to_string().is_empty(), "empty Display for 0x{byte:02X}"); + } + } + + assert_eq!(OpCode::ADD as u8, 0x00); + assert_eq!(OpCode::STRICT_EQ as u8, 0x1A); + assert_eq!(OpCode::JMP_IF_NULLISH as u8, 0x8F); + assert_eq!(OpCode::VOID as u8, 0xF3); + assert_eq!(OpCode::ADD.to_string(), "ADD"); + assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); + + assert!(OpCode::try_from(0x1B).is_err()); + assert!(OpCode::try_from(0xFF).is_err()); + + let ic_count = (0u8..=u8::MAX) + .filter_map(|b| OpCode::try_from(b).ok()) + .filter(OpCode::has_ic_ext_words) + .count(); + assert_eq!(ic_count, 10); + } +} diff --git a/crates/oxide_cli/Cargo.toml b/crates/oxide_cli/Cargo.toml index b87ca2f..de2be46 100644 --- a/crates/oxide_cli/Cargo.toml +++ b/crates/oxide_cli/Cargo.toml @@ -22,3 +22,5 @@ oxide_api = { path = "../oxide_api" } clap.workspace = true rustyline.workspace = true ansi_term.workspace = true +serde_json.workspace = true +serde = { workspace = true, features = ["derive"] } diff --git a/crates/oxide_cli/src/bench/baseline.rs b/crates/oxide_cli/src/bench/baseline.rs new file mode 100644 index 0000000..110e469 --- /dev/null +++ b/crates/oxide_cli/src/bench/baseline.rs @@ -0,0 +1,94 @@ +use crate::bench::metrics::MetricCollection; + +pub struct Baseline { + pub entries: Vec, +} + +impl Baseline { + pub fn empty() -> Self { + Self { entries: Vec::new() } + } +} + +pub struct Regression { + pub metric: String, + pub test_name: String, + pub baseline: f64, + pub current: f64, + pub ratio: f64, + pub tolerance: f64, +} + +pub fn load_baseline() -> Result { + let json_path = "benchmark_baseline.json"; + let data = match std::fs::read_to_string(json_path) { + Ok(d) => d, + Err(_) => { + eprintln!("No baseline found at {} — run --update-baseline first", json_path); + return Ok(Baseline::empty()); + } + }; + let entries: Vec = + serde_json::from_str(&data).map_err(|e| format!("Failed to parse baseline: {}", e))?; + Ok(Baseline { entries }) +} + +pub fn save_baseline(results: &[MetricCollection]) -> Result<(), String> { + let json = serde_json::to_string_pretty(results).map_err(|e| format!("Failed to serialize baseline: {}", e))?; + std::fs::write("benchmark_baseline.json", &json) + .map_err(|e| format!("Failed to write benchmark_baseline.json: {}", e))?; + let md = crate::bench::output::format_text_table(results); + std::fs::write("BENCHMARK_BASELINE.md", md).map_err(|e| format!("Failed to write BENCHMARK_BASELINE.md: {}", e))?; + Ok(()) +} + +pub fn compare_baseline(current: &[MetricCollection]) -> Vec { + let baseline = match load_baseline() { + Ok(b) => b, + Err(_) => return Vec::new(), + }; + if baseline.entries.is_empty() { + eprintln!("No baseline data — skipping regression check"); + return Vec::new(); + } + let mut regressions = Vec::new(); + for cur in current { + for base in &baseline.entries { + if base.test_name != cur.test_name { + continue; + } + for (name, cur_val) in cur.iter_metrics() { + if cur_val == 0.0 { + continue; + } + let base_val = base + .iter_metrics() + .iter() + .find(|(n, _)| *n == name) + .map(|(_, v)| *v) + .unwrap_or(cur_val); + if base_val == 0.0 { + continue; + } + let tolerance = match name { + "wall_time_us" => 0.10, + "gc_trigger_count" | "gc_bytes_freed" | "gc_objects_scanned" | "gc_collection_us" => 0.50, + "session_bytes" => 0.20, + _ => 0.20, + }; + let ratio = cur_val / base_val; + if ratio > 1.0 + tolerance { + regressions.push(Regression { + metric: name.to_string(), + test_name: cur.test_name.clone(), + baseline: base_val, + current: cur_val, + ratio, + tolerance, + }); + } + } + } + } + regressions +} diff --git a/crates/oxide_cli/src/bench/js_stress.rs b/crates/oxide_cli/src/bench/js_stress.rs new file mode 100644 index 0000000..0616be8 --- /dev/null +++ b/crates/oxide_cli/src/bench/js_stress.rs @@ -0,0 +1,208 @@ +use std::fs; +use std::process::ExitCode; +use std::sync::Arc; +use std::time::Instant; + +use oxide_compiler::compiler::{compiled_module_hash, Compiler}; +use oxide_kernel::kernel::KernelCore; +use oxide_parser::Allocator; +use oxide_vm::vm_pool::VmPool; + +use crate::bench::baseline::{compare_baseline, save_baseline}; +use crate::bench::metrics::MetricCollection; +use crate::bench::output::{format_json, format_text_table}; +use crate::bench::BenchConfig; + +pub fn run_js_stress_bench(config: &BenchConfig, kernel: &Arc, pool: &Arc) -> ExitCode { + let mut results: Vec = Vec::new(); + let compiler = Compiler::new(); + + let test_dir = "tests/stress"; + let mut files: Vec<_> = match fs::read_dir(test_dir) { + Ok(entries) => entries + .filter_map(|e| e.ok()) + .filter(|e| { + e.path().extension().is_some_and(|ext| ext == "js") + && config + .filter + .as_ref() + .map_or(true, |f| e.file_name().to_string_lossy().contains(f.as_str())) + }) + .map(|e| e.path()) + .collect(), + Err(e) => { + eprintln!("Could not read {}: {}", test_dir, e); + return ExitCode::FAILURE; + } + }; + files.sort(); + + if files.is_empty() { + eprintln!("No matching stress tests found in {}", test_dir); + return ExitCode::FAILURE; + } + + for path in &files { + let test_name = path.file_stem().unwrap_or_default().to_string_lossy().to_string(); + let mut js = match fs::read_to_string(path) { + Ok(s) => s, + Err(e) => { + eprintln!("Failed to read {:?}: {}", path, e); + continue; + } + }; + + if config.iterations > 0 { + js = override_iterations(&js, config.iterations); + } + + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, &js) { + Ok(p) => p, + Err(errors) => { + eprintln!("Parse error in {}: {:?}", test_name, errors.first()); + continue; + } + }; + + let hash = compiled_module_hash(&program); + let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { + Ok(m) => m, + Err(e) => { + eprintln!("Compile error in {}: {}", test_name, e); + continue; + } + }; + + for _ in 0..config.warmup { + let mut guard = pool.spawn(); + let _ = guard.vm_mut().run(&module); + } + + let mut metrics = Vec::new(); + for _ in 0..config.iterations { + let compile_start = Instant::now(); + let mut guard = pool.spawn(); + let vm = guard.vm_mut(); + let compile_time = compile_start.elapsed(); + + let pre_session = vm.session_object_count(); + let pre_epoch = vm.epoch_object_count(); + + let exec_start = Instant::now(); + let _result = vm.run(&module); + let exec_time = exec_start.elapsed(); + + let gc_stats = vm.session_gc_stats(); + + metrics.push(MetricCollection { + test_name: test_name.clone(), + wall_time_us: (compile_time + exec_time).as_micros() as u64, + session_objects: vm.session_object_count().saturating_sub(pre_session), + session_bytes: vm.session_bytes_allocated(), + epoch_objects: vm.epoch_object_count().saturating_sub(pre_epoch), + epoch_bytes: 0, + gc_trigger_count: gc_stats.total_collections, + gc_bytes_freed: gc_stats.last_collection_bytes_freed, + gc_objects_scanned: gc_stats.last_collection_objects_scanned, + gc_collection_us: gc_stats.last_collection_duration_us, + instruction_count: vm.instruction_count(), + compile_time_us: compile_time.as_micros() as u64, + exec_time_us: exec_time.as_micros() as u64, + ic_hit_rate: vm.ic_hit_rate(), + ic_hits: vm.ic_hit_count(), + ic_misses: vm.ic_miss_count(), + }); + } + + let avg = average_metrics(&metrics); + results.push(avg); + } + + let json = format_json(&results); + let table = format_text_table(&results); + eprintln!("{}", table); + println!("{}", json); + + if config.update_baseline { + if let Err(e) = save_baseline(&results) { + eprintln!("Failed to save baseline: {}", e); + return ExitCode::FAILURE; + } + eprintln!("Baseline saved to BENCHMARK_BASELINE.md + benchmark_baseline.json"); + } else { + let regressions = compare_baseline(&results); + if !regressions.is_empty() { + eprintln!("\nRegression detected:"); + for r in ®ressions { + eprintln!( + " {}: {} baseline={} current={} ratio={:.2}% tolerance={:.0}%", + r.test_name, + r.metric, + r.baseline, + r.current, + (r.ratio - 1.0) * 100.0, + r.tolerance * 100.0, + ); + } + return ExitCode::FAILURE; + } + } + + ExitCode::SUCCESS +} + +fn average_metrics(metrics: &[MetricCollection]) -> MetricCollection { + let n = metrics.len() as f64; + if n == 0.0 || metrics.is_empty() { + return metrics.first().cloned().unwrap_or(MetricCollection { + test_name: String::new(), + wall_time_us: 0, + session_objects: 0, + session_bytes: 0, + epoch_objects: 0, + epoch_bytes: 0, + gc_trigger_count: 0, + gc_bytes_freed: 0, + gc_objects_scanned: 0, + gc_collection_us: 0, + instruction_count: 0, + compile_time_us: 0, + exec_time_us: 0, + ic_hit_rate: 0.0, + ic_hits: 0, + ic_misses: 0, + }); + } + MetricCollection { + test_name: metrics[0].test_name.clone(), + wall_time_us: (metrics.iter().map(|m| m.wall_time_us as f64).sum::() / n) as u64, + session_objects: (metrics.iter().map(|m| m.session_objects as f64).sum::() / n) as usize, + session_bytes: (metrics.iter().map(|m| m.session_bytes as f64).sum::() / n) as usize, + epoch_objects: (metrics.iter().map(|m| m.epoch_objects as f64).sum::() / n) as usize, + epoch_bytes: (metrics.iter().map(|m| m.epoch_bytes as f64).sum::() / n) as u64, + gc_trigger_count: (metrics.iter().map(|m| m.gc_trigger_count as f64).sum::() / n) as u64, + gc_bytes_freed: (metrics.iter().map(|m| m.gc_bytes_freed as f64).sum::() / n) as u64, + gc_objects_scanned: (metrics.iter().map(|m| m.gc_objects_scanned as f64).sum::() / n) as u64, + gc_collection_us: (metrics.iter().map(|m| m.gc_collection_us as f64).sum::() / n) as u64, + instruction_count: (metrics.iter().map(|m| m.instruction_count as f64).sum::() / n) as u64, + compile_time_us: (metrics.iter().map(|m| m.compile_time_us as f64).sum::() / n) as u64, + exec_time_us: (metrics.iter().map(|m| m.exec_time_us as f64).sum::() / n) as u64, + ic_hit_rate: metrics.iter().map(|m| m.ic_hit_rate).sum::() / n, + ic_hits: (metrics.iter().map(|m| m.ic_hits as f64).sum::() / n) as u64, + ic_misses: (metrics.iter().map(|m| m.ic_misses as f64).sum::() / n) as u64, + } +} + +fn override_iterations(js: &str, iters: u32) -> String { + js.lines() + .map(|line| { + if line.trim_start().starts_with("var ITERATIONS ") { + format!("var ITERATIONS = {};", iters) + } else { + line.to_string() + } + }) + .collect::>() + .join("\n") +} diff --git a/crates/oxide_cli/src/bench/leak_detect.rs b/crates/oxide_cli/src/bench/leak_detect.rs new file mode 100644 index 0000000..d829ef9 --- /dev/null +++ b/crates/oxide_cli/src/bench/leak_detect.rs @@ -0,0 +1,141 @@ +use std::collections::VecDeque; +use std::process::ExitCode; +use std::sync::Arc; +use std::time::Instant; + +use oxide_compiler::compiler::{compiled_module_hash, Compiler}; +use oxide_kernel::kernel::KernelCore; +use oxide_parser::Allocator; +use oxide_vm::vm_pool::VmPool; + +use crate::bench::metrics::MetricCollection; +use crate::bench::output::format_json; +use crate::bench::BenchConfig; + +pub struct LeakSampler { + window: VecDeque<(usize, f64)>, + window_size: usize, +} + +impl LeakSampler { + pub fn new(window_size: usize) -> Self { + Self { + window: VecDeque::new(), + window_size, + } + } + + pub fn add_sample(&mut self, iteration: usize, value: f64) -> Option { + self.window.push_back((iteration, value)); + if self.window.len() >= self.window_size { + self.window.pop_front(); + } + if self.window.len() < 10 { + return None; + } + let n = self.window.len() as f64; + let sum_x: f64 = self.window.iter().map(|(i, _)| *i as f64).sum(); + let sum_y: f64 = self.window.iter().map(|(_, v)| v).sum(); + let sum_xy: f64 = self.window.iter().map(|(i, v)| *i as f64 * v).sum(); + let sum_x2: f64 = self.window.iter().map(|(i, _)| (*i as f64).powi(2)).sum(); + let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); + let intercept = (sum_y - slope * sum_x) / n; + let y_mean = sum_y / n; + let ss_res: f64 = self + .window + .iter() + .map(|(i, v)| (v - (slope * *i as f64 + intercept)).powi(2)) + .sum(); + let ss_tot: f64 = self.window.iter().map(|(_, v)| (v - y_mean).powi(2)).sum(); + let r2 = if ss_tot == 0.0 { 1.0 } else { 1.0 - ss_res / ss_tot }; + if r2 > 0.9 && slope > 0.0 { + Some(LeakVerdict { slope, r2 }) + } else { + None + } + } +} + +pub struct LeakVerdict { + pub slope: f64, + pub r2: f64, +} + +pub fn run_leak_detect(config: &BenchConfig, kernel: &Arc, pool: &Arc) -> ExitCode { + let js = + "var ITERATIONS = 1000; var obj = {}; for (var i = 0; i < ITERATIONS; i++) { obj['key' + i] = i; } obj['key0']"; + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, js) { + Ok(p) => p, + Err(_) => return ExitCode::FAILURE, + }; + let compiler = Compiler::new(); + let hash = compiled_module_hash(&program); + let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) { + Ok(m) => m, + Err(_) => return ExitCode::FAILURE, + }; + + let mut samplers: Vec<(&str, LeakSampler)> = vec![ + ("session_objects", LeakSampler::new(20)), + ("session_bytes", LeakSampler::new(20)), + ("code_forge_entries", LeakSampler::new(20)), + ("symbol_registry", LeakSampler::new(20)), + ]; + + let mut results: Vec = Vec::new(); + + for i in 0..config.iterations as usize { + let start = Instant::now(); + let mut guard = pool.spawn(); + let vm = guard.vm_mut(); + let _ = vm.run(&module); + let elapsed = start.elapsed(); + + for (name, sampler) in &mut samplers { + let value: f64 = match *name { + "session_objects" => vm.session_object_count() as f64, + "session_bytes" => vm.session_bytes_allocated() as f64, + "code_forge_entries" => kernel.code_forge().len() as f64, + "symbol_registry" => vm.symbol_registry_len() as f64, + _ => continue, + }; + if let Some(verdict) = sampler.add_sample(i, value) { + eprintln!( + "[LEAK] {}: slope={:.6} R²={:.4} over {} samples", + name, + verdict.slope, + verdict.r2, + sampler.window.len(), + ); + } + } + + if i == config.iterations as usize - 1 { + let guard = pool.spawn(); + let vm = guard.vm(); + results.push(MetricCollection { + test_name: "leak_detect".to_string(), + wall_time_us: elapsed.as_micros() as u64, + session_objects: vm.session_object_count(), + session_bytes: vm.session_bytes_allocated(), + epoch_objects: vm.epoch_object_count(), + epoch_bytes: 0, + gc_trigger_count: vm.session_gc_stats().total_collections, + gc_bytes_freed: vm.session_gc_stats().last_collection_bytes_freed, + gc_objects_scanned: vm.session_gc_stats().last_collection_objects_scanned, + gc_collection_us: vm.session_gc_stats().last_collection_duration_us, + instruction_count: vm.instruction_count(), + compile_time_us: 0, + exec_time_us: 0, + ic_hit_rate: vm.ic_hit_rate(), + ic_hits: vm.ic_hit_count(), + ic_misses: vm.ic_miss_count(), + }); + } + } + + let json = format_json(&results); + println!("{}", json); + ExitCode::SUCCESS +} diff --git a/crates/oxide_cli/src/bench/metrics.rs b/crates/oxide_cli/src/bench/metrics.rs new file mode 100644 index 0000000..65e6be4 --- /dev/null +++ b/crates/oxide_cli/src/bench/metrics.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MetricCollection { + pub test_name: String, + pub wall_time_us: u64, + pub session_objects: usize, + pub session_bytes: usize, + pub epoch_objects: usize, + pub epoch_bytes: u64, + pub gc_trigger_count: u64, + pub gc_bytes_freed: u64, + pub gc_objects_scanned: u64, + pub gc_collection_us: u64, + pub instruction_count: u64, + pub compile_time_us: u64, + pub exec_time_us: u64, + pub ic_hit_rate: f64, + pub ic_hits: u64, + pub ic_misses: u64, +} + +impl MetricCollection { + pub fn iter_metrics(&self) -> Vec<(&'static str, f64)> { + vec![ + ("wall_time_us", self.wall_time_us as f64), + ("session_objects", self.session_objects as f64), + ("session_bytes", self.session_bytes as f64), + ("epoch_objects", self.epoch_objects as f64), + ("epoch_bytes", self.epoch_bytes as f64), + ("gc_trigger_count", self.gc_trigger_count as f64), + ("gc_bytes_freed", self.gc_bytes_freed as f64), + ("gc_objects_scanned", self.gc_objects_scanned as f64), + ("gc_collection_us", self.gc_collection_us as f64), + ("instruction_count", self.instruction_count as f64), + ("compile_time_us", self.compile_time_us as f64), + ("exec_time_us", self.exec_time_us as f64), + ("ic_hit_rate", self.ic_hit_rate), + ("ic_hits", self.ic_hits as f64), + ("ic_misses", self.ic_misses as f64), + ] + } +} diff --git a/crates/oxide_cli/src/bench/mod.rs b/crates/oxide_cli/src/bench/mod.rs new file mode 100644 index 0000000..44b0f22 --- /dev/null +++ b/crates/oxide_cli/src/bench/mod.rs @@ -0,0 +1,34 @@ +pub mod baseline; +pub mod js_stress; +pub mod leak_detect; +pub mod metrics; +pub mod output; +pub mod rust_bench; + +use std::process::ExitCode; +use std::sync::Arc; + +use oxide_kernel::kernel::KernelCore; +use oxide_vm::vm_pool::VmPool; + +pub struct BenchConfig { + pub mode: String, + pub filter: Option, + pub warmup: u32, + pub iterations: u32, + pub process: bool, + pub update_baseline: bool, + pub leak_check_interval: usize, +} + +pub fn run_benchmarks(config: BenchConfig, kernel: Arc, pool: Arc) -> ExitCode { + match config.mode.as_str() { + "js" => js_stress::run_js_stress_bench(&config, &kernel, &pool), + "rust" => rust_bench::run_rust_bench(config.filter.as_deref()), + "leak" => leak_detect::run_leak_detect(&config, &kernel, &pool), + _ => { + eprintln!("Unknown bench mode: {}. Use: js, rust, or leak", config.mode); + ExitCode::FAILURE + } + } +} diff --git a/crates/oxide_cli/src/bench/output.rs b/crates/oxide_cli/src/bench/output.rs new file mode 100644 index 0000000..8a881fa --- /dev/null +++ b/crates/oxide_cli/src/bench/output.rs @@ -0,0 +1,40 @@ +use crate::bench::metrics::MetricCollection; + +pub fn format_json(results: &[MetricCollection]) -> String { + serde_json::to_string_pretty(results).unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e)) +} + +pub fn format_text_table(results: &[MetricCollection]) -> String { + let mut out = String::new(); + out.push_str(&format!( + "{:<35} {:>10} {:>10} {:>10} {:>12} {:>8} {:>8} {:>10} {:>10}\n", + "Test Name", "Wall(ms)", "Comp(ms)", "Exec(ms)", "Instrs", "GC Ct", "IC Rate", "Sess Objs", "Sess B", + )); + out.push_str(&"-".repeat(120)); + out.push('\n'); + + for m in results { + out.push_str(&format!( + "{:<35} {:>10.2} {:>10.3} {:>10.3} {:>12} {:>8} {:>7.1}% {:>10} {:>10}", + truncate(&m.test_name, 35), + m.wall_time_us as f64 / 1000.0, + m.compile_time_us as f64 / 1000.0, + m.exec_time_us as f64 / 1000.0, + m.instruction_count, + m.gc_trigger_count, + m.ic_hit_rate * 100.0, + m.session_objects, + m.session_bytes, + )); + out.push('\n'); + } + out +} + +fn truncate(s: &str, max: usize) -> &str { + if s.len() <= max { + s + } else { + &s[..max] + } +} diff --git a/crates/oxide_cli/src/bench/rust_bench.rs b/crates/oxide_cli/src/bench/rust_bench.rs new file mode 100644 index 0000000..5de7a7a --- /dev/null +++ b/crates/oxide_cli/src/bench/rust_bench.rs @@ -0,0 +1,20 @@ +pub fn run_rust_bench(filter: Option<&str>) -> std::process::ExitCode { + let mut cmd = std::process::Command::new("cargo"); + cmd.args(["bench", "-p", "oxide_vm"]); + if let Some(name) = filter { + cmd.args(["--bench", name]); + } + match cmd.status() { + Ok(status) => { + if status.success() { + std::process::ExitCode::SUCCESS + } else { + std::process::ExitCode::from(1) + } + } + Err(e) => { + eprintln!("Failed to run cargo bench: {}", e); + std::process::ExitCode::from(1) + } + } +} diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index ea818ce..af1a2dc 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -1,4 +1,5 @@ #![allow(clippy::arc_with_non_send_sync)] +#![allow(dead_code)] use std::fs; use std::process::ExitCode; @@ -9,12 +10,14 @@ use clap::{Parser, Subcommand}; use oxide_compiler::compiler::{compiled_module_hash, Compiler}; use oxide_kernel::kernel::{KernelConfig, KernelCore}; use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use oxide_kernel::string_forge::StringForge; +use oxide_kernel::string_forge::PermInterner; use oxide_parser::Allocator; use oxide_types::object::JsObject; use oxide_vm::vm_pool::VmPool; use oxide_vm::JsValue; +mod bench; + #[derive(Parser)] #[command(name = "oxide", version, about = "OxideJS - Rust JavaScript engine")] struct Cli { @@ -42,7 +45,20 @@ enum Commands { file: Option, }, Bench { - suite: Option, + #[arg(default_value = "js")] + mode: Option, + #[arg(short, long)] + filter: Option, + #[arg(long, default_value = "2")] + warmup: u32, + #[arg(long, default_value = "10")] + iterations: u32, + #[arg(long)] + process: bool, + #[arg(long)] + update_baseline: bool, + #[arg(long, default_value = "1000")] + leak_check_interval: usize, }, Test { suite: Option, @@ -64,7 +80,28 @@ fn main() -> ExitCode { run(&file, &kernel, &pool) } Some(Commands::Compile { expr, file }) => compile(expr, file), - Some(Commands::Bench { .. }) => not_implemented("bench"), + Some(Commands::Bench { + mode, + filter, + warmup, + iterations, + process, + update_baseline, + leak_check_interval, + }) => { + let kernel = make_kernel(); + let pool = make_pool(&kernel); + let config = bench::BenchConfig { + mode: mode.unwrap_or_else(|| "js".to_string()), + filter, + warmup, + iterations, + process, + update_baseline, + leak_check_interval, + }; + bench::run_benchmarks(config, kernel, pool) + } Some(Commands::Test { .. }) => not_implemented("test"), None => repl(), } @@ -103,7 +140,7 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { let mut guard = pool.spawn(); match guard.vm_mut().run(&module) { Ok(result) => { - format_result(kernel.string_forge().as_ref(), kernel.shape_forge().as_ref(), result); + format_result(kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); ExitCode::SUCCESS } Err(err) => { @@ -113,17 +150,15 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { } } -fn format_result(string_forge: &StringForge, shape_forge: &ShapeForge, val: JsValue) { +fn format_result(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) { println!("{}", format_js_value(string_forge, shape_forge, val)); } -fn format_js_value(string_forge: &StringForge, shape_forge: &ShapeForge, val: JsValue) -> String { +fn format_js_value(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) -> String { if val.is_string() { - if let Some(s) = string_forge.lookup(val.as_string_index()) { - format!("\"{s}\"") - } else { - format!("{val}") - } + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; + format!("\"{s}\"") } else if val.is_object() { let obj = unsafe { &*val.as_js_object_ptr() }; if obj.is_function() { @@ -140,7 +175,7 @@ fn format_js_value(string_forge: &StringForge, shape_forge: &ShapeForge, val: Js } } -fn format_object(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsObject) -> String { +fn format_object(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String { let mut entries = Vec::new(); let shape_id = obj.shape_id(); let mut shape_ids = Vec::new(); @@ -177,7 +212,7 @@ fn format_object(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsO format!("{{{}}}", entries.join(", ")) } -fn format_array(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsObject) -> String { +fn format_array(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String { let len = obj.prop_vec_len(); let mut items = Vec::new(); for i in 0..len { diff --git a/crates/oxide_cli/tests/cli_smoke.rs b/crates/oxide_cli/tests/cli_smoke.rs index d7451f8..4b088d9 100644 --- a/crates/oxide_cli/tests/cli_smoke.rs +++ b/crates/oxide_cli/tests/cli_smoke.rs @@ -38,12 +38,12 @@ fn run_file() { } #[test] -fn bench_not_implemented() { +fn bench_help() { let output = Command::new("cargo") - .args(["run", "--", "bench"]) + .args(["run", "--", "bench", "--help"]) .output() - .expect("failed to run oxide bench"); + .expect("failed to run oxide bench --help"); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!(stderr.contains("not yet implemented"), "bench should print not-implemented message"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("--iterations"), "bench --help should list --iterations flag"); } diff --git a/crates/oxide_code_cache/Cargo.toml b/crates/oxide_code_cache/Cargo.toml index 2e7cadf..bd29776 100644 --- a/crates/oxide_code_cache/Cargo.toml +++ b/crates/oxide_code_cache/Cargo.toml @@ -8,4 +8,5 @@ description = "OxideJS - Compiler-independent compiled module cache" [dependencies] oxide_bytecode = { path = "../oxide_bytecode" } -dashmap.workspace = true +oxide_log = { path = "../oxide_log" } +lru.workspace = true diff --git a/crates/oxide_code_cache/src/code_cache_log.rs b/crates/oxide_code_cache/src/code_cache_log.rs new file mode 100644 index 0000000..ed7c657 --- /dev/null +++ b/crates/oxide_code_cache/src/code_cache_log.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! code_cache_debug { + ($($arg:tt)*) => { + oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::DEBUG, $($arg)*) + }; +} diff --git a/crates/oxide_code_cache/src/lib.rs b/crates/oxide_code_cache/src/lib.rs index 06a4aab..28ff47c 100644 --- a/crates/oxide_code_cache/src/lib.rs +++ b/crates/oxide_code_cache/src/lib.rs @@ -1,49 +1,90 @@ #![doc = "OxideJS - Compiler-independent compiled module cache"] -use std::sync::Arc; +use std::num::NonZeroUsize; +use std::sync::{Arc, Mutex}; -use dashmap::DashMap; +use lru::LruCache; use oxide_bytecode::CompiledModule; +mod code_cache_log; + /// Shared compiled-module cache keyed by a caller-provided safe module hash. /// /// The cache intentionally does not know how to parse or compile JavaScript. /// Compiler-aware callers compute the key and provide the compile callback. +/// +/// Bounded by an LRU eviction policy: at most `capacity` modules are retained, +/// so an eval loop that compiles unbounded distinct sources cannot grow the +/// cache without limit. pub struct CodeForge { - map: DashMap>, + map: Mutex>>, } impl CodeForge { - pub fn new() -> Self { + pub fn new(capacity: NonZeroUsize) -> Self { Self { - map: DashMap::with_shard_amount(16), + map: Mutex::new(LruCache::new(capacity)), } } pub fn get(&self, hash: u64) -> Option> { - self.map.get(&hash).map(|entry| Arc::clone(&entry)) + let result = self.map.lock().unwrap().get(&hash).map(Arc::clone); + if result.is_some() { + code_cache_debug!("CodeForge hit: hash={:#x}", hash); + } + result } pub fn insert(&self, hash: u64, module: CompiledModule) -> Arc { let module = Arc::new(module); - Arc::clone(self.map.entry(hash).or_insert(module).value()) + let mut cache = self.map.lock().unwrap(); + let evicted_hash = if cache.len() == cache.cap().get() && !cache.contains(&hash) { + cache.peek_lru().map(|(k, _)| *k) + } else { + None + }; + cache.put(hash, Arc::clone(&module)); + drop(cache); + match evicted_hash { + Some(evicted_hash) => code_cache_debug!("CodeForge evict: hash={:#x}", evicted_hash), + None => code_cache_debug!("CodeForge insert: hash={:#x}", hash), + } + module } pub fn get_or_insert_with(&self, hash: u64, compile: F) -> Result, String> where - F: FnOnce() -> Result, + F: Fn() -> Result, { - if let Some(module) = self.get(hash) { - return Ok(module); + { + let mut cache = self.map.lock().unwrap(); + if let Some(module) = cache.get(&hash) { + let module = Arc::clone(module); + drop(cache); + code_cache_debug!("CodeForge hit: hash={:#x}", hash); + #[cfg(debug_assertions)] + { + let fresh = compile()?; + debug_assert_eq!( + module.bytecode, fresh.bytecode, + "structural hash collision: cached bytecode differs from recompiled for hash {hash}", + ); + } + return Ok(module); + } } - - Ok(self.insert(hash, compile()?)) + let module = Arc::new(compile()?); + self.map.lock().unwrap().put(hash, Arc::clone(&module)); + code_cache_debug!("CodeForge miss: hash={:#x}", hash); + Ok(module) } -} -impl Default for CodeForge { - fn default() -> Self { - Self::new() + pub fn len(&self) -> usize { + self.map.lock().unwrap().len() + } + + pub fn is_empty(&self) -> bool { + self.map.lock().unwrap().is_empty() } } @@ -51,9 +92,13 @@ impl Default for CodeForge { mod tests { use super::*; + fn forge(capacity: usize) -> CodeForge { + CodeForge::new(NonZeroUsize::new(capacity).unwrap()) + } + #[test] fn cache_returns_same_arc_for_same_hash() { - let forge = CodeForge::new(); + let forge = forge(16); let first = forge.insert(1, CompiledModule::new()); let second = forge.get_or_insert_with(1, || Ok(CompiledModule::new())).expect("cache hit"); @@ -62,10 +107,81 @@ mod tests { #[test] fn cache_misses_for_different_hashes() { - let forge = CodeForge::new(); + let forge = forge(16); let first = forge.insert(1, CompiledModule::new()); let second = forge.insert(2, CompiledModule::new()); assert!(!Arc::ptr_eq(&first, &second)); } + + #[test] + fn lru_eviction_enforces_cap() { + let forge = forge(2); + forge.insert(1, CompiledModule::new()); + forge.insert(2, CompiledModule::new()); + forge.insert(3, CompiledModule::new()); + + assert_eq!(forge.len(), 2); + assert!(forge.get(1).is_none()); + assert!(forge.get(2).is_some()); + assert!(forge.get(3).is_some()); + } + + #[test] + fn lru_eviction_evicts_least_recently_used() { + let forge = forge(3); + forge.insert(1, CompiledModule::new()); + forge.insert(2, CompiledModule::new()); + forge.insert(3, CompiledModule::new()); + + // Touch key 1 so key 2 becomes the least-recently-used entry. + assert!(forge.get(1).is_some()); + forge.insert(4, CompiledModule::new()); + + assert!(forge.get(1).is_some()); + assert!(forge.get(2).is_none()); + assert!(forge.get(3).is_some()); + assert!(forge.get(4).is_some()); + } + + #[test] + fn len_never_exceeds_cap_under_many_distinct_inserts() { + let forge = forge(10); + for hash in 0..100 { + forge.insert(hash, CompiledModule::new()); + } + assert_eq!(forge.len(), 10); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "structural hash collision"))] + fn cache_hit_debug_verifies_bytecode() { + let forge = forge(16); + let mut m1 = CompiledModule::new(); + m1.bytecode = vec![1, 2, 3]; + forge.insert(42, m1); + let _ = forge + .get_or_insert_with(42, || { + let mut m = CompiledModule::new(); + m.bytecode = vec![4, 5, 6]; + Ok(m) + }) + .unwrap(); + } + + #[test] + fn cache_hit_returns_cached_not_recompiled() { + let forge = forge(16); + let mut m = CompiledModule::new(); + m.bytecode = vec![1, 2, 3]; + let cached = forge.insert(99, m); + let result = forge + .get_or_insert_with(99, || { + let mut m = CompiledModule::new(); + m.bytecode = vec![1, 2, 3]; + Ok(m) + }) + .unwrap(); + assert!(Arc::ptr_eq(&cached, &result)); + } } diff --git a/crates/oxide_compiler/Cargo.toml b/crates/oxide_compiler/Cargo.toml index 681882d..d1d3897 100644 --- a/crates/oxide_compiler/Cargo.toml +++ b/crates/oxide_compiler/Cargo.toml @@ -9,6 +9,7 @@ description = "OxideJS - AST to bytecode compiler" [dependencies] oxide_bytecode = { path = "../oxide_bytecode" } oxide_parser = { path = "../oxide_parser" } +oxide_log = { path = "../oxide_log" } serde.workspace = true serde_json.workspace = true rustc-hash.workspace = true diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 936e2e1..e6fbcf3 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::emit_ctx::{LabelCtx, ScopeCtx}; use crate::symbol_table::{Binding, SymbolTable}; use oxide_bytecode::module::CompiledModule; use oxide_bytecode::opcode::{self, OpCode}; @@ -145,29 +146,18 @@ pub(crate) struct CompileCtx { next_reg: u8, pub(crate) max_regs: u8, reserved_reg_start: u8, - symbols: SymbolTable, - pub(crate) label_map: HashMap, - pub(crate) loop_stack: Vec<(Label, Label)>, - #[allow(dead_code)] - pub(crate) switch_stack: Vec