Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiword exports #424

Merged
merged 2 commits into from
Jul 11, 2023
Merged
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 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 crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ wasi-common = { workspace = true }
walrus = "0.20.1"
swc_core = { version = "0.78.15", features = ["common_sourcemap", "ecma_ast", "ecma_parser"] }
wit-parser = "0.8.0"
convert_case = "0.4.0"

[dev-dependencies]
serde_json = "1.0"
Expand Down
28 changes: 28 additions & 0 deletions crates/cli/src/exports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use anyhow::{anyhow, Result};
use convert_case::{Case, Casing};
use std::path::Path;

use crate::{js::JS, wit};

pub struct Export {
pub wit: String,
pub js: String,
}

pub fn process_exports(js: &JS, wit: &Path, wit_world: &str) -> Result<Vec<Export>> {
let js_exports = js.exports()?;
wit::parse_exports(wit, wit_world)?
.into_iter()
.map(|wit_export| {
let export = wit_export.from_case(Case::Kebab).to_case(Case::Camel);
if !js_exports.contains(&export) {
Err(anyhow!("JS module does not export {export}"))
} else {
Ok(Export {
wit: wit_export,
js: export,
})
}
})
.collect::<Result<Vec<Export>>>()
}
32 changes: 10 additions & 22 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod bytecode;
mod commands;
mod exports;
mod js;
mod wasm_generator;
mod wit;

