Skip to content

Commit

Permalink
make require local to modules instead of globals
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Oct 21, 2023
1 parent a04f277 commit cd6a1f8
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 49 deletions.
128 changes: 87 additions & 41 deletions crates/dash_node_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,75 +42,99 @@ async fn run_inner_fallible(path: &str, opt: OptLevel, initial_gc_threshold: Opt
bail!("Node project path currently needs to be a directory");
}

let package = std::fs::read_to_string(path.join("package.json")).context("Failed to read package.json")?;
let package = serde_json::from_str::<Package>(&package)?;
let package_state = process_package_json(path)?;
let entry =
std::fs::read_to_string(path.join(&package_state.metadata.main)).context("Failed to read entry point")?;

let entry = std::fs::read_to_string(path.join(package.main)).context("Failed to read entry point")?;
let global_state = Rc::new(GlobalState {
node_modules_dir: path.join("node_modules"),
ongoing_requires: RefCell::new(FxHashMap::default()),
});

let mut rt = Runtime::new(initial_gc_threshold).await;
let scope = &mut rt.vm_mut().scope();

execute_node_module(
scope,
path,
path,
&entry,
opt,
Rc::new(RefCell::new(FxHashMap::default())),
)
.map_err(|err| match err {
(EvalError::Middle(errs), _) => anyhow!("{}", errs.formattable(&entry, true)),
(EvalError::Exception(err), _) => anyhow!("{}", format_value(err.root(scope), scope).unwrap()),
})?;
execute_node_module(scope, path, path, &entry, opt, global_state, Rc::new(package_state)).map_err(
|err| match err {
(EvalError::Middle(errs), _, entry) => anyhow!("{}", errs.formattable(&entry, true)),
(EvalError::Exception(err), ..) => anyhow!("{}", format_value(err.root(scope), scope).unwrap()),
},
)?;

Ok(())
}

fn process_package_json(path: &Path) -> Result<PackageState, anyhow::Error> {
let package = std::fs::read_to_string(path.join("package.json")).context("Failed to read package.json")?;
let package = serde_json::from_str::<Package>(&package).context("Failed to parse package.json")?;
let base_dir = path.to_owned();
Ok(PackageState {
metadata: package,
base_dir,
})
}

/// Returns the `module` object
fn execute_node_module(
scope: &mut LocalScope,
dir_path: &Path,
file_path: &Path,
source: &str,
opt: OptLevel,
ongoing_requires: Rc<RefCell<FxHashMap<PathBuf, Value>>>,
) -> Result<Value, (EvalError, StringInterner)> {
global_state: Rc<GlobalState>,
package: Rc<PackageState>,
) -> Result<Value, (EvalError, Option<StringInterner>, String)> {
let exports = Value::Object(scope.register(NamedObject::new(scope)));
let module = Value::Object(scope.register(NamedObject::new(scope)));
let require = Value::Object(scope.register(RequireFunction {
dir: dir_path.to_owned(),
ongoing_requires: ongoing_requires.clone(),
current_dir: dir_path.to_owned(),
state: global_state.clone(),
package,
object: NamedObject::new(scope),
}));
module
.set_property(scope, "exports".into(), PropertyValue::static_default(exports.clone()))
.unwrap();
scope
.global()
.set_property(scope, "module".into(), PropertyValue::static_default(module.clone()))
.unwrap();
scope
.global()
.set_property(scope, "exports".into(), PropertyValue::static_default(exports.clone()))
.unwrap();
scope
.global()
.set_property(scope, "require".into(), PropertyValue::static_default(require))
.unwrap();

ongoing_requires
global_state
.ongoing_requires
.borrow_mut()
.insert(file_path.to_owned(), module.clone());

scope.eval(source, opt)?;
let mut code = String::from("(function(exports, module, require) {");
code += source;
code += "})";

let fun = match scope.eval(&code, opt) {
Ok(v) => v.root(scope),
Err((err, intern)) => return Err((err, Some(intern), code)),
};

fun.apply(scope, Value::undefined(), vec![exports, module.clone(), require])
.map_err(|err| (EvalError::Exception(err.into()), None, code))?;

Ok(module)
}

#[derive(Debug, Trace)]
struct PackageState {
metadata: Package,
/// Path to the base directory of the package
base_dir: PathBuf,
}

#[derive(Debug, Trace)]
struct GlobalState {
node_modules_dir: PathBuf,
ongoing_requires: RefCell<FxHashMap<PathBuf, Value>>,
}

