1use super::native::CallSite;
17use super::{Cx, Throw, error, ops};
18use crate::ErrorKind;
19use crate::bytecode::{CodeStore, CodeUnit, CodeUnitId, Units, ValidateError, validate};
20use crate::heap::hash::FxHashMap;
21use crate::heap::{AtomMode, BytecodeTemplate, Heap, HeapError};
22use crate::runtime::{Intrinsic, Realm};
23use crate::value::{ObjectId, Value};
24
25enum Store<'a> {
29 Owned(&'a mut CodeStore),
30 Shared(&'a CodeStore),
31}
32
33pub struct Compiler<'a> {
37 heap: &'a mut Heap,
38 code: Store<'a>,
39 realm: &'a Realm,
40 mode: AtomMode,
42 pins: usize,
44 held: usize,
45 ends: bool,
47}
48
49impl Drop for Compiler<'_> {
50 fn drop(&mut self) {
51 if !self.ends {
52 return;
53 }
54 self.heap.set_atom_mode(self.mode);
55 self.heap.code.unpin(self.pins);
56 self.heap.release_held_atoms(self.held);
57 }
58}
59
60impl<'a> Compiler<'a> {
61 fn with(heap: &'a mut Heap, code: Store<'a>, realm: &'a Realm, mode: AtomMode) -> Self {
62 let pins = heap.code.pin_mark();
63 let held = heap.held_atoms_mark();
64 let mode = heap.set_atom_mode(mode);
65 Compiler {
66 heap,
67 code,
68 realm,
69 mode,
70 pins,
71 held,
72 ends: true,
73 }
74 }
75
76 pub(crate) fn new(heap: &'a mut Heap, code: &'a CodeStore, realm: &'a Realm) -> Self {
77 Compiler::with(heap, Store::Shared(code), realm, AtomMode::Hold)
78 }
79
80 pub(crate) fn owned(heap: &'a mut Heap, code: &'a mut CodeStore, realm: &'a Realm) -> Self {
81 Compiler::with(heap, Store::Owned(code), realm, AtomMode::Pin)
82 }
83
84 pub fn reborrow(&mut self) -> Compiler<'_> {
86 let code = match &mut self.code {
87 Store::Owned(code) => Store::Owned(code),
88 Store::Shared(code) => Store::Shared(code),
89 };
90 Compiler {
91 heap: self.heap,
92 code,
93 realm: self.realm,
94 mode: self.mode,
95 pins: self.pins,
96 held: self.held,
97 ends: false,
98 }
99 }
100
101 pub fn new_host_function(
103 &mut self,
104 name: &str,
105 length: u32,
106 constructor: bool,
107 data: &[Value],
108 body: std::rc::Rc<super::native::HostFn>,
109 ) -> Result<ObjectId, HeapError> {
110 crate::embed::host_function(self.heap, self.realm, name, length, constructor, data, body)
111 }
112
113 pub fn new_host_object(
115 &mut self,
116 prototype: Option<ObjectId>,
117 data: std::rc::Rc<dyn std::any::Any>,
118 ) -> Result<ObjectId, HeapError> {
119 crate::embed::host_object(self.heap, prototype, data)
120 }
121
122 pub fn hold(&mut self, value: Value) -> crate::Handle {
125 self.heap.handles().create(value)
126 }
127
128 pub fn new_error(&mut self, kind: ErrorKind, message: &str) -> Result<Value, HeapError> {
131 let prototype = self.realm.intrinsic(kind.prototype());
132 let message = self.heap.alloc_string(message)?;
133 let mark = self.heap.roots().mark();
134 self.heap.roots().push(Value::string(message));
135 let result = (|| {
136 let object = self
137 .heap
138 .alloc_object(crate::ObjectKind::Ordinary, Some(prototype), 1)?;
139 self.heap
140 .set_object_flag(object, crate::heap::object::flags::ERROR);
141 let key = crate::runtime::Names::key(self.realm.names.message);
142 self.heap.add_property(
143 object,
144 key,
145 crate::PropertyFlags::data(true, false, true),
146 Value::string(message),
147 )?;
148 let empty = self.heap.intern("")?;
149 let stack = crate::runtime::Names::key(self.realm.names.stack);
150 self.heap.add_property(
151 object,
152 stack,
153 crate::PropertyFlags::data(true, false, true),
154 Value::string(empty),
155 )?;
156 Ok(Value::object(object))
157 })();
158 self.heap.roots().truncate(mark);
159 result
160 }
161
162 pub fn new_located_error(
166 &mut self,
167 kind: ErrorKind,
168 message: &str,
169 file: &str,
170 line: u32,
171 column: u32,
172 ) -> Result<Value, HeapError> {
173 let error = self.new_error(kind, message)?;
174 let object = error.as_object().expect("an error object");
175 let names = self.realm.names;
176 let mark = self.heap.roots().mark();
177 self.heap.roots().push(error);
178 let result = (|| {
179 let flags = crate::PropertyFlags::data(true, false, true);
180 let stack_key = crate::runtime::Names::key(names.stack);
181 self.heap.delete_property(object, stack_key)?;
185 let file_text = self.heap.alloc_string(file)?;
186 self.heap.add_property(
187 object,
188 crate::runtime::Names::key(names.file_name),
189 flags,
190 Value::string(file_text),
191 )?;
192 self.heap.add_property(
193 object,
194 crate::runtime::Names::key(names.line_number),
195 flags,
196 Value::int32(line as i32),
197 )?;
198 self.heap.add_property(
199 object,
200 crate::runtime::Names::key(names.column_number),
201 flags,
202 Value::int32(column as i32),
203 )?;
204 let stack = self
205 .heap
206 .alloc_string(&format!(" at {file}:{line}:{column}\n"))?;
207 self.heap
208 .add_property(object, stack_key, flags, Value::string(stack))?;
209 Ok(error)
210 })();
211 self.heap.roots().truncate(mark);
212 result
213 }
214
215 pub fn heap(&self) -> &Heap {
216 self.heap
217 }
218
219 pub fn heap_mut(&mut self) -> &mut Heap {
220 self.heap
221 }
222
223 pub fn units(&self) -> Units<'_> {
225 let code = match &self.code {
226 Store::Owned(code) => &**code,
227 Store::Shared(code) => *code,
228 };
229 Units {
230 code,
231 dynamic: &self.heap.code,
232 }
233 }
234
235 pub fn intrinsic(&self, intrinsic: Intrinsic) -> ObjectId {
236 self.realm.intrinsic(intrinsic)
237 }
238
239 pub fn publish(&mut self, mut unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
245 validate::validate(&unit, &|id| self.units().lookup(id))?;
246 self.heap.ics.reserve(&mut unit);
247 match &mut self.code {
248 Store::Owned(code) => {
251 for &atom in &unit.key_atoms() {
252 self.heap.pin_atom(atom);
253 }
254 Ok(code.push(unit))
255 }
256 Store::Shared(_) => {
257 let (id, bytes) = self.heap.code.publish(unit);
258 self.heap.gc.allocated += bytes;
259 Ok(id)
260 }
261 }
262 }
263
264 pub fn instantiate(
268 &mut self,
269 root: CodeUnitId,
270 constants: &[&[Value]],
271 ) -> Result<ObjectId, HeapError> {
272 let code = match &self.code {
273 Store::Owned(code) => &**code,
274 Store::Shared(code) => *code,
275 };
276 let code = Units {
277 code,
278 dynamic: &self.heap.code,
279 };
280 let order = code.tree(root);
281 assert_eq!(order.len(), constants.len(), "one constant table per unit");
282 let position: FxHashMap<CodeUnitId, u32> = order
283 .iter()
284 .enumerate()
285 .map(|(index, &unit)| (unit, index as u32))
286 .collect();
287 let children: Vec<Vec<u32>> = order
288 .iter()
289 .map(|&id| {
290 let unit = code.get(id);
291 unit.children.iter().map(|child| position[child]).collect()
292 })
293 .collect();
294 let tree: Vec<BytecodeTemplate<'_>> = order
295 .iter()
296 .zip(constants)
297 .zip(&children)
298 .map(|((&unit, &constants), children)| {
299 assert_eq!(
300 constants.len(),
301 code.get(unit).constant_count as usize,
302 "constant table length"
303 );
304 BytecodeTemplate {
305 unit,
306 constants,
307 children,
308 }
309 })
310 .collect();
311 let prototype = self.realm.intrinsic(Intrinsic::FunctionPrototype);
312 self.heap
313 .alloc_bytecode_function(Some(prototype), None, &tree)
314 }
315}
316
317impl Compiler<'_> {
318 pub fn template_object(
320 &mut self,
321 cooked: &[Value],
322 raw: &[Value],
323 ) -> Result<ObjectId, HeapError> {
324 use crate::heap::object::flags;
325 let prototype = self.intrinsic(Intrinsic::ArrayPrototype);
326 let names = self.realm.names;
327 let heap = &mut *self.heap;
328 let mark = heap.roots().mark();
329 for &value in cooked.iter().chain(raw) {
331 heap.roots().push(value);
332 }
333 let result = (|| {
334 let raw_array = heap.alloc_array(Some(prototype), raw.len() as u32)?;
335 heap.roots().push(Value::object(raw_array));
336 let array = heap.alloc_array(Some(prototype), cooked.len() as u32)?;
337 heap.roots().push(Value::object(array));
338 for (target, values) in [(raw_array, raw), (array, cooked)] {
339 for (index, &value) in values.iter().enumerate() {
340 let key =
341 crate::PropertyKey::index(index as u32).ok_or(HeapError::OutOfMemory)?;
342 heap.add_property(
343 target,
344 key,
345 crate::PropertyFlags::data(false, true, false),
346 value,
347 )?;
348 }
349 heap.set_array_length(target, values.len() as u32);
350 }
351 let raw_key = crate::runtime::Names::key(names.raw);
352 heap.add_property(
353 array,
354 raw_key,
355 crate::PropertyFlags::data(false, false, false),
356 Value::object(raw_array),
357 )?;
358 for target in [raw_array, array] {
359 heap.set_object_flag(target, flags::SPARSE | flags::LENGTH_READONLY);
360 heap.clear_object_flag(target, flags::EXTENSIBLE);
361 }
362 Ok(array)
363 })();
364 heap.roots().truncate(mark);
365 result
366 }
367}
368
369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub enum CompileKind {
372 Script,
374 IndirectEval,
377}
378
379#[derive(Clone, Copy, Debug)]
381pub struct CompileRequest<'s> {
382 pub source: &'s [u16],
383 pub kind: CompileKind,
384 pub filename: &'s str,
385}
386
387#[derive(Clone, Debug)]
389pub struct CompileError {
390 pub kind: ErrorKind,
391 pub message: String,
392 pub thrown: Option<Value>,
397 pub location: Option<(Box<str>, u32, u32)>,
401}
402
403impl CompileError {
404 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
405 CompileError {
406 kind,
407 message: message.into(),
408 thrown: None,
409 location: None,
410 }
411 }
412
413 pub fn at(mut self, file: &str, line: u32, column: u32) -> Self {
415 self.location = Some((file.into(), line, column));
416 self
417 }
418
419 pub fn thrown(value: Value) -> Self {
420 CompileError {
421 kind: ErrorKind::Internal,
422 message: String::new(),
423 thrown: Some(value),
424 location: None,
425 }
426 }
427}
428
429pub(crate) fn throw_failure(cx: &mut Cx<'_>, failure: CompileError) -> Throw {
431 match (failure.thrown, failure.location) {
432 (Some(value), _) => Throw(value),
433 (None, Some((file, line, column))) => {
434 error::throw_located(cx, failure.kind, &failure.message, &file, line, column)
435 }
436 (None, None) => error::throw(cx, failure.kind, &failure.message),
437 }
438}
439
440pub type CompileFn = fn(&mut Compiler<'_>, &CompileRequest<'_>) -> Result<ObjectId, CompileError>;
443
444pub(crate) fn compile(
446 cx: &mut Cx<'_>,
447 source: &[u16],
448 kind: CompileKind,
449) -> Result<ObjectId, Throw> {
450 compile_named(cx, source, kind, "<input>")
451}
452
453pub(crate) fn compile_named(
455 cx: &mut Cx<'_>,
456 source: &[u16],
457 kind: CompileKind,
458 filename: &str,
459) -> Result<ObjectId, Throw> {
460 let Some(compile) = cx.host.compile else {
461 return Err(super::object::unsupported(cx, "eval"));
462 };
463 let request = CompileRequest {
464 source,
465 kind,
466 filename,
467 };
468 let result = compile(
469 &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
470 &request,
471 );
472 match result {
473 Ok(function) => Ok(function),
474 Err(failure) => Err(throw_failure(cx, failure)),
475 }
476}
477
478fn run(cx: &mut Cx<'_>, source: &[u16], kind: CompileKind) -> Result<Value, Throw> {
480 let function = compile(cx, source, kind)?;
481 let mark = cx.heap.roots().mark();
482 cx.heap.roots().push(Value::object(function));
483 let global = Value::object(cx.realm.global());
484 let result = super::call(cx, Value::object(function), global, &[]);
485 cx.heap.roots().truncate(mark);
486 result
487}
488
489pub(crate) fn eval(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
491 let source = site.arg(cx, 0);
492 let Some(string) = source.as_string() else {
493 return Ok(source);
494 };
495 let units = ops::units(cx.heap, string);
496 run(cx, &units, CompileKind::IndirectEval)
497}
498
499#[derive(Clone, Copy, Debug, PartialEq, Eq)]
501pub(crate) enum FunctionKind {
502 Normal,
503 Generator,
504 Async,
505 AsyncGenerator,
506}
507
508pub(crate) fn function_constructor(
511 cx: &mut Cx<'_>,
512 site: CallSite,
513 kind: FunctionKind,
514) -> Result<Value, Throw> {
515 let mut source: Vec<u16> = Vec::new();
516 let text = |source: &mut Vec<u16>, text: &str| source.extend(text.encode_utf16());
517 text(&mut source, "(");
518 if matches!(kind, FunctionKind::Async | FunctionKind::AsyncGenerator) {
519 text(&mut source, "async ");
520 }
521 text(&mut source, "function");
522 if matches!(kind, FunctionKind::Generator | FunctionKind::AsyncGenerator) {
523 text(&mut source, "*");
524 }
525 text(&mut source, " anonymous(");
526 let parameters = site.argc.saturating_sub(1);
527 for index in 0..parameters {
528 if index > 0 {
529 text(&mut source, ",");
530 }
531 let parameter = site.arg(cx, index);
532 let parameter = ops::to_string(cx, parameter)?;
533 source.extend(ops::units(cx.heap, parameter.as_string().unwrap()));
534 }
535 text(&mut source, "\n) {\n");
536 if site.argc > 0 {
537 let body = site.arg(cx, site.argc - 1);
538 let body = ops::to_string(cx, body)?;
539 source.extend(ops::units(cx.heap, body.as_string().unwrap()));
540 }
541 text(&mut source, "\n})");
542 let function = run(cx, &source, CompileKind::IndirectEval)?;
543 let Some(object) = function.as_object() else {
544 return Err(error::type_error(cx, "not an object"));
545 };
546 let default = match kind {
547 FunctionKind::Normal => Intrinsic::FunctionPrototype,
548 FunctionKind::Generator => Intrinsic::GeneratorFunctionPrototype,
549 FunctionKind::Async => Intrinsic::AsyncFunctionPrototype,
550 FunctionKind::AsyncGenerator => Intrinsic::AsyncGeneratorFunctionPrototype,
551 };
552 if site.construct && !site.new_target.is_undefined() {
553 let mark = cx.heap.roots().mark();
554 cx.heap.roots().push(function);
555 let prototype = super::object::prototype_from_constructor(cx, site.new_target, default);
556 cx.heap.roots().truncate(mark);
557 let prototype = prototype?;
558 if !super::object::set_prototype_of(cx, object, Value::object(prototype))? {
559 return Err(error::type_error(cx, "prototype is immutable"));
560 }
561 }
562 Ok(function)
563}
564
565pub(crate) fn function_kind_flags(cx: &Cx<'_>, function: ObjectId) -> Option<u16> {
568 if cx.heap.object_kind(function) != crate::ObjectKind::Function {
569 return None;
570 }
571 let bytecode = cx.heap.function_bytecode(function)?;
572 let unit = cx.heap.bytecode_unit(bytecode);
573 Some(cx.unit(unit).flags)
574}
575
576pub(crate) fn function_source(cx: &Cx<'_>, function: ObjectId) -> Option<Vec<u16>> {
578 if cx.heap.object_kind(function) != crate::ObjectKind::Function {
579 return None;
580 }
581 let bytecode = cx.heap.function_bytecode(function)?;
582 let unit = cx.heap.bytecode_unit(bytecode);
583 let unit = cx.unit(unit);
584 Some(wtf8_units(unit.source.as_ref()?))
585}
586
587fn wtf8_units(bytes: &[u8]) -> Vec<u16> {
593 let continuation = |byte: u8| (0x80..0xc0).contains(&byte);
594 let mut units = Vec::with_capacity(bytes.len());
595 let mut index = 0;
596 while index < bytes.len() {
597 let lead = bytes[index];
598 if lead < 0x80 {
599 units.push(u16::from(lead));
600 index += 1;
601 continue;
602 }
603 let (length, mask, minimum) = match lead {
605 0xc0..=0xdf => (1, 0x1f, 0x80),
606 0xe0..=0xef => (2, 0x0f, 0x800),
607 0xf0..=0xf7 => (3, 0x07, 0x1_0000),
608 0xf8..=0xfb => (4, 0x03, 0x20_0000),
609 0xfc..=0xfd => (5, 0x01, 0x400_0000),
610 _ => (0, 0, 0),
611 };
612 let decoded = (length > 0 && index + length < bytes.len())
613 .then(|| &bytes[index + 1..=index + length])
614 .filter(|tail| tail.iter().all(|&byte| continuation(byte)))
615 .map(|tail| {
616 tail.iter().fold(u32::from(lead & mask), |code, &byte| {
617 code << 6 | u32::from(byte & 0x3f)
618 })
619 })
620 .filter(|&code| code >= minimum && code <= 0x10_ffff);
621 match decoded {
622 Some(code) if code >= 0x1_0000 => {
623 let code = code - 0x1_0000;
624 units.push(0xd800 | (code >> 10) as u16);
625 units.push(0xdc00 | (code & 0x3ff) as u16);
626 index += length + 1;
627 }
628 Some(code) => {
629 units.push(code as u16);
630 index += length + 1;
631 }
632 None => {
633 units.push(0xfffd);
634 while index < bytes.len() && continuation(bytes[index]) {
635 index += 1;
636 }
637 if index < bytes.len() {
638 index += 1;
639 while index < bytes.len() && continuation(bytes[index]) {
640 index += 1;
641 }
642 }
643 }
644 }
645 }
646 units
647}
648
649pub(crate) fn load(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
652 load_script(cx, site).map(|_| Value::UNDEFINED)
653}
654
655pub(crate) fn load_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
658 let path = site.arg(cx, 0);
659 let path = ops::to_string(cx, path)?;
660 let path = cx.heap.string_text(path.as_string().unwrap());
661 let Some(text) = cx.host.hooks.read_file(&path) else {
662 let message = format!("could not load '{path}'");
663 return Err(error::throw(cx, ErrorKind::Reference, &message));
664 };
665 let units: Vec<u16> = text.encode_utf16().collect();
666 let function = compile_named(cx, &units, CompileKind::Script, &path)?;
667 let mark = cx.heap.roots().mark();
668 cx.heap.roots().push(Value::object(function));
669 let global = Value::object(cx.realm.global());
670 let result = super::call(cx, Value::object(function), global, &[]);
671 cx.heap.roots().truncate(mark);
672 result
673}
674
675pub(crate) fn read(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
677 let path = site.arg(cx, 0);
678 let path = ops::to_string(cx, path)?;
679 let path = cx.heap.string_text(path.as_string().unwrap());
680 let Some(text) = cx.host.hooks.read_file(&path) else {
681 let message = format!("could not read '{path}'");
682 return Err(error::throw(cx, ErrorKind::Error, &message));
683 };
684 crate::builtins::string_text(cx, &text)
685}
686
687pub(crate) fn eval_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
688 let source = site.arg(cx, 0);
689 let source = ops::to_string(cx, source)?;
690 let units = ops::units(cx.heap, source.as_string().unwrap());
691 run(cx, &units, CompileKind::Script)
692}
693
694#[cfg(test)]
695mod tests {
696 use crate::{Runtime, RuntimeOptions};
697
698 #[test]
699 fn a_compilation_holds_each_atom_once_until_it_ends() {
700 let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap();
701 let mut compiler = runtime.transient_compiler();
702 let mark = compiler.heap().held_atoms_mark();
703 let atom = compiler.heap_mut().intern("held once").unwrap();
704 for _ in 0..3 {
705 assert_eq!(compiler.heap_mut().intern("held once").unwrap(), atom);
706 }
707 assert_eq!(
708 compiler.heap_mut().intern_latin1(b"held once").unwrap(),
709 atom
710 );
711 assert_eq!(compiler.heap().held_atoms_mark(), mark + 1);
712 {
714 let mut view = compiler.reborrow();
715 view.heap_mut().intern("seen by the view").unwrap();
716 }
717 compiler.heap_mut().collect();
718 assert_eq!(compiler.heap().string_text(atom), "held once");
719 assert_eq!(compiler.heap().held_atoms_mark(), mark + 2);
720 drop(compiler);
721 assert_eq!(runtime.heap().held_atoms_mark(), mark);
722 }
723}