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_atom_uses() {
253 self.heap.pin_atom(atom);
254 }
255 Ok(code.push(unit))
256 }
257 Store::Shared(_) => {
258 let (id, bytes) = self.heap.code.publish(unit);
259 self.heap.gc.allocated += bytes;
260 Ok(id)
261 }
262 }
263 }
264
265 pub fn instantiate(
269 &mut self,
270 root: CodeUnitId,
271 constants: &[&[Value]],
272 ) -> Result<ObjectId, HeapError> {
273 let code = match &self.code {
274 Store::Owned(code) => &**code,
275 Store::Shared(code) => *code,
276 };
277 let code = Units {
278 code,
279 dynamic: &self.heap.code,
280 };
281 let order = code.tree(root);
282 assert_eq!(order.len(), constants.len(), "one constant table per unit");
283 let position: FxHashMap<CodeUnitId, u32> = order
284 .iter()
285 .enumerate()
286 .map(|(index, &unit)| (unit, index as u32))
287 .collect();
288 let children: Vec<Vec<u32>> = order
289 .iter()
290 .map(|&id| {
291 let unit = code.get(id);
292 unit.children.iter().map(|child| position[child]).collect()
293 })
294 .collect();
295 let tree: Vec<BytecodeTemplate<'_>> = order
296 .iter()
297 .zip(constants)
298 .zip(&children)
299 .map(|((&unit, &constants), children)| {
300 assert_eq!(
301 constants.len(),
302 code.get(unit).constant_count as usize,
303 "constant table length"
304 );
305 BytecodeTemplate {
306 unit,
307 constants,
308 children,
309 }
310 })
311 .collect();
312 let prototype = self.realm.intrinsic(Intrinsic::FunctionPrototype);
313 self.heap
314 .alloc_bytecode_function(Some(prototype), None, &tree)
315 }
316}
317
318impl Compiler<'_> {
319 pub fn template_object(
321 &mut self,
322 cooked: &[Value],
323 raw: &[Value],
324 ) -> Result<ObjectId, HeapError> {
325 use crate::heap::object::flags;
326 let prototype = self.intrinsic(Intrinsic::ArrayPrototype);
327 let names = self.realm.names;
328 let heap = &mut *self.heap;
329 let mark = heap.roots().mark();
330 for &value in cooked.iter().chain(raw) {
332 heap.roots().push(value);
333 }
334 let result = (|| {
335 let raw_array = heap.alloc_array(Some(prototype), raw.len() as u32)?;
336 heap.roots().push(Value::object(raw_array));
337 let array = heap.alloc_array(Some(prototype), cooked.len() as u32)?;
338 heap.roots().push(Value::object(array));
339 for (target, values) in [(raw_array, raw), (array, cooked)] {
340 for (index, &value) in values.iter().enumerate() {
341 let key =
342 crate::PropertyKey::index(index as u32).ok_or(HeapError::OutOfMemory)?;
343 heap.add_property(
344 target,
345 key,
346 crate::PropertyFlags::data(false, true, false),
347 value,
348 )?;
349 }
350 heap.set_array_length(target, values.len() as u32);
351 }
352 let raw_key = crate::runtime::Names::key(names.raw);
353 heap.add_property(
354 array,
355 raw_key,
356 crate::PropertyFlags::data(false, false, false),
357 Value::object(raw_array),
358 )?;
359 for target in [raw_array, array] {
360 heap.set_object_flag(target, flags::SPARSE | flags::LENGTH_READONLY);
361 heap.clear_object_flag(target, flags::EXTENSIBLE);
362 }
363 Ok(array)
364 })();
365 heap.roots().truncate(mark);
366 result
367 }
368}
369
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
372pub enum CompileKind {
373 Script,
375 IndirectEval,
378}
379
380#[derive(Clone, Copy, Debug)]
382pub struct CompileRequest<'s> {
383 pub source: &'s [u16],
384 pub kind: CompileKind,
385 pub filename: &'s str,
386}
387
388#[derive(Clone, Debug)]
390pub struct CompileError {
391 pub kind: ErrorKind,
392 pub message: String,
393 pub thrown: Option<Value>,
398 pub location: Option<(Box<str>, u32, u32)>,
402}
403
404impl CompileError {
405 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
406 CompileError {
407 kind,
408 message: message.into(),
409 thrown: None,
410 location: None,
411 }
412 }
413
414 pub fn at(mut self, file: &str, line: u32, column: u32) -> Self {
416 self.location = Some((file.into(), line, column));
417 self
418 }
419
420 pub fn thrown(value: Value) -> Self {
421 CompileError {
422 kind: ErrorKind::Internal,
423 message: String::new(),
424 thrown: Some(value),
425 location: None,
426 }
427 }
428}
429
430pub(crate) fn throw_failure(cx: &mut Cx<'_>, failure: CompileError) -> Throw {
432 match (failure.thrown, failure.location) {
433 (Some(value), _) => Throw(value),
434 (None, Some((file, line, column))) => {
435 error::throw_located(cx, failure.kind, &failure.message, &file, line, column)
436 }
437 (None, None) => error::throw(cx, failure.kind, &failure.message),
438 }
439}
440
441pub type CompileFn = fn(&mut Compiler<'_>, &CompileRequest<'_>) -> Result<ObjectId, CompileError>;
444
445pub(crate) fn compile(
447 cx: &mut Cx<'_>,
448 source: &[u16],
449 kind: CompileKind,
450) -> Result<ObjectId, Throw> {
451 compile_named(cx, source, kind, "<input>")
452}
453
454pub(crate) fn compile_named(
456 cx: &mut Cx<'_>,
457 source: &[u16],
458 kind: CompileKind,
459 filename: &str,
460) -> Result<ObjectId, Throw> {
461 let Some(compile) = cx.host.compile else {
462 return Err(super::object::unsupported(cx, "eval"));
463 };
464 let request = CompileRequest {
465 source,
466 kind,
467 filename,
468 };
469 let result = compile(
470 &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
471 &request,
472 );
473 match result {
474 Ok(function) => Ok(function),
475 Err(failure) => Err(throw_failure(cx, failure)),
476 }
477}
478
479fn run(cx: &mut Cx<'_>, source: &[u16], kind: CompileKind) -> Result<Value, Throw> {
481 let function = compile(cx, source, kind)?;
482 let mark = cx.heap.roots().mark();
483 cx.heap.roots().push(Value::object(function));
484 let global = Value::object(cx.realm.global());
485 let result = super::call(cx, Value::object(function), global, &[]);
486 cx.heap.roots().truncate(mark);
487 result
488}
489
490pub(crate) fn eval(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
492 let source = site.arg(cx, 0);
493 let Some(string) = source.as_string() else {
494 return Ok(source);
495 };
496 let units = ops::units(cx.heap, string);
497 run(cx, &units, CompileKind::IndirectEval)
498}
499
500#[derive(Clone, Copy, Debug, PartialEq, Eq)]
502pub(crate) enum FunctionKind {
503 Normal,
504 Generator,
505 Async,
506 AsyncGenerator,
507}
508
509pub(crate) fn function_constructor(
512 cx: &mut Cx<'_>,
513 site: CallSite,
514 kind: FunctionKind,
515) -> Result<Value, Throw> {
516 let mut source: Vec<u16> = Vec::new();
517 let text = |source: &mut Vec<u16>, text: &str| source.extend(text.encode_utf16());
518 text(&mut source, "(");
519 if matches!(kind, FunctionKind::Async | FunctionKind::AsyncGenerator) {
520 text(&mut source, "async ");
521 }
522 text(&mut source, "function");
523 if matches!(kind, FunctionKind::Generator | FunctionKind::AsyncGenerator) {
524 text(&mut source, "*");
525 }
526 text(&mut source, " anonymous(");
527 let parameters = site.argc.saturating_sub(1);
528 for index in 0..parameters {
529 if index > 0 {
530 text(&mut source, ",");
531 }
532 let parameter = site.arg(cx, index);
533 let parameter = ops::to_string(cx, parameter)?;
534 source.extend(ops::units(cx.heap, parameter.as_string().unwrap()));
535 }
536 text(&mut source, "\n) {\n");
537 if site.argc > 0 {
538 let body = site.arg(cx, site.argc - 1);
539 let body = ops::to_string(cx, body)?;
540 source.extend(ops::units(cx.heap, body.as_string().unwrap()));
541 }
542 text(&mut source, "\n})");
543 let function = run(cx, &source, CompileKind::IndirectEval)?;
544 let Some(object) = function.as_object() else {
545 return Err(error::type_error(cx, "not an object"));
546 };
547 let default = match kind {
548 FunctionKind::Normal => Intrinsic::FunctionPrototype,
549 FunctionKind::Generator => Intrinsic::GeneratorFunctionPrototype,
550 FunctionKind::Async => Intrinsic::AsyncFunctionPrototype,
551 FunctionKind::AsyncGenerator => Intrinsic::AsyncGeneratorFunctionPrototype,
552 };
553 if site.construct && !site.new_target.is_undefined() {
554 let mark = cx.heap.roots().mark();
555 cx.heap.roots().push(function);
556 let prototype = super::object::prototype_from_constructor(cx, site.new_target, default);
557 cx.heap.roots().truncate(mark);
558 let prototype = prototype?;
559 if !super::object::set_prototype_of(cx, object, Value::object(prototype))? {
560 return Err(error::type_error(cx, "prototype is immutable"));
561 }
562 }
563 Ok(function)
564}
565
566pub(crate) fn function_kind_flags(cx: &Cx<'_>, function: ObjectId) -> Option<u16> {
569 if cx.heap.object_kind(function) != crate::ObjectKind::Function {
570 return None;
571 }
572 let bytecode = cx.heap.function_bytecode(function)?;
573 let unit = cx.heap.bytecode_unit(bytecode);
574 Some(cx.unit(unit).flags)
575}
576
577pub(crate) fn function_source(cx: &Cx<'_>, function: ObjectId) -> Option<Vec<u16>> {
579 if cx.heap.object_kind(function) != crate::ObjectKind::Function {
580 return None;
581 }
582 let bytecode = cx.heap.function_bytecode(function)?;
583 let unit = cx.heap.bytecode_unit(bytecode);
584 let unit = cx.unit(unit);
585 Some(wtf8_units(unit.source.as_ref()?))
586}
587
588fn wtf8_units(bytes: &[u8]) -> Vec<u16> {
594 let continuation = |byte: u8| (0x80..0xc0).contains(&byte);
595 let mut units = Vec::with_capacity(bytes.len());
596 let mut index = 0;
597 while index < bytes.len() {
598 let lead = bytes[index];
599 if lead < 0x80 {
600 units.push(u16::from(lead));
601 index += 1;
602 continue;
603 }
604 let (length, mask, minimum) = match lead {
606 0xc0..=0xdf => (1, 0x1f, 0x80),
607 0xe0..=0xef => (2, 0x0f, 0x800),
608 0xf0..=0xf7 => (3, 0x07, 0x1_0000),
609 0xf8..=0xfb => (4, 0x03, 0x20_0000),
610 0xfc..=0xfd => (5, 0x01, 0x400_0000),
611 _ => (0, 0, 0),
612 };
613 let decoded = (length > 0 && index + length < bytes.len())
614 .then(|| &bytes[index + 1..=index + length])
615 .filter(|tail| tail.iter().all(|&byte| continuation(byte)))
616 .map(|tail| {
617 tail.iter().fold(u32::from(lead & mask), |code, &byte| {
618 code << 6 | u32::from(byte & 0x3f)
619 })
620 })
621 .filter(|&code| code >= minimum && code <= 0x10_ffff);
622 match decoded {
623 Some(code) if code >= 0x1_0000 => {
624 let code = code - 0x1_0000;
625 units.push(0xd800 | (code >> 10) as u16);
626 units.push(0xdc00 | (code & 0x3ff) as u16);
627 index += length + 1;
628 }
629 Some(code) => {
630 units.push(code as u16);
631 index += length + 1;
632 }
633 None => {
634 units.push(0xfffd);
635 while index < bytes.len() && continuation(bytes[index]) {
636 index += 1;
637 }
638 if index < bytes.len() {
639 index += 1;
640 while index < bytes.len() && continuation(bytes[index]) {
641 index += 1;
642 }
643 }
644 }
645 }
646 }
647 units
648}
649
650pub(crate) fn load(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
653 load_script(cx, site).map(|_| Value::UNDEFINED)
654}
655
656pub(crate) fn load_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
659 let path = site.arg(cx, 0);
660 let path = ops::to_string(cx, path)?;
661 let path = cx.heap.string_text(path.as_string().unwrap());
662 let Some(text) = cx.host.hooks.read_file(&path) else {
663 let message = format!("could not load '{path}'");
664 return Err(error::throw(cx, ErrorKind::Reference, &message));
665 };
666 let units: Vec<u16> = text.encode_utf16().collect();
667 let function = compile_named(cx, &units, CompileKind::Script, &path)?;
668 let mark = cx.heap.roots().mark();
669 cx.heap.roots().push(Value::object(function));
670 let global = Value::object(cx.realm.global());
671 let result = super::call(cx, Value::object(function), global, &[]);
672 cx.heap.roots().truncate(mark);
673 result
674}
675
676pub(crate) fn read(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
678 let path = site.arg(cx, 0);
679 let path = ops::to_string(cx, path)?;
680 let path = cx.heap.string_text(path.as_string().unwrap());
681 let Some(text) = cx.host.hooks.read_file(&path) else {
682 let message = format!("could not read '{path}'");
683 return Err(error::throw(cx, ErrorKind::Error, &message));
684 };
685 crate::builtins::string_text(cx, &text)
686}
687
688pub(crate) fn eval_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
689 let source = site.arg(cx, 0);
690 let source = ops::to_string(cx, source)?;
691 let units = ops::units(cx.heap, source.as_string().unwrap());
692 run(cx, &units, CompileKind::Script)
693}
694
695#[cfg(test)]
696mod tests {
697 use crate::{Runtime, RuntimeOptions};
698
699 #[test]
700 fn a_compilation_holds_each_atom_once_until_it_ends() {
701 let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap();
702 let mut compiler = runtime.transient_compiler();
703 let mark = compiler.heap().held_atoms_mark();
704 let atom = compiler.heap_mut().intern("held once").unwrap();
705 for _ in 0..3 {
706 assert_eq!(compiler.heap_mut().intern("held once").unwrap(), atom);
707 }
708 assert_eq!(
709 compiler.heap_mut().intern_latin1(b"held once").unwrap(),
710 atom
711 );
712 assert_eq!(compiler.heap().held_atoms_mark(), mark + 1);
713 {
715 let mut view = compiler.reborrow();
716 view.heap_mut().intern("seen by the view").unwrap();
717 }
718 compiler.heap_mut().collect();
719 assert_eq!(compiler.heap().string_text(atom), "held once");
720 assert_eq!(compiler.heap().held_atoms_mark(), mark + 2);
721 drop(compiler);
722 assert_eq!(runtime.heap().held_atoms_mark(), mark);
723 }
724}