1use crate::awk_host::AwkHost;
27use crate::chunk::Chunk;
28use crate::host::ShellHost;
29#[cfg(feature = "jit")]
30use crate::jit::{DeoptInfo, JitCompiler, SlotKind, TraceLookup};
31use crate::op::Op;
32use crate::value::Value;
33
34#[cfg(feature = "jit")]
65struct TraceRecorder {
66 record_anchor_ip: usize,
69 close_anchor_ip: usize,
73 fallthrough_ip: usize,
76 ops: Vec<Op>,
78 recorded_ips: Vec<usize>,
83 slot_kinds_at_anchor: Vec<SlotKind>,
85 global_kinds_at_anchor: Vec<SlotKind>,
89 global_numeric_at_anchor: Vec<bool>,
90 entered_ips: Vec<usize>,
94 aborted: bool,
97}
98
99#[derive(Debug, Clone)]
101pub struct Frame {
102 pub return_ip: usize,
104 pub stack_base: usize,
106 pub slots: Vec<Value>,
108 pub entry_ip: Option<usize>,
120}
121
122pub type ExtensionHandler = Box<dyn FnMut(&mut VM, u16, u8) + Send>;
125pub type ExtensionWideHandler = Box<dyn FnMut(&mut VM, u16, usize) + Send>;
127pub type BuiltinHandler = fn(&mut VM, u8) -> Value;
129
130pub struct VM {
132 pub stack: Vec<Value>,
134 pub frames: Vec<Frame>,
136 pub globals: Vec<Value>,
138 pub ip: usize,
140 pub chunk: Chunk,
142 pub last_status: i32,
144 output_sink: Option<OutputSink>,
147 input_source: Option<InputSource>,
150 numeric_hook: Option<NumericHook>,
154 sited_numeric_hook: Option<SitedNumericHook>,
158 undef_hook: Option<UndefHook>,
162 chunk_ident: std::cell::Cell<u64>,
167 fixnum_range: Option<(i64, i64)>,
173 #[cfg(feature = "jit")]
179 slots_all_numeric: bool,
180 ext_handler: Option<ExtensionHandler>,
182 ext_wide_handler: Option<ExtensionWideHandler>,
184 builtin_table: Vec<Option<BuiltinHandler>>,
186 pub host: Option<Box<dyn ShellHost>>,
189 pub awk_host: Option<Box<dyn AwkHost>>,
194 awk_rand_seed: u64,
199 awk_signal: Option<u8>,
205 sched: Option<crate::sched::SchedReq>,
212 halted: bool,
214 #[cfg(feature = "jit")]
217 tracing_jit: bool,
218 #[cfg(feature = "jit")]
220 jit: JitCompiler,
221 #[cfg(feature = "jit")]
223 recorder: Option<TraceRecorder>,
224 #[cfg(feature = "jit")]
226 slot_buf: Vec<i64>,
227 #[cfg(feature = "jit")]
229 slot_kinds_buf: Vec<SlotKind>,
230 #[cfg(feature = "jit")]
234 global_buf: Vec<i64>,
235 #[cfg(feature = "jit")]
238 global_kinds_buf: Vec<SlotKind>,
239 #[cfg(feature = "jit")]
249 global_numeric: Vec<bool>,
250 #[cfg(feature = "jit")]
257 deopt_info: DeoptInfo,
258 #[cfg(feature = "jit")]
264 block_eligible_cached: Option<bool>,
265 #[cfg(feature = "aot")]
269 aot_result: Option<VMResult>,
270 #[cfg(feature = "aot")]
276 aot_guard_failed: bool,
277 #[cfg(feature = "aot")]
282 aot_arena: Vec<Value>,
283 #[cfg(feature = "aot")]
288 aot_free: Vec<u32>,
289}
290
291#[derive(Debug)]
293pub enum VMResult {
294 Ok(Value),
295 Halted,
297 Error(String),
299}
300
301pub(crate) enum ExecFlow {
307 Cont,
308 Ret(VMResult),
309}
310
311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub enum NumOp {
317 Add,
318 Sub,
319 Mul,
320 Div,
321 Mod,
322 Pow,
323 Neg,
324 Lt,
325 Gt,
326 Le,
327 Ge,
328 Eq,
329 Ne,
330}
331
332pub type NumericHook =
360 std::sync::Arc<dyn Fn(NumOp, &Value, &Value) -> Result<Value, String> + Send + Sync>;
361
362pub struct NumericCall<'a> {
378 pub op: NumOp,
380 pub a: &'a Value,
382 pub b: &'a Value,
384 pub chunk: u64,
389 pub ip: usize,
397}
398
399pub type SitedNumericHook =
407 std::sync::Arc<dyn for<'a> Fn(NumericCall<'a>) -> Result<Value, String> + Send + Sync>;
408
409#[derive(Debug, Clone, Copy)]
416pub struct UndefRead<'a> {
417 pub name: Option<&'a str>,
419 pub index: u16,
421 pub from_slot: bool,
423 pub chunk: u64,
437 pub ip: usize,
447}
448
449pub type UndefHook =
479 std::sync::Arc<dyn for<'a> Fn(UndefRead<'a>) -> Result<Value, String> + Send + Sync>;
480
481pub type OutputSink = Box<dyn FnMut(&str) + Send>;
491
492pub type InputSource = Box<dyn FnMut() -> Option<String> + Send>;
501
502#[inline(always)]
504fn is_native_num(v: &Value) -> bool {
505 matches!(v, Value::Int(_) | Value::Float(_))
506}
507
508#[inline(always)]
532fn f64_would_round(v: &Value) -> bool {
533 matches!(v, Value::Int(x) if x.unsigned_abs() > (1u64 << 53))
534}
535
536impl VM {
537 pub fn new(chunk: Chunk) -> Self {
543 let num_names = chunk.names.len();
544 let mut frames = Vec::with_capacity(32);
545 frames.push(Frame {
546 return_ip: 0,
547 stack_base: 0,
548 slots: Vec::with_capacity(16),
549 entry_ip: None,
550 });
551 Self {
552 stack: Vec::with_capacity(256),
553 frames,
554 globals: vec![Value::Undef; num_names],
555 ip: 0,
556 chunk,
557 last_status: 0,
558 output_sink: None,
559 input_source: None,
560 numeric_hook: None,
561 sited_numeric_hook: None,
562 undef_hook: None,
563 chunk_ident: std::cell::Cell::new(0),
564 fixnum_range: None,
565 #[cfg(feature = "jit")]
566 slots_all_numeric: true,
567 ext_handler: None,
568 ext_wide_handler: None,
569 builtin_table: Vec::new(),
570 host: None,
571 awk_host: None,
572 awk_rand_seed: 1,
573 awk_signal: None,
574 sched: None,
575 halted: false,
576 #[cfg(feature = "jit")]
577 tracing_jit: false,
578 #[cfg(feature = "jit")]
579 jit: JitCompiler::new(),
580 #[cfg(feature = "jit")]
581 recorder: None,
582 #[cfg(feature = "jit")]
583 slot_buf: Vec::new(),
584 #[cfg(feature = "jit")]
585 slot_kinds_buf: Vec::new(),
586 #[cfg(feature = "jit")]
587 global_buf: Vec::new(),
588 #[cfg(feature = "jit")]
589 global_kinds_buf: Vec::new(),
590 #[cfg(feature = "jit")]
591 global_numeric: Vec::new(),
592 #[cfg(feature = "jit")]
593 deopt_info: DeoptInfo::zeroed(),
594 #[cfg(feature = "jit")]
595 block_eligible_cached: None,
596 #[cfg(feature = "aot")]
597 aot_result: None,
598 #[cfg(feature = "aot")]
599 aot_guard_failed: false,
600 #[cfg(feature = "aot")]
601 aot_arena: Vec::new(),
602 #[cfg(feature = "aot")]
603 aot_free: Vec::new(),
604 }
605 }
606
607 pub fn set_sited_numeric_hook(&mut self, hook: SitedNumericHook) {
624 self.sited_numeric_hook = Some(hook);
625 #[cfg(feature = "jit")]
626 {
627 self.block_eligible_cached = None;
628 }
629 }
630
631 pub fn set_numeric_hook(&mut self, hook: NumericHook) {
632 self.numeric_hook = Some(hook);
633 #[cfg(feature = "jit")]
634 {
635 self.block_eligible_cached = None;
637 }
638 }
639
640 pub fn slot_names_at(&self, up: usize) -> &[String] {
658 let depth = self.frames.len();
659 if up >= depth {
660 return &[];
661 }
662 match self.frames[depth - 1 - up].entry_ip {
663 Some(ip) => self.chunk.sub_slot_names_at(ip),
664 None => &[],
665 }
666 }
667
668 pub fn slot_of_at(&self, up: usize, name: &str) -> Option<u16> {
675 self.slot_names_at(up)
676 .iter()
677 .position(|n| n == name)
678 .and_then(|i| u16::try_from(i).ok())
679 }
680
681 pub fn frame_slot_names(&self) -> &[String] {
683 self.slot_names_at(0)
684 }
685
686 pub fn frame_slot_of(&self, name: &str) -> Option<u16> {
688 self.slot_of_at(0, name)
689 }
690
691 fn chunk_identity(&self) -> u64 {
696 let cached = self.chunk_ident.get();
697 if cached != 0 {
698 return cached;
699 }
700 use std::hash::{Hash, Hasher};
701 let mut h = std::collections::hash_map::DefaultHasher::new();
702 self.chunk.op_hash.hash(&mut h);
703 self.chunk.names.hash(&mut h);
704 let ident = h.finish() | 1;
706 self.chunk_ident.set(ident);
707 ident
708 }
709
710 pub fn set_undef_hook(&mut self, hook: UndefHook) {
711 self.undef_hook = Some(hook);
712 #[cfg(feature = "jit")]
713 {
714 self.block_eligible_cached = None;
715 }
716 }
717
718 pub fn is_strict_undef(&self) -> bool {
720 self.undef_hook.is_some()
721 }
722
723 #[cfg(feature = "jit")]
728 #[inline]
729 fn strict_values(&self) -> bool {
730 self.strict_numeric_mode() || self.undef_hook.is_some()
731 }
732
733 pub fn set_output_sink(&mut self, sink: OutputSink) {
739 self.output_sink = Some(sink);
740 }
741
742 pub fn set_input_source(&mut self, source: InputSource) {
746 self.input_source = Some(source);
747 }
748
749 fn emit_output(&mut self, s: &str) {
753 if let Some(sink) = self.output_sink.as_mut() {
754 sink(s);
755 } else {
756 use std::io::Write;
757 let stdout = std::io::stdout();
758 let mut lock = stdout.lock();
759 let _ = lock.write_all(s.as_bytes());
760 }
761 }
762
763 pub fn is_strict_numeric(&self) -> bool {
765 self.strict_numeric_mode()
766 }
767
768 #[inline]
774 fn strict_numeric_mode(&self) -> bool {
775 self.numeric_hook.is_some() || self.sited_numeric_hook.is_some()
776 }
777
778 #[inline]
782 fn call_numeric(&self, op: NumOp, a: &Value, b: &Value, ip: usize) -> Result<Value, String> {
783 if let Some(sited) = &self.sited_numeric_hook {
784 return sited(NumericCall {
785 op,
786 a,
787 b,
788 chunk: self.chunk_identity(),
789 ip,
790 });
791 }
792 match &self.numeric_hook {
793 Some(hook) => hook(op, a, b),
794 None => Ok(Value::Undef),
796 }
797 }
798
799 pub fn set_fixnum_range(&mut self, lo: i64, hi: i64) {
812 self.fixnum_range = Some((lo, hi));
813 #[cfg(feature = "jit")]
814 {
815 self.block_eligible_cached = None;
816 }
817 }
818
819 #[inline(always)]
821 fn in_fixnum_range(&self, n: i64) -> bool {
822 match self.fixnum_range {
823 Some((lo, hi)) => n >= lo && n <= hi,
824 None => true,
825 }
826 }
827
828 #[cfg(feature = "jit")]
837 pub fn enable_tracing_jit(&mut self) {
838 self.tracing_jit = true;
839 }
840
841 #[cfg(feature = "jit")]
844 pub fn disable_tracing_jit(&mut self) {
845 self.tracing_jit = false;
846 self.recorder = None;
847 }
848
849 pub fn reset(&mut self, chunk: Chunk) {
870 self.stack.clear();
871 self.frames.clear();
872 let num_names = chunk.names.len();
873 self.globals.clear();
874 self.globals.resize(num_names, Value::Undef);
875 self.frames.push(Frame {
876 return_ip: 0,
877 stack_base: 0,
878 slots: Vec::with_capacity(16),
879 entry_ip: None,
880 });
881 self.ip = 0;
882 self.last_status = 0;
883 self.halted = false;
884 self.awk_rand_seed = 1;
885 self.chunk = chunk;
886 #[cfg(feature = "jit")]
887 {
888 self.recorder = None;
889 self.slot_buf.clear();
890 self.slot_kinds_buf.clear();
891 self.global_buf.clear();
892 self.global_kinds_buf.clear();
893 self.global_numeric.clear();
894 self.deopt_info = DeoptInfo::zeroed();
895 self.block_eligible_cached = None;
896 }
897 }
898
899 pub fn set_shell_host(&mut self, host: Box<dyn ShellHost>) {
901 self.host = Some(host);
902 }
903
904 pub fn set_awk_host(&mut self, host: Box<dyn AwkHost>) {
907 self.awk_host = Some(host);
908 }
909
910 pub fn awk_signal(&self) -> Option<u8> {
916 self.awk_signal
917 }
918
919 pub fn take_sched(&mut self) -> Option<crate::sched::SchedReq> {
924 self.sched.take()
925 }
926
927 pub fn clear_halt(&mut self) {
931 self.halted = false;
932 }
933
934 pub fn set_extension_handler(&mut self, handler: ExtensionHandler) {
936 self.ext_handler = Some(handler);
937 }
938
939 pub fn set_extension_wide_handler(&mut self, handler: ExtensionWideHandler) {
941 self.ext_wide_handler = Some(handler);
942 }
943
944 pub fn register_builtin(&mut self, id: u16, handler: BuiltinHandler) {
947 let idx = id as usize;
948 if idx >= self.builtin_table.len() {
949 self.builtin_table.resize(idx + 1, None);
950 }
951 self.builtin_table[idx] = Some(handler);
952 }
953
954 pub fn run_builtin_by_name(&mut self, name: &str, args: &[String]) -> Option<Value> {
968 let id = crate::shell_builtins::builtin_id(name)?;
969 let handler = match self.builtin_table.get(id as usize) {
970 Some(Some(h)) => *h,
971 _ => return None,
972 };
973 let argc = args.len().min(u8::MAX as usize);
976 for a in &args[..argc] {
977 self.push(Value::Str(std::sync::Arc::new(a.clone())));
978 }
979 Some(handler(self, argc as u8))
980 }
981
982 pub fn request_halt(&mut self) {
986 self.halted = true;
987 }
988
989 #[cfg(feature = "jit")]
1002 #[inline]
1003 fn refresh_slot_buffers(&mut self) {
1004 let frame = match self.frames.last() {
1005 Some(f) => f,
1006 None => return,
1007 };
1008 let n = frame.slots.len();
1009 let mut all_num = true;
1010 match n {
1011 0 => {
1012 self.slot_buf.clear();
1013 self.slot_kinds_buf.clear();
1014 }
1015 1 => {
1016 let (i, kind) = match &frame.slots[0] {
1017 Value::Int(v) => (*v, SlotKind::Int),
1018 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
1019 Value::Bool(b) => (*b as i64, SlotKind::Int),
1020 _ => {
1021 all_num = false;
1022 (0, SlotKind::Int)
1023 }
1024 };
1025 if self.slot_buf.is_empty() {
1026 self.slot_buf.push(i);
1027 self.slot_kinds_buf.push(kind);
1028 } else {
1029 self.slot_buf.truncate(1);
1030 self.slot_buf[0] = i;
1031 self.slot_kinds_buf.truncate(1);
1032 self.slot_kinds_buf[0] = kind;
1033 }
1034 }
1035 _ => {
1036 self.slot_buf.clear();
1037 self.slot_kinds_buf.clear();
1038 self.slot_buf.reserve(n);
1039 self.slot_kinds_buf.reserve(n);
1040 for v in &frame.slots {
1041 let (i, kind) = match v {
1042 Value::Int(n) => (*n, SlotKind::Int),
1043 Value::Float(f) => (f.to_bits() as i64, SlotKind::Float),
1044 Value::Bool(b) => (*b as i64, SlotKind::Int),
1045 _ => {
1046 all_num = false;
1047 (0, SlotKind::Int)
1048 }
1049 };
1050 self.slot_buf.push(i);
1051 self.slot_kinds_buf.push(kind);
1052 }
1053 }
1054 }
1055 self.slots_all_numeric = all_num;
1056 }
1057
1058 #[cfg(feature = "jit")]
1066 #[inline]
1067 fn write_slots_back(&mut self) {
1068 let frame = match self.frames.last_mut() {
1069 Some(f) => f,
1070 None => return,
1071 };
1072 let n = frame.slots.len().min(self.slot_buf.len());
1073 match n {
1074 0 => {}
1075 1 => match self.slot_kinds_buf.first() {
1076 Some(SlotKind::Int) => frame.slots[0] = Value::Int(self.slot_buf[0]),
1077 Some(SlotKind::Float) => {
1078 frame.slots[0] = Value::Float(f64::from_bits(self.slot_buf[0] as u64));
1079 }
1080 None => {}
1081 },
1082 _ => {
1083 for i in 0..n {
1084 match self.slot_kinds_buf.get(i) {
1085 Some(SlotKind::Int) => {
1086 frame.slots[i] = Value::Int(self.slot_buf[i]);
1087 }
1088 Some(SlotKind::Float) => {
1089 frame.slots[i] = Value::Float(f64::from_bits(self.slot_buf[i] as u64));
1090 }
1091 None => {}
1092 }
1093 }
1094 }
1095 }
1096 }
1097
1098 #[cfg(feature = "jit")]
1105 #[inline]
1106 fn write_globals_back(&mut self) {
1107 let n = self.globals.len().min(self.global_buf.len());
1108 for i in 0..n {
1109 if !self.global_numeric.get(i).copied().unwrap_or(false) {
1110 continue;
1111 }
1112 match self.global_kinds_buf.get(i) {
1113 Some(SlotKind::Int) => self.globals[i] = Value::Int(self.global_buf[i]),
1114 Some(SlotKind::Float) => {
1115 self.globals[i] = Value::Float(f64::from_bits(self.global_buf[i] as u64));
1116 }
1117 None => {}
1118 }
1119 }
1120 }
1121
1122 #[cfg(feature = "jit")]
1129 fn lookup_trace_for_backward(&mut self, anchor_ip: usize, fallthrough_ip: usize) -> usize {
1130 self.refresh_slot_buffers();
1131 if self.strict_values() && !self.slots_all_numeric {
1139 return anchor_ip;
1140 }
1141 let lookup = self.jit.trace_lookup(
1142 &self.chunk,
1143 anchor_ip,
1144 &mut self.slot_buf,
1145 &self.slot_kinds_buf,
1146 &self.globals,
1147 &mut self.global_buf,
1148 &mut self.global_kinds_buf,
1149 &mut self.global_numeric,
1150 &mut self.deopt_info,
1151 );
1152 match lookup {
1153 TraceLookup::Ran { resume_ip } => {
1154 if crate::jit::take_num_overflow_trap() {
1162 self.jit.trace_overflow_bail(&self.chunk, anchor_ip);
1163 return anchor_ip;
1164 }
1165 self.write_slots_back();
1166 self.write_globals_back();
1167 self.materialize_deopt_frames();
1168 self.chain_side_traces(anchor_ip, fallthrough_ip, resume_ip)
1171 }
1172 TraceLookup::StartRecording => {
1173 self.recorder = Some(TraceRecorder {
1177 record_anchor_ip: anchor_ip,
1178 close_anchor_ip: anchor_ip,
1179 fallthrough_ip,
1180 ops: Vec::new(),
1181 recorded_ips: Vec::new(),
1182 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
1183 global_kinds_at_anchor: self.global_kinds_buf.clone(),
1184 global_numeric_at_anchor: self.global_numeric.clone(),
1185 entered_ips: Vec::new(),
1186 aborted: false,
1187 });
1188 anchor_ip
1189 }
1190 TraceLookup::NotHot | TraceLookup::GuardMismatch | TraceLookup::Skip => anchor_ip,
1191 }
1192 }
1193
1194 #[cfg(feature = "jit")]
1212 fn chain_side_traces(
1213 &mut self,
1214 main_anchor: usize,
1215 main_fallthrough: usize,
1216 first_resume: usize,
1217 ) -> usize {
1218 let mut current = first_resume;
1219 if current == main_fallthrough {
1220 return current;
1221 }
1222 let max_chain = self.jit.get_config().max_trace_chain;
1223 for _ in 0..max_chain {
1224 let chained_fallthrough = self
1227 .jit
1228 .trace_loop_anchors(&self.chunk, current)
1229 .map(|(_, fallthrough)| fallthrough);
1230
1231 self.refresh_slot_buffers();
1232 if self.strict_values() && !self.slots_all_numeric {
1234 return current;
1235 }
1236 let lookup = self.jit.trace_lookup(
1237 &self.chunk,
1238 current,
1239 &mut self.slot_buf,
1240 &self.slot_kinds_buf,
1241 &self.globals,
1242 &mut self.global_buf,
1243 &mut self.global_kinds_buf,
1244 &mut self.global_numeric,
1245 &mut self.deopt_info,
1246 );
1247 match lookup {
1248 TraceLookup::Ran { resume_ip } => {
1249 if crate::jit::take_num_overflow_trap() {
1252 self.jit.trace_overflow_bail(&self.chunk, current);
1253 return current;
1254 }
1255 self.write_slots_back();
1256 self.write_globals_back();
1257 self.materialize_deopt_frames();
1258 current = resume_ip;
1259 if Some(current) == chained_fallthrough {
1260 return current;
1261 }
1262 }
1263 TraceLookup::StartRecording => {
1264 self.recorder = Some(TraceRecorder {
1269 record_anchor_ip: current,
1270 close_anchor_ip: main_anchor,
1271 fallthrough_ip: main_fallthrough,
1272 ops: Vec::new(),
1273 recorded_ips: Vec::new(),
1274 slot_kinds_at_anchor: self.slot_kinds_buf.clone(),
1275 global_kinds_at_anchor: self.global_kinds_buf.clone(),
1276 global_numeric_at_anchor: self.global_numeric.clone(),
1277 entered_ips: Vec::new(),
1278 aborted: false,
1279 });
1280 return current;
1281 }
1282 _ => {
1283 self.jit.trace_bump_side_exit(&self.chunk, main_anchor);
1286 return current;
1287 }
1288 }
1289 }
1290 current
1291 }
1292
1293 #[cfg(feature = "jit")]
1302 fn materialize_deopt_frames(&mut self) {
1303 let stack_count = self.deopt_info.stack_count;
1307 for i in 0..stack_count {
1308 let raw = self.deopt_info.stack_buf[i];
1309 let v = match self.deopt_info.stack_kinds[i] {
1310 crate::jit::STACK_KIND_FLOAT => Value::Float(f64::from_bits(raw as u64)),
1311 _ => Value::Int(raw),
1312 };
1313 self.stack.push(v);
1314 }
1315 let count = self.deopt_info.frame_count;
1317 if count == 0 {
1318 return;
1319 }
1320 for i in 0..count {
1321 let df = &self.deopt_info.frames[i];
1322 let mut slots: Vec<Value> = Vec::with_capacity(df.slot_count);
1323 for j in 0..df.slot_count {
1324 slots.push(Value::Int(df.slots[j]));
1325 }
1326 self.frames.push(Frame {
1327 return_ip: df.return_ip,
1328 stack_base: self.stack.len(),
1329 slots,
1330 entry_ip: None,
1334 });
1335 }
1336 }
1337
1338 #[cfg(feature = "jit")]
1342 fn finalize_recorder(&mut self) {
1343 let Some(rec) = self.recorder.as_ref() else {
1344 return;
1345 };
1346 let aborted = rec.aborted;
1347 let close_anchor = rec.close_anchor_ip;
1348 let record_anchor = rec.record_anchor_ip;
1349 if !aborted && self.ip == close_anchor {
1354 let r = self.recorder.take().unwrap();
1355 if self.jit.is_trace_eligible(&r.ops, r.close_anchor_ip) {
1356 let installed = self.jit.trace_install_with_kind(
1360 &self.chunk,
1361 r.record_anchor_ip,
1362 r.close_anchor_ip,
1363 r.fallthrough_ip,
1364 &r.ops,
1365 &r.recorded_ips,
1366 &r.slot_kinds_at_anchor,
1367 &r.global_kinds_at_anchor,
1368 &r.global_numeric_at_anchor,
1369 );
1370 if !installed {
1371 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
1372 }
1373 } else {
1374 self.jit.trace_abort(&self.chunk, r.record_anchor_ip);
1375 }
1376 } else {
1377 let _ = self.recorder.take();
1380 self.jit.trace_abort(&self.chunk, record_anchor);
1381 }
1382 }
1383
1384 #[inline(always)]
1390 pub fn push(&mut self, val: Value) {
1391 self.stack.push(val);
1392 }
1393
1394 #[inline(always)]
1399 pub fn pop(&mut self) -> Value {
1400 self.stack.pop().unwrap_or(Value::Undef)
1401 }
1402
1403 #[inline(always)]
1406 pub fn peek(&self) -> &Value {
1407 self.stack.last().unwrap_or(&Value::Undef)
1408 }
1409
1410 fn floor_correct_top(&mut self, k: &Value) {
1429 let kk = match k {
1430 Value::Int(n) => *n,
1431 Value::Bool(b) => i64::from(*b),
1432 _ => return,
1433 };
1434 if kk == 0 {
1435 return;
1436 }
1437 match self.stack.last_mut() {
1438 Some(Value::Int(r)) => {
1440 if *r != 0 && (*r < 0) != (kk < 0) {
1441 *r += kk;
1442 }
1443 }
1444 Some(Value::Float(f)) => {
1445 if *f != 0.0 && (*f < 0.0) != (kk < 0) {
1446 *f += kk as f64;
1447 } else if *f == 0.0 {
1448 *f = 0.0; }
1450 }
1451 _ => {}
1452 }
1453 }
1454
1455 #[inline(always)]
1457 fn arith_int_fast(
1458 &mut self,
1459 op: NumOp,
1460 int_op: fn(i64, i64) -> i64,
1461 ck_op: fn(i64, i64) -> Option<i64>,
1462 float_op: fn(f64, f64) -> f64,
1463 ip: usize,
1464 ) -> Option<String> {
1465 let len = self.stack.len();
1466 if len < 2 {
1467 return None;
1468 }
1469 let strict = self.strict_numeric_mode();
1470 let b = &self.stack[len - 1];
1472 let a = &self.stack[len - 2];
1473 let result = match (a, b, strict) {
1474 (Value::Int(x), Value::Int(y), false) => Value::Int(int_op(*x, *y)),
1476 (Value::Int(x), Value::Int(y), true) => match ck_op(*x, *y) {
1477 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1479 _ => match self.call_numeric(op, a, b, ip) {
1482 Ok(v) => v,
1483 Err(e) => return Some(e),
1484 },
1485 },
1486 (a, b, true)
1492 if is_native_num(a)
1493 && is_native_num(b)
1494 && !f64_would_round(a)
1495 && !f64_would_round(b) =>
1496 {
1497 Value::Float(float_op(a.to_float(), b.to_float()))
1498 }
1499 (a, b, false) => Value::Float(float_op(a.to_float(), b.to_float())),
1503 (a, b, true) => match self.call_numeric(op, a, b, ip) {
1504 Ok(v) => v,
1505 Err(e) => return Some(e),
1506 },
1507 };
1508 self.stack.truncate(len - 2);
1509 self.stack.push(result);
1510 None
1511 }
1512
1513 #[inline(always)]
1519 fn cmp_int_fast(
1520 &mut self,
1521 op: NumOp,
1522 int_cmp: fn(i64, i64) -> bool,
1523 float_cmp: fn(f64, f64) -> bool,
1524 ip: usize,
1525 ) -> Option<String> {
1526 let len = self.stack.len();
1527 if len < 2 {
1528 return None;
1529 }
1530 let strict = self.strict_numeric_mode();
1531 let b = &self.stack[len - 1];
1532 let a = &self.stack[len - 2];
1533 let result = match (a, b, strict) {
1534 (Value::Int(x), Value::Int(y), _) => Value::Bool(int_cmp(*x, *y)),
1535 (a, b, true)
1540 if is_native_num(a)
1541 && is_native_num(b)
1542 && !f64_would_round(a)
1543 && !f64_would_round(b) =>
1544 {
1545 Value::Bool(float_cmp(a.to_float(), b.to_float()))
1546 }
1547 (a, b, false) => Value::Bool(float_cmp(a.to_float(), b.to_float())),
1548 (a, b, true) => match self.call_numeric(op, a, b, ip) {
1549 Ok(v) => v,
1550 Err(e) => return Some(e),
1551 },
1552 };
1553 self.stack.truncate(len - 2);
1554 self.stack.push(result);
1555 None
1556 }
1557
1558 #[inline(always)]
1562 fn delegate_binary(&mut self, op: NumOp, ip: usize) -> Option<String> {
1563 let b = self.pop();
1564 let a = self.pop();
1565 if !self.strict_numeric_mode() {
1566 return None;
1567 }
1568 match self.call_numeric(op, &a, &b, ip) {
1569 Ok(v) => {
1570 self.push(v);
1571 None
1572 }
1573 Err(e) => Some(e),
1574 }
1575 }
1576
1577 #[inline(always)]
1580 fn negate_strict(&mut self, ip: usize) -> Option<String> {
1581 let val = self.pop();
1582 let strict = self.strict_numeric_mode();
1583 let result = match (&val, strict) {
1584 (Value::Int(n), false) => Value::Int(n.wrapping_neg()),
1585 (Value::Int(n), true) => match n.checked_neg() {
1586 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
1587 _ => match self.call_numeric(NumOp::Neg, &val, &Value::Undef, ip) {
1588 Ok(v) => v,
1589 Err(e) => return Some(e),
1590 },
1591 },
1592 (Value::Float(f), _) => Value::Float(-f),
1593 (v, false) => Value::Float(-v.to_float()),
1594 (v, true) => match self.call_numeric(NumOp::Neg, v, &Value::Undef, ip) {
1595 Ok(v) => v,
1596 Err(e) => return Some(e),
1597 },
1598 };
1599 self.push(result);
1600 None
1601 }
1602
1603 pub fn run(&mut self) -> VMResult {
1627 self.awk_signal = None;
1631 self.sched = None;
1634 #[cfg(feature = "jit")]
1649 crate::jit::set_strict_numeric(self.strict_numeric_mode(), self.fixnum_range);
1650
1651 #[cfg(feature = "jit")]
1652 if self.tracing_jit && self.frames.len() == 1 && self.ip == 0 {
1653 let eligible = match self.block_eligible_cached {
1654 Some(v) => v,
1655 None => {
1656 let v = self.jit.is_block_eligible(&self.chunk);
1657 self.block_eligible_cached = Some(v);
1658 v
1659 }
1660 };
1661 if eligible {
1662 self.refresh_slot_buffers();
1663 let strict_blocked = self.strict_values() && !self.slots_all_numeric;
1669 if !strict_blocked {
1670 if let Some(result) = self.jit.try_run_block_typed_kinded(
1671 &self.chunk,
1672 &mut self.slot_buf,
1673 &self.slot_kinds_buf,
1674 ) {
1675 if crate::jit::take_num_overflow_trap() {
1682 self.ip = 0;
1683 } else {
1684 match crate::jit::take_awk_div_trap() {
1689 1 => {
1690 return VMResult::Error(
1691 "division by zero attempted".to_string(),
1692 )
1693 }
1694 2 => {
1695 return VMResult::Error(
1696 "division by zero attempted in `%'".to_string(),
1697 )
1698 }
1699 3 => {
1700 return VMResult::Error(
1701 "lshift: negative values are not allowed".to_string(),
1702 )
1703 }
1704 4 => {
1705 return VMResult::Error(
1706 "rshift: negative values are not allowed".to_string(),
1707 )
1708 }
1709 5 => {
1710 return VMResult::Error(
1711 "compl: negative value is not allowed".to_string(),
1712 )
1713 }
1714 _ => {}
1715 }
1716 self.write_slots_back();
1717 self.halted = true;
1718 return VMResult::Ok(match result {
1726 crate::jit::BlockNum::Int(n) => Value::Int(n),
1727 crate::jit::BlockNum::Float(f) => Value::Float(f),
1728 crate::jit::BlockNum::Bool(b) => Value::Bool(b),
1734 });
1735 }
1736 }
1737 }
1738 }
1739 }
1740
1741 let ops = &self.chunk.ops as *const Vec<Op>;
1742 let ops = unsafe { &*ops };
1746
1747 while self.ip < ops.len() && !self.halted {
1748 let ip = self.ip;
1750 self.ip += 1;
1751
1752 #[cfg(feature = "jit")]
1759 let recorder_was_armed = self.recorder.is_some();
1760 #[cfg(not(feature = "jit"))]
1761 let recorder_was_armed = false;
1762 #[cfg(feature = "jit")]
1763 if self.recorder.is_some() {
1764 let cfg = self.jit.get_config();
1765 let max_len = cfg.max_trace_len;
1766 let max_inline_recursion = cfg.max_inline_recursion;
1767 let cur_op = ops[ip].clone();
1768 let resolved_entry = if let Op::Call(name_idx, _) = &cur_op {
1771 self.chunk.find_sub(*name_idx)
1772 } else {
1773 None
1774 };
1775 let rec = self.recorder.as_mut().unwrap();
1776 if !rec.aborted {
1777 rec.ops.push(cur_op.clone());
1778 rec.recorded_ips.push(ip);
1779 if rec.ops.len() > max_len {
1780 rec.aborted = true;
1781 } else {
1782 match cur_op {
1785 Op::Call(_, _) => match resolved_entry {
1786 Some(entry_ip) => {
1787 let occurrences = rec
1795 .entered_ips
1796 .iter()
1797 .filter(|&&ip| ip == entry_ip)
1798 .count();
1799 if occurrences >= max_inline_recursion {
1800 rec.aborted = true;
1801 } else {
1802 rec.entered_ips.push(entry_ip);
1803 }
1804 }
1805 None => rec.aborted = true, },
1807 Op::Return | Op::ReturnValue => {
1808 if rec.entered_ips.is_empty() {
1809 rec.aborted = true; } else {
1812 rec.entered_ips.pop();
1813 }
1814 }
1815 Op::CallBuiltin(_, _) => rec.aborted = true,
1816 Op::AwkInt
1825 | Op::AwkFieldGet
1826 | Op::AwkFieldSet
1827 | Op::AwkNf
1828 | Op::AwkSetRecord
1829 | Op::AwkSpecialGet(_)
1830 | Op::AwkSpecialSet(_)
1831 | Op::AwkPrint(_)
1832 | Op::AwkPrintf(_)
1833 | Op::AwkSprintf(_)
1834 | Op::AwkGetline(_)
1835 | Op::AwkLength(_)
1836 | Op::AwkSubstr(_)
1837 | Op::AwkIndex
1838 | Op::AwkSplit(_)
1839 | Op::AwkSub(_)
1840 | Op::AwkGsub(_)
1841 | Op::AwkGensub(_)
1842 | Op::AwkMatch
1843 | Op::AwkToLower
1844 | Op::AwkToUpper
1845 | Op::AwkSqrt
1846 | Op::AwkSin
1847 | Op::AwkCos
1848 | Op::AwkExp
1849 | Op::AwkLog
1850 | Op::AwkAtan2
1851 | Op::AwkDiv
1852 | Op::AwkMod
1853 | Op::AwkDivJit
1854 | Op::AwkModJit
1855 | Op::AwkSqrtJit
1856 | Op::AwkLogJit
1857 | Op::AwkLshiftJit
1858 | Op::AwkRshiftJit
1859 | Op::AwkComplJit
1860 | Op::AwkAnd(_)
1861 | Op::AwkOr(_)
1862 | Op::AwkXor(_)
1863 | Op::AwkCompl
1864 | Op::AwkLshift
1865 | Op::AwkRshift
1866 | Op::AwkStrtonum
1867 | Op::AwkSystime
1868 | Op::AwkRand
1869 | Op::AwkSrand(_)
1870 | Op::AwkStrftime(_)
1871 | Op::AwkMktime(_)
1872 | Op::AwkOrd
1873 | Op::AwkChr
1874 | Op::AwkMkbool
1875 | Op::AwkIntdiv
1876 | Op::AwkIntdiv0
1877 | Op::AwkArrayGet(_)
1878 | Op::AwkArraySet(_)
1879 | Op::AwkArrayExists(_)
1880 | Op::AwkArrayDelete(_)
1881 | Op::AwkArrayClear(_)
1882 | Op::AwkArrayLen(_) => rec.aborted = true,
1883 Op::AwkSignal(_) => rec.aborted = true,
1884 Op::Go(_, _)
1886 | Op::ChanMake
1887 | Op::ChanSend
1888 | Op::ChanRecv
1889 | Op::ChanRecvOk
1890 | Op::ChanClose
1891 | Op::Select(_, _)
1892 | Op::CallDynamic(_) => rec.aborted = true,
1893 _ => {}
1894 }
1895 }
1896 }
1897 }
1898
1899 match self.exec_op(ops, ip, recorder_was_armed) {
1900 ExecFlow::Cont => {}
1901 ExecFlow::Ret(r) => return r,
1902 }
1903 }
1904
1905 if let Some(val) = self.stack.pop() {
1906 VMResult::Ok(val)
1907 } else {
1908 VMResult::Halted
1909 }
1910 }
1911
1912 #[cfg_attr(not(feature = "jit"), allow(unused_variables))]
1920 pub(crate) fn exec_op(&mut self, ops: &[Op], ip: usize, recorder_was_armed: bool) -> ExecFlow {
1921 use crate::awk_builtins as ab;
1922 match &ops[ip] {
1923 Op::Nop => {}
1924
1925 Op::LoadInt(n) => self.push(Value::Int(*n)),
1927 Op::LoadFloat(f) => self.push(Value::Float(*f)),
1928 Op::LoadConst(idx) => {
1929 let val = match self.chunk.constants.get(*idx as usize) {
1930 Some(Value::Int(n)) => Value::Int(*n),
1931 Some(Value::Float(f)) => Value::Float(*f),
1932 Some(Value::Bool(b)) => Value::Bool(*b),
1933 Some(other) => other.clone(),
1934 None => Value::Undef,
1935 };
1936 self.push(val);
1937 }
1938 Op::LoadTrue => self.push(Value::Bool(true)),
1939 Op::LoadFalse => self.push(Value::Bool(false)),
1940 Op::LoadUndef => self.push(Value::Undef),
1941
1942 Op::Pop => {
1944 self.pop();
1945 }
1946 Op::Dup => {
1947 let val = self.peek().clone();
1948 self.push(val);
1949 }
1950 Op::Dup2 => {
1951 let len = self.stack.len();
1952 if len >= 2 {
1953 let a = self.stack[len - 2].clone();
1954 let b = self.stack[len - 1].clone();
1955 self.push(a);
1956 self.push(b);
1957 }
1958 }
1959 Op::Swap => {
1960 let len = self.stack.len();
1961 if len >= 2 {
1962 self.stack.swap(len - 1, len - 2);
1963 }
1964 }
1965 Op::Rot => {
1966 let len = self.stack.len();
1967 if len >= 3 {
1968 self.stack.swap(len - 3, len - 2);
1970 self.stack.swap(len - 2, len - 1);
1971 }
1972 }
1973
1974 Op::GetVar(idx) => {
1976 let val = self.get_var(*idx);
1977 if matches!(val, Value::Undef) && self.undef_hook.is_some() {
1978 let hook = self.undef_hook.clone().expect("checked above");
1981 let name = self.chunk.names.get(*idx as usize).map(String::as_str);
1982 match hook(UndefRead {
1983 name,
1984 index: *idx,
1985 from_slot: false,
1986 chunk: self.chunk_identity(),
1987 ip,
1988 }) {
1989 Ok(v) => self.push(v),
1990 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
1991 }
1992 } else {
1993 self.push(val);
1994 }
1995 }
1996 Op::SetVar(idx) => {
1997 let val = self.pop();
1998 self.set_var(*idx, val);
1999 }
2000 Op::DeclareVar(idx) => {
2001 let val = self.pop();
2002 self.set_var(*idx, val);
2003 }
2004 Op::GetSlot(slot) => {
2005 let val = self.get_slot(*slot);
2006 if matches!(val, Value::Undef) && self.undef_hook.is_some() {
2007 let hook = self.undef_hook.clone().expect("checked above");
2012 match hook(UndefRead {
2013 name: None,
2014 index: *slot,
2015 from_slot: true,
2016 chunk: self.chunk_identity(),
2017 ip,
2018 }) {
2019 Ok(v) => self.push(v),
2020 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
2021 }
2022 } else {
2023 self.push(val);
2024 }
2025 }
2026 Op::SetSlot(slot) => {
2027 let val = self.pop();
2028 self.set_slot(*slot, val);
2029 }
2030 Op::SlotArrayGet(slot) => {
2031 let index = self.pop().to_int() as usize;
2032 let result = self
2036 .frames
2037 .last()
2038 .and_then(|f| f.slots.get(*slot as usize))
2039 .and_then(|v| v.as_array())
2040 .and_then(|a| a.get(index))
2041 .cloned()
2042 .unwrap_or(Value::Undef);
2043 self.push(result);
2044 }
2045 Op::SlotArraySet(slot) => {
2046 let index = self.pop().to_int() as usize;
2047 let val = self.pop();
2048 if let Some(arr) = self
2052 .frames
2053 .last_mut()
2054 .and_then(|f| f.slots.get_mut(*slot as usize))
2055 .and_then(|v| v.array_mut())
2056 {
2057 if index >= arr.len() {
2058 arr.resize(index + 1, Value::Undef);
2059 }
2060 arr[index] = val;
2061 }
2062 }
2063
2064 Op::Add => {
2066 if let Some(e) = self.arith_int_fast(
2067 NumOp::Add,
2068 i64::wrapping_add,
2069 i64::checked_add,
2070 |a, b| a + b,
2071 ip,
2072 ) {
2073 return ExecFlow::Ret(VMResult::Error(e));
2074 }
2075 }
2076 Op::Sub => {
2077 if let Some(e) = self.arith_int_fast(
2078 NumOp::Sub,
2079 i64::wrapping_sub,
2080 i64::checked_sub,
2081 |a, b| a - b,
2082 ip,
2083 ) {
2084 return ExecFlow::Ret(VMResult::Error(e));
2085 }
2086 }
2087 Op::Mul => {
2088 if let Some(e) = self.arith_int_fast(
2089 NumOp::Mul,
2090 i64::wrapping_mul,
2091 i64::checked_mul,
2092 |a, b| a * b,
2093 ip,
2094 ) {
2095 return ExecFlow::Ret(VMResult::Error(e));
2096 }
2097 }
2098 Op::Div => {
2099 if self.strict_numeric_mode() {
2104 let len = self.stack.len();
2105 if len >= 2
2106 && !(is_native_num(&self.stack[len - 1])
2107 && is_native_num(&self.stack[len - 2]))
2108 {
2109 if let Some(e) = self.delegate_binary(NumOp::Div, ip) {
2110 return ExecFlow::Ret(VMResult::Error(e));
2111 }
2112 return ExecFlow::Cont;
2113 }
2114 }
2115 let b = self.pop();
2116 let a = self.pop();
2117 let divisor = b.to_float();
2118 self.push(if divisor == 0.0 {
2119 Value::Undef
2120 } else {
2121 Value::Float(a.to_float() / divisor)
2122 });
2123 }
2124 Op::Mod => {
2125 if let Some(e) = self.arith_int_fast(
2126 NumOp::Mod,
2127 |x, y| if y != 0 { x.wrapping_rem(y) } else { 0 },
2134 |x, y| if y != 0 { x.checked_rem(y) } else { Some(0) },
2135 |a, b| a % b,
2136 ip,
2137 ) {
2138 return ExecFlow::Ret(VMResult::Error(e));
2139 }
2140 }
2141 Op::Pow => {
2142 if self.strict_numeric_mode() {
2143 let len = self.stack.len();
2144 if len >= 2
2145 && !(is_native_num(&self.stack[len - 1])
2146 && is_native_num(&self.stack[len - 2]))
2147 {
2148 if let Some(e) = self.delegate_binary(NumOp::Pow, ip) {
2149 return ExecFlow::Ret(VMResult::Error(e));
2150 }
2151 return ExecFlow::Cont;
2152 }
2153 }
2154 let b = self.pop();
2155 let a = self.pop();
2156 self.push(Value::Float(a.to_float().powf(b.to_float())));
2157 }
2158 Op::Negate => {
2159 if let Some(e) = self.negate_strict(ip) {
2160 return ExecFlow::Ret(VMResult::Error(e));
2161 }
2162 }
2163 Op::Inc => {
2164 let val = self.pop();
2165 let strict = self.strict_numeric_mode();
2166 let result = match (&val, strict) {
2167 (Value::Int(n), false) => Value::Int(n.wrapping_add(1)),
2168 (Value::Int(n), true) => match n.checked_add(1) {
2169 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
2170 _ => match self.call_numeric(NumOp::Add, &val, &Value::Int(1), ip) {
2171 Ok(v) => v,
2172 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
2173 },
2174 },
2175 (v, false) => Value::Int(v.to_int().wrapping_add(1)),
2176 (v, true) => match self.call_numeric(NumOp::Add, v, &Value::Int(1), ip) {
2177 Ok(v) => v,
2178 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
2179 },
2180 };
2181 self.push(result);
2182 }
2183 Op::Dec => {
2184 let val = self.pop();
2185 let strict = self.strict_numeric_mode();
2186 let result = match (&val, strict) {
2187 (Value::Int(n), false) => Value::Int(n.wrapping_sub(1)),
2188 (Value::Int(n), true) => match n.checked_sub(1) {
2189 Some(r) if self.in_fixnum_range(r) => Value::Int(r),
2190 _ => match self.call_numeric(NumOp::Sub, &val, &Value::Int(1), ip) {
2191 Ok(v) => v,
2192 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
2193 },
2194 },
2195 (v, false) => Value::Int(v.to_int().wrapping_sub(1)),
2196 (v, true) => match self.call_numeric(NumOp::Sub, v, &Value::Int(1), ip) {
2197 Ok(v) => v,
2198 Err(e) => return ExecFlow::Ret(VMResult::Error(e)),
2199 },
2200 };
2201 self.push(result);
2202 }
2203
2204 Op::Concat => {
2206 let b = self.pop();
2207 let a = self.pop();
2208 let a_s = a.as_str_cow();
2209 let b_s = b.as_str_cow();
2210 let mut s = String::with_capacity(a_s.len() + b_s.len());
2211 s.push_str(&a_s);
2212 s.push_str(&b_s);
2213 self.push(Value::str(s));
2214 }
2215 Op::StringRepeat => {
2216 let count = self.pop().to_int();
2217 let s = self.pop().to_str();
2218 self.push(Value::str(s.repeat(count.max(0) as usize)));
2219 }
2220 Op::StringLen => {
2221 let s = self.pop();
2222 self.push(Value::Int(s.len() as i64));
2223 }
2224
2225 Op::NumEq => {
2227 if let Some(e) = self.cmp_int_fast(NumOp::Eq, |x, y| x == y, |a, b| a == b, ip) {
2228 return ExecFlow::Ret(VMResult::Error(e));
2229 }
2230 }
2231 Op::NumNe => {
2232 if let Some(e) = self.cmp_int_fast(NumOp::Ne, |x, y| x != y, |a, b| a != b, ip) {
2233 return ExecFlow::Ret(VMResult::Error(e));
2234 }
2235 }
2236 Op::NumLt => {
2237 if let Some(e) = self.cmp_int_fast(NumOp::Lt, |x, y| x < y, |a, b| a < b, ip) {
2238 return ExecFlow::Ret(VMResult::Error(e));
2239 }
2240 }
2241 Op::NumGt => {
2242 if let Some(e) = self.cmp_int_fast(NumOp::Gt, |x, y| x > y, |a, b| a > b, ip) {
2243 return ExecFlow::Ret(VMResult::Error(e));
2244 }
2245 }
2246 Op::NumLe => {
2247 if let Some(e) = self.cmp_int_fast(NumOp::Le, |x, y| x <= y, |a, b| a <= b, ip) {
2248 return ExecFlow::Ret(VMResult::Error(e));
2249 }
2250 }
2251 Op::NumGe => {
2252 if let Some(e) = self.cmp_int_fast(NumOp::Ge, |x, y| x >= y, |a, b| a >= b, ip) {
2253 return ExecFlow::Ret(VMResult::Error(e));
2254 }
2255 }
2256 Op::Spaceship => {
2257 let len = self.stack.len();
2258 if len >= 2 {
2259 let b = &self.stack[len - 1];
2260 let a = &self.stack[len - 2];
2261 let result = match (a, b) {
2262 (Value::Int(x), Value::Int(y)) => x.cmp(y) as i64,
2263 _ => {
2264 let af = a.to_float();
2265 let bf = b.to_float();
2266 if af < bf {
2267 -1
2268 } else if af > bf {
2269 1
2270 } else {
2271 0
2272 }
2273 }
2274 };
2275 self.stack.truncate(len - 2);
2276 self.stack.push(Value::Int(result));
2277 }
2278 }
2279
2280 Op::StrEq => {
2282 let b = self.pop();
2283 let a = self.pop();
2284 self.push(Value::Bool(a.as_str_cow() == b.as_str_cow()));
2285 }
2286 Op::StrNe => {
2287 let b = self.pop();
2288 let a = self.pop();
2289 self.push(Value::Bool(a.as_str_cow() != b.as_str_cow()));
2290 }
2291 Op::StrLt => {
2292 let b = self.pop();
2293 let a = self.pop();
2294 self.push(Value::Bool(a.as_str_cow() < b.as_str_cow()));
2295 }
2296 Op::StrGt => {
2297 let b = self.pop();
2298 let a = self.pop();
2299 self.push(Value::Bool(a.as_str_cow() > b.as_str_cow()));
2300 }
2301 Op::StrLe => {
2302 let b = self.pop();
2303 let a = self.pop();
2304 self.push(Value::Bool(a.as_str_cow() <= b.as_str_cow()));
2305 }
2306 Op::StrGe => {
2307 let b = self.pop();
2308 let a = self.pop();
2309 self.push(Value::Bool(a.as_str_cow() >= b.as_str_cow()));
2310 }
2311 Op::StrCmp => {
2312 let b = self.pop();
2313 let a = self.pop();
2314 self.push(Value::Int(match a.as_str_cow().cmp(&b.as_str_cow()) {
2315 std::cmp::Ordering::Less => -1,
2316 std::cmp::Ordering::Equal => 0,
2317 std::cmp::Ordering::Greater => 1,
2318 }));
2319 }
2320
2321 Op::RubyTruthy => {
2323 let val = self.pop();
2325 let truthy = !matches!(val, Value::Undef | Value::Bool(false));
2326 self.push(Value::Bool(truthy));
2327 }
2328 Op::LogNot => {
2329 let val = self.pop();
2330 self.push(Value::Bool(!val.is_truthy()));
2331 }
2332 Op::LogAnd => {
2333 let b = self.pop();
2334 let a = self.pop();
2335 self.push(Value::Bool(a.is_truthy() && b.is_truthy()));
2336 }
2337 Op::LogOr => {
2338 let b = self.pop();
2339 let a = self.pop();
2340 self.push(Value::Bool(a.is_truthy() || b.is_truthy()));
2341 }
2342 Op::BitAnd => {
2343 let b = self.pop();
2344 let a = self.pop();
2345 self.push(Value::Int(a.to_int() & b.to_int()));
2346 }
2347 Op::BitOr => {
2348 let b = self.pop();
2349 let a = self.pop();
2350 self.push(Value::Int(a.to_int() | b.to_int()));
2351 }
2352 Op::BitXor => {
2353 let b = self.pop();
2354 let a = self.pop();
2355 self.push(Value::Int(a.to_int() ^ b.to_int()));
2356 }
2357 Op::BitNot => {
2358 let val = self.pop();
2359 self.push(Value::Int(!val.to_int()));
2360 }
2361 Op::Shl => {
2362 let b = self.pop();
2363 let a = self.pop();
2364 self.push(Value::Int(a.to_int() << (b.to_int() as u32 & 63)));
2365 }
2366 Op::Shr => {
2367 let b = self.pop();
2368 let a = self.pop();
2369 self.push(Value::Int(a.to_int() >> (b.to_int() as u32 & 63)));
2370 }
2371
2372 Op::Jump(target) => {
2374 let target = *target;
2375 #[cfg(feature = "jit")]
2376 if self.tracing_jit && self.recorder.is_none() && target <= ip {
2377 self.ip = self.lookup_trace_for_backward(target, ip + 1);
2378 } else {
2379 self.ip = target;
2380 }
2381 #[cfg(not(feature = "jit"))]
2382 {
2383 self.ip = target;
2384 }
2385 }
2386 Op::JumpIfTrue(target) => {
2387 let target = *target;
2388 if self.pop().is_truthy() {
2389 #[cfg(feature = "jit")]
2390 if self.tracing_jit && self.recorder.is_none() && target <= ip {
2391 self.ip = self.lookup_trace_for_backward(target, ip + 1);
2392 } else {
2393 self.ip = target;
2394 }
2395 #[cfg(not(feature = "jit"))]
2396 {
2397 self.ip = target;
2398 }
2399 }
2400 }
2401 Op::JumpIfFalse(target) => {
2402 let target = *target;
2403 if !self.pop().is_truthy() {
2404 #[cfg(feature = "jit")]
2405 if self.tracing_jit && self.recorder.is_none() && target <= ip {
2406 self.ip = self.lookup_trace_for_backward(target, ip + 1);
2407 } else {
2408 self.ip = target;
2409 }
2410 #[cfg(not(feature = "jit"))]
2411 {
2412 self.ip = target;
2413 }
2414 }
2415 }
2416 Op::JumpIfTrueKeep(target) => {
2417 let target = *target;
2418 if self.peek().is_truthy() {
2419 #[cfg(feature = "jit")]
2420 if self.tracing_jit && self.recorder.is_none() && target <= ip {
2421 self.ip = self.lookup_trace_for_backward(target, ip + 1);
2422 } else {
2423 self.ip = target;
2424 }
2425 #[cfg(not(feature = "jit"))]
2426 {
2427 self.ip = target;
2428 }
2429 }
2430 }
2431 Op::JumpIfFalseKeep(target) => {
2432 let target = *target;
2433 if !self.peek().is_truthy() {
2434 #[cfg(feature = "jit")]
2435 if self.tracing_jit && self.recorder.is_none() && target <= ip {
2436 self.ip = self.lookup_trace_for_backward(target, ip + 1);
2437 } else {
2438 self.ip = target;
2439 }
2440 #[cfg(not(feature = "jit"))]
2441 {
2442 self.ip = target;
2443 }
2444 }
2445 }
2446
2447 Op::Call(name_idx, argc) => {
2449 if let Some(entry_ip) = self.chunk.find_sub(*name_idx) {
2450 self.frames.push(Frame {
2451 return_ip: self.ip,
2452 stack_base: self.stack.len() - *argc as usize,
2453 slots: Vec::new(),
2454 entry_ip: Some(entry_ip),
2455 });
2456 self.ip = entry_ip;
2457 } else {
2458 return ExecFlow::Ret(VMResult::Error(format!(
2459 "undefined function: {}",
2460 self.chunk
2461 .names
2462 .get(*name_idx as usize)
2463 .map(|s| s.as_str())
2464 .unwrap_or("?")
2465 )));
2466 }
2467 }
2468 Op::Return => {
2469 if let Some(frame) = self.frames.pop() {
2470 self.stack.truncate(frame.stack_base);
2471 self.ip = frame.return_ip;
2472 } else {
2473 self.halted = true;
2474 }
2475 }
2476 Op::ReturnValue => {
2477 let val = self.pop();
2478 if let Some(frame) = self.frames.pop() {
2479 self.stack.truncate(frame.stack_base);
2480 self.ip = frame.return_ip;
2481 self.push(val);
2482 } else {
2483 self.halted = true;
2484 return ExecFlow::Ret(VMResult::Ok(val));
2485 }
2486 }
2487
2488 Op::PushFrame => {
2490 self.frames.push(Frame {
2491 return_ip: self.ip,
2492 stack_base: self.stack.len(),
2493 slots: Vec::new(),
2494 entry_ip: None,
2496 });
2497 }
2498 Op::PopFrame => {
2499 if let Some(frame) = self.frames.pop() {
2500 self.stack.truncate(frame.stack_base);
2501 }
2502 }
2503
2504 Op::Print(n) => {
2506 let n = *n;
2507 let start = self.stack.len().saturating_sub(n as usize);
2508 let mut out = String::new();
2509 for v in &self.stack[start..] {
2510 out.push_str(&v.as_str_cow());
2511 }
2512 self.stack.truncate(start);
2513 self.emit_output(&out);
2514 }
2515 Op::PrintLn(n) => {
2516 let n = *n;
2517 let start = self.stack.len().saturating_sub(n as usize);
2518 let mut out = String::new();
2519 for v in &self.stack[start..] {
2520 out.push_str(&v.as_str_cow());
2521 }
2522 out.push('\n');
2523 self.stack.truncate(start);
2524 self.emit_output(&out);
2525 }
2526 Op::ReadLine => {
2527 if let Some(src) = self.input_source.as_mut() {
2528 match src() {
2529 Some(l) => self.push(Value::str(l)),
2530 None => self.push(Value::Undef),
2531 }
2532 } else {
2533 let mut line = String::new();
2534 let _ = std::io::stdin().read_line(&mut line);
2535 self.push(Value::str(line.trim_end_matches('\n')));
2536 }
2537 }
2538
2539 Op::PreIncSlot(slot) => {
2541 let val = self.get_slot(*slot).to_int() + 1;
2542 self.set_slot(*slot, Value::Int(val));
2543 self.push(Value::Int(val));
2544 }
2545 Op::PreIncSlotVoid(slot) => {
2546 let val = self.get_slot(*slot).to_int() + 1;
2547 self.set_slot(*slot, Value::Int(val));
2548 }
2549 Op::SlotLtIntJumpIfFalse(slot, limit, target) => {
2550 if self.get_slot(*slot).to_int() >= *limit as i64 {
2551 self.ip = *target;
2552 }
2553 }
2554 Op::SlotIncLtIntJumpBack(slot, limit, target) => {
2555 let val = self.get_slot(*slot).to_int() + 1;
2556 self.set_slot(*slot, Value::Int(val));
2557 if val < *limit as i64 {
2558 self.ip = *target;
2559 }
2560 }
2561 Op::AccumSumLoop(sum_slot, i_slot, limit) => {
2562 let mut sum = self.get_slot(*sum_slot).to_int();
2563 let mut i = self.get_slot(*i_slot).to_int();
2564 let lim = *limit as i64;
2565 while i < lim {
2566 sum += i;
2567 i += 1;
2568 }
2569 self.set_slot(*sum_slot, Value::Int(sum));
2570 self.set_slot(*i_slot, Value::Int(i));
2571 }
2572 Op::AddAssignSlotVoid(a, b) => {
2573 let sum = self.get_slot(*a).to_int() + self.get_slot(*b).to_int();
2574 self.set_slot(*a, Value::Int(sum));
2575 }
2576 Op::PreDecSlot(slot) => {
2577 let val = self.get_slot(*slot).to_int() - 1;
2578 self.set_slot(*slot, Value::Int(val));
2579 self.push(Value::Int(val));
2580 }
2581 Op::PostIncSlot(slot) => {
2582 let old = self.get_slot(*slot).to_int();
2583 self.set_slot(*slot, Value::Int(old + 1));
2584 self.push(Value::Int(old));
2585 }
2586 Op::PostDecSlot(slot) => {
2587 let old = self.get_slot(*slot).to_int();
2588 self.set_slot(*slot, Value::Int(old - 1));
2589 self.push(Value::Int(old));
2590 }
2591
2592 Op::SetStatus => {
2594 self.last_status = self.pop().to_int() as i32;
2595 }
2596 Op::GetStatus => {
2597 self.push(Value::Status(self.last_status));
2598 }
2599
2600 Op::Extended(id, arg) => {
2602 let (id, arg) = (*id, *arg);
2603 if let Some(mut handler) = self.ext_handler.take() {
2604 handler(self, id, arg);
2605 self.ext_handler = Some(handler);
2606 }
2607 }
2608 Op::ExtendedWide(id, payload) => {
2609 let (id, payload) = (*id, *payload);
2610 if crate::awk_builtins::is_awk_op(id) {
2611 self.dispatch_awk(id, payload);
2612 } else if let Some(mut handler) = self.ext_wide_handler.take() {
2613 handler(self, id, payload);
2614 self.ext_wide_handler = Some(handler);
2615 }
2616 }
2617
2618 Op::GetArray(idx) => {
2620 let val = self.get_var(*idx);
2621 self.push(val);
2622 }
2623 Op::SetArray(idx) => {
2624 let val = self.pop();
2625 self.set_var(*idx, val);
2626 }
2627 Op::DeclareArray(idx) => {
2628 self.set_var(*idx, Value::array(Vec::new()));
2629 }
2630 Op::ArrayGet(arr_idx) => {
2631 let index = self.pop().to_int() as usize;
2632 let idx = *arr_idx as usize;
2633 let val = if idx < self.globals.len() {
2634 if let Value::Array(ref arr) = self.globals[idx] {
2635 arr.get(index).cloned().unwrap_or(Value::Undef)
2636 } else {
2637 Value::Undef
2638 }
2639 } else {
2640 Value::Undef
2641 };
2642 self.push(val);
2643 }
2644 Op::ArraySet(arr_idx) => {
2645 let index = self.pop().to_int() as usize;
2646 let val = self.pop();
2647 let idx = *arr_idx as usize;
2648 if idx >= self.globals.len() {
2649 self.globals.resize(idx + 1, Value::Undef);
2650 }
2651 if let Some(vec) = self.globals[idx].array_mut() {
2652 if index >= vec.len() {
2653 vec.resize(index + 1, Value::Undef);
2654 }
2655 vec[index] = val;
2656 }
2657 }
2658 Op::ArrayPush(arr_idx) => {
2659 let val = self.pop();
2660 let idx = *arr_idx as usize;
2661 if idx >= self.globals.len() {
2662 self.globals.resize(idx + 1, Value::Undef);
2663 }
2664 if let Some(vec) = self.globals[idx].array_mut() {
2665 vec.push(val);
2666 }
2667 }
2668 Op::ArrayPop(arr_idx) => {
2669 let idx = *arr_idx as usize;
2670 let val = if idx < self.globals.len() {
2671 if let Some(vec) = self.globals[idx].array_mut() {
2672 vec.pop().unwrap_or(Value::Undef)
2673 } else {
2674 Value::Undef
2675 }
2676 } else {
2677 Value::Undef
2678 };
2679 self.push(val);
2680 }
2681 Op::ArrayShift(arr_idx) => {
2682 let idx = *arr_idx as usize;
2683 let val = if idx < self.globals.len() {
2684 if let Some(vec) = self.globals[idx].array_mut() {
2685 if vec.is_empty() {
2686 Value::Undef
2687 } else {
2688 vec.remove(0)
2689 }
2690 } else {
2691 Value::Undef
2692 }
2693 } else {
2694 Value::Undef
2695 };
2696 self.push(val);
2697 }
2698 Op::ArrayLen(arr_idx) => {
2699 let idx = *arr_idx as usize;
2700 let len = if idx < self.globals.len() {
2701 if let Value::Array(ref vec) = self.globals[idx] {
2702 vec.len() as i64
2703 } else {
2704 0
2705 }
2706 } else {
2707 0
2708 };
2709 self.push(Value::Int(len));
2710 }
2711 Op::MakeArray(n) => {
2712 let n = *n;
2713 let start = self.stack.len().saturating_sub(n as usize);
2714 let elements: Vec<Value> = self.stack.drain(start..).collect();
2715 self.push(Value::array(elements));
2716 }
2717
2718 Op::GetHash(idx) => {
2720 let val = self.get_var(*idx);
2721 self.push(val);
2722 }
2723 Op::SetHash(idx) => {
2724 let val = self.pop();
2725 self.set_var(*idx, val);
2726 }
2727 Op::DeclareHash(idx) => {
2728 self.set_var(*idx, Value::Hash(std::collections::HashMap::new()));
2729 }
2730 Op::HashGet(hash_idx) => {
2731 let key_val = self.pop();
2732 let key = key_val.as_str_cow();
2733 let idx = *hash_idx as usize;
2734 let val = if idx < self.globals.len() {
2735 if let Value::Hash(ref map) = self.globals[idx] {
2736 map.get(key.as_ref()).cloned().unwrap_or(Value::Undef)
2737 } else {
2738 Value::Undef
2739 }
2740 } else {
2741 Value::Undef
2742 };
2743 self.push(val);
2744 }
2745 Op::HashSet(hash_idx) => {
2746 let key = self.pop().to_str();
2747 let val = self.pop();
2748 let idx = *hash_idx as usize;
2749 if idx >= self.globals.len() {
2750 self.globals.resize(idx + 1, Value::Undef);
2751 }
2752 if let Value::Hash(ref mut map) = self.globals[idx] {
2753 map.insert(key, val);
2754 }
2755 }
2756 Op::HashDelete(hash_idx) => {
2757 let key_val = self.pop();
2758 let key = key_val.as_str_cow();
2759 let idx = *hash_idx as usize;
2760 let val = if idx < self.globals.len() {
2761 if let Value::Hash(ref mut map) = self.globals[idx] {
2762 map.remove(key.as_ref()).unwrap_or(Value::Undef)
2763 } else {
2764 Value::Undef
2765 }
2766 } else {
2767 Value::Undef
2768 };
2769 self.push(val);
2770 }
2771 Op::HashExists(hash_idx) => {
2772 let key_val = self.pop();
2773 let key = key_val.as_str_cow();
2774 let idx = *hash_idx as usize;
2775 let val = if idx < self.globals.len() {
2776 if let Value::Hash(ref map) = self.globals[idx] {
2777 map.contains_key(key.as_ref())
2778 } else {
2779 false
2780 }
2781 } else {
2782 false
2783 };
2784 self.push(Value::Bool(val));
2785 }
2786 Op::HashKeys(hash_idx) => {
2787 let idx = *hash_idx as usize;
2788 let arr = if idx < self.globals.len() {
2789 if let Value::Hash(ref map) = self.globals[idx] {
2790 let mut keys = Vec::with_capacity(map.len());
2791 keys.extend(map.keys().map(|k| Value::str(k.as_str())));
2792 keys
2793 } else {
2794 Vec::new()
2795 }
2796 } else {
2797 Vec::new()
2798 };
2799 self.push(Value::array(arr));
2800 }
2801 Op::HashValues(hash_idx) => {
2802 let idx = *hash_idx as usize;
2803 let arr = if idx < self.globals.len() {
2804 if let Value::Hash(ref map) = self.globals[idx] {
2805 let mut vals = Vec::with_capacity(map.len());
2806 vals.extend(map.values().cloned());
2807 vals
2808 } else {
2809 Vec::new()
2810 }
2811 } else {
2812 Vec::new()
2813 };
2814 self.push(Value::array(arr));
2815 }
2816 Op::MakeHash(n) => {
2817 let n = *n;
2818 let start = self.stack.len().saturating_sub(n as usize);
2819 let pairs: Vec<Value> = self.stack.drain(start..).collect();
2820 let mut map = std::collections::HashMap::with_capacity(pairs.len() / 2);
2821 let mut iter = pairs.into_iter();
2822 while let Some(key) = iter.next() {
2823 if let Some(val) = iter.next() {
2824 map.insert(key.to_str(), val);
2825 }
2826 }
2827 self.push(Value::Hash(map));
2828 }
2829
2830 Op::Range => {
2832 let to = self.pop().to_int();
2833 let from = self.pop().to_int();
2834 let cap = (to - from + 1).max(0) as usize;
2835 let mut arr = Vec::with_capacity(cap);
2836 arr.extend((from..=to).map(Value::Int));
2837 self.push(Value::array(arr));
2838 }
2839 Op::RangeStep => {
2840 let step = self.pop().to_int();
2841 let to = self.pop().to_int();
2842 let from = self.pop().to_int();
2843 let cap = if step > 0 {
2844 ((to - from) / step + 1).max(0) as usize
2845 } else if step < 0 {
2846 ((from - to) / (-step) + 1).max(0) as usize
2847 } else {
2848 0
2849 };
2850 let mut arr = Vec::with_capacity(cap);
2851 if step > 0 {
2852 let mut i = from;
2853 while i <= to {
2854 arr.push(Value::Int(i));
2855 i += step;
2856 }
2857 } else if step < 0 {
2858 let mut i = from;
2859 while i >= to {
2860 arr.push(Value::Int(i));
2861 i += step;
2862 }
2863 }
2864 self.push(Value::array(arr));
2865 }
2866
2867 Op::TestFile(test_type) => {
2869 let test_type = *test_type;
2870 let path = self.pop().to_str();
2871 let result = match test_type {
2872 crate::op::file_test::EXISTS => std::path::Path::new(&path).exists(),
2873 crate::op::file_test::IS_FILE => std::path::Path::new(&path).is_file(),
2874 crate::op::file_test::IS_DIR => std::path::Path::new(&path).is_dir(),
2875 crate::op::file_test::IS_SYMLINK => std::path::Path::new(&path).is_symlink(),
2876 crate::op::file_test::IS_READABLE | crate::op::file_test::IS_WRITABLE => {
2877 std::path::Path::new(&path).exists()
2878 }
2879 crate::op::file_test::IS_EXECUTABLE => {
2880 #[cfg(unix)]
2881 {
2882 use std::os::unix::fs::PermissionsExt;
2883 std::fs::metadata(&path)
2884 .map(|m| m.permissions().mode() & 0o111 != 0)
2885 .unwrap_or(false)
2886 }
2887 #[cfg(not(unix))]
2888 {
2889 std::path::Path::new(&path).exists()
2890 }
2891 }
2892 crate::op::file_test::IS_NONEMPTY => std::fs::metadata(&path)
2893 .map(|m| m.len() > 0)
2894 .unwrap_or(false),
2895 crate::op::file_test::IS_SOCKET => {
2896 #[cfg(unix)]
2897 {
2898 use std::os::unix::fs::FileTypeExt;
2899 std::fs::symlink_metadata(&path)
2900 .map(|m| m.file_type().is_socket())
2901 .unwrap_or(false)
2902 }
2903 #[cfg(not(unix))]
2904 {
2905 false
2906 }
2907 }
2908 crate::op::file_test::IS_FIFO => {
2909 #[cfg(unix)]
2910 {
2911 use std::os::unix::fs::FileTypeExt;
2912 std::fs::symlink_metadata(&path)
2913 .map(|m| m.file_type().is_fifo())
2914 .unwrap_or(false)
2915 }
2916 #[cfg(not(unix))]
2917 {
2918 false
2919 }
2920 }
2921 crate::op::file_test::IS_BLOCK_DEV => {
2922 #[cfg(unix)]
2923 {
2924 use std::os::unix::fs::FileTypeExt;
2925 std::fs::symlink_metadata(&path)
2926 .map(|m| m.file_type().is_block_device())
2927 .unwrap_or(false)
2928 }
2929 #[cfg(not(unix))]
2930 {
2931 false
2932 }
2933 }
2934 crate::op::file_test::IS_CHAR_DEV => {
2935 #[cfg(unix)]
2936 {
2937 use std::os::unix::fs::FileTypeExt;
2938 std::fs::symlink_metadata(&path)
2939 .map(|m| m.file_type().is_char_device())
2940 .unwrap_or(false)
2941 }
2942 #[cfg(not(unix))]
2943 {
2944 false
2945 }
2946 }
2947 _ => false,
2948 };
2949 self.push(Value::Bool(result));
2950 }
2951
2952 Op::Exec(argc) => {
2953 let argc = *argc;
2954 let start = self.stack.len().saturating_sub(argc as usize);
2955 let args: Vec<String> = self
2961 .stack
2962 .drain(start..)
2963 .flat_map(|v| match v {
2964 Value::Array(items) => items.iter().map(|i| i.to_str()).collect::<Vec<_>>(),
2965 other => vec![other.to_str()],
2966 })
2967 .collect();
2968 if let Some(cmd) = args.first() {
2969 let name_idx = self.chunk.names.iter().position(|n| n == cmd);
2971 if let Some(name_idx) = name_idx {
2972 if let Some(entry_ip) = self.chunk.find_sub(name_idx as u16) {
2973 for arg in &args[1..] {
2975 self.push(Value::str(arg));
2976 }
2977 self.frames.push(Frame {
2979 return_ip: self.ip,
2980 stack_base: self.stack.len() - (args.len() - 1),
2981 slots: Vec::with_capacity(8),
2982 entry_ip: Some(entry_ip),
2983 });
2984 self.ip = entry_ip;
2985 return ExecFlow::Cont;
2986 }
2987 }
2988
2989 match cmd.as_str() {
2990 "true" => self.push(Value::Status(0)),
2991 "false" => self.push(Value::Status(1)),
2992 "echo" => {
2993 println!("{}", args[1..].join(" "));
2994 self.push(Value::Status(0));
2995 }
2996 "test" | "[" => {
2997 self.push(Value::Status(0));
2998 }
2999 _ => {
3000 let status = if let Some(h) = self.host.as_mut() {
3010 h.exec(args.clone())
3011 } else {
3012 #[cfg(not(target_arch = "wasm32"))]
3013 {
3014 use std::process::{Command, Stdio};
3015 Command::new(cmd)
3016 .args(&args[1..])
3017 .stdout(Stdio::inherit())
3018 .stderr(Stdio::inherit())
3019 .status()
3020 .map(|s| s.code().unwrap_or(1))
3021 .unwrap_or(127)
3022 }
3023 #[cfg(target_arch = "wasm32")]
3026 {
3027 let _ = cmd;
3028 127
3029 }
3030 };
3031 self.push(Value::Status(status));
3032 }
3033 }
3034 } else {
3035 self.push(Value::Status(0));
3036 }
3037 }
3038 Op::ExecBg(argc) => {
3039 let argc = *argc;
3040 let start = self.stack.len().saturating_sub(argc as usize);
3041 let args: Vec<String> = self
3043 .stack
3044 .drain(start..)
3045 .flat_map(|v| match v {
3046 Value::Array(items) => items.iter().map(|i| i.to_str()).collect::<Vec<_>>(),
3047 other => vec![other.to_str()],
3048 })
3049 .collect();
3050 if let Some(cmd) = args.first() {
3051 if let Some(h) = self.host.as_mut() {
3060 let _ = h.exec_bg(args.clone());
3061 } else {
3062 #[cfg(not(target_arch = "wasm32"))]
3063 {
3064 use std::process::{Command, Stdio};
3065 let _ = Command::new(cmd)
3066 .args(&args[1..])
3067 .stdout(Stdio::null())
3068 .stderr(Stdio::null())
3069 .spawn();
3070 }
3071 #[cfg(target_arch = "wasm32")]
3073 {
3074 let _ = cmd;
3075 }
3076 }
3077 }
3078 self.push(Value::Status(0));
3079 }
3080
3081 Op::PipelineBegin(n) => {
3083 let n = *n;
3084 if let Some(h) = self.host.as_mut() {
3085 h.pipeline_begin(n);
3086 }
3087 }
3088 Op::PipelineStage => {
3089 if let Some(h) = self.host.as_mut() {
3090 h.pipeline_stage();
3091 }
3092 }
3093 Op::PipelineEnd => {
3094 let status = if let Some(h) = self.host.as_mut() {
3095 h.pipeline_end()
3096 } else {
3097 self.last_status
3098 };
3099 self.last_status = status;
3100 self.push(Value::Status(status));
3101 }
3102 Op::SubshellBegin => {
3103 if let Some(h) = self.host.as_mut() {
3104 h.subshell_begin();
3105 }
3106 }
3107 Op::SubshellEnd => {
3108 if let Some(h) = self.host.as_mut() {
3109 if let Some(status) = h.subshell_end() {
3110 self.last_status = status;
3111 }
3112 }
3113 }
3114 Op::Redirect(fd, op) => {
3115 let fd = *fd;
3116 let op = *op;
3117 let target = self.pop().to_str();
3118 if let Some(h) = self.host.as_mut() {
3119 h.redirect(fd, op, &target);
3120 }
3121 }
3122 Op::HereDoc(idx) => {
3123 let content = self
3124 .chunk
3125 .constants
3126 .get(*idx as usize)
3127 .map(|v| v.to_str())
3128 .unwrap_or_default();
3129 if let Some(h) = self.host.as_mut() {
3130 h.heredoc(&content);
3131 }
3132 }
3133 Op::HereString => {
3134 let s = self.pop().to_str();
3135 if let Some(h) = self.host.as_mut() {
3136 h.herestring(&s);
3137 }
3138 }
3139 Op::CmdSubst(idx) => {
3140 let result = match self.chunk.sub_chunks.get(*idx as usize) {
3141 Some(sub) => {
3142 let sub_ref: *const Chunk = sub;
3144 let sub_ref = unsafe { &*sub_ref };
3146 if let Some(h) = self.host.as_mut() {
3147 h.cmd_subst(sub_ref)
3148 } else {
3149 String::new()
3150 }
3151 }
3152 None => String::new(),
3153 };
3154 self.push(Value::str(result));
3155 }
3156 Op::ProcessSubIn(idx) => {
3157 let result = match self.chunk.sub_chunks.get(*idx as usize) {
3158 Some(sub) => {
3159 let sub_ref: *const Chunk = sub;
3160 let sub_ref = unsafe { &*sub_ref };
3161 if let Some(h) = self.host.as_mut() {
3162 h.process_sub_in(sub_ref)
3163 } else {
3164 String::new()
3165 }
3166 }
3167 None => String::new(),
3168 };
3169 self.push(Value::str(result));
3170 }
3171 Op::ProcessSubOut(idx) => {
3172 let result = match self.chunk.sub_chunks.get(*idx as usize) {
3173 Some(sub) => {
3174 let sub_ref: *const Chunk = sub;
3175 let sub_ref = unsafe { &*sub_ref };
3176 if let Some(h) = self.host.as_mut() {
3177 h.process_sub_out(sub_ref)
3178 } else {
3179 String::new()
3180 }
3181 }
3182 None => String::new(),
3183 };
3184 self.push(Value::str(result));
3185 }
3186 Op::Glob | Op::GlobRecursive => {
3187 let recursive = matches!(&ops[ip], Op::GlobRecursive);
3188 let pat_val = self.pop();
3189 let pattern = pat_val.to_str();
3190 let matches: Vec<String> = if let Some(h) = self.host.as_mut() {
3191 h.glob(&pattern, recursive)
3192 } else {
3193 glob::glob(&pattern)
3194 .into_iter()
3195 .flat_map(|paths| paths.filter_map(|p| p.ok()))
3196 .map(|p| p.to_string_lossy().into_owned())
3197 .collect()
3198 };
3199 let arr: Vec<Value> = matches.into_iter().map(Value::str).collect();
3200 self.push(Value::array(arr));
3201 }
3202 Op::TrapSet(idx) => {
3203 let sig = self.pop().to_str();
3205 if let Some(sub) = self.chunk.sub_chunks.get(*idx as usize) {
3206 let sub_ref: *const Chunk = sub;
3207 let sub_ref = unsafe { &*sub_ref };
3208 if let Some(h) = self.host.as_mut() {
3209 h.trap_set(&sig, sub_ref);
3210 }
3211 }
3212 }
3213 Op::TrapCheck => {
3214 if let Some(h) = self.host.as_mut() {
3215 h.trap_check();
3216 }
3217 }
3218 Op::TildeExpand => {
3219 let s = self.pop().to_str();
3220 let result = if let Some(h) = self.host.as_mut() {
3221 h.tilde_expand(&s)
3222 } else {
3223 s
3224 };
3225 self.push(Value::str(result));
3226 }
3227 Op::BraceExpand => {
3228 let s = self.pop().to_str();
3229 let result = if let Some(h) = self.host.as_mut() {
3230 h.brace_expand(&s)
3231 } else {
3232 vec![s]
3233 };
3234 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
3235 self.push(Value::array(arr));
3236 }
3237 Op::WordSplit => {
3238 let s = self.pop().to_str();
3239 let result = if let Some(h) = self.host.as_mut() {
3240 h.word_split(&s)
3241 } else {
3242 s.split_whitespace().map(|w| w.to_string()).collect()
3243 };
3244 let arr: Vec<Value> = result.into_iter().map(Value::str).collect();
3245 self.push(Value::array(arr));
3246 }
3247 Op::ExpandParam(modifier) => {
3248 let m = *modifier;
3254 let argc = match m {
3255 crate::op::param_mod::DEFAULT
3256 | crate::op::param_mod::ASSIGN
3257 | crate::op::param_mod::ERROR
3258 | crate::op::param_mod::ALTERNATE
3259 | crate::op::param_mod::STRIP_SHORT
3260 | crate::op::param_mod::STRIP_LONG
3261 | crate::op::param_mod::RSTRIP_SHORT
3262 | crate::op::param_mod::RSTRIP_LONG => 1,
3263 crate::op::param_mod::SUBST_FIRST
3264 | crate::op::param_mod::SUBST_ALL
3265 | crate::op::param_mod::SLICE => 2,
3266 _ => 0,
3267 };
3268 let mut args: Vec<Value> = Vec::with_capacity(argc);
3269 for _ in 0..argc {
3270 args.push(self.pop());
3271 }
3272 args.reverse();
3273 let name = self.pop().to_str();
3274 let result = if let Some(h) = self.host.as_mut() {
3275 h.expand_param(&name, m, &args)
3276 } else {
3277 Value::str("")
3278 };
3279 self.push(result);
3280 }
3281 Op::StrMatch => {
3282 let pat = self.pop().to_str();
3283 let s = self.pop().to_str();
3284 let result = if let Some(h) = self.host.as_mut() {
3285 h.str_match(&s, &pat)
3286 } else {
3287 s == pat
3288 };
3289 self.push(Value::Bool(result));
3290 }
3291 Op::RegexMatch => {
3292 let re = self.pop().to_str();
3293 let s = self.pop().to_str();
3294 let result = if let Some(h) = self.host.as_mut() {
3295 h.regex_match(&s, &re)
3296 } else {
3297 false
3298 };
3299 self.push(Value::Bool(result));
3300 }
3301 Op::WithRedirectsBegin(n) => {
3302 let n = *n;
3303 if let Some(h) = self.host.as_mut() {
3304 h.with_redirects_begin(n);
3305 }
3306 }
3307 Op::WithRedirectsEnd => {
3308 if let Some(h) = self.host.as_mut() {
3309 h.with_redirects_end();
3310 }
3311 }
3312 Op::CallFunction(name_idx, argc) => {
3313 let name = self
3314 .chunk
3315 .names
3316 .get(*name_idx as usize)
3317 .cloned()
3318 .unwrap_or_default();
3319 let argc = *argc as usize;
3320 let start = self.stack.len().saturating_sub(argc);
3321 let args: Vec<String> = self
3323 .stack
3324 .drain(start..)
3325 .flat_map(|v| match v {
3326 Value::Array(items) => items.iter().map(|i| i.to_str()).collect::<Vec<_>>(),
3327 other => vec![other.to_str()],
3328 })
3329 .collect();
3330 let status = if self.host.is_some() {
3331 let cf = self
3333 .host
3334 .as_mut()
3335 .unwrap()
3336 .call_function(&name, args.clone());
3337 match cf {
3338 Some(s) => s,
3339 None => {
3340 if let Some(v) = self.run_builtin_by_name(&name, &args) {
3349 v.to_int() as i32
3350 } else {
3351 let mut full = Vec::with_capacity(args.len() + 1);
3352 full.push(name.clone());
3353 full.extend(args);
3354 self.host.as_mut().unwrap().exec(full)
3355 }
3356 }
3357 }
3358 } else {
3359 let nidx = *name_idx;
3361 if let Some(entry_ip) = self.chunk.find_sub(nidx) {
3362 for arg in &args {
3363 self.push(Value::str(arg));
3364 }
3365 self.frames.push(Frame {
3366 return_ip: self.ip,
3367 stack_base: self.stack.len() - args.len(),
3368 slots: Vec::with_capacity(8),
3369 entry_ip: Some(entry_ip),
3370 });
3371 self.ip = entry_ip;
3372 return ExecFlow::Cont;
3373 }
3374 let mut full = Vec::with_capacity(args.len() + 1);
3375 full.push(name);
3376 full.extend(args);
3377 #[cfg(not(target_arch = "wasm32"))]
3378 {
3379 use std::process::Command;
3380 Command::new(&full[0])
3381 .args(&full[1..])
3382 .status()
3383 .map(|s| s.code().unwrap_or(1))
3384 .unwrap_or(127)
3385 }
3386 #[cfg(target_arch = "wasm32")]
3389 {
3390 let _ = &full;
3391 127
3392 }
3393 };
3394 self.last_status = status;
3395 self.push(Value::Status(status));
3396 }
3397
3398 Op::ConcatConstLoop(const_idx, s_slot, i_slot, limit) => {
3400 let c_str = self
3401 .chunk
3402 .constants
3403 .get(*const_idx as usize)
3404 .map(|v| v.as_str_cow())
3405 .unwrap_or(std::borrow::Cow::Borrowed(""));
3406 let mut s = self.get_slot(*s_slot).to_str();
3407 let mut i = self.get_slot(*i_slot).to_int();
3408 let lim = *limit as i64;
3409 let iters = (lim - i).max(0) as usize;
3410 s.reserve(c_str.len() * iters);
3411 while i < lim {
3412 s.push_str(&c_str);
3413 i += 1;
3414 }
3415 self.set_slot(*s_slot, Value::str(s));
3416 self.set_slot(*i_slot, Value::Int(i));
3417 }
3418 Op::PushIntRangeLoop(arr_idx, i_slot, limit) => {
3419 let mut i = self.get_slot(*i_slot).to_int();
3420 let lim = *limit as i64;
3421 let idx = *arr_idx as usize;
3422 if idx >= self.globals.len() {
3423 self.globals.resize(idx + 1, Value::Undef);
3424 }
3425 if self.globals[idx].as_array().is_none() {
3429 self.globals[idx] = Value::array(Vec::new());
3430 }
3431 let vec = self.globals[idx]
3432 .array_mut()
3433 .expect("globals[idx] was just made an array");
3434 vec.reserve((lim - i).max(0) as usize);
3435 while i < lim {
3436 vec.push(Value::Int(i));
3437 i += 1;
3438 }
3439 self.set_slot(*i_slot, Value::Int(i));
3440 }
3441
3442 Op::MapBlock(_)
3444 | Op::GrepBlock(_)
3445 | Op::SortBlock(_)
3446 | Op::SortDefault
3447 | Op::ForEachBlock(_) => {}
3448
3449 Op::CallBuiltin(id, argc) => {
3451 let (id, argc) = (*id, *argc);
3452 if let Some(Some(handler)) = self.builtin_table.get(id as usize) {
3453 let result = handler(self, argc);
3454 self.push(result);
3455 }
3456 }
3457
3458 Op::AwkFieldGet => self.dispatch_awk(ab::AWK_FIELD_GET, 0),
3461 Op::AwkFieldSet => self.dispatch_awk(ab::AWK_FIELD_SET, 0),
3462 Op::AwkNf => self.dispatch_awk(ab::AWK_NF, 0),
3463 Op::AwkSetRecord => self.dispatch_awk(ab::AWK_SET_RECORD, 0),
3464 Op::AwkSpecialGet(n) => self.dispatch_awk(ab::AWK_SPECIAL_GET, *n as usize),
3465 Op::AwkSpecialSet(n) => self.dispatch_awk(ab::AWK_SPECIAL_SET, *n as usize),
3466 Op::AwkPrint(argc) => self.dispatch_awk(ab::AWK_PRINT, *argc as usize),
3467 Op::AwkPrintf(argc) => self.dispatch_awk(ab::AWK_PRINTF, *argc as usize),
3468 Op::AwkSprintf(argc) => self.dispatch_awk(ab::AWK_SPRINTF, *argc as usize),
3469 Op::AwkGetline(src) => self.dispatch_awk(ab::AWK_GETLINE, *src as usize),
3470 Op::AwkLength(argc) => self.dispatch_awk(ab::AWK_LENGTH, *argc as usize),
3471 Op::AwkSubstr(argc) => self.dispatch_awk(ab::AWK_SUBSTR, *argc as usize),
3472 Op::AwkIndex => self.dispatch_awk(ab::AWK_INDEX, 0),
3473 Op::AwkSplit(argc) => self.dispatch_awk(ab::AWK_SPLIT, *argc as usize),
3474 Op::AwkSub(argc) => self.dispatch_awk(ab::AWK_SUB, *argc as usize),
3475 Op::AwkGsub(argc) => self.dispatch_awk(ab::AWK_GSUB, *argc as usize),
3476 Op::AwkMatch => self.dispatch_awk(ab::AWK_MATCH, 0),
3477 Op::AwkToLower => self.dispatch_awk(ab::AWK_TOLOWER, 0),
3478 Op::AwkToUpper => self.dispatch_awk(ab::AWK_TOUPPER, 0),
3479 Op::AwkInt => self.dispatch_awk(ab::AWK_INT, 0),
3480 Op::AwkSqrt => self.dispatch_awk(ab::AWK_SQRT, 0),
3481 Op::AwkSin => self.dispatch_awk(ab::AWK_SIN, 0),
3482 Op::AwkCos => self.dispatch_awk(ab::AWK_COS, 0),
3483 Op::AwkExp => self.dispatch_awk(ab::AWK_EXP, 0),
3484 Op::AwkLog => self.dispatch_awk(ab::AWK_LOG, 0),
3485 Op::AwkAtan2 => self.dispatch_awk(ab::AWK_ATAN2, 0),
3486 Op::AwkDiv => {
3490 let b = self.pop();
3491 let a = self.pop();
3492 let divisor = b.to_float();
3493 if divisor == 0.0 {
3494 return ExecFlow::Ret(VMResult::Error(
3495 "division by zero attempted".to_string(),
3496 ));
3497 }
3498 self.push(Value::Float(a.to_float() / divisor));
3499 }
3500 Op::AwkMod => {
3501 let b = self.pop();
3502 let a = self.pop();
3503 let divisor = b.to_float();
3504 if divisor == 0.0 {
3505 return ExecFlow::Ret(VMResult::Error(
3506 "division by zero attempted in `%'".to_string(),
3507 ));
3508 }
3509 self.push(Value::Float(a.to_float() % divisor));
3510 }
3511 Op::AwkDivJit => {
3515 let b = self.pop();
3516 let a = self.pop();
3517 let divisor = b.to_float();
3518 if divisor == 0.0 {
3519 return ExecFlow::Ret(VMResult::Error(
3520 "division by zero attempted".to_string(),
3521 ));
3522 }
3523 self.push(Value::Float(a.to_float() / divisor));
3524 }
3525 Op::AwkModJit => {
3526 let b = self.pop();
3527 let a = self.pop();
3528 let divisor = b.to_float();
3529 if divisor == 0.0 {
3530 return ExecFlow::Ret(VMResult::Error(
3531 "division by zero attempted in `%'".to_string(),
3532 ));
3533 }
3534 self.push(Value::Float(a.to_float() % divisor));
3535 }
3536 Op::AwkSqrtJit => {
3541 let a = self.pop().to_float();
3542 if a < 0.0 {
3543 eprintln!("awk: warning: sqrt: received negative argument {a}");
3544 self.push(Value::Float(f64::NAN));
3545 } else {
3546 self.push(Value::Float(a.sqrt()));
3547 }
3548 }
3549 Op::AwkLogJit => {
3554 let a = self.pop().to_float();
3555 if a < 0.0 {
3556 eprintln!("awk: warning: log: received negative argument {a}");
3557 self.push(Value::Float(f64::NAN));
3558 } else {
3559 self.push(Value::Float(a.ln()));
3560 }
3561 }
3562 Op::AwkLshiftJit => {
3566 let n = self.pop().to_float();
3567 let a = self.pop().to_float();
3568 if a < 0.0 || n < 0.0 {
3569 return ExecFlow::Ret(VMResult::Error(
3570 "lshift: negative values are not allowed".to_string(),
3571 ));
3572 }
3573 let shifted = (a as i64).wrapping_shl((n as u32) & 0x3f);
3574 self.push(Value::Float(shifted as f64));
3575 }
3576 Op::AwkRshiftJit => {
3578 let n = self.pop().to_float();
3579 let a = self.pop().to_float();
3580 if a < 0.0 || n < 0.0 {
3581 return ExecFlow::Ret(VMResult::Error(
3582 "rshift: negative values are not allowed".to_string(),
3583 ));
3584 }
3585 let shifted = ((a as i64) as u64).wrapping_shr((n as u32) & 0x3f);
3586 self.push(Value::Float(shifted as f64));
3587 }
3588 Op::AwkComplJit => {
3592 let a = self.pop().to_float();
3593 if a < 0.0 {
3594 return ExecFlow::Ret(VMResult::Error(
3595 "compl: negative value is not allowed".to_string(),
3596 ));
3597 }
3598 let v = !(a as i64);
3599 self.push(Value::Float(v as f64));
3600 }
3601 Op::AwkGetFieldNum(field_idx) => {
3606 let v = crate::jit::fusevm_jit_awk_get_field_num(*field_idx as i64);
3607 self.push(Value::Float(v));
3608 }
3609 Op::PowFloat => {
3610 let b = self.pop();
3611 let a = self.pop();
3612 self.push(Value::Float(a.to_float().powf(b.to_float())));
3613 }
3614 Op::SqrtFloat => {
3615 let a = self.pop();
3616 self.push(Value::Float(a.to_float().sqrt()));
3617 }
3618 Op::SinFloat => {
3619 let a = self.pop();
3620 self.push(Value::Float(a.to_float().sin()));
3621 }
3622 Op::CosFloat => {
3623 let a = self.pop();
3624 self.push(Value::Float(a.to_float().cos()));
3625 }
3626 Op::ExpFloat => {
3627 let a = self.pop();
3628 self.push(Value::Float(a.to_float().exp()));
3629 }
3630 Op::Atan2Float => {
3631 let x = self.pop();
3632 let y = self.pop();
3633 self.push(Value::Float(y.to_float().atan2(x.to_float())));
3634 }
3635 Op::LogFloat => {
3636 let a = self.pop();
3637 self.push(Value::Float(a.to_float().ln()));
3638 }
3639 Op::AbsFloat => {
3640 let a = self.pop();
3641 self.push(Value::Float(a.to_float().abs()));
3642 }
3643 Op::TruncInt => {
3644 let a = self.pop();
3645 self.push(Value::Int(a.to_int()));
3646 }
3647 Op::CeilFloat => {
3648 let a = self.pop();
3649 self.push(Value::Float(a.to_float().ceil()));
3650 }
3651 Op::FloorFloat => {
3652 let a = self.pop();
3653 self.push(Value::Float(a.to_float().floor()));
3654 }
3655 Op::TruncFloat => {
3656 let a = self.pop();
3657 self.push(Value::Float(a.to_float().trunc()));
3658 }
3659 Op::RoundFloat => {
3660 let a = self.pop();
3661 self.push(Value::Float(a.to_float().round_ties_even()));
3662 }
3663 Op::TanFloat => {
3664 let a = self.pop();
3665 self.push(Value::Float(a.to_float().tan()));
3666 }
3667 Op::AsinFloat => {
3668 let a = self.pop();
3669 self.push(Value::Float(a.to_float().asin()));
3670 }
3671 Op::AcosFloat => {
3672 let a = self.pop();
3673 self.push(Value::Float(a.to_float().acos()));
3674 }
3675 Op::AtanFloat => {
3676 let a = self.pop();
3677 self.push(Value::Float(a.to_float().atan()));
3678 }
3679 Op::SinhFloat => {
3680 let a = self.pop();
3681 self.push(Value::Float(a.to_float().sinh()));
3682 }
3683 Op::CoshFloat => {
3684 let a = self.pop();
3685 self.push(Value::Float(a.to_float().cosh()));
3686 }
3687 Op::TanhFloat => {
3688 let a = self.pop();
3689 self.push(Value::Float(a.to_float().tanh()));
3690 }
3691 Op::Log2Float => {
3692 let a = self.pop();
3693 self.push(Value::Float(a.to_float().log2()));
3694 }
3695 Op::Log10Float => {
3696 let a = self.pop();
3697 self.push(Value::Float(a.to_float().log10()));
3698 }
3699 Op::AbsInt => {
3700 let a = self.pop();
3701 self.push(Value::Int(a.to_int().wrapping_abs()));
3702 }
3703 Op::MulModFloor => {
3709 let len = self.stack.len();
3710 let all_int = len >= 3
3711 && self.stack[len - 3..]
3712 .iter()
3713 .all(|v| matches!(v, Value::Int(_)));
3714 if all_int {
3715 let k = self.pop().to_int();
3716 let b = self.pop().to_int();
3717 let a = self.pop().to_int();
3718 self.push(Value::Int(crate::floor_rem_i128(a as i128 * b as i128, k)));
3719 } else {
3720 let k = self.pop();
3721 let k_for_floor = k.clone();
3722 if let Some(e) = self.arith_int_fast(
3723 NumOp::Mul,
3724 i64::wrapping_mul,
3725 i64::checked_mul,
3726 |a, b| a * b,
3727 ip,
3728 ) {
3729 return ExecFlow::Ret(VMResult::Error(e));
3730 }
3731 self.push(k);
3732 if let Some(e) = self.arith_int_fast(
3733 NumOp::Mod,
3734 |x, y| if y != 0 { x % y } else { 0 },
3735 |x, y| if y != 0 { x.checked_rem(y) } else { Some(0) },
3736 |a, b| a % b,
3737 ip,
3738 ) {
3739 return ExecFlow::Ret(VMResult::Error(e));
3740 }
3741 self.floor_correct_top(&k_for_floor);
3742 }
3743 }
3744 Op::MulAddModFloor => {
3748 let len = self.stack.len();
3749 let all_int = len >= 4
3750 && self.stack[len - 4..]
3751 .iter()
3752 .all(|v| matches!(v, Value::Int(_)));
3753 if all_int {
3754 let k = self.pop().to_int();
3755 let c = self.pop().to_int();
3756 let b = self.pop().to_int();
3757 let a = self.pop().to_int();
3758 self.push(Value::Int(crate::floor_rem_i128(
3760 a as i128 * b as i128 + c as i128,
3761 k,
3762 )));
3763 } else {
3764 let k = self.pop();
3765 let k_for_floor = k.clone();
3766 let c = self.pop();
3767 if let Some(e) = self.arith_int_fast(
3768 NumOp::Mul,
3769 i64::wrapping_mul,
3770 i64::checked_mul,
3771 |a, b| a * b,
3772 ip,
3773 ) {
3774 return ExecFlow::Ret(VMResult::Error(e));
3775 }
3776 self.push(c);
3777 if let Some(e) = self.arith_int_fast(
3778 NumOp::Add,
3779 i64::wrapping_add,
3780 i64::checked_add,
3781 |a, b| a + b,
3782 ip,
3783 ) {
3784 return ExecFlow::Ret(VMResult::Error(e));
3785 }
3786 self.push(k);
3787 if let Some(e) = self.arith_int_fast(
3788 NumOp::Mod,
3789 |x, y| if y != 0 { x % y } else { 0 },
3790 |x, y| if y != 0 { x.checked_rem(y) } else { Some(0) },
3791 |a, b| a % b,
3792 ip,
3793 ) {
3794 return ExecFlow::Ret(VMResult::Error(e));
3795 }
3796 self.floor_correct_top(&k_for_floor);
3797 }
3798 }
3799 Op::GcdInt => {
3800 let b = self.pop().to_int().unsigned_abs();
3801 let a = self.pop().to_int().unsigned_abs();
3802 let mut x = a;
3803 let mut y = b;
3804 while y != 0 {
3805 let t = x % y;
3806 x = y;
3807 y = t;
3808 }
3809 self.push(Value::Int(x.min(i64::MAX as u64) as i64));
3817 }
3818 Op::LcmInt => {
3819 let b = self.pop().to_int().unsigned_abs();
3820 let a = self.pop().to_int().unsigned_abs();
3821 if a == 0 || b == 0 {
3822 self.push(Value::Int(0));
3823 } else {
3824 let mut x = a;
3825 let mut y = b;
3826 while y != 0 {
3827 let t = x % y;
3828 x = y;
3829 y = t;
3830 }
3831 let prod = (a / x).saturating_mul(b);
3832 self.push(Value::Int(prod.min(i64::MAX as u64) as i64));
3833 }
3834 }
3835 Op::TimeInt => {
3836 self.push(Value::Int(crate::sysclock::unix_secs()));
3837 }
3838 Op::AwkArrayGet(n) => self.dispatch_awk(ab::AWK_ARRAY_GET, *n as usize),
3839 Op::AwkArraySet(n) => self.dispatch_awk(ab::AWK_ARRAY_SET, *n as usize),
3840 Op::AwkArrayExists(n) => self.dispatch_awk(ab::AWK_ARRAY_EXISTS, *n as usize),
3841 Op::AwkArrayDelete(n) => self.dispatch_awk(ab::AWK_ARRAY_DELETE, *n as usize),
3842 Op::AwkArrayClear(n) => self.dispatch_awk(ab::AWK_ARRAY_CLEAR, *n as usize),
3843 Op::AwkArrayLen(n) => self.dispatch_awk(ab::AWK_ARRAY_LEN, *n as usize),
3844 Op::AwkAnd(argc) => self.dispatch_awk(ab::AWK_AND, *argc as usize),
3845 Op::AwkOr(argc) => self.dispatch_awk(ab::AWK_OR, *argc as usize),
3846 Op::AwkXor(argc) => self.dispatch_awk(ab::AWK_XOR, *argc as usize),
3847 Op::AwkCompl => self.dispatch_awk(ab::AWK_COMPL, 0),
3848 Op::AwkLshift => self.dispatch_awk(ab::AWK_LSHIFT, 0),
3849 Op::AwkRshift => self.dispatch_awk(ab::AWK_RSHIFT, 0),
3850 Op::AwkStrtonum => self.dispatch_awk(ab::AWK_STRTONUM, 0),
3851 Op::AwkSystime => self.dispatch_awk(ab::AWK_SYSTIME, 0),
3852 Op::AwkRand => self.dispatch_awk(ab::AWK_RAND, 0),
3853 Op::AwkSrand(argc) => self.dispatch_awk(ab::AWK_SRAND, *argc as usize),
3854 Op::AwkStrftime(argc) => self.dispatch_awk(ab::AWK_STRFTIME, *argc as usize),
3855 Op::AwkMktime(argc) => self.dispatch_awk(ab::AWK_MKTIME, *argc as usize),
3856 Op::AwkOrd => self.dispatch_awk(ab::AWK_ORD, 1),
3857 Op::AwkChr => self.dispatch_awk(ab::AWK_CHR, 1),
3858 Op::AwkMkbool => self.dispatch_awk(ab::AWK_MKBOOL, 1),
3859 Op::AwkIntdiv => self.dispatch_awk(ab::AWK_INTDIV, 2),
3860 Op::AwkIntdiv0 => self.dispatch_awk(ab::AWK_INTDIV0, 2),
3861 Op::AwkGensub(argc) => self.dispatch_awk(ab::AWK_GENSUB, *argc as usize),
3862 Op::AwkSignal(code) => {
3863 self.awk_signal = Some(*code);
3866 self.halted = true;
3867 }
3868
3869 Op::Go(name_idx, argc) => {
3874 let n = *argc as usize;
3875 let mut args = Vec::with_capacity(n);
3876 for _ in 0..n {
3877 args.push(self.pop());
3878 }
3879 args.reverse();
3880 self.sched = Some(crate::sched::SchedReq::Go {
3881 name_idx: *name_idx,
3882 args,
3883 });
3884 self.halted = true;
3885 }
3886 Op::ChanMake => {
3887 let cap = self.pop().to_int().max(0) as usize;
3888 self.sched = Some(crate::sched::SchedReq::Make { cap });
3889 self.halted = true;
3890 }
3891 Op::ChanSend => {
3892 let val = self.pop();
3893 let ch = self.pop().to_int();
3894 self.sched = Some(crate::sched::SchedReq::Send { ch, val });
3895 self.halted = true;
3896 }
3897 Op::ChanRecv => {
3898 let ch = self.pop().to_int();
3899 self.sched = Some(crate::sched::SchedReq::Recv { ch });
3900 self.halted = true;
3901 }
3902 Op::ChanRecvOk => {
3903 let ch = self.pop().to_int();
3904 self.sched = Some(crate::sched::SchedReq::RecvOk { ch });
3905 self.halted = true;
3906 }
3907 Op::ChanClose => {
3908 let ch = self.pop().to_int();
3909 self.sched = Some(crate::sched::SchedReq::Close { ch });
3910 self.halted = true;
3911 }
3912 Op::CallDynamic(argc) => {
3913 let name_idx = self.pop().to_int() as u16;
3915 if let Some(entry_ip) = self.chunk.find_sub(name_idx) {
3916 self.frames.push(Frame {
3917 return_ip: self.ip,
3918 stack_base: self.stack.len() - *argc as usize,
3919 slots: Vec::new(),
3920 entry_ip: Some(entry_ip),
3921 });
3922 self.ip = entry_ip;
3923 } else {
3924 return ExecFlow::Ret(VMResult::Error(
3925 "call of a nil or unknown function value".to_string(),
3926 ));
3927 }
3928 }
3929 Op::Select(num_cases, has_default) => {
3930 let n = *num_cases as usize;
3931 let mut raw = Vec::with_capacity(n * 3);
3932 for _ in 0..n * 3 {
3933 raw.push(self.pop());
3934 }
3935 raw.reverse();
3936 let cases = raw
3937 .chunks_exact(3)
3938 .map(|c| crate::sched::SelectCase {
3939 ch: c[0].to_int(),
3940 recv: c[1].to_int() != 0,
3941 val: c[2].clone(),
3942 })
3943 .collect();
3944 self.sched = Some(crate::sched::SchedReq::Select {
3945 cases,
3946 has_default: *has_default != 0,
3947 });
3948 self.halted = true;
3949 }
3950 }
3951
3952 #[cfg(feature = "jit")]
3965 if recorder_was_armed && self.recorder.is_some() {
3966 let aborted = self.recorder.as_ref().map_or(false, |r| r.aborted);
3967 let close_ip = self
3970 .recorder
3971 .as_ref()
3972 .map(|r| r.close_anchor_ip)
3973 .unwrap_or(0);
3974 let was_jump = matches!(
3975 &ops[ip],
3976 Op::Jump(_)
3977 | Op::JumpIfTrue(_)
3978 | Op::JumpIfFalse(_)
3979 | Op::JumpIfTrueKeep(_)
3980 | Op::JumpIfFalseKeep(_)
3981 );
3982 let landed_at_anchor = self.ip == close_ip;
3983 if aborted || (was_jump && landed_at_anchor) {
3984 self.finalize_recorder();
3985 }
3986 }
3987 ExecFlow::Cont
3988 }
3989
3990 #[cfg(feature = "aot")]
4000 pub(crate) fn aot_exec_op(&mut self, ip: usize) -> i64 {
4001 let ops = &self.chunk.ops as *const Vec<Op>;
4004 let ops = unsafe { &*ops };
4005 self.ip = ip + 1;
4006 match self.exec_op(ops, ip, false) {
4007 ExecFlow::Ret(r) => {
4008 self.aot_result = Some(r);
4009 return -1;
4010 }
4011 ExecFlow::Cont => {}
4012 }
4013 if self.halted {
4014 return -1;
4015 }
4016 self.ip as i64
4017 }
4018
4019 #[cfg(feature = "aot")]
4023 pub(crate) fn aot_finish(&mut self) {
4024 if self.aot_result.is_none() {
4025 self.aot_result = Some(match self.stack.pop() {
4026 Some(v) => VMResult::Ok(v),
4027 None => VMResult::Halted,
4028 });
4029 }
4030 }
4031
4032 #[cfg(feature = "aot")]
4037 pub(crate) fn aot_set_int_result(&mut self, n: i64) {
4038 self.aot_result = Some(VMResult::Ok(Value::Int(n)));
4039 }
4040
4041 #[cfg(feature = "aot")]
4044 pub(crate) fn aot_set_float_result(&mut self, f: f64) {
4045 self.aot_result = Some(VMResult::Ok(Value::Float(f)));
4046 }
4047
4048 #[cfg(feature = "aot")]
4051 fn aot_alloc(&mut self, v: Value) -> i64 {
4052 if let Some(h) = self.aot_free.pop() {
4053 self.aot_arena[h as usize] = v;
4054 h as i64
4055 } else {
4056 let h = self.aot_arena.len() as i64;
4057 self.aot_arena.push(v);
4058 h
4059 }
4060 }
4061
4062 #[cfg(feature = "aot")]
4065 fn aot_take(&mut self, handle: i64) -> Value {
4066 match self.aot_arena.get_mut(handle as usize) {
4067 Some(slot) => {
4068 let v = std::mem::replace(slot, Value::Undef);
4069 self.aot_free.push(handle as u32);
4070 v
4071 }
4072 None => Value::Undef,
4073 }
4074 }
4075
4076 #[cfg(feature = "aot")]
4081 pub(crate) fn aot_box(&mut self) -> i64 {
4082 let v = self.stack.pop().unwrap_or(Value::Undef);
4083 self.aot_alloc(v)
4084 }
4085
4086 #[cfg(feature = "aot")]
4090 pub(crate) fn aot_unbox(&mut self, handle: i64) {
4091 let v = self.aot_take(handle);
4092 self.stack.push(v);
4093 }
4094
4095 #[cfg(feature = "aot")]
4098 pub(crate) fn aot_clone(&mut self, handle: i64) -> i64 {
4099 let v = self
4100 .aot_arena
4101 .get(handle as usize)
4102 .cloned()
4103 .unwrap_or(Value::Undef);
4104 self.aot_alloc(v)
4105 }
4106
4107 #[cfg(all(feature = "aot", test))]
4111 pub(crate) fn aot_arena_len(&self) -> usize {
4112 self.aot_arena.len()
4113 }
4114
4115 #[cfg(feature = "aot")]
4121 pub(crate) fn aot_truthy(&mut self, handle: i64) -> i64 {
4122 self.aot_take(handle).is_truthy() as i64
4123 }
4124
4125 #[cfg(feature = "aot")]
4128 pub(crate) fn aot_truthy_keep(&mut self, handle: i64) -> i64 {
4129 match self.aot_arena.get(handle as usize) {
4130 Some(v) => v.is_truthy() as i64,
4131 None => 0,
4132 }
4133 }
4134
4135 #[cfg(feature = "aot")]
4138 pub(crate) fn aot_free(&mut self, handle: i64) {
4139 if handle >= 0 && (handle as usize) < self.aot_arena.len() {
4140 self.aot_arena[handle as usize] = Value::Undef;
4141 self.aot_free.push(handle as u32);
4142 }
4143 }
4144
4145 #[cfg(feature = "aot")]
4148 pub(crate) fn aot_set_obj_result(&mut self, handle: i64) {
4149 let v = self.aot_take(handle);
4150 self.aot_result = Some(VMResult::Ok(v));
4151 }
4152
4153 #[cfg(feature = "aot")]
4157 pub(crate) fn aot_store_slot_obj(&mut self, idx: u32, handle: i64) {
4158 let v = self
4159 .aot_arena
4160 .get(handle as usize)
4161 .cloned()
4162 .unwrap_or(Value::Undef);
4163 self.set_slot(idx as u16, v);
4164 }
4165
4166 #[cfg(feature = "aot")]
4168 pub(crate) fn aot_store_global_obj(&mut self, idx: u32, handle: i64) {
4169 let v = self
4170 .aot_arena
4171 .get(handle as usize)
4172 .cloned()
4173 .unwrap_or(Value::Undef);
4174 self.set_var(idx as u16, v);
4175 }
4176
4177 #[cfg(feature = "aot")]
4181 pub(crate) fn aot_push_int(&mut self, n: i64) {
4182 self.stack.push(Value::Int(n));
4183 }
4184
4185 #[cfg(feature = "aot")]
4187 pub(crate) fn aot_push_float(&mut self, f: f64) {
4188 self.stack.push(Value::Float(f));
4189 }
4190
4191 #[cfg(feature = "aot")]
4193 pub(crate) fn aot_push_bool(&mut self, n: i64) {
4194 self.stack.push(Value::Bool(n != 0));
4195 }
4196
4197 #[cfg(feature = "aot")]
4201 pub(crate) fn aot_store_slot_int(&mut self, idx: u32, n: i64) {
4202 self.set_slot(idx as u16, Value::Int(n));
4203 }
4204
4205 #[cfg(feature = "aot")]
4207 pub(crate) fn aot_store_slot_float(&mut self, idx: u32, f: f64) {
4208 self.set_slot(idx as u16, Value::Float(f));
4209 }
4210
4211 #[cfg(feature = "aot")]
4213 pub(crate) fn aot_store_global_int(&mut self, idx: u32, n: i64) {
4214 self.set_var(idx as u16, Value::Int(n));
4215 }
4216
4217 #[cfg(feature = "aot")]
4219 pub(crate) fn aot_store_global_float(&mut self, idx: u32, f: f64) {
4220 self.set_var(idx as u16, Value::Float(f));
4221 }
4222
4223 #[cfg(feature = "aot")]
4228 pub(crate) fn aot_resume(&mut self, ip: u32) {
4229 self.ip = ip as usize;
4230 let r = self.run();
4231 self.aot_result = Some(r);
4232 }
4233
4234 #[cfg(feature = "aot")]
4238 pub(crate) fn aot_pop_float(&mut self) -> f64 {
4239 self.stack.pop().map(|v| v.to_float()).unwrap_or(0.0)
4240 }
4241
4242 #[cfg(feature = "aot")]
4245 pub(crate) fn aot_pop_int(&mut self) -> i64 {
4246 self.stack.pop().map(|v| v.to_int()).unwrap_or(0)
4247 }
4248
4249 #[cfg(feature = "aot")]
4251 pub(crate) fn aot_push_status(&mut self, n: i64) {
4252 self.stack.push(Value::Status(n as i32));
4253 }
4254
4255 #[cfg(feature = "aot")]
4257 pub(crate) fn aot_set_status_result(&mut self, n: i64) {
4258 self.aot_result = Some(VMResult::Ok(Value::Status(n as i32)));
4259 }
4260
4261 #[cfg(feature = "aot")]
4265 pub(crate) fn aot_set_error(&mut self, code: u32) {
4266 let msg = match code {
4267 0 => "division by zero attempted",
4268 1 => "division by zero attempted in `%'",
4269 2 => "lshift: negative values are not allowed",
4270 3 => "rshift: negative values are not allowed",
4271 4 => "compl: negative value is not allowed",
4272 _ => "aot: runtime error",
4273 };
4274 self.aot_result = Some(VMResult::Error(msg.to_string()));
4275 }
4276
4277 #[cfg(feature = "aot")]
4279 pub fn take_aot_result(&mut self) -> VMResult {
4280 self.aot_result.take().unwrap_or(VMResult::Halted)
4281 }
4282
4283 #[cfg(feature = "aot")]
4288 pub fn aot_load_slot_int(&mut self, idx: u32) -> i64 {
4289 if let Value::Int(n) = self.get_slot(idx as u16) {
4290 n
4291 } else {
4292 self.aot_guard_failed = true;
4293 0
4294 }
4295 }
4296
4297 #[cfg(feature = "aot")]
4301 pub fn aot_guard_taken(&mut self) -> bool {
4302 std::mem::take(&mut self.aot_guard_failed)
4303 }
4304
4305 fn dispatch_awk(&mut self, id: u16, payload: usize) {
4318 use crate::awk_builtins as ab;
4319 use crate::awk_host::AwkLvalue;
4320
4321 let mut host = match self.awk_host.take() {
4325 Some(h) => h,
4326 None => {
4327 self.dispatch_awk_stub(id, payload);
4328 return;
4329 }
4330 };
4331
4332 macro_rules! pop_n {
4334 ($n:expr) => {{
4335 let n = $n;
4336 let mut v: Vec<Value> = (0..n).map(|_| self.pop()).collect();
4337 v.reverse();
4338 v
4339 }};
4340 }
4341 let name_at = |vm: &Self, idx: usize| -> String {
4342 vm.chunk.names.get(idx).cloned().unwrap_or_default()
4343 };
4344
4345 match id {
4346 ab::AWK_FIELD_GET => {
4347 let i = self.pop().to_int();
4348 let v = host.field_get(i);
4349 self.push(v);
4350 }
4351 ab::AWK_FIELD_SET => {
4352 let i = self.pop().to_int();
4353 let v = self.pop();
4354 host.field_set(i, v);
4355 }
4356 ab::AWK_NF => {
4357 let n = host.nf();
4358 self.push(Value::Int(n));
4359 }
4360 ab::AWK_SET_RECORD => {
4361 let v = self.pop();
4362 host.set_record(v);
4363 }
4364 ab::AWK_SPECIAL_GET => {
4365 let name = name_at(self, payload);
4366 let v = host.special_get(&name);
4367 self.push(v);
4368 }
4369 ab::AWK_SPECIAL_SET => {
4370 let name = name_at(self, payload);
4371 let v = self.pop();
4372 host.special_set(&name, v);
4373 }
4374 ab::AWK_PRINT => {
4375 let args = pop_n!(payload);
4376 host.print(&args);
4377 }
4378 ab::AWK_PRINTF => {
4379 let mut args = pop_n!(payload);
4380 let fmt = if args.is_empty() {
4381 String::new()
4382 } else {
4383 args.remove(0).to_str()
4384 };
4385 host.printf(&fmt, &args);
4386 }
4387 ab::AWK_SPRINTF => {
4388 let mut args = pop_n!(payload);
4389 let fmt = if args.is_empty() {
4390 String::new()
4391 } else {
4392 args.remove(0).to_str()
4393 };
4394 let v = host.sprintf(&fmt, &args);
4395 self.push(v);
4396 }
4397 ab::AWK_GETLINE => {
4398 let operand = match payload {
4400 ab::getline_source::FILE
4401 | ab::getline_source::FILE_VAR
4402 | ab::getline_source::CMD
4403 | ab::getline_source::CMD_VAR => Some(self.pop().to_str()),
4404 _ => None,
4405 };
4406 let status = host.getline(payload, operand.as_deref(), None);
4407 self.push(Value::Int(status));
4408 }
4409 ab::AWK_LENGTH => {
4410 let arg = if payload == 0 { None } else { Some(self.pop()) };
4411 let n = host.length(arg.as_ref());
4412 self.push(Value::Int(n));
4413 }
4414 ab::AWK_SUBSTR => {
4415 let args = pop_n!(payload);
4416 let s = args.first().cloned().unwrap_or(Value::str(""));
4417 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
4418 let n = args.get(2).map(|v| v.to_int());
4419 let v = host.substr(&s, m, n);
4420 self.push(v);
4421 }
4422 ab::AWK_INDEX => {
4423 let t = self.pop();
4424 let s = self.pop();
4425 let r = host.index(&s, &t);
4426 self.push(Value::Int(r));
4427 }
4428 ab::AWK_SPLIT => {
4429 let args = pop_n!(payload);
4430 let s = args.first().cloned().unwrap_or(Value::str(""));
4431 let arr = args.get(1).map(|v| v.to_str()).unwrap_or_default();
4432 let fs = args.get(2);
4433 let n = host.split(&s, &arr, fs);
4434 self.push(Value::Int(n));
4435 }
4436 ab::AWK_SUB | ab::AWK_GSUB => {
4437 let args = pop_n!(payload);
4441 let re = args.first().cloned().unwrap_or(Value::str(""));
4442 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
4443 let target = args
4444 .get(2)
4445 .map(|v| AwkLvalue::Var(v.to_str()))
4446 .unwrap_or(AwkLvalue::Field(0));
4447 let n = if id == ab::AWK_SUB {
4448 host.sub(&re, &repl, &target)
4449 } else {
4450 host.gsub(&re, &repl, &target)
4451 };
4452 self.push(Value::Int(n));
4453 }
4454 ab::AWK_MATCH => {
4455 let re = self.pop();
4456 let s = self.pop();
4457 let r = host.match_re(&s, &re);
4458 self.push(Value::Int(r));
4459 }
4460 ab::AWK_GENSUB => {
4461 let args = pop_n!(payload);
4463 let re = args.first().cloned().unwrap_or(Value::str(""));
4464 let repl = args.get(1).cloned().unwrap_or(Value::str(""));
4465 let how = args.get(2).cloned().unwrap_or(Value::str("g"));
4466 let target = args.get(3);
4467 let v = host.gensub(&re, &repl, &how, target);
4468 self.push(v);
4469 }
4470 ab::AWK_TOLOWER => {
4471 let s = self.pop();
4472 let v = host.tolower(&s);
4473 self.push(v);
4474 }
4475 ab::AWK_TOUPPER => {
4476 let s = self.pop();
4477 let v = host.toupper(&s);
4478 self.push(v);
4479 }
4480 ab::AWK_INT => {
4481 let x = self.pop();
4482 let v = host.int(&x);
4483 self.push(v);
4484 }
4485 ab::AWK_SQRT => {
4486 let x = self.pop();
4487 let v = host.sqrt(&x);
4488 self.push(v);
4489 }
4490 ab::AWK_SIN => {
4491 let x = self.pop();
4492 let v = host.sin(&x);
4493 self.push(v);
4494 }
4495 ab::AWK_COS => {
4496 let x = self.pop();
4497 let v = host.cos(&x);
4498 self.push(v);
4499 }
4500 ab::AWK_EXP => {
4501 let x = self.pop();
4502 let v = host.exp(&x);
4503 self.push(v);
4504 }
4505 ab::AWK_LOG => {
4506 let x = self.pop();
4507 let v = host.log(&x);
4508 self.push(v);
4509 }
4510 ab::AWK_ATAN2 => {
4511 let x = self.pop();
4512 let y = self.pop();
4513 let v = host.atan2(&y, &x);
4514 self.push(v);
4515 }
4516 ab::AWK_AND => {
4517 let args = pop_n!(payload);
4518 let v = host.and(&args);
4519 self.push(v);
4520 }
4521 ab::AWK_OR => {
4522 let args = pop_n!(payload);
4523 let v = host.or(&args);
4524 self.push(v);
4525 }
4526 ab::AWK_XOR => {
4527 let args = pop_n!(payload);
4528 let v = host.xor(&args);
4529 self.push(v);
4530 }
4531 ab::AWK_COMPL => {
4532 let v = self.pop();
4533 let r = host.compl(&v);
4534 self.push(r);
4535 }
4536 ab::AWK_LSHIFT => {
4537 let n = self.pop();
4538 let v = self.pop();
4539 let r = host.lshift(&v, &n);
4540 self.push(r);
4541 }
4542 ab::AWK_RSHIFT => {
4543 let n = self.pop();
4544 let v = self.pop();
4545 let r = host.rshift(&v, &n);
4546 self.push(r);
4547 }
4548 ab::AWK_STRTONUM => {
4549 let s = self.pop();
4550 let r = host.strtonum(&s);
4551 self.push(r);
4552 }
4553 ab::AWK_SYSTIME => {
4554 let r = host.systime();
4555 self.push(r);
4556 }
4557 ab::AWK_RAND => {
4558 let r = crate::awk_host::awk_rand(&mut self.awk_rand_seed);
4559 self.push(Value::Float(r));
4560 }
4561 ab::AWK_SRAND => {
4562 let n = if payload >= 1 {
4563 Some(self.pop().to_float() as u32 as u64)
4564 } else {
4565 None
4566 };
4567 let r = crate::awk_host::awk_srand(&mut self.awk_rand_seed, n);
4568 self.push(Value::Float(r));
4569 }
4570 ab::AWK_STRFTIME => {
4571 let args = pop_n!(payload);
4572 let r = host.strftime(&args);
4573 self.push(r);
4574 }
4575 ab::AWK_MKTIME => {
4576 let args = pop_n!(payload);
4577 let r = host.mktime(&args);
4578 self.push(r);
4579 }
4580 ab::AWK_ORD => {
4581 let a = self.pop();
4582 let r = host.ord(&a);
4583 self.push(r);
4584 }
4585 ab::AWK_CHR => {
4586 let a = self.pop();
4587 let r = host.chr(&a);
4588 self.push(r);
4589 }
4590 ab::AWK_MKBOOL => {
4591 let a = self.pop();
4592 let r = host.mkbool(&a);
4593 self.push(r);
4594 }
4595 ab::AWK_INTDIV => {
4596 let b = self.pop();
4597 let a = self.pop();
4598 let r = host.intdiv(&a, &b);
4599 self.push(r);
4600 }
4601 ab::AWK_INTDIV0 => {
4602 let b = self.pop();
4603 let a = self.pop();
4604 let r = host.intdiv0(&a, &b);
4605 self.push(r);
4606 }
4607 ab::AWK_ARRAY_GET => {
4608 let name = name_at(self, payload);
4609 let key = self.pop();
4610 let v = host.array_get(&name, &key);
4611 self.push(v);
4612 }
4613 ab::AWK_ARRAY_SET => {
4614 let name = name_at(self, payload);
4615 let key = self.pop();
4616 let v = self.pop();
4617 host.array_set(&name, &key, v);
4618 }
4619 ab::AWK_ARRAY_EXISTS => {
4620 let name = name_at(self, payload);
4621 let key = self.pop();
4622 let b = host.array_exists(&name, &key);
4623 self.push(Value::Bool(b));
4624 }
4625 ab::AWK_ARRAY_DELETE => {
4626 let name = name_at(self, payload);
4627 let key = self.pop();
4628 host.array_delete(&name, &key);
4629 }
4630 ab::AWK_ARRAY_CLEAR => {
4631 let name = name_at(self, payload);
4632 host.array_clear(&name);
4633 }
4634 ab::AWK_ARRAY_LEN => {
4635 let name = name_at(self, payload);
4636 let n = host.array_len(&name);
4637 self.push(Value::Int(n));
4638 }
4639 _ => self.push(Value::Undef),
4642 }
4643
4644 self.awk_host = Some(host);
4645 }
4646
4647 fn dispatch_awk_stub(&mut self, id: u16, payload: usize) {
4651 use crate::awk_builtins as ab;
4652 use crate::awk_host::{
4653 awk_canon_nan, awk_chr, awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_index,
4654 awk_int, awk_intdiv, awk_intdiv0, awk_length, awk_lshift, awk_mkbool, awk_mktime,
4655 awk_ord, awk_rand, awk_rshift, awk_srand, awk_strftime, awk_strtonum, awk_substr,
4656 awk_systime, awk_tolower, awk_toupper,
4657 };
4658 match id {
4659 ab::AWK_FIELD_GET => {
4661 self.pop();
4662 self.push(Value::str(""));
4663 }
4664 ab::AWK_LENGTH if payload > 0 => {
4668 let s = self.pop();
4669 self.push(Value::Int(awk_length(Some(&s))));
4670 }
4671 ab::AWK_NF | ab::AWK_LENGTH | ab::AWK_ARRAY_LEN => {
4672 self.push(Value::Int(0));
4673 }
4674 ab::AWK_SPECIAL_GET => self.push(Value::Undef),
4675 ab::AWK_SPRINTF => {
4676 for _ in 0..payload {
4677 self.pop();
4678 }
4679 self.push(Value::str(""));
4680 }
4681 ab::AWK_SUBSTR => {
4685 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4686 args.reverse();
4687 let s = args.first().cloned().unwrap_or(Value::str(""));
4688 let m = args.get(1).map(|v| v.to_int()).unwrap_or(1);
4689 let n = args.get(2).map(|v| v.to_int());
4690 self.push(awk_substr(&s, m, n));
4691 }
4692 ab::AWK_TOLOWER => {
4693 let s = self.pop();
4694 self.push(awk_tolower(&s));
4695 }
4696 ab::AWK_TOUPPER => {
4697 let s = self.pop();
4698 self.push(awk_toupper(&s));
4699 }
4700 ab::AWK_INT => {
4703 let x = self.pop();
4704 self.push(awk_int(&x));
4705 }
4706 ab::AWK_SQRT => {
4707 let x = self.pop();
4708 self.push(Value::Float(x.to_float().sqrt()));
4709 }
4710 ab::AWK_SIN => {
4711 let x = self.pop();
4712 self.push(Value::Float(awk_canon_nan(x.to_float().sin())));
4713 }
4714 ab::AWK_COS => {
4715 let x = self.pop();
4716 self.push(Value::Float(awk_canon_nan(x.to_float().cos())));
4717 }
4718 ab::AWK_EXP => {
4719 let x = self.pop();
4720 self.push(Value::Float(awk_canon_nan(x.to_float().exp())));
4721 }
4722 ab::AWK_LOG => {
4723 let x = self.pop();
4724 self.push(Value::Float(x.to_float().ln()));
4725 }
4726 ab::AWK_ATAN2 => {
4727 let x = self.pop();
4728 let y = self.pop();
4729 self.push(Value::Float(awk_canon_nan(
4730 y.to_float().atan2(x.to_float()),
4731 )));
4732 }
4733 ab::AWK_AND => {
4735 let args: Vec<Value> = {
4736 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4737 v.reverse();
4738 v
4739 };
4740 self.push(Value::Int(awk_fold_and(&args)));
4741 }
4742 ab::AWK_OR => {
4743 let args: Vec<Value> = {
4744 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4745 v.reverse();
4746 v
4747 };
4748 self.push(Value::Int(awk_fold_or(&args)));
4749 }
4750 ab::AWK_XOR => {
4751 let args: Vec<Value> = {
4752 let mut v: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4753 v.reverse();
4754 v
4755 };
4756 self.push(Value::Int(awk_fold_xor(&args)));
4757 }
4758 ab::AWK_COMPL => {
4759 let v = self.pop();
4760 self.push(Value::Int(awk_compl(&v)));
4761 }
4762 ab::AWK_LSHIFT => {
4763 let n = self.pop();
4764 let v = self.pop();
4765 self.push(Value::Int(awk_lshift(&v, &n)));
4766 }
4767 ab::AWK_RSHIFT => {
4768 let n = self.pop();
4769 let v = self.pop();
4770 self.push(Value::Int(awk_rshift(&v, &n)));
4771 }
4772 ab::AWK_STRTONUM => {
4773 let s = self.pop();
4774 self.push(Value::Float(awk_strtonum(&s.to_str())));
4775 }
4776 ab::AWK_SYSTIME => {
4777 self.push(Value::Float(awk_systime()));
4778 }
4779 ab::AWK_RAND => {
4780 let r = awk_rand(&mut self.awk_rand_seed);
4781 self.push(Value::Float(r));
4782 }
4783 ab::AWK_SRAND => {
4784 let n = if payload >= 1 {
4785 Some(self.pop().to_float() as u32 as u64)
4786 } else {
4787 None
4788 };
4789 let r = awk_srand(&mut self.awk_rand_seed, n);
4790 self.push(Value::Float(r));
4791 }
4792 ab::AWK_STRFTIME => {
4793 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4794 args.reverse();
4795 self.push(awk_strftime(&args));
4796 }
4797 ab::AWK_MKTIME => {
4798 let mut args: Vec<Value> = (0..payload).map(|_| self.pop()).collect();
4799 args.reverse();
4800 self.push(awk_mktime(&args));
4801 }
4802 ab::AWK_ORD => {
4803 let a = self.pop();
4804 self.push(awk_ord(&a));
4805 }
4806 ab::AWK_CHR => {
4807 let a = self.pop();
4808 self.push(awk_chr(&a));
4809 }
4810 ab::AWK_MKBOOL => {
4811 let a = self.pop();
4812 self.push(awk_mkbool(&a));
4813 }
4814 ab::AWK_INTDIV => {
4815 let b = self.pop();
4816 let a = self.pop();
4817 self.push(awk_intdiv(&a, &b));
4818 }
4819 ab::AWK_INTDIV0 => {
4820 let b = self.pop();
4821 let a = self.pop();
4822 self.push(awk_intdiv0(&a, &b));
4823 }
4824 ab::AWK_INDEX => {
4825 let t = self.pop();
4826 let s = self.pop();
4827 self.push(Value::Int(awk_index(&s, &t)));
4828 }
4829 ab::AWK_MATCH => {
4830 self.pop();
4831 self.pop();
4832 self.push(Value::Int(0));
4833 }
4834 ab::AWK_SPLIT | ab::AWK_SUB | ab::AWK_GSUB => {
4835 for _ in 0..payload {
4836 self.pop();
4837 }
4838 self.push(Value::Int(0));
4839 }
4840 ab::AWK_GENSUB => {
4841 for _ in 0..payload {
4842 self.pop();
4843 }
4844 self.push(Value::str(""));
4845 }
4846 ab::AWK_GETLINE => {
4847 if matches!(
4848 payload,
4849 ab::getline_source::FILE
4850 | ab::getline_source::FILE_VAR
4851 | ab::getline_source::CMD
4852 | ab::getline_source::CMD_VAR
4853 ) {
4854 self.pop();
4855 }
4856 self.push(Value::Int(0));
4857 }
4858 ab::AWK_ARRAY_GET => {
4859 self.pop();
4860 self.push(Value::str(""));
4861 }
4862 ab::AWK_ARRAY_EXISTS => {
4863 self.pop();
4864 self.push(Value::Bool(false));
4865 }
4866 ab::AWK_FIELD_SET | ab::AWK_ARRAY_SET => {
4868 self.pop();
4869 self.pop();
4870 }
4871 ab::AWK_SET_RECORD | ab::AWK_SPECIAL_SET | ab::AWK_ARRAY_DELETE => {
4872 self.pop();
4873 }
4874 ab::AWK_PRINT | ab::AWK_PRINTF => {
4875 for _ in 0..payload {
4876 self.pop();
4877 }
4878 }
4879 ab::AWK_ARRAY_CLEAR => {}
4880 _ => {}
4881 }
4882 }
4883
4884 fn get_var(&self, idx: u16) -> Value {
4885 self.globals
4886 .get(idx as usize)
4887 .cloned()
4888 .unwrap_or(Value::Undef)
4889 }
4890
4891 fn set_var(&mut self, idx: u16, val: Value) {
4892 let idx = idx as usize;
4893 if idx >= self.globals.len() {
4894 self.globals.resize(idx + 1, Value::Undef);
4895 }
4896 self.globals[idx] = val;
4897 }
4898
4899 pub fn get_slot(&self, slot: u16) -> Value {
4906 self.frames
4907 .last()
4908 .and_then(|f| f.slots.get(slot as usize))
4909 .cloned()
4910 .unwrap_or(Value::Undef)
4911 }
4912
4913 pub fn set_slot(&mut self, slot: u16, val: Value) {
4919 if let Some(frame) = self.frames.last_mut() {
4920 let idx = slot as usize;
4921 if idx >= frame.slots.len() {
4922 frame.slots.resize(idx + 1, Value::Undef);
4923 }
4924 frame.slots[idx] = val;
4925 }
4926 }
4927}
4928
4929pub struct VMPool {
4958 pool: Vec<VM>,
4959}
4960
4961impl VMPool {
4962 pub fn new() -> Self {
4964 Self { pool: Vec::new() }
4965 }
4966
4967 pub fn with_capacity(cap: usize) -> Self {
4969 Self {
4970 pool: Vec::with_capacity(cap),
4971 }
4972 }
4973
4974 pub fn acquire(&mut self, chunk: Chunk) -> VM {
4979 if let Some(mut vm) = self.pool.pop() {
4980 vm.reset(chunk);
4981 vm
4982 } else {
4983 VM::new(chunk)
4984 }
4985 }
4986
4987 pub fn release(&mut self, vm: VM) {
4990 self.pool.push(vm);
4991 }
4992
4993 pub fn with<F, T>(&mut self, chunk: Chunk, f: F) -> T
4996 where
4997 F: FnOnce(&mut VM) -> T,
4998 {
4999 let mut vm = self.acquire(chunk);
5000 let r = f(&mut vm);
5001 self.release(vm);
5002 r
5003 }
5004
5005 pub fn len(&self) -> usize {
5008 self.pool.len()
5009 }
5010
5011 pub fn is_empty(&self) -> bool {
5013 self.pool.is_empty()
5014 }
5015}
5016
5017impl Default for VMPool {
5018 fn default() -> Self {
5019 Self::new()
5020 }
5021}
5022
5023#[cfg(test)]
5024mod tests {
5025 use super::*;
5026 use crate::chunk::ChunkBuilder;
5027
5028 #[test]
5029 fn test_arithmetic() {
5030 let mut b = ChunkBuilder::new();
5031 b.emit(Op::LoadInt(10), 1);
5032 b.emit(Op::LoadInt(32), 1);
5033 b.emit(Op::Add, 1);
5034 let mut vm = VM::new(b.build());
5035 match vm.run() {
5036 VMResult::Ok(Value::Int(42)) => {}
5037 other => panic!("expected Int(42), got {:?}", other),
5038 }
5039 }
5040
5041 #[test]
5042 fn test_jump() {
5043 let mut b = ChunkBuilder::new();
5044 b.emit(Op::LoadInt(1), 1);
5045 b.emit(Op::Jump(3), 1);
5046 b.emit(Op::LoadInt(999), 1); b.emit(Op::LoadInt(2), 1);
5049 b.emit(Op::Add, 1);
5050 let mut vm = VM::new(b.build());
5051 match vm.run() {
5052 VMResult::Ok(Value::Int(3)) => {}
5053 other => panic!("expected Int(3), got {:?}", other),
5054 }
5055 }
5056
5057 #[test]
5058 fn test_fused_sum_loop() {
5059 let mut b = ChunkBuilder::new();
5061 b.emit(Op::PushFrame, 1);
5062 b.emit(Op::LoadInt(0), 1);
5063 b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
5065 b.emit(Op::SetSlot(1), 1); b.emit(Op::AccumSumLoop(0, 1, 100), 1);
5067 b.emit(Op::GetSlot(0), 1);
5068
5069 let mut vm = VM::new(b.build());
5070 match vm.run() {
5071 VMResult::Ok(Value::Int(4950)) => {}
5072 other => panic!("expected Int(4950), got {:?}", other),
5073 }
5074 }
5075
5076 #[test]
5077 fn test_call_dynamic() {
5078 let mut b = ChunkBuilder::new();
5079 let dbl = b.add_name("double");
5080 b.emit(Op::LoadInt(21), 1);
5082 b.emit(Op::LoadInt(dbl as i64), 1);
5083 b.emit(Op::CallDynamic(1), 1);
5084 let end = b.emit(Op::Jump(0), 1);
5085 let ip = b.current_pos();
5086 b.add_sub_entry(dbl, ip);
5087 b.emit(Op::LoadInt(2), 2);
5088 b.emit(Op::Mul, 2);
5089 b.emit(Op::ReturnValue, 2);
5090 b.patch_jump(end, b.current_pos());
5091 let mut vm = VM::new(b.build());
5092 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(42))));
5093 }
5094
5095 #[test]
5096 fn test_function_call() {
5097 let mut b = ChunkBuilder::new();
5098 let double_name = b.add_name("double");
5099
5100 b.emit(Op::LoadInt(21), 1);
5102 b.emit(Op::Call(double_name, 1), 1);
5103 let end_jump = b.emit(Op::Jump(0), 1); let double_ip = b.current_pos();
5107 b.add_sub_entry(double_name, double_ip);
5108 b.emit(Op::LoadInt(2), 2);
5109 b.emit(Op::Mul, 2);
5110 b.emit(Op::ReturnValue, 2);
5111
5112 b.patch_jump(end_jump, b.current_pos());
5113
5114 let mut vm = VM::new(b.build());
5115 match vm.run() {
5116 VMResult::Ok(Value::Int(42)) => {}
5117 other => panic!("expected Int(42), got {:?}", other),
5118 }
5119 }
5120
5121 #[test]
5122 fn test_builtin_cache() {
5123 let mut b = ChunkBuilder::new();
5124 b.emit(Op::LoadInt(10), 1);
5125 b.emit(Op::CallBuiltin(0, 1), 1);
5126 let mut vm = VM::new(b.build());
5127 vm.register_builtin(0, |vm, _argc| {
5128 let val = vm.pop();
5129 Value::Int(val.to_int() * 2)
5130 });
5131 match vm.run() {
5132 VMResult::Ok(Value::Int(20)) => {}
5133 other => panic!("expected Int(20), got {:?}", other),
5134 }
5135 }
5136
5137 fn run_one(ops: Vec<Op>) -> VMResult {
5140 let mut b = ChunkBuilder::new();
5141 for op in ops {
5142 b.emit(op, 1);
5143 }
5144 VM::new(b.build()).run()
5145 }
5146
5147 fn expect_int(ops: Vec<Op>, want: i64) {
5148 match run_one(ops) {
5149 VMResult::Ok(Value::Int(n)) => assert_eq!(n, want),
5150 other => panic!("expected Int({}), got {:?}", want, other),
5151 }
5152 }
5153
5154 fn expect_bool(ops: Vec<Op>, want: bool) {
5155 match run_one(ops) {
5156 VMResult::Ok(Value::Bool(b)) => assert_eq!(b, want),
5157 other => panic!("expected Bool({}), got {:?}", want, other),
5158 }
5159 }
5160
5161 #[test]
5164 fn arithmetic_sub_mul_div_mod() {
5165 expect_int(vec![Op::LoadInt(20), Op::LoadInt(8), Op::Sub], 12);
5166 expect_int(vec![Op::LoadInt(6), Op::LoadInt(7), Op::Mul], 42);
5167 expect_int(vec![Op::LoadInt(20), Op::LoadInt(3), Op::Mod], 2);
5168 match run_one(vec![Op::LoadInt(20), Op::LoadInt(5), Op::Div]) {
5170 VMResult::Ok(Value::Float(f)) => assert!((f - 4.0).abs() < 1e-9),
5171 VMResult::Ok(Value::Int(4)) => {} other => panic!("got {:?}", other),
5173 }
5174 }
5175
5176 #[test]
5177 fn arithmetic_negate_and_inc_dec() {
5178 expect_int(vec![Op::LoadInt(5), Op::Negate], -5);
5179 expect_int(vec![Op::LoadInt(5), Op::Inc], 6);
5180 expect_int(vec![Op::LoadInt(5), Op::Dec], 4);
5181 }
5182
5183 #[test]
5184 fn arithmetic_negate_preserves_float_zero_kind_and_sign() {
5185 match run_one(vec![Op::LoadFloat(-0.0), Op::Negate]) {
5188 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), 0.0f64.to_bits()),
5189 other => panic!("expected Float(0.0), got {:?}", other),
5190 }
5191 match run_one(vec![Op::LoadFloat(0.0), Op::Negate]) {
5192 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), (-0.0f64).to_bits()),
5193 other => panic!("expected Float(-0.0), got {:?}", other),
5194 }
5195 match run_one(vec![Op::LoadFloat(-0.0), Op::LoadInt(0), Op::Sub]) {
5196 VMResult::Ok(Value::Float(f)) => assert_eq!(f.to_bits(), (-0.0f64).to_bits()),
5197 other => panic!("expected Float(-0.0), got {:?}", other),
5198 }
5199 }
5200
5201 #[test]
5202 fn arithmetic_pow_returns_float() {
5203 match run_one(vec![Op::LoadInt(3), Op::LoadInt(4), Op::Pow]) {
5204 VMResult::Ok(Value::Float(f)) => assert!((f - 81.0).abs() < 1e-9),
5205 VMResult::Ok(Value::Int(81)) => {} other => panic!("got {:?}", other),
5207 }
5208 }
5209
5210 #[test]
5213 fn num_comparisons_produce_booleans() {
5214 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(1), Op::NumEq], true);
5215 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumEq], false);
5216 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumLt], true);
5217 expect_bool(vec![Op::LoadInt(1), Op::LoadInt(2), Op::NumGt], false);
5218 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumLe], true);
5219 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumGe], true);
5220 expect_bool(vec![Op::LoadInt(2), Op::LoadInt(2), Op::NumNe], false);
5221 }
5222
5223 #[test]
5224 fn spaceship_returns_neg_zero_pos() {
5225 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Spaceship], -1);
5226 expect_int(vec![Op::LoadInt(2), Op::LoadInt(2), Op::Spaceship], 0);
5227 expect_int(vec![Op::LoadInt(3), Op::LoadInt(2), Op::Spaceship], 1);
5228 }
5229
5230 #[test]
5231 fn string_comparisons() {
5232 let mut b = ChunkBuilder::new();
5233 let a = b.add_constant(Value::str("alpha"));
5234 let z = b.add_constant(Value::str("beta"));
5235 b.emit(Op::LoadConst(a), 1);
5236 b.emit(Op::LoadConst(z), 1);
5237 b.emit(Op::StrLt, 1);
5238 let mut vm = VM::new(b.build());
5239 assert!(matches!(vm.run(), VMResult::Ok(Value::Bool(true))));
5240 }
5241
5242 #[test]
5243 fn string_eq_and_ne() {
5244 let mut b = ChunkBuilder::new();
5245 let s1 = b.add_constant(Value::str("hi"));
5246 let s2 = b.add_constant(Value::str("hi"));
5247 b.emit(Op::LoadConst(s1), 1);
5248 b.emit(Op::LoadConst(s2), 1);
5249 b.emit(Op::StrEq, 1);
5250 assert!(matches!(
5251 VM::new(b.build()).run(),
5252 VMResult::Ok(Value::Bool(true))
5253 ));
5254 }
5255
5256 #[test]
5259 fn pop_discards_top() {
5260 expect_int(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Pop], 1);
5262 }
5263
5264 #[test]
5265 fn dup_duplicates_top() {
5266 expect_int(vec![Op::LoadInt(5), Op::Dup, Op::Add], 10);
5268 }
5269
5270 #[test]
5271 fn swap_exchanges_top_two() {
5272 expect_int(vec![Op::LoadInt(10), Op::LoadInt(3), Op::Swap, Op::Sub], -7);
5276 }
5277
5278 #[test]
5279 fn dup2_duplicates_top_two_values() {
5280 expect_int(
5282 vec![Op::LoadInt(3), Op::LoadInt(4), Op::Dup2, Op::Add, Op::Add],
5283 11,
5284 );
5285 }
5286
5287 #[test]
5290 fn log_not_inverts_truthiness() {
5291 expect_bool(vec![Op::LoadInt(0), Op::LogNot], true);
5292 expect_bool(vec![Op::LoadInt(1), Op::LogNot], false);
5293 expect_bool(vec![Op::LoadTrue, Op::LogNot], false);
5294 expect_bool(vec![Op::LoadFalse, Op::LogNot], true);
5295 }
5296
5297 #[test]
5298 fn bitwise_ops() {
5299 expect_int(
5300 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitAnd],
5301 0b1000,
5302 );
5303 expect_int(
5304 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitOr],
5305 0b1110,
5306 );
5307 expect_int(
5308 vec![Op::LoadInt(0b1100), Op::LoadInt(0b1010), Op::BitXor],
5309 0b0110,
5310 );
5311 expect_int(vec![Op::LoadInt(1), Op::LoadInt(4), Op::Shl], 16);
5312 expect_int(vec![Op::LoadInt(64), Op::LoadInt(2), Op::Shr], 16);
5313 }
5314
5315 #[test]
5316 fn bit_not_inverts_bits() {
5317 expect_int(vec![Op::LoadInt(0), Op::BitNot], -1);
5318 }
5319
5320 #[test]
5323 fn concat_joins_strings() {
5324 let mut b = ChunkBuilder::new();
5325 let h = b.add_constant(Value::str("hello "));
5326 let w = b.add_constant(Value::str("world"));
5327 b.emit(Op::LoadConst(h), 1);
5328 b.emit(Op::LoadConst(w), 1);
5329 b.emit(Op::Concat, 1);
5330 match VM::new(b.build()).run() {
5331 VMResult::Ok(v) => assert_eq!(v.to_str(), "hello world"),
5332 other => panic!("got {:?}", other),
5333 }
5334 }
5335
5336 #[test]
5337 fn string_repeat_op() {
5338 let mut b = ChunkBuilder::new();
5339 let s = b.add_constant(Value::str("ab"));
5340 b.emit(Op::LoadConst(s), 1);
5341 b.emit(Op::LoadInt(3), 1);
5342 b.emit(Op::StringRepeat, 1);
5343 match VM::new(b.build()).run() {
5344 VMResult::Ok(v) => assert_eq!(v.to_str(), "ababab"),
5345 other => panic!("got {:?}", other),
5346 }
5347 }
5348
5349 #[test]
5350 fn string_len_returns_int() {
5351 let mut b = ChunkBuilder::new();
5352 let s = b.add_constant(Value::str("abcd"));
5353 b.emit(Op::LoadConst(s), 1);
5354 b.emit(Op::StringLen, 1);
5355 match VM::new(b.build()).run() {
5356 VMResult::Ok(Value::Int(4)) => {}
5357 other => panic!("got {:?}", other),
5358 }
5359 }
5360
5361 #[test]
5364 fn load_true_false_undef() {
5365 assert!(matches!(
5366 run_one(vec![Op::LoadTrue]),
5367 VMResult::Ok(Value::Bool(true))
5368 ));
5369 assert!(matches!(
5370 run_one(vec![Op::LoadFalse]),
5371 VMResult::Ok(Value::Bool(false))
5372 ));
5373 assert!(matches!(
5374 run_one(vec![Op::LoadUndef]),
5375 VMResult::Ok(Value::Undef)
5376 ));
5377 }
5378
5379 #[test]
5380 fn load_const_string() {
5381 let mut b = ChunkBuilder::new();
5382 let c = b.add_constant(Value::str("xyz"));
5383 b.emit(Op::LoadConst(c), 1);
5384 match VM::new(b.build()).run() {
5385 VMResult::Ok(v) => assert_eq!(v.to_str(), "xyz"),
5386 other => panic!("got {:?}", other),
5387 }
5388 }
5389
5390 #[test]
5393 fn jump_if_true_taken() {
5394 let mut b = ChunkBuilder::new();
5396 b.emit(Op::LoadTrue, 1);
5397 let j = b.emit(Op::JumpIfTrue(0), 1);
5398 b.emit(Op::LoadInt(0), 1);
5399 b.patch_jump(j, b.current_pos());
5400 b.emit(Op::LoadInt(1), 1);
5401 assert!(matches!(
5402 VM::new(b.build()).run(),
5403 VMResult::Ok(Value::Int(1))
5404 ));
5405 }
5406
5407 #[test]
5408 fn jump_if_false_not_taken_for_true() {
5409 let mut b = ChunkBuilder::new();
5410 b.emit(Op::LoadTrue, 1);
5411 let j = b.emit(Op::JumpIfFalse(0), 1);
5412 b.emit(Op::LoadInt(7), 1); b.patch_jump(j, b.current_pos());
5414 match VM::new(b.build()).run() {
5415 VMResult::Ok(Value::Int(7)) => {}
5416 other => panic!("got {:?}", other),
5417 }
5418 }
5419
5420 #[test]
5423 fn push_pop_frame_with_slots() {
5424 let mut b = ChunkBuilder::new();
5428 b.emit(Op::PushFrame, 1);
5429 b.emit(Op::LoadInt(99), 1);
5430 b.emit(Op::SetSlot(0), 1);
5431 b.emit(Op::GetSlot(0), 1);
5432 match VM::new(b.build()).run() {
5433 VMResult::Ok(Value::Int(99)) => {}
5434 other => panic!("got {:?}", other),
5435 }
5436 }
5437
5438 #[test]
5441 fn vm_push_pop_peek_round_trip() {
5442 let chunk = ChunkBuilder::new().build();
5443 let mut vm = VM::new(chunk);
5444 vm.push(Value::Int(1));
5445 vm.push(Value::Int(2));
5446 assert_eq!(*vm.peek(), Value::Int(2));
5447 assert_eq!(vm.pop(), Value::Int(2));
5448 assert_eq!(vm.pop(), Value::Int(1));
5449 }
5450
5451 #[test]
5454 fn register_builtin_overwrites_existing_handler() {
5455 let mut b = ChunkBuilder::new();
5456 b.emit(Op::LoadInt(1), 1);
5457 b.emit(Op::CallBuiltin(0, 1), 1);
5458 let mut vm = VM::new(b.build());
5459 vm.register_builtin(0, |vm, _| {
5460 vm.pop();
5461 Value::Int(111)
5462 });
5463 vm.register_builtin(0, |vm, _| {
5465 vm.pop();
5466 Value::Int(222)
5467 });
5468 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(222))));
5469 }
5470
5471 #[test]
5472 fn run_builtin_by_name_dispatches_and_passes_args() {
5473 let id = crate::shell_builtins::builtin_id("true").expect("`true` is a builtin");
5476 let mut vm = VM::new(ChunkBuilder::new().build());
5477 vm.register_builtin(id, |vm, argc| {
5478 let mut got = Vec::new();
5480 for _ in 0..argc {
5481 got.push(vm.pop().to_str());
5482 }
5483 got.reverse();
5484 assert_eq!(got, vec!["x".to_string(), "y".to_string()]);
5485 Value::Int(7)
5486 });
5487 let out = vm.run_builtin_by_name("true", &["x".to_string(), "y".to_string()]);
5488 assert!(matches!(out, Some(Value::Int(7))));
5489 assert!(vm
5491 .run_builtin_by_name("definitely_not_a_builtin_xyz", &[])
5492 .is_none());
5493 }
5494
5495 #[test]
5496 fn register_builtin_grows_table_to_high_id() {
5497 let chunk = ChunkBuilder::new().build();
5499 let mut vm = VM::new(chunk);
5500 vm.register_builtin(500, |_, _| Value::Int(0));
5501 vm.register_builtin(1, |_, _| Value::Int(0));
5503 }
5504
5505 #[test]
5508 fn reset_clears_state_and_runs_new_chunk() {
5509 let mut b1 = ChunkBuilder::new();
5510 b1.emit(Op::LoadInt(1), 1);
5511 let mut vm = VM::new(b1.build());
5512 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(1))));
5513
5514 let mut b2 = ChunkBuilder::new();
5515 b2.emit(Op::LoadInt(2), 1);
5516 b2.emit(Op::LoadInt(3), 1);
5517 b2.emit(Op::Add, 1);
5518 vm.reset(b2.build());
5519 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(5))));
5520 }
5521
5522 #[test]
5525 fn extension_handler_invoked_with_payload() {
5526 use std::sync::{Arc, Mutex};
5527 let captured: Arc<Mutex<Option<(u16, u8)>>> = Arc::new(Mutex::new(None));
5528 let captured_cl = Arc::clone(&captured);
5529
5530 let mut b = ChunkBuilder::new();
5531 b.emit(Op::Extended(7, 42), 1);
5532 let mut vm = VM::new(b.build());
5533 vm.set_extension_handler(Box::new(move |vm, id, arg| {
5534 *captured_cl.lock().unwrap() = Some((id, arg));
5535 vm.push(Value::Int(123));
5536 }));
5537 match vm.run() {
5538 VMResult::Ok(Value::Int(123)) => {}
5539 other => panic!("got {:?}", other),
5540 }
5541 assert_eq!(*captured.lock().unwrap(), Some((7, 42)));
5542 }
5543
5544 #[test]
5545 fn extension_wide_handler_invoked_with_payload() {
5546 use std::sync::{Arc, Mutex};
5547 let captured: Arc<Mutex<Option<(u16, usize)>>> = Arc::new(Mutex::new(None));
5548 let captured_cl = Arc::clone(&captured);
5549 let mut b = ChunkBuilder::new();
5550 b.emit(Op::ExtendedWide(9, 9999), 1);
5551 let mut vm = VM::new(b.build());
5552 vm.set_extension_wide_handler(Box::new(move |vm, id, payload| {
5553 *captured_cl.lock().unwrap() = Some((id, payload));
5554 vm.push(Value::Int(0));
5555 }));
5556 let _ = vm.run();
5557 assert_eq!(*captured.lock().unwrap(), Some((9, 9999)));
5558 }
5559
5560 #[test]
5563 fn vmpool_new_default_and_with_capacity_start_empty() {
5564 let p = VMPool::new();
5565 assert!(p.is_empty());
5566 assert_eq!(p.len(), 0);
5567 let p = VMPool::with_capacity(8);
5568 assert!(p.is_empty());
5569 let p: VMPool = Default::default();
5570 assert!(p.is_empty());
5571 }
5572
5573 #[test]
5574 fn vmpool_release_then_acquire_reuses_vm() {
5575 let mut pool = VMPool::new();
5576 let chunk1 = {
5577 let mut b = ChunkBuilder::new();
5578 b.emit(Op::LoadInt(1), 1);
5579 b.build()
5580 };
5581 let vm = pool.acquire(chunk1);
5582 assert_eq!(pool.len(), 0);
5583 pool.release(vm);
5584 assert_eq!(pool.len(), 1);
5585
5586 let chunk2 = {
5587 let mut b = ChunkBuilder::new();
5588 b.emit(Op::LoadInt(2), 1);
5589 b.build()
5590 };
5591 let mut vm = pool.acquire(chunk2);
5592 assert_eq!(pool.len(), 0);
5593 assert!(matches!(vm.run(), VMResult::Ok(Value::Int(2))));
5594 }
5595
5596 #[test]
5597 fn vmpool_with_returns_value_and_recycles_vm() {
5598 let mut pool = VMPool::new();
5599 let chunk = {
5600 let mut b = ChunkBuilder::new();
5601 b.emit(Op::LoadInt(10), 1);
5602 b.emit(Op::LoadInt(5), 1);
5603 b.emit(Op::Add, 1);
5604 b.build()
5605 };
5606 let result = pool.with(chunk, |vm| match vm.run() {
5607 VMResult::Ok(Value::Int(n)) => n,
5608 other => panic!("got {:?}", other),
5609 });
5610 assert_eq!(result, 15);
5611 assert_eq!(pool.len(), 1, "VM should be returned to pool after with()");
5612 }
5613
5614 #[derive(Default)]
5619 struct RecordingAwkHost {
5620 record: String,
5621 fields: Vec<String>,
5622 printed: Vec<Vec<String>>,
5623 field_sets: Vec<(i64, String)>,
5624 special_sets: Vec<(String, String)>,
5625 array: std::collections::HashMap<String, String>,
5626 }
5627
5628 impl crate::awk_host::AwkHost for RecordingAwkHost {
5629 fn field_get(&mut self, i: i64) -> Value {
5630 Value::str(self.fields.get(i as usize).cloned().unwrap_or_default())
5631 }
5632 fn field_set(&mut self, i: i64, v: Value) {
5633 self.field_sets.push((i, v.to_str()));
5634 }
5635 fn nf(&mut self) -> i64 {
5636 self.fields.len() as i64
5637 }
5638 fn set_record(&mut self, v: Value) {
5639 self.record = v.to_str();
5640 self.fields = self.record.split(' ').map(|s| s.to_string()).collect();
5641 }
5642 fn special_get(&mut self, name: &str) -> Value {
5643 match name {
5644 "NR" => Value::Int(7),
5645 _ => Value::str(""),
5646 }
5647 }
5648 fn special_set(&mut self, name: &str, v: Value) {
5649 self.special_sets.push((name.to_string(), v.to_str()));
5650 }
5651 fn print(&mut self, args: &[Value]) {
5652 self.printed.push(args.iter().map(|v| v.to_str()).collect());
5653 }
5654 fn array_get(&mut self, _arr: &str, key: &Value) -> Value {
5655 Value::str(self.array.get(&key.to_str()).cloned().unwrap_or_default())
5656 }
5657 fn array_set(&mut self, _arr: &str, key: &Value, v: Value) {
5658 self.array.insert(key.to_str(), v.to_str());
5659 }
5660 }
5661
5662 fn awk_op(b: &mut ChunkBuilder, id: u16, payload: usize) {
5663 b.emit(Op::ExtendedWide(id, payload), 1);
5664 }
5665
5666 #[test]
5667 fn awk_field_get_routes_to_host() {
5668 use crate::awk_builtins::*;
5669 let chunk = {
5670 let mut b = ChunkBuilder::new();
5671 b.emit(Op::LoadInt(2), 1); awk_op(&mut b, AWK_FIELD_GET, 0);
5673 b.build()
5674 };
5675 let mut vm = VM::new(chunk);
5676 let host = RecordingAwkHost {
5677 fields: vec!["a".into(), "b".into(), "c".into()],
5678 ..Default::default()
5679 };
5680 vm.set_awk_host(Box::new(host));
5681 match vm.run() {
5682 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
5683 other => panic!("got {:?}", other),
5684 }
5685 }
5686
5687 #[test]
5688 fn awk_print_pops_args_in_source_order() {
5689 use crate::awk_builtins::*;
5690 let chunk = {
5691 let mut b = ChunkBuilder::new();
5692 let x = b.add_constant(Value::str("x"));
5693 let y = b.add_constant(Value::str("y"));
5694 b.emit(Op::LoadConst(x), 1);
5695 b.emit(Op::LoadConst(y), 1);
5696 awk_op(&mut b, AWK_PRINT, 2);
5697 b.build()
5698 };
5699 let mut vm = VM::new(chunk);
5700 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
5702 impl crate::awk_host::AwkHost for H {
5703 fn print(&mut self, args: &[Value]) {
5704 self.0
5705 .lock()
5706 .unwrap()
5707 .push(args.iter().map(|v| v.to_str()).collect());
5708 }
5709 }
5710 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
5711 vm.set_awk_host(Box::new(H(sink.clone())));
5712 let _ = vm.run();
5713 assert_eq!(
5714 sink.lock().unwrap().as_slice(),
5715 &[vec!["x".to_string(), "y".to_string()]]
5716 );
5717 }
5718
5719 #[test]
5720 fn awk_field_set_pops_value_and_index() {
5721 use crate::awk_builtins::*;
5722 let chunk = {
5723 let mut b = ChunkBuilder::new();
5724 let v = b.add_constant(Value::str("Z"));
5725 b.emit(Op::LoadConst(v), 1); b.emit(Op::LoadInt(3), 1); awk_op(&mut b, AWK_FIELD_SET, 0);
5728 b.build()
5729 };
5730 struct H(std::sync::Arc<std::sync::Mutex<Vec<(i64, String)>>>);
5731 impl crate::awk_host::AwkHost for H {
5732 fn field_set(&mut self, i: i64, v: Value) {
5733 self.0.lock().unwrap().push((i, v.to_str()));
5734 }
5735 }
5736 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
5737 let mut vm = VM::new(chunk);
5738 vm.set_awk_host(Box::new(H(sink.clone())));
5739 let _ = vm.run();
5740 assert_eq!(sink.lock().unwrap().as_slice(), &[(3i64, "Z".to_string())]);
5741 }
5742
5743 #[test]
5744 fn awk_special_get_and_array_roundtrip() {
5745 use crate::awk_builtins::*;
5746 let chunk = {
5747 let mut b = ChunkBuilder::new();
5748 let arr = b.add_name("counts");
5749 let k = b.add_constant(Value::str("k"));
5750 let val = b.add_constant(Value::str("42"));
5751 b.emit(Op::LoadConst(val), 1);
5753 b.emit(Op::LoadConst(k), 1);
5754 awk_op(&mut b, AWK_ARRAY_SET, arr as usize);
5755 b.emit(Op::LoadConst(k), 1);
5757 awk_op(&mut b, AWK_ARRAY_GET, arr as usize);
5758 b.build()
5759 };
5760 let mut vm = VM::new(chunk);
5761 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
5762 match vm.run() {
5763 VMResult::Ok(v) => assert_eq!(v.to_str(), "42"),
5764 other => panic!("got {:?}", other),
5765 }
5766 }
5767
5768 #[test]
5769 fn awk_ops_are_inert_without_host_but_keep_stack_balanced() {
5770 use crate::awk_builtins::*;
5771 let chunk = {
5773 let mut b = ChunkBuilder::new();
5774 b.emit(Op::LoadInt(1), 1);
5775 awk_op(&mut b, AWK_FIELD_GET, 0);
5776 b.build()
5777 };
5778 let mut vm = VM::new(chunk);
5779 match vm.run() {
5780 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
5781 other => panic!("got {:?}", other),
5782 }
5783 }
5784
5785 #[test]
5792 fn first_class_awk_field_get_routes_to_host() {
5793 let chunk = {
5794 let mut b = ChunkBuilder::new();
5795 b.emit(Op::LoadInt(2), 1); b.emit(Op::AwkFieldGet, 1);
5797 b.build()
5798 };
5799 let mut vm = VM::new(chunk);
5800 let host = RecordingAwkHost {
5801 fields: vec!["a".into(), "b".into(), "c".into()],
5802 ..Default::default()
5803 };
5804 vm.set_awk_host(Box::new(host));
5805 match vm.run() {
5806 VMResult::Ok(v) => assert_eq!(v.to_str(), "c"),
5807 other => panic!("got {:?}", other),
5808 }
5809 }
5810
5811 #[test]
5812 fn first_class_awk_print_pops_args_in_source_order() {
5813 struct H(std::sync::Arc<std::sync::Mutex<Vec<Vec<String>>>>);
5814 impl crate::awk_host::AwkHost for H {
5815 fn print(&mut self, args: &[Value]) {
5816 self.0
5817 .lock()
5818 .unwrap()
5819 .push(args.iter().map(|v| v.to_str()).collect());
5820 }
5821 }
5822 let chunk = {
5823 let mut b = ChunkBuilder::new();
5824 let x = b.add_constant(Value::str("x"));
5825 let y = b.add_constant(Value::str("y"));
5826 b.emit(Op::LoadConst(x), 1);
5827 b.emit(Op::LoadConst(y), 1);
5828 b.emit(Op::AwkPrint(2), 1);
5829 b.build()
5830 };
5831 let mut vm = VM::new(chunk);
5832 let sink = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
5833 vm.set_awk_host(Box::new(H(sink.clone())));
5834 let _ = vm.run();
5835 assert_eq!(
5836 sink.lock().unwrap().as_slice(),
5837 &[vec!["x".to_string(), "y".to_string()]]
5838 );
5839 }
5840
5841 #[test]
5842 fn first_class_awk_array_roundtrip_matches_extendedwide() {
5843 fn run_variant(first_class: bool) -> String {
5846 use crate::awk_builtins::*;
5847 let mut b = ChunkBuilder::new();
5848 let arr = b.add_name("counts");
5849 let k = b.add_constant(Value::str("k"));
5850 let val = b.add_constant(Value::str("42"));
5851 b.emit(Op::LoadConst(val), 1);
5852 b.emit(Op::LoadConst(k), 1);
5853 if first_class {
5854 b.emit(Op::AwkArraySet(arr), 1);
5855 } else {
5856 b.emit(Op::ExtendedWide(AWK_ARRAY_SET, arr as usize), 1);
5857 }
5858 b.emit(Op::LoadConst(k), 1);
5859 if first_class {
5860 b.emit(Op::AwkArrayGet(arr), 1);
5861 } else {
5862 b.emit(Op::ExtendedWide(AWK_ARRAY_GET, arr as usize), 1);
5863 }
5864 let mut vm = VM::new(b.build());
5865 vm.set_awk_host(Box::new(RecordingAwkHost::default()));
5866 match vm.run() {
5867 VMResult::Ok(v) => v.to_str(),
5868 other => panic!("got {:?}", other),
5869 }
5870 }
5871 assert_eq!(run_variant(true), "42");
5872 assert_eq!(run_variant(true), run_variant(false));
5873 }
5874
5875 #[test]
5876 fn first_class_awk_ops_inert_without_host() {
5877 let chunk = {
5879 let mut b = ChunkBuilder::new();
5880 b.emit(Op::LoadInt(1), 1);
5881 b.emit(Op::AwkFieldGet, 1);
5882 b.build()
5883 };
5884 let mut vm = VM::new(chunk);
5885 match vm.run() {
5886 VMResult::Ok(v) => assert_eq!(v.to_str(), ""),
5887 other => panic!("got {:?}", other),
5888 }
5889 }
5890
5891 fn run_native(chunk: crate::chunk::Chunk) -> Value {
5897 let mut vm = VM::new(chunk);
5898 match vm.run() {
5899 VMResult::Ok(v) => v,
5900 other => panic!("got {:?}", other),
5901 }
5902 }
5903
5904 #[test]
5905 fn awk_substr_executes_natively_without_host() {
5906 let chunk = {
5908 let mut b = ChunkBuilder::new();
5909 let s = b.add_constant(Value::str("hello"));
5910 b.emit(Op::LoadConst(s), 1);
5911 b.emit(Op::LoadInt(2), 1);
5912 b.emit(Op::LoadInt(3), 1);
5913 b.emit(Op::AwkSubstr(3), 1);
5914 b.build()
5915 };
5916 assert_eq!(run_native(chunk).to_str(), "ell");
5917 }
5918
5919 #[test]
5920 fn awk_substr_two_arg_to_end_without_host() {
5921 let chunk = {
5923 let mut b = ChunkBuilder::new();
5924 let s = b.add_constant(Value::str("hello"));
5925 b.emit(Op::LoadConst(s), 1);
5926 b.emit(Op::LoadInt(2), 1);
5927 b.emit(Op::AwkSubstr(2), 1);
5928 b.build()
5929 };
5930 assert_eq!(run_native(chunk).to_str(), "ello");
5931 }
5932
5933 #[test]
5934 fn awk_tolower_toupper_execute_natively_without_host() {
5935 let lower = {
5936 let mut b = ChunkBuilder::new();
5937 let s = b.add_constant(Value::str("MiXeD"));
5938 b.emit(Op::LoadConst(s), 1);
5939 b.emit(Op::AwkToLower, 1);
5940 b.build()
5941 };
5942 assert_eq!(run_native(lower).to_str(), "mixed");
5943 let upper = {
5944 let mut b = ChunkBuilder::new();
5945 let s = b.add_constant(Value::str("MiXeD"));
5946 b.emit(Op::LoadConst(s), 1);
5947 b.emit(Op::AwkToUpper, 1);
5948 b.build()
5949 };
5950 assert_eq!(run_native(upper).to_str(), "MIXED");
5951 }
5952
5953 #[test]
5954 fn awk_index_executes_natively_without_host() {
5955 let chunk = {
5957 let mut b = ChunkBuilder::new();
5958 let s = b.add_constant(Value::str("hello"));
5959 let t = b.add_constant(Value::str("ll"));
5960 b.emit(Op::LoadConst(s), 1);
5961 b.emit(Op::LoadConst(t), 1);
5962 b.emit(Op::AwkIndex, 1);
5963 b.build()
5964 };
5965 assert_eq!(run_native(chunk).to_int(), 3);
5966 }
5967
5968 #[test]
5969 fn awk_length_scalar_executes_natively_without_host() {
5970 let chunk = {
5972 let mut b = ChunkBuilder::new();
5973 let s = b.add_constant(Value::str("héllo"));
5974 b.emit(Op::LoadConst(s), 1);
5975 b.emit(Op::AwkLength(1), 1);
5976 b.build()
5977 };
5978 assert_eq!(run_native(chunk).to_int(), 5);
5979 }
5980
5981 #[test]
5982 fn awk_native_string_ops_match_host_path() {
5983 use crate::awk_host::{awk_index, awk_substr, awk_tolower};
5985 assert_eq!(awk_substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
5986 assert_eq!(awk_index(&Value::str("hello"), &Value::str("z")), 0);
5987 assert_eq!(awk_tolower(&Value::str("ABC")).to_str(), "abc");
5988 }
5989
5990 #[test]
5995 fn awk_int_truncates_toward_zero_without_host() {
5996 for (input, want) in [(3.7_f64, 3_i64), (-3.7, -3), (0.0, 0)] {
5997 let chunk = {
5998 let mut b = ChunkBuilder::new();
5999 let x = b.add_constant(Value::Float(input));
6000 b.emit(Op::LoadConst(x), 1);
6001 b.emit(Op::AwkInt, 1);
6002 b.build()
6003 };
6004 assert_eq!(run_native(chunk).to_int(), want, "int({input})");
6005 }
6006 }
6007
6008 #[test]
6009 fn awk_sqrt_exp_log_execute_natively_without_host() {
6010 let sqrt = {
6011 let mut b = ChunkBuilder::new();
6012 let x = b.add_constant(Value::Float(16.0));
6013 b.emit(Op::LoadConst(x), 1);
6014 b.emit(Op::AwkSqrt, 1);
6015 b.build()
6016 };
6017 assert_eq!(run_native(sqrt).to_float(), 4.0);
6018 let log = {
6019 let mut b = ChunkBuilder::new();
6020 let x = b.add_constant(Value::Float(std::f64::consts::E));
6021 b.emit(Op::LoadConst(x), 1);
6022 b.emit(Op::AwkLog, 1);
6023 b.build()
6024 };
6025 assert!((run_native(log).to_float() - 1.0).abs() < 1e-12);
6026 let exp = {
6027 let mut b = ChunkBuilder::new();
6028 let x = b.add_constant(Value::Float(0.0));
6029 b.emit(Op::LoadConst(x), 1);
6030 b.emit(Op::AwkExp, 1);
6031 b.build()
6032 };
6033 assert_eq!(run_native(exp).to_float(), 1.0);
6034 }
6035
6036 #[test]
6037 fn awk_sin_cos_execute_natively_without_host() {
6038 let sin = {
6039 let mut b = ChunkBuilder::new();
6040 let x = b.add_constant(Value::Float(0.0));
6041 b.emit(Op::LoadConst(x), 1);
6042 b.emit(Op::AwkSin, 1);
6043 b.build()
6044 };
6045 assert_eq!(run_native(sin).to_float(), 0.0);
6046 let cos = {
6047 let mut b = ChunkBuilder::new();
6048 let x = b.add_constant(Value::Float(0.0));
6049 b.emit(Op::LoadConst(x), 1);
6050 b.emit(Op::AwkCos, 1);
6051 b.build()
6052 };
6053 assert_eq!(run_native(cos).to_float(), 1.0);
6054 }
6055
6056 #[test]
6057 fn awk_atan2_pops_y_then_x_without_host() {
6058 let chunk = {
6060 let mut b = ChunkBuilder::new();
6061 let y = b.add_constant(Value::Float(1.0));
6062 let x = b.add_constant(Value::Float(1.0));
6063 b.emit(Op::LoadConst(y), 1);
6064 b.emit(Op::LoadConst(x), 1);
6065 b.emit(Op::AwkAtan2, 1);
6066 b.build()
6067 };
6068 assert!((run_native(chunk).to_float() - std::f64::consts::FRAC_PI_4).abs() < 1e-12);
6069 }
6070
6071 fn run_native_int(chunk: crate::chunk::Chunk) -> i64 {
6076 run_native(chunk).to_int()
6077 }
6078
6079 #[test]
6080 fn awk_and_or_xor_execute_natively_without_host() {
6081 let mk = |op: Op| {
6083 let mut b = ChunkBuilder::new();
6084 b.emit(Op::LoadInt(12), 1);
6085 b.emit(Op::LoadInt(10), 1);
6086 b.emit(op, 1);
6087 b.build()
6088 };
6089 assert_eq!(run_native_int(mk(Op::AwkAnd(2))), 8);
6090 assert_eq!(run_native_int(mk(Op::AwkOr(2))), 14);
6091 assert_eq!(run_native_int(mk(Op::AwkXor(2))), 6);
6092 }
6093
6094 #[test]
6095 fn awk_and_is_variadic_without_host() {
6096 let chunk = {
6098 let mut b = ChunkBuilder::new();
6099 b.emit(Op::LoadInt(15), 1);
6100 b.emit(Op::LoadInt(9), 1);
6101 b.emit(Op::LoadInt(5), 1);
6102 b.emit(Op::AwkAnd(3), 1);
6103 b.build()
6104 };
6105 assert_eq!(run_native_int(chunk), 1);
6106 }
6107
6108 #[test]
6109 fn awk_compl_matches_awkrs_i64_wrap_without_host() {
6110 let chunk = {
6112 let mut b = ChunkBuilder::new();
6113 b.emit(Op::LoadInt(0), 1);
6114 b.emit(Op::AwkCompl, 1);
6115 b.build()
6116 };
6117 assert_eq!(run_native_int(chunk), -1);
6118 }
6119
6120 #[test]
6121 fn awk_lshift_rshift_execute_natively_without_host() {
6122 let lshift = {
6124 let mut b = ChunkBuilder::new();
6125 b.emit(Op::LoadInt(1), 1);
6126 b.emit(Op::LoadInt(4), 1);
6127 b.emit(Op::AwkLshift, 1);
6128 b.build()
6129 };
6130 assert_eq!(run_native_int(lshift), 16);
6131 let rshift = {
6132 let mut b = ChunkBuilder::new();
6133 b.emit(Op::LoadInt(256), 1);
6134 b.emit(Op::LoadInt(4), 1);
6135 b.emit(Op::AwkRshift, 1);
6136 b.build()
6137 };
6138 assert_eq!(run_native_int(rshift), 16);
6139 }
6140
6141 #[test]
6142 fn awk_bitwise_free_fns_match_gawk_semantics() {
6143 use crate::awk_host::{awk_compl, awk_fold_and, awk_fold_or, awk_fold_xor, awk_lshift};
6144 assert_eq!(awk_fold_and(&[Value::Int(12), Value::Int(10)]), 8);
6145 assert_eq!(awk_fold_or(&[Value::Int(12), Value::Int(10)]), 14);
6146 assert_eq!(awk_fold_xor(&[Value::Int(12), Value::Int(10)]), 6);
6147 assert_eq!(awk_compl(&Value::Int(0)), -1);
6148 assert_eq!(awk_lshift(&Value::Int(1), &Value::Int(64)), 1);
6150 }
6151
6152 #[test]
6153 fn awk_mktime_utc_executes_natively_without_host() {
6154 let chunk = {
6156 let mut b = ChunkBuilder::new();
6157 let s = b.add_constant(Value::str("2020 01 01 00 00 00"));
6158 b.emit(Op::LoadConst(s), 1);
6159 b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkMktime(2), 1);
6161 b.build()
6162 };
6163 assert_eq!(run_native(chunk).to_float(), 1_577_836_800.0);
6164 }
6165
6166 #[test]
6167 fn awk_mktime_bad_datespec_returns_minus_one_without_host() {
6168 let chunk = {
6170 let mut b = ChunkBuilder::new();
6171 let s = b.add_constant(Value::str("2020 01 01"));
6172 b.emit(Op::LoadConst(s), 1);
6173 b.emit(Op::AwkMktime(1), 1);
6174 b.build()
6175 };
6176 assert_eq!(run_native(chunk).to_float(), -1.0);
6177 }
6178
6179 #[test]
6180 fn awk_strftime_utc_executes_natively_without_host() {
6181 let chunk = {
6183 let mut b = ChunkBuilder::new();
6184 let fmt = b.add_constant(Value::str("%Y-%m-%d"));
6185 b.emit(Op::LoadConst(fmt), 1);
6186 b.emit(Op::LoadInt(0), 1); b.emit(Op::LoadInt(1), 1); b.emit(Op::AwkStrftime(3), 1);
6189 b.build()
6190 };
6191 assert_eq!(run_native(chunk).to_str(), "1970-01-01");
6192 }
6193
6194 #[test]
6195 fn awk_strftime_mktime_free_fns_match_awkrs() {
6196 use crate::awk_host::{awk_mktime, awk_strftime};
6197 assert_eq!(
6199 awk_mktime(&[Value::str("2020 01 01 00 00 00"), Value::Int(1)]).to_float(),
6200 1_577_836_800.0
6201 );
6202 assert_eq!(awk_mktime(&[Value::str("garbage")]).to_float(), -1.0);
6203 assert_eq!(
6204 awk_strftime(&[Value::str("%H:%M:%S"), Value::Int(0), Value::Int(1)]).to_str(),
6205 "00:00:00"
6206 );
6207 }
6208
6209 #[test]
6210 fn awk_ord_executes_natively_without_host() {
6211 let chunk = {
6213 let mut b = ChunkBuilder::new();
6214 let s = b.add_constant(Value::str("ABC"));
6215 b.emit(Op::LoadConst(s), 1);
6216 b.emit(Op::AwkOrd, 1);
6217 b.build()
6218 };
6219 assert_eq!(run_native(chunk).to_float(), 65.0);
6220
6221 let empty = {
6222 let mut b = ChunkBuilder::new();
6223 let s = b.add_constant(Value::str(""));
6224 b.emit(Op::LoadConst(s), 1);
6225 b.emit(Op::AwkOrd, 1);
6226 b.build()
6227 };
6228 assert_eq!(run_native(empty).to_float(), 0.0);
6229 }
6230
6231 #[test]
6232 fn awk_chr_executes_natively_without_host() {
6233 let chunk = {
6235 let mut b = ChunkBuilder::new();
6236 b.emit(Op::LoadInt(65), 1);
6237 b.emit(Op::AwkChr, 1);
6238 b.build()
6239 };
6240 assert_eq!(run_native(chunk).to_str(), "A");
6241
6242 let bad = {
6243 let mut b = ChunkBuilder::new();
6244 b.emit(Op::LoadInt(0xD800), 1); b.emit(Op::AwkChr, 1);
6246 b.build()
6247 };
6248 assert_eq!(run_native(bad).to_str(), "");
6249 }
6250
6251 #[test]
6252 fn awk_mkbool_executes_natively_without_host() {
6253 let truthy = {
6255 let mut b = ChunkBuilder::new();
6256 b.emit(Op::LoadInt(7), 1);
6257 b.emit(Op::AwkMkbool, 1);
6258 b.build()
6259 };
6260 assert_eq!(run_native(truthy).to_float(), 1.0);
6261
6262 let zero = {
6263 let mut b = ChunkBuilder::new();
6264 b.emit(Op::LoadInt(0), 1);
6265 b.emit(Op::AwkMkbool, 1);
6266 b.build()
6267 };
6268 assert_eq!(run_native(zero).to_float(), 0.0);
6269 }
6270
6271 #[test]
6272 fn awk_intdiv_executes_natively_without_host() {
6273 let chunk = {
6275 let mut b = ChunkBuilder::new();
6276 b.emit(Op::LoadInt(17), 1);
6277 b.emit(Op::LoadInt(5), 1);
6278 b.emit(Op::AwkIntdiv, 1);
6279 b.build()
6280 };
6281 assert_eq!(run_native(chunk).to_float(), 3.0);
6282
6283 let div0 = {
6284 let mut b = ChunkBuilder::new();
6285 b.emit(Op::LoadInt(17), 1);
6286 b.emit(Op::LoadInt(0), 1);
6287 b.emit(Op::AwkIntdiv, 1);
6288 b.build()
6289 };
6290 assert!(matches!(run_native(div0), Value::Undef));
6291 }
6292
6293 #[test]
6294 fn awk_intdiv0_executes_natively_without_host() {
6295 let chunk = {
6297 let mut b = ChunkBuilder::new();
6298 b.emit(Op::LoadInt(17), 1);
6299 b.emit(Op::LoadInt(5), 1);
6300 b.emit(Op::AwkIntdiv0, 1);
6301 b.build()
6302 };
6303 assert_eq!(run_native(chunk).to_float(), 3.0);
6304
6305 let div0 = {
6306 let mut b = ChunkBuilder::new();
6307 b.emit(Op::LoadInt(17), 1);
6308 b.emit(Op::LoadInt(0), 1);
6309 b.emit(Op::AwkIntdiv0, 1);
6310 b.build()
6311 };
6312 assert_eq!(run_native(div0).to_float(), 0.0);
6313 }
6314
6315 #[test]
6316 fn awk_div_mod_compute_and_trap_on_zero() {
6317 let div = {
6322 let mut b = ChunkBuilder::new();
6323 b.emit(Op::LoadFloat(7.0), 1);
6324 b.emit(Op::LoadFloat(2.0), 1);
6325 b.emit(Op::AwkDiv, 1);
6326 b.build()
6327 };
6328 assert_eq!(run_native(div).to_float(), 3.5);
6329
6330 let md = {
6331 let mut b = ChunkBuilder::new();
6332 b.emit(Op::LoadFloat(7.0), 1);
6333 b.emit(Op::LoadFloat(3.0), 1);
6334 b.emit(Op::AwkMod, 1);
6335 b.build()
6336 };
6337 assert_eq!(run_native(md).to_float(), 1.0);
6338
6339 let div0 = {
6340 let mut b = ChunkBuilder::new();
6341 b.emit(Op::LoadFloat(1.0), 1);
6342 b.emit(Op::LoadFloat(0.0), 1);
6343 b.emit(Op::AwkDiv, 1);
6344 b.build()
6345 };
6346 match VM::new(div0).run() {
6347 VMResult::Error(m) => assert_eq!(m, "division by zero attempted"),
6348 other => panic!("expected div-by-zero trap, got {:?}", other),
6349 }
6350
6351 let mod0 = {
6352 let mut b = ChunkBuilder::new();
6353 b.emit(Op::LoadFloat(1.0), 1);
6354 b.emit(Op::LoadFloat(0.0), 1);
6355 b.emit(Op::AwkMod, 1);
6356 b.build()
6357 };
6358 match VM::new(mod0).run() {
6359 VMResult::Error(m) => assert_eq!(m, "division by zero attempted in `%'"),
6360 other => panic!("expected mod-by-zero trap, got {:?}", other),
6361 }
6362 }
6363
6364 #[test]
6365 fn awk_signal_halts_chunk_and_records_code() {
6366 use crate::awk_builtins::signal;
6367 let chunk = {
6370 let mut b = ChunkBuilder::new();
6371 b.emit(Op::LoadInt(1), 1);
6372 b.emit(Op::AwkSignal(signal::NEXTFILE), 1);
6373 b.emit(Op::LoadInt(99), 1);
6375 b.build()
6376 };
6377 let mut vm = VM::new(chunk);
6378 let r = vm.run();
6379 assert_eq!(vm.awk_signal(), Some(signal::NEXTFILE));
6380 match r {
6383 VMResult::Ok(v) => assert_eq!(v.to_float(), 1.0),
6384 other => panic!("expected Ok(1), got {:?}", other),
6385 }
6386
6387 let plain = {
6389 let mut b = ChunkBuilder::new();
6390 b.emit(Op::LoadInt(5), 1);
6391 b.build()
6392 };
6393 let mut vm2 = VM::new(plain);
6394 let _ = vm2.run();
6395 assert_eq!(vm2.awk_signal(), None);
6396 }
6397
6398 #[test]
6399 fn awk_gensub_stub_is_stack_balanced_without_host() {
6400 let chunk = {
6403 let mut b = ChunkBuilder::new();
6404 let re = b.add_constant(Value::str("x"));
6405 let repl = b.add_constant(Value::str("y"));
6406 let how = b.add_constant(Value::str("g"));
6407 let target = b.add_constant(Value::str("xax"));
6408 b.emit(Op::LoadConst(re), 1);
6409 b.emit(Op::LoadConst(repl), 1);
6410 b.emit(Op::LoadConst(how), 1);
6411 b.emit(Op::LoadConst(target), 1);
6412 b.emit(Op::AwkGensub(4), 1);
6413 b.build()
6414 };
6415 assert_eq!(run_native(chunk).to_str(), "");
6416 }
6417
6418 #[test]
6419 fn awk_char_scalar_free_fns_match_awkrs() {
6420 use crate::awk_host::{awk_chr, awk_intdiv, awk_intdiv0, awk_mkbool, awk_ord};
6421 assert_eq!(awk_ord(&Value::str("z")).to_float(), 122.0);
6422 assert_eq!(awk_chr(&Value::Int(0x1F600)).to_str(), "😀");
6423 assert_eq!(awk_mkbool(&Value::str("0")).to_float(), 0.0);
6424 assert_eq!(awk_mkbool(&Value::str("x")).to_float(), 1.0);
6425 assert_eq!(awk_intdiv(&Value::Int(-7), &Value::Int(2)).to_float(), -3.0);
6426 assert!(matches!(
6427 awk_intdiv(&Value::Int(1), &Value::Int(0)),
6428 Value::Undef
6429 ));
6430 assert_eq!(
6431 awk_intdiv0(&Value::Int(-7), &Value::Int(2)).to_float(),
6432 -3.0
6433 );
6434 assert_eq!(awk_intdiv0(&Value::Int(1), &Value::Int(0)).to_float(), 0.0);
6435 }
6436
6437 #[test]
6438 fn awk_rand_executes_natively_without_host() {
6439 let chunk = {
6441 let mut b = ChunkBuilder::new();
6442 b.emit(Op::AwkRand, 1);
6443 b.build()
6444 };
6445 let v = run_native(chunk).to_float();
6446 assert!((0.0..1.0).contains(&v), "rand() = {v} out of [0,1)");
6447 assert_eq!(
6448 v, 0.51385498046875,
6449 "first rand() from seed=1 must be stable"
6450 );
6451 }
6452
6453 #[test]
6454 fn awk_srand_reseeds_and_returns_prev_seed_without_host() {
6455 let chunk = {
6458 let mut b = ChunkBuilder::new();
6459 b.emit(Op::LoadInt(42), 1);
6460 b.emit(Op::AwkSrand(1), 1); b.emit(Op::Pop, 1);
6462 b.emit(Op::AwkRand, 1);
6463 b.build()
6464 };
6465 assert_eq!(run_native(chunk).to_float(), 0.582305908203125);
6466 }
6467
6468 #[test]
6469 fn awk_srand_no_arg_returns_prev_seed_without_host() {
6470 let chunk = {
6473 let mut b = ChunkBuilder::new();
6474 b.emit(Op::AwkSrand(0), 1);
6475 b.build()
6476 };
6477 assert_eq!(run_native(chunk).to_float(), 1.0);
6478 }
6479
6480 #[test]
6481 fn awk_rand_srand_free_fns_match_awkrs_lcg() {
6482 use crate::awk_host::{awk_rand, awk_srand};
6483 let mut seed: u64 = 1;
6484 assert_eq!(awk_rand(&mut seed), 0.51385498046875);
6485 let prev = awk_srand(&mut seed, Some(42));
6487 assert_eq!(prev, 1103527590.0);
6488 assert_eq!(awk_rand(&mut seed), 0.582305908203125);
6489 }
6490
6491 #[test]
6492 fn awk_systime_executes_natively_without_host() {
6493 let chunk = {
6496 let mut b = ChunkBuilder::new();
6497 b.emit(Op::AwkSystime, 1);
6498 b.build()
6499 };
6500 let v = run_native(chunk).to_float();
6501 assert!(v > 1_577_836_800.0, "systime() = {v} should be past 2020");
6502 }
6503
6504 #[test]
6505 fn awk_systime_free_fn_is_positive() {
6506 use crate::awk_host::awk_systime;
6507 assert!(awk_systime() > 1_577_836_800.0);
6508 }
6509
6510 #[test]
6511 fn awk_strtonum_executes_natively_without_host() {
6512 let chunk = {
6514 let mut b = ChunkBuilder::new();
6515 let s = b.add_constant(Value::str("0x10"));
6516 b.emit(Op::LoadConst(s), 1);
6517 b.emit(Op::AwkStrtonum, 1);
6518 b.build()
6519 };
6520 assert_eq!(run_native(chunk).to_float(), 16.0);
6521 }
6522
6523 #[test]
6524 fn awk_strtonum_octal_and_decimal_prefix_without_host() {
6525 let octal = {
6527 let mut b = ChunkBuilder::new();
6528 let s = b.add_constant(Value::str("010"));
6529 b.emit(Op::LoadConst(s), 1);
6530 b.emit(Op::AwkStrtonum, 1);
6531 b.build()
6532 };
6533 assert_eq!(run_native(octal).to_float(), 8.0);
6534 let prefix = {
6535 let mut b = ChunkBuilder::new();
6536 let s = b.add_constant(Value::str("42abc"));
6537 b.emit(Op::LoadConst(s), 1);
6538 b.emit(Op::AwkStrtonum, 1);
6539 b.build()
6540 };
6541 assert_eq!(run_native(prefix).to_float(), 42.0);
6542 }
6543
6544 #[test]
6545 fn awk_strtonum_free_fn_matches_awkrs_semantics() {
6546 use crate::awk_host::awk_strtonum;
6547 assert_eq!(awk_strtonum(""), 0.0);
6548 assert_eq!(awk_strtonum(" "), 0.0);
6549 assert_eq!(awk_strtonum("0x10"), 16.0);
6550 assert_eq!(awk_strtonum("0Xff"), 255.0);
6551 assert_eq!(awk_strtonum("010"), 8.0);
6552 assert_eq!(awk_strtonum("3.5"), 3.5);
6553 assert_eq!(awk_strtonum("0x"), 0.0);
6555 assert_eq!(awk_strtonum("0xzz"), 0.0);
6556 assert_eq!(awk_strtonum("+0x10"), 0.0);
6558 assert_eq!(awk_strtonum("nan"), 0.0);
6560 assert_eq!(awk_strtonum("inf"), 0.0);
6561 }
6562
6563 #[test]
6564 fn awk_builtin_substr_default_impl_is_posix() {
6565 use crate::awk_host::{AwkHost, DefaultAwkHost};
6566 let mut h = DefaultAwkHost;
6567 assert_eq!(h.substr(&Value::str("hello"), 2, Some(3)).to_str(), "ell");
6568 assert_eq!(h.substr(&Value::str("hello"), 2, None).to_str(), "ello");
6569 assert_eq!(h.substr(&Value::str("hello"), 0, Some(3)).to_str(), "he");
6570 assert_eq!(h.index(&Value::str("hello"), &Value::str("ll")), 3);
6571 assert_eq!(h.index(&Value::str("hello"), &Value::str("z")), 0);
6572 }
6573
6574 #[test]
6575 fn awk_op_range_is_disjoint_from_generic_extended_wide() {
6576 use crate::awk_builtins::*;
6577 assert!(!is_awk_op(0));
6578 assert!(!is_awk_op(AWK_OP_BASE - 1));
6579 assert!(is_awk_op(AWK_FIELD_GET));
6580 assert!(is_awk_op(AWK_ARRAY_LEN));
6581 assert!(!is_awk_op(AWK_OP_END));
6582 }
6583
6584 fn build_unary(op: Op, x: f64) -> crate::Chunk {
6591 let mut b = crate::ChunkBuilder::new();
6592 b.emit(Op::PushFrame, 1);
6593 b.emit(Op::LoadFloat(x), 1);
6594 b.emit(op, 1);
6595 b.build()
6596 }
6597
6598 fn build_binary(op: Op, a: f64, n: f64) -> crate::Chunk {
6599 let mut b = crate::ChunkBuilder::new();
6600 b.emit(Op::PushFrame, 1);
6601 b.emit(Op::LoadFloat(a), 1);
6602 b.emit(Op::LoadFloat(n), 1);
6603 b.emit(op, 1);
6604 b.build()
6605 }
6606
6607 #[test]
6608 fn awk_sqrt_jit_negative_warns_returns_nan() {
6609 let chunk = build_unary(Op::AwkSqrtJit, -4.0);
6610 let mut vm = VM::new(chunk);
6611 match vm.run() {
6612 VMResult::Ok(v) => assert!(v.to_float().is_nan(), "expected NaN, got {v:?}"),
6613 other => panic!("expected Ok(NaN), got {other:?}"),
6614 }
6615 }
6616
6617 #[test]
6618 fn awk_sqrt_jit_positive_returns_sqrt() {
6619 let chunk = build_unary(Op::AwkSqrtJit, 16.0);
6620 let mut vm = VM::new(chunk);
6621 match vm.run() {
6622 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0),
6623 other => panic!("expected Ok(4.0), got {other:?}"),
6624 }
6625 }
6626
6627 #[test]
6628 fn awk_log_jit_positive_returns_ln() {
6629 let chunk = build_unary(Op::AwkLogJit, std::f64::consts::E);
6630 let mut vm = VM::new(chunk);
6631 match vm.run() {
6632 VMResult::Ok(v) => assert!((v.to_float() - 1.0).abs() < 1e-10),
6633 other => panic!("expected Ok(~1.0), got {other:?}"),
6634 }
6635 }
6636
6637 #[test]
6638 fn awk_log_jit_negative_warns_returns_nan() {
6639 let chunk = build_unary(Op::AwkLogJit, -1.0);
6640 let mut vm = VM::new(chunk);
6641 match vm.run() {
6642 VMResult::Ok(v) => assert!(v.to_float().is_nan()),
6643 other => panic!("expected Ok(NaN), got {other:?}"),
6644 }
6645 }
6646
6647 #[test]
6648 fn awk_lshift_jit_computes_left_shift() {
6649 let chunk = build_binary(Op::AwkLshiftJit, 1.0, 4.0);
6650 let mut vm = VM::new(chunk);
6651 match vm.run() {
6652 VMResult::Ok(v) => assert_eq!(v.to_float(), 16.0, "1 << 4 == 16"),
6653 other => panic!("expected Ok(16.0), got {other:?}"),
6654 }
6655 }
6656
6657 #[test]
6658 fn awk_lshift_jit_negative_amount_errors() {
6659 let chunk = build_binary(Op::AwkLshiftJit, 1.0, -1.0);
6660 let mut vm = VM::new(chunk);
6661 match vm.run() {
6662 VMResult::Error(msg) => assert!(msg.contains("lshift"), "msg = {msg:?}"),
6663 other => panic!("expected Error, got {other:?}"),
6664 }
6665 }
6666
6667 #[test]
6668 fn awk_rshift_jit_computes_right_shift() {
6669 let chunk = build_binary(Op::AwkRshiftJit, 16.0, 2.0);
6670 let mut vm = VM::new(chunk);
6671 match vm.run() {
6672 VMResult::Ok(v) => assert_eq!(v.to_float(), 4.0, "16 >> 2 == 4"),
6673 other => panic!("expected Ok(4.0), got {other:?}"),
6674 }
6675 }
6676
6677 #[test]
6678 fn awk_compl_jit_negates_bits() {
6679 let chunk = build_unary(Op::AwkComplJit, 15.0);
6681 let mut vm = VM::new(chunk);
6682 match vm.run() {
6683 VMResult::Ok(v) => assert_eq!(v.to_float(), -16.0, "compl(15) == -16"),
6684 other => panic!("expected Ok(-16.0), got {other:?}"),
6685 }
6686 }
6687
6688 #[test]
6689 fn awk_compl_jit_negative_errors() {
6690 let chunk = build_unary(Op::AwkComplJit, -1.0);
6691 let mut vm = VM::new(chunk);
6692 match vm.run() {
6693 VMResult::Error(msg) => assert!(msg.contains("compl"), "msg = {msg:?}"),
6694 other => panic!("expected Error, got {other:?}"),
6695 }
6696 }
6697
6698 use std::sync::{Arc, Mutex};
6703
6704 #[test]
6705 fn output_sink_captures_print_and_println() {
6706 let mut b = ChunkBuilder::new();
6708 let a = b.add_constant(Value::str("a"));
6709 let bee = b.add_constant(Value::str("b"));
6710 b.emit(Op::LoadConst(a), 1);
6711 b.emit(Op::Print(1), 1);
6712 b.emit(Op::LoadConst(bee), 1);
6713 b.emit(Op::PrintLn(1), 1);
6714 let captured = Arc::new(Mutex::new(String::new()));
6715 let buf = Arc::clone(&captured);
6716 let mut vm = VM::new(b.build());
6717 vm.set_output_sink(Box::new(move |s: &str| buf.lock().unwrap().push_str(s)));
6718 vm.run();
6719 assert_eq!(*captured.lock().unwrap(), "ab\n");
6720 }
6721
6722 #[test]
6723 fn output_sink_receives_multi_arg_print_concatenated() {
6724 let mut b = ChunkBuilder::new();
6726 let x = b.add_constant(Value::str("x"));
6727 let y = b.add_constant(Value::str("y"));
6728 b.emit(Op::LoadConst(x), 1);
6729 b.emit(Op::LoadInt(1), 1);
6730 b.emit(Op::LoadConst(y), 1);
6731 b.emit(Op::Print(3), 1);
6732 let captured = Arc::new(Mutex::new(String::new()));
6733 let buf = Arc::clone(&captured);
6734 let mut vm = VM::new(b.build());
6735 vm.set_output_sink(Box::new(move |s: &str| buf.lock().unwrap().push_str(s)));
6736 vm.run();
6737 assert_eq!(*captured.lock().unwrap(), "x1y");
6738 }
6739
6740 #[test]
6741 fn input_source_feeds_readline_then_undef_at_eof() {
6742 let mut b = ChunkBuilder::new();
6744 b.emit(Op::ReadLine, 1); b.emit(Op::Pop, 1);
6746 b.emit(Op::ReadLine, 1); b.emit(Op::Pop, 1);
6748 b.emit(Op::ReadLine, 1); let mut pending = vec!["second".to_string(), "first".to_string()];
6750 let mut vm = VM::new(b.build());
6751 vm.set_input_source(Box::new(move || pending.pop()));
6752 match vm.run() {
6753 VMResult::Ok(Value::Undef) => {}
6754 other => panic!("expected Undef at EOF, got {other:?}"),
6755 }
6756 }
6757}