• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mattwparas / steel / 17772690385

16 Sep 2025 04:36PM UTC coverage: 43.331% (+0.04%) from 43.289%
17772690385

Pull #519

github

web-flow
Merge 98d4fd22c into 3c10433b9
Pull Request #519: fix a bunch more clippy lints

56 of 123 new or added lines in 30 files covered. (45.53%)

8 existing lines in 3 files now uncovered.

12416 of 28654 relevant lines covered (43.33%)

2985398.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.39
/crates/steel-core/src/compiler/code_gen.rs
1
use super::{
2
    constants::ConstantMap,
3
    passes::analysis::{
4
        Analysis,
5
        CallKind::{Normal, SelfTailCall, TailCall},
6
    },
7
};
8
use crate::{
9
    compiler::passes::analysis::IdentifierStatus::{
10
        Captured, Free, Global, HeapAllocated, LetVar, Local, LocallyDefinedFunction,
11
    },
12
    core::{
13
        instructions::{u24, Instruction},
14
        labels::{resolve_labels, LabeledInstruction},
15
        opcode::OpCode,
16
    },
17
    parser::{
18
        ast::{Atom, ExprKind, List},
19
        parser::SyntaxObject,
20
        span_visitor::get_span,
21
        tokens::TokenType,
22
        tryfrom_visitor::TryFromExprKindForSteelVal,
23
        visitors::VisitorMut,
24
    },
25
    rvals::IntoSteelVal,
26
    stop, SteelVal,
27
};
28
use smallvec::SmallVec;
29
use std::sync::atomic::AtomicUsize;
30
use steel_parser::tokens::{IntLiteral, NumberLiteral, RealLiteral};
31

32
use crate::rvals::Result;
33

34
// TODO: Have this interner also be a part of what gets saved...
35
pub(crate) static FUNCTION_ID: AtomicUsize = AtomicUsize::new(0);
36

37
pub fn fresh_function_id() -> usize {
98,828✔
38
    FUNCTION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
296,484✔
39
}
40

41
pub struct CodeGenerator<'a> {
42
    pub(crate) instructions: Vec<LabeledInstruction>,
43
    constant_map: &'a mut ConstantMap,
44
    analysis: &'a Analysis,
45
    local_count: Vec<usize>,
46
}
47

48
/// Converts a syntax object's token into a `SteelVal` or returns an error if it is not a valid
49
/// `SteelVal`.
50
fn eval_atom(t: &SyntaxObject) -> Result<SteelVal> {
322,009✔
51
    try_eval_atom_with_context(t)
644,018✔
52
}
53

54
/// This is similar to eval_atom but does not allocate a string on a failed match.
55
fn try_eval_atom(t: &SyntaxObject) -> Option<SteelVal> {
×
56
    match &t.ty {
×
57
        TokenType::BooleanLiteral(b) => Some((*b).into()),
×
58
        TokenType::Number(n) => (&**n).into_steelval().ok(),
×
59
        TokenType::StringLiteral(s) => Some(SteelVal::StringV(s.to_string().into())),
×
60
        TokenType::CharacterLiteral(c) => Some(SteelVal::CharV(*c)),
×
61
        // TODO: Keywords shouldn't be misused as an expression - only in function calls are keywords allowed
62
        TokenType::Keyword(k) => Some(SteelVal::SymbolV(k.clone().into())),
×
NEW
63
        _what => None,
×
64
    }
65
}
66

67
fn try_eval_atom_with_context(t: &SyntaxObject) -> Result<SteelVal> {
322,009✔
68
    match &t.ty {
322,009✔
69
        TokenType::BooleanLiteral(b) => Ok((*b).into()),
57,580✔
70
        TokenType::Number(n) => (&**n).into_steelval().map_err(|e| e.with_span(t.span)),
536,004✔
71
        TokenType::StringLiteral(s) => Ok(SteelVal::StringV(s.clone().into())),
175,230✔
72
        TokenType::CharacterLiteral(c) => Ok(SteelVal::CharV(*c)),
2,424✔
73
        TokenType::Keyword(k) => Ok(SteelVal::SymbolV(k.clone().into())),
120✔
74
        _what => {
98,344✔
75
            stop!(UnexpectedToken => t.ty; t.span)
295,032✔
76
        }
77
    }
78
}
79

