Source code

Revision control

Copy as Markdown

Other Tools

/* Copyright Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0, or the MIT license,
* (the "Licenses") at your option. You may not use this file except in
* compliance with one of the Licenses. You may obtain copies of the
* Licenses at:
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licenses is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*/
use bumpalo;
use env_logger;
use jsparagus::ast::source_atom_set::SourceAtomSet;
use jsparagus::ast::source_slice_list::SourceSliceList;
use jsparagus::ast::types::Program;
use jsparagus::emitter::{emit, EmitError, EmitOptions};
use jsparagus::parser::{parse_module, parse_script, ParseError, ParseOptions};
use jsparagus::stencil::gcthings::GCThing;
use jsparagus::stencil::regexp::RegExpItem;
use jsparagus::stencil::result::EmitResult;
use jsparagus::stencil::scope::{BindingName, ScopeData};
use jsparagus::stencil::scope_notes::ScopeNote;
use jsparagus::stencil::script::{ImmutableScriptData, ScriptStencil, SourceExtent};
use std::boxed::Box;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryInto;
use std::os::raw::{c_char, c_void};
use std::rc::Rc;
use std::{mem, slice, str};
#[repr(C)]
pub struct CVec<T> {
pub data: *mut T,
pub len: usize,
pub capacity: usize,
}
impl<T> CVec<T> {
fn empty() -> CVec<T> {
Self {
data: std::ptr::null_mut(),
len: 0,
capacity: 0,
}
}
fn from(mut v: Vec<T>) -> CVec<T> {
let result = Self {
data: v.as_mut_ptr(),
len: v.len(),
capacity: v.capacity(),
};
mem::forget(v);
result
}
unsafe fn into(self) -> Vec<T> {
Vec::from_raw_parts(self.data, self.len, self.capacity)
}
}
#[repr(C)]
pub struct SmooshCompileOptions {
no_script_rval: bool,
}
#[repr(C, u8)]
pub enum SmooshGCThing {
Null,
Atom(usize),
Function(usize),
Scope(usize),
RegExp(usize),
}
fn convert_gcthing(item: GCThing, scope_index_map: &HashMap<usize, usize>) -> SmooshGCThing {
match item {
GCThing::Null => SmooshGCThing::Null,
GCThing::Atom(index) => SmooshGCThing::Atom(index.into()),
GCThing::Function(index) => SmooshGCThing::Function(index.into()),
GCThing::RegExp(index) => SmooshGCThing::RegExp(index.into()),
GCThing::Scope(index) => {
let mapped_index = *scope_index_map
.get(&index.into())
.expect("Scope map should be populated");
SmooshGCThing::Scope(mapped_index)
}
}
}
#[repr(C)]
pub struct SmooshBindingName {
name: usize,
is_closed_over: bool,
is_top_level_function: bool,
}
impl From<BindingName> for SmooshBindingName {
fn from(info: BindingName) -> Self {
Self {
name: info.name.into(),
is_closed_over: info.is_closed_over,
is_top_level_function: info.is_top_level_function,
}
}
}
#[repr(C)]
pub struct SmooshGlobalScopeData {
bindings: CVec<SmooshBindingName>,
let_start: usize,
const_start: usize,
}
#[repr(C)]
pub struct SmooshVarScopeData {
bindings: CVec<SmooshBindingName>,
enclosing: usize,
function_has_extensible_scope: bool,
first_frame_slot: u32,
}
#[repr(C)]
pub struct SmooshLexicalScopeData {
bindings: CVec<SmooshBindingName>,
const_start: usize,
enclosing: usize,
first_frame_slot: u32,
}
#[repr(C)]
pub struct SmooshFunctionScopeData {
bindings: CVec<COption<SmooshBindingName>>,
has_parameter_exprs: bool,
non_positional_formal_start: usize,
var_start: usize,
enclosing: usize,
first_frame_slot: u32,
function_index: usize,
is_arrow: bool,
}
#[repr(C, u8)]
pub enum SmooshScopeData {
Global(SmooshGlobalScopeData),
Var(SmooshVarScopeData),
Lexical(SmooshLexicalScopeData),
Function(SmooshFunctionScopeData),
}
/// Convert single Scope data, resolving enclosing index with scope_index_map.
fn convert_scope(scope: ScopeData, scope_index_map: &mut HashMap<usize, usize>) -> SmooshScopeData {
match scope {
ScopeData::Alias(_) => panic!("alias should be handled in convert_scopes"),
ScopeData::Global(data) => SmooshScopeData::Global(SmooshGlobalScopeData {
bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
let_start: data.let_start,
const_start: data.const_start,
}),
ScopeData::Var(data) => {
let enclosing: usize = data.enclosing.into();
SmooshScopeData::Var(SmooshVarScopeData {
bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
enclosing: *scope_index_map
.get(&enclosing)
.expect("Alias target should be earlier index"),
function_has_extensible_scope: data.function_has_extensible_scope,
first_frame_slot: data.first_frame_slot.into(),
})
}
ScopeData::Lexical(data) => {
let enclosing: usize = data.enclosing.into();
SmooshScopeData::Lexical(SmooshLexicalScopeData {
bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()),
const_start: data.const_start,
enclosing: *scope_index_map
.get(&enclosing)
.expect("Alias target should be earlier index"),
first_frame_slot: data.first_frame_slot.into(),
})
}
ScopeData::Function(data) => {
let enclosing: usize = data.enclosing.into();
SmooshScopeData::Function(SmooshFunctionScopeData {
bindings: CVec::from(
data.base
.bindings
.into_iter()
.map(|x| COption::from(x.map(|x| x.into())))
.collect(),
),
has_parameter_exprs: data.has_parameter_exprs,
non_positional_formal_start: data.non_positional_formal_start,
var_start: data.var_start,
enclosing: *scope_index_map
.get(&enclosing)
.expect("Alias target should be earlier index"),
first_frame_slot: data.first_frame_slot.into(),
function_index: data.function_index.into(),
is_arrow: data.is_arrow,
})
}
}
}
/// Convert list of Scope data, removing aliases.
/// Also create a map between original index into index into result vector
/// without aliases.
fn convert_scopes(
scopes: Vec<ScopeData>,
scope_index_map: &mut HashMap<usize, usize>,
) -> CVec<SmooshScopeData> {
let mut result = Vec::with_capacity(scopes.len());
for (i, scope) in scopes.into_iter().enumerate() {
if let ScopeData::Alias(index) = scope {
let mapped_index = *scope_index_map
.get(&index.into())
.expect("Alias target should be earlier index");
scope_index_map.insert(i, mapped_index);
continue;
}
scope_index_map.insert(i, result.len());
result.push(convert_scope(scope, scope_index_map))
}
CVec::from(result)
}
#[repr(C)]
pub struct SmooshScopeNote {
index: u32,
start: u32,
length: u32,
parent: u32,
}
impl From<ScopeNote> for SmooshScopeNote {
fn from(note: ScopeNote) -> Self {
let start = usize::from(note.start) as u32;
let end = usize::from(note.end) as u32;
let parent = match note.parent {
Some(index) => usize::from(index) as u32,
None => std::u32::MAX,
};
Self {
index: usize::from(note.index) as u32,
start,
length: end - start,
parent,
}
}
}
#[repr(C)]
pub struct SmooshRegExpItem {
pattern: usize,
global: bool,
ignore_case: bool,
multi_line: bool,
dot_all: bool,
sticky: bool,
unicode: bool,
}
impl From<RegExpItem> for SmooshRegExpItem {
fn from(data: RegExpItem) -> Self {
Self {
pattern: data.pattern.into(),
global: data.global,
ignore_case: data.ignore_case,
multi_line: data.multi_line,
dot_all: data.dot_all,
sticky: data.sticky,
unicode: data.unicode,
}
}
}
#[repr(C)]
pub struct SmooshImmutableScriptData {
pub main_offset: u32,
pub nfixed: u32,
pub nslots: u32,
pub body_scope_index: u32,
pub num_ic_entries: u32,
pub fun_length: u16,
pub bytecode: CVec<u8>,
pub scope_notes: CVec<SmooshScopeNote>,
}
#[repr(C)]
pub struct SmooshSourceExtent {
pub source_start: u32,
pub source_end: u32,
pub to_string_start: u32,
pub to_string_end: u32,
pub lineno: u32,
pub column: u32,
}
#[repr(C, u8)]
pub enum COption<T> {
Some(T),
None,
}
impl<T> COption<T> {
fn from(v: Option<T>) -> Self {
match v {
Option::Some(v) => COption::Some(v),
Option::None => COption::None,
}
}
}
#[repr(C)]
pub struct SmooshScriptStencil {
pub immutable_flags: u32,
pub gcthings: CVec<SmooshGCThing>,
pub immutable_script_data: COption<usize>,
pub extent: SmooshSourceExtent,
pub fun_name: COption<usize>,
pub fun_nargs: u16,
pub fun_flags: u16,
pub lazy_function_enclosing_scope_index: COption<usize>,
pub is_standalone_function: bool,
pub was_function_emitted: bool,
pub is_singleton_function: bool,
}
#[repr(C)]
pub struct SmooshResult {
unimplemented: bool,
error: CVec<u8>,
scopes: CVec<SmooshScopeData>,
regexps: CVec<SmooshRegExpItem>,
scripts: CVec<SmooshScriptStencil>,
script_data_list: CVec<SmooshImmutableScriptData>,
all_atoms: *mut c_void,
all_atoms_len: usize,
slices: *mut c_void,
slices_len: usize,
allocator: *mut c_void,
}
impl SmooshResult {
fn unimplemented() -> Self {
Self::unimplemented_or_error(true, CVec::empty())
}
fn error(message: String) -> Self {
let error = CVec::from(format!("{}\0", message).into_bytes());
Self::unimplemented_or_error(false, error)
}
fn unimplemented_or_error(unimplemented: bool, error: CVec<u8>) -> Self {
Self {
unimplemented,
error,
scopes: CVec::empty(),
regexps: CVec::empty(),
scripts: CVec::empty(),
script_data_list: CVec::empty(),
all_atoms: std::ptr::null_mut(),
all_atoms_len: 0,
slices: std::ptr::null_mut(),
slices_len: 0,
allocator: std::ptr::null_mut(),
}
}
}
enum SmooshError {
GenericError(String),
NotImplemented,
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_init() {
// Gecko might set a logger before we do, which is all fine; try to
// initialize ours, and reset the FilterLevel env_logger::try_init might
// have set to what it was in case of initialization failure
let filter = log::max_level();
match env_logger::try_init() {
Ok(_) => {}
Err(_) => {
log::set_max_level(filter);
}
}
}
fn convert_extent(extent: SourceExtent) -> SmooshSourceExtent {
SmooshSourceExtent {
source_start: extent.source_start,
source_end: extent.source_end,
to_string_start: extent.to_string_start,
to_string_end: extent.to_string_end,
lineno: extent.lineno,
column: extent.column,
}
}
fn convert_script(
script: ScriptStencil,
scope_index_map: &HashMap<usize, usize>,
) -> SmooshScriptStencil {
let immutable_flags = script.immutable_flags.into();
let gcthings = CVec::from(
script
.gcthings
.into_iter()
.map(|x| convert_gcthing(x, scope_index_map))
.collect(),
);
let immutable_script_data = COption::from(script.immutable_script_data.map(|n| n.into()));
let extent = convert_extent(script.extent);
let fun_name = COption::from(script.fun_name.map(|n| n.into()));
let fun_nargs = script.fun_nargs;
let fun_flags = script.fun_flags.into();
let lazy_function_enclosing_scope_index =
COption::from(script.lazy_function_enclosing_scope_index.map(|index| {
*scope_index_map
.get(&index.into())
.expect("Alias target should be earlier index")
}));
let is_standalone_function = script.is_standalone_function;
let was_function_emitted = script.was_function_emitted;
let is_singleton_function = script.is_singleton_function;
SmooshScriptStencil {
immutable_flags,
gcthings,
immutable_script_data,
extent,
fun_name,
fun_nargs,
fun_flags,
lazy_function_enclosing_scope_index,
is_standalone_function,
was_function_emitted,
is_singleton_function,
}
}
fn convert_script_data(script_data: ImmutableScriptData) -> SmooshImmutableScriptData {
let main_offset = script_data.main_offset;
let nfixed = script_data.nfixed.into();
let nslots = script_data.nslots;
let body_scope_index = script_data.body_scope_index;
let num_ic_entries = script_data.num_ic_entries;
let fun_length = script_data.fun_length;
let bytecode = CVec::from(script_data.bytecode);
let scope_notes = CVec::from(
script_data
.scope_notes
.into_iter()
.map(|x| x.into())
.collect(),
);
SmooshImmutableScriptData {
main_offset,
nfixed,
nslots,
body_scope_index,
num_ic_entries,
fun_length,
bytecode,
scope_notes,
}
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_run(
text: *const u8,
text_len: usize,
options: &SmooshCompileOptions,
) -> SmooshResult {
let text = str::from_utf8(slice::from_raw_parts(text, text_len)).expect("Invalid UTF8");
let allocator = Box::new(bumpalo::Bump::new());
match smoosh(&allocator, text, options) {
Ok(result) => {
let mut scope_index_map = HashMap::new();
let scopes = convert_scopes(result.scopes, &mut scope_index_map);
let regexps = CVec::from(result.regexps.into_iter().map(|x| x.into()).collect());
let scripts = CVec::from(
result
.scripts
.into_iter()
.map(|x| convert_script(x, &scope_index_map))
.collect(),
);
let script_data_list = CVec::from(
result
.script_data_list
.into_iter()
.map(convert_script_data)
.collect(),
);
let all_atoms_len = result.atoms.len();
let all_atoms = Box::new(result.atoms);
let raw_all_atoms = Box::into_raw(all_atoms);
let opaque_all_atoms = raw_all_atoms as *mut c_void;
let slices_len = result.slices.len();
let slices = Box::new(result.slices);
let raw_slices = Box::into_raw(slices);
let opaque_slices = raw_slices as *mut c_void;
let raw_allocator = Box::into_raw(allocator);
let opaque_allocator = raw_allocator as *mut c_void;
SmooshResult {
unimplemented: false,
error: CVec::empty(),
scopes,
regexps,
scripts,
script_data_list,
all_atoms: opaque_all_atoms,
all_atoms_len,
slices: opaque_slices,
slices_len,
allocator: opaque_allocator,
}
}
Err(SmooshError::GenericError(message)) => SmooshResult::error(message),
Err(SmooshError::NotImplemented) => SmooshResult::unimplemented(),
}
}
#[repr(C)]
pub struct SmooshParseResult {
unimplemented: bool,
error: CVec<u8>,
}
fn convert_parse_result<'alloc, T>(r: jsparagus::parser::Result<T>) -> SmooshParseResult {
match r {
Ok(_) => SmooshParseResult {
unimplemented: false,
error: CVec::empty(),
},
Err(err) => match *err {
ParseError::NotImplemented(_) => {
let message = err.message();
SmooshParseResult {
unimplemented: true,
error: CVec::from(format!("{}\0", message).into_bytes()),
}
}
_ => {
let message = err.message();
SmooshParseResult {
unimplemented: false,
error: CVec::from(format!("{}\0", message).into_bytes()),
}
}
},
}
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_test_parse_script(
text: *const u8,
text_len: usize,
) -> SmooshParseResult {
let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) {
Ok(text) => text,
Err(_) => {
return SmooshParseResult {
unimplemented: false,
error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()),
};
}
};
let allocator = bumpalo::Bump::new();
let parse_options = ParseOptions::new();
let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
let slices = Rc::new(RefCell::new(SourceSliceList::new()));
convert_parse_result(parse_script(
&allocator,
text,
&parse_options,
atoms,
slices,
))
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_test_parse_module(
text: *const u8,
text_len: usize,
) -> SmooshParseResult {
let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) {
Ok(text) => text,
Err(_) => {
return SmooshParseResult {
unimplemented: false,
error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()),
};
}
};
let allocator = bumpalo::Bump::new();
let parse_options = ParseOptions::new();
let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
let slices = Rc::new(RefCell::new(SourceSliceList::new()));
convert_parse_result(parse_module(
&allocator,
text,
&parse_options,
atoms,
slices,
))
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_free_parse_result(result: SmooshParseResult) {
let _ = result.error.into();
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_get_atom_at(result: SmooshResult, index: usize) -> *const c_char {
let all_atoms = result.all_atoms as *const Vec<&str>;
let atom = (*all_atoms)[index];
atom.as_ptr() as *const c_char
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_get_atom_len_at(result: SmooshResult, index: usize) -> usize {
let all_atoms = result.all_atoms as *const Vec<&str>;
let atom = (*all_atoms)[index];
atom.len()
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_get_slice_at(result: SmooshResult, index: usize) -> *const c_char {
let slices = result.slices as *const Vec<&str>;
let slice = (*slices)[index];
slice.as_ptr() as *const c_char
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_get_slice_len_at(result: SmooshResult, index: usize) -> usize {
let slices = result.slices as *const Vec<&str>;
let slice = (*slices)[index];
slice.len()
}
unsafe fn free_script(script: SmooshScriptStencil) {
let _ = script.gcthings.into();
}
unsafe fn free_script_data(script_data: SmooshImmutableScriptData) {
let _ = script_data.bytecode.into();
let _ = script_data.scope_notes.into();
}
#[no_mangle]
pub unsafe extern "C" fn smoosh_free(result: SmooshResult) {
let _ = result.error.into();
let _ = result.scopes.into();
let _ = result.regexps.into();
for fun in result.scripts.into() {
free_script(fun);
}
for script_data in result.script_data_list.into() {
free_script_data(script_data);
}
if !result.all_atoms.is_null() {
let _ = Box::from_raw(result.all_atoms as *mut Vec<&str>);
}
if !result.slices.is_null() {
let _ = Box::from_raw(result.slices as *mut Vec<&str>);
}
if !result.allocator.is_null() {
let _ = Box::from_raw(result.allocator as *mut bumpalo::Bump);
}
}
fn smoosh<'alloc>(
allocator: &'alloc bumpalo::Bump,
text: &'alloc str,
options: &SmooshCompileOptions,
) -> Result<EmitResult<'alloc>, SmooshError> {
let parse_options = ParseOptions::new();
let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
let slices = Rc::new(RefCell::new(SourceSliceList::new()));
let text_length = text.len();
let parse_result = match parse_script(
&allocator,
text,
&parse_options,
atoms.clone(),
slices.clone(),
) {
Ok(result) => result,
Err(err) => match *err {
ParseError::NotImplemented(_) => {
println!("Unimplemented: {}", err.message());
return Err(SmooshError::NotImplemented);
}
_ => {
return Err(SmooshError::GenericError(err.message()));
}
},
};
let extent = SourceExtent::top_level_script(text_length.try_into().unwrap(), 1, 0);
let mut emit_options = EmitOptions::new(extent);
emit_options.no_script_rval = options.no_script_rval;
let script = parse_result.unbox();
match emit(
allocator.alloc(Program::Script(script)),
&emit_options,
atoms.replace(SourceAtomSet::new_uninitialized()),
slices.replace(SourceSliceList::new()),
) {
Ok(result) => Ok(result),
Err(EmitError::NotImplemented(message)) => {
println!("Unimplemented: {}", message);
return Err(SmooshError::NotImplemented);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}