1use std::io::Write;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::time::Instant;
5
6use anyhow::{anyhow, Result};
7
8use crate::cli::{AssetKind, CommandBase, Config, ExitCode, Io, SharedFlags};
9use crate::runtime::{UnitStorage, VmError, VmExecution, VmResult};
10use crate::{Context, Hash, Sources, Unit, Value, Vm};
11
12mod cli {
13 use std::path::PathBuf;
14 use std::vec::Vec;
15
16 use clap::Parser;
17
18 #[derive(Parser, Debug)]
19 #[command(rename_all = "kebab-case")]
20 pub(crate) struct Flags {
21 #[arg(short, long)]
23 pub(super) trace: bool,
24 #[arg(long)]
27 pub(super) without_source: bool,
28 #[arg(long)]
30 pub(super) time: bool,
31 #[arg(short, long)]
33 pub(super) dump: bool,
34 #[arg(long)]
36 pub(super) dump_return: bool,
37 #[arg(long)]
39 pub(super) dump_all: bool,
40 #[arg(long)]
42 pub(super) dump_unit: bool,
43 #[arg(long)]
45 pub(super) dump_constants: bool,
46 #[arg(long)]
48 pub(super) emit_instructions: bool,
49 #[arg(long)]
53 pub(super) dump_stack: bool,
54 #[arg(long)]
56 pub(super) dump_functions: bool,
57 #[arg(long)]
59 pub(super) dump_types: bool,
60 #[arg(long)]
62 pub(super) dump_native_functions: bool,
63 #[arg(long)]
65 pub(super) dump_native_types: bool,
66 #[arg(long)]
69 pub(super) trace_limit: Option<usize>,
70 pub(super) run_path: Vec<PathBuf>,
72 }
73}
74
75pub(super) use cli::Flags;
76
77impl CommandBase for Flags {
78 #[inline]
79 fn is_workspace(&self, kind: AssetKind) -> bool {
80 matches!(kind, AssetKind::Bin)
81 }
82
83 #[inline]
84 fn propagate(&mut self, _: &mut Config, _: &mut SharedFlags) {
85 if self.dump || self.dump_all {
86 self.dump_unit = true;
87 self.dump_stack = true;
88 self.dump_return = true;
89 }
90
91 if self.dump_all {
92 self.dump_constants = true;
93 self.dump_functions = true;
94 self.dump_types = true;
95 self.dump_native_functions = true;
96 self.dump_native_types = true;
97 }
98
99 if self.dump_functions
100 || self.dump_native_functions
101 || self.dump_stack
102 || self.dump_types
103 || self.dump_constants
104 {
105 self.dump_unit = true;
106 }
107
108 if self.dump_unit {
109 self.emit_instructions = true;
110 }
111
112 if self.trace_limit.is_some() {
113 self.trace = true;
114 }
115 }
116
117 fn paths(&self) -> &[PathBuf] {
118 &self.run_path
119 }
120}
121
122enum TraceError {
123 Io(std::io::Error),
124 VmError(VmError),
125 Limited,
126}
127
128impl From<std::io::Error> for TraceError {
129 #[inline]
130 fn from(error: std::io::Error) -> Self {
131 Self::Io(error)
132 }
133}
134
135impl From<VmError> for TraceError {
136 #[inline]
137 fn from(error: VmError) -> Self {
138 Self::VmError(error)
139 }
140}
141
142pub(super) async fn run(
143 io: &mut Io<'_>,
144 c: &Config,
145 args: &Flags,
146 context: &Context,
147 unit: Arc<Unit>,
148 sources: &Sources,
149 entry: Hash,
150) -> Result<ExitCode> {
151 if args.dump_native_functions {
152 writeln!(io.stdout, "# functions")?;
153
154 for (i, (meta, _)) in context.iter_functions().enumerate() {
155 if let Some(item) = &meta.item {
156 writeln!(io.stdout, "{:04} = {} ({})", i, item, meta.hash)?;
157 }
158 }
159 }
160
161 if args.dump_native_types {
162 writeln!(io.stdout, "# types")?;
163
164 for (i, (hash, ty)) in context.iter_types().enumerate() {
165 writeln!(io.stdout, "{:04} = {} ({})", i, ty, hash)?;
166 }
167 }
168
169 if args.dump_unit {
170 writeln!(
171 io.stdout,
172 "Unit size: {} bytes",
173 unit.instructions().bytes()
174 )?;
175
176 if args.emit_instructions {
177 let mut o = io.stdout.lock();
178 writeln!(o, "# instructions")?;
179 unit.emit_instructions(&mut o, sources, args.without_source)?;
180 }
181
182 let mut functions = unit.iter_functions().peekable();
183 let mut strings = unit.iter_static_strings().peekable();
184 let mut bytes = unit.iter_static_bytes().peekable();
185 let mut drop_sets = unit.iter_static_drop_sets().peekable();
186 let mut keys = unit.iter_static_object_keys().peekable();
187 let mut constants = unit.iter_constants().peekable();
188
189 if args.dump_functions && functions.peek().is_some() {
190 writeln!(io.stdout, "# dynamic functions")?;
191
192 for (hash, kind) in functions {
193 if let Some(signature) = unit.debug_info().and_then(|d| d.functions.get(&hash)) {
194 writeln!(io.stdout, "{} = {}", hash, signature)?;
195 } else {
196 writeln!(io.stdout, "{} = {}", hash, kind)?;
197 }
198 }
199 }
200
201 if strings.peek().is_some() {
202 writeln!(io.stdout, "# strings")?;
203
204 for (i, string) in strings.enumerate() {
205 writeln!(io.stdout, "{i} = {string:?}")?;
206 }
207 }
208
209 if bytes.peek().is_some() {
210 writeln!(io.stdout, "# bytes")?;
211
212 for (i, bytes) in bytes.enumerate() {
213 writeln!(io.stdout, "{i} = {bytes:?}")?;
214 }
215 }
216
217 if keys.peek().is_some() {
218 writeln!(io.stdout, "# object keys")?;
219
220 for (hash, keys) in keys {
221 writeln!(io.stdout, "{} = {:?}", hash, keys)?;
222 }
223 }
224
225 if drop_sets.peek().is_some() {
226 writeln!(io.stdout, "# drop sets")?;
227
228 for (i, set) in drop_sets.enumerate() {
229 writeln!(io.stdout, "{i} = {set:?}")?;
230 }
231 }
232
233 if args.dump_constants && constants.peek().is_some() {
234 writeln!(io.stdout, "# constants")?;
235
236 for (hash, constant) in constants {
237 writeln!(io.stdout, "{hash} = {constant:?}")?;
238 }
239 }
240 }
241
242 let runtime = Arc::new(context.runtime()?);
243
244 let last = Instant::now();
245
246 let mut vm = Vm::new(runtime, unit);
247 let mut execution: VmExecution<_> = vm.execute(entry, ())?;
248
249 let result = if args.trace {
250 match do_trace(
251 io,
252 &mut execution,
253 sources,
254 args.dump_stack,
255 args.without_source,
256 args.trace_limit.unwrap_or(usize::MAX),
257 )
258 .await
259 {
260 Ok(value) => VmResult::Ok(value),
261 Err(TraceError::Io(io)) => return Err(io.into()),
262 Err(TraceError::VmError(vm)) => VmResult::Err(vm),
263 Err(TraceError::Limited) => return Err(anyhow!("Trace limit reached")),
264 }
265 } else {
266 execution.async_complete().await
267 };
268
269 let errored = match result {
270 VmResult::Ok(result) => {
271 if c.verbose || args.time || args.dump_return {
272 let duration = Instant::now().saturating_duration_since(last);
273
274 execution
275 .vm()
276 .with(|| writeln!(io.stderr, "== {result:?} ({duration:?})"))?;
277 }
278
279 None
280 }
281 VmResult::Err(error) => {
282 if c.verbose || args.time || args.dump_return {
283 let duration = Instant::now().saturating_duration_since(last);
284
285 execution
286 .vm()
287 .with(|| writeln!(io.stderr, "== ! ({error}) ({duration:?})"))?;
288 }
289
290 Some(error)
291 }
292 };
293
294 let exit = if let Some(error) = errored {
295 error.emit(io.stdout, sources)?;
296 ExitCode::VmError
297 } else {
298 ExitCode::Success
299 };
300
301 if args.dump_stack {
302 writeln!(io.stdout, "# call frames after halting")?;
303
304 let vm = execution.vm();
305
306 let frames = vm.call_frames();
307 let stack = vm.stack();
308
309 let mut it = frames.iter().enumerate().peekable();
310
311 while let Some((count, frame)) = it.next() {
312 let stack_top = match it.peek() {
313 Some((_, next)) => next.top,
314 None => stack.top(),
315 };
316
317 let values = stack.get(frame.top..stack_top).expect("bad stack slice");
318
319 writeln!(io.stdout, " frame #{} (+{})", count, frame.top)?;
320
321 if values.is_empty() {
322 writeln!(io.stdout, " *empty*")?;
323 }
324
325 vm.with(|| {
326 for (n, value) in values.iter().enumerate() {
327 writeln!(io.stdout, " {}+{n} = {value:?}", frame.top)?;
328 }
329
330 Ok::<_, crate::support::Error>(())
331 })?;
332 }
333
334 writeln!(io.stdout, " frame #{} (+{})", frames.len(), stack.top())?;
336
337 let values = stack.get(stack.top()..).expect("bad stack slice");
338
339 if values.is_empty() {
340 writeln!(io.stdout, " *empty*")?;
341 }
342
343 vm.with(|| {
344 for (n, value) in values.iter().enumerate() {
345 writeln!(io.stdout, " {}+{n} = {value:?}", stack.top())?;
346 }
347
348 Ok::<_, crate::support::Error>(())
349 })?;
350 }
351
352 Ok(exit)
353}
354
355async fn do_trace<T>(
357 io: &Io<'_>,
358 execution: &mut VmExecution<T>,
359 sources: &Sources,
360 dump_stack: bool,
361 without_source: bool,
362 mut limit: usize,
363) -> Result<Value, TraceError>
364where
365 T: AsRef<Vm> + AsMut<Vm>,
366{
367 let mut current_frame_len = execution.vm().call_frames().len();
368 let mut result = VmResult::Ok(None);
369
370 while limit > 0 {
371 let vm = execution.vm();
372 let ip = vm.ip();
373 let mut o = io.stdout.lock();
374
375 if let Some((hash, signature)) = vm.unit().debug_info().and_then(|d| d.function_at(ip)) {
376 writeln!(o, "fn {} ({}):", signature, hash)?;
377 }
378
379 let debug = vm.unit().debug_info().and_then(|d| d.instruction_at(ip));
380
381 for label in debug.map(|d| d.labels.as_slice()).unwrap_or_default() {
382 writeln!(o, "{}:", label)?;
383 }
384
385 if dump_stack {
386 let frames = vm.call_frames();
387 let stack = vm.stack();
388
389 if current_frame_len != frames.len() {
390 let op = if current_frame_len < frames.len() {
391 "push"
392 } else {
393 "pop"
394 };
395 write!(o, " {op} frame {} (+{})", frames.len(), stack.top())?;
396
397 if let Some(frame) = frames.last() {
398 writeln!(o, " {frame:?}")?;
399 } else {
400 writeln!(o, " *root*")?;
401 }
402
403 current_frame_len = frames.len();
404 }
405 }
406
407 if let Some((inst, _)) = vm.unit().instruction_at(ip).map_err(VmError::from)? {
408 write!(o, " {:04} = {}", ip, inst)?;
409 } else {
410 write!(o, " {:04} = *out of bounds*", ip)?;
411 }
412
413 if let Some(comment) = debug.and_then(|d| d.comment.as_ref()) {
414 write!(o, " // {}", comment)?;
415 }
416
417 writeln!(o)?;
418
419 if !without_source {
420 let debug_info = debug.and_then(|d| sources.get(d.source_id).map(|s| (s, d.span)));
421
422 if let Some(line) = debug_info.and_then(|(s, span)| s.source_line(span)) {
423 write!(o, " ")?;
424 line.write(&mut o)?;
425 writeln!(o)?;
426 }
427 }
428
429 if dump_stack {
430 let stack = vm.stack();
431 let values = stack.get(stack.top()..).expect("bad stack slice");
432
433 if !values.is_empty() {
434 vm.with(|| {
435 for (n, value) in values.iter().enumerate() {
436 writeln!(o, " {}+{n} = {value:?}", stack.top())?;
437 }
438
439 Ok::<_, TraceError>(())
440 })?;
441 }
442 }
443
444 match result {
445 VmResult::Ok(result) => {
446 if let Some(result) = result {
447 return Ok(result);
448 }
449 }
450 VmResult::Err(error) => {
451 return Err(TraceError::VmError(error));
452 }
453 }
454
455 result = execution.async_step().await;
456
457 result = match result {
458 VmResult::Err(error) => {
459 return Err(TraceError::VmError(error));
460 }
461 result => result,
462 };
463
464 limit = limit.wrapping_sub(1);
465 }
466
467 Err(TraceError::Limited)
468}