use super::native::CallSite;
use super::{Cx, Throw, error, ops};
use crate::ErrorKind;
use crate::bytecode::{CodeStore, CodeUnit, CodeUnitId, Units, ValidateError, validate};
use crate::heap::hash::FxHashMap;
use crate::heap::{AtomMode, BytecodeTemplate, Heap, HeapError};
use crate::runtime::{Intrinsic, Realm};
use crate::value::{ObjectId, Value};
enum Store<'a> {
Owned(&'a mut CodeStore),
Shared(&'a CodeStore),
}
pub struct Compiler<'a> {
heap: &'a mut Heap,
code: Store<'a>,
realm: &'a Realm,
mode: AtomMode,
pins: usize,
held: usize,
ends: bool,
}
impl Drop for Compiler<'_> {
fn drop(&mut self) {
if !self.ends {
return;
}
self.heap.set_atom_mode(self.mode);
self.heap.code.unpin(self.pins);
self.heap.release_held_atoms(self.held);
}
}
impl<'a> Compiler<'a> {
fn with(heap: &'a mut Heap, code: Store<'a>, realm: &'a Realm, mode: AtomMode) -> Self {
let pins = heap.code.pin_mark();
let held = heap.held_atoms_mark();
let mode = heap.set_atom_mode(mode);
Compiler {
heap,
code,
realm,
mode,
pins,
held,
ends: true,
}
}
pub(crate) fn new(heap: &'a mut Heap, code: &'a CodeStore, realm: &'a Realm) -> Self {
Compiler::with(heap, Store::Shared(code), realm, AtomMode::Hold)
}
pub(crate) fn owned(heap: &'a mut Heap, code: &'a mut CodeStore, realm: &'a Realm) -> Self {
Compiler::with(heap, Store::Owned(code), realm, AtomMode::Pin)
}
pub fn reborrow(&mut self) -> Compiler<'_> {
let code = match &mut self.code {
Store::Owned(code) => Store::Owned(code),
Store::Shared(code) => Store::Shared(code),
};
Compiler {
heap: self.heap,
code,
realm: self.realm,
mode: self.mode,
pins: self.pins,
held: self.held,
ends: false,
}
}
pub fn new_host_function(
&mut self,
name: &str,
length: u32,
constructor: bool,
data: &[Value],
body: std::rc::Rc<super::native::HostFn>,
) -> Result<ObjectId, HeapError> {
crate::embed::host_function(self.heap, self.realm, name, length, constructor, data, body)
}
pub fn new_host_object(
&mut self,
prototype: Option<ObjectId>,
data: std::rc::Rc<dyn std::any::Any>,
) -> Result<ObjectId, HeapError> {
crate::embed::host_object(self.heap, prototype, data)
}
pub fn hold(&mut self, value: Value) -> crate::Handle {
self.heap.handles().create(value)
}
pub fn new_error(&mut self, kind: ErrorKind, message: &str) -> Result<Value, HeapError> {
let prototype = self.realm.intrinsic(kind.prototype());
let message = self.heap.alloc_string(message)?;
let mark = self.heap.roots().mark();
self.heap.roots().push(Value::string(message));
let result = (|| {
let object = self
.heap
.alloc_object(crate::ObjectKind::Ordinary, Some(prototype), 1)?;
self.heap
.set_object_flag(object, crate::heap::object::flags::ERROR);
let key = crate::runtime::Names::key(self.realm.names.message);
self.heap.add_property(
object,
key,
crate::PropertyFlags::data(true, false, true),
Value::string(message),
)?;
let empty = self.heap.intern("")?;
let stack = crate::runtime::Names::key(self.realm.names.stack);
self.heap.add_property(
object,
stack,
crate::PropertyFlags::data(true, false, true),
Value::string(empty),
)?;
Ok(Value::object(object))
})();
self.heap.roots().truncate(mark);
result
}
pub fn new_located_error(
&mut self,
kind: ErrorKind,
message: &str,
file: &str,
line: u32,
column: u32,
) -> Result<Value, HeapError> {
let error = self.new_error(kind, message)?;
let object = error.as_object().expect("an error object");
let names = self.realm.names;
let mark = self.heap.roots().mark();
self.heap.roots().push(error);
let result = (|| {
let flags = crate::PropertyFlags::data(true, false, true);
let stack_key = crate::runtime::Names::key(names.stack);
self.heap.delete_property(object, stack_key)?;
let file_text = self.heap.alloc_string(file)?;
self.heap.add_property(
object,
crate::runtime::Names::key(names.file_name),
flags,
Value::string(file_text),
)?;
self.heap.add_property(
object,
crate::runtime::Names::key(names.line_number),
flags,
Value::int32(line as i32),
)?;
self.heap.add_property(
object,
crate::runtime::Names::key(names.column_number),
flags,
Value::int32(column as i32),
)?;
let stack = self
.heap
.alloc_string(&format!(" at {file}:{line}:{column}\n"))?;
self.heap
.add_property(object, stack_key, flags, Value::string(stack))?;
Ok(error)
})();
self.heap.roots().truncate(mark);
result
}
pub fn heap(&self) -> &Heap {
self.heap
}
pub fn heap_mut(&mut self) -> &mut Heap {
self.heap
}
pub fn units(&self) -> Units<'_> {
let code = match &self.code {
Store::Owned(code) => &**code,
Store::Shared(code) => *code,
};
Units {
code,
dynamic: &self.heap.code,
}
}
pub fn intrinsic(&self, intrinsic: Intrinsic) -> ObjectId {
self.realm.intrinsic(intrinsic)
}
pub fn publish(&mut self, mut unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
validate::validate(&unit, &|id| self.units().lookup(id))?;
self.heap.ics.reserve(&mut unit);
match &mut self.code {
Store::Owned(code) => {
for &atom in &unit.key_atoms() {
self.heap.pin_atom(atom);
}
Ok(code.push(unit))
}
Store::Shared(_) => {
let (id, bytes) = self.heap.code.publish(unit);
self.heap.gc.allocated += bytes;
Ok(id)
}
}
}
pub fn instantiate(
&mut self,
root: CodeUnitId,
constants: &[&[Value]],
) -> Result<ObjectId, HeapError> {
let code = match &self.code {
Store::Owned(code) => &**code,
Store::Shared(code) => *code,
};
let code = Units {
code,
dynamic: &self.heap.code,
};
let order = code.tree(root);
assert_eq!(order.len(), constants.len(), "one constant table per unit");
let position: FxHashMap<CodeUnitId, u32> = order
.iter()
.enumerate()
.map(|(index, &unit)| (unit, index as u32))
.collect();
let children: Vec<Vec<u32>> = order
.iter()
.map(|&id| {
let unit = code.get(id);
unit.children.iter().map(|child| position[child]).collect()
})
.collect();
let tree: Vec<BytecodeTemplate<'_>> = order
.iter()
.zip(constants)
.zip(&children)
.map(|((&unit, &constants), children)| {
assert_eq!(
constants.len(),
code.get(unit).constant_count as usize,
"constant table length"
);
BytecodeTemplate {
unit,
constants,
children,
}
})
.collect();
let prototype = self.realm.intrinsic(Intrinsic::FunctionPrototype);
self.heap
.alloc_bytecode_function(Some(prototype), None, &tree)
}
}
impl Compiler<'_> {
pub fn template_object(
&mut self,
cooked: &[Value],
raw: &[Value],
) -> Result<ObjectId, HeapError> {
use crate::heap::object::flags;
let prototype = self.intrinsic(Intrinsic::ArrayPrototype);
let names = self.realm.names;
let heap = &mut *self.heap;
let mark = heap.roots().mark();
for &value in cooked.iter().chain(raw) {
heap.roots().push(value);
}
let result = (|| {
let raw_array = heap.alloc_array(Some(prototype), raw.len() as u32)?;
heap.roots().push(Value::object(raw_array));
let array = heap.alloc_array(Some(prototype), cooked.len() as u32)?;
heap.roots().push(Value::object(array));
for (target, values) in [(raw_array, raw), (array, cooked)] {
for (index, &value) in values.iter().enumerate() {
let key =
crate::PropertyKey::index(index as u32).ok_or(HeapError::OutOfMemory)?;
heap.add_property(
target,
key,
crate::PropertyFlags::data(false, true, false),
value,
)?;
}
heap.set_array_length(target, values.len() as u32);
}
let raw_key = crate::runtime::Names::key(names.raw);
heap.add_property(
array,
raw_key,
crate::PropertyFlags::data(false, false, false),
Value::object(raw_array),
)?;
for target in [raw_array, array] {
heap.set_object_flag(target, flags::SPARSE | flags::LENGTH_READONLY);
heap.clear_object_flag(target, flags::EXTENSIBLE);
}
Ok(array)
})();
heap.roots().truncate(mark);
result
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompileKind {
Script,
IndirectEval,
}
#[derive(Clone, Copy, Debug)]
pub struct CompileRequest<'s> {
pub source: &'s [u16],
pub kind: CompileKind,
pub filename: &'s str,
}
#[derive(Clone, Debug)]
pub struct CompileError {
pub kind: ErrorKind,
pub message: String,
pub thrown: Option<Value>,
pub location: Option<(Box<str>, u32, u32)>,
}
impl CompileError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
CompileError {
kind,
message: message.into(),
thrown: None,
location: None,
}
}
pub fn at(mut self, file: &str, line: u32, column: u32) -> Self {
self.location = Some((file.into(), line, column));
self
}
pub fn thrown(value: Value) -> Self {
CompileError {
kind: ErrorKind::Internal,
message: String::new(),
thrown: Some(value),
location: None,
}
}
}
pub(crate) fn throw_failure(cx: &mut Cx<'_>, failure: CompileError) -> Throw {
match (failure.thrown, failure.location) {
(Some(value), _) => Throw(value),
(None, Some((file, line, column))) => {
error::throw_located(cx, failure.kind, &failure.message, &file, line, column)
}
(None, None) => error::throw(cx, failure.kind, &failure.message),
}
}
pub type CompileFn = fn(&mut Compiler<'_>, &CompileRequest<'_>) -> Result<ObjectId, CompileError>;
pub(crate) fn compile(
cx: &mut Cx<'_>,
source: &[u16],
kind: CompileKind,
) -> Result<ObjectId, Throw> {
compile_named(cx, source, kind, "<input>")
}
pub(crate) fn compile_named(
cx: &mut Cx<'_>,
source: &[u16],
kind: CompileKind,
filename: &str,
) -> Result<ObjectId, Throw> {
let Some(compile) = cx.host.compile else {
return Err(super::object::unsupported(cx, "eval"));
};
let request = CompileRequest {
source,
kind,
filename,
};
let result = compile(
&mut Compiler::new(cx.heap, cx.static_code, cx.realm),
&request,
);
match result {
Ok(function) => Ok(function),
Err(failure) => Err(throw_failure(cx, failure)),
}
}
fn run(cx: &mut Cx<'_>, source: &[u16], kind: CompileKind) -> Result<Value, Throw> {
let function = compile(cx, source, kind)?;
let mark = cx.heap.roots().mark();
cx.heap.roots().push(Value::object(function));
let global = Value::object(cx.realm.global());
let result = super::call(cx, Value::object(function), global, &[]);
cx.heap.roots().truncate(mark);
result
}
pub(crate) fn eval(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let source = site.arg(cx, 0);
let Some(string) = source.as_string() else {
return Ok(source);
};
let units = ops::units(cx.heap, string);
run(cx, &units, CompileKind::IndirectEval)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FunctionKind {
Normal,
Generator,
Async,
AsyncGenerator,
}
pub(crate) fn function_constructor(
cx: &mut Cx<'_>,
site: CallSite,
kind: FunctionKind,
) -> Result<Value, Throw> {
let mut source: Vec<u16> = Vec::new();
let text = |source: &mut Vec<u16>, text: &str| source.extend(text.encode_utf16());
text(&mut source, "(");
if matches!(kind, FunctionKind::Async | FunctionKind::AsyncGenerator) {
text(&mut source, "async ");
}
text(&mut source, "function");
if matches!(kind, FunctionKind::Generator | FunctionKind::AsyncGenerator) {
text(&mut source, "*");
}
text(&mut source, " anonymous(");
let parameters = site.argc.saturating_sub(1);
for index in 0..parameters {
if index > 0 {
text(&mut source, ",");
}
let parameter = site.arg(cx, index);
let parameter = ops::to_string(cx, parameter)?;
source.extend(ops::units(cx.heap, parameter.as_string().unwrap()));
}
text(&mut source, "\n) {\n");
if site.argc > 0 {
let body = site.arg(cx, site.argc - 1);
let body = ops::to_string(cx, body)?;
source.extend(ops::units(cx.heap, body.as_string().unwrap()));
}
text(&mut source, "\n})");
let function = run(cx, &source, CompileKind::IndirectEval)?;
let Some(object) = function.as_object() else {
return Err(error::type_error(cx, "not an object"));
};
let default = match kind {
FunctionKind::Normal => Intrinsic::FunctionPrototype,
FunctionKind::Generator => Intrinsic::GeneratorFunctionPrototype,
FunctionKind::Async => Intrinsic::AsyncFunctionPrototype,
FunctionKind::AsyncGenerator => Intrinsic::AsyncGeneratorFunctionPrototype,
};
if site.construct && !site.new_target.is_undefined() {
let mark = cx.heap.roots().mark();
cx.heap.roots().push(function);
let prototype = super::object::prototype_from_constructor(cx, site.new_target, default);
cx.heap.roots().truncate(mark);
let prototype = prototype?;
if !super::object::set_prototype_of(cx, object, Value::object(prototype))? {
return Err(error::type_error(cx, "prototype is immutable"));
}
}
Ok(function)
}
pub(crate) fn function_kind_flags(cx: &Cx<'_>, function: ObjectId) -> Option<u16> {
if cx.heap.object_kind(function) != crate::ObjectKind::Function {
return None;
}
let bytecode = cx.heap.function_bytecode(function)?;
let unit = cx.heap.bytecode_unit(bytecode);
Some(cx.unit(unit).flags)
}
pub(crate) fn function_source(cx: &Cx<'_>, function: ObjectId) -> Option<Vec<u16>> {
if cx.heap.object_kind(function) != crate::ObjectKind::Function {
return None;
}
let bytecode = cx.heap.function_bytecode(function)?;
let unit = cx.heap.bytecode_unit(bytecode);
let unit = cx.unit(unit);
Some(wtf8_units(unit.source.as_ref()?))
}
fn wtf8_units(bytes: &[u8]) -> Vec<u16> {
let continuation = |byte: u8| (0x80..0xc0).contains(&byte);
let mut units = Vec::with_capacity(bytes.len());
let mut index = 0;
while index < bytes.len() {
let lead = bytes[index];
if lead < 0x80 {
units.push(u16::from(lead));
index += 1;
continue;
}
let (length, mask, minimum) = match lead {
0xc0..=0xdf => (1, 0x1f, 0x80),
0xe0..=0xef => (2, 0x0f, 0x800),
0xf0..=0xf7 => (3, 0x07, 0x1_0000),
0xf8..=0xfb => (4, 0x03, 0x20_0000),
0xfc..=0xfd => (5, 0x01, 0x400_0000),
_ => (0, 0, 0),
};
let decoded = (length > 0 && index + length < bytes.len())
.then(|| &bytes[index + 1..=index + length])
.filter(|tail| tail.iter().all(|&byte| continuation(byte)))
.map(|tail| {
tail.iter().fold(u32::from(lead & mask), |code, &byte| {
code << 6 | u32::from(byte & 0x3f)
})
})
.filter(|&code| code >= minimum && code <= 0x10_ffff);
match decoded {
Some(code) if code >= 0x1_0000 => {
let code = code - 0x1_0000;
units.push(0xd800 | (code >> 10) as u16);
units.push(0xdc00 | (code & 0x3ff) as u16);
index += length + 1;
}
Some(code) => {
units.push(code as u16);
index += length + 1;
}
None => {
units.push(0xfffd);
while index < bytes.len() && continuation(bytes[index]) {
index += 1;
}
if index < bytes.len() {
index += 1;
while index < bytes.len() && continuation(bytes[index]) {
index += 1;
}
}
}
}
}
units
}
pub(crate) fn load(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
load_script(cx, site).map(|_| Value::UNDEFINED)
}
pub(crate) fn load_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let path = site.arg(cx, 0);
let path = ops::to_string(cx, path)?;
let path = cx.heap.string_text(path.as_string().unwrap());
let Some(text) = cx.host.hooks.read_file(&path) else {
let message = format!("could not load '{path}'");
return Err(error::throw(cx, ErrorKind::Reference, &message));
};
let units: Vec<u16> = text.encode_utf16().collect();
let function = compile_named(cx, &units, CompileKind::Script, &path)?;
let mark = cx.heap.roots().mark();
cx.heap.roots().push(Value::object(function));
let global = Value::object(cx.realm.global());
let result = super::call(cx, Value::object(function), global, &[]);
cx.heap.roots().truncate(mark);
result
}
pub(crate) fn read(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let path = site.arg(cx, 0);
let path = ops::to_string(cx, path)?;
let path = cx.heap.string_text(path.as_string().unwrap());
let Some(text) = cx.host.hooks.read_file(&path) else {
let message = format!("could not read '{path}'");
return Err(error::throw(cx, ErrorKind::Error, &message));
};
crate::builtins::string_text(cx, &text)
}
pub(crate) fn eval_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let source = site.arg(cx, 0);
let source = ops::to_string(cx, source)?;
let units = ops::units(cx.heap, source.as_string().unwrap());
run(cx, &units, CompileKind::Script)
}
#[cfg(test)]
mod tests {
use crate::{Runtime, RuntimeOptions};
#[test]
fn a_compilation_holds_each_atom_once_until_it_ends() {
let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap();
let mut compiler = runtime.transient_compiler();
let mark = compiler.heap().held_atoms_mark();
let atom = compiler.heap_mut().intern("held once").unwrap();
for _ in 0..3 {
assert_eq!(compiler.heap_mut().intern("held once").unwrap(), atom);
}
assert_eq!(
compiler.heap_mut().intern_latin1(b"held once").unwrap(),
atom
);
assert_eq!(compiler.heap().held_atoms_mark(), mark + 1);
{
let mut view = compiler.reborrow();
view.heap_mut().intern("seen by the view").unwrap();
}
compiler.heap_mut().collect();
assert_eq!(compiler.heap().string_text(atom), "held once");
assert_eq!(compiler.heap().held_atoms_mark(), mark + 2);
drop(compiler);
assert_eq!(runtime.heap().held_atoms_mark(), mark);
}
}