diff --git a/libs/jsparser/src/symbol/builtin.yaml b/libs/jsparser/src/symbol/builtin.yaml index c5ec0a0b4..e7ef8154d 100644 --- a/libs/jsparser/src/symbol/builtin.yaml +++ b/libs/jsparser/src/symbol/builtin.yaml @@ -55,9 +55,12 @@ - [NaN, NAN] - [async, ASYNC] - all +- apply - arguments - assign - at +- bind +- call - cause - charAt - charCodeAt diff --git a/libs/jsruntime/src/backend/bridge.rs b/libs/jsruntime/src/backend/bridge.rs index 387032fc9..6b5c15329 100644 --- a/libs/jsruntime/src/backend/bridge.rs +++ b/libs/jsruntime/src/backend/bridge.rs @@ -370,28 +370,7 @@ pub(crate) extern "C" fn runtime_create_capture( target: *mut Value, ) -> HandleMut { logger::debug!(event = "runtime_create_capture", ?target); - - debug_assert!( - std::alloc::Layout::from_size_align( - std::mem::size_of::(), - std::mem::align_of::() - ) - .is_ok() - ); - // SAFETY: `from_size_align()` always succeeds. - const LAYOUT: std::alloc::Layout = unsafe { - std::alloc::Layout::from_size_align_unchecked( - std::mem::size_of::(), - std::mem::align_of::(), - ) - }; - - runtime.heap.alloc_layout_mut(LAYOUT, move |ptr| { - // SAFETY: `ptr` is a non-null pointer to a `Capture`. - let capture = unsafe { ptr.cast::().as_mut() }; - capture.target = target; - // `capture.escaped` will be filled with an actual value. - }) + runtime.create_capture(target) } pub(crate) extern "C" fn runtime_create_closure( diff --git a/libs/jsruntime/src/builtins/builtin_mod.rs.hbs b/libs/jsruntime/src/builtins/builtin_mod.rs.hbs index 3501acedf..a9b3d292f 100644 --- a/libs/jsruntime/src/builtins/builtin_mod.rs.hbs +++ b/libs/jsruntime/src/builtins/builtin_mod.rs.hbs @@ -3,13 +3,14 @@ // This file was automagically generated with: // template: {{@template}} +logging::define_logger! {"bee::jsruntime::builtins::{{metadata.id}}"} + mod imp; use jsgc::HandleMut; use jsparser::Symbol; use crate::Runtime; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Property; @@ -53,12 +54,14 @@ impl Runtime { slots: &[], prototype: None, }); - let _ = constructor.define_own_property(Symbol::{{symbol}}.into(), Property::data_wxc(Value::Object(func))); + let result = constructor.define_own_property(Symbol::{{symbol}}.into(), Property::data_wxc(Value::Object(func))); + debug_assert!(matches!(result, Ok(true))); {{/if}} {{/each}} // TODO(refactor): bind outside this function - let _ = self.builtins.{{metadata.id}}_prototype.define_own_property(Symbol::CONSTRUCTOR.into(), Property::data_wxc(Value::Object(constructor))); + let result = self.builtins.{{metadata.id}}_prototype.define_own_property(Symbol::CONSTRUCTOR.into(), Property::data_wxc(Value::Object(constructor))); + debug_assert!(matches!(result, Ok(true))); constructor } @@ -85,7 +88,8 @@ impl Runtime { slots: &[], prototype: None, }); - let _ = prototype.define_own_property(Symbol::{{symbol}}.into(), Property::data_wxc(Value::Object(func))); + let result = prototype.define_own_property(Symbol::{{symbol}}.into(), Property::data_wxc(Value::Object(func))); + debug_assert!(matches!(result, Ok(true))); {{/if}} {{/each}} @@ -140,6 +144,9 @@ extern "C" fn {{imp}}( context: &mut CallContext, retv: &mut Value, ) -> Status { + {{#if options.no_adapter}} + imp::{{imp}}(runtime, context, retv) + {{else}} match imp::{{imp}}(runtime, context) { Ok(value) => { *retv = value; @@ -150,6 +157,7 @@ extern "C" fn {{imp}}( Status::Exception } } + {{/if}} } {{/if}} {{/each}} diff --git a/libs/jsruntime/src/builtins/error/imp.rs b/libs/jsruntime/src/builtins/error/imp.rs index 49de1989d..92a2f7d7f 100644 --- a/libs/jsruntime/src/builtins/error/imp.rs +++ b/libs/jsruntime/src/builtins/error/imp.rs @@ -8,13 +8,14 @@ use jsparser::Symbol; use crate::Error; use crate::Runtime; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Property; use crate::types::String; use crate::types::Value; +use super::logger; + //#sec-error-message constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { logger::debug!(event = "error_constructor"); diff --git a/libs/jsruntime/src/builtins/function/README.md b/libs/jsruntime/src/builtins/function/README.md index 634bc632a..ffc9bb5a2 100644 --- a/libs/jsruntime/src/builtins/function/README.md +++ b/libs/jsruntime/src/builtins/function/README.md @@ -1,9 +1,9 @@ # Function * [x] [Function](https://tc39.es/ecma262/#sec-function-p1-p2-pn-body) -* [ ] [Function.prototype.apply](https://tc39.es/ecma262/#sec-function.prototype.apply) -* [ ] [Function.prototype.bind](https://tc39.es/ecma262/#sec-function.prototype.bind) -* [ ] [Function.prototype.call](https://tc39.es/ecma262/#sec-function.prototype.call) +* [x] [Function.prototype.apply](https://tc39.es/ecma262/#sec-function.prototype.apply) +* [x] [Function.prototype.bind](https://tc39.es/ecma262/#sec-function.prototype.bind) +* [x] [Function.prototype.call](https://tc39.es/ecma262/#sec-function.prototype.call) * [ ] [Function.prototype.constructor](https://tc39.es/ecma262/#sec-function.prototype.constructor) -* [ ] [Function.prototype.toString](https://tc39.es/ecma262/#sec-function.prototype.tostring) +* [x] [Function.prototype.toString](https://tc39.es/ecma262/#sec-function.prototype.tostring) * [ ] [Function.prototype \[ %Symbol.hasInstance% \]](https://tc39.es/ecma262/#sec-function.prototype-%symbol.hasinstance%) diff --git a/libs/jsruntime/src/builtins/function/imp.rs b/libs/jsruntime/src/builtins/function/imp.rs index 07af10a31..47b7a9fe4 100644 --- a/libs/jsruntime/src/builtins/function/imp.rs +++ b/libs/jsruntime/src/builtins/function/imp.rs @@ -2,12 +2,32 @@ //$class Function //$inherits object +use jsgc::HandleMut; +use jsparser::Symbol; + use crate::Error; +use crate::LambdaId; use crate::Runtime; -use crate::logger; use crate::types::CallContext; +use crate::types::Closure; +use crate::types::Object; +use crate::types::Status; use crate::types::Value; +use super::logger; + +macro_rules! catch { + ($result:expr; $runtime:expr, $retv:expr) => { + match $result { + Ok(v) => v, + Err(err) => { + *$retv = $runtime.create_exception(err); + return Status::Exception; + } + } + }; +} + //#sec-function-p1-p2-pn-body constructor pub fn constructor( _runtime: &mut Runtime, @@ -16,3 +36,158 @@ pub fn constructor( logger::debug!(event = "function"); runtime_todo!("TODO: Function constructor") } + +//#sec-function.prototype.apply prototype.function { "no_adapter": true } +pub fn function_prototype_apply( + runtime: &mut Runtime, + context: &mut CallContext, + retv: &mut Value, +) -> Status { + logger::debug!(event = "function_prototype_apply"); + let func = catch!(runtime.this_func(context.this()); runtime, retv); + let this = context.arg(0); + match context.arg(1) { + Value::None => unreachable!(), + Value::Undefined | Value::Null => { + // TODO: PrepareForTailCall() + runtime.call(context, func, this, &[], retv) + } + args => { + let args = catch!(runtime.create_vec_from_array_like(args); runtime, retv); + // TODO: PrepareForTailCall() + runtime.call(context, func, this, &args, retv) + } + } +} + +//#sec-function.prototype.bind prototype.function +pub fn function_prototype_bind( + runtime: &mut Runtime, + context: &mut CallContext, +) -> Result { + logger::debug!(event = "function_prototype_bind"); + let target = runtime.this_func(context.this())?; + let this = context.arg(0); + let args = &context.args()[1..]; + let mut bound_func = runtime.bound_function_create(target, this, args)?; + let length = match target.get_own_property(&Symbol::LENGTH.into()) { + Some(prop) => { + let target_length = runtime.value_to_length(prop.value())?; + if target_length > args.len() as u64 { + target_length - args.len() as u64 + } else { + 0 + } + } + None => 0, + }; + debug_assert!(length <= u16::MAX as u64); + runtime.set_function_length(&mut bound_func, length as u16); + let target_name = target + .get_value(&Symbol::NAME.into()) + .unwrap_or(&Value::Undefined); + if let Value::String(name) = target_name { + runtime.set_function_name(&mut bound_func, *name); + } + Ok(Value::Object(bound_func)) +} + +//#sec-function.prototype.call prototype.function { "no_adapter": true } +pub fn function_prototype_call( + runtime: &mut Runtime, + context: &mut CallContext, + retv: &mut Value, +) -> Status { + logger::debug!(event = "function_prototype_call"); + let func = catch!(runtime.this_func(context.this()); runtime, retv); + let this = context.arg(0); + let args = &context.args()[1..]; + // TODO: PrepareForTailCall() + runtime.call(context, func, this, args, retv) +} + +//#sec-function.prototype.tostring prototype.function +pub fn function_prototype_to_string( + _runtime: &mut Runtime, + _context: &mut CallContext, +) -> Result { + logger::debug!(event = "function_prototype_to_string"); + runtime_todo!("TODO: Function.prototype.toString()") +} + +impl Runtime { + fn this_func(&mut self, this: &Value) -> Result, Error> { + match this { + Value::None => unreachable!(), + Value::Object(v) if v.is_callable() => Ok(*v), + _ => type_error!(), + } + } + + // #sec-boundfunctioncreate + fn bound_function_create( + &mut self, + func: HandleMut, + this: &Value, + args: &[Value], + ) -> Result, Error> { + extern "C" fn bound_function( + runtime: &mut Runtime, + context: &mut CallContext, + retv: &mut Value, + ) -> Status { + let closure = context.closure(); + let (func, this, mut args) = closure.get_bound_function_params(); + args.extend_from_slice(context.args()); + // TODO(feat): [[Construct]], newTarget + runtime.call(context, func, this, &args, retv) + } + + let prototype = func.prototype(); + let mut obj = self.create_object(); + if let Some(prototype) = prototype { + obj.set_prototype(prototype); + } + + let num_captures = 2 + args.len() as u16; + let mut closure = self.create_closure(bound_function::, LambdaId::HOST, num_captures); + + let mut func = Value::Object(func); + let mut capture = self.create_capture(&mut func as *mut Value); + capture.escape(); + closure.put_capture(0, capture); + + let mut this = this.clone(); + let mut capture = self.create_capture(&mut this as *mut Value); + capture.escape(); + closure.put_capture(1, capture); + + for (i, arg) in args.iter().enumerate() { + let mut arg = arg.clone(); + let mut capture = self.create_capture(&mut arg as *mut Value); + capture.escape(); + closure.put_capture(2 + i, capture); + } + + obj.set_closure(closure); + + Ok(obj) + } +} + +impl Closure { + fn get_bound_function_params(&self) -> (HandleMut, &Value, Vec) { + let captures = self.captures(); + debug_assert!(captures.len() >= 2); + let func = match captures.first().expect("[[BoundTargetFunction]]").value() { + Value::Object(v) => *v, + _ => unreachable!(), + }; + let this = captures.get(1).expect("[[BoundThis]]").value(); + let args: Vec = captures[2..] + .iter() + .map(|capture| capture.value().clone()) + .collect(); + (func, this, args) + } +} diff --git a/libs/jsruntime/src/builtins/global/imp.rs b/libs/jsruntime/src/builtins/global/imp.rs index 3f21f92eb..62a1c546c 100644 --- a/libs/jsruntime/src/builtins/global/imp.rs +++ b/libs/jsruntime/src/builtins/global/imp.rs @@ -114,7 +114,7 @@ pub fn define_function_constructor(runtime: &mut Runtime) { //#_internalerror global.constructor { "name": "InternalError" } pub fn define_internal_error_constructor(runtime: &mut Runtime) { let constructor = runtime.create_internal_error_constructor(); - runtime.define_constructor(Symbol::FUNCTION, constructor); + runtime.define_constructor(Symbol::INTERNAL_ERROR, constructor); } //#sec-constructor-properties-of-the-global-object-object global.constructor diff --git a/libs/jsruntime/src/builtins/global/mod.rs.hbs b/libs/jsruntime/src/builtins/global/mod.rs.hbs index cace26260..8e73db708 100644 --- a/libs/jsruntime/src/builtins/global/mod.rs.hbs +++ b/libs/jsruntime/src/builtins/global/mod.rs.hbs @@ -3,13 +3,14 @@ // This file was automagically generated with: // template: {{@template}} +logging::define_logger! {"bee::jsruntime::builtins::global"} + mod imp; use jsgc::HandleMut; use jsparser::Symbol; use crate::Runtime; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Property; diff --git a/libs/jsruntime/src/builtins/imp.js b/libs/jsruntime/src/builtins/imp.js index aaaef633c..0c113dc22 100644 --- a/libs/jsruntime/src/builtins/imp.js +++ b/libs/jsruntime/src/builtins/imp.js @@ -112,7 +112,7 @@ function dataStream(impRs) { log.error(`Incorrect ID line: ${line}`); Deno.exit(1); } - let options = undefined; + let options = {}; if (parts.length > 2) { options = JSON.parse(parts.slice(2).join(' ')); } diff --git a/libs/jsruntime/src/builtins/mod.rs b/libs/jsruntime/src/builtins/mod.rs index 1e25900a7..19ce83ef3 100644 --- a/libs/jsruntime/src/builtins/mod.rs +++ b/libs/jsruntime/src/builtins/mod.rs @@ -1,3 +1,5 @@ +logging::define_logger! {"bee::jsruntime::builtins"} + mod aggregate_error; mod error; mod eval_error; @@ -22,7 +24,6 @@ use crate::Error; use crate::ErrorKind; use crate::Runtime; use crate::lambda::LambdaId; -use crate::logger; use crate::types::Lambda; use crate::types::Object; use crate::types::Property; @@ -128,10 +129,11 @@ impl Runtime { func.set_closure(closure); if let Some(prototype) = params.prototype { func.set_constructor(); - let _ = func.define_own_property( + let result = func.define_own_property( Symbol::PROTOTYPE.into(), Property::data_xxx(Value::Object(prototype)), ); + debug_assert!(matches!(result, Ok(true))); } self.set_function_length(&mut func, params.length); // TODO: prefix @@ -141,16 +143,18 @@ impl Runtime { // 10.2.9 SetFunctionName ( F, name [ , prefix ] ) fn set_function_name(&mut self, func: &mut HandleMut, name: Handle) { - let _ = + let result = func.define_own_property(Symbol::NAME.into(), Property::data_xxc(Value::String(name))); + debug_assert!(matches!(result, Ok(true))); } // 10.2.10 SetFunctionLength ( F, length ) fn set_function_length(&mut self, func: &mut HandleMut, length: u16) { - let _ = func.define_own_property( + let result = func.define_own_property( Symbol::LENGTH.into(), Property::data_xxc(Value::Number(length as f64)), ); + debug_assert!(matches!(result, Ok(true))); } // 7.1.4 ToNumber ( argument ) @@ -190,7 +194,7 @@ impl Runtime { if len < 0.0 { Ok(0) } else { - Ok(len.min(0x1F_FFFF_FFFF_FFFFu64 as f64) as u64) + Ok(len.min(crate::types::number::MAX_SAFE_INTEGER) as u64) } } @@ -263,6 +267,36 @@ impl Runtime { s.repeat(n, &mut self.heap) } } + + //#sec-lengthofarraylike + fn length_of_array_like(&mut self, obj: HandleMut) -> Result { + logger::debug!(event = "runtime.length_of_array_like", ?obj); + let value = obj + .get_value(&Symbol::LENGTH.into()) + .unwrap_or(&Value::Undefined); + Ok(self.value_to_length(value)? as f64) + } + + //#sec-createlistfromarraylike + fn create_vec_from_array_like(&mut self, obj: &Value) -> Result, Error> { + logger::debug!(event = "runtime.create_vec_from_array_like", ?obj); + // TODO(feat): validElementTypes + let obj = match obj { + Value::None => unreachable!(), + Value::Object(v) => *v, + _ => return type_error!(), + }; + let len = self.length_of_array_like(obj)?; + let mut values = Vec::with_capacity(len as usize); + let mut index = 0.0; + while index < len { + let next = obj.get_value(&index.into()).unwrap_or(&Value::Undefined); + // TODO(feat): validElementTypes + values.push(next.clone()); + index += 1.0; + } + Ok(values) + } } // 7.2.1 RequireObjectCoercible ( argument ) diff --git a/libs/jsruntime/src/builtins/native_error_imp.rs.hbs b/libs/jsruntime/src/builtins/native_error_imp.rs.hbs index e0809d150..80d0bf795 100644 --- a/libs/jsruntime/src/builtins/native_error_imp.rs.hbs +++ b/libs/jsruntime/src/builtins/native_error_imp.rs.hbs @@ -13,13 +13,14 @@ use jsparser::Symbol; use crate::Error; use crate::Runtime; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Property; use crate::types::String; use crate::types::Value; +use crate::logger; + //#sec-nativeerror constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { logger::debug!(event = "{{id}}_constructor"); @@ -44,10 +45,11 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re message => { let msg = runtime.value_to_string(message)?; // TODO: error handling - let _ = object.define_own_property( + let result = object.define_own_property( Symbol::MESSAGE.into(), Property::data_wxc(Value::String(msg)), ); + debug_assert!(matches!(result, Ok(true))); } } @@ -55,7 +57,8 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re let key = Symbol::CAUSE.into(); if let Some(value) = value.get_value(&key) { // TODO: error handling - let _ = object.define_own_property(key, Property::data_wxc(value.clone())); + let result = object.define_own_property(key, Property::data_wxc(value.clone())); + debug_assert!(matches!(result, Ok(true))); } } @@ -64,18 +67,20 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re //#sec-nativeerror.prototype.message prototype.property pub fn {{id}}_prototype_message(_runtime: &mut Runtime, mut prototype: HandleMut) { - let _ = prototype.define_own_property( + let result = prototype.define_own_property( Symbol::MESSAGE.into(), Property::data_xxx(Value::String(crate::types::string::EMPTY)), ); + debug_assert!(matches!(result, Ok(true))); } //#sec-nativeerror.prototype.name prototype.property pub fn {{id}}_prototype_name(_runtime: &mut Runtime, mut prototype: HandleMut) { - let _ = prototype.define_own_property( + let result = prototype.define_own_property( Symbol::NAME.into(), Property::data_xxx(Value::String(const_string_handle!("{{class}}"))), ); + debug_assert!(matches!(result, Ok(true))); } impl Runtime { @@ -84,10 +89,11 @@ impl Runtime { let mut object = self.create_object(); object.set_prototype(self.builtins.{{id}}_prototype); if let Some(msg) = msg { - let _ = object.define_own_property( + let result = object.define_own_property( Symbol::MESSAGE.into(), Property::data_wxc(Value::String(msg)), ); + debug_assert!(matches!(result, Ok(true))); } object } diff --git a/libs/jsruntime/src/builtins/object/imp.rs b/libs/jsruntime/src/builtins/object/imp.rs index e5bc68df8..62544869f 100644 --- a/libs/jsruntime/src/builtins/object/imp.rs +++ b/libs/jsruntime/src/builtins/object/imp.rs @@ -6,7 +6,6 @@ use jsparser::Symbol; use crate::Error; use crate::Runtime; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Property; @@ -14,6 +13,8 @@ use crate::types::PropertyFlags; use crate::types::PropertyKey; use crate::types::Value; +use super::logger; + //#sec-object-value constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { logger::debug!(event = "object"); diff --git a/libs/jsruntime/src/builtins/promise/imp.rs b/libs/jsruntime/src/builtins/promise/imp.rs index df1e1d135..dec1e3112 100644 --- a/libs/jsruntime/src/builtins/promise/imp.rs +++ b/libs/jsruntime/src/builtins/promise/imp.rs @@ -7,13 +7,13 @@ use jsgc::HandleMut; use crate::Error; use crate::Runtime; use crate::lambda::LambdaId; -use crate::logger; use crate::types::CallContext; use crate::types::Object; use crate::types::Status; use crate::types::Value; use super::BuiltinFunctionParams; +use super::logger; //#sec-promise-executor constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { @@ -50,7 +50,8 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re if let Status::Exception = runtime.call( context, executor, - &mut [Value::Object(resolve), Value::Object(reject)], + &Value::Undefined, + &[Value::Object(resolve), Value::Object(reject)], &mut retv, ) { runtime.emit_promise_rejected(object, retv); diff --git a/libs/jsruntime/src/builtins/string/imp.rs b/libs/jsruntime/src/builtins/string/imp.rs index f1b110a35..dd8e78067 100644 --- a/libs/jsruntime/src/builtins/string/imp.rs +++ b/libs/jsruntime/src/builtins/string/imp.rs @@ -8,7 +8,6 @@ use jsparser::Symbol; use crate::Error; use crate::Runtime; use crate::builtins::require_object_coercible; -use crate::logger; use crate::types::CallContext; use crate::types::Property; use crate::types::String; @@ -16,6 +15,8 @@ use crate::types::Value; use crate::types::string::EMPTY; use crate::types::string::SPACE; +use super::logger; + //#sec-string-constructor-string-value constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { logger::debug!(event = "string"); diff --git a/libs/jsruntime/src/jobs.rs b/libs/jsruntime/src/jobs.rs index ad76434ff..d20b6823f 100644 --- a/libs/jsruntime/src/jobs.rs +++ b/libs/jsruntime/src/jobs.rs @@ -54,8 +54,8 @@ impl Runtime { error: &Value, ) -> (Status, Value) { logger::debug!(event = "resume", ?coroutine, ?object, ?result, ?error); - let mut args = [object.into(), result.clone(), error.clone()]; - let mut context = CallContext::new_for_promise(coroutine, &mut args); + let args = [object.into(), result.clone(), error.clone()]; + let mut context = CallContext::new_for_promise(coroutine, &args); let mut retv = Value::None; let lambda = Lambda::from(coroutine.closure.lambda); let status = lambda(self, &mut context, &mut retv); diff --git a/libs/jsruntime/src/lib.rs b/libs/jsruntime/src/lib.rs index a9c14102b..d734fd4ad 100644 --- a/libs/jsruntime/src/lib.rs +++ b/libs/jsruntime/src/lib.rs @@ -267,11 +267,12 @@ impl Runtime { &mut self, caller: &CallContext, callable: HandleMut, - args: &mut [Value], + this: &Value, + args: &[Value], retv: &mut Value, ) -> Status { let closure = callable.closure(); - let mut context = caller.new_child(callable, closure, args); + let mut context = caller.new_child(callable, closure, this, args); let lambda = Lambda::from(closure.lambda); lambda(self, &mut context, retv) } @@ -284,8 +285,8 @@ impl Runtime { module: bool, ) -> Result { logger::debug!(event = "call_entry_lambda", ?lambda_id, ?lambda, module); - let mut args: [_; 0] = []; - let mut context = CallContext::new_for_entry(&mut args); + let args: [_; 0] = []; + let mut context = CallContext::new_for_entry(&args); let mut retv = Value::Undefined; let status = lambda(self, &mut context, &mut retv); retv.into_result(status) @@ -313,6 +314,34 @@ impl Runtime { self.create_string(&utf16) } + fn create_capture(&mut self, target: *mut Value) -> HandleMut { + debug_assert!( + std::alloc::Layout::from_size_align( + std::mem::size_of::(), + std::mem::align_of::() + ) + .is_ok() + ); + + // SAFETY: `from_size_align()` always succeeds. + const LAYOUT: std::alloc::Layout = unsafe { + std::alloc::Layout::from_size_align_unchecked( + std::mem::size_of::(), + std::mem::align_of::(), + ) + }; + + self.heap.alloc_layout_mut(LAYOUT, move |ptr| { + // SAFETY: `ptr` is a non-null pointer to a `Capture`. + unsafe { + ptr.cast::().write(Capture { + target, + escaped: Value::None, + }); + } + }) + } + fn create_closure( &mut self, lambda: Lambda, @@ -326,6 +355,7 @@ impl Runtime { ) .is_ok(), ); + // SAFETY: `from_size_align()` always succeeds. const BASE_LAYOUT: std::alloc::Layout = unsafe { std::alloc::Layout::from_size_align_unchecked( @@ -340,10 +370,14 @@ impl Runtime { self.heap.alloc_layout_mut(layout, move |ptr| { // SAFETY: `ptr` is a non-null pointer to a `Closure`. - let closure = unsafe { ptr.cast::().as_mut() }; - closure.lambda = lambda.into(); - closure.lambda_id = lambda_id; - closure.num_captures = num_captures; + unsafe { + ptr.cast::().write(Closure { + lambda: lambda.into(), + lambda_id, + num_captures, + captures: [], + }) + }; // `closure.captures[]` will be filled with actual pointers to `Captures`. }) } diff --git a/libs/jsruntime/src/types/call_context.rs b/libs/jsruntime/src/types/call_context.rs index c3657a6c4..d23a69f27 100644 --- a/libs/jsruntime/src/types/call_context.rs +++ b/libs/jsruntime/src/types/call_context.rs @@ -45,7 +45,8 @@ pub struct CallContext { argc_max: u16, /// A pointer to the arguments. - argv: *mut Value, + // TODO(feat): arguments object + argv: *const Value, } impl CallContext { @@ -61,7 +62,7 @@ impl CallContext { pub const ARGC_MAX_OFFSET: usize = std::mem::offset_of!(Self, argc_max); pub const ARGV_OFFSET: usize = std::mem::offset_of!(Self, argv); - pub(crate) fn new_for_entry(args: &mut [Value]) -> Self { + pub(crate) fn new_for_entry(args: &[Value]) -> Self { Self { envp: std::ptr::null_mut(), this: Value::Undefined, @@ -71,11 +72,11 @@ impl CallContext { depth: 0, argc: args.len() as u16, argc_max: args.len() as u16, - argv: args.as_mut_ptr(), + argv: args.as_ptr(), } } - pub(crate) fn new_for_promise(coroutine: HandleMut, args: &mut [Value]) -> Self { + pub(crate) fn new_for_promise(coroutine: HandleMut, args: &[Value]) -> Self { Self { envp: coroutine.as_ptr() as *mut std::ffi::c_void, this: Value::Undefined, @@ -85,7 +86,7 @@ impl CallContext { depth: 0, argc: args.len() as u16, argc_max: args.len() as u16, - argv: args.as_mut_ptr(), + argv: args.as_ptr(), } } @@ -93,18 +94,19 @@ impl CallContext { &self, func: HandleMut, closure: HandleMut, - args: &mut [Value], + this: &Value, + args: &[Value], ) -> Self { Self { envp: closure.as_ptr() as *mut std::ffi::c_void, - this: Value::Undefined, + this: this.clone(), func: Some(func), caller: self, flags: CallContextFlags::empty(), depth: self.depth + 1, argc: args.len() as u16, argc_max: args.len() as u16, - argv: args.as_mut_ptr(), + argv: args.as_ptr(), } } @@ -154,7 +156,7 @@ impl CallContext { unsafe { debug_assert!(!self.argv.is_null()); debug_assert!(self.argv.is_aligned()); - std::slice::from_raw_parts(self.argv as *const Value, self.argc as usize) + std::slice::from_raw_parts(self.argv, self.argc as usize) } } } diff --git a/libs/jsruntime/src/types/capture.rs b/libs/jsruntime/src/types/capture.rs index c926cc5b1..e79d75682 100644 --- a/libs/jsruntime/src/types/capture.rs +++ b/libs/jsruntime/src/types/capture.rs @@ -29,6 +29,26 @@ impl Capture { pub(crate) const TARGET_OFFSET: usize = std::mem::offset_of!(Self, target); pub(crate) const ESCAPED_OFFSET: usize = std::mem::offset_of!(Self, escaped); + pub(crate) fn value(&self) -> &Value { + if self.is_escaped() { + &self.escaped + } else { + debug_assert!(!self.target.is_null()); + // SAFETY: `self.target` is a non-null pointer to a `Value`. + unsafe { &*self.target } + } + } + + pub(crate) fn escape(&mut self) { + debug_assert!(!self.is_escaped()); + debug_assert!(!self.target.is_null()); + // SAFETY: `self.target` is a non-null pointer to a `Value`. + unsafe { + self.escaped = (*self.target).clone(); + } + self.target = &mut self.escaped as *mut Value; + } + fn is_escaped(&self) -> bool { debug_assert!(!self.target.is_null()); addr_eq(self.target, &self.escaped) @@ -51,8 +71,6 @@ impl std::fmt::Debug for Capture { impl Trace for Capture { fn trace(&self, visits: &mut VisitList) { - if self.is_escaped() { - self.escaped.trace(visits); - } + self.value().trace(visits); } } diff --git a/libs/jsruntime/src/types/closure.rs b/libs/jsruntime/src/types/closure.rs index 7a2592c2e..e65f51d53 100644 --- a/libs/jsruntime/src/types/closure.rs +++ b/libs/jsruntime/src/types/closure.rs @@ -40,7 +40,15 @@ impl Closure { pub(crate) const LAMBDA_OFFSET: usize = std::mem::offset_of!(Self, lambda); pub(crate) const CAPTURES_OFFSET: usize = std::mem::offset_of!(Self, captures); - fn captures(&self) -> &[HandleMut] { + pub(crate) fn put_capture(&mut self, index: usize, capture: HandleMut) { + debug_assert!(index < self.num_captures as usize); + // SAFETY: self.captures[index] = capture + unsafe { + self.captures.as_mut_ptr().add(index).write(capture); + } + } + + pub(crate) fn captures(&self) -> &[HandleMut] { let len = self.num_captures as usize; let data = self.captures.as_ptr(); // SAFETY: `data` is a non-null pointer to an array of pointers. diff --git a/libs/jsruntime/src/types/number.rs b/libs/jsruntime/src/types/number.rs index 9fce9f78c..ef382e258 100644 --- a/libs/jsruntime/src/types/number.rs +++ b/libs/jsruntime/src/types/number.rs @@ -1,6 +1,9 @@ use super::Value; use crate::Error; +// TODO: pub const MIN_SAFE_INTEGER: f64 = -((1i64 << 53) - 1) as f64; +pub const MAX_SAFE_INTEGER: f64 = ((1i64 << 53) - 1) as f64; + // 7.1.4 ToNumber ( argument ) pub fn to_number(value: &Value) -> Result { match value { diff --git a/libs/jsruntime/src/types/object.rs b/libs/jsruntime/src/types/object.rs index a8e9a5d5c..e1b8d390e 100644 --- a/libs/jsruntime/src/types/object.rs +++ b/libs/jsruntime/src/types/object.rs @@ -59,8 +59,14 @@ impl From for PropertyKey { impl Hash for PropertyKey { fn hash(&self, state: &mut H) { match self { - Self::Symbol(v) => state.write_u32(v.id()), - Self::Number(v) => state.write_u64(v.to_bits()), + Self::Symbol(v) => { + state.write_u8(1); + state.write_u64(v.id() as u64); + } + Self::Number(v) => { + state.write_u8(2); + state.write_u64(v.to_bits()); + } } } } @@ -306,8 +312,7 @@ impl Object { } pub(crate) fn set_closure(&mut self, closure: HandleMut) { - self.kernel.data = closure.as_addr(); - self.kernel.tracing = true; + self.set_handle_mut(closure); self.set_callable(); } @@ -322,8 +327,7 @@ impl Object { } pub(crate) fn set_string(&mut self, string: Handle) { - self.kernel.data = string.as_addr(); - self.kernel.tracing = true; + self.set_handle(string); } pub(crate) fn promise(&self) -> HandleMut { @@ -332,7 +336,16 @@ impl Object { } pub(crate) fn set_promise(&mut self, promise: HandleMut) { - self.kernel.data = promise.as_addr(); + self.set_handle_mut(promise); + } + + fn set_handle(&mut self, handle: Handle) { + self.kernel.data = handle.as_addr(); + self.kernel.tracing = true; + } + + fn set_handle_mut(&mut self, handle: HandleMut) { + self.kernel.data = handle.as_addr(); self.kernel.tracing = true; } diff --git a/libs/jsruntime/tests/scripts/function_apply.js b/libs/jsruntime/tests/scripts/function_apply.js new file mode 100644 index 000000000..9bcb9f5d0 --- /dev/null +++ b/libs/jsruntime/tests/scripts/function_apply.js @@ -0,0 +1,9 @@ +print(typeof Function.prototype.apply); ///="function" +print(Function.prototype.apply.length); ///=2 + +function a(x) { + print(x); +} + +a.apply(this); ///=undefined +a.apply(this, [1, 2]); ///=1 diff --git a/libs/jsruntime/tests/scripts/function_bind.js b/libs/jsruntime/tests/scripts/function_bind.js new file mode 100644 index 000000000..6d7492f39 --- /dev/null +++ b/libs/jsruntime/tests/scripts/function_bind.js @@ -0,0 +1,15 @@ +print(typeof Function.prototype.bind); ///="function" +print(Function.prototype.bind.length); ///=2 + +const o = { + x: 1, +}; + +function sum(a, b, c) { + return a + b + c + this.x; +}; + +print(sum()); ///=NaN + +const bound = sum.bind(o, 2, 3, 4); +print(bound()); ///=10 diff --git a/libs/jsruntime/tests/scripts/function_call.js b/libs/jsruntime/tests/scripts/function_call.js new file mode 100644 index 000000000..92f38519e --- /dev/null +++ b/libs/jsruntime/tests/scripts/function_call.js @@ -0,0 +1,8 @@ +print(typeof Function.prototype.call); ///="function" +print(Function.prototype.call.length); ///=2 + +function a(x, y) { + print(y); +} + +a.call(this, 1, 2); ///=2 diff --git a/libs/jsruntime/tests/scripts/function_to_string.js b/libs/jsruntime/tests/scripts/function_to_string.js new file mode 100644 index 000000000..b687ff453 --- /dev/null +++ b/libs/jsruntime/tests/scripts/function_to_string.js @@ -0,0 +1,9 @@ +print(typeof Function.prototype.toString); ///="function" +print(Function.prototype.toString.length); ///=0 + +function a() {}; +try { + print(a.toString()); // TODO +} catch (e) { + print(e.name); ///="InternalError" +}