Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/7382-node-vm-node26-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Fixed

- **Completed Node.js 26.5.0 parity for `node:vm`.** Contexts now preserve
sandbox, lexical, descriptor, strict-write, code-generation, microtask, and
cross-realm behavior; `Script`, `compileFunction`, cached-data metadata, and
experimental VM modules now match the Node oracle across the full 64-case
module suite.
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ pub(crate) const NODE_CORE_INSPECTOR_VM_ROWS: &[NativeModSig] = &[
has_receiver: false,
method: "createScript",
class_filter: None,
runtime: "js_vm_create_script",
runtime: "js_vm_create_script_branded",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,13 @@ pub(super) const NODE_MISC_ROWS: &[NativeModSig] = &[
ret: NR_F64,
},
// ========== node:vm ==========
// Minimal contextification surface for APIs that require a vm context
// object but do not execute code inside it yet.
NativeModSig {
module: "vm",
has_receiver: false,
method: "createContext",
class_filter: None,
runtime: "js_vm_create_context",
args: &[NA_F64],
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
// ========== node:repl ==========
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use crate::types::{DOUBLE, I32, I64, VOID};

pub(crate) fn declare_net_http(module: &mut LlModule) {
// ========== node:vm ==========
module.declare_function("js_vm_create_context", DOUBLE, &[DOUBLE]);
module.declare_function("js_vm_create_context", DOUBLE, &[DOUBLE, DOUBLE]);
module.declare_function("js_vm_create_script_branded", DOUBLE, &[DOUBLE, DOUBLE]);
module.declare_function("js_vm_module_call", DOUBLE, &[]);
module.declare_function("js_vm_module_constructor_error", DOUBLE, &[]);

Expand Down
41 changes: 10 additions & 31 deletions crates/perry-hir/src/lower/lower_expr/arm_bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,37 +246,16 @@ pub(crate) fn lower_bin_expr(ctx: &mut LoweringContext, bin: &ast::BinExpr) -> R
}),

// Comparison (treat == same as === for typed code)
ast::BinaryOp::EqEq => {
// Proxy/Reflect fold: `Reflect.getPrototypeOf(x) === <Class>.prototype`
// always true in our model (we don't maintain real prototypes).
// Same fold for `Object.getPrototypeOf(x) === <Class>.prototype`.
if matches!(
&*left,
Expr::ReflectGetPrototypeOf(_) | Expr::ObjectGetPrototypeOf(_)
) && matches!(&*right, Expr::PropertyGet { property, .. } if property == "prototype")
{
return Ok(Expr::Bool(true));
}
Ok(Expr::Compare {
op: CompareOp::LooseEq,
left,
right,
})
}
ast::BinaryOp::EqEqEq => {
if matches!(
&*left,
Expr::ReflectGetPrototypeOf(_) | Expr::ObjectGetPrototypeOf(_)
) && matches!(&*right, Expr::PropertyGet { property, .. } if property == "prototype")
{
return Ok(Expr::Bool(true));
}
Ok(Expr::Compare {
op: CompareOp::Eq,
left,
right,
})
}
ast::BinaryOp::EqEq => Ok(Expr::Compare {
op: CompareOp::LooseEq,
left,
right,
}),
ast::BinaryOp::EqEqEq => Ok(Expr::Compare {
op: CompareOp::Eq,
left,
right,
}),
ast::BinaryOp::NotEq => Ok(Expr::Compare {
op: CompareOp::LooseNe,
left,
Expand Down
93 changes: 87 additions & 6 deletions crates/perry-runtime/src/dyn_eval/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,26 @@ pub(crate) fn throw_syntax_error(message: &str) -> ! {
throw_error_kind(crate::error::ERROR_KIND_SYNTAX_ERROR, message)
}

pub(crate) fn throw_eval_error(message: &str) -> ! {
throw_error_kind(crate::error::ERROR_KIND_EVAL_ERROR, message)
}

fn wasm_codegen_error(api: &str) -> f64 {
let message = format!("{api}(): Wasm code generation disallowed by embedder");
let message = crate::string::js_string_from_bytes(message.as_ptr(), message.len() as u32);
let error = crate::error::js_error_new_with_name_message_bytes(b"CompileError", message);
crate::value::js_nanbox_pointer(error as i64)
}

pub(crate) fn wasm_codegen_rejection(api: &str) -> f64 {
let promise = crate::promise::js_promise_rejected(wasm_codegen_error(api));
crate::value::js_nanbox_pointer(promise as i64)
}

pub(crate) fn throw_wasm_codegen_error(api: &str) -> ! {
crate::exception::js_throw(wasm_codegen_error(api))
}

/// The diagnostic contract of #6559: anything outside the interpreter subset
/// throws a TypeError that NAMES the construct, so gaps met in the wild show
/// up as actionable errors, never as silent miscomputation.
Expand Down Expand Up @@ -214,15 +234,15 @@ pub(crate) fn get_index(base: f64, key: f64) -> f64 {
}

/// `base[key] = value` (also used for `base.name = value` with a string key).
pub(crate) fn set_index(base: f64, key: f64, value: f64) {
crate::value::js_dyn_index_set(base, key, value);
pub(crate) fn set_index(base: f64, key: f64, value: f64, strict: bool) {
crate::proxy::js_put_value_set(base, key, value, base, strict as i32);
}

pub(crate) fn set_member(base: f64, name: &str, value: f64) {
let base_idx = root_push(base);
let value_idx = root_push(value);
let key = make_string(name);
set_index(root_get(base_idx), key, root_get(value_idx));
set_index(root_get(base_idx), key, root_get(value_idx), false);
roots_truncate(base_idx);
}

Expand Down Expand Up @@ -272,9 +292,70 @@ pub(crate) fn construct(callee: f64, args: &[f64]) -> f64 {

// ── globals ────────────────────────────────────────────────────────────────

/// Look `name` up on the real `globalThis` (Math, JSON, Array, isNaN, …).
pub(crate) fn global_lookup(name: &str) -> f64 {
crate::object::js_get_global_this_builtin_value(name.as_ptr(), name.len())
/// Whether the selected global (including its prototype chain) binds `name`.
pub(crate) fn global_has_property(global: f64, name: &str) -> bool {
let global = if crate::proxy::js_proxy_is_proxy(global) != 0 {
crate::proxy::js_proxy_target(global)
} else {
global
};
let global_idx = root_push(global);
let key = make_string(name);
let present = crate::object::js_object_has_property(root_get(global_idx), key);
roots_truncate(global_idx);
truthy(present)
}

/// Look `name` up on the selected global receiver, then on the selected realm's
/// intrinsic-global backing. VM contexts pass distinct values; ordinary
/// dynamic Function calls pass the process global for both.
pub(crate) fn global_lookup(global_this: f64, intrinsics: f64, name: &str) -> f64 {
let lookup_target = if crate::proxy::js_proxy_is_proxy(global_this) != 0 {
crate::proxy::js_proxy_target(global_this)
} else {
global_this
};
if global_has_property(lookup_target, name) {
return get_member(lookup_target, name);
}
let builtin = crate::object::GLOBAL_THIS_BUILTIN_CONSTRUCTORS.contains(&name)
|| crate::object::GLOBAL_THIS_BUILTIN_NAMESPACES.contains(&name)
|| crate::object::GLOBAL_THIS_BUILTIN_FUNCTIONS.contains(&name);
if builtin {
get_member(intrinsics, name)
} else {
undefined()
}
}

/// The selected realm's `<Constructor>.prototype` value.
pub(crate) fn intrinsic_prototype(intrinsics: f64, name: &str) -> f64 {
let intrinsics_idx = root_push(intrinsics);
let constructor = get_member(root_get(intrinsics_idx), name);
let constructor_idx = root_push(constructor);
let prototype = get_member(root_get(constructor_idx), "prototype");
roots_truncate(intrinsics_idx);
prototype
}

/// Link a freshly-created value to the actual prototype of its creation realm.
/// The value is returned through a handle because creating object metadata may
/// collect and move it.
pub(crate) fn attach_intrinsic_prototype(value: f64, intrinsics: f64, name: &str) -> f64 {
let scope = crate::gc::RuntimeHandleScope::new();
let value_handle = scope.root_nanbox_f64(value);
let prototype = intrinsic_prototype(intrinsics, name);
let prototype_handle = scope.root_nanbox_f64(prototype);
let prototype = prototype_handle.get_nanbox_f64();
if !crate::value::JSValue::from_bits(prototype.to_bits()).is_pointer() {
return value_handle.get_nanbox_f64();
}
let value = value_handle.get_nanbox_f64();
let raw = crate::value::js_nanbox_get_pointer(value) as usize;
if raw != 0 {
crate::object::prototype_chain::object_set_static_prototype(raw, prototype.to_bits());
}
value_handle.get_nanbox_f64()
}

// ── operators ──────────────────────────────────────────────────────────────
Expand Down
Loading
Loading