Skip to content

Commit

Permalink
Add more primordials (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
richarddavison authored Nov 28, 2024
1 parent aed832b commit 30e0cec
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 300 deletions.
13 changes: 7 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libs/llrt_context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://github.com/awslabs/llrt"
rquickjs = { git = "https://github.com/DelSkayn/rquickjs.git", version = "0.8.1", features = [
"futures",
], default-features = false }
llrt_utils = { version = "0.3.0-beta", path = "../llrt_utils", default-features = false }
tokio = { version = "1", features = ["sync"], default-features = false }
tracing = "0.1"

Expand Down
9 changes: 4 additions & 5 deletions libs/llrt_context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
use std::future::Future;
use std::sync::OnceLock;

use rquickjs::{
atom::PredefinedAtom, function::Constructor, CatchResultExt, CaughtError, Ctx, Object, Result,
};
use llrt_utils::primordials::{BasePrimordials, Primordial};
use rquickjs::{atom::PredefinedAtom, CatchResultExt, CaughtError, Ctx, Object, Result};
use tokio::sync::oneshot::{self, Receiver};
use tracing::trace;

Expand Down Expand Up @@ -34,8 +33,8 @@ impl<'js> CtxExtension<'js> for Ctx<'js> {
{
let ctx = self.clone();

let type_error_ctor: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
let type_error: Object = type_error_ctor.construct(())?;
let primordials = BasePrimordials::get(self)?;
let type_error: Object = primordials.constructor_type_error.construct(())?;
let stack: Option<String> = type_error.get(PredefinedAtom::Stack).ok();

let (join_channel_tx, join_channel_rx) = oneshot::channel();
Expand Down
12 changes: 5 additions & 7 deletions libs/llrt_utils/src/object.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
atom::PredefinedAtom,
function::{Constructor, IntoJsFunc},
prelude::Func,
Array, Ctx, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value,
atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Ctx, FromJs, IntoAtom,
IntoJs, Object, Result, Symbol, Undefined, Value,
};

use crate::primordials::{BasePrimordials, Primordial};
Expand Down Expand Up @@ -50,9 +48,9 @@ pub struct Proxy<'js> {

impl<'js> IntoJs<'js> for Proxy<'js> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
let proxy_ctor = ctx.globals().get::<_, Constructor>(PredefinedAtom::Proxy)?;

proxy_ctor.construct::<_, Value>((self.target, self.options))
BasePrimordials::get(ctx)?
.constructor_proxy
.construct::<_, Value>((self.target, self.options))
}
}

Expand Down
7 changes: 7 additions & 0 deletions libs/llrt_utils/src/primordials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub struct BasePrimordials<'js> {
pub constructor_set: Constructor<'js>,
pub constructor_date: Constructor<'js>,
pub constructor_error: Constructor<'js>,
pub constructor_type_error: Constructor<'js>,
pub constructor_regexp: Constructor<'js>,
pub constructor_proxy: Constructor<'js>,

// Prototypes
pub prototype_object: Object<'js>,
Expand Down Expand Up @@ -62,6 +64,8 @@ impl<'js> Primordial<'js> for BasePrimordials<'js> {
let constructor_object: Object = globals.get(PredefinedAtom::Object)?;
let prototype_object: Object = constructor_object.get(PredefinedAtom::Prototype)?;

let constructor_proxy: Constructor = globals.get(PredefinedAtom::Proxy)?;

let function_get_own_property_descriptor: Function =
constructor_object.get(PredefinedAtom::GetOwnPropertyDescriptor)?;

Expand All @@ -78,6 +82,7 @@ impl<'js> Primordial<'js> for BasePrimordials<'js> {
let prototype_regexp: Object = constructor_regexp.get(PredefinedAtom::Prototype)?;

let constructor_error: Constructor = globals.get(PredefinedAtom::Error)?;
let constructor_type_error: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
let prototype_error: Object = constructor_error.get(PredefinedAtom::Prototype)?;