use crate::commands::{Command, CompileCommandOpts, EmitProviderCommandOpts};
use crate::wasm_generator::r#static as static_generator;
use anyhow::{anyhow, bail, Result};
use anyhow::{bail, Result};
use exports::Export;
use js::JS;
use std::env;
use std::fs::File;
Expand All @@ -23,7 +25,12 @@ fn main() -> Result<()> {
Command::EmitProvider(opts) => emit_provider(opts),
Command::Compile(opts) => {
let js = JS::from_file(&opts.input)?;
let exports = determine_js_exports(&js, opts)?;
let exports = match (&opts.wit, &opts.wit_world) {
(None, None) => Ok(vec![]),
(None, Some(_)) => Ok(vec![]),
(Some(_), None) => bail!("Must provide WIT world when providing WIT file"),
(Some(wit), Some(world)) => exports::process_exports(&js, wit, world),
}?;
if opts.dynamic {
let wasm = dynamic_generator::generate(&js, exports)?;
fs::write(&opts.output, wasm)?;
Expand All @@ -44,7 +51,7 @@ fn emit_provider(opts: &EmitProviderCommandOpts) -> Result<()> {
Ok(())
}

fn create_statically_linked_module(opts: &CompileCommandOpts, exports: Vec<String>) -> Result<()> {
fn create_statically_linked_module(opts: &CompileCommandOpts, exports: Vec<Export>) -> Result<()> {
// The javy-core `main.rs` pre-initializer uses WASI to read the JS source
// code from stdin. Wizer doesn't let us customize its WASI context so we
// don't have a better option right now. Since we can't set the content of
Expand Down Expand Up @@ -83,22 +90,3 @@ fn create_statically_linked_module(opts: &CompileCommandOpts, exports: Vec<Strin
fs::write(&opts.output, wasm)?;
Ok(())
}

fn determine_js_exports(js: &JS, opts: &CompileCommandOpts) -> Result<Vec<String>> {
let js_exports = js.exports()?;
match (&opts.wit, &opts.wit_world) {
(None, None) => Ok(vec![]),
(None, Some(_)) => Ok(vec![]),
(Some(_), None) => bail!("Must provide WIT world when providing WIT file"),
(Some(path), Some(world)) => wit::parse_exports(path, world)?
.into_iter()
.map(|wit_export| {
if !js_exports.contains(&wit_export) {
Err(anyhow!("JS module does not export {wit_export}"))
} else {
Ok(wit_export)
}
})
.collect::<Result<Vec<String>>>(),
}
}
10 changes: 5 additions & 5 deletions crates/cli/src/wasm_generator/dynamic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::js::JS;
use crate::{exports::Export, js::JS};

use super::transform::{self, SourceCodeSection};
use anyhow::Result;
Expand Down Expand Up @@ -64,7 +64,7 @@ use walrus::{DataKind, FunctionBuilder, Module, ValType};
// (data (;0;) "\02\05\18function.mjs\06foo\0econsole\06log\06bar\0f\bc\03\00\01\00\00\be\03\00\00\0e\00\06\01\a0\01\00\00\00\03\01\01\1a\00\be\03\00\01\08\ea\05\c0\00\e1)8\e0\00\00\00B\e1\00\00\00\04\e2\00\00\00$\01\00)\bc\03\01\04\01\00\07\0a\0eC\06\01\be\03\00\00\00\03\00\00\13\008\e0\00\00\00B\e1\00\00\00\04\df\00\00\00$\01\00)\bc\03\01\02\03]")
// (data (;1;) "foo")
// )
pub fn generate(js: &JS, exported_functions: Vec<String>) -> Result<Vec<u8>> {
pub fn generate(js: &JS, exported_functions: Vec<Export>) -> Result<Vec<u8>> {
let mut module = Module::with_config(transform::module_config());

const IMPORT_NAMESPACE: &str = "javy_quickjs_provider_v1";
Expand Down Expand Up @@ -123,9 +123,9 @@ pub fn generate(js: &JS, exported_functions: Vec<String>) -> Result<Vec<u8>> {
let (invoke_fn, _) = module.add_import_func(IMPORT_NAMESPACE, "invoke", invoke_type);

let fn_name_ptr_local = module.locals.add(ValType::I32);
for js_export in exported_functions {
for export in exported_functions {
// For each JS function export, add an export that copies the name of the function into memory and invokes it.
let js_export_bytes = js_export.as_bytes();
let js_export_bytes = export.js.as_bytes();
let js_export_len: i32 = js_export_bytes.len().try_into().unwrap();
let fn_name_data = module.data.add(DataKind::Passive, js_export_bytes.to_vec());

Expand Down Expand Up @@ -161,7 +161,7 @@ pub fn generate(js: &JS, exported_functions: Vec<String>) -> Result<Vec<u8>> {
.i32_const(js_export_len)
.call(invoke_fn);
let export_fn = export_fn.finish(vec![], &mut module.funcs);
module.exports.add(&js_export, export_fn);
module.exports.add(&export.wit, export_fn);
}
}

Expand Down
12 changes: 6 additions & 6 deletions crates/cli/src/wasm_generator/static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use binaryen::{CodegenConfig, Module};
use walrus::{DataKind, ExportItem, FunctionBuilder, FunctionId, MemoryId, ValType};
use wizer::Wizer;

use crate::js::JS;
use crate::{exports::Export, js::JS};

use super::transform::{self, SourceCodeSection};

Expand All @@ -26,7 +26,7 @@ pub fn generate() -> Result<Vec<u8>> {
/// Takes Wasm created by `Generator` and makes additional changes.
///
/// This is intended to be run in the parent process after generating the Wasm.
pub fn refine(wasm: Vec<u8>, js: &JS, exports: Vec<String>) -> Result<Vec<u8>> {
pub fn refine(wasm: Vec<u8>, js: &JS, exports: Vec<Export>) -> Result<Vec<u8>> {
let mut module = transform::module_config().parse(&wasm)?;

let (realloc, invoke, memory) = {
Expand Down Expand Up @@ -82,12 +82,12 @@ fn export_exported_js_functions(
realloc_fn: FunctionId,
invoke_fn: FunctionId,
memory: MemoryId,
js_exports: Vec<String>,
js_exports: Vec<Export>,
) {
let ptr_local = module.locals.add(ValType::I32);
for js_export in js_exports {
for export in js_exports {
// For each JS function export, add an export that copies the name of the function into memory and invokes it.
let js_export_bytes = js_export.as_bytes();
let js_export_bytes = export.js.as_bytes();
let js_export_len: i32 = js_export_bytes.len().try_into().unwrap();
let fn_name_data = module.data.add(DataKind::Passive, js_export_bytes.to_vec());

Expand All @@ -108,6 +108,6 @@ fn export_exported_js_functions(
.i32_const(js_export_len)
.call(invoke_fn);
let export_fn = export_fn.finish(vec![], &mut module.funcs);
module.exports.add(&js_export, export_fn);
module.exports.add(&export.wit, export_fn);
}
}
6 changes: 3 additions & 3 deletions crates/cli/tests/dynamic_linking_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ pub fn test_dynamic_linking() -> Result<()> {

#[test]
pub fn test_dynamic_linking_with_func() -> Result<()> {
let js_src = "export function foo() { console.log('In foo'); }; console.log('Toplevel');";
let js_src = "export function fooBar() { console.log('In foo'); }; console.log('Toplevel');";
let wit = "
package local:main

world foo-test {
export foo: func()
export foo-bar: func()
}
";
let log_output = invoke_fn_on_generated_module(js_src, "foo", Some((wit, "foo-test")))?;
let log_output = invoke_fn_on_generated_module(js_src, "foo-bar", Some((wit, "foo-test")))?;
assert_eq!("Toplevel\nIn foo\n", &log_output);
Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ fn test_exported_functions() {
let (_, logs, fuel_consumed) = run_fn(&mut runner, "foo", &[]);
assert_eq!("Hello from top-level\nHello from foo\n", logs);
assert_fuel_consumed_within_threshold(54610, fuel_consumed);
let (_, logs, _) = run_fn(&mut runner, "foo-bar", &[]);
assert_eq!("Hello from top-level\nHello from fooBar\n", logs);
}

#[cfg(feature = "experimental_event_loop")]
Expand Down
4 changes: 4 additions & 0 deletions crates/cli/tests/sample-scripts/exported-fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ export function foo() {
console.log("Hello from foo");
}

export function fooBar() {
console.log("Hello from fooBar");
}

console.log("Hello from top-level");
1 change: 1 addition & 0 deletions crates/cli/tests/sample-scripts/exported-fn.wit
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package local:test
world exported-fn {
export foo: func()
export bar: func()
export foo-bar: func()
}
6 changes: 6 additions & 0 deletions supply-chain/imports.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,12 @@ who = "Johan Andersson <opensource@embark-studios.com>"
criteria = "safe-to-deploy"
version = "1.0.58"

[[audits.embark-studios.audits.convert_case]]
who = "Johan Andersson <opensource@embark-studios.com>"
criteria = "safe-to-deploy"
version = "0.4.0"
notes = "No unsafe usage or ambient capabilities"

[[audits.embark-studios.audits.epaint]]
who = "Johan Andersson <opensource@embark-studios.com>"
criteria = "safe-to-deploy"
Expand Down