Skip to content

Commit

Permalink
Refactor to add abstraction around the JS source (#407)
Browse files Browse the repository at this point in the history
* Add abstraction around the JS source

* Add module documentation
  • Loading branch information
jeffcharles committed Jun 23, 2023
1 parent a5e14ce commit bcbed2d
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 34 deletions.
51 changes: 51 additions & 0 deletions crates/cli/src/js.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// Higher-level representation of JavaScript.
///
/// This is intended to be used to derive different representations of source
/// code. For example, as a byte array, a string, QuickJS bytecode, compressed
/// bytes, or attributes of the source code like what it exports.
use anyhow::{Context, Result};
use brotli::enc::{self, BrotliEncoderParams};
use std::{
fs::File,
io::{Cursor, Read},
path::Path,
};

use crate::bytecode;

pub struct JS {
source_code: Vec<u8>,
}

impl JS {
pub fn from_file(path: &Path) -> Result<JS> {
let mut input_file = File::open(path)
.with_context(|| format!("Failed to open input file {}", path.display()))?;
let mut contents: Vec<u8> = vec![];
input_file.read_to_end(&mut contents)?;
Ok(JS {
source_code: contents,
})
}

pub fn as_bytes(&self) -> &[u8] {
&self.source_code
}

pub fn compile(&self) -> Result<Vec<u8>> {
bytecode::compile_source(&self.source_code)
}

pub fn compress(&self) -> Result<Vec<u8>> {
let mut compressed_source_code: Vec<u8> = vec![];
enc::BrotliCompress(
&mut Cursor::new(&self.source_code),
&mut compressed_source_code,
&BrotliEncoderParams {
quality: 11,
..Default::default()
},
)?;
Ok(compressed_source_code)
}
}
29 changes: 11 additions & 18 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
mod bytecode;
mod commands;
mod js;
mod module_generator;
mod opt;
mod transform;

use crate::commands::{Command, CompileCommandOpts, EmitProviderCommandOpts};
use anyhow::{bail, Context, Result};
use anyhow::{bail, Result};
use js::JS;
use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::io::Write;
use std::path::Path;
use std::process::Stdio;
use std::{fs, process::Command as OsCommand};
Expand Down Expand Up @@ -54,7 +56,7 @@ fn create_statically_linked_module(opts: &CompileCommandOpts) -> Result<()> {
return Ok(());
}

let contents = read_input_file(&opts.input)?;
let js = JS::from_file(&opts.input)?;

let self_cmd = env::args().next().unwrap();

Expand All @@ -67,39 +69,30 @@ fn create_statically_linked_module(opts: &CompileCommandOpts) -> Result<()> {
.arg(&opts.output)
.stdin(Stdio::piped())
.spawn()?;
command.stdin.take().unwrap().write_all(&contents)?;
command.stdin.take().unwrap().write_all(js.as_bytes())?;
let status = command.wait()?;
if !status.success() {
bail!("Couldn't create wasm from input");
}
}

add_producers_and_source_code_sections(&opts.output, &contents)?;
add_producers_and_source_code_sections(&opts.output, &js)?;

Ok(())
}

fn add_producers_and_source_code_sections<P: AsRef<Path>>(file: P, contents: &[u8]) -> Result<()> {
fn add_producers_and_source_code_sections<P: AsRef<Path>>(file: P, js: &JS) -> Result<()> {
let mut module = Module::from_file_with_config(&file, &transform::module_config())?;
transform::add_producers_section(&mut module.producers);
module.customs.add(SourceCodeSection::new(contents)?);
module.customs.add(SourceCodeSection::new(js)?);
module.emit_wasm_file(&file)?;
Ok(())
}

fn create_dynamically_linked_module(opts: &CompileCommandOpts) -> Result<()> {
let js_source_code = read_input_file(&opts.input)?;
let quickjs_bytecode = bytecode::compile_source(&js_source_code)?;
let wasm_module = module_generator::generate_module(quickjs_bytecode, &js_source_code)?;
let js = JS::from_file(&opts.input)?;
let wasm_module = module_generator::generate_module(&js)?;
let mut output_file = fs::File::create(&opts.output)?;
output_file.write_all(&wasm_module)?;
Ok(())
}

fn read_input_file(path: &Path) -> Result<Vec<u8>> {
let mut input_file = fs::File::open(path)
.with_context(|| format!("Failed to open input file {}", path.display()))?;
let mut contents: Vec<u8> = vec![];
input_file.read_to_end(&mut contents)?;
Ok(contents)
}
10 changes: 7 additions & 3 deletions crates/cli/src/module_generator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use anyhow::Result;
use walrus::{DataKind, FunctionBuilder, Module, ValType};

use crate::transform::{self, SourceCodeSection};
use crate::{
js::JS,
transform::{self, SourceCodeSection},
};

// Run the calling code with the `dump_wat` feature enabled to print the WAT to stdout
//
Expand Down Expand Up @@ -31,7 +34,7 @@ use crate::transform::{self, SourceCodeSection};
// (export "_start" (func 2))
// (data (;0;) "\02\04\18function.mjs\0econsole\06log\18Hello world!\0f\bc\03\00\00\00\00\0e\00\06\01\a0\01\00\00\00\03\00\00\17\00\08\ea\02)8\df\00\00\00B\e0\00\00\00\04\e1\00\00\00$\01\00)\bc\03\01\02\01\17")
// )
pub fn generate_module(bytecode: Vec<u8>, js_src: &[u8]) -> Result<Vec<u8>> {
pub fn generate_module(js: &JS) -> Result<Vec<u8>> {
let mut module = Module::with_config(transform::module_config());

const IMPORT_NAMESPACE: &str = "javy_quickjs_provider_v1";
Expand All @@ -53,8 +56,9 @@ pub fn generate_module(bytecode: Vec<u8>, js_src: &[u8]) -> Result<Vec<u8>> {
let (memory, _) = module.add_import_memory(IMPORT_NAMESPACE, "memory", false, 0, None);

transform::add_producers_section(&mut module.producers);
module.customs.add(SourceCodeSection::new(js_src)?);
module.customs.add(SourceCodeSection::new(js)?);

let bytecode = js.compile()?;
let bytecode_len: i32 = bytecode.len().try_into()?;
let bytecode_data = module.data.add(DataKind::Passive, bytecode);

Expand Down
18 changes: 5 additions & 13 deletions crates/cli/src/transform.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
use std::{borrow::Cow, io::Cursor};
use std::borrow::Cow;

use anyhow::Result;
use brotli::enc::{self, BrotliEncoderParams};
use walrus::{CustomSection, IdsToIndices, ModuleConfig, ModuleProducers};

use crate::js::JS;

#[derive(Debug)]
pub struct SourceCodeSection {
compressed_source_code: Vec<u8>,
}

impl SourceCodeSection {
pub fn new(source_code: &[u8]) -> Result<SourceCodeSection> {
let mut compressed_source_code: Vec<u8> = vec![];
enc::BrotliCompress(
&mut Cursor::new(source_code),
&mut compressed_source_code,
&BrotliEncoderParams {
quality: 11,
..Default::default()
},
)?;
pub fn new(js: &JS) -> Result<SourceCodeSection> {
Ok(SourceCodeSection {
compressed_source_code,
compressed_source_code: js.compress()?,
})
}
}
Expand Down

0 comments on commit bcbed2d

Please sign in to comment.