let constructor_array: Object = globals.get(PredefinedAtom::Array)?;
Expand All @@ -100,7 +105,9 @@ impl<'js> Primordial<'js> for BasePrimordials<'js> {
constructor_map,
constructor_set,
constructor_date,
constructor_proxy,
constructor_error,
constructor_type_error,
constructor_regexp,
prototype_object,
prototype_date,
Expand Down
1 change: 1 addition & 0 deletions llrt_core/src/module_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl Default for ModuleBuilder {
.with_module(FsModule)
.with_module(OsModule)
.with_module(TimersModule)
.with_global(llrt_modules::timers::init)
.with_module(EventsModule)
.with_global(crate::modules::events::init)
.with_global(crate::modules::abort::init)
Expand Down
1 change: 1 addition & 0 deletions llrt_core/src/module_loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use once_cell::sync::Lazy;
use crate::environment;

pub mod loader;
pub mod require;
pub mod resolver;

// added when .cjs files are imported
Expand Down
232 changes: 232 additions & 0 deletions llrt_core/src/module_loader/require.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
fs,
rc::Rc,
sync::Mutex,
};

use llrt_json::parse::json_parse;
use llrt_modules::{path::resolve_path, timers::poll_timers};
use rquickjs::{
atom::PredefinedAtom, object::Accessor, prelude::Func, qjs, Ctx, Error, Filter, JsLifetime,
Module, Object, Result, Value,
};
use tokio::time::Instant;
use tracing::trace;

use crate::{
bytecode::BYTECODE_FILE_EXT,
module_loader::{resolver::require_resolve, CJS_IMPORT_PREFIX},
utils::ctx::CtxExt,
};

#[derive(Default)]
struct RequireState<'js> {
cache: HashMap<Rc<str>, Value<'js>>,
exports: HashMap<Rc<str>, Value<'js>>,
}

unsafe impl<'js> JsLifetime<'js> for RequireState<'js> {
type Changed<'to> = RequireState<'to>;
}

pub fn init(ctx: &Ctx, module_names: HashSet<&'static str>) -> Result<()> {
let globals = ctx.globals();

let require_in_progress: Rc<Mutex<HashMap<Rc<str>, Object>>> =
Rc::new(Mutex::new(HashMap::new()));

let _ = ctx.store_userdata(RefCell::new(RequireState::default()));

let exports_accessor = Accessor::new(
|ctx| {
struct Args<'js>(Ctx<'js>);
let Args(ctx) = Args(ctx);
let name = ctx.get_script_or_module_name()?;
let name = name.trim_start_matches(CJS_IMPORT_PREFIX);

let binding = ctx.userdata::<RefCell<RequireState>>().unwrap();
let mut state = binding.borrow_mut();

if let Some(value) = state.exports.get(name) {
Ok::<_, Error>(value.clone())
} else {
let obj = Object::new(ctx.clone())?.into_value();
state.exports.insert(name.into(), obj.clone());
Ok::<_, Error>(obj)
}
},
|ctx, exports| {
struct Args<'js>(Ctx<'js>, Value<'js>);
let Args(ctx, exports) = Args(ctx, exports);
let name = ctx.get_script_or_module_name()?;
let name = name.trim_start_matches(CJS_IMPORT_PREFIX);
let binding = ctx.userdata::<RefCell<RequireState>>().unwrap();
let mut state = binding.borrow_mut();
state.exports.insert(name.into(), exports);
Ok::<_, Error>(())
},
)
.configurable()
.enumerable();

let module = Object::new(ctx.clone())?;
module.prop("exports", exports_accessor)?;

globals.prop("module", module)?;
globals.prop("exports", exports_accessor)?;

