use crate::PropertyKey;
use crate::bytecode::{self, CodeStore, CodeUnit, CodeUnitId, ValidateError};
use crate::heap::atom::KeyKind;
use crate::heap::gc::{Handle, HeapLimits};
use crate::heap::object::ObjectKind;
use crate::heap::{Heap, HeapError};
use crate::value::{ObjectId, ScopeId, StringId, SymbolId, Value};
use crate::vm::regexp::RegExpStore;
use crate::vm::{Cx, ErrorKind, Output, Services, Throw, VmStack};
macro_rules! intrinsics {
($($(#[$doc:meta])* $name:ident,)*) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Intrinsic {
$($(#[$doc])* $name,)*
}
impl Intrinsic {
pub const ALL: &'static [Intrinsic] = &[$(Intrinsic::$name,)*];
pub const COUNT: usize = Intrinsic::ALL.len();
}
};
}
intrinsics! {
ObjectPrototype,
FunctionPrototype,
ArrayPrototype,
StringPrototype,
RegExpPrototype,
NumberPrototype,
BooleanPrototype,
SymbolPrototype,
BigIntPrototype,
DatePrototype,
ErrorPrototype,
EvalErrorPrototype,
RangeErrorPrototype,
ReferenceErrorPrototype,
SyntaxErrorPrototype,
TypeErrorPrototype,
InternalErrorPrototype,
UriErrorPrototype,
AggregateErrorPrototype,
OutOfMemoryError,
ThrowTypeError,
Object,
Function,
Array,
String,
Number,
Boolean,
Symbol,
BigInt,
RegExp,
Date,
Promise,
Proxy,
Eval,
IteratorPrototype,
AsyncIteratorPrototype,
ArrayIteratorPrototype,
StringIteratorPrototype,
MapIteratorPrototype,
SetIteratorPrototype,
RegExpStringIteratorPrototype,
IteratorHelperPrototype,
Iterator,
WrapForValidIteratorPrototype,
AsyncFromSyncIteratorPrototype,
GeneratorFunctionPrototype,
GeneratorPrototype,
AsyncFunctionPrototype,
AsyncGeneratorFunctionPrototype,
AsyncGeneratorPrototype,
GeneratorFunction,
AsyncFunction,
AsyncGeneratorFunction,
PromisePrototype,
MapPrototype,
SetPrototype,
WeakMapPrototype,
WeakSetPrototype,
WeakRefPrototype,
FinalizationRegistryPrototype,
ArrayBufferPrototype,
SharedArrayBufferPrototype,
DataViewPrototype,
TypedArray,
TypedArrayPrototype,
Int8ArrayPrototype,
Uint8ArrayPrototype,
Uint8ClampedArrayPrototype,
Int16ArrayPrototype,
Uint16ArrayPrototype,
Int32ArrayPrototype,
Uint32ArrayPrototype,
Float16ArrayPrototype,
Float32ArrayPrototype,
Float64ArrayPrototype,
BigInt64ArrayPrototype,
BigUint64ArrayPrototype,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float16Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
ArrayBuffer,
ArrayValues,
GlobalLexical,
}
macro_rules! names {
($($field:ident: $text:expr,)*) => {
#[derive(Clone, Copy, Debug)]
pub(crate) struct Names {
$(pub $field: StringId,)*
}
impl Names {
fn new(heap: &mut Heap) -> Result<Names, HeapError> {
Ok(Names {
$($field: heap.intern($text)?,)*
})
}
}
};
}
names! {
length: "length",
callee: "callee",
caller: "caller",
arguments: "arguments",
last_index: "lastIndex",
index: "index",
indices: "indices",
input: "input",
groups: "groups",
prototype: "prototype",
constructor: "constructor",
message: "message",
name: "name",
cause: "cause",
stack: "stack",
file_name: "fileName",
line_number: "lineNumber",
column_number: "columnNumber",
errors: "errors",
value_of: "valueOf",
to_string: "toString",
to_json: "toJSON",
undefined: "undefined",
null: "null",
true_: "true",
false_: "false",
object: "object",
boolean: "boolean",
number: "number",
string: "string",
symbol: "symbol",
function: "function",
bigint: "bigint",
empty: "",
value: "value",
writable: "writable",
enumerable: "enumerable",
configurable: "configurable",
get: "get",
set: "set",
next: "next",
done: "done",
return_: "return",
throw: "throw",
then: "then",
proto: "__proto__",
raw: "raw",
join: "join",
default: "default",
flags: "flags",
source: "source",
exec: "exec",
size: "size",
iterator_tag: "Iterator",
resolve: "resolve",
reject: "reject",
promise: "promise",
status: "status",
reason: "reason",
fulfilled: "fulfilled",
rejected: "rejected",
}
impl Names {
#[inline]
pub fn key(id: StringId) -> PropertyKey {
PropertyKey::atom(id)
}
}
macro_rules! well_known {
($($field:ident: $text:expr,)*) => {
#[derive(Clone, Copy, Debug)]
pub(crate) struct WellKnown {
$(pub $field: SymbolId,)*
}
impl WellKnown {
fn new(heap: &mut Heap) -> Result<WellKnown, HeapError> {
Ok(WellKnown {
$($field: {
let description = heap.intern($text)?;
let symbol = heap.alloc_symbol(Some(description), false)?;
heap.add_fixed_root(Value::symbol(symbol));
symbol
},)*
})
}
pub fn all(&self) -> Vec<(&'static str, SymbolId)> {
vec![$((&$text["Symbol.".len()..], self.$field),)*]
}
}
};
}
well_known! {
async_iterator: "Symbol.asyncIterator",
has_instance: "Symbol.hasInstance",
is_concat_spreadable: "Symbol.isConcatSpreadable",
iterator: "Symbol.iterator",
match_: "Symbol.match",
match_all: "Symbol.matchAll",
replace: "Symbol.replace",
search: "Symbol.search",
species: "Symbol.species",
split: "Symbol.split",
to_primitive: "Symbol.toPrimitive",
to_string_tag: "Symbol.toStringTag",
unscopables: "Symbol.unscopables",
}
impl WellKnown {
#[inline]
pub fn key(id: SymbolId) -> PropertyKey {
PropertyKey::symbol(id)
}
}
pub struct Realm {
global: ObjectId,
intrinsics: [std::cell::Cell<ObjectId>; Intrinsic::COUNT],
pub(crate) names: Names,
pub(crate) symbols: WellKnown,
pub(crate) brand: SymbolId,
pub(crate) regexp_flags: std::cell::Cell<Option<crate::builtins::RegExpFlagCache>>,
pub(crate) regexp_instance_shape: std::cell::Cell<Option<(crate::value::ShapeId, u32)>>,
}
impl Realm {
#[inline]
pub fn global(&self) -> ObjectId {
self.global
}
#[inline]
pub fn intrinsic(&self, intrinsic: Intrinsic) -> ObjectId {
self.intrinsics[intrinsic as usize].get()
}
pub(crate) fn record_intrinsic(&self, intrinsic: Intrinsic, object: ObjectId) {
self.intrinsics[intrinsic as usize].set(object);
}
}
pub struct Runtime {
pub(crate) heap: Heap,
pub(crate) code: CodeStore,
pub(crate) realm: Realm,
pub(crate) host: Services,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RuntimeOptions {
pub heap: HeapLimits,
pub min_heap: usize,
pub growth: f64,
pub stack_limit: u32,
pub native_stack_budget: usize,
pub native_depth_limit: u32,
}
impl Default for RuntimeOptions {
fn default() -> Self {
RuntimeOptions {
heap: HeapLimits::default(),
min_heap: crate::heap::gc::DEFAULT_MIN_HEAP,
growth: 2.0,
stack_limit: crate::vm::DEFAULT_STACK_LIMIT,
native_stack_budget: crate::vm::NATIVE_STACK_BUDGET,
native_depth_limit: u32::MAX,
}
}
}
impl From<HeapLimits> for RuntimeOptions {
fn from(heap: HeapLimits) -> Self {
RuntimeOptions {
heap,
..RuntimeOptions::default()
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ThrownSummary {
pub name: Option<String>,
pub message: Option<String>,
pub stack: Option<String>,
}
impl Runtime {
pub fn new(options: impl Into<RuntimeOptions>) -> Result<Runtime, HeapError> {
let options = options.into();
let mut heap = Heap::new(options.heap);
let reserved = heap.bytes();
heap.gc
.configure(options.min_heap, options.growth, reserved);
heap.vm.limit = options.stack_limit;
heap.vm.native_budget = options.native_stack_budget;
heap.vm.native_depth = options.native_depth_limit;
let root = |heap: &mut Heap, object: ObjectId| {
heap.add_fixed_root(Value::object(object));
object
};
let object_prototype = heap.alloc_object(ObjectKind::Ordinary, None, 4)?;
let object_prototype = root(&mut heap, object_prototype);
let function_prototype = heap.alloc_native_function(
Some(object_prototype),
crate::builtins::Native::FunctionPrototype.index(),
)?;
let function_prototype = root(&mut heap, function_prototype);
let array_prototype = heap.alloc_array(Some(object_prototype), 0)?;
let array_prototype = root(&mut heap, array_prototype);
let string_prototype =
heap.alloc_object(ObjectKind::Ordinary, Some(object_prototype), 4)?;
let string_prototype = root(&mut heap, string_prototype);
let regexp_prototype =
heap.alloc_object(ObjectKind::Ordinary, Some(object_prototype), 4)?;
let regexp_prototype = root(&mut heap, regexp_prototype);
let number_prototype = heap.alloc_internal(
ObjectKind::NumberWrapper,
crate::heap::object::InternalTag::None,
Some(object_prototype),
4,
&[Value::int32(0)],
Default::default(),
0,
)?;
let number_prototype = root(&mut heap, number_prototype);
let boolean_prototype = heap.alloc_internal(
ObjectKind::BooleanWrapper,
crate::heap::object::InternalTag::None,
Some(object_prototype),
4,
&[Value::FALSE],
Default::default(),
0,
)?;
let boolean_prototype = root(&mut heap, boolean_prototype);
let global = heap.alloc_object(ObjectKind::GlobalObject, Some(object_prototype), 16)?;
let global = root(&mut heap, global);
let lexical = heap.alloc_object(ObjectKind::GlobalObject, None, 16)?;
let lexical = root(&mut heap, lexical);
let names = Names::new(&mut heap)?;
let symbols = WellKnown::new(&mut heap)?;
let brand = heap.alloc_symbol(None, true)?;
heap.add_fixed_root(Value::symbol(brand));
let mut intrinsics = [object_prototype; Intrinsic::COUNT];
intrinsics[Intrinsic::FunctionPrototype as usize] = function_prototype;
intrinsics[Intrinsic::ArrayPrototype as usize] = array_prototype;
intrinsics[Intrinsic::StringPrototype as usize] = string_prototype;
intrinsics[Intrinsic::RegExpPrototype as usize] = regexp_prototype;
intrinsics[Intrinsic::NumberPrototype as usize] = number_prototype;
intrinsics[Intrinsic::BooleanPrototype as usize] = boolean_prototype;
intrinsics[Intrinsic::GlobalLexical as usize] = lexical;
let error_prototype = heap.alloc_object(ObjectKind::Ordinary, Some(object_prototype), 4)?;
intrinsics[Intrinsic::ErrorPrototype as usize] = root(&mut heap, error_prototype);
for kind in ErrorKind::ALL {
if kind != ErrorKind::Error {
let prototype =
heap.alloc_object(ObjectKind::Ordinary, Some(error_prototype), 4)?;
intrinsics[kind.prototype() as usize] = root(&mut heap, prototype);
}
}
let realm = Realm {
global,
intrinsics: intrinsics.map(std::cell::Cell::new),
names,
symbols,
brand,
regexp_flags: std::cell::Cell::new(None),
regexp_instance_shape: std::cell::Cell::new(None),
};
let mut runtime = Runtime {
heap,
code: CodeStore::default(),
realm,
host: Services::default(),
};
let out_of_memory = crate::builtins::install(&mut runtime.cx())?;
runtime.set_intrinsic(Intrinsic::OutOfMemoryError, out_of_memory);
runtime.heap.set_atom_mode(crate::heap::AtomMode::Weak);
Ok(runtime)
}
fn installing<T>(&mut self, install: impl FnOnce(&mut Cx<'_>) -> T) -> T {
self.native_cx().installing(install)
}
#[inline]
pub(crate) fn cx(&mut self) -> Cx<'_> {
Cx {
heap: &mut self.heap,
static_code: &self.code,
realm: &self.realm,
host: &mut self.host,
}
}
pub(crate) fn set_intrinsic(&mut self, intrinsic: Intrinsic, object: ObjectId) {
self.heap.add_fixed_root(Value::object(object));
self.realm.record_intrinsic(intrinsic, object);
}
pub fn regexps(&self) -> &RegExpStore {
&self.heap.regexps
}
pub fn realm(&self) -> &Realm {
&self.realm
}
pub fn host(&mut self) -> &mut Services {
&mut self.host
}
pub fn set_can_block(&mut self, can_block: bool) {
self.host.can_block = can_block;
}
pub fn set_host(&mut self, mut hooks: Box<dyn crate::host::Host>) {
let seed = hooks.random_seed();
self.host.seed_random(seed);
self.host.hooks = hooks;
}
pub fn set_track_rejections(&mut self, track: bool) {
self.host.track_rejections = track;
if !track {
self.heap.rejections.clear();
}
}
pub fn unhandled_rejections(&self) -> &[Value] {
&self.heap.rejections
}
pub fn clear_unhandled_rejections(&mut self) {
self.heap.rejections.clear();
}
pub fn capture_output(&mut self) {
self.host.output = Output::Capture(String::new());
}
pub fn take_output(&mut self) -> String {
match &mut self.host.output {
Output::Capture(text) => std::mem::take(text),
Output::Stdout => String::new(),
}
}
pub fn install_test262_host(&mut self) -> Result<(), HeapError> {
self.installing(crate::builtins::test262::install)
}
pub fn install_print(&mut self) -> Result<(), HeapError> {
self.installing(crate::builtins::install_print)
}
pub fn install_std(&mut self) -> Result<(), HeapError> {
self.native_cx().install_std()
}
pub fn install_qjs_helpers(&mut self, script_args: Option<&[String]>) -> Result<(), HeapError> {
self.native_cx().install_qjs_helpers(script_args)
}
pub fn install_script_args(&mut self, args: &[String]) -> Result<(), HeapError> {
self.installing(|cx| crate::builtins::install_script_args(cx, args))
}
pub fn run_script(&mut self, script: ObjectId) -> Result<Value, Throw> {
self.native_cx().run_script(script)
}
pub fn run_jobs(&mut self) -> Result<(), Throw> {
crate::vm::jobs::run_jobs(&mut self.cx())
}
pub fn run_pending_job(&mut self) -> Result<bool, Throw> {
crate::vm::jobs::run_one_job(&mut self.cx())
}
pub fn clear_kept_objects(&mut self) {
self.heap.kept.clear();
}
pub fn has_pending_jobs(&self) -> bool {
!self.heap.jobs.is_empty()
}
pub fn call(&mut self, function: Value, this: Value, args: &[Value]) -> Result<Value, Throw> {
self.native_cx().call(function, this, args)
}
pub fn display(&mut self, value: Value) -> Result<String, Throw> {
crate::vm::ops::display(&mut self.cx(), value)
}
pub fn vm_stack(&self) -> &VmStack {
&self.heap.vm
}
pub fn set_stack_limit(&mut self, registers: u32) {
self.heap.vm.limit = registers;
}
pub fn set_native_stack_budget(&mut self, bytes: usize) {
self.heap.vm.native_budget = bytes;
}
#[inline]
pub fn heap(&self) -> &Heap {
&self.heap
}
#[inline]
pub fn heap_mut(&mut self) -> &mut Heap {
&mut self.heap
}
#[inline]
pub fn code(&self) -> &CodeStore {
&self.code
}
#[inline]
pub fn units(&self) -> crate::bytecode::Units<'_> {
crate::bytecode::Units {
code: &self.code,
dynamic: &self.heap.code,
}
}
#[inline]
pub fn heap_and_code(&mut self) -> (&mut Heap, &CodeStore) {
(&mut self.heap, &self.code)
}
pub fn publish(&mut self, unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
self.compiler().publish(unit)
}
pub fn instantiate(
&mut self,
root: CodeUnitId,
constants: &[&[Value]],
) -> Result<ObjectId, HeapError> {
self.compiler().instantiate(root, constants)
}
pub fn compiler(&mut self) -> crate::vm::compile::Compiler<'_> {
crate::vm::compile::Compiler::owned(&mut self.heap, &mut self.code, &self.realm)
}
pub fn transient_compiler(&mut self) -> crate::vm::compile::Compiler<'_> {
crate::vm::compile::Compiler::new(&mut self.heap, &self.code, &self.realm)
}
pub fn set_compiler(&mut self, compile: crate::vm::compile::CompileFn) {
self.host.compile = Some(compile);
}
pub fn ic_report(&self, worst: usize) -> Option<String> {
use std::fmt::Write;
use crate::vm::ic::IcKind;
let ics = self.heap.ics();
let stats = ics.stats()?;
let mut out = String::new();
let _ = writeln!(
out,
"{:<10}{:>14}{:>14}{:>14}{:>14}{:>9}",
"ic", "mono hits", "poly hits", "fills", "unfilled", "hit %"
);
let kinds = [IcKind::Get, IcKind::Set, IcKind::Global, IcKind::Construct];
for kind in kinds {
let k = kind as usize;
let hits = stats.mono_hits[k] + stats.poly_hits[k];
let total = hits + stats.fills[k] + stats.unfilled[k];
let rate = if total == 0 {
0.0
} else {
100.0 * hits as f64 / total as f64
};
let _ = writeln!(
out,
"{:<10}{:>14}{:>14}{:>14}{:>14}{:>8.2}%",
kind.name(),
stats.mono_hits[k],
stats.poly_hits[k],
stats.fills[k],
stats.unfilled[k],
rate
);
}
let (mono, poly, mega) = ics.site_states();
let _ = writeln!(
out,
"sites: {} entries, {mono} monomorphic, {poly} polymorphic, {mega} megamorphic",
ics.len()
);
let _ = writeln!(
out,
"iteration: {} pristine arrays, {} through the protocol",
stats.iter_pristine, stats.iter_generic
);
let mut sites = Vec::new();
for (id, unit) in self.units().all() {
for (pc, insn) in unit.code.iter().enumerate() {
if let Some(site) = insn.site() {
let entry = (unit.ic_base + site) as usize;
let misses = stats.site_misses[entry];
if misses > 0 {
sites.push((misses, id, pc, *insn));
}
}
}
}
sites.sort_by_key(|&(misses, id, pc, _)| (std::cmp::Reverse(misses), id, pc));
for &(misses, id, pc, insn) in sites.iter().take(worst) {
let unit = self.units().get(id);
let name = if unit.debug.name.is_empty() {
"<anonymous>"
} else {
&unit.debug.name
};
let key = match insn.op {
crate::bytecode::Opcode::New => String::new(),
_ => crate::vm::property::key_text(&self.heap, insn.key()),
};
let _ = writeln!(
out,
" {misses:>10} misses {name} @{pc} {} {key}",
insn.op.name()
);
}
Some(out)
}
pub fn disassemble(&self, unit: CodeUnitId) -> String {
let heap = &self.heap;
bytecode::disassemble(&self.units().get(unit), &mut |key| match key.kind() {
KeyKind::String(id) => format!(".{}", heap.string_text(id)),
_ => bytecode::disasm::key_placeholder(key),
})
}
pub fn global_object(&self) -> ObjectId {
self.realm.global
}
pub fn intrinsic(&self, intrinsic: Intrinsic) -> ObjectId {
self.realm.intrinsic(intrinsic)
}
pub fn template_object(
&mut self,
cooked: &[Value],
raw: &[Value],
) -> Result<ObjectId, HeapError> {
self.compiler().template_object(cooked, raw)
}
pub fn new_object(&mut self) -> Result<ObjectId, HeapError> {
self.native_cx().new_object()
}
pub fn new_array(&mut self, capacity: u32) -> Result<ObjectId, HeapError> {
let prototype = self.intrinsic(Intrinsic::ArrayPrototype);
self.heap.alloc_array(Some(prototype), capacity)
}
pub fn new_function(&mut self, scope: Option<ScopeId>) -> Result<ObjectId, HeapError> {
let prototype = self.intrinsic(Intrinsic::FunctionPrototype);
self.heap.alloc_function(Some(prototype), scope)
}
pub fn new_scope(&mut self, parent: Option<ScopeId>, slots: u32) -> Result<ScopeId, HeapError> {
self.heap.alloc_scope(parent, slots)
}
pub fn thrown_summary(&mut self, value: Value) -> Option<ThrownSummary> {
let object = value.as_object()?;
let heap = &mut self.heap;
let (Ok(name), Ok(message), Ok(constructor)) = (
heap.intern("name"),
heap.intern("message"),
heap.intern("constructor"),
) else {
return Some(ThrownSummary::default());
};
let heap = &self.heap;
let names = &self.realm.names;
let own = |object: ObjectId, key: StringId| {
crate::vm::property::own(heap, names, object, Names::key(key))
};
let own_string = |object: ObjectId, key: StringId| match own(object, key) {
Some(crate::vm::property::Found::Data(value)) => {
Some(value.as_string().map(|id| heap.string_text(id)))
}
Some(_) => Some(None),
None => None,
};
let named = |found: Option<Option<String>>| match found {
Some(Some(name)) if !name.is_empty() => Some(Some(name)),
Some(_) => Some(None),
None => None,
};
let error_name = named(own_string(object, name))
.or_else(|| {
let prototype = heap.prototype_of(object)?;
named(own_string(prototype, name)).or_else(|| {
let Some(crate::vm::property::Found::Data(function)) =
own(prototype, constructor)
else {
return Some(None);
};
let function = function.as_object()?;
Some(
own_string(function, name)
.flatten()
.filter(|name| !name.is_empty() && name != "Object"),
)
})
})
.flatten();
Some(ThrownSummary {
name: error_name,
message: own_string(object, message).flatten(),
stack: own_string(object, self.realm.names.stack).flatten(),
})
}
pub fn memory_report(&self) -> crate::heap::report::MemoryReport {
let mut report = self.heap.report();
let code = &mut report.code;
code.static_units = self.code.len();
for (_, unit) in self.units().all() {
code.units += 1;
code.instructions += unit.code.len();
code.instruction_bytes += std::mem::size_of_val(&*unit.code);
code.position_bytes += std::mem::size_of_val(&*unit.debug.positions);
code.source_bytes += unit.source.as_ref().map_or(0, |source| source.len());
}
report
}
pub fn hold(&mut self, value: Value) -> Handle {
self.native_cx().hold(value)
}
pub fn held(&self, handle: Handle) -> Value {
self.heap.handles_ref().get(handle)
}
pub fn release(&mut self, handle: Handle) {
self.native_cx().release(handle);
}
}