80
impl<'a> CodeGenerator<'a> {
81
    pub fn new(constant_map: &'a mut ConstantMap, analysis: &'a Analysis) -> Self {
48,493✔
82
        CodeGenerator {
83
            instructions: Vec::new(),
96,986✔
84
            constant_map,
85
            analysis,
86
            local_count: Vec::new(),
48,493✔
87
        }
88
    }
89

90
    pub fn top_level_compile(mut self, expr: &ExprKind) -> Result<Vec<Instruction>> {
60,862✔
91
        self.visit(expr)?;
182,586✔
92
        self.instructions
60,862✔
93
            .push(LabeledInstruction::builder(OpCode::POPPURE));
×
94

95
        Ok(resolve_labels(self.instructions))
×
96
    }
97

98
    fn push(&mut self, instr: LabeledInstruction) {
4,328,241✔
99
        self.instructions.push(instr);
12,984,723✔
100
    }
101

102
    fn len(&self) -> usize {
419,758✔
103
        self.instructions.len()
839,516✔
104
    }
105

106
    fn specialize_constant(&mut self, syn: &SyntaxObject) -> Result<()> {
155,647✔
107
        let value = eval_atom(syn)?;
466,941✔
108

109
        let idx = self.constant_map.add_or_get(value);
×
110
        // First, lets check that the value fits
111
        self.push(
×
112
            LabeledInstruction::builder(OpCode::PUSHCONST)
×
113
                .payload(idx)
×
114
                .contents(syn.clone()),
×
115
        );
116
        Ok(())
×
117
    }
118

119
    fn specialize_immediate(&self, l: &List) -> Option<OpCode> {
828,843✔
120
        if l.args.len() == 3 {
828,843✔
121
            let function = l.first()?;
727,774✔
122

123
            let _ = l.args[1].atom_identifier()?;
176,439✔
124
            let _ = eval_atom(l.args[2].atom_syntax_object()?).ok()?;
424,068✔
125

126
            if let Some(info) = self.analysis.get(function.atom_syntax_object()?) {
107,799✔
127
                if info.kind == Free || info.kind == Global {
30,982✔
128
                    return match function.atom_identifier().unwrap().resolve() {
34,103✔
129
                        "+" | "#%prim.+" => Some(OpCode::ADDIMMEDIATE),
34,414✔
130
                        "-" | "#%prim.-" => Some(OpCode::SUBIMMEDIATE),
62,636✔
131
                        "<=" | "#%prim.<=" => Some(OpCode::LTEIMMEDIATE),
60,075✔
132
                        _ => None,
29,995✔
133
                    };
134
                }
135
            }
136
        }
137

138
        None
466,526✔
139
    }
140

141
    fn should_specialize_call(&self, l: &List) -> Option<OpCode> {
824,735✔
142
        if l.args.len() == 3 {
824,735✔
143
            let function = l.first()?;
719,558✔
144

145
            let _ = l.args[1].atom_identifier()?;
176,439✔
146
            let _ = eval_atom(l.args[2].atom_syntax_object()?).ok()?;
415,852✔
147

148
            if let Some(info) = self.analysis.get(function.atom_syntax_object()?) {
95,475✔
149
                if info.kind == Free || info.kind == Global {
27,310✔
150
                    return match function.atom_identifier().unwrap().resolve() {
29,995✔
151
                        "+" => Some(OpCode::ADDREGISTER),
×
152
                        "-" => Some(OpCode::SUBREGISTER),
29,995✔
153
                        "<=" => Some(OpCode::LTEREGISTER),
29,995✔
154
                        _ => None,
29,995✔
155
                    };
156
                }
157
            }
158
        }
159

160
        None
466,526✔
161
    }
162

163
    fn specialize_immediate_call(&mut self, l: &List, op: OpCode) -> Option<()> {
4,108✔
164
        // let value = eval_atom(l.args[2].atom_syntax_object().unwrap())?;
165

166
        if l.args.len() != 3 {
4,108✔
167
            return None;
×
168
        }
169

170
        let value =
4,104✔
171
            if let Some(TokenType::Number(n)) = l.args[2].atom_syntax_object().map(|x| &x.ty) {
4,108✔
172
                if let NumberLiteral::Real(RealLiteral::Int(IntLiteral::Small(l))) = n.as_ref() {
×
173
                    *l
×
174
                } else {
175
                    return None;
4✔
176
                }
177
            } else {
178
                return None;
×
179
                // stop!()
180
            };
181

182
        if value < 0 {
×
183
            return None;
×
184
        }
185

186
        // if let Some(analysis) = &l.args[1]
187
        //     .atom_syntax_object()
188
        //     .and_then(|a| self.analysis.get(a))
189
        // {
190
        //     if let Some(offset) = analysis.stack_offset {
191
        //         self.push(LabeledInstruction::builder(op).payload(offset));
192
        //     } else {
193
        //         stop!(Generic => "Missing stack offset!");
194
        //     }
195
        // } else {
196
        //     panic!("Shouldn't be happening")
197
        // }
198

199
        let analysis = &l.args[1]
4,104✔
200
            .atom_syntax_object()
×
201
            .and_then(|a| self.analysis.get(a))?;
12,312✔
202

203
        let offset = analysis.stack_offset?;
4,104✔
204

205
        self.push(LabeledInstruction::builder(op).payload(offset as _));
×
206

207
        // let idx = self.constant_map.add_or_get(value);
208

209
        self.push(LabeledInstruction::builder(OpCode::PASS).payload(value as usize));
×
210

211
        Some(())
×
212
    }
213

214
    #[allow(unused)]
215
    fn specialize_call(&mut self, l: &List, op: OpCode) -> Option<()> {
×
216
        if l.args.len() != 3 {
×
217
            return None;
×
218
        }
219

220
        let value = try_eval_atom(l.args[2].atom_syntax_object().unwrap())?;
×
221

222
        // Specialize SUB1 -> specializing here is a bit much but it should help
223
        // if value == SteelVal::IntV(1) && op == OpCode::SUBREGISTER {
224
        //     self.push(LabeledInstruction::builder(OpCode::SUBREGISTER1));
225

226
        //     if let Some(analysis) = &l.args[1]
227
        //         .atom_syntax_object()
228
        //         .and_then(|a| self.analysis.get(&a))
229
        //     {
230
        //         self.push(
231
        //             LabeledInstruction::builder(OpCode::PASS)
232
        //                 .payload(analysis.stack_offset.unwrap()),
233
        //         );
234
        //     } else {
235
        //         panic!("Shouldn't be happening")
236
        //     }
237

238
        //     return Ok(());
239
        // }
240

241
        let analysis = &l.args[1]
×
242
            .atom_syntax_object()
×
243
            .and_then(|a| self.analysis.get(a))?;
×
244

245
        let offset = analysis.stack_offset?;
×
246

247
        self.push(LabeledInstruction::builder(op).payload(offset as _));
×
248

249
        // if let Some(analysis) =
250
        // {
251
        //     if let Some(offset) = analysis.stack_offset {
252
        //         self.push(LabeledInstruction::builder(op).payload(offset));
253
        //     } else {
254
        //         return None;
255
        //     }
256
        // } else {
257
        //     panic!("Shouldn't be happening")
258
        // }
259

260
        // local variable, map to index:
261
        // if let ExprKind::Atom(a) = &l.args[1] {
262
        //     if let Some(analysis) = self.analysis.get(&a.syn) {
263
        //         self.push(
264
        //             LabeledInstruction::builder(OpCode::PASS)
265
        //                 .payload(analysis.stack_offset.unwrap()),
266
        //         );
267
        //     } else {
268
        //         panic!("Shouldn't be getting here")
269
        //     }
270
        // } else {
271
        //     panic!("Shouldn't be getting here")
272
        // }
273

274
        let idx = self.constant_map.add_or_get(value);
×
275

276
        // println!("Index: {:?}", idx);
277

278
        self.push(LabeledInstruction::builder(OpCode::PASS).payload(idx));
×
279

280
        Some(())
×
281

282
        // Ok(())
283
    }
284
}
285