globals.set(
"require",
Func::from(move |ctx, specifier: String| -> Result<Value> {
struct Args<'js>(Ctx<'js>);
let Args(ctx) = Args(ctx);

let is_cjs_import = specifier.starts_with(CJS_IMPORT_PREFIX);

let import_name: Rc<str>;

let is_json = specifier.ends_with(".json");

trace!("Before specifier: {}", specifier);

let import_specifier: Rc<str> = if !is_cjs_import {
let is_bytecode = specifier.ends_with(BYTECODE_FILE_EXT);
let is_bytecode_or_json = is_json || is_bytecode;
let specifier = if is_bytecode_or_json {
specifier
} else {
specifier.trim_start_matches("node:").to_string()
};

if module_names.contains(specifier.as_str()) {
import_name = specifier.into();
import_name.clone()
} else {
let module_name = ctx.get_script_or_module_name()?;
let module_name = module_name.trim_start_matches(CJS_IMPORT_PREFIX);
let abs_path = resolve_path([module_name].iter())?;

let resolved_path =
require_resolve(&ctx, &specifier, &abs_path, false)?.into_owned();
import_name = resolved_path.into();
if is_bytecode_or_json {
import_name.clone()
} else {
[CJS_IMPORT_PREFIX, &import_name].concat().into()
}
}
} else {
import_name = specifier[CJS_IMPORT_PREFIX.len()..].into();
specifier.into()
};

trace!("After specifier: {}", import_specifier);

let binding = ctx.userdata::<RefCell<RequireState>>().unwrap();
let mut state = binding.borrow_mut();

if let Some(cached_value) = state.cache.get(import_name.as_ref()) {
return Ok(cached_value.clone());
}

if is_json {
let json = fs::read_to_string(import_name.as_ref())?;
let json = json_parse(&ctx, json)?;
state.cache.insert(import_name, json.clone());
return Ok(json);
}

let mut require_in_progress_map = require_in_progress.lock().unwrap();
if let Some(obj) = require_in_progress_map.get(&import_name) {
let value = obj.clone().into_value();
return Ok(value);
}

trace!("Require: {}", import_specifier);

let obj = Object::new(ctx.clone())?;
require_in_progress_map.insert(import_name.clone(), obj.clone());
drop(require_in_progress_map);
drop(state);

let import_promise = Module::import(&ctx, import_specifier.as_bytes())?;

let rt = unsafe { qjs::JS_GetRuntime(ctx.as_raw().as_ptr()) };

let mut deadline = Instant::now();

let mut executing_timers = Vec::new();

let imported_object = loop {
if let Some(x) = import_promise.result::<Object>() {
break x?;
}

if deadline < Instant::now() {
poll_timers(rt, &mut executing_timers, None, Some(&mut deadline))?;
}

ctx.execute_pending_job();
};

let binding = ctx.userdata::<RefCell<RequireState>>().unwrap();
let mut state = binding.borrow_mut();

let exports_obj = state.exports.get(&import_name).cloned();

require_in_progress
.lock()
.unwrap()
.remove(import_name.as_ref());

if let Some(exports_obj) = exports_obj {
if exports_obj.type_of() == rquickjs::Type::Object {
let exports = unsafe { exports_obj.as_object().unwrap_unchecked() };

for prop in
exports.own_props::<Value, Value>(Filter::new().private().string().symbol())
{
let (key, value) = prop?;
obj.set(key, value)?;
}
} else {
//we have explicitly set it
state.cache.insert(import_name, exports_obj.clone());
return Ok(exports_obj);
}
}

let props = imported_object.props::<String, Value>();

let default_export: Option<Value> = imported_object.get(PredefinedAtom::Default)?;
if let Some(default_export) = default_export {
//if default export is object attach all named exports to
if let Some(default_object) = default_export.as_object() {
for prop in props {
let (key, value) = prop?;
if !default_object.contains_key(&key)? {
default_object.set(key, value)?;
}
}
let default_object = default_object.clone().into_value();
state.cache.insert(import_name, default_object.clone());
return Ok(default_object);
}
}

for prop in props {
let (key, value) = prop?;
obj.set(key, value)?;
}

let value = obj.into_value();

state.cache.insert(import_name, value.clone());
Ok(value)
}),
)?;

Ok(())
}
Loading

0 comments on commit 30e0cec

Please sign in to comment.