#[derive(Debug, Trace)]
struct RequireFunction {
dir: PathBuf,
ongoing_requires: Rc<RefCell<FxHashMap<PathBuf, Value>>>,
/// Path to the current directory
current_dir: PathBuf,
package: Rc<PackageState>,
state: Rc<GlobalState>,
object: NamedObject,
}

Expand Down Expand Up @@ -143,12 +167,12 @@ impl Object for RequireFunction {

let is_path = matches!(arg.chars().next(), Some('.' | '/' | '~'));
if is_path {
let canonicalized_path = match self.dir.join(&**arg).canonicalize() {
let canonicalized_path = match self.current_dir.join(&**arg).canonicalize() {
Ok(v) => v,
Err(err) => throw!(scope, Error, err.to_string()),
};

if let Some(module) = self.ongoing_requires.borrow().get(&canonicalized_path) {
if let Some(module) = self.state.ongoing_requires.borrow().get(&canonicalized_path) {
return Ok(module.clone());
}

Expand All @@ -172,19 +196,41 @@ impl Object for RequireFunction {
&canonicalized_path,
&source,
OptLevel::default(),
self.ongoing_requires.clone(),
self.state.clone(),
self.package.clone(),
) {
Ok(v) => v,
Err((EvalError::Exception(value), _)) => return Err(value.root(scope)),
Err((EvalError::Middle(errs), _)) => {
Err((EvalError::Exception(value), ..)) => return Err(value.root(scope)),
Err((EvalError::Middle(errs), _, source)) => {
throw!(scope, SyntaxError, "{}", errs.formattable(&source, true))
}
};

module.get_property(scope, "exports".into())
} else {
// Resolve dependency
todo!("Resolve external dependency");
let dir_path = self.state.node_modules_dir.join(&**arg);
let package_state = process_package_json(&dir_path).unwrap();
let file_path = dir_path.join(&package_state.metadata.main);
let source = std::fs::read_to_string(&file_path).unwrap();

let module = match execute_node_module(
scope,
&dir_path,
&file_path,
&source,
OptLevel::default(),
self.state.clone(),
Rc::new(package_state),
) {
Ok(v) => v,
Err((EvalError::Exception(value), ..)) => return Err(value.root(scope)),
Err((EvalError::Middle(errs), _, source)) => {
throw!(scope, SyntaxError, "{}", errs.formattable(&source, true))
}
};

module.get_property(scope, "exports".into())
}
}
}
3 changes: 2 additions & 1 deletion crates/dash_node_impl/src/package.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::collections::HashMap;

use dash_proc_macro::Trace;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Trace)]
pub struct Package {
pub name: String,
pub version: String,
Expand Down
5 changes: 4 additions & 1 deletion crates/dash_vm/src/gc/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;

Expand Down Expand Up @@ -116,5 +117,7 @@ unsafe_empty_trace!(
Number,
RegExpInner,
TypedArrayKind,
PathBuf
PathBuf,
Path,
String
);
11 changes: 5 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,14 @@ $ curl -sSf https://sh.rustup.rs | sh
# Clone repo
$ git clone https://github.com/y21/dash
# Build cli
$ cd dash/cli && cargo install --path .
$ cargo install --path dash/cli
# Optional: rename binary to `dashjs`
$ mv ~/.cargo/bin/dash-cli ~/.cargo/bin/dashjs
# Run the program (run with --help for help)
$ dashjs run example.js
```
Now open up your browser, navigate to http://localhost:3030, refresh a bunch of times and see the numbers go up.

### JIT
This engine has basic support for JIT compilation. It uses LLVM for optimizations and codegen, based on specialized type information and branches tracked by tracing iterations of hot loops.

You can enable it by passing the `jit` feature flag to the dash_vm crate. Beware that it's a very early WIP, expect bugs and crashes!

### Embedding into a Rust application
Note that the API is not stable. Things are constantly changing, so your code may break at any time when bumping the version, which is why it is highly recommended to lock in to a specific revision for now.

Expand Down Expand Up @@ -78,3 +73,7 @@ fn main() {
}
```
<sub>See `dash-cli/` for a more detailed example</sub>

### Node compatibility
There's experimental support for node compatibility. If you want to try it out, pass `--features nodejs` to the cargo install/build command.
When running dash, you can then pass `--node` and various node-specific things will be available to the JS environment, such as the `require` function.

0 comments on commit cd6a1f8

Please sign in to comment.