286
impl<'a> VisitorMut for CodeGenerator<'a> {
287
    type Output = Result<()>;
288

289
    // TODO come back later and resolve this properly using labels
290
    // If looks like label resolution needs to be able to arbitrarily point to a label one
291
    // instruction after
292
    fn visit_if(&mut self, f: &crate::parser::ast::If) -> Self::Output {
111,068✔
293
        // load in the test condition
294
        self.visit(&f.test_expr)?;
333,204✔
295
        // Get the if index
296
        let if_idx = self.instructions.len();
111,068✔
297
        // push in if
298
        self.push(LabeledInstruction::builder(OpCode::IF).payload(self.instructions.len() + 2));
×
299
        // save spot of jump instruction, fill in after
300
        // let idx = self.len();
301
        // self.push(Instruction::new_jmp(0)); // dummy value
302

303
        // emit instructions for then
304
        self.visit(&f.then_expr)?;
×
305
        self.push(LabeledInstruction::builder(OpCode::JMP));
111,068✔
306
        let false_start = self.len();
×
307

308
        // emit instructions for else expression
309
        self.visit(&f.else_expr)?;
×
310
        let j3 = self.len(); // first instruction after else
111,068✔
311

312
        // println!("false_start: {:?}", false_start);
313
        // println!("j3: {:?}", j3);
314

315
        // // set index of jump instruction
316
        // if let Some(elem) = self.instructions.get_mut(idx) {
317
        //     (*elem).payload_size = false_start;
318
        // } else {
319
        //     stop!(Generic => "out of bounds jump");
320
        // }
321

322
        // if let Some(potential_pop_instr) = self.instructions.get(j3 - 1) {
323
        //     if potential_pop_instr.op_code == OpCode::POPPURE {
324
        //         println!("Found a pop instruction: {:?}", potential_pop_instr);
325
        //     }
326
        // }
327

328
        if let Some(elem) = self.instructions.get_mut(false_start - 1) {
111,068✔
329
            elem.payload_size = u24::from_usize(j3);
×
330
            // (*elem).payload_size = false_start;
331
        } else {
332
            stop!(Generic => "out of bounds jump");
×
333
        }
334

335
        if let Some(elem) = self.instructions.get_mut(if_idx) {
111,068✔
336
            elem.payload_size = u24::from_usize(false_start);
×
337
            // (*elem).payload_size = false_start;
338
        } else {
339
            stop!(Generic => "out of bounds jump");
×
340
        }
341

342
        Ok(())
×
343

344
        // self.visit(&f.test_expr)?;
345

346
        // let if_idx = self.instructions.len();
347

348
        // self.push(LabeledInstruction::builder(OpCode::IF).payload(self.instructions.len() + 2));
349

350
        // self.visit(&f.then_expr)?;
351

352
        // let false_start_label = fresh();
353
        // let j3_label = fresh();
354

355
        // self.push(LabeledInstruction::builder(OpCode::JMP).goto(j3_label));
356

357
        // let false_start = self.len(); // index after the jump
358

359
        // // self.instructions
360
        // //     .last_mut()
361
        // //     .unwrap()
362
        // //     .set_tag(false_start_label);
363

364
        // self.visit(&f.else_expr)?;
365

366
        // self.instructions.last_mut().unwrap().set_tag(j3_label);
367

368
        // // if let Some(elem) = self.instructions.get_mut(false_start - 1) {
369
        // //     // (*elem).goto = Some(j3);
370

371
        // //     elem.set_goto(j3_label);
372

373
        // //     // (*elem).payload_size = false_start;
374
        // // } else {
375
        // //     stop!(Generic => "out of bounds jump");
376
        // // }
377

378
        // if let Some(elem) = self.instructions.get_mut(if_idx) {
379
        //     // (*elem).goto = Some(false_start_label);
380

381
        //     elem.set_goto(false_start_label);
382

383
        //     // (*elem).payload_size = false_start;
384
        // } else {
385
        //     stop!(Generic => "out of bounds jump");
386
        // }
387

388
        // Ok(())
389
    }
390

391
    fn visit_define(&mut self, define: &crate::parser::ast::Define) -> Self::Output {
117,487✔
392
        // let sidx = self.len();
393

394
        if let ExprKind::Atom(name) = &define.name {
234,974✔
395
            self.push(LabeledInstruction::builder(OpCode::SDEF).contents(name.syn.clone()));
822,409✔
396

397
            self.visit(&define.body)?;
352,461✔
398

399
            // let defn_body_size = self.len() - sidx;
400

401
            // TODO: Consider whether SDEF and EDEF are even necessary at all
402
            // Just remove them otherwise
403
            self.push(LabeledInstruction::builder(OpCode::EDEF));
117,487✔
404

405
            // println!("binding global: {}", name);
406
            self.push(LabeledInstruction::builder(OpCode::BIND).contents(name.syn.clone()));
×
407

408
            self.push(LabeledInstruction::builder(OpCode::VOID));
×
409
        } else {
410
            panic!(
×
411
                "Complex defines not supported in bytecode generation: {}",
412
                define.name
×
413
            )
414
        }
415

416
        Ok(())
×
417
    }
418

419
    fn visit_lambda_function(
98,811✔
420
        &mut self,
421
        lambda_function: &crate::parser::ast::LambdaFunction,
422
    ) -> Self::Output {
423
        let idx = self.len();
296,433✔
424

425
        // Grab the function information from the analysis - this is going to tell us what the captured
426
        // vars are, and subsequently how to compile the let for this use case
427
        let function_info = self
197,622✔
428
            .analysis
98,811✔
429
            .function_info
98,811✔
430
            .get(&lambda_function.syntax_object_id)
197,622✔
431
            .unwrap();
432

433
        // Distinguishing between a pure function and a non pure function will enable
434
        // thinner code for the resulting function
435
        let op_code = if function_info.captured_vars().is_empty() {
296,433✔
436
            OpCode::PUREFUNC
59,844✔
437
        } else {
438
            OpCode::NEWSCLOSURE
38,967✔
439
        };
440

441
        // Attach the debug symbols here
442
        self.push(LabeledInstruction::builder(op_code).contents(lambda_function.location.clone()));
691,677✔
443
        self.push(
197,622✔
444
            LabeledInstruction::builder(OpCode::PASS).payload(usize::from(lambda_function.rest)),
494,055✔
445
        );
446

447
        let arity = lambda_function.args.len();
296,433✔
448

449
        // Patching over the changes, see old code generator for more information
450
        // TODO: This is not a portable change. Syntax Object IDs need to have a patched unique ID
451
        // This should be doable by supplementing everything with an offset when consuming external modules
452
        // self.push(
453
        //     LabeledInstruction::builder(OpCode::PASS).payload(lambda_function.syntax_object_id),
454
        // );
455

456
        self.push(LabeledInstruction::builder(OpCode::PASS).payload(fresh_function_id()));
592,866✔
457

458
        // Save how many locals we have, for when we hit lets
459
        self.local_count.push(arity);
296,433✔
460

461
        let mut body_instructions = {
98,811✔
462
            let mut code_gen = CodeGenerator::new(self.constant_map, self.analysis);
395,244✔
463
            code_gen.visit(&lambda_function.body)?;
296,433✔
464
            code_gen.instructions
×
465
        };
466

467
        // In the event we actually have a closure, we need to add the necessarily
468
        // boilerplate to lift out closed over variables since they could escape
469
        if op_code == OpCode::NEWSCLOSURE {
×
470
            // Mark the upvalues here
471
            self.push(
77,934✔
472
                LabeledInstruction::builder(OpCode::NDEFS)
77,934✔
473
                    .payload(function_info.captured_vars().len()),
116,901✔
474
            );
475

476
            let mut vars = function_info.captured_vars().iter().collect::<Vec<_>>();
155,868✔
477

478
            vars.sort_by_key(|x| x.1.id);
77,934✔
479

480
            // vars.sort_by_key(|x| x.stack_offset);
481

482
            // Here we're going to explicitly capture from either the enclosing scope
483
            // or the stack. For example:
484
            //
485
            // (lambda (x)
486
            //      (lambda (y)
487
            //            (+ x y)))
488
            //
489
            // The inner lambda here will capture x from the stack, since that
490
            // is the environment in which it was immediately available.
491
            // Whereas:
492
            //
493
            // (lambda (x)
494
            //      (lambda (y)
495
            //            (lambda (z)
496
            //                   (+ x y z))))
497
            //
498
            // The innermost lambda is going to have to capture x and y from the closure above it,
499
            // where they've already been captured.
500
            //
501
            // This way, at closure construction (in the VM) we can immediately patch in the kind
502
            // of closure that we want to create, and where to get it
503
            for (key, var) in vars {
195,102✔
504
                // If we're patching in from the enclosing, check to see if this is a heap allocated var that
505
                // we need to patch in to the current scope
506
                if var.captured_from_enclosing {
52,045✔
507
                    if var.mutated {
7,870✔
508
                        self.push(
×
509
                            LabeledInstruction::builder(OpCode::COPYHEAPCAPTURECLOSURE)
×
510
                                .payload(var.parent_heap_offset.unwrap() as _)
×
511
                                .contents(SyntaxObject::default(TokenType::Identifier(*key))),
×
512
                        );
513
                    } else {
514
                        // In this case we're gonna patch in the variable from the current captured scope
515
                        self.push(
7,870✔
516
                            LabeledInstruction::builder(OpCode::COPYCAPTURECLOSURE)
7,870✔
517
                                .payload(var.capture_offset.unwrap() as _)
7,870✔
518
                                .contents(SyntaxObject::default(TokenType::Identifier(*key))),
7,870✔
519
                        );
520
                    }
521
                } else if var.mutated {
44,175✔
522
                    self.push(
×
523
                        LabeledInstruction::builder(OpCode::FIRSTCOPYHEAPCAPTURECLOSURE)
×
524
                            .payload(var.heap_offset.unwrap() as _)
×
525
                            .contents(SyntaxObject::default(TokenType::Identifier(*key))),
×
526
                    );
527
                } else {
528
                    // In this case, it hasn't yet been captured, so we'll just capture
529
                    // directly from the stack
530
                    self.push(
88,350✔
531
                        LabeledInstruction::builder(OpCode::COPYCAPTURESTACK)
×
532
                            .payload(var.stack_offset.ok_or_else(crate::throw!(Generic => format!("Error compiling this function - are you missing an expression after a local define?"); lambda_function.location.span))? as _)
×
533
                            .contents(SyntaxObject::default(TokenType::Identifier(*key))),
×
534
                    );
535
                }
536
            }
537
        }
538

539
        let pop_op_code = OpCode::POPPURE;
98,811✔
540

541
        // Elide the POPN if the POPPURE will take care of it
542
        if let Some(x) = body_instructions.last_mut() {
98,811✔
543
            if x.op_code == OpCode::POPN {
×
544
                x.op_code = OpCode::PASS;
×
545
            }
546
        }
547

548
        let size = body_instructions.len();
×
549

550
        for instr in &mut body_instructions {
8,005,741✔
551
            if instr.op_code == OpCode::JMP && instr.payload_size.to_usize() == size {
256,165✔
552
                instr.op_code = OpCode::POPJMP;
32,127✔
553
            }
554
        }
555

556
        body_instructions
×
557
            .push(LabeledInstruction::builder(pop_op_code).payload(lambda_function.args.len()));
×
558

559
        // Load in the heap alloc instructions - on each invocation we can copy in to the current frame
560
        {
561
            let mut captured_mutable_arguments = function_info
×
562
                .arguments()
563
                .iter()
564
                // .values()
565
                .filter(|x| x.1.captured && x.1.mutated)
126,199✔
566
                .collect::<Vec<_>>();
567

568
            captured_mutable_arguments.sort_by_key(|x| x.1.stack_offset);
×
569

570
            for (key, var) in captured_mutable_arguments {
98,811✔
571
                self.push(
×
572
                    LabeledInstruction::builder(OpCode::ALLOC)
×
573
                        .payload(var.stack_offset.unwrap() as _)
×
574
                        .contents(SyntaxObject::default(TokenType::Identifier(*key))),
×
575
                );
576
            }
577
        }
578

579
        self.instructions.append(&mut body_instructions);
×
580

581
        // pop off the local variables from the run time stack, so we don't have them
582

583
        let closure_body_size = self.len() - idx;
×
584
        self.push(LabeledInstruction::builder(OpCode::ECLOSURE).payload(arity));
×
585

586
        if let Some(elem) = self.instructions.get_mut(idx) {
98,811✔
587
            elem.payload_size = u24::from_usize(closure_body_size);
×
588
        } else {
589
            stop!(Generic => "out of bounds closure len");
×
590
        }
591

592
        self.local_count.pop();
×
593

594
        Ok(())
×
595
    }
596

597
    fn visit_begin(&mut self, begin: &crate::parser::ast::Begin) -> Self::Output {
48,316✔
598
        if begin.exprs.is_empty() {
96,632✔
599
            self.push(LabeledInstruction::builder(OpCode::VOID));
248✔
600
            return Ok(());
62✔
601
        }
602

603
        let (last, elements) = begin.exprs.split_last().unwrap();
×
604

605
        // Just insert a single pop value from stack
606
        // when
607
        // for expr in &begin.exprs {
608
        //     self.visit(expr)?;
609
        // }
610

611
        for expr in elements {
352,504✔
612
            self.visit(expr)?;
456,375✔
613
            self.push(LabeledInstruction::builder(OpCode::POPSINGLE));
152,125✔
614
        }
615

616
        self.visit(last)?;
48,254✔
617

618
        // if begin.exprs.len() > 1 {
619
        //     self.push(LabeledInstruction::builder(OpCode::POPN).payload(begin.exprs.len() - 1));
620
        // }
621

622
        Ok(())
48,254✔
623
    }
624

625
    fn visit_return(&mut self, r: &crate::parser::ast::Return) -> Self::Output {
452✔
626
        self.visit(&r.expr)?;
1,356✔
627
        self.push(LabeledInstruction::builder(OpCode::POPPURE));
452✔
628
        Ok(())
×
629
    }
630

631
    fn visit_quote(&mut self, quote: &crate::parser::ast::Quote) -> Self::Output {
255,574✔
632
        let converted =
255,574✔
633
            SteelVal::try_from(crate::parser::ast::ExprKind::Quote(Box::new(quote.clone())))?;
766,722✔
634

635
        let idx = self.constant_map.add_or_get(converted);
×
636
        self.push(
×
637
            LabeledInstruction::builder(OpCode::PUSHCONST)
×
638
                .payload(idx)
×
639
                // TODO: This is a little suspect, we're doing a bunch of stuff twice
640
                // that we really don't need. In fact, we probably can get away with just...
641
                // embedding the steel val directly here.
642
                .list_contents(crate::parser::ast::ExprKind::Quote(Box::new(quote.clone()))),
×
643
        );
644

645
        Ok(())
×
646
    }
647

648
    fn visit_macro(&mut self, m: &crate::parser::ast::Macro) -> Self::Output {
×
649
        stop!(BadSyntax => format!("unexpected macro definition: {}", m); m.location.span)
×
650
    }
651

652
    fn visit_atom(&mut self, a: &crate::parser::ast::Atom) -> Self::Output {
1,685,008✔
653
        if let Some(analysis) = self.analysis.get(&a.syn) {
4,899,377✔
654
            let op_code = match (&analysis.kind, analysis.last_usage) {
1,529,361✔
655
                (Global, _) => OpCode::PUSH,
755,864✔
656
                (Local, true) | (LetVar, true) => OpCode::MOVEREADLOCAL,
247,825✔
657
                // (Local, true) | (LetVar, true) => OpCode::READLOCAL,
658
                (Local, false) | (LetVar, false) => OpCode::READLOCAL,
311,704✔
659

660
                (LocallyDefinedFunction, _) => {
×
661
                    stop!(Generic => "Unable to lower to bytecode: locally defined function should be lifted to an external scope")
×
662
                }
663
                // In the event we're captured, we're going to just read the
664
                // offset from the captured var
665
                (Captured, _) => OpCode::READCAPTURED,
56,150✔
666
                (Free, _) => OpCode::PUSH,
157,818✔
667
                (HeapAllocated, _) => OpCode::READALLOC,
×
668
                // This is technically true, but in an incremental compilation mode, we assume the variable is already bound
669
                // stop!(FreeIdentifier => format!("free identifier: {}", a); a.syn.span),
670
            };
671

672
            // println!("Atom: {}", a);
673
            // println!("{:#?}", analysis);
674

675
            let payload = match op_code {
×
676
                OpCode::READCAPTURED => analysis.read_capture_offset.unwrap(),
56,150✔
677
                OpCode::READALLOC => analysis.read_heap_offset.unwrap(),
×
678
                _ => analysis.stack_offset.unwrap_or_default(),
2,946,422✔
679
            };
680

681
            self.push(
×
682
                LabeledInstruction::builder(op_code)
×
683
                    .payload(payload as _)
×
684
                    .contents(a.syn.clone()),
×
685
            );
686

687
            Ok(())
×
688
        } else {
689
            self.specialize_constant(&a.syn)
466,941✔
690
        }
691
    }
692

693
    // TODO: Specialize the calls to binops here
694
    // This should be pretty straightforward - just check if they're still globals
695
    // then, specialize accordingly.
696
    fn visit_list(&mut self, l: &crate::parser::ast::List) -> Self::Output {
828,843✔
697
        if let Some(op) = self.specialize_immediate(l) {
1,661,794✔
698
            match self.specialize_immediate_call(l, op) {
×
699
                Some(r) => return Ok(r),
6,862✔
700
                None => {}
677✔
701
            }
702
        } else if let Some(op) = self.should_specialize_call(l) {
1,649,470✔
703
            match self.specialize_call(l, op) {
×
704
                Some(r) => return Ok(r),
×
705
                None => {}
×
706
            }
707
        }
708

709
        if l.args.is_empty() {
825,412✔
710
            stop!(BadSyntax => "function application empty");
×
711
        }
712

713
        let pop_len = if !l.args.is_empty() {
×
714
            l.args[1..].len()
×
715
        } else {
716
            0
×
717
        };
718

719
        for expr in &l.args[1..] {
1,343,171✔
720
            self.visit(expr)?;
4,029,513✔
721
        }
722

723
        // emit instructions for the func
724
        self.visit(&l.args[0])?;
825,412✔
725

726
        let contents = if let ExprKind::Atom(Atom { syn: s }) = &l.args[0] {
1,629,675✔
727
            s.clone()
×
728
        } else {
729
            // TODO check span information here by coalescing the entire list
730
            SyntaxObject::new(TokenType::Identifier("lambda".into()), get_span(&l.args[0]))
84,596✔
731
        };
732

733
        if let Some(call_info) = self.analysis.call_info.get(&l.syntax_object_id) {
825,412✔
734
            // TODO: Check the arity of the function call, against the arity
735
            // of the bound identifier. This can probably be included in the analysis
736
            // assuming we have it?
737
            let op_code = match call_info.kind {
×
738
                Normal => OpCode::FUNC,
686,440✔
739
                TailCall => OpCode::TAILCALL,
102,457✔
740
                super::passes::analysis::CallKind::NoArityNormal => OpCode::FUNCNOARITY,
20,992✔
741
                super::passes::analysis::CallKind::NoArityTailCall => OpCode::TAILCALLNOARITY,
10,630✔
742
                super::passes::analysis::CallKind::NoAritySelfTailCall(_) => {
×
743
                    // panic!();
744
                    self.instructions.pop();
8,456✔
745
                    // OpCode::SELFTAILCALLNOARITY
746
                    OpCode::SELFTAILCALLNOARITY
4,228✔
747
                }
748
                // Elide the arity checks, if we can
749
                SelfTailCall(_) => {
×
750
                    // We don't need to push the function onto the stack if we're doing a self
751
                    // tail call
752
                    self.instructions.pop();
1,330✔
753
                    OpCode::TCOJMP
665✔
754
                    // OpCode::TAILCALL
755
                }
756
            };
757

758
            self.push(
×
759
                LabeledInstruction::builder(op_code)
×
760
                    .contents(contents)
×
761
                    .payload(pop_len),
×
762
            );
763

764
            if let SelfTailCall(depth) = call_info.kind {
665✔
765
                self.push(LabeledInstruction::builder(OpCode::PASS).payload(depth - 1));
×
766
            }
767

768
            Ok(())
×
769
        } else {
770
            self.push(
×
771
                LabeledInstruction::builder(OpCode::FUNC)
×
772
                    .contents(contents)
×
773
                    .payload(pop_len),
×
774
            );
775

776
            Ok(())
×
777

778
            // stop!(Generic => "Unable to find analysis information for call site!")
779
        }
780
    }
781

782
    fn visit_syntax_rules(&mut self, l: &crate::parser::ast::SyntaxRules) -> Self::Output {
×
783
        stop!(BadSyntax => "unexpected syntax rules"; l.location.span)
×
784
    }
785

786
    fn visit_set(&mut self, s: &crate::parser::ast::Set) -> Self::Output {
16,350✔
787
        // stop!(BadSyntax => "set! is currently not implemented"; s.location.span);
788

789
        self.visit(&s.expr)?;
49,050✔
790

791
        let a = s.variable.atom_syntax_object().unwrap();
16,350✔
792

793
        if let Some(analysis) = self.analysis.get(a) {
16,350✔
794
            // TODO: We really want to fail as early as possible, but lets just try this here:
795
            if analysis.builtin {
×
796
                stop!(Generic => "set!: cannot mutate module-required identifier"; s.location.span);
×
797
            }
798

799
            let op_code = match &analysis.kind {
16,350✔
800
                Global => OpCode::SET,
15,524✔
801
                Local | LetVar => OpCode::SETLOCAL,
62✔
802

803
                LocallyDefinedFunction => {
×
804
                    stop!(Generic => "Unable to lower to bytecode: locally defined function should be lifted to an external scope")
×
805
                }
806
                // In the event we're captured, we're going to just read the
807
                // offset from the captured var
808
                Captured => {
×
809
                    stop!(Generic => "Compiler error: Should not be able to set! an immutably captured variable")
×
810
                }
811
                Free => OpCode::SET,
764✔
812
                HeapAllocated => OpCode::SETALLOC,
×
813
                // This is technically true, but in an incremental compilation mode, we assume the variable is already bound
814
                // stop!(FreeIdentifier => format!("free identifier: {}", a); a.syn.span),
815
            };
816

817
            let payload = match op_code {
×
818
                // OpCode::SETCAPTURED => analysis.read_capture_offset.unwrap(),
819
                OpCode::SETALLOC => analysis.read_heap_offset.unwrap(),
×
820
                _ => analysis.stack_offset.unwrap_or_default(),
32,700✔
821
            };
822

823
            self.push(
×
824
                LabeledInstruction::builder(op_code)
×
825
                    .payload(payload as _)
×
826
                    .contents(a.clone()),
×
827
            );
828

829
            Ok(())
×
830
        } else {
831
            stop!(Generic => "Something went wrong with getting the var in set!"; s.location.span);
×
832
        }
833
    }
834

835
    fn visit_require(&mut self, r: &crate::parser::ast::Require) -> Self::Output {
×
836
        stop!(BadSyntax => "unexpected require statement in code gen"; r.location.span)
×
837
    }
838

839
    fn visit_let(&mut self, l: &crate::parser::ast::Let) -> Self::Output {
103,453✔
840
        // What we're gonna do here is pretty straight forward:
841
        // Since we're entering a scope, we need to include the code to remove from this from
842
        // the stack as well
843
        //
844
        // Otherwise, the machinery is more or less the same as before - we're just not going to
845
        // enter a new section of bytecode, its all going to live under the same block.
846
        //
847
        // (let ((a 10) (b 20))
848
        //      (+ 1 2 3 4 5) <--- Could get dropped immediately, but for now it does not
849
        //      (+ 2 3 4 5 6) <--|
850
        //      (+ a b))
851
        //
852
        // This should result in something like
853
        // PUSHCONST 10
854
        // PUSHCONST 20
855
        // READLOCAL a
856
        // READLOCAL b
857
        // PUSH +
858
        // FUNC 2
859
        // LETENDSCOPE 0 <- index of the stack when we entered this let expr
860

861
        // if !l.bindings.is_empty() {
862
        self.push(
206,906✔
863
            LabeledInstruction::builder(OpCode::BEGINSCOPE)
206,906✔
864
                .payload(*self.local_count.last().unwrap_or(&0)),
310,359✔
865
        );
866
        // }
867

868
        let info = self
206,906✔
869
            .analysis
103,453✔
870
            .let_info
103,453✔
871
            .get(&l.syntax_object_id)
206,906✔
872
            .expect("Missing analysis information for let");
873

874
        // Push the scope + the number of arguments in this scope
875
        // self.push(LabeledInstruction::builder(OpCode::BEGINSCOPE).payload(l.bindings.len()));
876
        // self.push(LabeledInstruction::builder(OpCode::BEGINSCOPE).payload(info.stack_offset));
877

878
        // We just assume these will live on the stack at whatever position we're entering now
879
        for expr in l.expression_arguments() {
372,760✔
880
            self.visit(expr)?;
497,562✔
881
            // For the JIT -> push the last instruction to the internal scope
882
            // TODO: Rename from BEGINSCOPE to something like MARKLETVAR
883
            // self.push(LabeledInstruction::builder(OpCode::LetVar));
884
        }
885

886
        let mut heap_allocated_arguments = info
103,453✔
887
            .arguments
×
888
            .values()
889
            .filter(|x| x.captured && x.mutated)
185,614✔
890
            .collect::<SmallVec<[_; 8]>>();
891

892
        heap_allocated_arguments.sort_by_key(|x| x.stack_offset);
×
893

894
        for var in heap_allocated_arguments {
103,453✔
895
            // println!("Found a var that is both mutated and captured");
896
            // println!("{:#?}", var);
897

898
            self.push(
×
899
                LabeledInstruction::builder(OpCode::ALLOC).payload(var.stack_offset.unwrap() as _),
×
900
            );
901
        }
902

903
        self.visit(&l.body_expr)?;
×
904

905
        // TODO:
906
        // It is possible, that during the course of execution, local variables get captured.
907
        // For example:
908
        //
909
        // (let ((a 10) (b 20))
910
        //      (lambda (x) (+ x a b)))
911
        //
912
        // In this case, at the end of the let scope, we can move those variables
913
        // into the slot that exists on the heap - and local variable references internally
914
        // in the function will refer to the local function slots, rather than the spots on the stack
915
        //
916
        // Having things on the stack will make things much faster. Lets try to keep them there.
917
        //
918
        // The let will have to keep track of if any of these values are captured, and if they are
919
        // just insert an instruction to close that upvalue
920

921
        self.push(LabeledInstruction::builder(OpCode::LETENDSCOPE).payload(info.stack_offset));
103,453✔
922

923
        Ok(())
×
924
    }
925

926
    fn visit_vector(&mut self, v: &crate::parser::ast::Vector) -> Self::Output {
159✔
927
        for arg in &v.args {
971✔
928
            if v.bytes {
406✔
929
                self.visit(arg)?;
240✔
930
            } else {
931
                let converted = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(arg.clone())?;
652✔
932

933
                let idx = self.constant_map.add_or_get(converted);
×
934
                self.push(LabeledInstruction::builder(OpCode::PUSHCONST).payload(idx));
×
935
            }
936
        }
937

938
        let payload_size = 2 * v.args.len() + usize::from(v.bytes);
159✔
939
        self.push(LabeledInstruction::builder(OpCode::VEC).payload(payload_size));
×
940

941
        Ok(())
×
942
    }
943
}
944

