1#[cfg(feature = "internal_debug")]
2use std::fmt;
3
4use crate::compiler::tokens::Span;
5use crate::output::CaptureMode;
6use crate::value::Value;
7
8pub const LOOP_FLAG_WITH_LOOP_VAR: u8 = 1;
10
11pub const LOOP_FLAG_RECURSIVE: u8 = 2;
13
14#[cfg(feature = "macros")]
16pub const MACRO_CALLER: u8 = 2;
17
18pub type LocalId = u8;
20
21pub const MAX_LOCALS: usize = 50;
23
24#[cfg_attr(feature = "internal_debug", derive(Debug))]
26#[cfg_attr(
27 feature = "unstable_machinery_serde",
28 derive(serde::Serialize),
29 serde(tag = "op", content = "arg")
30)]
31#[derive(Clone)]
32pub enum Instruction<'source> {
33 EmitRaw(&'source str),
35
36 StoreLocal(&'source str),
38
39 Lookup(&'source str),
41
42 GetAttr(&'source str),
44
45 SetAttr(&'source str),
47
48 GetItem,
50
51 Slice,
53
54 LoadConst(Value),
56
57 BuildMap(usize),
59
60 BuildKwargs(usize),
62
63 MergeKwargs(usize),
65
66 BuildList(Option<usize>),
68
69 UnpackList(usize),
71
72 UnpackLists(usize),
74
75 Add,
77
78 Sub,
80
81 Mul,
83
84 Div,
86
87 IntDiv,
93
94 Rem,
96
97 Pow,
99
100 Neg,
102
103 Eq,
105
106 Ne,
108
109 Gt,
111
112 Gte,
114
115 Lt,
117
118 Lte,
120
121 Not,
123
124 StringConcat,
126
127 In,
129
130 ApplyFilter(&'source str, Option<u16>, LocalId),
132
133 PerformTest(&'source str, Option<u16>, LocalId),
135
136 Emit,
138
139 PushLoop(u8),
143
144 PushWith,
146
147 Iterate(u32),
152
153 PushDidNotIterate,
155
156 PopFrame,
158
159 PopLoopFrame,
161
162 Jump(u32),
164
165 JumpIfFalse(u32),
167
168 JumpIfFalseOrPop(u32),
170
171 JumpIfTrueOrPop(u32),
173
174 PushAutoEscape,
176
177 PopAutoEscape,
179
180 BeginCapture(CaptureMode),
182
183 EndCapture,
185
186 CallFunction(&'source str, Option<u16>),
188
189 CallMethod(&'source str, Option<u16>),
191
192 CallObject(Option<u16>),
194
195 DupTop,
197
198 DiscardTop,
200
201 FastSuper,
203
204 FastRecurse,
206
207 Swap,
209
210 #[cfg(feature = "multi_template")]
212 CallBlock(&'source str),
213
214 #[cfg(feature = "multi_template")]
216 LoadBlocks,
217
218 #[cfg(feature = "multi_template")]
220 Include(bool),
221
222 #[cfg(feature = "multi_template")]
224 ExportLocals,
225
226 #[cfg(feature = "macros")]
228 BuildMacro(&'source str, u32, u8),
229
230 #[cfg(feature = "macros")]
232 Return,
233
234 #[cfg(feature = "macros")]
236 IsUndefined,
237
238 #[cfg(feature = "macros")]
240 Enclose(&'source str),
241
242 #[cfg(feature = "macros")]
244 GetClosure,
245}
246
247#[derive(Copy, Clone)]
248struct LineInfo {
249 first_instruction: u32,
250 line: u16,
251}
252
253#[cfg(feature = "debug")]
254#[derive(Copy, Clone)]
255struct SpanInfo {
256 first_instruction: u32,
257 span: Span,
258}
259
260pub struct Instructions<'source> {
262 pub(crate) instructions: Vec<Instruction<'source>>,
263 line_infos: Vec<LineInfo>,
264 #[cfg(feature = "debug")]
265 span_infos: Vec<SpanInfo>,
266 name: &'source str,
267 source: &'source str,
268}
269
270pub(crate) static EMPTY_INSTRUCTIONS: Instructions<'static> = Instructions {
271 instructions: Vec::new(),
272 line_infos: Vec::new(),
273 #[cfg(feature = "debug")]
274 span_infos: Vec::new(),
275 name: "<unknown>",
276 source: "",
277};
278
279impl<'source> Instructions<'source> {
280 pub fn new(name: &'source str, source: &'source str) -> Instructions<'source> {
282 Instructions {
283 instructions: Vec::with_capacity(128),
284 line_infos: Vec::with_capacity(128),
285 #[cfg(feature = "debug")]
286 span_infos: Vec::with_capacity(128),
287 name,
288 source,
289 }
290 }
291
292 pub fn name(&self) -> &'source str {
294 self.name
295 }
296
297 pub fn source(&self) -> &'source str {
299 self.source
300 }
301
302 #[inline(always)]
304 pub fn get(&self, idx: u32) -> Option<&Instruction<'source>> {
305 self.instructions.get(idx as usize)
306 }
307
308 pub fn get_mut(&mut self, idx: u32) -> Option<&mut Instruction<'source>> {
310 self.instructions.get_mut(idx as usize)
311 }
312
313 pub fn add(&mut self, instr: Instruction<'source>) -> u32 {
315 let rv = self.instructions.len();
316 self.instructions.push(instr);
317 rv as u32
318 }
319
320 fn add_line_record(&mut self, instr: u32, line: u16) {
321 let same_loc = self
322 .line_infos
323 .last()
324 .is_some_and(|last_loc| last_loc.line == line);
325 if !same_loc {
326 self.line_infos.push(LineInfo {
327 first_instruction: instr,
328 line,
329 });
330 }
331 }
332
333 pub fn add_with_line(&mut self, instr: Instruction<'source>, line: u16) -> u32 {
335 let rv = self.add(instr);
336 self.add_line_record(rv, line);
337
338 #[cfg(feature = "debug")]
340 {
341 if self
342 .span_infos
343 .last()
344 .is_some_and(|x| x.span != Span::default())
345 {
346 self.span_infos.push(SpanInfo {
347 first_instruction: rv,
348 span: Span::default(),
349 });
350 }
351 }
352 rv
353 }
354
355 pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> u32 {
357 let rv = self.add(instr);
358 #[cfg(feature = "debug")]
359 {
360 let same_loc = self
361 .span_infos
362 .last()
363 .is_some_and(|last_loc| last_loc.span == span);
364 if !same_loc {
365 self.span_infos.push(SpanInfo {
366 first_instruction: rv,
367 span,
368 });
369 }
370 }
371 self.add_line_record(rv, span.start_line);
372 rv
373 }
374
375 pub fn get_line(&self, idx: u32) -> Option<usize> {
377 let loc = match self
378 .line_infos
379 .binary_search_by_key(&idx, |x| x.first_instruction)
380 {
381 Ok(idx) => &self.line_infos[idx],
382 Err(0) => return None,
383 Err(idx) => &self.line_infos[idx - 1],
384 };
385 Some(loc.line as usize)
386 }
387
388 pub fn get_span(&self, idx: u32) -> Option<Span> {
390 #[cfg(feature = "debug")]
391 {
392 let loc = match self
393 .span_infos
394 .binary_search_by_key(&idx, |x| x.first_instruction)
395 {
396 Ok(idx) => &self.span_infos[idx],
397 Err(0) => return None,
398 Err(idx) => &self.span_infos[idx - 1],
399 };
400 (loc.span != Span::default()).then_some(loc.span)
401 }
402 #[cfg(not(feature = "debug"))]
403 {
404 let _ = idx;
405 None
406 }
407 }
408
409 #[cfg(feature = "debug")]
412 pub fn get_referenced_names(&self, idx: u32) -> Vec<&'source str> {
413 let mut rv = Vec::new();
414 if self.instructions.is_empty() {
416 return rv;
417 }
418 let idx = (idx as usize).min(self.instructions.len() - 1);
419 for instr in self.instructions[..=idx].iter().rev() {
420 let name = match instr {
421 Instruction::Lookup(name)
422 | Instruction::StoreLocal(name)
423 | Instruction::CallFunction(name, _) => *name,
424 Instruction::PushLoop(flags) if flags & LOOP_FLAG_WITH_LOOP_VAR != 0 => "loop",
425 Instruction::PushLoop(_) | Instruction::PushWith => break,
426 _ => continue,
427 };
428 if !rv.contains(&name) {
429 rv.push(name);
430 }
431 }
432 rv
433 }
434
435 pub fn len(&self) -> usize {
437 self.instructions.len()
438 }
439
440 #[allow(unused)]
442 pub fn is_empty(&self) -> bool {
443 self.instructions.is_empty()
444 }
445}
446
447#[cfg(feature = "internal_debug")]
448impl fmt::Debug for Instructions<'_> {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 struct InstructionWrapper<'a>(usize, &'a Instruction<'a>, Option<usize>);
451
452 impl fmt::Debug for InstructionWrapper<'_> {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 ok!(write!(f, "{:>05} | {:?}", self.0, self.1,));
455 if let Some(line) = self.2 {
456 ok!(write!(f, " [line {line}]"));
457 }
458 Ok(())
459 }
460 }
461
462 let mut list = f.debug_list();
463 let mut last_line = None;
464 for (idx, instr) in self.instructions.iter().enumerate() {
465 let line = self.get_line(idx as u32);
466 list.entry(&InstructionWrapper(
467 idx,
468 instr,
469 if line != last_line { line } else { None },
470 ));
471 last_line = line;
472 }
473 list.finish()
474 }
475}
476
477#[test]
478#[cfg(target_pointer_width = "64")]
479fn test_sizes() {
480 assert_eq!(std::mem::size_of::<Instruction>(), 32);
481}