1#![forbid(unsafe_code)]
164
165#[cfg(feature = "host-jit")]
166mod jit;
167#[cfg(feature = "host-jit")]
168pub use jit::{JitError, JitProgram, compile_jit};
169
170#[cfg(feature = "aot")]
171mod aot;
172#[cfg(feature = "aot")]
173pub use aot::{AotError, AotObject, PROGRAM_DESCRIPTOR_SYMBOL, compile_aot};
174
175use std::collections::{BTreeMap, BTreeSet};
176use std::error::Error;
177use std::fmt;
178
179use bamts_bytecode::{
180 AccessorKind, BinaryOp, ExceptionHandler, FunctionId, Instruction, IteratorKind, Module,
181 ModuleId, Pc, Program, Register, UnaryOp, Verified,
182};
183use cranelift_codegen::ir::condcodes::IntCC;
184use cranelift_codegen::ir::{
185 AbiParam, Block, ExtFuncData, ExternalName, Function, InstBuilder, MemFlagsData, Signature,
186 Type, UserExternalName, UserFuncName, Value, types,
187};
188use cranelift_codegen::isa::{CallConv, TargetFrontendConfig};
189use cranelift_codegen::settings::{self, Flags};
190use cranelift_codegen::verifier::verify_function;
191use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
192
193const SHADOW_FRAME_PC_OFFSET: i32 = 8;
197const SHADOW_FRAME_MODULE_OFFSET: i32 = 12;
199const SHADOW_FRAME_HANDLES_OFFSET: i32 = 16;
201const COMPLETION_VALUE_OFFSET: i32 = 0;
203const VALUE_BYTES: i64 = 8;
205
206const UNDEFINED_BITS: i64 = 0x7ffb_0000_0000_0000;
209
210const TRAP_INVALID_RESUME: i64 = 1;
212
213const TAG_NORMAL: i64 = 0;
215const TAG_THROW: i64 = 1;
217const TAG_SUSPEND: i64 = 2;
219const TAG_FATAL_TRAP: i64 = 3;
221
222pub const FUNCTION_NAMESPACE: u32 = 0;
225pub const HELPER_NAMESPACE: u32 = 1;
229
230#[cfg(feature = "host-jit")]
234const _: () = {
235 use core::mem::offset_of;
236 assert!(offset_of!(bamts_native::ShadowFrame, bytecode_pc) == SHADOW_FRAME_PC_OFFSET as usize);
237 assert!(
238 offset_of!(bamts_native::ShadowFrame, module_id) == SHADOW_FRAME_MODULE_OFFSET as usize
239 );
240 assert!(offset_of!(bamts_native::ShadowFrame, handles) == SHADOW_FRAME_HANDLES_OFFSET as usize);
241 assert!(core::mem::size_of::<bamts_native::Completion>() == VALUE_BYTES as usize);
242 assert!(bamts_native::Value::UNDEFINED.to_bits() == UNDEFINED_BITS as u64);
243 assert!(bamts_native::CompletionTag::Normal.as_u32() as i64 == TAG_NORMAL);
244 assert!(bamts_native::CompletionTag::Throw.as_u32() as i64 == TAG_THROW);
245 assert!(bamts_native::CompletionTag::Suspend.as_u32() as i64 == TAG_SUSPEND);
246 assert!(bamts_native::CompletionTag::FatalTrap.as_u32() as i64 == TAG_FATAL_TRAP);
247};
248
249#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
299pub enum Helper {
300 LoadConstant,
303 Unary,
306 Binary,
309 CreateObject,
311 CreateArray,
313 CreateCell,
315 CreateClosure,
320 GetProperty,
323 SetProperty,
325 DeleteProperty,
328 Call,
331 Construct,
334 Import,
337 Truthy,
341 ResumeValue,
346 DefineAccessor,
349 LoadGlobal,
352 StoreGlobal,
354 TypeOfGlobal,
357 LoadThis,
360 LoadArguments,
363 LoadNewTarget,
366 ArrayPush,
368 ArrayExtend,
371 ObjectSpread,
374 SetPrototype,
377 CreatePrivateName,
380 CreateRegExp,
383 GetIterator,
386 IteratorNext,
391 Export,
394 ConsumeFuel,
398}
399
400impl Helper {
401 #[must_use]
403 pub const fn symbol(self) -> &'static str {
404 match self {
405 Helper::LoadConstant => "bamts_load_constant",
406 Helper::Unary => "bamts_unary",
407 Helper::Binary => "bamts_binary",
408 Helper::CreateObject => "bamts_create_object",
409 Helper::CreateArray => "bamts_create_array",
410 Helper::CreateClosure => "bamts_create_closure",
411 Helper::GetProperty => "bamts_get_property",
412 Helper::SetProperty => "bamts_set_property",
413 Helper::DeleteProperty => "bamts_delete_property",
414 Helper::Call => "bamts_call",
415 Helper::Construct => "bamts_construct",
416 Helper::Import => "bamts_import",
417 Helper::Truthy => "bamts_truthy",
418 Helper::ResumeValue => "bamts_resume_value",
419 Helper::DefineAccessor => "bamts_define_accessor",
420 Helper::LoadGlobal => "bamts_load_global",
421 Helper::StoreGlobal => "bamts_store_global",
422 Helper::TypeOfGlobal => "bamts_typeof_global",
423 Helper::LoadThis => "bamts_load_this",
424 Helper::LoadArguments => "bamts_load_arguments",
425 Helper::LoadNewTarget => "bamts_load_new_target",
426 Helper::ArrayPush => "bamts_array_push",
427 Helper::ArrayExtend => "bamts_array_extend",
428 Helper::ObjectSpread => "bamts_object_spread",
429 Helper::SetPrototype => "bamts_set_prototype",
430 Helper::CreatePrivateName => "bamts_create_private_name",
431 Helper::CreateRegExp => "bamts_create_regexp",
432 Helper::GetIterator => "bamts_get_iterator",
433 Helper::IteratorNext => "bamts_iterator_next",
434 Helper::Export => "bamts_export",
435 Helper::ConsumeFuel => "bamts_consume_fuel",
436 Helper::CreateCell => "bamts_create_cell",
437 }
438 }
439
440 #[must_use]
443 pub const fn external_index(self) -> u32 {
444 match self {
445 Helper::LoadConstant => 0,
446 Helper::Unary => 1,
447 Helper::Binary => 2,
448 Helper::CreateObject => 3,
449 Helper::CreateArray => 4,
450 Helper::CreateClosure => 5,
451 Helper::GetProperty => 6,
452 Helper::SetProperty => 7,
453 Helper::DeleteProperty => 8,
454 Helper::Call => 9,
455 Helper::Construct => 10,
456 Helper::Import => 11,
457 Helper::Truthy => 12,
458 Helper::ResumeValue => 13,
459 Helper::DefineAccessor => 14,
460 Helper::LoadGlobal => 15,
461 Helper::StoreGlobal => 16,
462 Helper::TypeOfGlobal => 17,
463 Helper::LoadThis => 18,
464 Helper::LoadArguments => 19,
465 Helper::LoadNewTarget => 20,
466 Helper::ArrayPush => 21,
467 Helper::ArrayExtend => 22,
468 Helper::ObjectSpread => 23,
469 Helper::SetPrototype => 24,
470 Helper::CreatePrivateName => 25,
471 Helper::CreateRegExp => 26,
472 Helper::GetIterator => 27,
473 Helper::IteratorNext => 28,
474 Helper::Export => 29,
475 Helper::ConsumeFuel => 30,
476 Helper::CreateCell => 31,
477 }
478 }
479
480 #[must_use]
483 pub const fn from_external_index(index: u32) -> Option<Helper> {
484 match index {
485 0 => Some(Helper::LoadConstant),
486 1 => Some(Helper::Unary),
487 2 => Some(Helper::Binary),
488 3 => Some(Helper::CreateObject),
489 4 => Some(Helper::CreateArray),
490 5 => Some(Helper::CreateClosure),
491 6 => Some(Helper::GetProperty),
492 7 => Some(Helper::SetProperty),
493 8 => Some(Helper::DeleteProperty),
494 9 => Some(Helper::Call),
495 10 => Some(Helper::Construct),
496 11 => Some(Helper::Import),
497 12 => Some(Helper::Truthy),
498 13 => Some(Helper::ResumeValue),
499 14 => Some(Helper::DefineAccessor),
500 15 => Some(Helper::LoadGlobal),
501 16 => Some(Helper::StoreGlobal),
502 17 => Some(Helper::TypeOfGlobal),
503 18 => Some(Helper::LoadThis),
504 19 => Some(Helper::LoadArguments),
505 20 => Some(Helper::LoadNewTarget),
506 21 => Some(Helper::ArrayPush),
507 22 => Some(Helper::ArrayExtend),
508 23 => Some(Helper::ObjectSpread),
509 24 => Some(Helper::SetPrototype),
510 25 => Some(Helper::CreatePrivateName),
511 26 => Some(Helper::CreateRegExp),
512 27 => Some(Helper::GetIterator),
513 28 => Some(Helper::IteratorNext),
514 29 => Some(Helper::Export),
515 30 => Some(Helper::ConsumeFuel),
516 31 => Some(Helper::CreateCell),
517 _ => None,
518 }
519 }
520
521 const fn param_types(self) -> &'static [Type] {
525 match self {
526 Helper::LoadConstant => &[types::I64, types::I32, types::I64],
528 Helper::Unary => &[types::I64, types::I32, types::I64, types::I64],
530 Helper::Binary => &[types::I64, types::I32, types::I64, types::I64, types::I64],
532 Helper::CreateObject
534 | Helper::CreateArray
535 | Helper::CreateCell
536 | Helper::ResumeValue
537 | Helper::LoadThis
538 | Helper::LoadArguments
539 | Helper::LoadNewTarget => &[types::I64, types::I64],
540 Helper::Import
542 | Helper::LoadGlobal
543 | Helper::TypeOfGlobal
544 | Helper::CreatePrivateName
545 | Helper::ConsumeFuel => &[types::I64, types::I32, types::I64],
546 Helper::CreateClosure => &[types::I64, types::I32, types::I64, types::I64],
548 Helper::GetProperty | Helper::DeleteProperty => {
550 &[types::I64, types::I64, types::I64, types::I64]
551 }
552 Helper::SetProperty => &[types::I64, types::I64, types::I64, types::I64, types::I64],
554 Helper::DefineAccessor => &[
556 types::I64,
557 types::I64,
558 types::I64,
559 types::I64,
560 types::I32,
561 types::I64,
562 ],
563 Helper::Call => &[types::I64, types::I64, types::I64, types::I64, types::I64],
565 Helper::Construct => &[types::I64, types::I64, types::I64, types::I64],
567 Helper::ArrayPush
569 | Helper::ArrayExtend
570 | Helper::ObjectSpread
571 | Helper::SetPrototype => &[types::I64, types::I64, types::I64, types::I64],
572 Helper::StoreGlobal | Helper::Export => {
574 &[types::I64, types::I32, types::I64, types::I64]
575 }
576 Helper::CreateRegExp => &[types::I64, types::I32, types::I32, types::I64],
578 Helper::GetIterator => &[types::I64, types::I64, types::I32, types::I64],
580 Helper::IteratorNext => &[types::I64, types::I64, types::I32, types::I32, types::I64],
582 Helper::Truthy => &[types::I64, types::I64],
584 }
585 }
586
587 fn signature(self, call_conv: CallConv) -> Signature {
591 let mut signature = Signature::new(call_conv);
592 for &ty in self.param_types() {
593 signature.params.push(AbiParam::new(ty));
594 }
595 signature.returns.push(AbiParam::new(types::I32));
596 signature
597 }
598}
599
600const fn unary_op_selector(op: UnaryOp) -> i64 {
603 match op {
604 UnaryOp::Void => 0,
605 UnaryOp::TypeOf => 1,
606 UnaryOp::Plus => 2,
607 UnaryOp::Negate => 3,
608 UnaryOp::BitwiseNot => 4,
609 UnaryOp::LogicalNot => 5,
610 }
611}
612
613const fn binary_op_selector(op: BinaryOp) -> i64 {
616 match op {
617 BinaryOp::Add => 0,
618 BinaryOp::Subtract => 1,
619 BinaryOp::Multiply => 2,
620 BinaryOp::Divide => 3,
621 BinaryOp::Remainder => 4,
622 BinaryOp::Exponent => 5,
623 BinaryOp::BitAnd => 6,
624 BinaryOp::BitOr => 7,
625 BinaryOp::BitXor => 8,
626 BinaryOp::ShiftLeft => 9,
627 BinaryOp::ShiftRight => 10,
628 BinaryOp::UnsignedShiftRight => 11,
629 BinaryOp::Equal => 12,
630 BinaryOp::NotEqual => 13,
631 BinaryOp::StrictEqual => 14,
632 BinaryOp::StrictNotEqual => 15,
633 BinaryOp::LessThan => 16,
634 BinaryOp::LessThanOrEqual => 17,
635 BinaryOp::GreaterThan => 18,
636 BinaryOp::GreaterThanOrEqual => 19,
637 BinaryOp::InstanceOf => 20,
638 BinaryOp::In => 21,
639 }
640}
641
642const fn iterator_kind_selector(kind: IteratorKind) -> i64 {
645 match kind {
646 IteratorKind::Sync => 0,
647 IteratorKind::Async => 1,
648 IteratorKind::Keys => 2,
649 }
650}
651
652const fn accessor_kind_selector(kind: AccessorKind) -> i64 {
655 match kind {
656 AccessorKind::Getter => 0,
657 AccessorKind::Setter => 1,
658 }
659}
660
661#[derive(Clone, Debug, Eq, PartialEq)]
665pub enum LowerError {
666 UnsupportedPointerWidth {
668 bits: u8,
670 },
671 TooManyFunctions {
673 count: usize,
675 },
676 RegisterFileTooLarge {
678 function: FunctionId,
680 register_count: u32,
682 },
683 EntrySignatureMismatch {
685 function: FunctionId,
687 },
688 IrVerification {
690 function: FunctionId,
692 message: String,
694 },
695}
696
697impl fmt::Display for LowerError {
698 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699 match self {
700 LowerError::UnsupportedPointerWidth { bits } => {
701 write!(f, "target must be 64-bit, got {bits}-bit")
702 }
703 LowerError::TooManyFunctions { count } => {
704 write!(f, "module has {count} functions, exceeding u32 index range")
705 }
706 LowerError::RegisterFileTooLarge {
707 function,
708 register_count,
709 } => write!(
710 f,
711 "function {} register file of {register_count} slots is not 32-bit addressable",
712 function.get()
713 ),
714 LowerError::EntrySignatureMismatch { function } => write!(
715 f,
716 "function {} lowered to a non-native entry signature",
717 function.get()
718 ),
719 LowerError::IrVerification { function, message } => {
720 write!(
721 f,
722 "function {} failed IR verification: {message}",
723 function.get()
724 )
725 }
726 }
727 }
728}
729
730impl Error for LowerError {}
731
732#[derive(Clone, Debug, Eq, PartialEq)]
734pub struct ProgramLowerError {
735 pub module: ModuleId,
737 pub kind: LowerError,
739}
740
741impl fmt::Display for ProgramLowerError {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 write!(
744 f,
745 "module {} could not be lowered: {}",
746 self.module.get(),
747 self.kind
748 )
749 }
750}
751
752impl Error for ProgramLowerError {
753 fn source(&self) -> Option<&(dyn Error + 'static)> {
754 Some(&self.kind)
755 }
756}
757
758#[derive(Clone)]
763pub struct LoweredFunction {
764 pub id: FunctionId,
766 pub symbol: String,
768 pub signature: Signature,
770 pub clif: Function,
772 pub entry_points: Vec<u32>,
774 pub helpers: Vec<Helper>,
776 pub capture_count: u32,
781}
782
783impl fmt::Debug for LoweredFunction {
784 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785 f.debug_struct("LoweredFunction")
786 .field("id", &self.id)
787 .field("symbol", &self.symbol)
788 .field("entry_points", &self.entry_points)
789 .field("helpers", &self.helpers)
790 .field("capture_count", &self.capture_count)
791 .finish_non_exhaustive()
792 }
793}
794
795#[derive(Clone)]
797pub struct LoweredModule {
798 pub id: ModuleId,
800 pub functions: Vec<LoweredFunction>,
802 pub entry: FunctionId,
804 pub call_conv: CallConv,
806}
807
808impl fmt::Debug for LoweredModule {
809 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
810 f.debug_struct("LoweredModule")
811 .field("id", &self.id)
812 .field("functions", &self.functions)
813 .field("entry", &self.entry)
814 .field("call_conv", &self.call_conv)
815 .finish()
816 }
817}
818
819#[derive(Clone, Debug)]
821pub struct LoweredProgram {
822 pub modules: Vec<LoweredModule>,
824 pub entry_module: ModuleId,
826 pub entry_function: FunctionId,
828}
829
830#[must_use]
832pub fn function_symbol(module_id: u32, function_id: u32) -> String {
833 format!("bamts_m{module_id}_fn_{function_id}")
834}
835
836pub fn lower_program(
843 program: &Program<Verified>,
844 config: TargetFrontendConfig,
845) -> Result<LoweredProgram, ProgramLowerError> {
846 let mut modules = Vec::with_capacity(program.modules().len());
847 for (index, module) in program.modules().iter().enumerate() {
848 let module_id = ModuleId::new(index as u32);
849 modules.push(
850 lower_code_module(module_id, module.code(), config).map_err(|kind| {
851 ProgramLowerError {
852 module: module_id,
853 kind,
854 }
855 })?,
856 );
857 }
858 let entry_module = program.entry();
859 let entry_function = program
860 .module(entry_module)
861 .expect("verified program entry module exists")
862 .code()
863 .entry();
864 Ok(LoweredProgram {
865 modules,
866 entry_module,
867 entry_function,
868 })
869}
870
871fn lower_code_module(
872 module_id: ModuleId,
873 module: &Module<Verified>,
874 config: TargetFrontendConfig,
875) -> Result<LoweredModule, LowerError> {
876 if config.pointer_bits() != 64 {
877 return Err(LowerError::UnsupportedPointerWidth {
878 bits: config.pointer_bits(),
879 });
880 }
881 let function_count = module.functions().len();
882 if u32::try_from(function_count).is_err() {
883 return Err(LowerError::TooManyFunctions {
884 count: function_count,
885 });
886 }
887
888 let call_conv = config.default_call_conv;
889 let entry_signature = entry_signature(call_conv);
890 let flags = Flags::new(settings::builder());
891 let mut builder_context = FunctionBuilderContext::new();
892
893 let mut functions = Vec::with_capacity(function_count);
894 for (index, function) in module.functions().iter().enumerate() {
895 let id = FunctionId::new(index as u32);
897 let lowered = lower_function(
898 module_id,
899 id,
900 function,
901 &entry_signature,
902 config,
903 &flags,
904 &mut builder_context,
905 )?;
906 functions.push(lowered);
907 }
908
909 Ok(LoweredModule {
910 id: module_id,
911 functions,
912 entry: module.entry(),
913 call_conv,
914 })
915}
916
917fn entry_signature(call_conv: CallConv) -> Signature {
919 let mut signature = Signature::new(call_conv);
920 signature.params.push(AbiParam::new(types::I64)); signature.params.push(AbiParam::new(types::I64)); signature.returns.push(AbiParam::new(types::I32)); signature
924}
925
926fn validate_slots(id: FunctionId, function: &bamts_bytecode::Function) -> Result<(), LowerError> {
931 let register_count = function.register_count();
932 let addressable = i64::from(register_count)
933 .checked_mul(VALUE_BYTES)
934 .is_some_and(|bytes| i32::try_from(bytes).is_ok());
935 if addressable {
936 Ok(())
937 } else {
938 Err(LowerError::RegisterFileTooLarge {
939 function: id,
940 register_count,
941 })
942 }
943}
944
945fn lower_function(
946 module_id: ModuleId,
947 id: FunctionId,
948 function: &bamts_bytecode::Function,
949 entry_signature: &Signature,
950 config: TargetFrontendConfig,
951 flags: &Flags,
952 builder_context: &mut FunctionBuilderContext,
953) -> Result<LoweredFunction, LowerError> {
954 validate_slots(id, function)?;
955
956 let code = function.code();
957 let handlers = function.handlers();
958 let reachable = reachable_pcs(code, handlers);
959 let entry_points = resume_tokens(code, &reachable);
960
961 let name = UserFuncName::user(FUNCTION_NAMESPACE, id.get());
962 let mut clif = Function::with_name_signature(name, entry_signature.clone());
963
964 let helpers = {
965 let builder = FunctionBuilder::new(&mut clif, builder_context);
966 let mut lowering = Lowering::new(builder, code.len(), handlers, config.default_call_conv);
967 lowering.build(code, &reachable);
968 lowering.finish(config)
969 };
970
971 if clif.signature != *entry_signature {
975 return Err(LowerError::EntrySignatureMismatch { function: id });
976 }
977
978 verify_function(&clif, flags).map_err(|errors| LowerError::IrVerification {
979 function: id,
980 message: errors.to_string(),
981 })?;
982
983 Ok(LoweredFunction {
984 id,
985 symbol: function_symbol(module_id.get(), id.get()),
986 signature: entry_signature.clone(),
987 clif,
988 entry_points,
989 helpers,
990 capture_count: function.capture_count(),
991 })
992}
993
994struct Lowering<'a> {
997 builder: FunctionBuilder<'a>,
998 pc_blocks: Vec<Option<Block>>,
1001 resume_blocks: BTreeMap<usize, Block>,
1004 handlers: &'a [ExceptionHandler],
1005 call_conv: CallConv,
1006 frame: Value,
1007 out: Value,
1008 helper_refs: BTreeMap<Helper, cranelift_codegen::ir::FuncRef>,
1009}
1010
1011impl<'a> Lowering<'a> {
1012 fn new(
1013 mut builder: FunctionBuilder<'a>,
1014 code_len: usize,
1015 handlers: &'a [ExceptionHandler],
1016 call_conv: CallConv,
1017 ) -> Self {
1018 let pc_blocks = vec![None; code_len];
1019 let dispatch = builder.create_block();
1022 builder.append_block_params_for_function_params(dispatch);
1023 builder.switch_to_block(dispatch);
1024 let frame = builder.block_params(dispatch)[0];
1025 let out = builder.block_params(dispatch)[1];
1026 Self {
1027 builder,
1028 pc_blocks,
1029 resume_blocks: BTreeMap::new(),
1030 handlers,
1031 call_conv,
1032 frame,
1033 out,
1034 helper_refs: BTreeMap::new(),
1035 }
1036 }
1037
1038 fn build(&mut self, code: &[Instruction], reachable: &BTreeSet<usize>) {
1039 for &pc in reachable {
1040 self.pc_blocks[pc] = Some(self.builder.create_block());
1041 }
1042 for &pc in reachable {
1043 if let Instruction::Suspend { .. } = code[pc] {
1044 let block = self.builder.create_block();
1045 self.resume_blocks.insert(pc, block);
1046 }
1047 }
1048 self.emit_dispatch();
1049 for &pc in reachable {
1050 self.emit_instruction(pc, code[pc]);
1051 }
1052 for &pc in reachable {
1053 if let Instruction::Suspend { dst, resume, .. } = code[pc] {
1054 self.emit_resume_prologue(pc, dst, resume);
1055 }
1056 }
1057 self.builder.seal_all_blocks();
1058 }
1059
1060 fn finish(self, config: TargetFrontendConfig) -> Vec<Helper> {
1062 let helpers = self.helper_refs.keys().copied().collect();
1063 self.builder.finalize(config);
1064 helpers
1065 }
1066
1067 fn emit_dispatch(&mut self) {
1071 if self.resume_blocks.is_empty() {
1074 let target = self.pc_blocks[0].expect("entry pc is reachable");
1075 self.builder.ins().jump(target, &[]);
1076 return;
1077 }
1078
1079 let token = self.builder.ins().load(
1080 types::I32,
1081 MemFlagsData::trusted(),
1082 self.frame,
1083 SHADOW_FRAME_PC_OFFSET,
1084 );
1085 let fresh = self.pc_blocks[0].expect("entry pc is reachable");
1087 let after_fresh = self.builder.create_block();
1088 let is_fresh = self.builder.ins().icmp_imm_u(IntCC::Equal, token, 0);
1089 self.builder
1090 .ins()
1091 .brif(is_fresh, fresh, &[], after_fresh, &[]);
1092 self.builder.switch_to_block(after_fresh);
1093 let resume: Vec<(usize, Block)> = self
1095 .resume_blocks
1096 .iter()
1097 .map(|(&pc, &block)| (pc, block))
1098 .collect();
1099 for (pc, block) in resume {
1100 let token_value = i64::from(pc as u32 + 1);
1101 let matches = self
1102 .builder
1103 .ins()
1104 .icmp_imm_u(IntCC::Equal, token, token_value);
1105 let next = self.builder.create_block();
1106 self.builder.ins().brif(matches, block, &[], next, &[]);
1107 self.builder.switch_to_block(next);
1108 }
1109 self.emit_trap(TRAP_INVALID_RESUME);
1111 }
1112
1113 fn emit_instruction(&mut self, pc: usize, instruction: Instruction) {
1114 let block = self.pc_blocks[pc].expect("reachable pc has a block");
1115 self.builder.switch_to_block(block);
1116 let current_pc = self.iconst32(i64::from(pc as u32));
1117 self.builder.ins().store(
1118 MemFlagsData::trusted(),
1119 current_pc,
1120 self.frame,
1121 SHADOW_FRAME_PC_OFFSET,
1122 );
1123 if is_inline_instruction(instruction) {
1124 self.emit_consume_fuel();
1125 }
1126 match instruction {
1127 Instruction::LoadConst { dst, constant } => {
1128 let const_id = self.iconst32(i64::from(constant.get()));
1129 let tag = self.call_helper(Helper::LoadConstant, &[self.frame, const_id, self.out]);
1130 self.route_completion(pc, tag, Some(dst));
1131 }
1132 Instruction::Move { dst, src } => {
1133 let handles = self.load_handles();
1134 let value = self.load_register(handles, src);
1135 self.store_register(handles, dst, value);
1136 self.jump_to_next(pc);
1137 }
1138 Instruction::Unary { dst, op, operand } => {
1139 let handles = self.load_handles();
1140 let operand_value = self.load_register(handles, operand);
1141 let selector = self.iconst32(unary_op_selector(op));
1142 let tag = self.call_helper(
1143 Helper::Unary,
1144 &[self.frame, selector, operand_value, self.out],
1145 );
1146 self.route_completion(pc, tag, Some(dst));
1147 }
1148 Instruction::Binary {
1149 dst,
1150 op,
1151 left,
1152 right,
1153 } => {
1154 let handles = self.load_handles();
1155 let left_value = self.load_register(handles, left);
1156 let right_value = self.load_register(handles, right);
1157 let selector = self.iconst32(binary_op_selector(op));
1158 let tag = self.call_helper(
1159 Helper::Binary,
1160 &[self.frame, selector, left_value, right_value, self.out],
1161 );
1162 self.route_completion(pc, tag, Some(dst));
1163 }
1164 Instruction::CreateObject { dst } => {
1165 let tag = self.call_helper(Helper::CreateObject, &[self.frame, self.out]);
1166 self.route_completion(pc, tag, Some(dst));
1167 }
1168 Instruction::CreateArray { dst } => {
1169 let tag = self.call_helper(Helper::CreateArray, &[self.frame, self.out]);
1170 self.route_completion(pc, tag, Some(dst));
1171 }
1172 Instruction::CreateCell { dst } => {
1173 let tag = self.call_helper(Helper::CreateCell, &[self.frame, self.out]);
1174 self.route_completion(pc, tag, Some(dst));
1175 }
1176 Instruction::CreateClosure {
1177 dst,
1178 function,
1179 captures,
1180 } => {
1181 let handles = self.load_handles();
1182 let captures_value = self.load_register(handles, captures);
1183 let function_id = self.iconst32(i64::from(function.get()));
1184 let tag = self.call_helper(
1185 Helper::CreateClosure,
1186 &[self.frame, function_id, captures_value, self.out],
1187 );
1188 self.route_completion(pc, tag, Some(dst));
1189 }
1190 Instruction::GetProperty { dst, object, key } => {
1191 let handles = self.load_handles();
1192 let object_value = self.load_register(handles, object);
1193 let key_value = self.load_register(handles, key);
1194 let tag = self.call_helper(
1195 Helper::GetProperty,
1196 &[self.frame, object_value, key_value, self.out],
1197 );
1198 self.route_completion(pc, tag, Some(dst));
1199 }
1200 Instruction::SetProperty { object, key, value } => {
1201 let handles = self.load_handles();
1202 let object_value = self.load_register(handles, object);
1203 let key_value = self.load_register(handles, key);
1204 let value_value = self.load_register(handles, value);
1205 let tag = self.call_helper(
1206 Helper::SetProperty,
1207 &[self.frame, object_value, key_value, value_value, self.out],
1208 );
1209 self.route_completion(pc, tag, None);
1210 }
1211 Instruction::DeleteProperty { dst, object, key } => {
1212 let handles = self.load_handles();
1213 let object_value = self.load_register(handles, object);
1214 let key_value = self.load_register(handles, key);
1215 let tag = self.call_helper(
1216 Helper::DeleteProperty,
1217 &[self.frame, object_value, key_value, self.out],
1218 );
1219 self.route_completion(pc, tag, Some(dst));
1220 }
1221 Instruction::DefineAccessor {
1222 object,
1223 key,
1224 accessor,
1225 kind,
1226 } => {
1227 let handles = self.load_handles();
1228 let object_value = self.load_register(handles, object);
1229 let key_value = self.load_register(handles, key);
1230 let accessor_value = self.load_register(handles, accessor);
1231 let selector = self.iconst32(accessor_kind_selector(kind));
1232 let tag = self.call_helper(
1233 Helper::DefineAccessor,
1234 &[
1235 self.frame,
1236 object_value,
1237 key_value,
1238 accessor_value,
1239 selector,
1240 self.out,
1241 ],
1242 );
1243 self.route_completion(pc, tag, None);
1244 }
1245 Instruction::Call {
1246 dst,
1247 callee,
1248 this_value,
1249 arguments,
1250 } => {
1251 let handles = self.load_handles();
1252 let callee_value = self.load_register(handles, callee);
1253 let this = self.load_register(handles, this_value);
1254 let args = self.load_register(handles, arguments);
1255 let tag = self.call_helper(
1256 Helper::Call,
1257 &[self.frame, callee_value, this, args, self.out],
1258 );
1259 self.route_completion(pc, tag, Some(dst));
1260 }
1261 Instruction::Construct {
1262 dst,
1263 callee,
1264 arguments,
1265 } => {
1266 let handles = self.load_handles();
1267 let callee_value = self.load_register(handles, callee);
1268 let args = self.load_register(handles, arguments);
1269 let tag = self.call_helper(
1270 Helper::Construct,
1271 &[self.frame, callee_value, args, self.out],
1272 );
1273 self.route_completion(pc, tag, Some(dst));
1274 }
1275 Instruction::LoadGlobal { dst, name } => {
1276 let name_id = self.iconst32(i64::from(name.get()));
1277 let tag = self.call_helper(Helper::LoadGlobal, &[self.frame, name_id, self.out]);
1278 self.route_completion(pc, tag, Some(dst));
1279 }
1280 Instruction::StoreGlobal { name, value } => {
1281 let handles = self.load_handles();
1282 let value_value = self.load_register(handles, value);
1283 let name_id = self.iconst32(i64::from(name.get()));
1284 let tag = self.call_helper(
1285 Helper::StoreGlobal,
1286 &[self.frame, name_id, value_value, self.out],
1287 );
1288 self.route_completion(pc, tag, None);
1289 }
1290 Instruction::TypeOfGlobal { dst, name } => {
1291 let name_id = self.iconst32(i64::from(name.get()));
1292 let tag = self.call_helper(Helper::TypeOfGlobal, &[self.frame, name_id, self.out]);
1293 self.route_completion(pc, tag, Some(dst));
1294 }
1295 Instruction::LoadThis { dst } => {
1296 let tag = self.call_helper(Helper::LoadThis, &[self.frame, self.out]);
1297 self.route_completion(pc, tag, Some(dst));
1298 }
1299 Instruction::LoadArguments { dst } => {
1300 let tag = self.call_helper(Helper::LoadArguments, &[self.frame, self.out]);
1301 self.route_completion(pc, tag, Some(dst));
1302 }
1303 Instruction::LoadNewTarget { dst } => {
1304 let tag = self.call_helper(Helper::LoadNewTarget, &[self.frame, self.out]);
1305 self.route_completion(pc, tag, Some(dst));
1306 }
1307 Instruction::ArrayPush { array, value } => {
1308 let handles = self.load_handles();
1309 let array_value = self.load_register(handles, array);
1310 let value_value = self.load_register(handles, value);
1311 let tag = self.call_helper(
1312 Helper::ArrayPush,
1313 &[self.frame, array_value, value_value, self.out],
1314 );
1315 self.route_completion(pc, tag, None);
1316 }
1317 Instruction::ArrayExtend { array, iterable } => {
1318 let handles = self.load_handles();
1319 let array_value = self.load_register(handles, array);
1320 let iterable_value = self.load_register(handles, iterable);
1321 let tag = self.call_helper(
1322 Helper::ArrayExtend,
1323 &[self.frame, array_value, iterable_value, self.out],
1324 );
1325 self.route_completion(pc, tag, None);
1326 }
1327 Instruction::ObjectSpread { target, source } => {
1328 let handles = self.load_handles();
1329 let target_value = self.load_register(handles, target);
1330 let source_value = self.load_register(handles, source);
1331 let tag = self.call_helper(
1332 Helper::ObjectSpread,
1333 &[self.frame, target_value, source_value, self.out],
1334 );
1335 self.route_completion(pc, tag, None);
1336 }
1337 Instruction::SetPrototype { object, prototype } => {
1338 let handles = self.load_handles();
1339 let object_value = self.load_register(handles, object);
1340 let prototype_value = self.load_register(handles, prototype);
1341 let tag = self.call_helper(
1342 Helper::SetPrototype,
1343 &[self.frame, object_value, prototype_value, self.out],
1344 );
1345 self.route_completion(pc, tag, None);
1346 }
1347 Instruction::CreatePrivateName { dst, description } => {
1348 let description_id = self.iconst32(i64::from(description.get()));
1349 let tag = self.call_helper(
1350 Helper::CreatePrivateName,
1351 &[self.frame, description_id, self.out],
1352 );
1353 self.route_completion(pc, tag, Some(dst));
1354 }
1355 Instruction::CreateRegExp {
1356 dst,
1357 pattern,
1358 flags,
1359 } => {
1360 let pattern_id = self.iconst32(i64::from(pattern.get()));
1361 let flags_id = self.iconst32(i64::from(flags.get()));
1362 let tag = self.call_helper(
1363 Helper::CreateRegExp,
1364 &[self.frame, pattern_id, flags_id, self.out],
1365 );
1366 self.route_completion(pc, tag, Some(dst));
1367 }
1368 Instruction::GetIterator { dst, src, kind } => {
1369 let handles = self.load_handles();
1370 let src_value = self.load_register(handles, src);
1371 let selector = self.iconst32(iterator_kind_selector(kind));
1372 let tag = self.call_helper(
1373 Helper::GetIterator,
1374 &[self.frame, src_value, selector, self.out],
1375 );
1376 self.route_completion(pc, tag, Some(dst));
1377 }
1378 Instruction::IteratorNext {
1379 done,
1380 value,
1381 iterator,
1382 } => {
1383 let handles = self.load_handles();
1384 let iterator_value = self.load_register(handles, iterator);
1385 let done_reg = self.iconst32(i64::from(done.get()));
1386 let value_reg = self.iconst32(i64::from(value.get()));
1387 let tag = self.call_helper(
1390 Helper::IteratorNext,
1391 &[self.frame, iterator_value, done_reg, value_reg, self.out],
1392 );
1393 self.route_completion(pc, tag, None);
1394 }
1395 Instruction::Import { dst, specifier } => {
1396 let specifier_id = self.iconst32(i64::from(specifier.get()));
1397 let tag = self.call_helper(Helper::Import, &[self.frame, specifier_id, self.out]);
1398 self.route_completion(pc, tag, Some(dst));
1399 }
1400 Instruction::Export { name, src } => {
1401 let handles = self.load_handles();
1402 let src_value = self.load_register(handles, src);
1403 let name_id = self.iconst32(i64::from(name.get()));
1404 let tag =
1405 self.call_helper(Helper::Export, &[self.frame, name_id, src_value, self.out]);
1406 self.route_completion(pc, tag, None);
1407 }
1408 Instruction::Jump { target } => {
1409 let target = self.pc_block(target);
1410 self.builder.ins().jump(target, &[]);
1411 }
1412 Instruction::JumpIfTrue { condition, target } => {
1413 self.emit_conditional(condition, target.get() as usize, pc + 1);
1414 }
1415 Instruction::JumpIfFalse { condition, target } => {
1416 self.emit_conditional(condition, pc + 1, target.get() as usize);
1417 }
1418 Instruction::Return { value } => self.emit_return(value),
1419 Instruction::Throw { value } => self.emit_throw(pc, value),
1420 Instruction::Suspend { src, .. } => self.emit_suspend(pc, src),
1421 Instruction::Halt => self.emit_halt(),
1422 }
1423 }
1424
1425 fn route_completion(&mut self, pc: usize, tag: Value, dst: Option<Register>) {
1429 let normal = self.builder.create_block();
1430 let abnormal = self.builder.create_block();
1431 self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1433
1434 self.builder.switch_to_block(normal);
1435 if let Some(dst) = dst {
1436 let handles = self.load_handles();
1437 let result = self.load_completion_value();
1438 self.store_register(handles, dst, result);
1439 }
1440 self.jump_to_next(pc);
1441
1442 self.builder.switch_to_block(abnormal);
1443 self.emit_abnormal_completion(pc, tag);
1444 }
1445
1446 fn emit_abnormal_completion(&mut self, pc: usize, tag: Value) {
1454 let covering = innermost_handler(self.handlers, pc).and_then(|handler| {
1455 self.emitted_handler_block(handler)
1456 .map(|block| (handler, block))
1457 });
1458 match covering {
1459 Some((handler, handler_block)) => {
1460 let bind = self.builder.create_block();
1461 let propagate = self.builder.create_block();
1462 let is_throw = self.builder.ins().icmp_imm_u(IntCC::Equal, tag, TAG_THROW);
1463 self.builder.ins().brif(is_throw, bind, &[], propagate, &[]);
1464
1465 self.builder.switch_to_block(bind);
1466 let handles = self.load_handles();
1467 let thrown = self.load_completion_value();
1468 self.store_register(handles, handler.catch_register, thrown);
1469 self.builder.ins().jump(handler_block, &[]);
1470
1471 self.builder.switch_to_block(propagate);
1472 self.builder.ins().return_(&[tag]);
1473 }
1474 None => {
1475 self.builder.ins().return_(&[tag]);
1476 }
1477 }
1478 }
1479
1480 fn emitted_handler_block(&self, handler: ExceptionHandler) -> Option<Block> {
1483 self.pc_blocks[handler.handler.get() as usize]
1484 }
1485
1486 fn emit_conditional(&mut self, condition: Register, true_target: usize, false_target: usize) {
1490 let handles = self.load_handles();
1491 let condition_value = self.load_register(handles, condition);
1492 let truth = self.call_helper(Helper::Truthy, &[self.frame, condition_value]);
1493 let then_block = self.pc_blocks[true_target].expect("branch target is reachable");
1494 let else_block = self.pc_blocks[false_target].expect("branch target is reachable");
1495 self.builder
1496 .ins()
1497 .brif(truth, then_block, &[], else_block, &[]);
1498 }
1499
1500 fn emit_return(&mut self, value: Register) {
1501 let handles = self.load_handles();
1502 let return_value = self.load_register(handles, value);
1503 self.store_completion_value(return_value);
1504 let tag = self.iconst32(TAG_NORMAL);
1505 self.builder.ins().return_(&[tag]);
1506 }
1507
1508 fn emit_throw(&mut self, pc: usize, value: Register) {
1512 let handles = self.load_handles();
1513 let thrown = self.load_register(handles, value);
1514 match innermost_handler(self.handlers, pc) {
1515 Some(handler) => {
1516 self.store_register(handles, handler.catch_register, thrown);
1517 let handler_block = self.pc_block(handler.handler);
1518 self.builder.ins().jump(handler_block, &[]);
1519 }
1520 None => {
1521 self.store_completion_value(thrown);
1522 let tag = self.iconst32(TAG_THROW);
1523 self.builder.ins().return_(&[tag]);
1524 }
1525 }
1526 }
1527
1528 fn emit_suspend(&mut self, pc: usize, src: Register) {
1531 let token = self.iconst32(i64::from(pc as u32 + 1));
1532 self.builder.ins().store(
1533 MemFlagsData::trusted(),
1534 token,
1535 self.frame,
1536 SHADOW_FRAME_PC_OFFSET,
1537 );
1538 let handles = self.load_handles();
1539 let yielded = self.load_register(handles, src);
1540 self.store_completion_value(yielded);
1541 let tag = self.iconst32(TAG_SUSPEND);
1542 self.builder.ins().return_(&[tag]);
1543 }
1544
1545 fn emit_resume_prologue(&mut self, pc: usize, dst: Register, resume: Pc) {
1550 let block = self.resume_blocks[&pc];
1551 self.builder.switch_to_block(block);
1552 let current_pc = self.iconst32(i64::from(pc as u32));
1553 self.builder.ins().store(
1554 MemFlagsData::trusted(),
1555 current_pc,
1556 self.frame,
1557 SHADOW_FRAME_PC_OFFSET,
1558 );
1559 let tag = self.call_helper(Helper::ResumeValue, &[self.frame, self.out]);
1560
1561 let normal = self.builder.create_block();
1562 let abnormal = self.builder.create_block();
1563 self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1564
1565 self.builder.switch_to_block(normal);
1566 let handles = self.load_handles();
1567 let resumed = self.load_completion_value();
1568 self.store_register(handles, dst, resumed);
1569 let target = self.pc_block(resume);
1570 self.builder.ins().jump(target, &[]);
1571
1572 self.builder.switch_to_block(abnormal);
1573 self.emit_abnormal_completion(pc, tag);
1574 }
1575
1576 fn emit_halt(&mut self) {
1577 let undefined = self.builder.ins().iconst(types::I64, UNDEFINED_BITS);
1578 self.store_completion_value(undefined);
1579 let tag = self.iconst32(TAG_NORMAL);
1580 self.builder.ins().return_(&[tag]);
1581 }
1582
1583 fn emit_trap(&mut self, trap_id: i64) {
1584 let value = self.builder.ins().iconst(types::I64, trap_id);
1585 self.store_completion_value(value);
1586 let tag = self.iconst32(TAG_FATAL_TRAP);
1587 self.builder.ins().return_(&[tag]);
1588 }
1589
1590 fn jump_to_next(&mut self, pc: usize) {
1591 let next = self.pc_blocks[pc + 1].expect("fallthrough successor is reachable");
1592 self.builder.ins().jump(next, &[]);
1593 }
1594
1595 fn pc_block(&self, target: Pc) -> Block {
1597 self.pc_blocks[target.get() as usize].expect("control-flow target is reachable")
1598 }
1599
1600 fn iconst32(&mut self, value: i64) -> Value {
1601 self.builder.ins().iconst(types::I32, value)
1602 }
1603
1604 fn load_handles(&mut self) -> Value {
1605 self.builder.ins().load(
1607 types::I64,
1608 MemFlagsData::trusted(),
1609 self.frame,
1610 SHADOW_FRAME_HANDLES_OFFSET,
1611 )
1612 }
1613
1614 fn load_register(&mut self, handles: Value, register: Register) -> Value {
1615 self.builder.ins().load(
1616 types::I64,
1617 MemFlagsData::trusted(),
1618 handles,
1619 register_offset(register),
1620 )
1621 }
1622
1623 fn store_register(&mut self, handles: Value, register: Register, value: Value) {
1624 self.builder.ins().store(
1625 MemFlagsData::trusted(),
1626 value,
1627 handles,
1628 register_offset(register),
1629 );
1630 }
1631
1632 fn load_completion_value(&mut self) -> Value {
1633 self.builder.ins().load(
1634 types::I64,
1635 MemFlagsData::trusted(),
1636 self.out,
1637 COMPLETION_VALUE_OFFSET,
1638 )
1639 }
1640
1641 fn store_completion_value(&mut self, value: Value) {
1642 self.builder.ins().store(
1643 MemFlagsData::trusted(),
1644 value,
1645 self.out,
1646 COMPLETION_VALUE_OFFSET,
1647 );
1648 }
1649
1650 fn call_helper(&mut self, helper: Helper, args: &[Value]) -> Value {
1651 let func_ref = self.helper_ref(helper);
1652 let call = self.builder.ins().call(func_ref, args);
1653 self.builder.inst_results(call)[0]
1654 }
1655
1656 fn helper_ref(&mut self, helper: Helper) -> cranelift_codegen::ir::FuncRef {
1657 if let Some(&func_ref) = self.helper_refs.get(&helper) {
1658 return func_ref;
1659 }
1660 let signature = helper.signature(self.call_conv);
1661 let sig_ref = self.builder.import_signature(signature);
1662 let name = self
1663 .builder
1664 .func
1665 .declare_imported_user_function(UserExternalName::new(
1666 HELPER_NAMESPACE,
1667 helper.external_index(),
1668 ));
1669 let func_ref = self.builder.import_function(ExtFuncData {
1670 name: ExternalName::user(name),
1671 signature: sig_ref,
1672 colocated: false,
1673 patchable: false,
1674 });
1675 self.helper_refs.insert(helper, func_ref);
1676 func_ref
1677 }
1678
1679 fn emit_consume_fuel(&mut self) {
1680 let amount = self.iconst32(1);
1681 let tag = self.call_helper(Helper::ConsumeFuel, &[self.frame, amount, self.out]);
1682 let normal = self.builder.create_block();
1683 let abnormal = self.builder.create_block();
1684 self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1685
1686 self.builder.switch_to_block(abnormal);
1687 self.builder.ins().return_(&[tag]);
1688
1689 self.builder.switch_to_block(normal);
1690 }
1691}
1692
1693fn register_offset(register: Register) -> i32 {
1698 i32::try_from(i64::from(register.get()) * VALUE_BYTES).expect("register slot offset fits i32")
1699}
1700
1701fn reachable_pcs(code: &[Instruction], handlers: &[ExceptionHandler]) -> BTreeSet<usize> {
1708 let mut reachable = BTreeSet::new();
1709 let mut worklist = Vec::new();
1710 if !code.is_empty() {
1711 worklist.push(0usize);
1712 }
1713 while let Some(pc) = worklist.pop() {
1714 if !reachable.insert(pc) {
1715 continue;
1716 }
1717 let instruction = code[pc];
1718 instruction.visit_normal_successors(pc, |target| worklist.push(target));
1719 if routes_to_handler(instruction)
1720 && let Some(handler) = innermost_handler(handlers, pc)
1721 {
1722 worklist.push(handler.handler.get() as usize);
1723 }
1724 }
1725 reachable
1726}
1727
1728fn routes_to_handler(instruction: Instruction) -> bool {
1739 match instruction {
1740 Instruction::LoadConst { .. }
1741 | Instruction::Unary { .. }
1742 | Instruction::Binary { .. }
1743 | Instruction::CreateObject { .. }
1744 | Instruction::CreateArray { .. }
1745 | Instruction::CreateCell { .. }
1746 | Instruction::CreateClosure { .. }
1747 | Instruction::GetProperty { .. }
1748 | Instruction::SetProperty { .. }
1749 | Instruction::DeleteProperty { .. }
1750 | Instruction::DefineAccessor { .. }
1751 | Instruction::Call { .. }
1752 | Instruction::Construct { .. }
1753 | Instruction::LoadGlobal { .. }
1754 | Instruction::StoreGlobal { .. }
1755 | Instruction::ArrayPush { .. }
1756 | Instruction::ArrayExtend { .. }
1757 | Instruction::ObjectSpread { .. }
1758 | Instruction::SetPrototype { .. }
1759 | Instruction::CreateRegExp { .. }
1760 | Instruction::GetIterator { .. }
1761 | Instruction::IteratorNext { .. }
1762 | Instruction::Import { .. }
1763 | Instruction::Export { .. }
1764 | Instruction::Suspend { .. }
1765 | Instruction::Throw { .. } => true,
1766 Instruction::Move { .. }
1767 | Instruction::TypeOfGlobal { .. }
1768 | Instruction::LoadThis { .. }
1769 | Instruction::LoadArguments { .. }
1770 | Instruction::LoadNewTarget { .. }
1771 | Instruction::CreatePrivateName { .. }
1772 | Instruction::Jump { .. }
1773 | Instruction::JumpIfTrue { .. }
1774 | Instruction::JumpIfFalse { .. }
1775 | Instruction::Return { .. }
1776 | Instruction::Halt => false,
1777 }
1778}
1779
1780fn is_inline_instruction(instruction: Instruction) -> bool {
1784 match instruction {
1785 Instruction::Move { .. }
1786 | Instruction::Jump { .. }
1787 | Instruction::JumpIfTrue { .. }
1788 | Instruction::JumpIfFalse { .. }
1789 | Instruction::Return { .. }
1790 | Instruction::Halt
1791 | Instruction::Throw { .. }
1792 | Instruction::Suspend { .. } => true,
1793 Instruction::LoadConst { .. }
1794 | Instruction::Unary { .. }
1795 | Instruction::Binary { .. }
1796 | Instruction::CreateObject { .. }
1797 | Instruction::CreateArray { .. }
1798 | Instruction::CreateCell { .. }
1799 | Instruction::CreateClosure { .. }
1800 | Instruction::GetProperty { .. }
1801 | Instruction::SetProperty { .. }
1802 | Instruction::DeleteProperty { .. }
1803 | Instruction::DefineAccessor { .. }
1804 | Instruction::Call { .. }
1805 | Instruction::Construct { .. }
1806 | Instruction::LoadGlobal { .. }
1807 | Instruction::StoreGlobal { .. }
1808 | Instruction::TypeOfGlobal { .. }
1809 | Instruction::LoadThis { .. }
1810 | Instruction::LoadArguments { .. }
1811 | Instruction::LoadNewTarget { .. }
1812 | Instruction::ArrayPush { .. }
1813 | Instruction::ArrayExtend { .. }
1814 | Instruction::ObjectSpread { .. }
1815 | Instruction::SetPrototype { .. }
1816 | Instruction::CreatePrivateName { .. }
1817 | Instruction::CreateRegExp { .. }
1818 | Instruction::GetIterator { .. }
1819 | Instruction::IteratorNext { .. }
1820 | Instruction::Import { .. }
1821 | Instruction::Export { .. } => false,
1822 }
1823}
1824
1825fn resume_tokens(code: &[Instruction], reachable: &BTreeSet<usize>) -> Vec<u32> {
1828 let mut tokens = BTreeSet::new();
1829 if !code.is_empty() {
1830 tokens.insert(0u32);
1831 }
1832 for &pc in reachable {
1833 if let Instruction::Suspend { .. } = code[pc] {
1834 tokens.insert(pc as u32 + 1);
1835 }
1836 }
1837 tokens.into_iter().collect()
1838}
1839
1840fn innermost_handler(handlers: &[ExceptionHandler], pc: usize) -> Option<ExceptionHandler> {
1846 let pc = pc as u32;
1847 let mut best: Option<(usize, ExceptionHandler)> = None;
1848 for (index, handler) in handlers.iter().copied().enumerate() {
1849 if handler.start.get() > pc || pc >= handler.end.get() {
1850 continue;
1851 }
1852 let is_better = match best {
1853 None => true,
1854 Some((best_index, current)) => {
1855 (handler.start.get(), current.end.get(), index)
1856 > (current.start.get(), handler.end.get(), best_index)
1857 }
1858 };
1859 if is_better {
1860 best = Some((index, handler));
1861 }
1862 }
1863 best.map(|(_, handler)| handler)
1864}
1865
1866trait NormalSuccessors {
1867 fn visit_normal_successors(self, pc: usize, visit: impl FnMut(usize));
1869}
1870
1871impl NormalSuccessors for Instruction {
1872 fn visit_normal_successors(self, pc: usize, mut visit: impl FnMut(usize)) {
1873 match self {
1874 Instruction::Jump { target } => visit(target.get() as usize),
1875 Instruction::JumpIfTrue { target, .. } | Instruction::JumpIfFalse { target, .. } => {
1876 visit(target.get() as usize);
1877 visit(pc + 1);
1878 }
1879 Instruction::Suspend { resume, .. } => visit(resume.get() as usize),
1882 Instruction::Return { .. } | Instruction::Throw { .. } | Instruction::Halt => {}
1883 Instruction::LoadConst { .. }
1884 | Instruction::Move { .. }
1885 | Instruction::Unary { .. }
1886 | Instruction::Binary { .. }
1887 | Instruction::CreateObject { .. }
1888 | Instruction::CreateArray { .. }
1889 | Instruction::CreateCell { .. }
1890 | Instruction::CreateClosure { .. }
1891 | Instruction::GetProperty { .. }
1892 | Instruction::SetProperty { .. }
1893 | Instruction::DeleteProperty { .. }
1894 | Instruction::DefineAccessor { .. }
1895 | Instruction::Call { .. }
1896 | Instruction::Construct { .. }
1897 | Instruction::LoadGlobal { .. }
1898 | Instruction::StoreGlobal { .. }
1899 | Instruction::TypeOfGlobal { .. }
1900 | Instruction::LoadThis { .. }
1901 | Instruction::LoadArguments { .. }
1902 | Instruction::LoadNewTarget { .. }
1903 | Instruction::ArrayPush { .. }
1904 | Instruction::ArrayExtend { .. }
1905 | Instruction::ObjectSpread { .. }
1906 | Instruction::SetPrototype { .. }
1907 | Instruction::CreatePrivateName { .. }
1908 | Instruction::CreateRegExp { .. }
1909 | Instruction::GetIterator { .. }
1910 | Instruction::IteratorNext { .. }
1911 | Instruction::Import { .. }
1912 | Instruction::Export { .. } => visit(pc + 1),
1913 }
1914 }
1915}
1916
1917#[cfg(test)]
1918mod tests {
1919 use super::*;
1920 use bamts_bytecode::{
1921 Constant, ConstantId, EcmaString, Function as BytecodeFunction, FunctionFlags, Instruction,
1922 Pc, Register,
1923 };
1924 use cranelift_codegen::isa;
1925
1926 fn host_config() -> TargetFrontendConfig {
1928 let flags = Flags::new(settings::builder());
1929 for name in [
1930 "x86_64",
1931 "aarch64",
1932 "riscv64",
1933 "s390x",
1934 "x86_64-unknown-linux-gnu",
1935 ] {
1936 if let Ok(builder) = isa::lookup_by_name(name)
1937 && let Ok(target) = builder.finish(flags.clone())
1938 {
1939 return target.frontend_config();
1940 }
1941 }
1942 panic!("no native ISA available for tests");
1943 }
1944
1945 fn reg(index: u32) -> Register {
1946 Register::new(index)
1947 }
1948
1949 fn func(
1950 register_count: u32,
1951 code: Vec<Instruction>,
1952 handlers: Vec<ExceptionHandler>,
1953 ) -> BytecodeFunction {
1954 BytecodeFunction::new(
1955 None,
1956 0,
1957 0,
1958 register_count,
1959 FunctionFlags::default(),
1960 code,
1961 handlers,
1962 )
1963 }
1964
1965 fn verified(constants: Vec<Constant>, functions: Vec<BytecodeFunction>) -> Module<Verified> {
1966 Module::new(constants, functions, FunctionId::new(0))
1967 .verify()
1968 .expect("test module verifies")
1969 }
1970
1971 fn single(function: BytecodeFunction) -> Module<Verified> {
1972 verified(vec![Constant::Undefined], vec![function])
1973 }
1974
1975 #[test]
1976 fn capture_count_metadata_is_surfaced() {
1977 let function = BytecodeFunction::new(
1982 None,
1983 2, 1, 4, FunctionFlags::default(),
1987 vec![
1988 Instruction::Move {
1989 dst: reg(3),
1990 src: reg(0), },
1992 Instruction::Halt,
1993 ],
1994 Vec::new(),
1995 );
1996 let module = single(function);
1997 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
1998 assert_eq!(lowered.functions[0].capture_count, 2);
1999 }
2000
2001 fn load_undef(dst: Register) -> Instruction {
2004 Instruction::LoadConst {
2005 dst,
2006 constant: ConstantId::new(0),
2007 }
2008 }
2009
2010 fn clif_of(module: &Module<Verified>) -> String {
2011 let lowered = lower_code_module(ModuleId::new(0), module, host_config()).expect("lowers");
2012 lowered.functions[0].clif.display().to_string()
2013 }
2014
2015 fn lower_one(module: &Module<Verified>) -> (Vec<Helper>, String) {
2017 let lowered = lower_code_module(ModuleId::new(0), module, host_config()).expect("lowers");
2018 let function = &lowered.functions[0];
2019 (
2020 function.helpers.clone(),
2021 function.clif.display().to_string(),
2022 )
2023 }
2024
2025 #[test]
2026 fn entry_signature_is_the_native_abi() {
2027 let module = single(func(1, vec![Instruction::Halt], Vec::new()));
2028 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2029 let function = &lowered.functions[0];
2030 let signature = &function.signature;
2031 assert_eq!(signature.params.len(), 2);
2032 assert_eq!(signature.params[0].value_type, types::I64);
2033 assert_eq!(signature.params[1].value_type, types::I64);
2034 assert_eq!(signature.returns.len(), 1);
2035 assert_eq!(signature.returns[0].value_type, types::I32);
2036 assert_eq!(function.symbol, "bamts_m0_fn_0");
2037 assert_eq!(function.id.get(), 0);
2038 assert_eq!(lowered.entry.get(), 0);
2039 }
2040
2041 #[test]
2042 fn halt_only_function_returns_normal_with_undefined() {
2043 let module = single(func(1, vec![Instruction::Halt], Vec::new()));
2044 let clif = clif_of(&module);
2045 assert!(
2047 !clif.contains("load.i32"),
2048 "no dispatch load expected:\n{clif}"
2049 );
2050 assert!(
2051 clif.contains("0x7ffb_0000_0000_0000"),
2052 "undefined store missing:\n{clif}"
2053 );
2054 assert!(clif.contains("return"), "must return:\n{clif}");
2055 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2056 assert_eq!(lowered.functions[0].helpers, vec![Helper::ConsumeFuel]);
2057 assert_eq!(lowered.functions[0].entry_points, vec![0]);
2058 }
2059
2060 #[test]
2061 fn helper_index_table_is_a_stable_bijection() {
2062 let helpers = [
2065 Helper::LoadConstant,
2066 Helper::Unary,
2067 Helper::Binary,
2068 Helper::CreateObject,
2069 Helper::CreateArray,
2070 Helper::CreateClosure,
2071 Helper::GetProperty,
2072 Helper::SetProperty,
2073 Helper::DeleteProperty,
2074 Helper::Call,
2075 Helper::Construct,
2076 Helper::Import,
2077 Helper::Truthy,
2078 Helper::ResumeValue,
2079 Helper::DefineAccessor,
2080 Helper::LoadGlobal,
2081 Helper::StoreGlobal,
2082 Helper::TypeOfGlobal,
2083 Helper::LoadThis,
2084 Helper::LoadArguments,
2085 Helper::LoadNewTarget,
2086 Helper::ArrayPush,
2087 Helper::ArrayExtend,
2088 Helper::ObjectSpread,
2089 Helper::SetPrototype,
2090 Helper::CreatePrivateName,
2091 Helper::CreateRegExp,
2092 Helper::GetIterator,
2093 Helper::IteratorNext,
2094 Helper::Export,
2095 Helper::ConsumeFuel,
2096 Helper::CreateCell,
2097 ];
2098 let mut symbols = BTreeSet::new();
2099 for (expected_index, helper) in helpers.iter().copied().enumerate() {
2100 let index = helper.external_index();
2101 assert_eq!(index as usize, expected_index, "dense index for {helper:?}");
2102 assert_eq!(
2103 Helper::from_external_index(index),
2104 Some(helper),
2105 "round-trip for {helper:?}"
2106 );
2107 assert!(symbols.insert(helper.symbol()), "unique symbol {helper:?}");
2108 }
2109 assert_eq!(symbols.len(), 32);
2110 assert_eq!(Helper::from_external_index(32), None);
2111 }
2112
2113 #[test]
2114 fn load_const_routes_through_the_constant_helper() {
2115 let module = single(func(
2116 1,
2117 vec![load_undef(reg(0)), Instruction::Halt],
2118 Vec::new(),
2119 ));
2120 let (helpers, clif) = lower_one(&module);
2121 assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2122 assert_eq!(Helper::LoadConstant.symbol(), "bamts_load_constant");
2123 assert_eq!(Helper::LoadConstant.external_index(), 0);
2124 assert_eq!(Helper::from_external_index(0), Some(Helper::LoadConstant));
2125 assert!(
2126 clif.contains("u1:0"),
2127 "constant helper import missing:\n{clif}"
2128 );
2129 assert!(
2130 clif.contains("(i64, i32, i64) -> i32"),
2131 "constant helper sig wrong:\n{clif}"
2132 );
2133 assert!(clif.contains("call"), "helper call missing:\n{clif}");
2134 }
2135
2136 #[test]
2137 fn binary_routes_through_the_binary_helper() {
2138 let code = vec![
2139 load_undef(reg(0)),
2140 load_undef(reg(1)),
2141 Instruction::Binary {
2142 dst: reg(2),
2143 op: BinaryOp::Add,
2144 left: reg(0),
2145 right: reg(1),
2146 },
2147 Instruction::Halt,
2148 ];
2149 let module = single(func(3, code, Vec::new()));
2150 let (helpers, clif) = lower_one(&module);
2151 assert!(helpers.contains(&Helper::Binary));
2152 assert_eq!(Helper::Binary.external_index(), 2);
2153 assert!(
2154 clif.contains("u1:2"),
2155 "binary helper import missing:\n{clif}"
2156 );
2157 assert!(
2158 clif.contains("(i64, i32, i64, i64, i64) -> i32"),
2159 "binary helper sig wrong:\n{clif}"
2160 );
2161 }
2162
2163 #[test]
2164 fn unary_routes_through_the_unary_helper() {
2165 let code = vec![
2166 load_undef(reg(0)),
2167 Instruction::Unary {
2168 dst: reg(1),
2169 op: UnaryOp::Negate,
2170 operand: reg(0),
2171 },
2172 Instruction::Halt,
2173 ];
2174 let module = single(func(2, code, Vec::new()));
2175 let (helpers, clif) = lower_one(&module);
2176 assert!(helpers.contains(&Helper::Unary));
2177 assert!(
2178 clif.contains("u1:1"),
2179 "unary helper import missing:\n{clif}"
2180 );
2181 assert!(
2182 clif.contains("(i64, i32, i64, i64) -> i32"),
2183 "unary helper sig wrong:\n{clif}"
2184 );
2185 }
2186
2187 #[test]
2188 fn move_copies_registers_without_a_helper() {
2189 let code = vec![
2190 load_undef(reg(0)),
2191 Instruction::Move {
2192 dst: reg(1),
2193 src: reg(0),
2194 },
2195 Instruction::Halt,
2196 ];
2197 let module = single(func(2, code, Vec::new()));
2198 let (helpers, _) = lower_one(&module);
2199 assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2201 }
2202
2203 #[test]
2204 fn reachable_inline_pcs_each_emit_one_fuel_charge() {
2205 let code = vec![
2206 load_undef(reg(0)),
2207 Instruction::Move {
2208 dst: reg(1),
2209 src: reg(0),
2210 },
2211 Instruction::Jump { target: Pc::new(4) },
2212 Instruction::Binary {
2213 dst: reg(0),
2214 op: BinaryOp::Add,
2215 left: reg(0),
2216 right: reg(0),
2217 },
2218 Instruction::Halt,
2219 ];
2220 let module = single(func(2, code, Vec::new()));
2221 let (helpers, clif) = lower_one(&module);
2222 assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2223
2224 let declaration = clif
2225 .lines()
2226 .find(|line| line.contains("u1:30"))
2227 .expect("consume-fuel import");
2228 let function_ref = declaration
2229 .split_whitespace()
2230 .next()
2231 .expect("helper function reference");
2232 assert_eq!(
2233 clif.matches(&format!("call {function_ref}")).count(),
2234 3,
2235 "Move, Jump, and Halt each charge once; LoadConst and unreachable Binary do not:\n{clif}"
2236 );
2237 }
2238
2239 #[test]
2240 fn conditional_branch_coerces_via_truthy() {
2241 let code = vec![
2243 load_undef(reg(0)),
2244 Instruction::JumpIfTrue {
2245 condition: reg(0),
2246 target: Pc::new(3),
2247 },
2248 Instruction::Halt,
2249 Instruction::Halt,
2250 ];
2251 let module = single(func(1, code, Vec::new()));
2252 let (helpers, clif) = lower_one(&module);
2253 assert!(helpers.contains(&Helper::Truthy));
2254 assert_eq!(Helper::Truthy.external_index(), 12);
2255 assert!(
2256 clif.contains("u1:12"),
2257 "truthy helper import missing:\n{clif}"
2258 );
2259 assert!(
2261 clif.contains("(i64, i64) -> i32"),
2262 "truthy helper sig wrong:\n{clif}"
2263 );
2264 assert!(clif.contains("brif"), "conditional branch missing:\n{clif}");
2265 }
2266
2267 #[test]
2268 fn jump_if_false_branches_with_inverted_polarity() {
2269 let code = vec![
2273 load_undef(reg(0)),
2274 Instruction::JumpIfFalse {
2275 condition: reg(0),
2276 target: Pc::new(3),
2277 },
2278 Instruction::Halt,
2279 Instruction::Halt,
2280 ];
2281 let module = single(func(1, code, Vec::new()));
2282 let (helpers, clif) = lower_one(&module);
2283 assert!(helpers.contains(&Helper::Truthy));
2284 let truthy_declaration = clif
2285 .lines()
2286 .find(|line| line.contains("u1:12"))
2287 .expect("truthy import");
2288 let truthy_ref = truthy_declaration
2289 .split_whitespace()
2290 .next()
2291 .expect("truthy function reference");
2292 let mut lines = clif.lines();
2293 lines
2294 .find(|line| line.contains(&format!("call {truthy_ref}")))
2295 .expect("truthy call");
2296 let brif = lines
2297 .find(|line| line.contains("brif"))
2298 .expect("conditional branch missing");
2299 let edges: Vec<u32> = brif
2300 .split(|c: char| !c.is_ascii_alphanumeric())
2301 .filter_map(|token| token.strip_prefix("block").and_then(|n| n.parse().ok()))
2302 .collect();
2303 assert_eq!(
2304 edges.len(),
2305 2,
2306 "conditional brif has two block edges:\n{clif}"
2307 );
2308 assert!(
2309 edges[0] < edges[1],
2310 "JumpIfFalse polarity: truthy edge must target the earlier fallthrough block:\n{clif}"
2311 );
2312 }
2313
2314 #[test]
2315 fn create_closure_passes_the_captures_value_not_an_index() {
2316 let code = vec![
2318 load_undef(reg(0)),
2319 Instruction::CreateClosure {
2320 dst: reg(1),
2321 function: FunctionId::new(0),
2322 captures: reg(0),
2323 },
2324 Instruction::Halt,
2325 ];
2326 let module = single(func(2, code, Vec::new()));
2327 let (helpers, clif) = lower_one(&module);
2328 assert!(helpers.contains(&Helper::CreateClosure));
2329 assert_eq!(Helper::CreateClosure.external_index(), 5);
2330 assert!(
2331 clif.contains("u1:5"),
2332 "closure helper import missing:\n{clif}"
2333 );
2334 assert!(
2336 clif.contains("(i64, i32, i64, i64) -> i32"),
2337 "closure helper sig wrong:\n{clif}"
2338 );
2339 assert!(
2342 !clif.contains("iadd"),
2343 "closure captures must be a value, not a computed pointer:\n{clif}"
2344 );
2345 }
2346
2347 #[test]
2348 fn call_passes_arguments_as_a_value_without_pointer_math() {
2349 let code = vec![
2351 load_undef(reg(0)),
2352 load_undef(reg(1)),
2353 load_undef(reg(2)),
2354 Instruction::Call {
2355 dst: reg(3),
2356 callee: reg(0),
2357 this_value: reg(1),
2358 arguments: reg(2),
2359 },
2360 Instruction::Halt,
2361 ];
2362 let module = single(func(4, code, Vec::new()));
2363 let (helpers, clif) = lower_one(&module);
2364 assert!(helpers.contains(&Helper::Call));
2365 assert_eq!(Helper::Call.external_index(), 9);
2366 assert!(clif.contains("u1:9"), "call helper import missing:\n{clif}");
2367 assert!(
2369 clif.contains("(i64, i64, i64, i64, i64) -> i32"),
2370 "call helper sig wrong:\n{clif}"
2371 );
2372 assert!(
2375 !clif.contains("iadd"),
2376 "arguments must be a value, not a window pointer:\n{clif}"
2377 );
2378 }
2379
2380 #[test]
2381 fn construct_passes_arguments_as_a_value() {
2382 let code = vec![
2383 load_undef(reg(0)),
2384 load_undef(reg(1)),
2385 Instruction::Construct {
2386 dst: reg(2),
2387 callee: reg(0),
2388 arguments: reg(1),
2389 },
2390 Instruction::Halt,
2391 ];
2392 let module = single(func(3, code, Vec::new()));
2393 let (helpers, clif) = lower_one(&module);
2394 assert!(helpers.contains(&Helper::Construct));
2395 assert_eq!(Helper::Construct.external_index(), 10);
2396 assert!(clif.contains("u1:10"), "construct import missing:\n{clif}");
2397 assert!(
2398 clif.contains("(i64, i64, i64, i64) -> i32"),
2399 "construct helper sig wrong:\n{clif}"
2400 );
2401 }
2402
2403 #[test]
2404 fn calls_scale_past_fixed_window_via_arguments_array() {
2405 let code = vec![
2408 load_undef(reg(0)),
2409 load_undef(reg(1)),
2410 load_undef(reg(2)),
2411 Instruction::Call {
2412 dst: reg(3),
2413 callee: reg(0),
2414 this_value: reg(1),
2415 arguments: reg(2),
2416 },
2417 Instruction::Halt,
2418 ];
2419 let module = single(func(4, code, Vec::new()));
2420 let (_, clif) = lower_one(&module);
2421 assert!(
2423 !clif.contains("iadd"),
2424 "no window arithmetic for any arity:\n{clif}"
2425 );
2426 }
2427
2428 #[test]
2429 fn property_access_uses_a_register_key() {
2430 let code = vec![
2432 Instruction::CreateObject { dst: reg(0) },
2433 load_undef(reg(1)),
2434 Instruction::GetProperty {
2435 dst: reg(2),
2436 object: reg(0),
2437 key: reg(1),
2438 },
2439 Instruction::SetProperty {
2440 object: reg(0),
2441 key: reg(1),
2442 value: reg(1),
2443 },
2444 Instruction::DeleteProperty {
2445 dst: reg(3),
2446 object: reg(0),
2447 key: reg(1),
2448 },
2449 Instruction::Halt,
2450 ];
2451 let module = single(func(4, code, Vec::new()));
2452 let (helpers, clif) = lower_one(&module);
2453 assert!(helpers.contains(&Helper::GetProperty));
2454 assert!(helpers.contains(&Helper::SetProperty));
2455 assert!(helpers.contains(&Helper::DeleteProperty));
2456 assert!(
2457 clif.contains("u1:6"),
2458 "get-property import missing:\n{clif}"
2459 );
2460 assert!(
2461 clif.contains("u1:7"),
2462 "set-property import missing:\n{clif}"
2463 );
2464 assert!(
2465 clif.contains("u1:8"),
2466 "delete-property import missing:\n{clif}"
2467 );
2468 assert!(
2470 clif.contains("(i64, i64, i64, i64) -> i32"),
2471 "get/delete property sig wrong (register key):\n{clif}"
2472 );
2473 assert!(
2475 clif.contains("(i64, i64, i64, i64, i64) -> i32"),
2476 "set property sig wrong (register key):\n{clif}"
2477 );
2478 }
2479
2480 #[test]
2481 fn define_accessor_carries_a_kind_selector() {
2482 let code = vec![
2483 Instruction::CreateObject { dst: reg(0) },
2484 load_undef(reg(1)),
2485 load_undef(reg(2)),
2486 Instruction::DefineAccessor {
2487 object: reg(0),
2488 key: reg(1),
2489 accessor: reg(2),
2490 kind: AccessorKind::Getter,
2491 },
2492 Instruction::Halt,
2493 ];
2494 let module = single(func(3, code, Vec::new()));
2495 let (helpers, clif) = lower_one(&module);
2496 assert!(helpers.contains(&Helper::DefineAccessor));
2497 assert_eq!(Helper::DefineAccessor.external_index(), 14);
2498 assert!(clif.contains("u1:14"), "accessor import missing:\n{clif}");
2499 assert!(
2501 clif.contains("(i64, i64, i64, i64, i32, i64) -> i32"),
2502 "accessor helper sig wrong:\n{clif}"
2503 );
2504 }
2505
2506 #[test]
2507 fn globals_lower_to_load_store_and_typeof_helpers() {
2508 let code = vec![
2509 Instruction::LoadGlobal {
2510 dst: reg(0),
2511 name: ConstantId::new(0),
2512 },
2513 Instruction::StoreGlobal {
2514 name: ConstantId::new(0),
2515 value: reg(0),
2516 },
2517 Instruction::TypeOfGlobal {
2518 dst: reg(1),
2519 name: ConstantId::new(0),
2520 },
2521 Instruction::Halt,
2522 ];
2523 let module = verified(
2524 vec![Constant::String(EcmaString::from_utf8("g"))],
2525 vec![func(2, code, Vec::new())],
2526 );
2527 let (helpers, clif) = lower_one(&module);
2528 assert!(helpers.contains(&Helper::LoadGlobal));
2529 assert!(helpers.contains(&Helper::StoreGlobal));
2530 assert!(helpers.contains(&Helper::TypeOfGlobal));
2531 assert!(
2532 clif.contains("u1:15"),
2533 "load-global import missing:\n{clif}"
2534 );
2535 assert!(
2536 clif.contains("u1:16"),
2537 "store-global import missing:\n{clif}"
2538 );
2539 assert!(
2540 clif.contains("u1:17"),
2541 "typeof-global import missing:\n{clif}"
2542 );
2543 }
2544
2545 #[test]
2546 fn this_arguments_new_target_are_total_and_unhandled() {
2547 let code = vec![
2551 Instruction::LoadThis { dst: reg(0) },
2552 Instruction::LoadArguments { dst: reg(1) },
2553 Instruction::LoadNewTarget { dst: reg(2) },
2554 Instruction::Halt,
2555 ];
2556 let module = single(func(3, code, Vec::new()));
2557 let (helpers, clif) = lower_one(&module);
2558 assert!(helpers.contains(&Helper::LoadThis));
2559 assert!(helpers.contains(&Helper::LoadArguments));
2560 assert!(helpers.contains(&Helper::LoadNewTarget));
2561 assert!(clif.contains("u1:18"), "load-this import missing:\n{clif}");
2562 assert!(
2563 clif.contains("u1:19"),
2564 "load-arguments import missing:\n{clif}"
2565 );
2566 assert!(
2567 clif.contains("u1:20"),
2568 "load-new-target import missing:\n{clif}"
2569 );
2570 }
2571
2572 #[test]
2573 fn total_helper_under_handler_does_not_emit_handler_edge() {
2574 let code = vec![
2579 Instruction::TypeOfGlobal {
2580 dst: reg(0),
2581 name: ConstantId::new(0),
2582 },
2583 Instruction::Halt,
2584 load_undef(reg(0)),
2586 Instruction::Halt,
2587 ];
2588 let handlers = vec![ExceptionHandler {
2589 start: Pc::new(0),
2590 end: Pc::new(1),
2591 handler: Pc::new(2),
2592 catch_register: reg(0),
2593 }];
2594 let module = verified(
2595 vec![Constant::String(EcmaString::from_utf8("g"))],
2596 vec![func(1, code, handlers)],
2597 );
2598 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2600 assert!(lowered.functions[0].helpers.contains(&Helper::TypeOfGlobal));
2601 }
2602
2603 #[test]
2604 fn arrays_and_spreads_lower_to_their_helpers() {
2605 let code = vec![
2606 Instruction::CreateArray { dst: reg(0) },
2607 load_undef(reg(1)),
2608 Instruction::ArrayPush {
2609 array: reg(0),
2610 value: reg(1),
2611 },
2612 Instruction::ArrayExtend {
2613 array: reg(0),
2614 iterable: reg(1),
2615 },
2616 Instruction::CreateObject { dst: reg(2) },
2617 Instruction::ObjectSpread {
2618 target: reg(2),
2619 source: reg(1),
2620 },
2621 Instruction::Halt,
2622 ];
2623 let module = single(func(3, code, Vec::new()));
2624 let (helpers, clif) = lower_one(&module);
2625 assert!(helpers.contains(&Helper::ArrayPush));
2626 assert!(helpers.contains(&Helper::ArrayExtend));
2627 assert!(helpers.contains(&Helper::ObjectSpread));
2628 assert!(clif.contains("u1:21"), "array-push import missing:\n{clif}");
2629 assert!(
2630 clif.contains("u1:22"),
2631 "array-extend import missing:\n{clif}"
2632 );
2633 assert!(
2634 clif.contains("u1:23"),
2635 "object-spread import missing:\n{clif}"
2636 );
2637 }
2638
2639 #[test]
2640 fn prototype_private_and_regexp_lower_to_their_helpers() {
2641 let code = vec![
2642 Instruction::CreateObject { dst: reg(0) },
2643 Instruction::CreateObject { dst: reg(1) },
2644 Instruction::SetPrototype {
2645 object: reg(0),
2646 prototype: reg(1),
2647 },
2648 Instruction::CreatePrivateName {
2649 dst: reg(2),
2650 description: ConstantId::new(0),
2651 },
2652 Instruction::CreateRegExp {
2653 dst: reg(3),
2654 pattern: ConstantId::new(0),
2655 flags: ConstantId::new(1),
2656 },
2657 Instruction::Halt,
2658 ];
2659 let module = verified(
2660 vec![
2661 Constant::String(EcmaString::from_utf8("p")),
2662 Constant::String(EcmaString::from_utf8("g")),
2663 ],
2664 vec![func(4, code, Vec::new())],
2665 );
2666 let (helpers, clif) = lower_one(&module);
2667 assert!(helpers.contains(&Helper::SetPrototype));
2668 assert!(helpers.contains(&Helper::CreatePrivateName));
2669 assert!(helpers.contains(&Helper::CreateRegExp));
2670 assert!(
2671 clif.contains("u1:24"),
2672 "set-prototype import missing:\n{clif}"
2673 );
2674 assert!(
2675 clif.contains("u1:25"),
2676 "private-name import missing:\n{clif}"
2677 );
2678 assert!(clif.contains("u1:26"), "regexp import missing:\n{clif}");
2679 assert!(
2681 clif.contains("(i64, i32, i32, i64) -> i32"),
2682 "regexp helper sig wrong:\n{clif}"
2683 );
2684 }
2685
2686 #[test]
2687 fn get_iterator_carries_a_kind_selector() {
2688 let code = vec![
2689 load_undef(reg(0)),
2690 Instruction::GetIterator {
2691 dst: reg(1),
2692 src: reg(0),
2693 kind: IteratorKind::Sync,
2694 },
2695 Instruction::Halt,
2696 ];
2697 let module = single(func(2, code, Vec::new()));
2698 let (helpers, clif) = lower_one(&module);
2699 assert!(helpers.contains(&Helper::GetIterator));
2700 assert_eq!(Helper::GetIterator.external_index(), 27);
2701 assert!(
2702 clif.contains("u1:27"),
2703 "get-iterator import missing:\n{clif}"
2704 );
2705 assert!(
2707 clif.contains("(i64, i64, i32, i64) -> i32"),
2708 "get-iterator helper sig wrong:\n{clif}"
2709 );
2710 }
2711
2712 #[test]
2713 fn iterator_next_writes_both_done_and_value_registers() {
2714 let code = vec![
2716 load_undef(reg(0)),
2717 Instruction::IteratorNext {
2718 done: reg(1),
2719 value: reg(2),
2720 iterator: reg(0),
2721 },
2722 Instruction::Halt,
2723 ];
2724 let module = single(func(3, code, Vec::new()));
2725 let (helpers, clif) = lower_one(&module);
2726 assert!(helpers.contains(&Helper::IteratorNext));
2727 assert_eq!(Helper::IteratorNext.external_index(), 28);
2728 assert!(
2729 clif.contains("u1:28"),
2730 "iterator-next import missing:\n{clif}"
2731 );
2732 assert!(
2735 clif.contains("(i64, i64, i32, i32, i64) -> i32"),
2736 "iterator-next helper sig wrong:\n{clif}"
2737 );
2738 assert!(
2741 clif.contains("iconst.i32 1") && clif.contains("iconst.i32 2"),
2742 "both destination register indices must be passed:\n{clif}"
2743 );
2744 }
2745
2746 #[test]
2747 fn iterator_next_under_handler_binds_catch_on_throw() {
2748 let code = vec![
2750 load_undef(reg(0)),
2751 Instruction::IteratorNext {
2752 done: reg(1),
2753 value: reg(2),
2754 iterator: reg(0),
2755 },
2756 Instruction::Halt,
2757 Instruction::Halt,
2759 ];
2760 let handlers = vec![ExceptionHandler {
2761 start: Pc::new(0),
2762 end: Pc::new(3),
2763 handler: Pc::new(3),
2764 catch_register: reg(0),
2765 }];
2766 let module = single(func(3, code, handlers));
2767 let clif = clif_of(&module);
2768 let brif_count = clif.matches("brif").count();
2770 assert!(
2771 brif_count >= 2,
2772 "expected handler routing brifs, got {brif_count}:\n{clif}"
2773 );
2774 }
2775
2776 #[test]
2777 fn export_lowers_to_the_export_helper() {
2778 let code = vec![
2779 load_undef(reg(0)),
2780 Instruction::Export {
2781 name: ConstantId::new(0),
2782 src: reg(0),
2783 },
2784 Instruction::Halt,
2785 ];
2786 let module = verified(
2787 vec![Constant::String(EcmaString::from_utf8("x"))],
2788 vec![func(1, code, Vec::new())],
2789 );
2790 let (helpers, clif) = lower_one(&module);
2791 assert!(helpers.contains(&Helper::Export));
2792 assert_eq!(Helper::Export.external_index(), 29);
2793 assert!(clif.contains("u1:29"), "export import missing:\n{clif}");
2794 assert!(
2795 clif.contains("(i64, i32, i64, i64) -> i32"),
2796 "export helper sig wrong:\n{clif}"
2797 );
2798 }
2799
2800 #[test]
2801 fn suspend_uses_a_resume_token_and_resume_helper() {
2802 let code = vec![
2804 load_undef(reg(0)),
2805 Instruction::Suspend {
2806 dst: reg(0),
2807 src: reg(0),
2808 resume: Pc::new(2),
2809 },
2810 Instruction::Halt,
2811 ];
2812 let module = single(func(1, code, Vec::new()));
2813 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2814 let function = &lowered.functions[0];
2815 assert_eq!(function.entry_points, vec![0, 2]);
2817 assert!(function.helpers.contains(&Helper::ResumeValue));
2818 assert_eq!(Helper::ResumeValue.external_index(), 13);
2819 let clif = function.clif.display().to_string();
2820 assert!(
2822 clif.contains("load.i32"),
2823 "dispatch token load missing:\n{clif}"
2824 );
2825 assert!(clif.contains("icmp"), "dispatch compare missing:\n{clif}");
2826 assert!(
2828 clif.contains("store"),
2829 "resume token store missing:\n{clif}"
2830 );
2831 assert!(
2832 clif.contains("u1:13"),
2833 "resume helper import missing:\n{clif}"
2834 );
2835 }
2836
2837 #[test]
2838 fn throwing_op_under_a_handler_routes_and_binds_catch_register() {
2839 let code = vec![
2841 load_undef(reg(0)),
2842 load_undef(reg(1)),
2843 Instruction::Binary {
2844 dst: reg(2),
2845 op: BinaryOp::Add,
2846 left: reg(0),
2847 right: reg(1),
2848 },
2849 Instruction::Halt,
2850 ];
2851 let handlers = vec![ExceptionHandler {
2852 start: Pc::new(0),
2853 end: Pc::new(3),
2854 handler: Pc::new(3),
2855 catch_register: reg(0),
2856 }];
2857 let module = single(func(3, code, handlers));
2858 let clif = clif_of(&module);
2859 let brif_count = clif.matches("brif").count();
2861 assert!(
2862 brif_count >= 2,
2863 "expected handler routing brifs, got {brif_count}:\n{clif}"
2864 );
2865 assert!(
2866 clif.contains("icmp"),
2867 "throw discriminator missing:\n{clif}"
2868 );
2869 }
2870
2871 #[test]
2872 fn explicit_throw_binds_catch_register_and_jumps() {
2873 let code = vec![
2875 load_undef(reg(0)),
2876 Instruction::Throw { value: reg(0) },
2877 Instruction::Halt,
2878 ];
2879 let handlers = vec![ExceptionHandler {
2880 start: Pc::new(0),
2881 end: Pc::new(2),
2882 handler: Pc::new(2),
2883 catch_register: reg(0),
2884 }];
2885 let module = single(func(1, code, handlers));
2886 let (helpers, clif) = lower_one(&module);
2887 assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2889 assert!(clif.contains("jump"), "handler jump missing:\n{clif}");
2890 assert!(
2891 clif.contains("store"),
2892 "catch-register bind missing:\n{clif}"
2893 );
2894 }
2895
2896 #[test]
2897 fn return_writes_completion_and_normal_tag() {
2898 let code = vec![load_undef(reg(0)), Instruction::Return { value: reg(0) }];
2899 let module = single(func(1, code, Vec::new()));
2900 let (helpers, clif) = lower_one(&module);
2901 assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2902 assert!(
2903 clif.contains("store"),
2904 "return value store missing:\n{clif}"
2905 );
2906 assert!(clif.contains("return"), "return missing:\n{clif}");
2907 }
2908
2909 #[test]
2910 fn high_register_offsets_scale_past_127() {
2911 let code = vec![load_undef(reg(500)), Instruction::Halt];
2915 let module = single(func(501, code, Vec::new()));
2916 let (_, clif) = lower_one(&module);
2917 assert!(
2918 clif.contains("+4000"),
2919 "expected a +4000 byte offset for r500:\n{clif}"
2920 );
2921 }
2922
2923 #[test]
2924 fn import_and_closure_are_lowered() {
2925 let code = vec![
2926 load_undef(reg(0)),
2927 Instruction::CreateClosure {
2928 dst: reg(1),
2929 function: FunctionId::new(0),
2930 captures: reg(0),
2931 },
2932 Instruction::Import {
2933 dst: reg(2),
2934 specifier: ConstantId::new(0),
2935 },
2936 Instruction::Halt,
2937 ];
2938 let module = verified(
2939 vec![Constant::String(EcmaString::from_utf8("mod"))],
2940 vec![func(3, code, Vec::new())],
2941 );
2942 let (helpers, _) = lower_one(&module);
2943 assert!(helpers.contains(&Helper::CreateClosure));
2944 assert!(helpers.contains(&Helper::Import));
2945 }
2946
2947 #[test]
2948 fn lowering_is_deterministic() {
2949 let code = vec![
2950 load_undef(reg(0)),
2951 Instruction::Binary {
2952 dst: reg(1),
2953 op: BinaryOp::Add,
2954 left: reg(0),
2955 right: reg(0),
2956 },
2957 Instruction::Jump { target: Pc::new(3) },
2958 Instruction::Halt,
2959 ];
2960 let make = || single(func(2, code.clone(), Vec::new()));
2961 let a = clif_of(&make());
2962 let b = clif_of(&make());
2963 assert_eq!(a, b);
2964 }
2965
2966 #[test]
2967 fn unreachable_code_is_not_emitted() {
2968 let code = vec![
2971 Instruction::Jump { target: Pc::new(2) },
2972 Instruction::Binary {
2973 dst: reg(0),
2974 op: BinaryOp::Add,
2975 left: reg(0),
2976 right: reg(0),
2977 },
2978 Instruction::Halt,
2979 ];
2980 let module = single(func(1, code, Vec::new()));
2981 let (helpers, _) = lower_one(&module);
2982 assert_eq!(
2983 helpers,
2984 vec![Helper::ConsumeFuel],
2985 "unreachable Binary must not lower its helper"
2986 );
2987 }
2988
2989 #[test]
2990 fn multiple_functions_get_distinct_symbols() {
2991 let functions = vec![
2992 func(0, vec![Instruction::Halt], Vec::new()),
2993 func(0, vec![Instruction::Halt], Vec::new()),
2994 ];
2995 let module = verified(Vec::new(), functions);
2996 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2997 assert_eq!(lowered.functions.len(), 2);
2998 assert_eq!(lowered.functions[0].symbol, "bamts_m0_fn_0");
2999 assert_eq!(lowered.functions[1].symbol, "bamts_m0_fn_1");
3000 let name0 = lowered.functions[0].clif.display().to_string();
3001 assert!(name0.contains("u0:0"), "function 0 name wrong:\n{name0}");
3002 let name1 = lowered.functions[1].clif.display().to_string();
3003 assert!(name1.contains("u0:1"), "function 1 name wrong:\n{name1}");
3004 }
3005
3006 #[test]
3007 fn innermost_handler_prefers_the_tightest_interval() {
3008 let outer = ExceptionHandler {
3009 start: Pc::new(0),
3010 end: Pc::new(10),
3011 handler: Pc::new(20),
3012 catch_register: reg(0),
3013 };
3014 let inner = ExceptionHandler {
3015 start: Pc::new(2),
3016 end: Pc::new(6),
3017 handler: Pc::new(30),
3018 catch_register: reg(1),
3019 };
3020 let handlers = [outer, inner];
3021 assert_eq!(
3022 innermost_handler(&handlers, 4).map(|h| h.handler),
3023 Some(Pc::new(30))
3024 );
3025 assert_eq!(
3026 innermost_handler(&handlers, 8).map(|h| h.handler),
3027 Some(Pc::new(20))
3028 );
3029 assert_eq!(innermost_handler(&handlers, 10), None);
3030 }
3031
3032 #[test]
3033 fn non_64_bit_targets_are_rejected() {
3034 let config = TargetFrontendConfig {
3035 default_call_conv: CallConv::SystemV,
3036 pointer_width: {
3037 let flags = Flags::new(settings::builder());
3038 match isa::lookup_by_name("i686")
3039 .ok()
3040 .and_then(|b| b.finish(flags).ok())
3041 {
3042 Some(target) => target.frontend_config().pointer_width,
3043 None => return, }
3045 },
3046 page_size_align_log2: 12,
3047 };
3048 let module = single(func(0, vec![Instruction::Halt], Vec::new()));
3049 let error =
3050 lower_code_module(ModuleId::new(0), &module, config).expect_err("32-bit rejected");
3051 assert!(matches!(
3052 error,
3053 LowerError::UnsupportedPointerWidth { bits: 32 }
3054 ));
3055 }
3056
3057 #[test]
3058 fn error_display_is_stable() {
3059 let width = LowerError::UnsupportedPointerWidth { bits: 32 };
3060 assert!(width.to_string().contains("64-bit"));
3061 let many = LowerError::TooManyFunctions { count: 200 };
3062 assert!(many.to_string().contains("200"));
3063 let slots = LowerError::RegisterFileTooLarge {
3064 function: FunctionId::new(1),
3065 register_count: 9,
3066 };
3067 assert!(slots.to_string().contains("function 1"));
3068 let sig = LowerError::EntrySignatureMismatch {
3069 function: FunctionId::new(2),
3070 };
3071 assert!(sig.to_string().contains("function 2"));
3072 let ir = LowerError::IrVerification {
3073 function: FunctionId::new(3),
3074 message: "boom".to_string(),
3075 };
3076 let text = ir.to_string();
3077 assert!(text.contains("function 3"));
3078 assert!(text.contains("boom"));
3079 }
3080
3081 #[test]
3082 fn constant_pool_does_not_perturb_lowering() {
3083 let functions = vec![BytecodeFunction::new(
3084 Some(ConstantId::new(0)),
3085 0,
3086 0,
3087 0,
3088 FunctionFlags::default(),
3089 vec![Instruction::Halt],
3090 Vec::new(),
3091 )];
3092 let module = Module::new(
3093 vec![Constant::String(EcmaString::from_utf8("main"))],
3094 functions,
3095 FunctionId::new(0),
3096 )
3097 .verify()
3098 .expect("verifies");
3099 let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
3100 assert_eq!(lowered.functions[0].symbol, "bamts_m0_fn_0");
3101 }
3102 #[test]
3103 fn program_lowering_retains_module_local_ids_and_entry_tuple() {
3104 let make_module = |name: &str| bamts_bytecode::ProgramModule {
3105 name: ConstantId::new(0),
3106 code: Module::new(
3107 vec![Constant::String(EcmaString::from_utf8(name))],
3108 vec![func(0, vec![Instruction::Halt], Vec::new())],
3109 FunctionId::new(0),
3110 )
3111 .verify()
3112 .expect("module verifies"),
3113 edges: Vec::new(),
3114 bindings: Vec::new(),
3115 exports: Vec::new(),
3116 };
3117 let program = Program::link(
3118 vec![make_module("dependency"), make_module("entry")],
3119 ModuleId::new(1),
3120 )
3121 .expect("program verifies");
3122
3123 let lowered = lower_program(&program, host_config()).expect("program lowers");
3124 assert_eq!(lowered.modules.len(), 2);
3125 assert_eq!(lowered.modules[0].id, ModuleId::new(0));
3126 assert_eq!(lowered.modules[1].id, ModuleId::new(1));
3127 assert_eq!(lowered.modules[0].functions[0].id, FunctionId::new(0));
3128 assert_eq!(lowered.modules[1].functions[0].id, FunctionId::new(0));
3129 assert_eq!(lowered.modules[0].functions[0].symbol, "bamts_m0_fn_0");
3130 assert_eq!(lowered.modules[1].functions[0].symbol, "bamts_m1_fn_0");
3131 assert_eq!(lowered.entry_module, ModuleId::new(1));
3132 assert_eq!(lowered.entry_function, FunctionId::new(0));
3133 }
3134}