945
#[cfg(test)]
946
mod code_gen_tests {
947
    use super::*;
948

949
    use crate::parser::parser::Parser;
950

951
    #[test]
952
    fn check_function_calls() {
953
        let expr = r#"
954
        ;; (define fib (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))
955

956
        (fib 10)
957
            "#;
958

959
        let exprs = Parser::parse(expr).unwrap();
960
        let mut analysis = Analysis::from_exprs(&exprs);
961
        analysis.populate_captures(&exprs);
962

963
        let mut constants = ConstantMap::new();
964

965
        let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
966

967
        code_gen.visit(&exprs[0]).unwrap();
968
        // code_gen.visit(&exprs[1]).unwrap();
969

970
        println!("{:#?}", code_gen.instructions);
971
    }
972

973
    #[test]
974
    fn check_fib() {
975
        let expr = r#"
976
        (define fib (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))
977

978
        (fib 10)
979
            "#;
980

981
        let exprs = Parser::parse(expr).unwrap();
982
        let mut analysis = Analysis::from_exprs(&exprs);
983
        analysis.populate_captures(&exprs);
984

985
        let mut constants = ConstantMap::new();
986

987
        let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
988

989
        code_gen.visit(&exprs[0]).unwrap();
990
        code_gen.visit(&exprs[1]).unwrap();
991

992
        println!("{:#?}", code_gen.instructions);
993
    }
994

995
    #[test]
996
    fn check_new_closure() {
997
        let expr = r#"
998
            (lambda (x)
999
                (lambda (y) (+ x y)))
1000
        "#;
1001

1002
        let exprs = Parser::parse(expr).unwrap();
1003
        let mut analysis = Analysis::from_exprs(&exprs);
1004
        analysis.populate_captures(&exprs);
1005

1006
        let mut constants = ConstantMap::new();
1007

1008
        let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
1009
        code_gen.visit(&exprs[0]).unwrap();
1010
        println!("{:#?}", code_gen.instructions);
1011
    }
1012

1013
    #[test]
1014
    fn check_lambda_output() {
1015
        let expr = r#"
1016
        (lambda (x y z)
1017
            (+ x y z))
1018
        "#;
1019

1020
        let exprs = Parser::parse(expr).unwrap();
1021

1022
        let mut analysis = Analysis::from_exprs(&exprs);
1023
        analysis.populate_captures(&exprs);
1024

1025
        let mut constants = ConstantMap::new();
1026

1027
        let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
1028

1029
        code_gen.visit(&exprs[0]).unwrap();
1030

1031
        println!("{:#?}", code_gen.instructions);
1032

1033
        let expected = vec![
1034
            (OpCode::PUREFUNC, 9), // This captures no variables - should be able to be lifted as well
1035
            (OpCode::PASS, 0),     // multi arity
1036
            (OpCode::PASS, 0),     // This shouldn't need to be here
1037
            (OpCode::MOVEREADLOCAL, 0), // last usage of x, first var
1038
            (OpCode::MOVEREADLOCAL, 1), // last usage of y
1039
            (OpCode::MOVEREADLOCAL, 2), // last usage of z
1040
            (OpCode::PUSH, 0), // + is a global, that is late bound and the index is resolved later
1041
            (OpCode::TAILCALL, 3), // tail call function, with 3 arguments
1042
            (OpCode::POPPURE, 3), // Pop 3 arguments
1043
            (OpCode::ECLOSURE, 3), // Something about 3 arguments...
1044
        ];
1045

1046
        let mut found = code_gen
1047
            .instructions
1048
            .iter()
1049
            .map(|x| (x.op_code, x.payload_size.to_usize()))
1050
            .collect::<Vec<_>>();
1051

1052
        // Wipe out the syntax object id from the PASS
1053
        found[2].1 = 0;
1054

1055
        assert_eq!(expected, found);
1056
    }
1057

1058
    #[test]
1059
    fn check_let_captured_var() {
1060
        let expr = r#"
1061
        (%plain-let ((a 10) (b 20))
1062
            (lambda () (+ a b)))
1063
        "#;
1064

1065
        let exprs = Parser::parse(expr).unwrap();
1066

1067
        let mut analysis = Analysis::from_exprs(&exprs);
1068
        analysis.populate_captures(&exprs);
1069

1070
        let mut constants = ConstantMap::new();
1071

1072
        let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
1073

1074
        code_gen.visit(&exprs[0]).unwrap();
1075
    }
1076

1077
    // #[test]
1078
    // fn check_let_output() {
1079
    //     let expr = r#"
1080
    //         (%plain-let ((a 10) (b 20))
1081
    //             (+ a b))
1082
    //     "#;
1083

1084
    //     let exprs = Parser::parse(expr).unwrap();
1085

1086
    //     let analysis = Analysis::from_exprs(&exprs);
1087
    //     let mut constants = ConstantMap::new();
1088

1089
    //     let mut code_gen = CodeGenerator::new(&mut constants, &analysis);
1090

1091
    //     code_gen.visit(&exprs[0]).unwrap();
1092

1093
    //     println!("{:#?}", code_gen.instructions);
1094

1095
    //     let expected = vec![
1096
    //         (OpCode::BEGINSCOPE, 0),
1097
    //         (OpCode::PUSHCONST, 0),   // Should be the only constant in the map
1098
    //         (OpCode::PUSHCONST, 1),   // Should be the second constant in the map
1099
    //         (OpCode::READLOCAL, 0),   // Corresponds to index 0
1100
    //         (OpCode::READLOCAL, 1),   // Corresponds to index 1
1101
    //         (OpCode::PUSH, 0), // + is a global, that is late bound and the index is resolved later
1102
    //         (OpCode::FUNC, 2), // Function call with 2 arguments
1103
    //         (OpCode::LETENDSCOPE, 2), // Exit the let scope and drop the vars and anything above it
1104
    //     ];
1105

1106
    //     let found = code_gen
1107
    //         .instructions
1108
    //         .iter()
1109
    //         .map(|x| (x.op_code, x.payload_size))
1110
    //         .collect::<Vec<_>>();
1111

1112
    //     assert_eq!(expected, found);
1113
    // }
1114
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc