Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-06-27 01:20:10 +08:00
195 changed files with 7893 additions and 4633 deletions
+7
View File
@@ -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
Generated
+311 -3
View File
@@ -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"
+7 -2
View File
@@ -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 }
+92
View File
@@ -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
}
]
+21
View File
@@ -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
@@ -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<H: VmHost>(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<H: VmHost>(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<JsValue, JsValue> {
fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
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<JsValue, JsVal
Ok(callback_val)
}
fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject {
fn create_new_array<H: VmHost>(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<usize, JsValue> {
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
fn array_length_arg<H: VmHost>(vm: &mut H, value: JsValue) -> Result<usize, JsValue> {
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<H: VmHost>(
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<String> = (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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> {
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> = (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<H: VmHost>(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)
}
}
}
@@ -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<H: VmHost>(vm: &mut H, msg: &str) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(vm, msg))
}
fn to_index(vm: &mut Vm, value: JsValue) -> Result<usize, JsValue> {
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
fn to_index<H: VmHost>(vm: &mut H, value: JsValue) -> Result<usize, JsValue> {
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<H: VmHost>(_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<H: VmHost>(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<u8>) -> *mut JsObject {
pub(crate) fn new_array_buffer<H: VmHost>(vm: &mut H, data: Vec<u8>) -> *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<u8>> {
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut Vec<u8>)
}
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<u8>, JsValue> {
pub(crate) fn array_buffer_data_ptr<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<*mut Vec<u8>, 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<u8>;
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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]"))
}
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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"))
}
}
+34
View File
@@ -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)*)
};
}
@@ -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<usize, JsValue> {
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, JsValue> {
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<DataViewData, JsValue> {
fn get_data_view_data<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<DataViewData, JsValue> {
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<usize, JsValue> {
fn checked_absolute_offset<H: VmHost>(
vm: &mut H, view: DataViewData, offset: usize, width: usize,
) -> Result<usize, JsValue> {
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<const N: usize>(vm: &mut Vm, args: &[u8]) -> Result<[u8; N], JsValue> {
fn read_bytes<const N: usize, H: VmHost>(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<const N: usize>(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<const N: usize>(vm: &mut Vm, args: &[u8], bytes: [u8; N]) -> Result<(), JsValue> {
fn write_bytes<const N: usize, H: VmHost>(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<const N: usize>(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<H: VmHost>(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<JsValue> {
pub fn data_view_native_edges(obj: &JsObject) -> Vec<JsValue> {
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<JsValue> {
}
}
pub(crate) fn clone_data_view_native_with_rewrite<F>(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F)
pub fn clone_data_view_native_with_rewrite<F>(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<F>(obj: &mut JsObject, mut rewrite: F)
pub fn rewrite_data_view_native<F>(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::<DataViewData>() 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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]"))
}
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<NaiveDateTime> {
dt_from_ms(ms).map(|dt| dt.naive_utc())
}
pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn date_constructor<H: VmHost>(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<H: VmHost>(_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<H: VmHost>(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<H: VmHost>(_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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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))
+154
View File
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "TypeError", msg)
}
pub fn create_error<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "Error", msg)
}
pub fn create_reference_error<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "ReferenceError", msg)
}
pub fn create_range_error<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "RangeError", msg)
}
pub fn create_syntax_error<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "SyntaxError", msg)
}
pub fn create_uri_error<H: VmHost>(host: &mut H, msg: &str) -> JsValue {
create_kind_error(host, "URIError", msg)
}
fn get_msg<H: VmHost>(host: &mut H, args: &[u8]) -> String {
if args.len() > 1 {
to_string(host.reg(args[1]))
} else {
String::new()
}
}
pub fn error_constructor<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(host: &mut H, _args: &[u8]) -> NativeResult {
let names = host.call_stack_function_names();
let mut lines: Vec<String> = names.iter().rev().map(|n| format!(" at {} (native)", n)).collect();
lines.push(" at <anonymous> (native)".to_string());
let result = lines.join("\n");
NativeResult::Ok(host.new_string(&result))
}
@@ -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<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
let args: Vec<JsValue> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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::<H> 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<H: VmHost>(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))
}
@@ -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<H: VmHost>(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()
}
@@ -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<H: VmHost>(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<H: VmHost>(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)]
@@ -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<String, ()>
Ok(out)
}
fn uri_error(vm: &mut Vm) -> NativeResult {
NativeResult::Err(crate::builtins::error::create_uri_error(vm, URI_ERROR_MESSAGE))
fn uri_error<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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),
}
}
@@ -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<H: VmHost>(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<H: VmHost>(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<JsValue, JsValue> {
pub fn make_iterator_for_value<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValue> {
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::<H> 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<H: VmHost>(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<JsValue, JsValue> {
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValue> {
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<JsValue, JsValue> {
}
}
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<JsValue> {
fn next_array_like<H: VmHost>(vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32) -> Option<JsValue> {
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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)
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(
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<H: VmHost>(
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<H: VmHost>(
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 {
@@ -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;
@@ -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<SetKey, JsValue>;
/// 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<H: VmHost>(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<H: VmHost>(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<JsValue> {
pub fn map_native_edges(obj: &JsObject) -> Vec<JsValue> {
if !obj.is_map() {
return Vec::new();
}
@@ -80,12 +80,12 @@ pub(crate) fn map_native_edges(obj: &JsObject) -> Vec<JsValue> {
(*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<F>(src: &JsObject, dst: &mut JsObject, mut rewrite: F)
pub fn clone_map_native_with_rewrite<F>(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<F>(obj: &mut JsObject, mut rewrite: F)
pub fn rewrite_map_native<F>(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<MapInner> = Box::from_raw(inner);
let bytes = std::mem::size_of::<MapInner>() + 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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))
@@ -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<H: VmHost>(vm: &H, reg: u8) -> f64 {
oxide_runtime_api::to_number(vm.reg(reg))
}
fn arg1(vm: &Vm, args: &[u8]) -> f64 {
fn arg1<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
vm.step_rng();
NativeResult::Ok(JsValue::float(vm.math_rng_value()))
}
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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",
))
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> {
fn own_field<H: VmHost>(vm: &H, obj: &JsObject, prop_si: u32) -> Option<JsValue> {
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
let key_names: Vec<String> = {
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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);
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<String> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, message: &str) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(vm, message))
}
fn array_like_elements(value: JsValue) -> Option<Vec<JsValue>> {
@@ -212,14 +209,14 @@ fn array_like_elements(value: JsValue) -> Option<Vec<JsValue>> {
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<JsValue> {
fn own_field<H: VmHost>(vm: &H, obj: &JsObject, prop_si: u32) -> Option<JsValue> {
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<H: VmHost>(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<H: VmHost>(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;
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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::<regex::Regex>() as u64
}
pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn regexp_test<H: VmHost>(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<regex::Regex> 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<H: VmHost>(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<regex::Regex> 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<H: VmHost>(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))
}
@@ -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<SetKey>;
/// native builtin is executing. The `Box<IndexSet>` 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<H: VmHost>(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<IndexSet<SetKey>>`.
// 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<H: VmHost>(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<JsValue> {
pub fn set_native_edges(obj: &JsObject) -> Vec<JsValue> {
if !obj.is_set() {
return Vec::new();
}
@@ -116,10 +112,16 @@ pub(crate) fn set_native_edges(obj: &JsObject) -> Vec<JsValue> {
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<F>(src: &JsObject, dst: &mut JsObject, mut rewrite: F)
pub fn clone_set_native_with_rewrite<F>(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<F>(obj: &mut JsObject, mut rewrite: F)
pub fn rewrite_set_native<F>(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<SetInner> = Box::from_raw(inner);
let bytes = std::mem::size_of::<SetInner>() + boxed.capacity() * std::mem::size_of::<SetKey>();
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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))
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(_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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<regex::Regex> 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<regex::Regex> 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))
}
+31
View File
@@ -0,0 +1,31 @@
use oxide_runtime_api::{NativeResult, VmHost};
fn stub_error<H: VmHost>(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<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "Proxy")
}
pub fn bigint_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "BigInt")
}
pub fn weakmap_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "WeakMap")
}
pub fn weakset_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "WeakSet")
}
pub fn weakref_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "WeakRef")
}
pub fn finalization_registry_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "FinalizationRegistry")
}
pub fn shared_array_buffer_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "SharedArrayBuffer")
}
pub fn atomics_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "Atomics")
}
+74
View File
@@ -0,0 +1,74 @@
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
pub fn symbol_constructor<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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()),
}
}
@@ -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<H: VmHost>(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<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
crate::error::create_range_error(vm, msg)
}
fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result<usize, JsValue> {
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, JsValue> {
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<usize, JsValue> {
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<H: VmHost>(_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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(
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<JsValue> {
pub fn typed_array_native_edges(obj: &JsObject) -> Vec<JsValue> {
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<JsValue> {
}
}
pub(crate) fn clone_typed_array_native_with_rewrite<F>(old_obj: &JsObject, new_obj: &mut JsObject, mut rewrite: F)
pub fn clone_typed_array_native_with_rewrite<F>(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<F>(obj: &mut JsObject, mut rewrite: F)
pub fn rewrite_typed_array_native<F>(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::<TypedArrayData>() as u64
}
pub(crate) fn get_typed_array_data(vm: &mut Vm, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
pub(crate) fn get_typed_array_data<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
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<H: VmHost>(_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<Vec<JsValue>, JsValue> {
fn collect_array_like<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Vec<JsValue>, 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<Vec<JsValue>, 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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]"))
}
+3 -8
View File
@@ -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<CompiledModule>,
/// 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<String>,
/// 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() {
+188 -371
View File
@@ -1,170 +1,196 @@
use std::fmt;
/// OpCode for register-based bytecode VM.
/// Generate the `OpCode` enum plus its `TryFrom<u8>` 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<u8> for OpCode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
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<u8> for OpCode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
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);
}
}
+2
View File
@@ -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"] }
+94
View File
@@ -0,0 +1,94 @@
use crate::bench::metrics::MetricCollection;
pub struct Baseline {
pub entries: Vec<MetricCollection>,
}
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<Baseline, String> {
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<MetricCollection> =
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<Regression> {
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
}
+208
View File
@@ -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<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
let mut results: Vec<MetricCollection> = 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 &regressions {
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::<f64>() / n) as u64,
session_objects: (metrics.iter().map(|m| m.session_objects as f64).sum::<f64>() / n) as usize,
session_bytes: (metrics.iter().map(|m| m.session_bytes as f64).sum::<f64>() / n) as usize,
epoch_objects: (metrics.iter().map(|m| m.epoch_objects as f64).sum::<f64>() / n) as usize,
epoch_bytes: (metrics.iter().map(|m| m.epoch_bytes as f64).sum::<f64>() / n) as u64,
gc_trigger_count: (metrics.iter().map(|m| m.gc_trigger_count as f64).sum::<f64>() / n) as u64,
gc_bytes_freed: (metrics.iter().map(|m| m.gc_bytes_freed as f64).sum::<f64>() / n) as u64,
gc_objects_scanned: (metrics.iter().map(|m| m.gc_objects_scanned as f64).sum::<f64>() / n) as u64,
gc_collection_us: (metrics.iter().map(|m| m.gc_collection_us as f64).sum::<f64>() / n) as u64,
instruction_count: (metrics.iter().map(|m| m.instruction_count as f64).sum::<f64>() / n) as u64,
compile_time_us: (metrics.iter().map(|m| m.compile_time_us as f64).sum::<f64>() / n) as u64,
exec_time_us: (metrics.iter().map(|m| m.exec_time_us as f64).sum::<f64>() / n) as u64,
ic_hit_rate: metrics.iter().map(|m| m.ic_hit_rate).sum::<f64>() / n,
ic_hits: (metrics.iter().map(|m| m.ic_hits as f64).sum::<f64>() / n) as u64,
ic_misses: (metrics.iter().map(|m| m.ic_misses as f64).sum::<f64>() / 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::<Vec<_>>()
.join("\n")
}
+141
View File
@@ -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<LeakVerdict> {
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<KernelCore>, pool: &Arc<VmPool>) -> 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<MetricCollection> = 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
}
+43
View File
@@ -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),
]
}
}
+34
View File
@@ -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<String>,
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<KernelCore>, pool: Arc<VmPool>) -> 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
}
}
}
+40
View File
@@ -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]
}
}
+20
View File
@@ -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)
}
}
}
+48 -13
View File
@@ -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<String>,
},
Bench {
suite: Option<String>,
#[arg(default_value = "js")]
mode: Option<String>,
#[arg(short, long)]
filter: Option<String>,
#[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<String>,
@@ -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<KernelCore>, pool: &Arc<VmPool>) -> 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<KernelCore>, pool: &Arc<VmPool>) -> 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 {
+5 -5
View File
@@ -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");
}
+2 -1
View File
@@ -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
@@ -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)*)
};
}
+134 -18
View File
@@ -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<u64, Arc<CompiledModule>>,
map: Mutex<LruCache<u64, Arc<CompiledModule>>>,
}
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<Arc<CompiledModule>> {
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<CompiledModule> {
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<F>(&self, hash: u64, compile: F) -> Result<Arc<CompiledModule>, String>
where
F: FnOnce() -> Result<CompiledModule, String>,
F: Fn() -> Result<CompiledModule, String>,
{
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));
}
}
+1
View File
@@ -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
+76 -71
View File
@@ -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<Label, usize>,
pub(crate) loop_stack: Vec<(Label, Label)>,
#[allow(dead_code)]
pub(crate) switch_stack: Vec<Label>,
/// Active labeled-statement scopes (resolves `break label` / `continue label`).
pub(crate) label_scopes: Vec<LabelScope>,
/// Label names awaiting binding to the next emitted loop's continue target.
pub(crate) pending_loop_labels: Vec<String>,
pub(crate) label_counter: u32,
pub(crate) labels: LabelCtx,
pub(crate) scopes: ScopeCtx,
pub(crate) projected_pc: usize,
pub(crate) builtin_reg_map: Vec<(String, u8)>,
pub(crate) sub_modules: Vec<CompiledModule>,
/// Register holding `this` in the enclosing function context.
/// Used by arrow functions to capture lexical `this` (D-01).
/// Used by arrow functions to capture lexical `this`.
/// Initialized to 254 (conventional this register) at the top level.
pub(crate) enclosing_this_reg: u8,
pub(crate) in_derived_constructor: bool,
pub(crate) in_instance_method: bool,
pub(crate) in_static_method: bool,
pub(crate) static_block_this_reg: Option<u8>,
pub(crate) private_name_map: Vec<(String, u32)>,
pub(crate) next_private_name_id: u32,
pub(crate) after_super_insert: Option<Vec<opcode::Instr>>,
pub(crate) after_super_inserted: bool,
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
@@ -209,7 +199,6 @@ enum ConstantKey {
Boolean(bool),
Null,
Undefined,
BytecodeFunc(u32),
}
impl CompileCtx {
@@ -221,23 +210,27 @@ impl CompileCtx {
next_reg: 1,
max_regs: 1,
reserved_reg_start: 1,
symbols: SymbolTable::new(),
label_map: HashMap::new(),
loop_stack: Vec::new(),
switch_stack: Vec::new(),
label_scopes: Vec::new(),
pending_loop_labels: Vec::new(),
label_counter: 0,
labels: LabelCtx {
label_map: HashMap::new(),
loop_stack: Vec::new(),
switch_stack: Vec::new(),
label_scopes: Vec::new(),
pending_loop_labels: Vec::new(),
label_counter: 0,
},
scopes: ScopeCtx {
symbols: SymbolTable::new(),
builtin_reg_map: Vec::new(),
private_name_map: Vec::new(),
next_private_name_id: 1,
},
projected_pc: 0,
builtin_reg_map: Vec::new(),
sub_modules: Vec::new(),
enclosing_this_reg: 254, // conventional this register at top level
in_derived_constructor: false,
in_instance_method: false,
in_static_method: false,
static_block_this_reg: None,
private_name_map: Vec::new(),
next_private_name_id: 1,
after_super_insert: None,
after_super_inserted: false,
reg_overflow: false,
@@ -254,6 +247,11 @@ impl CompileCtx {
self.emit(opcode::encode(OpCode::LOAD_CONST, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8));
}
pub(crate) fn emit_create_closure(&mut self, reg: u8, sub_idx: u32) {
let idx = sub_idx as u16;
self.emit(opcode::encode(OpCode::CREATE_CLOSURE, reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8));
}
pub(crate) fn count_word(&mut self) {
self.projected_pc += 1;
}
@@ -335,7 +333,7 @@ impl CompileCtx {
pub(crate) fn reset_regs(&mut self) {
self.next_reg = self.builtin_reg_floor().max(self.reserved_reg_start);
self.projected_pc = 0;
self.label_counter = 0;
self.labels.label_counter = 0;
}
pub(crate) fn reg_checkpoint(&self) -> u8 {
@@ -391,48 +389,49 @@ impl CompileCtx {
}
pub(crate) fn resolve_label(&self, label: Label) -> Result<usize, String> {
self.label_map
self.labels
.label_map
.get(&label)
.copied()
.ok_or_else(|| format!("Label {:?} not found in bytecode map", label))
}
pub(crate) fn push_scope(&mut self) {
self.symbols.push_scope();
self.scopes.symbols.push_scope();
}
pub(crate) fn pop_scope(&mut self) {
self.symbols.pop_scope();
self.scopes.symbols.pop_scope();
}
pub(crate) fn declare(
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
) -> Result<(), String> {
self.symbols.declare(name, reg, kind, is_const)
self.scopes.symbols.declare(name, reg, kind, is_const)
}
pub(crate) fn declare_initialized(
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
) -> Result<(), String> {
self.symbols.declare_initialized(name, reg, kind, is_const)
self.scopes.symbols.declare_initialized(name, reg, kind, is_const)
}
#[allow(dead_code)]
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
self.symbols.push_scope_with_kind(kind);
self.scopes.symbols.push_scope_with_kind(kind);
}
pub(crate) fn lookup(&self, name: &str) -> Result<u8, String> {
self.symbols.lookup(name)
self.scopes.symbols.lookup(name)
}
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u8, String> {
match self.symbols.lookup(name) {
match self.scopes.symbols.lookup(name) {
Ok(reg) => Ok(reg),
Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => {
let reg = self.alloc_reg();
self.symbols.pre_register_global(name, reg);
self.builtin_reg_map.push((name.to_string(), reg));
self.scopes.symbols.pre_register_global(name, reg);
self.scopes.builtin_reg_map.push((name.to_string(), reg));
Ok(reg)
}
Err(err) => Err(err),
@@ -440,58 +439,58 @@ impl CompileCtx {
}
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u8 {
if let Some(reg) = self.symbols.lookup_any(name) {
if let Some(reg) = self.scopes.symbols.lookup_any(name) {
return reg;
}
let reg = self.alloc_reg();
self.symbols.lookup_or_global(name, reg)
self.scopes.symbols.lookup_or_global(name, reg)
}
pub(crate) fn lookup_const_flag(&self, name: &str) -> bool {
self.symbols.lookup_is_const(name)
self.scopes.symbols.lookup_is_const(name)
}
pub(crate) fn init_var(&mut self, name: &str) {
self.symbols.init_var(name);
self.scopes.symbols.init_var(name);
}
pub(crate) fn next_label_id(&mut self) -> u32 {
let id = self.label_counter;
self.label_counter += 1;
let id = self.labels.label_counter;
self.labels.label_counter += 1;
id
}
pub(crate) fn push_loop(&mut self, break_label: Label, continue_label: Label) {
self.loop_stack.push((break_label, continue_label));
self.labels.loop_stack.push((break_label, continue_label));
}
pub(crate) fn pop_loop(&mut self) {
self.loop_stack.pop();
self.labels.loop_stack.pop();
}
pub(crate) fn current_loop(&self) -> Option<&(Label, Label)> {
self.loop_stack.last()
self.labels.loop_stack.last()
}
pub(crate) fn push_switch(&mut self, break_label: Label) {
self.switch_stack.push(break_label);
self.labels.switch_stack.push(break_label);
}
pub(crate) fn pop_switch(&mut self) {
self.switch_stack.pop();
self.labels.switch_stack.pop();
}
pub(crate) fn current_switch(&self) -> Option<&Label> {
self.switch_stack.last()
self.labels.switch_stack.last()
}
pub(crate) fn push_label_scope(
&mut self, name: &str, break_label: Label, continue_label: Option<Label>,
) -> Result<(), String> {
if self.label_scopes.iter().any(|s| s.name == name) {
if self.labels.label_scopes.iter().any(|s| s.name == name) {
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
}
self.label_scopes.push(LabelScope {
self.labels.label_scopes.push(LabelScope {
name: name.to_string(),
break_label,
continue_label,
@@ -500,31 +499,33 @@ impl CompileCtx {
}
pub(crate) fn pop_label_scope(&mut self) {
self.label_scopes.pop();
self.labels.label_scopes.pop();
}
pub(crate) fn find_label(&self, name: &str) -> Option<&LabelScope> {
self.label_scopes.iter().rev().find(|s| s.name == name)
self.labels.label_scopes.iter().rev().find(|s| s.name == name)
}
/// Queue a label name to be bound to the continue target of the next loop
/// emitted as the labeled statement's body. Rejects duplicates in the active
/// or pending sets.
pub(crate) fn queue_loop_label(&mut self, name: &str) -> Result<(), String> {
if self.label_scopes.iter().any(|s| s.name == name) || self.pending_loop_labels.iter().any(|n| n == name) {
if self.labels.label_scopes.iter().any(|s| s.name == name)
|| self.labels.pending_loop_labels.iter().any(|n| n == name)
{
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
}
self.pending_loop_labels.push(name.to_string());
self.labels.pending_loop_labels.push(name.to_string());
Ok(())
}
/// Drain queued loop labels into active scopes bound to this loop's break and
/// continue targets. Returns how many scopes were pushed (to pop after).
pub(crate) fn take_pending_loop_labels(&mut self, break_label: Label, continue_label: Label) -> usize {
let names = std::mem::take(&mut self.pending_loop_labels);
let names = std::mem::take(&mut self.labels.pending_loop_labels);
let count = names.len();
for name in names {
self.label_scopes.push(LabelScope {
self.labels.label_scopes.push(LabelScope {
name,
break_label,
continue_label: Some(continue_label),
@@ -535,12 +536,12 @@ impl CompileCtx {
pub(crate) fn pop_label_scopes(&mut self, n: usize) {
for _ in 0..n {
self.label_scopes.pop();
self.labels.label_scopes.pop();
}
}
pub(crate) fn is_builtin(&self, name: &str) -> bool {
self.builtin_reg_map.iter().any(|(n, _)| n == name)
self.scopes.builtin_reg_map.iter().any(|(n, _)| n == name)
}
pub(crate) fn is_known_builtin(name: &str) -> bool {
@@ -548,7 +549,8 @@ impl CompileCtx {
}
fn builtin_reg_floor(&self) -> u8 {
self.builtin_reg_map
self.scopes
.builtin_reg_map
.iter()
.map(|(_, reg)| reg.saturating_add(1))
.max()
@@ -571,8 +573,6 @@ impl ConstantKey {
Constant::Boolean(v) => Some(Self::Boolean(*v)),
Constant::Null => Some(Self::Null),
Constant::Undefined => Some(Self::Undefined),
Constant::BytecodeFunc(v) => Some(Self::BytecodeFunc(*v)),
Constant::RegExp(_, _) => None,
}
}
}
@@ -646,7 +646,7 @@ impl Compiler {
)
}
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub(crate) fn compile_function_body_with_field_hooks<'a, C, E>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
@@ -660,9 +660,9 @@ impl Compiler {
// Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.)
// resolve to the correct pre-allocated registers in the sub-module's register file.
ctx.builtin_reg_map = parent_ctx.builtin_reg_map.clone();
ctx.private_name_map = parent_ctx.private_name_map.clone();
ctx.next_private_name_id = parent_ctx.next_private_name_id;
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone();
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
@@ -681,8 +681,8 @@ impl Compiler {
// Inherit parent's global scope entries so previously-declared function names
// are visible from within the body.
let mut inherited_reg_start = 1u8.max(ctx.builtin_reg_floor());
for (name, binding) in &parent_ctx.symbols.scopes[0].bindings {
ctx.symbols.scopes[0].bindings.insert(
for (name, binding) in &parent_ctx.scopes.symbols.scopes[0].bindings {
ctx.scopes.symbols.scopes[0].bindings.insert(
name.clone(),
Binding {
reg: binding.reg,
@@ -693,7 +693,7 @@ impl Compiler {
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
}
for (name, reg) in extra_bindings {
ctx.symbols.scopes[0].bindings.insert(
ctx.scopes.symbols.scopes[0].bindings.insert(
(*name).to_string(),
Binding {
reg: *reg,
@@ -818,7 +818,7 @@ impl Compiler {
n_registers: ctx.max_regs,
n_args: param_specs.len() as u8,
param_base,
builtin_reg_map: ctx.builtin_reg_map,
builtin_reg_map: ctx.scopes.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
@@ -830,12 +830,14 @@ impl Compiler {
}
pub fn compile(&self, program: &oxide_parser::Program) -> Result<CompiledModule, String> {
crate::compiler_debug!("compile: starting...");
let mut ctx = CompileCtx::new();
ctx.pre_register_builtins();
for stmt in &program.body {
self.count_statement(stmt, &mut ctx);
}
crate::compiler_debug!("counter: {} instructions estimated", ctx.projected_pc);
ctx.max_regs = ctx.max_regs.max(1);
ctx.reg_overflow = false;
ctx.reset_regs();
@@ -859,6 +861,7 @@ impl Compiler {
None => last_result = None,
}
}
crate::compiler_debug!("emitter: {} bytes emitted", ctx.bytecode.len());
if let Some(r) = last_result {
ctx.emit(opcode::encode(OpCode::LOAD_VAR, 0, r, 0));
@@ -883,13 +886,15 @@ impl Compiler {
return Err("RangeError: jump offset out of range".into());
}
crate::compiler_debug!("compile: done, {} instructions, {} constants", ctx.bytecode.len(), ctx.constants.len());
Ok(CompiledModule {
bytecode: ctx.bytecode,
constants: ctx.constants,
n_registers: ctx.max_regs,
n_args: 0,
param_base: ctx.builtin_reg_map.len() as u8,
builtin_reg_map: ctx.builtin_reg_map,
param_base: ctx.scopes.builtin_reg_map.len() as u8,
builtin_reg_map: ctx.scopes.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
+34
View File
@@ -0,0 +1,34 @@
#[macro_export]
macro_rules! compiler_error {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::ERROR, $($arg)*)
};
}
#[macro_export]
macro_rules! compiler_warn {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::WARN, $($arg)*)
};
}
#[macro_export]
macro_rules! compiler_info {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::INFO, $($arg)*)
};
}
#[macro_export]
macro_rules! compiler_debug {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::DEBUG, $($arg)*)
};
}
#[macro_export]
macro_rules! compiler_trace {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::TRACE, $($arg)*)
};
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_assignment_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_assignment_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::AssignmentExpression(assign) = expr else {
return;
};
@@ -72,7 +72,7 @@ impl Compiler {
self.count_expression(right, ctx);
ctx.count_ic_set_with_ext(); // IC_SET_PROP + 3 ext
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
}
fn count_computed_member_logical_assignment(
@@ -88,7 +88,7 @@ impl Compiler {
self.count_expression(right, ctx);
ctx.projected_pc += 1; // SET_PROP_DYNAMIC
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
}
fn count_identifier_logical_assignment(
@@ -101,6 +101,10 @@ impl Compiler {
self.count_expression(right, ctx);
ctx.projected_pc += 1; // STORE_VAR
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
}
pub(in crate::counter) fn count_assignment(&self, expr: &Expression, ctx: &mut CompileCtx) {
self.count_assignment_expression(expr, ctx);
}
}
+10 -2
View File
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_call_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_call_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::CallExpression(call) = expr else {
return;
};
@@ -50,7 +50,7 @@ impl Compiler {
ctx.count_load_var(); // result <- regs[0]
}
pub(in crate::counter) fn count_new_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_new_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::NewExpression(ne) = expr else {
return;
};
@@ -63,4 +63,12 @@ impl Compiler {
ctx.alloc_reg(); // result register
ctx.count_instr_with_ext(1); // NEW_EXPRESSION + arg_count ext word
}
pub(in crate::counter) fn count_call_domain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::CallExpression(_) => self.count_call_expression(expr, ctx),
Expression::NewExpression(_) => self.count_new_expression(expr, ctx),
_ => {}
}
}
}
@@ -10,8 +10,8 @@ impl Compiler {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_VAR result <- chain value
ctx.projected_pc += 1; // JMP over short-circuit writer
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
ctx.projected_pc += 1; // LOAD_CONST undefined
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_conditional_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_conditional_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::ConditionalExpression(cond) = expr else {
return;
};
@@ -15,13 +15,13 @@ impl Compiler {
ctx.alloc_reg(); // result register
ctx.projected_pc += 1; // LOAD_VAR result <- consequent
ctx.projected_pc += 1; // JMP to end
ctx.label_map.insert(else_label, ctx.projected_pc);
ctx.labels.label_map.insert(else_label, ctx.projected_pc);
self.count_expression(&cond.alternate, ctx);
ctx.projected_pc += 1; // LOAD_VAR result <- alternate
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_sequence_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_sequence_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::SequenceExpression(seq) = expr else {
return;
};
@@ -30,7 +30,7 @@ impl Compiler {
}
}
pub(in crate::counter) fn count_logical_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_logical_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::LogicalExpression(log) = expr else {
return;
};
@@ -49,7 +49,7 @@ impl Compiler {
ctx.projected_pc += 1; // JMP_IF_FALSE, JMP_IF_TRUE, or JMP_IF_NULLISH
if matches!(log.operator, LogicalOperator::Coalesce) {
ctx.projected_pc += 1; // JMP over RHS on non-nullish
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
}
self.count_expression(&log.right, ctx);
ctx.projected_pc += 1; // LOAD_VAR (overwrite)
@@ -58,7 +58,16 @@ impl Compiler {
LogicalOperator::Or => Label::TernaryElse(id),
LogicalOperator::Coalesce => Label::TernaryEnd(id),
};
ctx.label_map.insert(skip_label, ctx.projected_pc);
ctx.labels.label_map.insert(skip_label, ctx.projected_pc);
}
}
pub(in crate::counter) fn count_conditional_chain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::ConditionalExpression(_) => self.count_conditional_expression(expr, ctx),
Expression::SequenceExpression(_) => self.count_sequence_expression(expr, ctx),
Expression::LogicalExpression(_) => self.count_logical_expression(expr, ctx),
_ => {}
}
}
}
@@ -1,20 +1,29 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_arrow_function_expression(&self, ctx: &mut CompileCtx) {
fn count_arrow_function_expression(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST
}
pub(in crate::counter) fn count_function_expression(&self, ctx: &mut CompileCtx) {
fn count_function_expression(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST
}
pub(in crate::counter) fn count_class_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_class_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::ClassExpression(class) = expr else {
return;
};
self.count_class(class, ctx);
}
pub(in crate::counter) fn count_function_domain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::ArrowFunctionExpression(_) => self.count_arrow_function_expression(ctx),
Expression::FunctionExpression(_) => self.count_function_expression(ctx),
Expression::ClassExpression(_) => self.count_class_expression(expr, ctx),
_ => {}
}
}
}
@@ -1,6 +1,17 @@
use super::*;
impl Compiler {
/// Domain entry point: count any literal expression. The central
/// `count_expression` match delegates all literal variants here in one arm.
/// Numeric/String/Boolean/Null share the default single-`LOAD_CONST` shape;
/// RegExp emits three instructions.
pub(in crate::counter) fn count_literal(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::RegExpLiteral(_) => self.count_regexp_literal(ctx),
_ => self.count_default_expression(ctx),
}
}
pub(in crate::counter) fn count_this_expression(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_VAR from reg 254
@@ -17,8 +28,12 @@ impl Compiler {
ctx.projected_pc += 1; // LOAD_VAR
}
pub(in crate::counter) fn count_regexp_literal(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg();
fn count_regexp_literal(&self, ctx: &mut CompileCtx) {
ctx.alloc_reg(); // pattern reg (LOAD_CONST)
ctx.projected_pc += 1;
ctx.alloc_reg(); // flags reg (LOAD_CONST)
ctx.projected_pc += 1;
ctx.alloc_reg(); // result reg (CREATE_REGEXP)
ctx.projected_pc += 1;
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_static_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_static_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::StaticMemberExpression(member) = expr else {
return;
};
@@ -17,7 +17,7 @@ impl Compiler {
ctx.count_ic_instr_with_ext(); // IC_GET_PROP + 3 ext words
}
pub(in crate::counter) fn count_computed_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_computed_member_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::ComputedMemberExpression(member) = expr else {
return;
};
@@ -27,7 +27,7 @@ impl Compiler {
ctx.projected_pc += 1; // GET_PROP_DYNAMIC
}
pub(in crate::counter) fn count_private_field_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_private_field_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::PrivateFieldExpression(member) = expr else {
return;
};
@@ -41,4 +41,13 @@ impl Compiler {
};
self.count_expression(&p.expression, ctx);
}
pub(in crate::counter) fn count_member_domain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::StaticMemberExpression(_) => self.count_static_member_expression(expr, ctx),
Expression::ComputedMemberExpression(_) => self.count_computed_member_expression(expr, ctx),
Expression::PrivateFieldExpression(_) => self.count_private_field_expression(expr, ctx),
_ => {}
}
}
}
+24 -21
View File
@@ -14,31 +14,34 @@ mod template;
impl Compiler {
pub(crate) fn count_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::BinaryExpression(_) => self.count_binary_expression(expr, ctx),
Expression::PrivateInExpression(_) => self.count_private_in_expression(expr, ctx),
Expression::UnaryExpression(_) => self.count_unary_expression(expr, ctx),
Expression::CallExpression(_) => self.count_call_expression(expr, ctx),
Expression::AssignmentExpression(_) => self.count_assignment_expression(expr, ctx),
Expression::ConditionalExpression(_) => self.count_conditional_expression(expr, ctx),
Expression::SequenceExpression(_) => self.count_sequence_expression(expr, ctx),
Expression::LogicalExpression(_) => self.count_logical_expression(expr, ctx),
Expression::BinaryExpression(_)
| Expression::PrivateInExpression(_)
| Expression::UnaryExpression(_)
| Expression::UpdateExpression(_) => self.count_operator(expr, ctx),
Expression::CallExpression(_) | Expression::NewExpression(_) => self.count_call_domain(expr, ctx),
Expression::AssignmentExpression(_) => self.count_assignment(expr, ctx),
Expression::ConditionalExpression(_)
| Expression::SequenceExpression(_)
| Expression::LogicalExpression(_) => self.count_conditional_chain(expr, ctx),
Expression::ChainExpression(_) => self.count_chain_expression(expr, ctx),
Expression::ObjectExpression(_) => self.count_object_expression(expr, ctx),
Expression::TemplateLiteral(_) => self.count_template_literal(expr, ctx),
Expression::TaggedTemplateExpression(_) => self.count_tagged_template_expression(expr, ctx),
Expression::ArrowFunctionExpression(_) => self.count_arrow_function_expression(ctx),
Expression::FunctionExpression(_) => self.count_function_expression(ctx),
Expression::ClassExpression(_) => self.count_class_expression(expr, ctx),
Expression::NewExpression(_) => self.count_new_expression(expr, ctx),
Expression::ArrayExpression(_) => self.count_array_expression(expr, ctx),
Expression::StaticMemberExpression(_) => self.count_static_member_expression(expr, ctx),
Expression::ComputedMemberExpression(_) => self.count_computed_member_expression(expr, ctx),
Expression::PrivateFieldExpression(_) => self.count_private_field_expression(expr, ctx),
Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => self.count_object_domain(expr, ctx),
Expression::TemplateLiteral(_) | Expression::TaggedTemplateExpression(_) => {
self.count_template_domain(expr, ctx)
}
Expression::ArrowFunctionExpression(_)
| Expression::FunctionExpression(_)
| Expression::ClassExpression(_) => self.count_function_domain(expr, ctx),
Expression::StaticMemberExpression(_)
| Expression::ComputedMemberExpression(_)
| Expression::PrivateFieldExpression(_) => self.count_member_domain(expr, ctx),
Expression::ParenthesizedExpression(_) => self.count_parenthesized_expression(expr, ctx),
Expression::ThisExpression(_) => self.count_this_expression(ctx),
Expression::Identifier(_) => self.count_identifier_expression(expr, ctx),
Expression::UpdateExpression(_) => self.count_update_expression(expr, ctx),
Expression::RegExpLiteral(_) => self.count_regexp_literal(ctx),
Expression::NumericLiteral(_)
| Expression::StringLiteral(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::RegExpLiteral(_) => self.count_literal(expr, ctx),
_ => self.count_default_expression(ctx),
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_object_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_object_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::ObjectExpression(obj) = expr else {
return;
};
@@ -25,7 +25,7 @@ impl Compiler {
}
}
pub(in crate::counter) fn count_array_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_array_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::ArrayExpression(arr) = expr else {
return;
};
@@ -42,4 +42,12 @@ impl Compiler {
}
}
}
pub(in crate::counter) fn count_object_domain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::ObjectExpression(_) => self.count_object_expression(expr, ctx),
Expression::ArrayExpression(_) => self.count_array_expression(expr, ctx),
_ => {}
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_binary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_binary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::BinaryExpression(bin) = expr else {
return;
};
@@ -14,7 +14,7 @@ impl Compiler {
}
}
pub(in crate::counter) fn count_private_in_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_private_in_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::PrivateInExpression(pin) = expr else {
return;
};
@@ -22,7 +22,7 @@ impl Compiler {
ctx.count_private_access();
}
pub(in crate::counter) fn count_unary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_unary_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::UnaryExpression(un) = expr else {
return;
};
@@ -79,7 +79,7 @@ impl Compiler {
ctx.projected_pc += 1; // LOAD_CONST true
}
pub(in crate::counter) fn count_update_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_update_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::UpdateExpression(update) = expr else {
return;
};
@@ -106,4 +106,14 @@ impl Compiler {
}
}
}
pub(in crate::counter) fn count_operator(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::BinaryExpression(_) => self.count_binary_expression(expr, ctx),
Expression::PrivateInExpression(_) => self.count_private_in_expression(expr, ctx),
Expression::UnaryExpression(_) => self.count_unary_expression(expr, ctx),
Expression::UpdateExpression(_) => self.count_update_expression(expr, ctx),
_ => {}
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_template_literal(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_template_literal(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::TemplateLiteral(tl) = expr else {
return;
};
@@ -12,7 +12,7 @@ impl Compiler {
ctx.count_template_str(segment_count);
}
pub(in crate::counter) fn count_tagged_template_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
fn count_tagged_template_expression(&self, expr: &Expression, ctx: &mut CompileCtx) {
let Expression::TaggedTemplateExpression(tt) = expr else {
return;
};
@@ -50,4 +50,12 @@ impl Compiler {
ctx.count_call_instr_with_arg_ext();
ctx.count_load_var(); // result <- regs[0]
}
pub(in crate::counter) fn count_template_domain(&self, expr: &Expression, ctx: &mut CompileCtx) {
match expr {
Expression::TemplateLiteral(_) => self.count_template_literal(expr, ctx),
Expression::TaggedTemplateExpression(_) => self.count_tagged_template_expression(expr, ctx),
_ => {}
}
}
}
@@ -166,7 +166,7 @@ impl Compiler {
LogicalOperator::Coalesce => {
ctx.projected_pc += 1; // JMP_IF_NULLISH to store body
ctx.projected_pc += 1; // JMP to end on non-nullish
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
}
}
}
+17 -15
View File
@@ -1,32 +1,26 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_expression_statement(
&self, stmt: &oxide_parser::ExpressionStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_expression_statement(&self, stmt: &oxide_parser::ExpressionStatement<'_>, ctx: &mut CompileCtx) {
self.count_expression(&stmt.expression, ctx);
}
pub(in crate::counter) fn count_return_statement(
&self, stmt: &oxide_parser::ReturnStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_return_statement(&self, stmt: &oxide_parser::ReturnStatement<'_>, ctx: &mut CompileCtx) {
if let Some(arg) = &stmt.argument {
self.count_expression(arg, ctx);
}
ctx.projected_pc += 1; // RETURN
}
pub(in crate::counter) fn count_break_statement(&self, ctx: &mut CompileCtx) {
fn count_break_statement(&self, ctx: &mut CompileCtx) {
ctx.projected_pc += 1; // JMP
}
pub(in crate::counter) fn count_continue_statement(&self, ctx: &mut CompileCtx) {
fn count_continue_statement(&self, ctx: &mut CompileCtx) {
ctx.projected_pc += 1; // JMP
}
pub(in crate::counter) fn count_labeled_statement(
&self, stmt: &oxide_parser::LabeledStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_labeled_statement(&self, stmt: &oxide_parser::LabeledStatement<'_>, ctx: &mut CompileCtx) {
let body_is_loop = matches!(
stmt.body,
Statement::WhileStatement(_)
@@ -36,14 +30,22 @@ impl Compiler {
| Statement::ForOfStatement(_)
);
if body_is_loop {
// Loop body owns its labels; labeled break/continue reuse them.
self.count_statement(&stmt.body, ctx);
} else {
// Non-loop label: consume one id (parity with emit) and place the
// LabeledEnd target right after the body for `break label`.
let id = ctx.next_label_id();
self.count_statement(&stmt.body, ctx);
ctx.label_map.insert(Label::LabeledEnd(id), ctx.projected_pc);
ctx.labels.label_map.insert(Label::LabeledEnd(id), ctx.projected_pc);
}
}
pub(in crate::counter) fn count_basic(&self, stmt: &Statement, ctx: &mut CompileCtx) {
match stmt {
Statement::ExpressionStatement(es) => self.count_expression_statement(es, ctx),
Statement::ReturnStatement(ret) => self.count_return_statement(ret, ctx),
Statement::BreakStatement(_) => self.count_break_statement(ctx),
Statement::ContinueStatement(_) => self.count_continue_statement(ctx),
Statement::LabeledStatement(ls) => self.count_labeled_statement(ls, ctx),
_ => {}
}
}
}
@@ -1,11 +1,15 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_block_statement(
&self, block: &oxide_parser::BlockStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_block_statement(&self, block: &oxide_parser::BlockStatement<'_>, ctx: &mut CompileCtx) {
for s in &block.body {
self.count_statement(s, ctx);
}
}
pub(in crate::counter) fn count_block_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
if let Statement::BlockStatement(block) = stmt {
self.count_block_statement(block, ctx);
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_if_statement(&self, stmt: &oxide_parser::IfStatement<'_>, ctx: &mut CompileCtx) {
fn count_if_statement(&self, stmt: &oxide_parser::IfStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let else_label = Label::IfElse(id);
let end_label = Label::IfEnd(id);
@@ -14,11 +14,17 @@ impl Compiler {
if stmt.alternate.is_some() {
ctx.projected_pc += 1; // JMP (skip else)
}
ctx.label_map.insert(else_label, ctx.projected_pc);
ctx.labels.label_map.insert(else_label, ctx.projected_pc);
if let Some(alt_stmt) = &stmt.alternate {
self.count_statement(alt_stmt, ctx);
ctx.projected_pc += 1; // LOAD_VAR result <- alternate
}
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_control_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
if let Statement::IfStatement(ifs) = stmt {
self.count_if_statement(ifs, ctx);
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_variable_declaration(
&self, decl: &oxide_parser::VariableDeclaration<'_>, ctx: &mut CompileCtx,
) {
fn count_variable_declaration(&self, decl: &oxide_parser::VariableDeclaration<'_>, ctx: &mut CompileCtx) {
for d in &decl.declarations {
if let Some(init) = &d.init {
self.count_expression(init, ctx);
@@ -15,9 +13,7 @@ impl Compiler {
}
}
pub(in crate::counter) fn count_function_declaration(
&self, decl: &oxide_parser::Function<'_>, ctx: &mut CompileCtx,
) {
fn count_function_declaration(&self, decl: &oxide_parser::Function<'_>, ctx: &mut CompileCtx) {
let name = if let Some(id) = &decl.id {
id.name.to_string()
} else {
@@ -31,9 +27,18 @@ impl Compiler {
ctx.count_words(2); // LOAD_CONST(BytecodeFunc) + STORE_VAR
}
pub(in crate::counter) fn count_class_declaration(&self, class: &oxide_parser::Class<'_>, ctx: &mut CompileCtx) {
fn count_class_declaration(&self, class: &oxide_parser::Class<'_>, ctx: &mut CompileCtx) {
ctx.alloc_reg(); // class binding reg
self.count_class(class, ctx);
ctx.projected_pc += 1; // STORE_VAR binding <- ctor
}
pub(in crate::counter) fn count_declaration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
match stmt {
Statement::VariableDeclaration(decl) => self.count_variable_declaration(decl, ctx),
Statement::FunctionDeclaration(fd) => self.count_function_declaration(fd, ctx),
Statement::ClassDeclaration(class) => self.count_class_declaration(class, ctx),
_ => {}
}
}
}
@@ -1,14 +1,12 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_throw_statement(
&self, stmt: &oxide_parser::ThrowStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_throw_statement(&self, stmt: &oxide_parser::ThrowStatement<'_>, ctx: &mut CompileCtx) {
self.count_expression(&stmt.argument, ctx);
ctx.projected_pc += 1; // THROW
}
pub(in crate::counter) fn count_try_statement(&self, stmt: &oxide_parser::TryStatement<'_>, ctx: &mut CompileCtx) {
fn count_try_statement(&self, stmt: &oxide_parser::TryStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let catch_label = Label::CatchBody(id);
let try_end_label = Label::TryEnd(id);
@@ -39,7 +37,7 @@ impl Compiler {
ctx.projected_pc += 1; // JMP
}
ctx.label_map.insert(catch_label, ctx.projected_pc);
ctx.labels.label_map.insert(catch_label, ctx.projected_pc);
if let Some(catch) = &stmt.handler {
ctx.push_scope();
if let Some(_param) = &catch.param {
@@ -55,7 +53,7 @@ impl Compiler {
if let Some(finally) = &stmt.finalizer {
let finally_label = Label::FinallyBody(id);
ctx.label_map.insert(finally_label, ctx.projected_pc);
ctx.labels.label_map.insert(finally_label, ctx.projected_pc);
for fs in &finally.body {
self.count_statement(fs, ctx);
}
@@ -63,6 +61,14 @@ impl Compiler {
ctx.projected_pc += 1; // TRY_FINALLY_END
}
ctx.label_map.insert(try_end_label, ctx.projected_pc);
ctx.labels.label_map.insert(try_end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
match stmt {
Statement::ThrowStatement(ts) => self.count_throw_statement(ts, ctx),
Statement::TryStatement(ts) => self.count_try_statement(ts, ctx),
_ => {}
}
}
}
@@ -1,36 +1,32 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_while_statement(
&self, stmt: &oxide_parser::WhileStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_while_statement(&self, stmt: &oxide_parser::WhileStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let start_label = Label::WhileStart(id);
let end_label = Label::WhileEnd(id);
ctx.label_map.insert(start_label, ctx.projected_pc);
ctx.labels.label_map.insert(start_label, ctx.projected_pc);
self.count_expression(&stmt.test, ctx);
ctx.projected_pc += 1; // JMP_IF_FALSE
self.count_statement(&stmt.body, ctx);
ctx.projected_pc += 1; // JMP (backward)
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_do_while_statement(
&self, stmt: &oxide_parser::DoWhileStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_do_while_statement(&self, stmt: &oxide_parser::DoWhileStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let start_label = Label::DoWhileStart(id);
let end_label = Label::DoWhileEnd(id);
ctx.label_map.insert(start_label, ctx.projected_pc);
ctx.labels.label_map.insert(start_label, ctx.projected_pc);
self.count_statement(&stmt.body, ctx);
self.count_expression(&stmt.test, ctx);
ctx.projected_pc += 1; // JMP_IF_TRUE (backward)
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_for_statement(&self, stmt: &oxide_parser::ForStatement<'_>, ctx: &mut CompileCtx) {
fn count_for_statement(&self, stmt: &oxide_parser::ForStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let start_label = Label::ForStart(id);
let update_label = Label::ForUpdate(id);
@@ -51,23 +47,21 @@ impl Compiler {
}
}
}
ctx.label_map.insert(start_label, ctx.projected_pc);
ctx.labels.label_map.insert(start_label, ctx.projected_pc);
if let Some(test) = &stmt.test {
self.count_expression(test, ctx);
ctx.projected_pc += 1; // JMP_IF_FALSE
}
self.count_statement(&stmt.body, ctx);
ctx.label_map.insert(update_label, ctx.projected_pc);
ctx.labels.label_map.insert(update_label, ctx.projected_pc);
if let Some(update) = &stmt.update {
self.count_expression(update, ctx);
}
ctx.projected_pc += 1; // JMP (backward)
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
}
pub(in crate::counter) fn count_for_in_statement(
&self, stmt: &oxide_parser::ForInStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_for_in_statement(&self, stmt: &oxide_parser::ForInStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let start_label = Label::ForInStart(id);
let end_label = Label::ForInEnd(id);
@@ -75,7 +69,7 @@ impl Compiler {
self.count_expression(&stmt.right, ctx);
ctx.count_instr(); // FOR_IN_INIT
ctx.label_map.insert(start_label, ctx.projected_pc);
ctx.labels.label_map.insert(start_label, ctx.projected_pc);
ctx.count_instr(); // FOR_IN_DONE
ctx.count_jump(); // JMP_IF_FALSE
ctx.count_jump(); // JMP cleanup
@@ -98,20 +92,18 @@ impl Compiler {
self.count_statement(&stmt.body, ctx);
ctx.count_jump(); // JMP back to start
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
ctx.count_instr(); // FOR_IN_CLEANUP
}
pub(in crate::counter) fn count_for_of_statement(
&self, stmt: &oxide_parser::ForOfStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_for_of_statement(&self, stmt: &oxide_parser::ForOfStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let start_label = Label::ForOfStart(id);
let end_label = Label::ForOfEnd(id);
self.count_expression(&stmt.right, ctx);
ctx.count_instr(); // FOR_OF_INIT
ctx.label_map.insert(start_label, ctx.projected_pc);
ctx.labels.label_map.insert(start_label, ctx.projected_pc);
ctx.alloc_reg(); // has_reg
ctx.count_instr(); // FOR_OF_DONE
ctx.count_jump(); // JMP_IF_FALSE
@@ -137,7 +129,18 @@ impl Compiler {
}
self.count_statement(&stmt.body, ctx);
ctx.count_jump(); // JMP back
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
ctx.count_instr(); // FOR_OF_CLOSE
}
pub(in crate::counter) fn count_iteration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
match stmt {
Statement::WhileStatement(wh) => self.count_while_statement(wh, ctx),
Statement::DoWhileStatement(dw) => self.count_do_while_statement(dw, ctx),
Statement::ForStatement(fr) => self.count_for_statement(fr, ctx),
Statement::ForInStatement(fi) => self.count_for_in_statement(fi, ctx),
Statement::ForOfStatement(fo) => self.count_for_of_statement(fo, ctx),
_ => {}
}
}
}
+17 -18
View File
@@ -11,24 +11,23 @@ mod switch;
impl Compiler {
pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) {
match stmt {
Statement::ExpressionStatement(es) => self.count_expression_statement(es, ctx),
Statement::VariableDeclaration(decl) => self.count_variable_declaration(decl, ctx),
Statement::ReturnStatement(ret) => self.count_return_statement(ret, ctx),
Statement::IfStatement(ifs) => self.count_if_statement(ifs, ctx),
Statement::WhileStatement(wh) => self.count_while_statement(wh, ctx),
Statement::DoWhileStatement(dw) => self.count_do_while_statement(dw, ctx),
Statement::ForStatement(fr) => self.count_for_statement(fr, ctx),
Statement::ForInStatement(fi) => self.count_for_in_statement(fi, ctx),
Statement::ForOfStatement(fo) => self.count_for_of_statement(fo, ctx),
Statement::SwitchStatement(sw) => self.count_switch_statement(sw, ctx),
Statement::BreakStatement(_) => self.count_break_statement(ctx),
Statement::ContinueStatement(_) => self.count_continue_statement(ctx),
Statement::BlockStatement(block) => self.count_block_statement(block, ctx),
Statement::FunctionDeclaration(fd) => self.count_function_declaration(fd, ctx),
Statement::ClassDeclaration(class) => self.count_class_declaration(class, ctx),
Statement::ThrowStatement(ts) => self.count_throw_statement(ts, ctx),
Statement::TryStatement(ts) => self.count_try_statement(ts, ctx),
Statement::LabeledStatement(ls) => self.count_labeled_statement(ls, ctx),
Statement::ExpressionStatement(_)
| Statement::ReturnStatement(_)
| Statement::BreakStatement(_)
| Statement::ContinueStatement(_)
| Statement::LabeledStatement(_) => self.count_basic(stmt, ctx),
Statement::BlockStatement(_) => self.count_block_domain(stmt, ctx),
Statement::VariableDeclaration(_) | Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {
self.count_declaration_domain(stmt, ctx)
}
Statement::IfStatement(_) => self.count_control_domain(stmt, ctx),
Statement::WhileStatement(_)
| Statement::DoWhileStatement(_)
| Statement::ForStatement(_)
| Statement::ForInStatement(_)
| Statement::ForOfStatement(_) => self.count_iteration_domain(stmt, ctx),
Statement::SwitchStatement(_) => self.count_switch_domain(stmt, ctx),
Statement::ThrowStatement(_) | Statement::TryStatement(_) => self.count_exception_domain(stmt, ctx),
_ => {}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::counter) fn count_switch_statement(
&self, stmt: &oxide_parser::SwitchStatement<'_>, ctx: &mut CompileCtx,
) {
fn count_switch_statement(&self, stmt: &oxide_parser::SwitchStatement<'_>, ctx: &mut CompileCtx) {
let id = ctx.next_label_id();
let end_label = Label::SwitchEnd(id);
ctx.push_switch(end_label);
@@ -29,13 +27,19 @@ impl Compiler {
for (case_idx, case) in cases.iter().enumerate() {
let case_label = Label::SwitchCase(id, case_idx as u32);
ctx.label_map.insert(case_label, ctx.projected_pc);
ctx.labels.label_map.insert(case_label, ctx.projected_pc);
for s in &case.consequent {
self.count_statement(s, ctx);
}
}
ctx.label_map.insert(end_label, ctx.projected_pc);
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
ctx.pop_switch();
}
pub(in crate::counter) fn count_switch_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
if let Statement::SwitchStatement(sw) = stmt {
self.count_switch_statement(sw, ctx);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
//! Grouped `CompileCtx` sub-contexts.
//!
//! Splits the compiler's central `CompileCtx` so syntax-domain workers borrow
//! only the slice they need: `LabelCtx` for jump-target resolution, `ScopeCtx`
//! for identifier binding. `PatternCtx` is reserved for destructuring work.
//! Execution-stream fields (bytecode, registers, pc, …) stay flat on
//! `CompileCtx`.
use std::collections::HashMap;
use crate::compiler::{Label, LabelScope};
use crate::symbol_table::SymbolTable;
/// Jump-target / labeled-statement resolution state.
pub(crate) struct LabelCtx {
pub(crate) label_map: HashMap<Label, usize>,
pub(crate) loop_stack: Vec<(Label, Label)>,
#[allow(dead_code)]
pub(crate) switch_stack: Vec<Label>,
/// Active labeled-statement scopes (resolves `break label` / `continue label`).
pub(crate) label_scopes: Vec<LabelScope>,
/// Label names awaiting binding to the next emitted loop's continue target.
pub(crate) pending_loop_labels: Vec<String>,
pub(crate) label_counter: u32,
}
/// Identifier-binding state: symbols, builtin registers, private names.
pub(crate) struct ScopeCtx {
pub(crate) symbols: SymbolTable,
pub(crate) builtin_reg_map: Vec<(String, u8)>,
pub(crate) private_name_map: Vec<(String, u32)>,
pub(crate) next_private_name_id: u32,
}
/// Reserved for destructuring-pattern state (Phase 15). Intentionally empty
/// today; named so future pattern work has a home without touching `ScopeCtx`.
#[allow(dead_code)]
pub(crate) struct PatternCtx;
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_call_expression(
&self, call: &oxide_parser::CallExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_call_expression(&self, call: &oxide_parser::CallExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
if matches!(&call.callee, Expression::Super(_)) {
if !ctx.in_derived_constructor {
return Err("super() only supported in derived constructors".into());
@@ -94,4 +92,11 @@ impl Compiler {
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0));
Ok(result_reg)
}
pub(in crate::emitter) fn emit_call_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
@@ -1,10 +1,10 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_arrow_function_expression(
fn emit_arrow_function_expression(
&self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
// Rest params not yet supported (D-06 placeholder)
// Rest params not yet supported
if let Some(_rest) = &arrow.params.rest {
return Err("rest params in arrow functions not yet supported".into());
}
@@ -37,15 +37,12 @@ impl Compiler {
// 1-indexed: 0 = no sub_module (sentinel)
let sub_idx = ctx.sub_modules.len() as u32;
let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx));
let r = ctx.alloc_reg();
ctx.emit_load_const(r, const_idx);
ctx.emit_create_closure(r, sub_idx);
Ok(r)
}
pub(in crate::emitter) fn emit_function_expression(
&self, fe: &oxide_parser::Function, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_function_expression(&self, fe: &oxide_parser::Function, ctx: &mut CompileCtx) -> Result<u8, String> {
// FunctionExpression: compile body, emit LOAD_CONST(BytecodeFunc)
let mut param_names = Vec::new();
for (idx, param) in fe.params.items.iter().enumerate() {
@@ -72,19 +69,16 @@ impl Compiler {
// 1-indexed: 0 = no sub_module (sentinel)
let sub_idx = ctx.sub_modules.len() as u32;
let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx));
let r = ctx.alloc_reg();
ctx.emit_load_const(r, const_idx);
ctx.emit_create_closure(r, sub_idx);
Ok(r)
}
pub(in crate::emitter) fn emit_class_expression(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
fn emit_class_expression(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
self.emit_class(class, ctx)
}
pub(in crate::emitter) fn emit_new_expression(
&self, ne: &oxide_parser::NewExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_new_expression(&self, ne: &oxide_parser::NewExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
let constructor_reg = self.emit_expression(&ne.callee, ctx)?;
let mut arg_regs = Vec::new();
for arg in &ne.arguments {
@@ -98,4 +92,16 @@ impl Compiler {
ctx.emit(arg_regs.len() as u32);
Ok(r)
}
pub(in crate::emitter) fn emit_function_domain(
&self, expr: &Expression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
match expr {
Expression::ArrowFunctionExpression(arrow) => self.emit_arrow_function_expression(arrow, ctx),
Expression::FunctionExpression(fe) => self.emit_function_expression(fe, ctx),
Expression::ClassExpression(class) => self.emit_class_expression(class, ctx),
Expression::NewExpression(ne) => self.emit_new_expression(ne, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
@@ -1,7 +1,21 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_numeric_literal_expression(
/// Domain entry point: emit any literal expression. The central
/// `emit_expression` match delegates all literal variants here in one arm;
/// adding a new literal kind only touches this file plus that one arm.
pub(in crate::emitter) fn emit_literal(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::NumericLiteral(n) => self.emit_numeric_literal_expression(n, ctx),
Expression::StringLiteral(s) => self.emit_string_literal_expression(s, ctx),
Expression::BooleanLiteral(b) => self.emit_boolean_literal_expression(b, ctx),
Expression::NullLiteral(_) => self.emit_null_literal_expression(ctx),
Expression::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
fn emit_numeric_literal_expression(
&self, n: &oxide_parser::NumericLiteral, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let idx = if is_int_literal(n.value) {
@@ -14,7 +28,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_string_literal_expression(
fn emit_string_literal_expression(
&self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let idx = ctx.add_constant(Constant::String(s.value.to_string()));
@@ -23,7 +37,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_boolean_literal_expression(
fn emit_boolean_literal_expression(
&self, b: &oxide_parser::BooleanLiteral, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let idx = ctx.add_constant(Constant::Boolean(b.value));
@@ -32,7 +46,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_null_literal_expression(&self, ctx: &mut CompileCtx) -> Result<u8, String> {
fn emit_null_literal_expression(&self, ctx: &mut CompileCtx) -> Result<u8, String> {
let idx = ctx.add_constant(Constant::Null);
let r = ctx.alloc_reg();
ctx.emit_load_const(r, idx);
@@ -61,18 +75,23 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_reg_exp_literal_expression(
fn emit_reg_exp_literal_expression(
&self, lit: &oxide_parser::RegExpLiteral, ctx: &mut CompileCtx,
) -> Result<u8, String> {
if let Some(raw) = &lit.raw {
let raw_str = raw.to_string();
if raw_str.len() >= 2 && raw_str.starts_with('/') {
let last_slash = raw_str.rfind('/').unwrap_or(raw_str.len() - 1);
let pattern = &raw_str[1..last_slash];
let flags = &raw_str[last_slash + 1..];
let ci = ctx.add_constant(Constant::RegExp(pattern.to_string(), flags.to_string()));
let pattern = raw_str[1..last_slash].to_string();
let flags = raw_str[last_slash + 1..].to_string();
let pat_ci = ctx.add_constant(Constant::String(pattern));
let pat_reg = ctx.alloc_reg();
ctx.emit_load_const(pat_reg, pat_ci);
let flags_ci = ctx.add_constant(Constant::String(flags));
let flags_reg = ctx.alloc_reg();
ctx.emit_load_const(flags_reg, flags_ci);
let r = ctx.alloc_reg();
ctx.emit_load_const(r, ci);
ctx.emit(opcode::encode(OpCode::CREATE_REGEXP, r, pat_reg, flags_reg));
Ok(r)
} else {
Err(format!("unsupported regexp literal: {:?}", lit))
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_static_member_expression(
fn emit_static_member_expression(
&self, member: &oxide_parser::StaticMemberExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
if matches!(&member.object, Expression::Super(_)) {
@@ -35,7 +35,7 @@ impl Compiler {
Ok(obj_reg)
}
pub(in crate::emitter) fn emit_computed_member_expression(
fn emit_computed_member_expression(
&self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let obj_reg = self.emit_expression(&member.object, ctx)?;
@@ -45,7 +45,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_private_field_expression(
fn emit_private_field_expression(
&self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let obj_reg = self.emit_expression(&member.object, ctx)?;
@@ -55,9 +55,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_chain_expression(
&self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_chain_expression(&self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
let id = ctx.next_label_id();
let short_label = Label::TernaryElse(id);
let end_label = Label::TernaryEnd(id);
@@ -72,4 +70,14 @@ impl Compiler {
ctx.emit_load_const(result_reg, undefined_idx);
Ok(result_reg)
}
pub(in crate::emitter) fn emit_member_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::StaticMemberExpression(member) => self.emit_static_member_expression(member, ctx),
Expression::ComputedMemberExpression(member) => self.emit_computed_member_expression(member, ctx),
Expression::PrivateFieldExpression(member) => self.emit_private_field_expression(member, ctx),
Expression::ChainExpression(chain) => self.emit_chain_expression(chain, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_object_expression(
&self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_object_expression(&self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
let obj_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0));
let prop_checkpoint = ctx.reg_checkpoint();
@@ -38,7 +36,7 @@ impl Compiler {
let key_reg = ctx.alloc_reg();
ctx.emit_load_const(key_reg, idx);
let val_reg = self.emit_expression(&p.value, ctx)?;
// Name inference (D-04): if property value is an arrow function,
// Name inference: if property value is an arrow function,
// set the compiled sub_module's function_name to the property key.
if matches!(&p.value, Expression::ArrowFunctionExpression(_)) {
if let Some(sub_mod) = ctx.sub_modules.last_mut() {
@@ -53,9 +51,7 @@ impl Compiler {
Ok(obj_reg)
}
pub(in crate::emitter) fn emit_array_expression(
&self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_array_expression(&self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
let arr_reg = ctx.alloc_reg();
let n = arr.elements.len() as u16;
ctx.emit(opcode::encode(OpCode::NEW_ARRAY, arr_reg, (n & 0xFF) as u8, ((n >> 8) & 0xFF) as u8));
@@ -73,4 +69,12 @@ impl Compiler {
}
Ok(arr_reg)
}
pub(in crate::emitter) fn emit_object_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::ObjectExpression(obj) => self.emit_object_expression(obj, ctx),
Expression::ArrayExpression(arr) => self.emit_array_expression(arr, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_private_in_expression(
fn emit_private_in_expression(
&self, pin: &oxide_parser::PrivateInExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let obj_reg = self.emit_expression(&pin.right, ctx)?;
@@ -11,9 +11,7 @@ impl Compiler {
Ok(result_reg)
}
pub(in crate::emitter) fn emit_binary_expression(
&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_binary_expression(&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
let checkpoint = ctx.reg_checkpoint();
let left = self.emit_expression(&bin.left, ctx)?;
let right = self.emit_expression(&bin.right, ctx)?;
@@ -48,9 +46,7 @@ impl Compiler {
Ok(left)
}
pub(in crate::emitter) fn emit_unary_expression(
&self, un: &oxide_parser::UnaryExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
fn emit_unary_expression(&self, un: &oxide_parser::UnaryExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
if matches!(un.operator, UnaryOperator::Delete) {
return match &un.argument {
Expression::Identifier(_) => {
@@ -154,7 +150,7 @@ impl Compiler {
}
}
pub(in crate::emitter) fn emit_conditional_expression(
fn emit_conditional_expression(
&self, cond: &oxide_parser::ConditionalExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let id = ctx.next_label_id();
@@ -183,7 +179,7 @@ impl Compiler {
Ok(result_reg)
}
pub(in crate::emitter) fn emit_logical_expression(
fn emit_logical_expression(
&self, log: &oxide_parser::LogicalExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
use oxide_parser::LogicalOperator;
@@ -251,7 +247,7 @@ impl Compiler {
Ok(dup_reg)
}
pub(in crate::emitter) fn emit_update_expression(
fn emit_update_expression(
&self, update: &oxide_parser::UpdateExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
match &update.argument {
@@ -304,4 +300,16 @@ impl Compiler {
_ => Err("member update not yet supported".into()),
}
}
pub(in crate::emitter) fn emit_operator(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::BinaryExpression(bin) => self.emit_binary_expression(bin, ctx),
Expression::PrivateInExpression(pin) => self.emit_private_in_expression(pin, ctx),
Expression::UnaryExpression(un) => self.emit_unary_expression(un, ctx),
Expression::ConditionalExpression(cond) => self.emit_conditional_expression(cond, ctx),
Expression::LogicalExpression(log) => self.emit_logical_expression(log, ctx),
Expression::UpdateExpression(update) => self.emit_update_expression(update, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_template_literal_expression(
fn emit_template_literal_expression(
&self, tl: &oxide_parser::TemplateLiteral, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let r = ctx.alloc_reg();
@@ -52,7 +52,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_tagged_template_expression(
fn emit_tagged_template_expression(
&self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
let quasis = &tt.quasi.quasis;
@@ -125,4 +125,14 @@ impl Compiler {
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0));
Ok(result_reg)
}
pub(in crate::emitter) fn emit_template_domain(
&self, expr: &Expression, ctx: &mut CompileCtx,
) -> Result<u8, String> {
match expr {
Expression::TemplateLiteral(tl) => self.emit_template_literal_expression(tl, ctx),
Expression::TaggedTemplateExpression(tt) => self.emit_tagged_template_expression(tt, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
}
+52 -54
View File
@@ -217,7 +217,8 @@ impl Compiler {
}
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
ctx.private_name_map
ctx.scopes
.private_name_map
.iter()
.find_map(|(n, id)| (n == name).then_some(*id))
.ok_or_else(|| format!("private name #{name} is not defined"))
@@ -640,8 +641,8 @@ impl Compiler {
if private_names.iter().any(|(existing, _)| existing == &name) {
return Err(format!("duplicate private name #{name}"));
}
let id = ctx.next_private_name_id;
ctx.next_private_name_id = ctx.next_private_name_id.saturating_add(1);
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
private_names.push((name, id));
}
if method.kind == MethodDefinitionKind::Constructor {
@@ -658,8 +659,8 @@ impl Compiler {
if private_names.iter().any(|(existing, _)| existing == &name) {
return Err(format!("duplicate private name #{name}"));
}
let id = ctx.next_private_name_id;
ctx.next_private_name_id = ctx.next_private_name_id.saturating_add(1);
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
private_names.push((name, id));
}
if !prop.r#static {
@@ -682,9 +683,9 @@ impl Compiler {
};
let saved_derived = ctx.in_derived_constructor;
let saved_private_names = ctx.private_name_map.clone();
let saved_private_names = ctx.scopes.private_name_map.clone();
ctx.in_derived_constructor = is_derived;
ctx.private_name_map = private_names.clone();
ctx.scopes.private_name_map = private_names.clone();
let mut ctor_module = if let Some(method) = constructor_method {
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
@@ -784,7 +785,7 @@ impl Compiler {
module.bytecode.push(opcode::encode(OpCode::SUPER_CALL, 0, 0, 0));
module.bytecode.push(0);
let mut field_ctx = CompileCtx::new();
field_ctx.private_name_map = private_names.clone();
field_ctx.scopes.private_name_map = private_names.clone();
for field in &instance_fields {
if let PropertyKey::PrivateIdentifier(private) = &field.key {
self.emit_private_field_init(254, private.name.as_str(), field.value.as_ref(), &mut field_ctx)?;
@@ -811,8 +812,7 @@ impl Compiler {
ctor_module.function_name = ctor_name.clone();
ctx.sub_modules.push(ctor_module);
let ctor_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32));
ctx.emit_load_const(ctor_reg, ctor_idx);
ctx.emit_create_closure(ctor_reg, ctx.sub_modules.len() as u32);
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0));
if let Some(super_reg) = super_reg {
@@ -913,7 +913,7 @@ impl Compiler {
}
}
ctx.private_name_map = saved_private_names;
ctx.scopes.private_name_map = saved_private_names;
Ok(ctor_reg)
}
@@ -947,68 +947,66 @@ impl Compiler {
method_module.needs_home_object = true;
ctx.sub_modules.push(method_module);
let method_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32));
let method_reg = ctx.alloc_reg();
ctx.emit_load_const(method_reg, method_idx);
ctx.emit_create_closure(method_reg, ctx.sub_modules.len() as u32);
ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, home_reg, 0));
Ok(method_reg)
}
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
match stmt {
Statement::ExpressionStatement(_) => self.emit_expression_statement(stmt, ctx),
Statement::VariableDeclaration(_) => self.emit_variable_declaration_statement(stmt, ctx),
Statement::ReturnStatement(_) => self.emit_return_statement(stmt, ctx),
Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx),
Statement::IfStatement(_) => self.emit_if_statement(stmt, ctx),
Statement::WhileStatement(_) => self.emit_while_statement(stmt, ctx),
Statement::DoWhileStatement(_) => self.emit_do_while_statement(stmt, ctx),
Statement::ForStatement(_) => self.emit_for_statement(stmt, ctx),
Statement::ForInStatement(_) => self.emit_for_in_statement(stmt, ctx),
Statement::ForOfStatement(_) => self.emit_for_of_statement(stmt, ctx),
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
Statement::ExpressionStatement(_) | Statement::ReturnStatement(_) | Statement::EmptyStatement(_) => {
self.emit_basic_domain(stmt, ctx)
}
Statement::BlockStatement(_) => self.emit_block_domain(stmt, ctx),
Statement::VariableDeclaration(_) | Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {
self.emit_declaration_domain(stmt, ctx)
}
Statement::IfStatement(_) => self.emit_control_domain(stmt, ctx),
Statement::WhileStatement(_)
| Statement::DoWhileStatement(_)
| Statement::ForStatement(_)
| Statement::ForInStatement(_)
| Statement::ForOfStatement(_) => self.emit_iteration_domain(stmt, ctx),
Statement::SwitchStatement(_) => self.emit_switch_domain(stmt, ctx),
Statement::ThrowStatement(_) | Statement::TryStatement(_) => self.emit_exception_domain(stmt, ctx),
Statement::BreakStatement(b) => self.emit_break_statement(b, ctx),
Statement::ContinueStatement(c) => self.emit_continue_statement(c, ctx),
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
Statement::ClassDeclaration(_) => self.emit_class_declaration_statement(stmt, ctx),
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
Statement::TryStatement(_) => self.emit_try_statement(stmt, ctx),
Statement::LabeledStatement(ls) => self.emit_labeled_statement(ls, ctx),
Statement::EmptyStatement(_) => Ok(None),
_ => Ok(None),
}
}
pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
match expr {
Expression::NumericLiteral(n) => self.emit_numeric_literal_expression(n, ctx),
Expression::StringLiteral(s) => self.emit_string_literal_expression(s, ctx),
Expression::BooleanLiteral(b) => self.emit_boolean_literal_expression(b, ctx),
Expression::NullLiteral(_) => self.emit_null_literal_expression(ctx),
Expression::PrivateInExpression(pin) => self.emit_private_in_expression(pin, ctx),
Expression::BinaryExpression(bin) => self.emit_binary_expression(bin, ctx),
Expression::UnaryExpression(un) => self.emit_unary_expression(un, ctx),
Expression::ConditionalExpression(cond) => self.emit_conditional_expression(cond, ctx),
Expression::LogicalExpression(log) => self.emit_logical_expression(log, ctx),
Expression::StaticMemberExpression(member) => self.emit_static_member_expression(member, ctx),
Expression::ComputedMemberExpression(member) => self.emit_computed_member_expression(member, ctx),
Expression::PrivateFieldExpression(member) => self.emit_private_field_expression(member, ctx),
Expression::ChainExpression(chain) => self.emit_chain_expression(chain, ctx),
Expression::ObjectExpression(obj) => self.emit_object_expression(obj, ctx),
Expression::ArrayExpression(arr) => self.emit_array_expression(arr, ctx),
Expression::NumericLiteral(_)
| Expression::StringLiteral(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::RegExpLiteral(_) => self.emit_literal(expr, ctx),
Expression::BinaryExpression(_)
| Expression::PrivateInExpression(_)
| Expression::UnaryExpression(_)
| Expression::ConditionalExpression(_)
| Expression::LogicalExpression(_)
| Expression::UpdateExpression(_) => self.emit_operator(expr, ctx),
Expression::StaticMemberExpression(_)
| Expression::ComputedMemberExpression(_)
| Expression::PrivateFieldExpression(_)
| Expression::ChainExpression(_) => self.emit_member_domain(expr, ctx),
Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => self.emit_object_domain(expr, ctx),
Expression::AssignmentExpression(assign) => self.emit_assignment_expression(assign, ctx),
Expression::UpdateExpression(update) => self.emit_update_expression(update, ctx),
Expression::TemplateLiteral(_) | Expression::TaggedTemplateExpression(_) => {
self.emit_template_domain(expr, ctx)
}
Expression::ArrowFunctionExpression(_)
| Expression::FunctionExpression(_)
| Expression::ClassExpression(_)
| Expression::NewExpression(_) => self.emit_function_domain(expr, ctx),
Expression::Identifier(ident) => self.emit_identifier_expression(ident, ctx),
Expression::TemplateLiteral(tl) => self.emit_template_literal_expression(tl, ctx),
Expression::TaggedTemplateExpression(tt) => self.emit_tagged_template_expression(tt, ctx),
Expression::ArrowFunctionExpression(arrow) => self.emit_arrow_function_expression(arrow, ctx),
Expression::FunctionExpression(fe) => self.emit_function_expression(fe, ctx),
Expression::ClassExpression(class) => self.emit_class_expression(class, ctx),
Expression::NewExpression(ne) => self.emit_new_expression(ne, ctx),
Expression::ParenthesizedExpression(p) => self.emit_parenthesized_expression(p, ctx),
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
Expression::CallExpression(_) => self.emit_call_domain(expr, ctx),
Expression::ThisExpression(_) => self.emit_this_expression(ctx),
Expression::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx),
Expression::SequenceExpression(seq) => self.emit_sequence_expression(seq, ctx),
Expression::ParenthesizedExpression(p) => self.emit_parenthesized_expression(p, ctx),
_ => self.emit_unsupported_expression(expr, ctx),
}
}
@@ -1,18 +1,14 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_expression_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_expression_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ExpressionStatement(es) = stmt else {
return Ok(None);
};
Ok(Some(self.emit_expression(&es.expression, ctx)?))
}
pub(in crate::emitter) fn emit_return_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_return_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ReturnStatement(ret) = stmt else {
return Ok(None);
};
@@ -92,7 +88,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn is_iteration_statement(stmt: &Statement) -> bool {
fn is_iteration_statement(stmt: &Statement) -> bool {
matches!(
stmt,
Statement::WhileStatement(_)
@@ -102,4 +98,15 @@ impl Compiler {
| Statement::ForOfStatement(_)
)
}
pub(in crate::emitter) fn emit_basic_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::ExpressionStatement(_) => self.emit_expression_statement(stmt, ctx),
Statement::ReturnStatement(_) => self.emit_return_statement(stmt, ctx),
Statement::EmptyStatement(_) => Ok(None),
_ => Ok(None),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_block_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_block_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::BlockStatement(block) = stmt else {
return Ok(None);
};
@@ -17,4 +15,13 @@ impl Compiler {
ctx.pop_scope();
Ok(r)
}
pub(in crate::emitter) fn emit_block_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx),
_ => Ok(None),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_if_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_if_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::IfStatement(ifs) = stmt else {
return Ok(None);
};
@@ -46,4 +44,13 @@ impl Compiler {
Ok(Some(result_reg))
}
pub(in crate::emitter) fn emit_control_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::IfStatement(_) => self.emit_if_statement(stmt, ctx),
_ => Ok(None),
}
}
}
@@ -1,7 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_variable_declaration_statement(
fn emit_variable_declaration_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
let Statement::VariableDeclaration(decl) = stmt else {
@@ -41,7 +41,7 @@ impl Compiler {
Ok(r)
}
pub(in crate::emitter) fn emit_function_declaration_statement(
fn emit_function_declaration_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
let Statement::FunctionDeclaration(fd) = stmt else {
@@ -77,16 +77,13 @@ impl Compiler {
let var_reg = ctx.lookup(&name)?;
ctx.reserve_reg(var_reg);
let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx));
ctx.emit_load_const(var_reg, const_idx);
ctx.emit_create_closure(var_reg, sub_idx);
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, var_reg, 0));
Ok(None)
}
pub(in crate::emitter) fn emit_class_declaration_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_class_declaration_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ClassDeclaration(class) = stmt else {
return Ok(None);
};
@@ -102,4 +99,15 @@ impl Compiler {
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, ctor_reg, 0));
Ok(None)
}
pub(in crate::emitter) fn emit_declaration_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::VariableDeclaration(_) => self.emit_variable_declaration_statement(stmt, ctx),
Statement::FunctionDeclaration(_) => self.emit_function_declaration_statement(stmt, ctx),
Statement::ClassDeclaration(_) => self.emit_class_declaration_statement(stmt, ctx),
_ => Ok(None),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_throw_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_throw_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ThrowStatement(ts) = stmt else {
return Ok(None);
};
@@ -12,9 +10,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_try_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_try_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::TryStatement(ts) = stmt else {
return Ok(None);
};
@@ -60,7 +56,7 @@ impl Compiler {
};
let catch_label_pc = ctx.bytecode.len();
ctx.label_map.insert(catch_label, catch_label_pc);
ctx.labels.label_map.insert(catch_label, catch_label_pc);
if let Some(try_begin_pc) = try_begin_pos {
let offset = catch_label_pc as isize - (try_begin_pc as isize);
@@ -90,7 +86,7 @@ impl Compiler {
if has_finally {
let finally_label = Label::FinallyBody(id);
let finally_label_pc = ctx.bytecode.len();
ctx.label_map.insert(finally_label, finally_label_pc);
ctx.labels.label_map.insert(finally_label, finally_label_pc);
if let Some(fb_pos) = try_finally_begin_pos {
let offset = finally_label_pc as isize - (fb_pos as isize);
let offset = ctx.checked_jump_offset(offset);
@@ -118,8 +114,18 @@ impl Compiler {
}
let try_end_pc = ctx.bytecode.len();
ctx.label_map.insert(try_end_label, try_end_pc);
ctx.labels.label_map.insert(try_end_label, try_end_pc);
Ok(Some(result_reg))
}
pub(in crate::emitter) fn emit_exception_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
Statement::TryStatement(_) => self.emit_try_statement(stmt, ctx),
_ => Ok(None),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_while_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_while_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::WhileStatement(wh) = stmt else {
return Ok(None);
};
@@ -32,9 +30,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_do_while_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_do_while_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::DoWhileStatement(dw) = stmt else {
return Ok(None);
};
@@ -57,9 +53,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_for_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_for_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ForStatement(fr) = stmt else {
return Ok(None);
};
@@ -113,9 +107,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_for_in_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_for_in_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ForInStatement(fi) = stmt else {
return Ok(None);
};
@@ -176,9 +168,7 @@ impl Compiler {
Ok(None)
}
pub(in crate::emitter) fn emit_for_of_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_for_of_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::ForOfStatement(fo) = stmt else {
return Ok(None);
};
@@ -230,4 +220,17 @@ impl Compiler {
ctx.pop_loop();
Ok(None)
}
pub(in crate::emitter) fn emit_iteration_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::WhileStatement(_) => self.emit_while_statement(stmt, ctx),
Statement::DoWhileStatement(_) => self.emit_do_while_statement(stmt, ctx),
Statement::ForStatement(_) => self.emit_for_statement(stmt, ctx),
Statement::ForInStatement(_) => self.emit_for_in_statement(stmt, ctx),
Statement::ForOfStatement(_) => self.emit_for_of_statement(stmt, ctx),
_ => Ok(None),
}
}
}
@@ -1,9 +1,7 @@
use super::*;
impl Compiler {
pub(in crate::emitter) fn emit_switch_statement(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
fn emit_switch_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
let Statement::SwitchStatement(sw) = stmt else {
return Ok(None);
};
@@ -47,4 +45,13 @@ impl Compiler {
ctx.pop_switch();
Ok(None)
}
pub(in crate::emitter) fn emit_switch_domain(
&self, stmt: &Statement, ctx: &mut CompileCtx,
) -> Result<Option<u8>, String> {
match stmt {
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
_ => Ok(None),
}
}
}
+2
View File
@@ -1,7 +1,9 @@
#![doc = "OxideJS - AST to bytecode compiler"]
pub mod compiler;
pub mod compiler_log;
pub mod counter;
pub mod emit_ctx;
pub mod emitter;
pub mod hash;
pub mod symbol_table;
@@ -76,7 +76,7 @@ fn compile_arrow_name_inference() {
.sub_modules
.iter()
.any(|m| m.is_arrow && m.function_name.as_deref() == Some("myArrow"));
assert!(named, "arrow function should have function_name='myArrow' from D-04 inference");
assert!(named, "arrow function should have function_name='myArrow' from name inference");
}
#[test]
@@ -1,3 +1,5 @@
use std::num::NonZeroUsize;
use oxide_code_cache::CodeForge;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
@@ -24,7 +26,7 @@ fn parse_to_compiled_hash(source: &str) -> u64 {
#[test]
fn structural_hash_hit() {
let forge = CodeForge::new();
let forge = CodeForge::new(NonZeroUsize::new(512).unwrap());
let compiler = Compiler::new();
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed");
+4 -5
View File
@@ -11,9 +11,8 @@ oxide_bytecode = { path = "../oxide_bytecode" }
oxide_code_cache = { path = "../oxide_code_cache" }
oxide_parser = { path = "../oxide_parser" }
oxide_types = { path = "../oxide_types" }
dashmap.workspace = true
hashbrown.workspace = true
rustc-hash.workspace = true
oxide_log = { path = "../oxide_log" }
bumpalo.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
hashbrown.workspace = true
dashmap.workspace = true
rustc-hash.workspace = true
+93 -66
View File
@@ -2,9 +2,10 @@ use oxide_types::mem::P;
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue;
use crate::kernel::BuiltinDirtySet;
use crate::kernel::{BuiltinDirtySet, BuiltinId};
use crate::kernel_info;
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use crate::string_forge::StringForge;
use crate::string_forge::PermInterner;
#[macro_export]
macro_rules! bind_method {
@@ -144,20 +145,6 @@ pub struct RegExpMethods {
pub to_string: *const (),
}
pub struct NumberMethods {
pub is_nan: *const (),
pub is_finite: *const (),
pub parse_int: *const (),
pub parse_float: *const (),
pub to_string: *const (),
pub to_fixed: *const (),
pub is_integer: *const (),
pub is_safe_integer: *const (),
pub to_exponential: *const (),
pub to_precision: *const (),
pub value_of: *const (),
}
pub struct FunctionMethods {
pub call: *const (),
pub apply: *const (),
@@ -233,12 +220,12 @@ pub struct BuiltinWorld {
pub stub_objects: Vec<P<JsObject>>,
}
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
fn intern_label(string_forge: &PermInterner, label: &str) -> u32 {
string_forge.intern(label).0
}
fn make_pair(
string_forge: &StringForge, shape_forge: &ShapeForge, name: &str, si_prototype: u32, si_constructor: u32,
string_forge: &PermInterner, shape_forge: &ShapeForge, name: &str, si_prototype: u32, si_constructor: u32,
si_name: u32,
) -> (P<JsObject>, P<JsObject>) {
intern_label(string_forge, name);
@@ -272,7 +259,7 @@ struct BuiltinLabels {
name: u32,
}
fn builtin_labels(string_forge: &StringForge) -> BuiltinLabels {
fn builtin_labels(string_forge: &PermInterner) -> BuiltinLabels {
let labels = BuiltinLabels {
prototype: intern_label(string_forge, "prototype"),
constructor: intern_label(string_forge, "constructor"),
@@ -285,7 +272,7 @@ fn builtin_labels(string_forge: &StringForge) -> BuiltinLabels {
}
fn make_named_pair(
string_forge: &StringForge, shape_forge: &ShapeForge, labels: BuiltinLabels, name: &str,
string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, name: &str,
) -> (P<JsObject>, P<JsObject>) {
make_pair(string_forge, shape_forge, name, labels.prototype, labels.constructor, labels.name)
}
@@ -338,7 +325,7 @@ struct TypedArrayFamily {
}
fn make_typed_array_family(
string_forge: &StringForge, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P<JsObject>,
string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P<JsObject>,
) -> TypedArrayFamily {
let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject);
let typed_array_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, obj_proto_val));
@@ -486,7 +473,76 @@ impl BuiltinWorld {
JsValue::from_js_object(self.function_proto.as_ptr() as *mut JsObject)
}
pub fn new(string_forge: &StringForge, shape_forge: &ShapeForge) -> Self {
pub fn get_by_id(&self, id: BuiltinId) -> &P<JsObject> {
match id {
BuiltinId::ObjectProto => &self.object_proto,
BuiltinId::ArrayProto => &self.array_proto,
BuiltinId::FunctionProto => &self.function_proto,
BuiltinId::StringProto => &self.string_proto,
BuiltinId::NumberProto => &self.number_proto,
BuiltinId::BooleanProto => &self.boolean_proto,
BuiltinId::ErrorProto => &self.error_proto,
BuiltinId::SymbolProto => &self.symbol_proto,
BuiltinId::ObjectConstructor => &self.object_constructor,
BuiltinId::ArrayConstructor => &self.array_constructor,
BuiltinId::FunctionConstructor => &self.function_constructor,
BuiltinId::StringConstructor => &self.string_constructor,
BuiltinId::NumberConstructor => &self.number_constructor,
BuiltinId::BooleanConstructor => &self.boolean_constructor,
BuiltinId::ErrorConstructor => &self.error_constructor,
BuiltinId::SymbolConstructor => &self.symbol_constructor,
BuiltinId::TypeErrorProto => &self.type_error_proto,
BuiltinId::ReferenceErrorProto => &self.reference_error_proto,
BuiltinId::RangeErrorProto => &self.range_error_proto,
BuiltinId::SyntaxErrorProto => &self.syntax_error_proto,
BuiltinId::UriErrorProto => &self.uri_error_proto,
BuiltinId::EvalErrorProto => &self.eval_error_proto,
BuiltinId::MathObject => &self.math_object,
BuiltinId::JsonObject => &self.json_object,
BuiltinId::DateConstructor => &self.date_constructor,
BuiltinId::DateProto => &self.date_proto,
BuiltinId::SetConstructor => &self.set_constructor,
BuiltinId::SetProto => &self.set_proto,
BuiltinId::MapConstructor => &self.map_constructor,
BuiltinId::MapProto => &self.map_proto,
BuiltinId::RegExpConstructor => &self.regexp_constructor,
BuiltinId::RegExpProto => &self.regexp_proto,
BuiltinId::ArrayBufferConstructor => &self.array_buffer_constructor,
BuiltinId::ArrayBufferProto => &self.array_buffer_proto,
BuiltinId::DataViewConstructor => &self.data_view_constructor,
BuiltinId::DataViewProto => &self.data_view_proto,
BuiltinId::TypedArrayProto => &self.typed_array_proto,
BuiltinId::Int8ArrayConstructor => &self.int8array_constructor,
BuiltinId::Int8ArrayProto => &self.int8array_proto,
BuiltinId::Uint8ArrayConstructor => &self.uint8array_constructor,
BuiltinId::Uint8ArrayProto => &self.uint8array_proto,
BuiltinId::Uint8ClampedArrayConstructor => &self.uint8clampedarray_constructor,
BuiltinId::Uint8ClampedArrayProto => &self.uint8clampedarray_proto,
BuiltinId::Int16ArrayConstructor => &self.int16array_constructor,
BuiltinId::Int16ArrayProto => &self.int16array_proto,
BuiltinId::Uint16ArrayConstructor => &self.uint16array_constructor,
BuiltinId::Uint16ArrayProto => &self.uint16array_proto,
BuiltinId::Int32ArrayConstructor => &self.int32array_constructor,
BuiltinId::Int32ArrayProto => &self.int32array_proto,
BuiltinId::Uint32ArrayConstructor => &self.uint32array_constructor,
BuiltinId::Uint32ArrayProto => &self.uint32array_proto,
BuiltinId::Float32ArrayConstructor => &self.float32array_constructor,
BuiltinId::Float32ArrayProto => &self.float32array_proto,
BuiltinId::Float64ArrayConstructor => &self.float64array_constructor,
BuiltinId::Float64ArrayProto => &self.float64array_proto,
BuiltinId::BigInt64ArrayConstructor => &self.bigint64array_constructor,
BuiltinId::BigInt64ArrayProto => &self.bigint64array_proto,
BuiltinId::BigUint64ArrayConstructor => &self.biguint64array_constructor,
BuiltinId::BigUint64ArrayProto => &self.biguint64array_proto,
BuiltinId::SymMatch => &self.sym_match,
BuiltinId::SymReplace => &self.sym_replace,
BuiltinId::SymSearch => &self.sym_search,
BuiltinId::SymSplit => &self.sym_split,
BuiltinId::SymIterator => &self.sym_iterator,
}
}
pub fn new(string_forge: &PermInterner, shape_forge: &ShapeForge) -> Self {
let labels = builtin_labels(string_forge);
let (object_proto, object_constructor) = make_named_pair(string_forge, shape_forge, labels, "Object");
@@ -588,11 +644,12 @@ impl BuiltinWorld {
stub_objects,
};
wire_builtin_world_links(&world);
kernel_info!("BuiltinWorld initialized");
world
}
pub fn rebuild_with_dirty(
current: &BuiltinWorld, string_forge: &StringForge, shape_forge: &ShapeForge, dirty: &BuiltinDirtySet,
current: &BuiltinWorld, string_forge: &PermInterner, shape_forge: &ShapeForge, dirty: &BuiltinDirtySet,
) -> BuiltinWorld {
let labels = builtin_labels(string_forge);
@@ -810,7 +867,7 @@ impl BuiltinWorld {
world
}
pub fn bind_object_methods(&self, methods: &ObjectMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
pub fn bind_object_methods(&self, methods: &ObjectMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.object_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(
@@ -857,7 +914,7 @@ impl BuiltinWorld {
}
}
pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.array_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(self, ctor, string_forge, shape_forge, ("isArray", methods.is_array, 1),);
@@ -909,7 +966,7 @@ impl BuiltinWorld {
debug_assert!(shape_forge.lookup_position(proto.shape_id(), si).is_some());
}
pub fn bind_error_methods(&self, methods: &ErrorMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
pub fn bind_error_methods(&self, methods: &ErrorMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.error_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(
@@ -938,7 +995,7 @@ impl BuiltinWorld {
);
}
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1),);
@@ -979,38 +1036,8 @@ impl BuiltinWorld {
);
}
pub fn bind_number_methods(&self, methods: &NumberMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.number_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
let proto_ptr = P::as_ptr(&self.number_proto) as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
bind_methods!(
self,
ctor,
string_forge,
shape_forge,
("isNaN", methods.is_nan, 1),
("isFinite", methods.is_finite, 1),
("parseInt", methods.parse_int, 1),
("parseFloat", methods.parse_float, 1),
("isInteger", methods.is_integer, 1),
("isSafeInteger", methods.is_safe_integer, 1),
);
bind_methods!(
self,
proto,
string_forge,
shape_forge,
("toString", methods.to_string, 0),
("toFixed", methods.to_fixed, 0),
("toExponential", methods.to_exponential, 0),
("toPrecision", methods.to_precision, 0),
("valueOf", methods.value_of, 0),
);
}
pub fn bind_function_methods(
&self, methods: &FunctionMethods, string_forge: &StringForge, shape_forge: &ShapeForge,
&self, methods: &FunctionMethods, string_forge: &PermInterner, shape_forge: &ShapeForge,
) {
let proto_ptr = P::as_ptr(&self.function_proto) as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
@@ -1028,7 +1055,7 @@ impl BuiltinWorld {
}
pub fn bind_method(
&self, proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &StringForge, method_name: &str,
&self, proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str,
native_fn_ptr: NativeFnPtr, arg_count: u8,
) -> Result<(), String> {
Self::bind_method_static(
@@ -1043,7 +1070,7 @@ impl BuiltinWorld {
}
pub fn bind_method_static(
proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &StringForge, method_name: &str,
proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str,
native_fn_ptr: NativeFnPtr, arg_count: u8, wrapper_proto: JsValue,
) -> Result<(), String> {
let si = string_forge.intern(method_name).0;
@@ -1066,7 +1093,7 @@ impl BuiltinWorld {
wrapper.set_shape_id(name_shape);
wrapper
.ensure_hash_props()
.push(JsValue::string(string_forge.intern(method_name).0, 0));
.push(JsValue::perm_string(string_forge.string_ptr(si)));
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
proto.set_shape_id(new_shape);
@@ -1080,10 +1107,10 @@ impl BuiltinWorld {
mod tests {
use super::*;
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use crate::string_forge::StringForge;
use crate::string_forge::PermInterner;
fn make_world() -> BuiltinWorld {
let sf = StringForge::new();
let sf = PermInterner::new();
let sh = ShapeForge::new();
BuiltinWorld::new(&sf, &sh)
}
@@ -1159,7 +1186,7 @@ mod tests {
#[test]
fn builtin_rebuild_with_dirty_reuses_clean_fields() {
let sf = StringForge::new();
let sf = PermInterner::new();
let sh = ShapeForge::new();
let w = BuiltinWorld::new(&sf, &sh);
let rebuilt = BuiltinWorld::rebuild_with_dirty(&w, &sf, &sh, &crate::kernel::BuiltinDirtySet::default());
@@ -1171,7 +1198,7 @@ mod tests {
#[test]
fn builtin_rebuild_with_dirty_replaces_only_dirty_group() {
let sf = StringForge::new();
let sf = PermInterner::new();
let sh = ShapeForge::new();
let w = BuiltinWorld::new(&sf, &sh);
let dirty = crate::kernel::BuiltinDirtySet {
@@ -1188,7 +1215,7 @@ mod tests {
#[test]
fn builtin_rebuild_with_dirty_repairs_ctor_proto_links() {
let sf = StringForge::new();
let sf = PermInterner::new();
let sh = ShapeForge::new();
let w = BuiltinWorld::new(&sf, &sh);
let dirty = crate::kernel::BuiltinDirtySet {
+272 -229
View File
@@ -1,5 +1,6 @@
#![allow(clippy::arc_with_non_send_sync)]
use std::num::NonZeroUsize;
use std::sync::Arc;
use oxide_types::mem::P;
@@ -8,10 +9,12 @@ use oxide_types::value::JsValue;
use crate::builtin::BuiltinWorld;
use crate::code_forge::CodeForge;
use crate::logging::{init_logging, LogLevel, SUBSYSTEM_COUNT};
use crate::kernel_info;
use crate::prop_forge::PropForge;
use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use crate::string_forge::StringForge;
use crate::string_forge::PermInterner;
use oxide_log;
use oxide_log::{Level, SUBSYSTEM_COUNT};
#[derive(Clone)]
pub struct KernelConfig {
@@ -21,7 +24,8 @@ pub struct KernelConfig {
pub max_steps: Option<u64>,
pub max_call_depth: usize,
pub session_gc_threshold: usize,
pub log_levels: [LogLevel; SUBSYSTEM_COUNT],
pub max_cached_modules: usize,
pub log_levels: [Level; SUBSYSTEM_COUNT],
pub warmup_builtin_shapes: bool,
pub warmup_builtin_code: bool,
pub warmup_builtin_ic: bool,
@@ -36,7 +40,8 @@ impl KernelConfig {
max_steps: None,
max_call_depth: 1024,
session_gc_threshold: 8_388_608,
log_levels: [LogLevel::Off; SUBSYSTEM_COUNT],
max_cached_modules: 512,
log_levels: [Level::Off; SUBSYSTEM_COUNT],
warmup_builtin_shapes: true,
warmup_builtin_code: false,
warmup_builtin_ic: false,
@@ -51,7 +56,8 @@ impl KernelConfig {
max_steps: None,
max_call_depth: 1024,
session_gc_threshold: 8_388_608,
log_levels: [LogLevel::Off; SUBSYSTEM_COUNT],
max_cached_modules: 512,
log_levels: [Level::Off; SUBSYSTEM_COUNT],
warmup_builtin_shapes: true,
warmup_builtin_code: true,
warmup_builtin_ic: false,
@@ -66,7 +72,8 @@ impl KernelConfig {
max_steps: None,
max_call_depth: 1024,
session_gc_threshold: 8_388_608,
log_levels: [LogLevel::Off; SUBSYSTEM_COUNT],
max_cached_modules: 512,
log_levels: [Level::Off; SUBSYSTEM_COUNT],
warmup_builtin_shapes: true,
warmup_builtin_code: true,
warmup_builtin_ic: true,
@@ -81,9 +88,12 @@ impl KernelConfig {
self.session_gc_threshold = bytes;
}
pub fn with_session_gc_threshold(mut self, bytes: usize) -> Self {
self.session_gc_threshold = bytes;
self
pub fn max_cached_modules(&self) -> usize {
self.max_cached_modules
}
pub fn set_max_cached_modules(&mut self, cap: usize) {
self.max_cached_modules = cap;
}
}
@@ -97,7 +107,7 @@ impl Default for KernelConfig {
/// Never rebuilt after construction — forge tables are append-only.
pub struct KernelCore {
pub config: KernelConfig,
pub string_forge: Arc<StringForge>,
pub perm_interner: Arc<PermInterner>,
pub shape_forge: Arc<ShapeForge>,
pub code_forge: Arc<CodeForge>,
pub prop_forge: Arc<PropForge>,
@@ -105,22 +115,31 @@ pub struct KernelCore {
impl KernelCore {
pub fn new(config: KernelConfig) -> Arc<Self> {
init_logging(&config.log_levels);
let string_forge = Arc::new(StringForge::new());
oxide_log::init(&oxide_log::LogConfig {
output: oxide_log::Output::Stderr,
levels: config.log_levels,
});
let perm_interner = Arc::new(PermInterner::new());
let shape_forge = Arc::new(ShapeForge::new());
let code_forge = Arc::new(CodeForge::new());
let code_forge = Arc::new(CodeForge::new(
NonZeroUsize::new(config.max_cached_modules).expect("max_cached_modules must be greater than zero"),
));
let prop_forge = Arc::new(PropForge::new());
Arc::new(Self {
let max_cached = config.max_cached_modules;
let min_pool = config.min_pool_size;
let core = Arc::new(Self {
config,
string_forge,
perm_interner,
shape_forge,
code_forge,
prop_forge,
})
});
kernel_info!("KernelCore initialized: max_cached_modules={}, min_pool={}", max_cached, min_pool);
core
}
pub fn string_forge(&self) -> &Arc<StringForge> {
&self.string_forge
pub fn perm_interner(&self) -> &Arc<PermInterner> {
&self.perm_interner
}
pub fn shape_forge(&self) -> &Arc<ShapeForge> {
@@ -147,8 +166,17 @@ impl KernelCore {
self.config.session_gc_threshold = bytes;
}
pub fn max_cached_modules(&self) -> usize {
self.config.max_cached_modules
}
pub fn set_max_cached_modules(&mut self, cap: usize) {
self.config.max_cached_modules = cap;
}
pub fn sweep_runner_forges(&self) {
self.string_forge.maybe_sweep(self.config.max_dead_strings.or(Some(1)));
// The key interner is append-only (no per-run sweep); only the transient
// shape/prop tables need bounding at the test262 per-test boundary.
// test262 creates a fresh VM/session per test. At this boundary no JS object
// from prior tests may retain transient shapes/templates.
if self.shape_forge.len() > 50_000 {
@@ -173,72 +201,149 @@ pub struct KernelSession {
/// Maintenance: every new `BuiltinWorld` object field must be added here and to
/// `KernelSession::dirty_since_snapshot()` so selective reset can rebuild the
/// correct builtin family.
pub const NUM_BUILTINS: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum BuiltinId {
ObjectProto = 0,
ArrayProto = 1,
FunctionProto = 2,
StringProto = 3,
NumberProto = 4,
BooleanProto = 5,
ErrorProto = 6,
SymbolProto = 7,
ObjectConstructor = 8,
ArrayConstructor = 9,
FunctionConstructor = 10,
StringConstructor = 11,
NumberConstructor = 12,
BooleanConstructor = 13,
ErrorConstructor = 14,
SymbolConstructor = 15,
TypeErrorProto = 16,
ReferenceErrorProto = 17,
RangeErrorProto = 18,
SyntaxErrorProto = 19,
UriErrorProto = 20,
EvalErrorProto = 21,
MathObject = 22,
JsonObject = 23,
DateConstructor = 24,
DateProto = 25,
SetConstructor = 26,
SetProto = 27,
MapConstructor = 28,
MapProto = 29,
RegExpConstructor = 30,
RegExpProto = 31,
ArrayBufferConstructor = 32,
ArrayBufferProto = 33,
DataViewConstructor = 34,
DataViewProto = 35,
TypedArrayProto = 36,
Int8ArrayConstructor = 37,
Int8ArrayProto = 38,
Uint8ArrayConstructor = 39,
Uint8ArrayProto = 40,
Uint8ClampedArrayConstructor = 41,
Uint8ClampedArrayProto = 42,
Int16ArrayConstructor = 43,
Int16ArrayProto = 44,
Uint16ArrayConstructor = 45,
Uint16ArrayProto = 46,
Int32ArrayConstructor = 47,
Int32ArrayProto = 48,
Uint32ArrayConstructor = 49,
Uint32ArrayProto = 50,
Float32ArrayConstructor = 51,
Float32ArrayProto = 52,
Float64ArrayConstructor = 53,
Float64ArrayProto = 54,
BigInt64ArrayConstructor = 55,
BigInt64ArrayProto = 56,
BigUint64ArrayConstructor = 57,
BigUint64ArrayProto = 58,
SymMatch = 59,
SymReplace = 60,
SymSearch = 61,
SymSplit = 62,
SymIterator = 63,
}
impl BuiltinId {
pub const ALL: [BuiltinId; NUM_BUILTINS] = [
BuiltinId::ObjectProto,
BuiltinId::ArrayProto,
BuiltinId::FunctionProto,
BuiltinId::StringProto,
BuiltinId::NumberProto,
BuiltinId::BooleanProto,
BuiltinId::ErrorProto,
BuiltinId::SymbolProto,
BuiltinId::ObjectConstructor,
BuiltinId::ArrayConstructor,
BuiltinId::FunctionConstructor,
BuiltinId::StringConstructor,
BuiltinId::NumberConstructor,
BuiltinId::BooleanConstructor,
BuiltinId::ErrorConstructor,
BuiltinId::SymbolConstructor,
BuiltinId::TypeErrorProto,
BuiltinId::ReferenceErrorProto,
BuiltinId::RangeErrorProto,
BuiltinId::SyntaxErrorProto,
BuiltinId::UriErrorProto,
BuiltinId::EvalErrorProto,
BuiltinId::MathObject,
BuiltinId::JsonObject,
BuiltinId::DateConstructor,
BuiltinId::DateProto,
BuiltinId::SetConstructor,
BuiltinId::SetProto,
BuiltinId::MapConstructor,
BuiltinId::MapProto,
BuiltinId::RegExpConstructor,
BuiltinId::RegExpProto,
BuiltinId::ArrayBufferConstructor,
BuiltinId::ArrayBufferProto,
BuiltinId::DataViewConstructor,
BuiltinId::DataViewProto,
BuiltinId::TypedArrayProto,
BuiltinId::Int8ArrayConstructor,
BuiltinId::Int8ArrayProto,
BuiltinId::Uint8ArrayConstructor,
BuiltinId::Uint8ArrayProto,
BuiltinId::Uint8ClampedArrayConstructor,
BuiltinId::Uint8ClampedArrayProto,
BuiltinId::Int16ArrayConstructor,
BuiltinId::Int16ArrayProto,
BuiltinId::Uint16ArrayConstructor,
BuiltinId::Uint16ArrayProto,
BuiltinId::Int32ArrayConstructor,
BuiltinId::Int32ArrayProto,
BuiltinId::Uint32ArrayConstructor,
BuiltinId::Uint32ArrayProto,
BuiltinId::Float32ArrayConstructor,
BuiltinId::Float32ArrayProto,
BuiltinId::Float64ArrayConstructor,
BuiltinId::Float64ArrayProto,
BuiltinId::BigInt64ArrayConstructor,
BuiltinId::BigInt64ArrayProto,
BuiltinId::BigUint64ArrayConstructor,
BuiltinId::BigUint64ArrayProto,
BuiltinId::SymMatch,
BuiltinId::SymReplace,
BuiltinId::SymSearch,
BuiltinId::SymSplit,
BuiltinId::SymIterator,
];
}
#[derive(Clone, Debug)]
pub struct BuiltinSnapshot {
pub object_proto_generation: u32,
pub array_proto_generation: u32,
pub function_proto_generation: u32,
pub string_proto_generation: u32,
pub number_proto_generation: u32,
pub boolean_proto_generation: u32,
pub error_proto_generation: u32,
pub symbol_proto_generation: u32,
pub object_constructor_generation: u32,
pub array_constructor_generation: u32,
pub function_constructor_generation: u32,
pub string_constructor_generation: u32,
pub number_constructor_generation: u32,
pub boolean_constructor_generation: u32,
pub error_constructor_generation: u32,
pub symbol_constructor_generation: u32,
pub type_error_proto_generation: u32,
pub reference_error_proto_generation: u32,
pub range_error_proto_generation: u32,
pub syntax_error_proto_generation: u32,
pub uri_error_proto_generation: u32,
pub eval_error_proto_generation: u32,
pub math_object_generation: u32,
pub json_object_generation: u32,
pub date_constructor_generation: u32,
pub date_proto_generation: u32,
pub set_constructor_generation: u32,
pub set_proto_generation: u32,
pub map_constructor_generation: u32,
pub map_proto_generation: u32,
pub regexp_constructor_generation: u32,
pub regexp_proto_generation: u32,
pub array_buffer_constructor_generation: u32,
pub array_buffer_proto_generation: u32,
pub data_view_constructor_generation: u32,
pub data_view_proto_generation: u32,
pub typed_array_proto_generation: u32,
pub int8array_constructor_generation: u32,
pub int8array_proto_generation: u32,
pub uint8array_constructor_generation: u32,
pub uint8array_proto_generation: u32,
pub uint8clampedarray_constructor_generation: u32,
pub uint8clampedarray_proto_generation: u32,
pub int16array_constructor_generation: u32,
pub int16array_proto_generation: u32,
pub uint16array_constructor_generation: u32,
pub uint16array_proto_generation: u32,
pub int32array_constructor_generation: u32,
pub int32array_proto_generation: u32,
pub uint32array_constructor_generation: u32,
pub uint32array_proto_generation: u32,
pub float32array_constructor_generation: u32,
pub float32array_proto_generation: u32,
pub float64array_constructor_generation: u32,
pub float64array_proto_generation: u32,
pub bigint64array_constructor_generation: u32,
pub bigint64array_proto_generation: u32,
pub biguint64array_constructor_generation: u32,
pub biguint64array_proto_generation: u32,
pub sym_match_generation: u32,
pub sym_replace_generation: u32,
pub sym_search_generation: u32,
pub sym_split_generation: u32,
pub sym_iterator_generation: u32,
pub generations: [u32; NUM_BUILTINS],
pub global_object_generation: u32,
pub stub_objects_len: usize,
pub stub_object_generations: Vec<u32>,
@@ -250,71 +355,13 @@ impl BuiltinSnapshot {
}
pub fn new(world: &BuiltinWorld, global_object: &P<JsObject>) -> Self {
let mut generations = [0u32; NUM_BUILTINS];
for (i, id) in BuiltinId::ALL.iter().enumerate() {
generations[i] = Self::gen(world.get_by_id(*id));
}
debug_assert_eq!(generations.len(), NUM_BUILTINS);
Self {
object_proto_generation: Self::gen(&world.object_proto),
array_proto_generation: Self::gen(&world.array_proto),
function_proto_generation: Self::gen(&world.function_proto),
string_proto_generation: Self::gen(&world.string_proto),
number_proto_generation: Self::gen(&world.number_proto),
boolean_proto_generation: Self::gen(&world.boolean_proto),
error_proto_generation: Self::gen(&world.error_proto),
symbol_proto_generation: Self::gen(&world.symbol_proto),
object_constructor_generation: Self::gen(&world.object_constructor),
array_constructor_generation: Self::gen(&world.array_constructor),
function_constructor_generation: Self::gen(&world.function_constructor),
string_constructor_generation: Self::gen(&world.string_constructor),
number_constructor_generation: Self::gen(&world.number_constructor),
boolean_constructor_generation: Self::gen(&world.boolean_constructor),
error_constructor_generation: Self::gen(&world.error_constructor),
symbol_constructor_generation: Self::gen(&world.symbol_constructor),
type_error_proto_generation: Self::gen(&world.type_error_proto),
reference_error_proto_generation: Self::gen(&world.reference_error_proto),
range_error_proto_generation: Self::gen(&world.range_error_proto),
syntax_error_proto_generation: Self::gen(&world.syntax_error_proto),
uri_error_proto_generation: Self::gen(&world.uri_error_proto),
eval_error_proto_generation: Self::gen(&world.eval_error_proto),
math_object_generation: Self::gen(&world.math_object),
json_object_generation: Self::gen(&world.json_object),
date_constructor_generation: Self::gen(&world.date_constructor),
date_proto_generation: Self::gen(&world.date_proto),
set_constructor_generation: Self::gen(&world.set_constructor),
set_proto_generation: Self::gen(&world.set_proto),
map_constructor_generation: Self::gen(&world.map_constructor),
map_proto_generation: Self::gen(&world.map_proto),
regexp_constructor_generation: Self::gen(&world.regexp_constructor),
regexp_proto_generation: Self::gen(&world.regexp_proto),
array_buffer_constructor_generation: Self::gen(&world.array_buffer_constructor),
array_buffer_proto_generation: Self::gen(&world.array_buffer_proto),
data_view_constructor_generation: Self::gen(&world.data_view_constructor),
data_view_proto_generation: Self::gen(&world.data_view_proto),
typed_array_proto_generation: Self::gen(&world.typed_array_proto),
int8array_constructor_generation: Self::gen(&world.int8array_constructor),
int8array_proto_generation: Self::gen(&world.int8array_proto),
uint8array_constructor_generation: Self::gen(&world.uint8array_constructor),
uint8array_proto_generation: Self::gen(&world.uint8array_proto),
uint8clampedarray_constructor_generation: Self::gen(&world.uint8clampedarray_constructor),
uint8clampedarray_proto_generation: Self::gen(&world.uint8clampedarray_proto),
int16array_constructor_generation: Self::gen(&world.int16array_constructor),
int16array_proto_generation: Self::gen(&world.int16array_proto),
uint16array_constructor_generation: Self::gen(&world.uint16array_constructor),
uint16array_proto_generation: Self::gen(&world.uint16array_proto),
int32array_constructor_generation: Self::gen(&world.int32array_constructor),
int32array_proto_generation: Self::gen(&world.int32array_proto),
uint32array_constructor_generation: Self::gen(&world.uint32array_constructor),
uint32array_proto_generation: Self::gen(&world.uint32array_proto),
float32array_constructor_generation: Self::gen(&world.float32array_constructor),
float32array_proto_generation: Self::gen(&world.float32array_proto),
float64array_constructor_generation: Self::gen(&world.float64array_constructor),
float64array_proto_generation: Self::gen(&world.float64array_proto),
bigint64array_constructor_generation: Self::gen(&world.bigint64array_constructor),
bigint64array_proto_generation: Self::gen(&world.bigint64array_proto),
biguint64array_constructor_generation: Self::gen(&world.biguint64array_constructor),
biguint64array_proto_generation: Self::gen(&world.biguint64array_proto),
sym_match_generation: Self::gen(&world.sym_match),
sym_replace_generation: Self::gen(&world.sym_replace),
sym_search_generation: Self::gen(&world.sym_search),
sym_split_generation: Self::gen(&world.sym_split),
sym_iterator_generation: Self::gen(&world.sym_iterator),
generations,
global_object_generation: Self::gen(global_object),
stub_objects_len: world.stub_objects.len(),
stub_object_generations: world.stub_objects.iter().map(Self::gen).collect(),
@@ -376,9 +423,9 @@ impl KernelSession {
fn new_global_object(core: &KernelCore) -> P<JsObject> {
let mut global_obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
let si_nan = core.string_forge.intern("NaN").0;
let si_undef = core.string_forge.intern("undefined").0;
let si_infinity = core.string_forge.intern("Infinity").0;
let si_nan = core.perm_interner.intern("NaN").0;
let si_undef = core.perm_interner.intern("undefined").0;
let si_infinity = core.perm_interner.intern("Infinity").0;
let nan_shape = core.shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan);
global_obj.set_shape_id(nan_shape);
@@ -398,7 +445,7 @@ impl KernelSession {
/// Build a fresh session from a KernelCore. All string/shape intern calls hit
/// cache on second and subsequent calls — net cost is < 0.5 ms.
pub fn new(core: &KernelCore) -> Self {
let builtin_world = Arc::new(BuiltinWorld::new(&core.string_forge, &core.shape_forge));
let builtin_world = Arc::new(BuiltinWorld::new(&core.perm_interner, &core.shape_forge));
let global_object = Self::new_global_object(core);
let builtin_snapshot = BuiltinSnapshot::new(&builtin_world, &global_object);
@@ -429,78 +476,74 @@ impl KernelSession {
.iter()
.zip(snapshot.stub_object_generations.iter())
.any(|(obj, generation)| BuiltinSnapshot::gen(obj) != *generation);
let gen = |id: BuiltinId| BuiltinSnapshot::gen(world.get_by_id(id));
let snap = |id: BuiltinId| snapshot.generations[id as usize];
BuiltinDirtySet {
object: BuiltinSnapshot::gen(&world.object_proto) != snapshot.object_proto_generation
|| BuiltinSnapshot::gen(&world.object_constructor) != snapshot.object_constructor_generation,
array: BuiltinSnapshot::gen(&world.array_proto) != snapshot.array_proto_generation
|| BuiltinSnapshot::gen(&world.array_constructor) != snapshot.array_constructor_generation,
function: BuiltinSnapshot::gen(&world.function_proto) != snapshot.function_proto_generation
|| BuiltinSnapshot::gen(&world.function_constructor) != snapshot.function_constructor_generation,
string: BuiltinSnapshot::gen(&world.string_proto) != snapshot.string_proto_generation
|| BuiltinSnapshot::gen(&world.string_constructor) != snapshot.string_constructor_generation,
number: BuiltinSnapshot::gen(&world.number_proto) != snapshot.number_proto_generation
|| BuiltinSnapshot::gen(&world.number_constructor) != snapshot.number_constructor_generation,
boolean: BuiltinSnapshot::gen(&world.boolean_proto) != snapshot.boolean_proto_generation
|| BuiltinSnapshot::gen(&world.boolean_constructor) != snapshot.boolean_constructor_generation,
error_family: BuiltinSnapshot::gen(&world.error_proto) != snapshot.error_proto_generation
|| BuiltinSnapshot::gen(&world.error_constructor) != snapshot.error_constructor_generation
|| BuiltinSnapshot::gen(&world.type_error_proto) != snapshot.type_error_proto_generation
|| BuiltinSnapshot::gen(&world.reference_error_proto) != snapshot.reference_error_proto_generation
|| BuiltinSnapshot::gen(&world.range_error_proto) != snapshot.range_error_proto_generation
|| BuiltinSnapshot::gen(&world.syntax_error_proto) != snapshot.syntax_error_proto_generation
|| BuiltinSnapshot::gen(&world.uri_error_proto) != snapshot.uri_error_proto_generation
|| BuiltinSnapshot::gen(&world.eval_error_proto) != snapshot.eval_error_proto_generation,
symbol_family: BuiltinSnapshot::gen(&world.symbol_proto) != snapshot.symbol_proto_generation
|| BuiltinSnapshot::gen(&world.symbol_constructor) != snapshot.symbol_constructor_generation
|| BuiltinSnapshot::gen(&world.sym_match) != snapshot.sym_match_generation
|| BuiltinSnapshot::gen(&world.sym_replace) != snapshot.sym_replace_generation
|| BuiltinSnapshot::gen(&world.sym_search) != snapshot.sym_search_generation
|| BuiltinSnapshot::gen(&world.sym_split) != snapshot.sym_split_generation
|| BuiltinSnapshot::gen(&world.sym_iterator) != snapshot.sym_iterator_generation,
math: BuiltinSnapshot::gen(&world.math_object) != snapshot.math_object_generation,
json: BuiltinSnapshot::gen(&world.json_object) != snapshot.json_object_generation,
date: BuiltinSnapshot::gen(&world.date_constructor) != snapshot.date_constructor_generation
|| BuiltinSnapshot::gen(&world.date_proto) != snapshot.date_proto_generation,
set: BuiltinSnapshot::gen(&world.set_constructor) != snapshot.set_constructor_generation
|| BuiltinSnapshot::gen(&world.set_proto) != snapshot.set_proto_generation,
map: BuiltinSnapshot::gen(&world.map_constructor) != snapshot.map_constructor_generation
|| BuiltinSnapshot::gen(&world.map_proto) != snapshot.map_proto_generation,
regexp: BuiltinSnapshot::gen(&world.regexp_constructor) != snapshot.regexp_constructor_generation
|| BuiltinSnapshot::gen(&world.regexp_proto) != snapshot.regexp_proto_generation,
array_buffer: BuiltinSnapshot::gen(&world.array_buffer_constructor)
!= snapshot.array_buffer_constructor_generation
|| BuiltinSnapshot::gen(&world.array_buffer_proto) != snapshot.array_buffer_proto_generation,
data_view: BuiltinSnapshot::gen(&world.data_view_constructor) != snapshot.data_view_constructor_generation
|| BuiltinSnapshot::gen(&world.data_view_proto) != snapshot.data_view_proto_generation,
typed_array_family: BuiltinSnapshot::gen(&world.typed_array_proto) != snapshot.typed_array_proto_generation
|| BuiltinSnapshot::gen(&world.int8array_constructor) != snapshot.int8array_constructor_generation
|| BuiltinSnapshot::gen(&world.int8array_proto) != snapshot.int8array_proto_generation
|| BuiltinSnapshot::gen(&world.uint8array_constructor) != snapshot.uint8array_constructor_generation
|| BuiltinSnapshot::gen(&world.uint8array_proto) != snapshot.uint8array_proto_generation
|| BuiltinSnapshot::gen(&world.uint8clampedarray_constructor)
!= snapshot.uint8clampedarray_constructor_generation
|| BuiltinSnapshot::gen(&world.uint8clampedarray_proto) != snapshot.uint8clampedarray_proto_generation
|| BuiltinSnapshot::gen(&world.int16array_constructor) != snapshot.int16array_constructor_generation
|| BuiltinSnapshot::gen(&world.int16array_proto) != snapshot.int16array_proto_generation
|| BuiltinSnapshot::gen(&world.uint16array_constructor) != snapshot.uint16array_constructor_generation
|| BuiltinSnapshot::gen(&world.uint16array_proto) != snapshot.uint16array_proto_generation
|| BuiltinSnapshot::gen(&world.int32array_constructor) != snapshot.int32array_constructor_generation
|| BuiltinSnapshot::gen(&world.int32array_proto) != snapshot.int32array_proto_generation
|| BuiltinSnapshot::gen(&world.uint32array_constructor) != snapshot.uint32array_constructor_generation
|| BuiltinSnapshot::gen(&world.uint32array_proto) != snapshot.uint32array_proto_generation
|| BuiltinSnapshot::gen(&world.float32array_constructor)
!= snapshot.float32array_constructor_generation
|| BuiltinSnapshot::gen(&world.float32array_proto) != snapshot.float32array_proto_generation
|| BuiltinSnapshot::gen(&world.float64array_constructor)
!= snapshot.float64array_constructor_generation
|| BuiltinSnapshot::gen(&world.float64array_proto) != snapshot.float64array_proto_generation
|| BuiltinSnapshot::gen(&world.bigint64array_constructor)
!= snapshot.bigint64array_constructor_generation
|| BuiltinSnapshot::gen(&world.bigint64array_proto) != snapshot.bigint64array_proto_generation
|| BuiltinSnapshot::gen(&world.biguint64array_constructor)
!= snapshot.biguint64array_constructor_generation
|| BuiltinSnapshot::gen(&world.biguint64array_proto) != snapshot.biguint64array_proto_generation,
object: gen(BuiltinId::ObjectProto) != snap(BuiltinId::ObjectProto)
|| gen(BuiltinId::ObjectConstructor) != snap(BuiltinId::ObjectConstructor),
array: gen(BuiltinId::ArrayProto) != snap(BuiltinId::ArrayProto)
|| gen(BuiltinId::ArrayConstructor) != snap(BuiltinId::ArrayConstructor),
function: gen(BuiltinId::FunctionProto) != snap(BuiltinId::FunctionProto)
|| gen(BuiltinId::FunctionConstructor) != snap(BuiltinId::FunctionConstructor),
string: gen(BuiltinId::StringProto) != snap(BuiltinId::StringProto)
|| gen(BuiltinId::StringConstructor) != snap(BuiltinId::StringConstructor),
number: gen(BuiltinId::NumberProto) != snap(BuiltinId::NumberProto)
|| gen(BuiltinId::NumberConstructor) != snap(BuiltinId::NumberConstructor),
boolean: gen(BuiltinId::BooleanProto) != snap(BuiltinId::BooleanProto)
|| gen(BuiltinId::BooleanConstructor) != snap(BuiltinId::BooleanConstructor),
error_family: gen(BuiltinId::ErrorProto) != snap(BuiltinId::ErrorProto)
|| gen(BuiltinId::ErrorConstructor) != snap(BuiltinId::ErrorConstructor)
|| gen(BuiltinId::TypeErrorProto) != snap(BuiltinId::TypeErrorProto)
|| gen(BuiltinId::ReferenceErrorProto) != snap(BuiltinId::ReferenceErrorProto)
|| gen(BuiltinId::RangeErrorProto) != snap(BuiltinId::RangeErrorProto)
|| gen(BuiltinId::SyntaxErrorProto) != snap(BuiltinId::SyntaxErrorProto)
|| gen(BuiltinId::UriErrorProto) != snap(BuiltinId::UriErrorProto)
|| gen(BuiltinId::EvalErrorProto) != snap(BuiltinId::EvalErrorProto),
symbol_family: gen(BuiltinId::SymbolProto) != snap(BuiltinId::SymbolProto)
|| gen(BuiltinId::SymbolConstructor) != snap(BuiltinId::SymbolConstructor)
|| gen(BuiltinId::SymMatch) != snap(BuiltinId::SymMatch)
|| gen(BuiltinId::SymReplace) != snap(BuiltinId::SymReplace)
|| gen(BuiltinId::SymSearch) != snap(BuiltinId::SymSearch)
|| gen(BuiltinId::SymSplit) != snap(BuiltinId::SymSplit)
|| gen(BuiltinId::SymIterator) != snap(BuiltinId::SymIterator),
math: gen(BuiltinId::MathObject) != snap(BuiltinId::MathObject),
json: gen(BuiltinId::JsonObject) != snap(BuiltinId::JsonObject),
date: gen(BuiltinId::DateConstructor) != snap(BuiltinId::DateConstructor)
|| gen(BuiltinId::DateProto) != snap(BuiltinId::DateProto),
set: gen(BuiltinId::SetConstructor) != snap(BuiltinId::SetConstructor)
|| gen(BuiltinId::SetProto) != snap(BuiltinId::SetProto),
map: gen(BuiltinId::MapConstructor) != snap(BuiltinId::MapConstructor)
|| gen(BuiltinId::MapProto) != snap(BuiltinId::MapProto),
regexp: gen(BuiltinId::RegExpConstructor) != snap(BuiltinId::RegExpConstructor)
|| gen(BuiltinId::RegExpProto) != snap(BuiltinId::RegExpProto),
array_buffer: gen(BuiltinId::ArrayBufferConstructor) != snap(BuiltinId::ArrayBufferConstructor)
|| gen(BuiltinId::ArrayBufferProto) != snap(BuiltinId::ArrayBufferProto),
data_view: gen(BuiltinId::DataViewConstructor) != snap(BuiltinId::DataViewConstructor)
|| gen(BuiltinId::DataViewProto) != snap(BuiltinId::DataViewProto),
typed_array_family: gen(BuiltinId::TypedArrayProto) != snap(BuiltinId::TypedArrayProto)
|| gen(BuiltinId::Int8ArrayConstructor) != snap(BuiltinId::Int8ArrayConstructor)
|| gen(BuiltinId::Int8ArrayProto) != snap(BuiltinId::Int8ArrayProto)
|| gen(BuiltinId::Uint8ArrayConstructor) != snap(BuiltinId::Uint8ArrayConstructor)
|| gen(BuiltinId::Uint8ArrayProto) != snap(BuiltinId::Uint8ArrayProto)
|| gen(BuiltinId::Uint8ClampedArrayConstructor) != snap(BuiltinId::Uint8ClampedArrayConstructor)
|| gen(BuiltinId::Uint8ClampedArrayProto) != snap(BuiltinId::Uint8ClampedArrayProto)
|| gen(BuiltinId::Int16ArrayConstructor) != snap(BuiltinId::Int16ArrayConstructor)
|| gen(BuiltinId::Int16ArrayProto) != snap(BuiltinId::Int16ArrayProto)
|| gen(BuiltinId::Uint16ArrayConstructor) != snap(BuiltinId::Uint16ArrayConstructor)
|| gen(BuiltinId::Uint16ArrayProto) != snap(BuiltinId::Uint16ArrayProto)
|| gen(BuiltinId::Int32ArrayConstructor) != snap(BuiltinId::Int32ArrayConstructor)
|| gen(BuiltinId::Int32ArrayProto) != snap(BuiltinId::Int32ArrayProto)
|| gen(BuiltinId::Uint32ArrayConstructor) != snap(BuiltinId::Uint32ArrayConstructor)
|| gen(BuiltinId::Uint32ArrayProto) != snap(BuiltinId::Uint32ArrayProto)
|| gen(BuiltinId::Float32ArrayConstructor) != snap(BuiltinId::Float32ArrayConstructor)
|| gen(BuiltinId::Float32ArrayProto) != snap(BuiltinId::Float32ArrayProto)
|| gen(BuiltinId::Float64ArrayConstructor) != snap(BuiltinId::Float64ArrayConstructor)
|| gen(BuiltinId::Float64ArrayProto) != snap(BuiltinId::Float64ArrayProto)
|| gen(BuiltinId::BigInt64ArrayConstructor) != snap(BuiltinId::BigInt64ArrayConstructor)
|| gen(BuiltinId::BigInt64ArrayProto) != snap(BuiltinId::BigInt64ArrayProto)
|| gen(BuiltinId::BigUint64ArrayConstructor) != snap(BuiltinId::BigUint64ArrayConstructor)
|| gen(BuiltinId::BigUint64ArrayProto) != snap(BuiltinId::BigUint64ArrayProto),
stubs: world.stub_objects.len() != snapshot.stub_objects_len || stub_generations_dirty,
global: BuiltinSnapshot::gen(&self.global_object) != snapshot.global_object_generation,
}
@@ -518,7 +561,7 @@ impl KernelSession {
if dirty.any_builtin_dirty() {
self.builtin_world = Arc::new(BuiltinWorld::rebuild_with_dirty(
&self.builtin_world,
core.string_forge.as_ref(),
core.perm_interner.as_ref(),
core.shape_forge.as_ref(),
&dirty,
));
@@ -535,8 +578,8 @@ mod tests {
#[test]
fn test_kernel_new() {
let core = KernelCore::new(KernelConfig::minimal());
let (i1, _) = core.string_forge().intern("test");
let (i2, _) = core.string_forge().intern("test");
let (i1, _) = core.perm_interner().intern("test");
let (i2, _) = core.perm_interner().intern("test");
assert_eq!(i1, i2);
}
@@ -557,8 +600,8 @@ mod tests {
#[test]
fn test_kernel_string_forge() {
let core = KernelCore::new(KernelConfig::minimal());
let (i1, _) = core.string_forge().intern("hello");
let (i2, _) = core.string_forge().intern("hello");
let (i1, _) = core.perm_interner().intern("hello");
let (i2, _) = core.perm_interner().intern("hello");
assert_eq!(i1, i2);
}
@@ -569,7 +612,7 @@ mod tests {
assert_eq!(KernelConfig::minimal().max_steps, None);
assert_eq!(KernelConfig::standard().max_steps, None);
assert_eq!(KernelConfig::full().max_steps, None);
assert_eq!(KernelConfig::minimal().log_levels, [LogLevel::Off; SUBSYSTEM_COUNT]);
assert_eq!(KernelConfig::minimal().log_levels, [Level::Off; SUBSYSTEM_COUNT]);
assert!(!KernelConfig::minimal().warmup_builtin_ic);
assert!(KernelConfig::full().warmup_builtin_ic);
assert_eq!(KernelConfig::full().max_pool_size, None);
@@ -578,9 +621,9 @@ mod tests {
#[test]
fn test_session_rebuild_shares_forges() {
let core = KernelCore::new(KernelConfig::minimal());
let (i1, _) = core.string_forge().intern("hello");
let (i1, _) = core.perm_interner().intern("hello");
let _s2 = KernelSession::new(&core);
let (i2, _) = core.string_forge().intern("hello");
let (i2, _) = core.perm_interner().intern("hello");
assert_eq!(i1, i2);
}
+34
View File
@@ -0,0 +1,34 @@
#[macro_export]
macro_rules! kernel_error {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::ERROR, $($arg)*)
};
}
#[macro_export]
macro_rules! kernel_warn {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::WARN, $($arg)*)
};
}
#[macro_export]
macro_rules! kernel_info {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::INFO, $($arg)*)
};
}
#[macro_export]
macro_rules! kernel_debug {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::DEBUG, $($arg)*)
};
}
#[macro_export]
macro_rules! kernel_trace {
($($arg:tt)*) => {
oxide_log::__log_event!("oxide::kernel", oxide_log::tracing::Level::TRACE, $($arg)*)
};
}
+1 -1
View File
@@ -3,7 +3,7 @@
pub mod builtin;
pub mod code_forge;
pub mod kernel;
pub mod logging;
pub mod kernel_log;
pub mod prop_forge;
pub mod shape_forge;
pub mod string_forge;
-235
View File
@@ -1,235 +0,0 @@
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Once;
use tracing_subscriber::EnvFilter;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogLevel {
Off = 0,
Error = 1,
Warn = 2,
Info = 3,
Debug = 4,
Trace = 5,
}
#[repr(usize)]
#[derive(Clone, Copy, Debug)]
pub enum Subsystem {
Vm = 0,
Ic = 1,
Kernel = 2,
Builtins = 3,
}
pub const SUBSYSTEM_COUNT: usize = 4;
pub struct SubsystemLevel(AtomicU8);
impl SubsystemLevel {
pub const fn new() -> Self {
Self(AtomicU8::new(LogLevel::Off as u8))
}
pub fn set(&self, level: LogLevel) {
self.0.store(level as u8, Ordering::Relaxed);
}
pub fn get(&self) -> LogLevel {
match self.0.load(Ordering::Relaxed) {
1 => LogLevel::Error,
2 => LogLevel::Warn,
3 => LogLevel::Info,
4 => LogLevel::Debug,
5 => LogLevel::Trace,
_ => LogLevel::Off,
}
}
pub fn is_enabled(&self, level: LogLevel) -> bool {
self.0.load(Ordering::Relaxed) >= level as u8
}
}
impl Default for SubsystemLevel {
fn default() -> Self {
Self::new()
}
}
pub static VM_LEVEL: SubsystemLevel = SubsystemLevel::new();
pub static IC_LEVEL: SubsystemLevel = SubsystemLevel::new();
pub static KERNEL_LEVEL: SubsystemLevel = SubsystemLevel::new();
pub static BUILTINS_LEVEL: SubsystemLevel = SubsystemLevel::new();
pub fn apply_log_levels(levels: &[LogLevel; SUBSYSTEM_COUNT]) {
VM_LEVEL.set(levels[Subsystem::Vm as usize]);
IC_LEVEL.set(levels[Subsystem::Ic as usize]);
KERNEL_LEVEL.set(levels[Subsystem::Kernel as usize]);
BUILTINS_LEVEL.set(levels[Subsystem::Builtins as usize]);
apply_env_log_levels();
}
pub fn init_logging(log_levels: &[LogLevel; SUBSYSTEM_COUNT]) {
static LOGGING_INIT: Once = Once::new();
LOGGING_INIT.call_once(|| {
let filter = EnvFilter::try_from_env("OXIDE_LOG").unwrap_or_else(|_| EnvFilter::new("oxide=off"));
let subscriber = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_ansi(false)
.without_time()
.compact()
.finish();
let _ = tracing::subscriber::set_global_default(subscriber);
});
apply_log_levels(log_levels);
}
fn apply_env_log_levels() {
let Ok(spec) = std::env::var("OXIDE_LOG") else {
return;
};
for directive in spec.split(',') {
let Some((target, level)) = directive.split_once('=') else {
continue;
};
let Some(level) = parse_level(level.trim()) else {
continue;
};
match target.trim() {
"oxide" => {
VM_LEVEL.set(level);
IC_LEVEL.set(level);
KERNEL_LEVEL.set(level);
BUILTINS_LEVEL.set(level);
}
"oxide::vm" => VM_LEVEL.set(level),
"oxide::ic" => IC_LEVEL.set(level),
"oxide::kernel" => KERNEL_LEVEL.set(level),
"oxide::builtins" => BUILTINS_LEVEL.set(level),
_ => {}
}
}
}
fn parse_level(level: &str) -> Option<LogLevel> {
if level.eq_ignore_ascii_case("off") {
Some(LogLevel::Off)
} else if level.eq_ignore_ascii_case("error") {
Some(LogLevel::Error)
} else if level.eq_ignore_ascii_case("warn") {
Some(LogLevel::Warn)
} else if level.eq_ignore_ascii_case("info") {
Some(LogLevel::Info)
} else if level.eq_ignore_ascii_case("debug") {
Some(LogLevel::Debug)
} else if level.eq_ignore_ascii_case("trace") {
Some(LogLevel::Trace)
} else {
None
}
}
#[macro_export]
macro_rules! vm_debug {
($($arg:tt)*) => {
if $crate::logging::VM_LEVEL.is_enabled($crate::logging::LogLevel::Debug) {
tracing::event!(target: "oxide::vm", tracing::Level::DEBUG, $($arg)*);
}
};
}
#[macro_export]
macro_rules! vm_trace {
($($arg:tt)*) => {
if $crate::logging::VM_LEVEL.is_enabled($crate::logging::LogLevel::Trace) {
tracing::event!(target: "oxide::vm", tracing::Level::TRACE, $($arg)*);
}
};
}
#[macro_export]
macro_rules! ic_debug {
($($arg:tt)*) => {
if $crate::logging::IC_LEVEL.is_enabled($crate::logging::LogLevel::Debug) {
tracing::event!(target: "oxide::ic", tracing::Level::DEBUG, $($arg)*);
}
};
}
#[macro_export]
macro_rules! ic_trace {
($($arg:tt)*) => {
if $crate::logging::IC_LEVEL.is_enabled($crate::logging::LogLevel::Trace) {
tracing::event!(target: "oxide::ic", tracing::Level::TRACE, $($arg)*);
}
};
}
#[macro_export]
macro_rules! kernel_info {
($($arg:tt)*) => {
if $crate::logging::KERNEL_LEVEL.is_enabled($crate::logging::LogLevel::Info) {
tracing::event!(target: "oxide::kernel", tracing::Level::INFO, $($arg)*);
}
};
}
#[macro_export]
macro_rules! kernel_debug {
($($arg:tt)*) => {
if $crate::logging::KERNEL_LEVEL.is_enabled($crate::logging::LogLevel::Debug) {
tracing::event!(target: "oxide::kernel", tracing::Level::DEBUG, $($arg)*);
}
};
}
#[macro_export]
macro_rules! kernel_trace {
($($arg:tt)*) => {
if $crate::logging::KERNEL_LEVEL.is_enabled($crate::logging::LogLevel::Trace) {
tracing::event!(target: "oxide::kernel", tracing::Level::TRACE, $($arg)*);
}
};
}
#[macro_export]
macro_rules! builtins_debug {
($($arg:tt)*) => {
if $crate::logging::BUILTINS_LEVEL.is_enabled($crate::logging::LogLevel::Debug) {
tracing::event!(target: "oxide::builtins", tracing::Level::DEBUG, $($arg)*);
}
};
}
#[macro_export]
macro_rules! builtins_trace {
($($arg:tt)*) => {
if $crate::logging::BUILTINS_LEVEL.is_enabled($crate::logging::LogLevel::Trace) {
tracing::event!(target: "oxide::builtins", tracing::Level::TRACE, $($arg)*);
}
};
}
#[cfg(test)]
mod tests {
use super::{LogLevel, SubsystemLevel};
#[test]
fn log_level_ordinals_match_gate_contract() {
assert_eq!(LogLevel::Off as u8, 0);
assert_eq!(LogLevel::Trace as u8, 5);
}
#[test]
fn subsystem_level_defaults_off() {
let level = SubsystemLevel::new();
assert_eq!(level.get(), LogLevel::Off);
assert!(!level.is_enabled(LogLevel::Error));
}
}
+73 -2
View File
@@ -1,9 +1,10 @@
use std::sync::atomic::{AtomicU32, Ordering};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, RwLock};
use dashmap::DashMap;
use crate::kernel_debug;
use crate::{kernel_debug, kernel_trace};
pub type ShapeId = u32;
pub type StringIndex = u32;
@@ -29,6 +30,8 @@ pub struct ShapeForge {
shapes: RwLock<Vec<Option<Arc<Shape>>>>,
transitions: DashMap<u64, ShapeId>,
next_id: AtomicU32,
overflow_map: RwLock<HashMap<u64, ShapeId>>,
overflow_active: AtomicBool,
}
impl ShapeForge {
@@ -37,6 +40,8 @@ impl ShapeForge {
shapes: RwLock::new(Vec::with_capacity(256)),
transitions: DashMap::with_capacity_and_shard_amount(256, 16),
next_id: AtomicU32::new(2),
overflow_map: RwLock::new(HashMap::new()),
overflow_active: AtomicBool::new(false),
};
{
let mut shapes = forge.shapes.write().unwrap();
@@ -78,12 +83,33 @@ impl ShapeForge {
pub fn make_shape(&self, parent_id: ShapeId, prop_name: StringIndex) -> ShapeId {
let key = Self::pack_key(parent_id, prop_name);
kernel_trace!("ShapeForge lookup: parent={} prop={}", parent_id, prop_name);
if let Some(entry) = self.transitions.get(&key) {
kernel_debug!("ShapeForge transition cached parent={} prop={} -> id={}", parent_id, prop_name, *entry);
return *entry;
}
if self.overflow_active.load(Ordering::Relaxed) {
if let Some(id) = self.overflow_map.read().unwrap().get(&key) {
kernel_debug!("ShapeForge overflow cached parent={} prop={} -> id={}", parent_id, prop_name, *id);
return *id;
}
}
let new_id = self.next_id.fetch_add(1, Ordering::Relaxed);
debug_assert!(new_id <= 0x00FF_FFFF, "shape_id {} exceeds 24-bit limit (16M)", new_id,);
if new_id > 0x00FF_FFFF {
self.overflow_active.store(true, Ordering::Relaxed);
let mut map = self.overflow_map.write().unwrap();
if let Some(id) = map.get(&key) {
return *id;
}
map.insert(key, new_id);
kernel_debug!("ShapeForge overflow parent={} prop={} -> id={}", parent_id, prop_name, new_id);
return new_id;
}
let shape = Arc::new(Shape {
id: new_id,
@@ -324,4 +350,49 @@ mod tests {
assert!(forge.has_property(shape, private_key));
assert_eq!(forge.lookup_position(shape, private_key), Some(0));
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "exceeds 24-bit"))]
fn overflow_debug_assert_fires() {
let forge = ShapeForge::new();
forge.next_id.store(0x0100_0000, Ordering::Relaxed);
let _ = forge.make_shape(EMPTY_SHAPE_ID, 1_000_400);
}
#[test]
fn overflow_fallback_works() {
let forge = ShapeForge::new();
let key = ShapeForge::pack_key(EMPTY_SHAPE_ID, 1_000_500);
forge.overflow_active.store(true, Ordering::Relaxed);
forge.overflow_map.write().unwrap().insert(key, 0xDEAD);
let id = forge.make_shape(EMPTY_SHAPE_ID, 1_000_500);
assert_eq!(id, 0xDEAD);
}
#[test]
fn overflow_concurrent_same_key() {
let forge = Arc::new(ShapeForge::new());
let key: StringIndex = 1_000_600;
forge.overflow_active.store(true, Ordering::Relaxed);
let barrier = Arc::new(Barrier::new(2));
let f1 = Arc::clone(&forge);
let b1 = Arc::clone(&barrier);
let h1 = std::thread::spawn(move || {
b1.wait();
f1.make_shape(EMPTY_SHAPE_ID, key)
});
let f2 = Arc::clone(&forge);
let b2 = Arc::clone(&barrier);
let h2 = std::thread::spawn(move || {
b2.wait();
f2.make_shape(EMPTY_SHAPE_ID, key)
});
let id1 = h1.join().unwrap();
let id2 = h2.join().unwrap();
assert_eq!(id1, id2);
assert!(id1 > 0);
}
}
+143 -148
View File
@@ -1,142 +1,141 @@
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::RwLock;
use hashbrown::hash_map::DefaultHashBuilder as FxBuildHasher;
use hashbrown::HashMap;
use dashmap::DashMap;
use oxide_types::object::JsString;
use rustc_hash::FxHasher;
use crate::{kernel_debug, kernel_trace};
pub fn hash16(s: &str) -> u16 {
let mut h = rustc_hash::FxHasher::default();
/// Full 64-bit content hash. Replaces the old 16-bit `hash16` so the interner's
/// hash→candidate map has negligible collision risk.
fn hash64(s: &str) -> u64 {
let mut h = FxHasher::default();
s.hash(&mut h);
(h.finish() >> 48) as u16
h.finish()
}
pub struct StringEntry {
pub data: String,
pub ref_count: AtomicU32,
pub hash: u16,
/// One interned key. `data` is a leaked `&'static str` — permanent keys are never
/// freed (append-only by design), so the leak is the storage model, not a bug.
#[derive(Clone, Copy)]
struct PermEntry {
data: &'static str,
hash: u64,
}
struct StringForgeInner {
map: HashMap<String, u32, FxBuildHasher>,
entries: Vec<StringEntry>,
}
/// Global string interning pool shared by all VMs.
/// Append-only, never-move, lock-free-read key interner shared by all VMs.
///
/// Concurrency model:
/// `StringForge` keeps a single `RwLock` around the interner state.
/// - `lookup()` takes a shared read lock and scales across readers.
/// - `intern()` uses a read-check then write-check pattern to keep hot-path hits cheap.
/// - New string insertion is serialized, which matches the workload here: most strings
/// are interned early and later accesses are dominated by reads.
pub struct StringForge {
inner: RwLock<StringForgeInner>,
/// Replaces the old ref-counted `StringForge` (and its buggy `maybe_sweep`
/// renumber path). Keys (property names, method names) are interned once and
/// addressed by a stable `u32` id that the shape/IC system keys on. Runtime
/// string *values* are no longer interned here — they are heap `JsString`
/// pointers (see `oxide_vm::Vm::new_string`).
///
/// Concurrency:
/// - `hash_map` (`DashMap`) gives sharded, lock-free reads of the hash→candidates
/// mapping on the hot intern path.
/// - `entries` sits behind a short `RwLock`; reads copy out a `&'static str`
/// (Copy) so the borrow outlives the guard.
pub struct PermInterner {
hash_map: DashMap<u64, Vec<u32>>,
entries: RwLock<Vec<PermEntry>>,
/// Lazily materialized permanent `JsString` values, indexed by entry id.
/// Used when a permanent key must also exist as a JS string *value*
/// (e.g. method names exposed as property values at builtin init).
permanent_strings: RwLock<Vec<Option<Box<JsString>>>>,
}
impl StringForge {
impl PermInterner {
pub fn new() -> Self {
Self {
inner: RwLock::new(StringForgeInner {
map: HashMap::with_hasher(FxBuildHasher::default()),
entries: Vec::new(),
}),
hash_map: DashMap::new(),
entries: RwLock::new(Vec::new()),
permanent_strings: RwLock::new(Vec::new()),
}
}
pub fn intern(&self, s: &str) -> (u32, u16) {
{
let inner = self.inner.read().unwrap();
if let Some(&idx) = inner.map.get(s) {
let entry = &inner.entries[idx as usize];
entry.ref_count.fetch_add(1, Ordering::Release);
kernel_trace!("StringForge intern hit idx={}", idx);
return (idx, entry.hash);
/// Intern a key. Returns its stable id and full 64-bit hash. Each unique
/// string is stored exactly once (single allocation, no double `to_string`).
pub fn intern(&self, s: &str) -> (u32, u64) {
let hash = hash64(s);
// Fast path: lock-free candidate read, short entries read-lock.
if let Some(candidates) = self.hash_map.get(&hash) {
let entries = self.entries.read().unwrap();
for &id in candidates.iter() {
if entries[id as usize].data == s {
kernel_trace!("PermInterner intern hit id={}", id);
return (id, hash);
}
}
}
let mut inner = self.inner.write().unwrap();
if let Some(&idx) = inner.map.get(s) {
let entry = &inner.entries[idx as usize];
entry.ref_count.fetch_add(1, Ordering::Release);
kernel_trace!("StringForge intern hit idx={}", idx);
return (idx, entry.hash);
}
let h = hash16(s);
let idx = inner.entries.len() as u32;
inner.map.insert(s.to_string(), idx);
inner.entries.push(StringEntry {
data: s.to_string(),
ref_count: AtomicU32::new(1),
hash: h,
});
kernel_debug!("StringForge intern new idx={} len={}", idx, s.len());
(idx, h)
}
pub fn lookup(&self, idx: u32) -> Option<String> {
let inner = self.inner.read().unwrap();
inner.entries.get(idx as usize).map(|e| e.data.clone())
}
pub fn get_hash(&self, idx: u32) -> Option<u16> {
let inner = self.inner.read().unwrap();
inner.entries.get(idx as usize).map(|e| e.hash)
}
pub fn decref(&self, idx: u32) {
let inner = self.inner.read().unwrap();
if let Some(entry) = inner.entries.get(idx as usize) {
entry.ref_count.fetch_sub(1, Ordering::Release);
}
}
pub fn maybe_sweep(&self, max_dead: Option<usize>) {
let threshold = match max_dead {
Some(t) => t,
None => return,
};
let mut inner = self.inner.write().unwrap();
let dead_count = inner
.entries
.iter()
.filter(|e| e.ref_count.load(Ordering::Acquire) == 0)
.count();
if dead_count < threshold {
return;
}
let mut new_map: HashMap<String, u32, FxBuildHasher> = HashMap::with_hasher(FxBuildHasher::default());
let mut new_entries = Vec::with_capacity(inner.entries.len() - dead_count);
for entry in inner.entries.drain(..) {
let rc = entry.ref_count.load(Ordering::Acquire);
if rc > 0 {
let new_idx = new_entries.len() as u32;
new_map.insert(entry.data.clone(), new_idx);
new_entries.push(entry);
// Slow path: append under write lock, re-checking for a racing insert.
let mut entries = self.entries.write().unwrap();
if let Some(candidates) = self.hash_map.get(&hash) {
for &id in candidates.iter() {
if entries[id as usize].data == s {
return (id, hash);
}
}
}
inner.map = new_map;
inner.entries = new_entries;
let id = entries.len() as u32;
let leaked: &'static str = Box::leak(s.to_string().into_boxed_str());
entries.push(PermEntry { data: leaked, hash });
let entry_count = entries.len();
drop(entries);
self.hash_map.entry(hash).or_default().push(id);
kernel_debug!("PermInterner intern new id={} len={}", id, s.len());
if entry_count % 1000 == 0 {
kernel_debug!("PermInterner stats: {} strings", entry_count);
}
(id, hash)
}
pub fn len(&self) -> usize {
self.inner.read().unwrap().entries.len()
/// Resolve a key id to its text with zero clone. The returned `&'static str`
/// is valid for the program lifetime (keys are never freed).
pub fn lookup(&self, id: u32) -> Option<&'static str> {
let entries = self.entries.read().unwrap();
entries.get(id as usize).map(|e| e.data)
}
/// Full 64-bit hash for a key id.
pub fn get_hash(&self, id: u32) -> Option<u64> {
let entries = self.entries.read().unwrap();
entries.get(id as usize).map(|e| e.hash)
}
/// Total number of unique keys interned.
pub fn entry_count(&self) -> u32 {
self.entries.read().unwrap().len() as u32
}
pub fn is_empty(&self) -> bool {
self.len() == 0
self.entry_count() == 0
}
/// Materialize (once) and return a stable pointer to a permanent `JsString`
/// for the given key id. The `JsString` lives for the program lifetime.
pub fn string_ptr(&self, id: u32) -> *const JsString {
{
let perm = self.permanent_strings.read().unwrap();
if let Some(Some(boxed)) = perm.get(id as usize) {
return boxed.as_ref() as *const JsString;
}
}
let text = self.lookup(id).unwrap_or("");
let mut perm = self.permanent_strings.write().unwrap();
if perm.len() <= id as usize {
perm.resize_with(id as usize + 1, || None);
}
if perm[id as usize].is_none() {
perm[id as usize] = Some(Box::new(JsString::new(text.to_string())));
}
perm[id as usize].as_ref().unwrap().as_ref() as *const JsString
}
}
impl Default for StringForge {
impl Default for PermInterner {
fn default() -> Self {
Self::new()
}
@@ -147,67 +146,63 @@ mod tests {
use super::*;
#[test]
fn test_hash16_same() {
assert_eq!(hash16("hello"), hash16("hello"));
}
#[test]
fn test_hash16_different() {
assert_ne!(hash16("hello"), hash16("world"));
}
#[test]
fn test_intern_dedup() {
let string_forge = StringForge::new();
let (i1, h1) = string_forge.intern("abc");
let (i2, h2) = string_forge.intern("abc");
fn intern_dedup() {
let interner = PermInterner::new();
let (i1, h1) = interner.intern("abc");
let (i2, h2) = interner.intern("abc");
assert_eq!(i1, i2);
assert_eq!(h1, h2);
}
#[test]
fn test_intern_different() {
let string_forge = StringForge::new();
let (i1, _) = string_forge.intern("x");
let (i2, _) = string_forge.intern("y");
fn intern_different() {
let interner = PermInterner::new();
let (i1, _) = interner.intern("x");
let (i2, _) = interner.intern("y");
assert_ne!(i1, i2);
}
#[test]
fn test_lookup_found() {
let string_forge = StringForge::new();
let (idx, _) = string_forge.intern("hello");
assert_eq!(string_forge.lookup(idx), Some("hello".to_string()));
fn lookup_zero_clone() {
let interner = PermInterner::new();
let (id, _) = interner.intern("hello");
assert_eq!(interner.lookup(id), Some("hello"));
}
#[test]
fn test_lookup_not_found() {
let string_forge = StringForge::new();
assert_eq!(string_forge.lookup(99999), None);
fn lookup_not_found() {
let interner = PermInterner::new();
assert_eq!(interner.lookup(99999), None);
}
#[test]
fn test_ref_count_and_decref() {
let string_forge = StringForge::new();
let (idx, _) = string_forge.intern("s");
assert_eq!(string_forge.lookup(idx), Some("s".to_string()));
string_forge.decref(idx);
assert_eq!(string_forge.lookup(idx), Some("s".to_string()));
fn entry_count_monotonic() {
let interner = PermInterner::new();
assert_eq!(interner.entry_count(), 0);
interner.intern("a");
interner.intern("b");
interner.intern("a");
assert_eq!(interner.entry_count(), 2);
}
#[test]
fn test_maybe_sweep_noop_when_none() {
let string_forge = StringForge::new();
string_forge.intern("live");
string_forge.maybe_sweep(None);
assert!(string_forge.lookup(0).is_some());
fn many_unique_no_collision() {
let interner = PermInterner::new();
for i in 0..10_000 {
let s = format!("key{i}");
let (id, _) = interner.intern(&s);
assert_eq!(interner.lookup(id), Some(&*Box::leak(s.into_boxed_str())));
}
assert_eq!(interner.entry_count(), 10_000);
}
#[test]
fn test_maybe_sweep_skip_below_threshold() {
let string_forge = StringForge::new();
string_forge.intern("live");
string_forge.maybe_sweep(Some(100));
assert_eq!(string_forge.lookup(0), Some("live".to_string()));
fn string_ptr_roundtrip() {
let interner = PermInterner::new();
let (id, _) = interner.intern("perm");
let ptr = interner.string_ptr(id);
assert_eq!(unsafe { (*ptr).as_str() }, "perm");
// Second call returns the same stable pointer (materialized once).
assert_eq!(interner.string_ptr(id), ptr);
}
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "oxide_log"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "OxideJS - Structured logging subsystem (tracing-based)"
[dependencies]
tracing = { workspace = true, features = ["max_level_trace", "release_max_level_info"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "registry"] }
tracing-appender = "0.2.5"
+90
View File
@@ -0,0 +1,90 @@
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use tracing::Subscriber;
use tracing_subscriber::layer::Filter;
use crate::level::Level;
use crate::subsystem::{SubsystemId, SUBSYSTEM_COUNT};
#[derive(Clone)]
pub struct SubsystemFilter {
levels: Arc<[AtomicU8; SUBSYSTEM_COUNT]>,
}
impl SubsystemFilter {
pub fn new() -> Self {
Self {
levels: Arc::new([AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0), AtomicU8::new(0)]),
}
}
pub fn set_level(&self, id: SubsystemId, level: Level) {
self.levels[id as usize].store(level as u8, Ordering::Relaxed);
}
pub fn get_level(&self, id: SubsystemId) -> Level {
let raw = self.levels[id as usize].load(Ordering::Relaxed);
match raw {
1 => Level::Error,
2 => Level::Warn,
3 => Level::Info,
4 => Level::Debug,
5 => Level::Trace,
_ => Level::Off,
}
}
}
impl SubsystemFilter {
fn is_enabled_for(&self, target: &str, tracing_level: &tracing::Level) -> bool {
let sid = subsystem_for_target(target);
let max = self.get_level(sid);
match max {
Level::Off => false,
Level::Error => *tracing_level <= tracing::Level::ERROR,
Level::Warn => *tracing_level <= tracing::Level::WARN,
Level::Info => *tracing_level <= tracing::Level::INFO,
Level::Debug => *tracing_level <= tracing::Level::DEBUG,
Level::Trace => true,
}
}
}
impl Default for SubsystemFilter {
fn default() -> Self {
Self::new()
}
}
impl<S: Subscriber> Filter<S> for SubsystemFilter {
fn enabled(&self, meta: &tracing::Metadata<'_>, _cx: &tracing_subscriber::layer::Context<'_, S>) -> bool {
self.is_enabled_for(meta.target(), meta.level())
}
}
fn subsystem_for_target(target: &str) -> SubsystemId {
if let Some(rest) = target.strip_prefix("oxide::") {
match rest {
"vm" => SubsystemId::Vm,
"ic" => SubsystemId::Ic,
"kernel" => SubsystemId::Kernel,
"builtins" => SubsystemId::Builtins,
_ => {
if rest.starts_with("vm::") {
SubsystemId::Vm
} else if rest.starts_with("ic::") {
SubsystemId::Ic
} else if rest.starts_with("kernel::") {
SubsystemId::Kernel
} else if rest.starts_with("builtins::") {
SubsystemId::Builtins
} else {
SubsystemId::Kernel
}
}
}
} else {
SubsystemId::Kernel
}
}
+109
View File
@@ -0,0 +1,109 @@
use std::io;
use std::path::PathBuf;
use std::sync::{Once, OnceLock};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::Layer;
use crate::filter::SubsystemFilter;
use crate::level::Level;
use crate::subsystem::{self, SubsystemId, SUBSYSTEM_COUNT};
#[derive(Debug, Clone)]
pub enum Output {
Stderr,
Stdout,
File(PathBuf),
}
pub struct LogConfig {
pub output: Output,
pub levels: [Level; SUBSYSTEM_COUNT],
}
impl Default for LogConfig {
fn default() -> Self {
Self {
output: Output::Stderr,
levels: [Level::Off; SUBSYSTEM_COUNT],
}
}
}
static INIT: Once = Once::new();
static SUBSYSTEM: OnceLock<SubsystemFilter> = OnceLock::new();
static FILE_GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new();
pub fn get_subsystem() -> &'static SubsystemFilter {
SUBSYSTEM.get().expect("oxide_log::init() must be called before logging")
}
pub fn set_level(id: SubsystemId, level: Level) {
get_subsystem().set_level(id, level);
}
pub fn init(config: &LogConfig) {
INIT.call_once(|| {
let filter = SubsystemFilter::new();
for (i, level) in config.levels.iter().enumerate() {
let id = match i {
0 => SubsystemId::Vm,
1 => SubsystemId::Ic,
2 => SubsystemId::Kernel,
3 => SubsystemId::Builtins,
_ => continue,
};
filter.set_level(id, *level);
}
SUBSYSTEM.set(filter.clone()).ok();
let env_filter = EnvFilter::try_from_env("OXIDE_LOG").unwrap_or_else(|_| EnvFilter::new("oxide=off"));
subsystem::apply_env_levels();
let stderr_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(io::stderr)
.with_ansi(false)
.without_time()
.compact();
match &config.output {
Output::Stderr => {
let subscriber = tracing_subscriber::Registry::default()
.with(env_filter)
.with(stderr_layer.with_filter(filter));
subscriber.init();
}
Output::Stdout => {
let stdout_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(io::stdout)
.with_ansi(false)
.without_time()
.compact();
let subscriber = tracing_subscriber::Registry::default()
.with(env_filter)
.with(stdout_layer.with_filter(filter));
subscriber.init();
}
Output::File(dir) => {
let file_appender = tracing_appender::rolling::daily(dir, "oxide.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
FILE_GUARD.set(guard).ok();
let file_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(non_blocking)
.with_ansi(false)
.without_time()
.compact();
let subscriber = tracing_subscriber::Registry::default()
.with(env_filter)
.with(stderr_layer.with_filter(filter.clone()))
.with(file_layer.with_filter(filter));
subscriber.init();
}
}
});
}
+22
View File
@@ -0,0 +1,22 @@
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Off = 0,
Error = 1,
Warn = 2,
Info = 3,
Debug = 4,
Trace = 5,
}
impl Level {
pub const fn as_tracing_level(self) -> tracing::Level {
match self {
Level::Off | Level::Error => tracing::Level::ERROR,
Level::Warn => tracing::Level::WARN,
Level::Info => tracing::Level::INFO,
Level::Debug => tracing::Level::DEBUG,
Level::Trace => tracing::Level::TRACE,
}
}
}
+13
View File
@@ -0,0 +1,13 @@
pub mod filter;
pub mod init;
pub mod level;
pub mod macros;
pub mod subsystem;
pub use filter::SubsystemFilter;
pub use init::{init, set_level, LogConfig, Output};
pub use level::Level;
pub use subsystem::{SubsystemId, SUBSYSTEM_COUNT};
#[doc(hidden)]
pub use tracing;

Some files were not shown because too many files have changed in this diff Show More