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

mattwparas / steel / 11811241505

13 Nov 2024 05:29AM UTC coverage: 46.964% (-0.03%) from 46.995%
11811241505

push

github

web-flow
Reduce allocations during compilation (#289)

* first pass and reducing allocations

* use compact string in begin lowering

* cleanup

* clean up

* use arc in proptest

* add another case for the doc macro

* hide doc values in the lsp

97 of 175 new or added lines in 22 files covered. (55.43%)

44 existing lines in 8 files now uncovered.

12443 of 26495 relevant lines covered (46.96%)

538832.32 hits per line

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

65.72
/crates/steel-core/src/compiler/program.rs
1
use crate::core::instructions::u24;
2
use crate::core::labels::Expr;
3
use crate::gc::Shared;
4
use crate::parser::span_visitor::get_span;
5
use crate::rvals::{Result, SteelComplex};
6
use crate::{
7
    compiler::constants::ConstantMap,
8
    core::{instructions::Instruction, opcode::OpCode},
9
    stop, SteelVal,
10
};
11
use crate::{core::instructions::DenseInstruction, parser::span::Span};
12
use crate::{
13
    parser::{
14
        ast::ExprKind,
15
        interner::InternedString,
16
        parser::{RawSyntaxObject, SyntaxObject},
17
        tokens::TokenType,
18
    },
19
    rvals::IntoSteelVal,
20
};
21

22
use num::{BigInt, BigRational, Rational32};
23
use serde::{Deserialize, Serialize};
24
use std::{collections::HashMap, convert::TryInto, time::SystemTime};
25
use steel_parser::tokens::{IntLiteral, NumberLiteral, RealLiteral};
26

27
#[cfg(feature = "profiling")]
28
use std::time::Instant;
29

30
#[cfg(feature = "profiling")]
31
use log::{debug, log_enabled};
32

33
use super::{compiler::DebruijnIndicesInterner, map::SymbolMap};
34

35
const _TILE_SUPER_INSTRUCTIONS: bool = true;
36

37
pub fn number_literal_to_steel(n: &NumberLiteral) -> Result<SteelVal> {
113,535✔
38
    // real_to_steel does some cloning of bignums. It may be possible to optimize this away.
39
    let real_to_steel = |re: &RealLiteral| match re {
339,435✔
40
        RealLiteral::Int(IntLiteral::Small(i)) => i.into_steelval(),
111,766✔
41
        RealLiteral::Int(IntLiteral::Big(i)) => i.clone().into_steelval(),
280✔
42
        RealLiteral::Rational(n, d) => match (n, d) {
470✔
43
            (IntLiteral::Small(n), IntLiteral::Small(d)) => {
450✔
44
                match (i32::try_from(*n), i32::try_from(*d)) {
450✔
45
                    (Ok(n), Ok(0)) => {
×
46
                        stop!(BadSyntax => format!("division by zero in {:?}/0", n))
×
47
                    }
48
                    (Ok(n), Ok(d)) => Rational32::new(n, d).into_steelval(),
430✔
49
                    _ => BigRational::new(BigInt::from(*n), BigInt::from(*d)).into_steelval(),
20✔
50
                }
51
            }
52
            (IntLiteral::Small(n), IntLiteral::Big(d)) => {
×
53
                BigRational::new(BigInt::from(*n), *d.clone()).into_steelval()
×
54
            }
55
            (IntLiteral::Big(n), IntLiteral::Small(d)) => {
20✔
56
                BigRational::new(*n.clone(), BigInt::from(*d)).into_steelval()
20✔
57
            }
58
            (IntLiteral::Big(n), IntLiteral::Big(d)) => {
×
59
                BigRational::new(*n.clone(), *d.clone()).into_steelval()
×
60
            }
61
        },
62
        RealLiteral::Float(f) => f.into_steelval(),
1,338✔
63
    };
64
    match n {
113,535✔
65
        NumberLiteral::Real(re) => real_to_steel(re),
113,216✔
66
        NumberLiteral::Complex(re, im) => SteelComplex {
67
            re: real_to_steel(re)?,
319✔
68
            im: real_to_steel(im)?,
319✔
69
        }
70
        .into_steelval(),
71
    }
72
}
73

74
/// Evaluates an atom expression in given environment.
75
fn eval_atom(t: &SyntaxObject) -> Result<SteelVal> {
110,780✔
76
    match &t.ty {
110,780✔
77
        TokenType::BooleanLiteral(b) => Ok((*b).into()),
30,155✔
78
        TokenType::Number(n) => number_literal_to_steel(n),
40,189✔
79
        TokenType::StringLiteral(s) => Ok(SteelVal::StringV(s.to_string().into())),
39,526✔
80
        TokenType::CharacterLiteral(c) => Ok(SteelVal::CharV(*c)),
846✔
81
        // TODO: Keywords shouldn't be misused as an expression - only in function calls are keywords allowed
82
        TokenType::Keyword(k) => Ok(SteelVal::SymbolV(k.clone().into())),
64✔
83
        what => {
×
84
            println!("getting here in the eval_atom - code_generator");
×
85
            stop!(UnexpectedToken => what; t.span)
×
86
        }
87
    }
88
}
89

90
pub fn specialize_read_local(instructions: &mut [Instruction]) {
47,946✔
91
    for i in 0..instructions.len() {
3,539,781✔
92
        let read_local = instructions.get(i);
3,491,835✔
93

94
        match read_local {
3,491,835✔
95
            Some(Instruction {
96
                op_code: OpCode::MOVEREADLOCAL,
97
                payload_size,
108,508✔
98
                ..
99
            }) => {
100
                let op_code = match payload_size.to_u32() {
170,527✔
101
                    0 => OpCode::MOVEREADLOCAL0,
31,037✔
102
                    1 => OpCode::MOVEREADLOCAL1,
15,052✔
103
                    2 => OpCode::MOVEREADLOCAL2,
9,237✔
104
                    3 => OpCode::MOVEREADLOCAL3,
6,693✔
105
                    _ => continue,
46,489✔
106
                };
107

108
                if let Some(x) = instructions.get_mut(i) {
62,019✔
109
                    x.op_code = op_code;
110
                }
111
            }
112

113
            Some(Instruction {
114
                op_code: OpCode::READLOCAL,
115
                payload_size,
127,040✔
116
                ..
117
            }) => {
118
                let op_code = match payload_size.to_u32() {
91,232✔
119
                    0 => OpCode::READLOCAL0,
53,579✔
120
                    1 => OpCode::READLOCAL1,
23,340✔
121
                    2 => OpCode::READLOCAL2,
10,102✔
122
                    3 => OpCode::READLOCAL3,
4,211✔
123
                    _ => continue,
35,808✔
124
                };
125

126
                if let Some(x) = instructions.get_mut(i) {
91,232✔
127
                    x.op_code = op_code;
128
                }
129
            } // instructions[i + 1].op_code = OpCode::PASS;
130
            // instructions[i + 2].op_code = OpCode::PASS;
131
            // instructions[i + 4].op_code = OpCode::PASS;
132
            _ => continue,
3,256,287✔
133
        }
134
    }
135
}
136

137
pub fn specialize_constants(instructions: &mut [Instruction]) -> Result<()> {
47,946✔
138
    for instruction in instructions.iter_mut() {
3,539,781✔
139
        match instruction {
110,780✔
140
            Instruction {
141
                op_code: OpCode::PUSHCONST,
142
                contents:
143
                    Some(Expr::Atom(SyntaxObject {
144
                        ty: TokenType::Identifier(_),
145
                        ..
146
                    })),
147
                ..
148
            } => continue,
×
149
            Instruction {
150
                op_code: OpCode::PUSHCONST,
151
                contents: Some(Expr::Atom(syn)),
110,780✔
152
                ..
153
            } => {
154
                let value = eval_atom(syn)?;
110,780✔
155
                let opcode = match &value {
21,913✔
156
                    SteelVal::IntV(0) => OpCode::LOADINT0,
9,801✔
157
                    SteelVal::IntV(1) => OpCode::LOADINT1,
8,450✔
158
                    SteelVal::IntV(2) => OpCode::LOADINT2,
3,662✔
159
                    _ => continue,
88,867✔
160
                };
161
                instruction.op_code = opcode;
162
            }
163
            _ => continue,
3,381,055✔
164
        }
165
    }
166
    Ok(())
47,946✔
167
}
168

169
pub fn convert_call_globals(instructions: &mut [Instruction]) {
23,998✔
170
    if instructions.is_empty() {
23,998✔
171
        return;
×
172
    }
173

174
    for i in 0..instructions.len() - 1 {
1,724,535✔
175
        let push = instructions.get(i);
1,724,535✔
176
        let func = instructions.get(i + 1);
1,724,535✔
177

178
        match (push, func) {
1,724,535✔
179
            (
180
                Some(Instruction {
181
                    op_code: OpCode::PUSH,
182
                    payload_size: index,
258,185✔
183
                    contents: Some(Expr::Atom(ident)),
258,185✔
184
                    ..
258,185✔
185
                }),
186
                Some(Instruction {
258,185✔
187
                    op_code: OpCode::FUNC,
258,185✔
188
                    payload_size: arity,
258,185✔
189
                    ..
258,185✔
190
                }),
191
            ) => {
258,185✔
192
                let arity = arity.to_usize();
258,185✔
193
                let index = *index;
258,185✔
194

195
                if let TokenType::Identifier(ident) = ident.ty {
516,370✔
196
                    match ident {
197
                        _ if ident == *PRIM_CONS_SYMBOL && arity == 2 => {
541✔
198
                            if let Some(x) = instructions.get_mut(i) {
1,082✔
199
                                x.op_code = OpCode::CONS;
200
                                x.payload_size = u24::from_u32(2);
201
                                continue;
202
                            }
203
                        }
204

205
                        // Specialize lists, cons, hashmap, etc. - anything that we expect to be used often in
206
                        // real code.
207
                        // _ if ident == *PRIM_LIST_SYMBOL => {
208
                        //     if let Some(x) = instructions.get_mut(i) {
209
                        //         x.op_code = OpCode::LIST;
210
                        //         x.payload_size = arity;
211
                        //         continue;
212
                        //     }
213
                        // }
214
                        _ if ident == *BOX || ident == *PRIM_BOX && arity == 1 => {
511,269✔
215
                            if let Some(x) = instructions.get_mut(i) {
8,038✔
216
                                x.op_code = OpCode::NEWBOX;
217
                                continue;
218
                            }
219
                        }
220

221
                        _ if ident == *UNBOX || ident == *PRIM_UNBOX && arity == 1 => {
497,411✔
222
                            if let Some(x) = instructions.get_mut(i) {
19,678✔
223
                                x.op_code = OpCode::UNBOX;
224
                                continue;
225
                            }
226
                        }
227

228
                        _ if ident == *SETBOX || ident == *PRIM_SETBOX && arity == 1 => {
483,877✔
229
                            if let Some(x) = instructions.get_mut(i) {
7,390✔
230
                                x.op_code = OpCode::SETBOX;
231
                                continue;
232
                            }
233
                        }
234

235
                        _ if ident == *PRIM_CAR && arity == 1 => {
241,692✔
236
                            if let Some(x) = instructions.get_mut(i) {
3,202✔
237
                                x.op_code = OpCode::CAR;
238
                                continue;
239
                            }
240
                        }
241

242
                        _ if ident == *PRIM_CDR && arity == 1 => {
239,917✔
243
                            if let Some(x) = instructions.get_mut(i) {
2,854✔
244
                                x.op_code = OpCode::CDR;
245
                                continue;
246
                            }
247
                        }
248

249
                        _ if ident == *PRIM_NOT && arity == 1 => {
237,670✔
250
                            if let Some(x) = instructions.get_mut(i) {
1,214✔
251
                                x.op_code = OpCode::NOT;
252
                                continue;
253
                            }
254
                        }
255

256
                        _ if ident == *PRIM_NULL && arity == 1 => {
236,936✔
257
                            if let Some(x) = instructions.get_mut(i) {
960✔
258
                                x.op_code = OpCode::NULL;
259
                                continue;
260
                            }
261
                        }
262

263
                        // _ if ident == *CDR_SYMBOL || ident == *PRIM_CAR => {
264
                        //     if let Some(x) = instructions.get_mut(i) {
265
                        //         x.op_code = OpCode::CAR;
266
                        //         continue;
267
                        //     }
268
                        // }
269
                        _ => {
235,976✔
270
                            // println!("Converting call global: {}", ident);
271
                        }
272
                    }
273
                }
274

275
                // TODO:
276
                if let Some(x) = instructions.get_mut(i) {
471,952✔
277
                    x.op_code = OpCode::CALLGLOBAL;
278
                    x.payload_size = index;
279
                }
280

281
                if let Some(x) = instructions.get_mut(i + 1) {
235,976✔
282
                    // Leave this as the OpCode::FUNC;
283
                    // x.op_code = OpCode::Arity;
284
                    x.payload_size = u24::from_usize(arity);
285
                }
286
            }
287
            (
288
                Some(Instruction {
289
                    op_code: OpCode::PUSH,
290
                    payload_size: index,
38,413✔
291
                    contents: Some(Expr::Atom(ident)),
38,413✔
292
                    ..
38,413✔
293
                }),
294
                Some(Instruction {
38,413✔
295
                    op_code: OpCode::TAILCALL,
38,413✔
296
                    // payload_size: arity,
297
                    ..
38,413✔
298
                }),
299
            ) => {
38,413✔
300
                // let arity = *arity;
301
                let index = *index;
38,413✔
302

303
                if let TokenType::Identifier(ident) = ident.ty {
76,826✔
304
                    match ident {
305
                        _ if ident == *PRIM_CONS_SYMBOL => {
306
                            if let Some(x) = instructions.get_mut(i) {
52✔
307
                                x.op_code = OpCode::CONS;
308
                                x.payload_size = u24::from_u32(2);
309
                                continue;
310
                            }
311
                        }
312

313
                        _ if ident == *BOX || ident == *PRIM_BOX => {
76,774✔
314
                            if let Some(x) = instructions.get_mut(i) {
×
315
                                x.op_code = OpCode::NEWBOX;
316
                                continue;
317
                            }
318
                        }
319

320
                        _ if ident == *UNBOX || ident == *PRIM_UNBOX => {
76,440✔
321
                            if let Some(x) = instructions.get_mut(i) {
668✔
322
                                x.op_code = OpCode::UNBOX;
323
                                continue;
324
                            }
325
                        }
326

327
                        _ if ident == *SETBOX || ident == *PRIM_SETBOX => {
75,768✔
328
                            if let Some(x) = instructions.get_mut(i) {
676✔
329
                                x.op_code = OpCode::SETBOX;
330
                                continue;
331
                            }
332
                        }
333

334
                        _ if ident == *PRIM_CAR => {
37,715✔
335
                            if let Some(x) = instructions.get_mut(i) {
8✔
336
                                x.op_code = OpCode::CAR;
337
                                continue;
338
                            }
339
                        }
340

341
                        // Specialize lists, cons, hashmap, etc. - anything that we expect to be used often in
342
                        // real code.
343
                        _ if ident == *LIST_SYMBOL || ident == *PRIM_LIST_SYMBOL => {}
75,439✔
344

345
                        _ => {}
37,006✔
346
                    }
347
                }
348

349
                if let Some(x) = instructions.get_mut(i) {
75,422✔
350
                    x.op_code = OpCode::CALLGLOBALTAIL;
351
                    x.payload_size = index;
352
                }
353

354
                // if let Some(x) = instructions.get_mut(i + 1) {
355
                // x.op_code = OpCode::Arity;
356
                // x.payload_size = arity;
357
                // }
358
            }
359
            _ => {}
1,427,937✔
360
        }
361
    }
362
}
363

364
#[macro_export]
365
macro_rules! define_primitive_symbols {
366
    ($(($prim_name:tt, $name:tt) => $str:expr,) * ) => {
367
        $(
UNCOV
368
            pub static $name: once_cell::sync::Lazy<InternedString> = once_cell::sync::Lazy::new(|| InternedString::from_static($str));
×
369

370
            pub static $prim_name: once_cell::sync::Lazy<InternedString> = once_cell::sync::Lazy::new(|| InternedString::from_static(concat!("#%prim.", $str)));
44✔
371
        )*
372
    };
373
}
374

375
#[macro_export]
376
macro_rules! define_symbols {
377
    ($($name:tt => $str:expr,) * ) => {
378
        $(
379
            pub static $name: once_cell::sync::Lazy<InternedString> = once_cell::sync::Lazy::new(|| InternedString::from_static($str));
98✔
380
        )*
381
    };
382
}
383

384
define_primitive_symbols! {
385
    (PRIM_PLUS, PLUS) => "+",
386
    (PRIM_MINUS, MINUS) => "-",
387
    (PRIM_DIV, DIV) => "/",
388
    (PRIM_STAR, STAR) => "*",
389
    (PRIM_EQUAL, EQUAL) => "equal?",
390
    (PRIM_NUM_EQUAL, NUM_EQUAL) => "=",
391
    (PRIM_LTE, LTE) => "<=",
392
    (PRIM_CAR, CAR_SYMBOL) => "car",
393
    (PRIM_CDR, CDR_SYMBOL) => "cdr",
394
    (PRIM_NOT, NOT_SYMBOL) => "not",
395
    (PRIM_NULL, NULL_SYMBOL) => "null?",
396
}
397

398
define_symbols! {
399
    UNREADABLE_MODULE_GET => "##__module-get",
400
    STANDARD_MODULE_GET => "%module-get%",
401
    CONTRACT_OUT => "contract/out",
402
    REQUIRE_IDENT_SPEC => "%require-ident-spec",
403
    PROVIDE => "provide",
404
    FOR_SYNTAX => "for-syntax",
405
    PREFIX_IN => "prefix-in",
406
    ONLY_IN => "only-in",
407
    DATUM_SYNTAX => "datum->syntax",
408
    SYNTAX_SPAN => "#%syntax-span",
409
    IF => "if",
410
    DEFINE => "define",
411
    LET => "let",
412
    QUOTE =>"quote",
413
    RETURN => "return!",
414
    REQUIRE => "require",
415
    SET => "set!",
416
    PLAIN_LET => "%plain-let",
417
    LAMBDA => "lambda",
418
    LAMBDA_SYMBOL => "λ",
419
    LAMBDA_FN => "fn",
420
    BEGIN => "begin",
421
    DOC_MACRO => "@doc",
422
    REQUIRE_BUILTIN => "require-builtin",
423
    REQUIRE_DYLIB => "#%require-dylib",
424
    STRUCT_KEYWORD => "struct",
425
    BETTER_LAMBDA => "#%better-lambda",
426
    DEFINE_VALUES => "define-values",
427
    AS_KEYWORD => "as",
428
    SYNTAX_CONST_IF => "syntax-const-if",
429
    UNQUOTE => "unquote",
430
    UNQUOTE_COMMA => "#%unquote-comma",
431
    RAW_UNQUOTE => "#%unquote",
432
    UNQUOTE_SPLICING => "unquote-splicing",
433
    RAW_UNQUOTE_SPLICING => "#%unquote-splicing",
434
    QUASIQUOTE => "quasiquote",
435
    RAW_QUOTE => "#%quote",
436
    QUASISYNTAX => "quasisyntax",
437
    UNSYNTAX => "unsyntax",
438
    RAW_UNSYNTAX => "#%unsyntax",
439
    UNSYNTAX_SPLICING => "unsyntax-splicing",
440
    RAW_UNSYNTAX_SPLICING => "#%unsyntax-splicing",
441
    SYNTAX_QUOTE => "syntax",
442
    CONS_SYMBOL => "cons",
443
    PRIM_CONS_SYMBOL => "#%prim.cons",
444
    LIST_SYMBOL => "list",
445
    PRIM_LIST_SYMBOL => "#%prim.list",
446
    BOX => "#%box",
447
    PRIM_BOX => "#%prim.box",
448
    UNBOX => "#%unbox",
449
    PRIM_UNBOX => "#%prim.unbox",
450
    SETBOX => "#%set-box!",
451
    PRIM_SETBOX => "#%prim.set-box!",
452
    DEFMACRO => "defmacro",
453
    BEGIN_FOR_SYNTAX => "begin-for-syntax",
454
}
455

456
pub fn inline_num_operations(instructions: &mut [Instruction]) {
23,998✔
457
    for i in 0..instructions.len() - 1 {
1,748,533✔
458
        let push = instructions.get(i);
1,724,535✔
459
        let func = instructions.get(i + 1);
1,724,535✔
460

461
        if let (
462
            Some(Instruction {
463
                op_code: OpCode::PUSH,
464
                ..
465
            }),
466
            Some(Instruction {
467
                op_code: OpCode::FUNC | OpCode::TAILCALL,
468
                contents:
469
                    Some(Expr::Atom(RawSyntaxObject {
470
                        ty: TokenType::Identifier(ident),
297,630✔
471
                        ..
297,630✔
472
                    })),
473
                payload_size,
297,630✔
474
                ..
475
            }),
476
        ) = (push, func)
1,724,535✔
477
        {
478
            let payload_size = payload_size.to_u32();
297,630✔
479

480
            let replaced = match *ident {
594,229✔
481
                x if x == *PRIM_PLUS && payload_size == 2 => Some(OpCode::BINOPADD),
297,766✔
482
                x if x == *PRIM_PLUS && payload_size > 0 => Some(OpCode::ADD),
297,606✔
483
                // x if x == *PRIM_MINUS && *payload_size == 2 => Some(OpCode::BINOPSUB),
484
                x if x == *PRIM_MINUS && payload_size > 0 => Some(OpCode::SUB),
297,602✔
485
                x if x == *PRIM_DIV && payload_size > 0 => Some(OpCode::DIV),
297,583✔
486
                x if x == *PRIM_STAR && payload_size > 0 => Some(OpCode::MUL),
297,596✔
487
                x if x == *PRIM_NUM_EQUAL && payload_size == 2 => Some(OpCode::NUMEQUAL),
300,188✔
488
                x if x == *PRIM_EQUAL && payload_size > 0 => Some(OpCode::EQUAL),
296,955✔
489
                x if x == *PRIM_LTE && payload_size > 0 => Some(OpCode::LTE),
296,602✔
490
                _ => None,
296,598✔
491
            };
492

493
            if let Some(new_op_code) = replaced {
1,032✔
494
                // let payload_size = *payload_size;
495
                if let Some(x) = instructions.get_mut(i) {
1,032✔
496
                    x.op_code = new_op_code;
497
                    x.payload_size = u24::from_u32(payload_size);
498
                }
499

500
                if let Some(x) = instructions.get_mut(i + 1) {
1,032✔
501
                    x.op_code = OpCode::PASS;
502
                }
503
            }
504
        }
505
    }
506
}
507

508
pub const fn sequence_to_opcode(pattern: &[(OpCode, usize)]) -> &'static [steel_gen::Pattern] {
×
509
    match pattern {
×
510
        &[(OpCode::MOVEREADLOCAL, _)] => &[steel_gen::Pattern::Single(OpCode::MOVEREADLOCAL)],
×
511
        _ => todo!(),
512
    }
513
}
514

515
#[allow(unused)]
516
pub fn tile_super_instructions(instructions: &mut [Instruction]) {
23,998✔
517
    #[cfg(feature = "dynamic")]
518
    {
519
        pub fn tile<const N: usize>(instructions: &mut [Instruction]) {
23,998✔
520
            // let mut list: List<(usize, OpCode)> = List::new();
521

522
            let mut buffer = [(OpCode::VOID, 0); N];
23,998✔
523

524
            let mut pattern_buffer = Vec::with_capacity(N);
23,998✔
525

526
            // Cell::from_mut()
527

528
            if N > instructions.len() {
23,998✔
529
                return;
23,998✔
530
            }
531

532
            for i in 0..instructions.len() - N {
23,998✔
533
                for j in 0..N {
23,998✔
534
                    buffer[j] = (
23,998✔
535
                        instructions[i + j].op_code,
23,998✔
536
                        instructions[i + j].payload_size,
23,998✔
537
                    );
538
                }
539

540
                // If this is a candidate to match the pattern, let's try to apply it!
541
                if let Some(op_code) = steel_gen::opcode::sequence_to_opcode(&buffer) {
23,998✔
542
                    // Check if this pattern genuinely matches one of the code gen'd ones
543
                    steel_gen::Pattern::from_opcodes_with_buffer(&buffer, &mut pattern_buffer);
23,998✔
544

545
                    if crate::steel_vm::vm::pattern_exists(&pattern_buffer) {
23,998✔
546
                        // log::debug!(target: "super-instructions", "Applying tiling for: {:?}", op_code);
547

548
                        // println!("Applying tiling for: {:?}", op_code);
549
                        // println!("{:?}", pattern_buffer);
550

551
                        instructions[i].op_code = op_code;
23,998✔
552

553
                        continue;
23,998✔
554
                    }
555
                }
556
            }
557

558
            // for (index, op) in list {
559
            //     instructions[index].op_code = op;
560
            // }
561
        }
562

563
        // Super instruction tiling here!
564

565
        if _TILE_SUPER_INSTRUCTIONS {
23,998✔
566
            tile::<11>(instructions);
23,998✔
567
            tile::<10>(instructions);
23,998✔
568
            tile::<9>(instructions);
23,998✔
569
            tile::<8>(instructions);
23,998✔
570
            tile::<7>(instructions);
23,998✔
571
            tile::<6>(instructions);
23,998✔
572
            tile::<5>(instructions);
23,998✔
573
            tile::<4>(instructions);
23,998✔
574
            tile::<3>(instructions);
23,998✔
575
            tile::<2>(instructions);
23,998✔
576
        }
577
    }
578
}
579

580
pub fn merge_conditions_with_if(instructions: &mut [Instruction]) {
23,998✔
581
    for i in 0..instructions.len() - 1 {
1,748,533✔
582
        let condition = instructions.get(i);
1,724,535✔
583
        let guard = instructions.get(i + 2);
1,724,535✔
584

585
        if let (
1,724,535✔
586
            Some(Instruction {
1,724,535✔
587
                op_code: OpCode::LTEIMMEDIATE,
1,724,535✔
588
                ..
1,724,535✔
589
            }),
590
            Some(Instruction {
1,724,535✔
591
                op_code: OpCode::IF,
1,724,535✔
592
                ..
1,724,535✔
593
            }),
594
        ) = (condition, guard)
1,724,535✔
595
        {
596
            if let Some(x) = instructions.get_mut(i) {
8✔
597
                x.op_code = OpCode::LTEIMMEDIATEIF;
598
            }
599

600
            // let replaced = match *ident {
601
            //     x if x == *PLUS && *payload_size == 2 => Some(OpCode::BINOPADD),
602
            //     x if x == *PLUS => Some(OpCode::ADD),
603
            //     x if x == *MINUS => Some(OpCode::SUB),
604
            //     x if x == *DIV => Some(OpCode::DIV),
605
            //     x if x == *STAR => Some(OpCode::MUL),
606
            //     x if x == *EQUAL => Some(OpCode::EQUAL),
607
            //     x if x == *LTE => Some(OpCode::LTE),
608
            //     _ => None,
609
            // };
610

611
            // if let Some(new_op_code) = replaced {
612
            //     let payload_size = *payload_size;
613
            //     if let Some(x) = instructions.get_mut(i) {
614
            //         x.op_code = new_op_code;
615
            //         x.payload_size = payload_size;
616
            //     }
617

618
            //     if let Some(x) = instructions.get_mut(i + 1) {
619
            //         x.op_code = OpCode::PASS;
620
            //     }
621
            // }
622
        }
623
    }
624
}
625

626
pub struct ProgramBuilder(Vec<Vec<DenseInstruction>>);
627
impl Default for ProgramBuilder {
628
    fn default() -> Self {
×
629
        Self::new()
×
630
    }
631
}
632

633
impl ProgramBuilder {
634
    pub fn new() -> Self {
×
635
        ProgramBuilder(Vec::new())
×
636
    }
637

638
    pub fn push(&mut self, val: Vec<DenseInstruction>) {
×
639
        self.0.push(val);
×
640
    }
641
}
642

643
#[derive(Serialize, Deserialize)]
644
pub struct SerializableProgram {
645
    pub instructions: Vec<Vec<DenseInstruction>>,
646
    pub constant_map: Vec<u8>,
647
}
648

649
impl SerializableProgram {
650
    pub fn write_to_file(&self, filename: &str) -> Result<()> {
×
651
        use std::io::prelude::*;
652

653
        let mut file = File::create(format!("{filename}.txt")).unwrap();
×
654

655
        let buffer = bincode::serialize(self).unwrap();
×
656

657
        file.write_all(&buffer)?;
×
658
        Ok(())
×
659
    }
660

661
    pub fn read_from_file(filename: &str) -> Result<Self> {
×
662
        use std::io::prelude::*;
663

664
        let mut file = File::open(format!("{filename}.txt")).unwrap();
×
665

666
        let mut buffer = Vec::new();
×
667

668
        let _ = file.read_to_end(&mut buffer).unwrap();
×
669

670
        let program: SerializableProgram = bincode::deserialize(&buffer).unwrap();
×
671

672
        Ok(program)
×
673
    }
674

675
    pub fn into_program(self) -> Program {
×
676
        let constant_map = ConstantMap::from_bytes(&self.constant_map).unwrap();
×
677
        Program {
678
            constant_map,
679
            instructions: self.instructions,
×
680
            ast: HashMap::new(),
×
681
        }
682
    }
683
}
684

685
/// Represents a Steel program
686
/// The program holds the instructions and the constant map, serialized to bytes
687
pub struct Program {
688
    pub instructions: Vec<Vec<DenseInstruction>>,
689
    pub constant_map: ConstantMap,
690
    pub ast: HashMap<usize, ExprKind>,
691
}
692

693
impl Program {
694
    pub fn new(
×
695
        instructions: Vec<Vec<DenseInstruction>>,
696
        constant_map: ConstantMap,
697
        ast: HashMap<usize, ExprKind>,
698
    ) -> Self {
699
        Program {
700
            instructions,
701
            constant_map,
702
            ast,
703
        }
704
    }
705

706
    pub fn into_serializable_program(self) -> Result<SerializableProgram> {
×
707
        Ok(SerializableProgram {
×
708
            instructions: self.instructions,
×
709
            constant_map: self.constant_map.to_bytes()?,
×
710
        })
711
    }
712
}
713

714
// An inspectable program with debug symbols still included on the instructions
715
// ConstantMap needs to get passed in to the run time to execute the program
716
// This way, the VM knows where to look up values
717
#[derive(Clone)]
718
pub struct RawProgramWithSymbols {
719
    pub(crate) instructions: Vec<Vec<Instruction>>,
720
    pub(crate) constant_map: ConstantMap,
721
    version: String, // TODO -> this should be semver
722
}
723

724
#[derive(Serialize, Deserialize)]
725
pub struct SerializableRawProgramWithSymbols {
726
    instructions: Vec<Vec<Instruction>>,
727
    constant_map: Vec<u8>,
728
    version: String,
729
}
730

731
impl SerializableRawProgramWithSymbols {
732
    pub fn write_to_file(&self, filename: &str) -> Result<()> {
×
733
        use std::io::prelude::*;
734

735
        let mut file = File::create(format!("{filename}.txt")).unwrap();
×
736

737
        let buffer = bincode::serialize(self).unwrap();
×
738

739
        file.write_all(&buffer)?;
×
740
        Ok(())
×
741
    }
742

743
    pub fn read_from_file(filename: &str) -> Result<Self> {
×
744
        use std::io::prelude::*;
745

746
        let mut file = File::open(format!("{filename}.txt")).unwrap();
×
747
        let mut buffer = Vec::new();
×
748
        let _ = file.read_to_end(&mut buffer).unwrap();
×
749
        let program: Self = bincode::deserialize(&buffer).unwrap();
×
750

751
        Ok(program)
×
752
    }
753

754
    pub fn into_raw_program(self) -> RawProgramWithSymbols {
×
755
        let constant_map = ConstantMap::from_bytes(&self.constant_map).unwrap();
×
756
        RawProgramWithSymbols {
757
            // struct_functions: self.struct_functions,
758
            instructions: self.instructions,
×
759
            constant_map,
760
            version: self.version,
×
761
        }
762
    }
763
}
764

765
use std::fs::File;
766
use std::io::{self, BufRead};
767
use std::path::Path;
768

769
// The output is wrapped in a Result to allow matching on errors
770
// Returns an Iterator to the Reader of the lines of the file.
771
fn _read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
×
772
where
773
    P: AsRef<Path>,
774
{
775
    let file = File::open(filename)?;
×
776
    Ok(io::BufReader::new(file).lines())
×
777
}
778

779
// trait Profiler {
780
//     #[inline(always)]
781
//     fn process() -> bool;
782

783
//     fn report(&self);
784
// }
785

786
impl RawProgramWithSymbols {
787
    pub fn new(
×
788
        // struct_functions: Vec<StructFuncBuilderConcrete>,
789
        instructions: Vec<Vec<Instruction>>,
790
        constant_map: ConstantMap,
791
        version: String,
792
    ) -> Self {
793
        Self {
794
            // struct_functions,
795
            instructions,
796
            constant_map,
797
            version,
798
        }
799
    }
800

801
    pub fn profile_instructions(&self) {
×
802
        let iter = self
×
803
            .instructions
×
804
            .iter()
805
            .flat_map(|x| x.iter())
×
806
            .filter(|x| !matches!(x.op_code, OpCode::PASS));
×
807

808
        let mut occurrences = HashMap::new();
×
809
        for instr in iter {
×
810
            *occurrences.entry(instr.op_code).or_default() += 1;
×
811
        }
812

813
        let total: usize = occurrences.values().sum();
×
814

815
        let mut counts = occurrences
×
816
            .into_iter()
817
            .map(|x| (x.0, (x.1 as f64 / total as f64) * 100.0))
×
818
            .collect::<Vec<(OpCode, f64)>>();
819

820
        counts.sort_by(|x, y| y.1.partial_cmp(&x.1).unwrap());
×
821

822
        println!("{counts:#?}");
×
823
    }
824

825
    // Definitely can be improved
826
    // pub fn parse_from_self_hosted_file<P>(file: P) -> Result<Self>
827
    // where
828
    //     P: AsRef<Path>,
829
    // {
830
    //     let mut lines = read_lines(file)?;
831

832
    //     // First line should be the constant map label
833
    //     // let constant_map =
834

835
    //     if let Some(constant_map_label) = lines.next() {
836
    //         if constant_map_label? != "'ConstantMap" {
837
    //             stop!(Generic => "Compiled file expected constant map label")
838
    //         }
839
    //     } else {
840
    //         stop!(Generic => "Missing constant map label")
841
    //     }
842

843
    //     // Temportary interner
844
    //     let mut intern = HashMap::new();
845

846
    //     let constant_map = if let Some(constant_map) = lines.next() {
847
    //         let constant_map = constant_map?;
848

849
    //         let constant_map = constant_map
850
    //             .trim_start_matches('[')
851
    //             .trim_end_matches(']')
852
    //             .split(',')
853
    //             .map(|x| {
854
    //                 // Parse the input
855
    //                 let parsed: std::result::Result<Vec<ExprKind>, ParseError> =
856
    //                     Parser::new(&x, &mut intern).collect();
857
    //                 let parsed = parsed?;
858

859
    //                 Ok(SteelVal::try_from(parsed[0].clone()).unwrap())
860
    //             })
861
    //             .collect::<Result<Vec<_>>>()
862
    //             .map(ConstantMap::from_vec)?;
863

864
    //         constant_map
865
    //     } else {
866
    //         stop!(Generic => "Missing constant map")
867
    //     };
868

869
    //     if let Some(instructions_label) = lines.next() {
870
    //         if instructions_label? != "'Instructions" {
871
    //             stop!(Generic => "Compiled file expected instructions label")
872
    //         }
873
    //     } else {
874
    //         stop!(Generic => "Missing instructions label")
875
    //     }
876

877
    //     let mut instruction_set = Vec::new();
878

879
    //     let mut instructions = Vec::new();
880

881
    //     // Skip past the first 'Expression
882
    //     lines.next();
883

884
    //     for instruction_string in lines {
885
    //         let instruction_string = instruction_string?;
886

887
    //         if instruction_string == "'Expression" {
888
    //             // instructions = Vec::new();
889
    //             // if instruction_set.is_empty() {
890
    //             instruction_set.push(instructions);
891
    //             instructions = Vec::new();
892
    //             // }
893

894
    //             continue;
895
    //         }
896

897
    //         let parsed: std::result::Result<Vec<ExprKind>, ParseError> =
898
    //             Parser::new(&instruction_string, &mut intern).collect();
899
    //         let parsed = parsed?;
900

901
    //         let value = SteelVal::try_from(parsed[0].clone()).unwrap();
902

903
    //         if let SteelVal::ListV(v) = value {
904
    //             // Get the op code here
905
    //             let op_code =
906
    //                 OpCode::from_str(v.get(1).unwrap().symbol_or_else(|| unreachable!()).unwrap());
907

908
    //             // Get the payload
909
    //             let payload = v.get(2).unwrap().int_or_else(|| unreachable!()).unwrap() as usize;
910

911
    //             // Get the contents
912
    //             // If I can't parse the object, just move on
913
    //             let contents = ExprKind::try_from(v.get(3).unwrap())
914
    //                 .ok()
915
    //                 .and_then(|x| x.atom_syntax_object().cloned());
916

917
    //             let instruction = Instruction::new_from_parts(op_code, payload, contents);
918

919
    //             instructions.push(instruction)
920
    //         } else {
921
    //             stop!(Generic => "Instruction serialized incorrectly")
922
    //         }
923
    //     }
924

925
    //     instruction_set.push(instructions);
926

927
    //     Ok(Self::new(
928
    //         instruction_set,
929
    //         constant_map,
930
    //         "0.0.1".to_string(),
931
    //     ))
932
    // }
933

934
    pub fn into_serializable_program(self) -> Result<SerializableRawProgramWithSymbols> {
×
935
        Ok(SerializableRawProgramWithSymbols {
×
936
            instructions: self.instructions,
×
937
            constant_map: self.constant_map.to_bytes()?,
×
938
            version: self.version,
×
939
        })
940
    }
941

942
    pub fn debug_print(&self) {
×
943
        self.instructions
×
944
            .iter()
945
            .for_each(|i| println!("{}\n\n", crate::core::instructions::disassemble(i)))
×
946
    }
947

948
    /// Applies a peephole style optimization to the underlying instruction set
949
    pub fn with_optimization<F: Fn(&mut [Instruction])>(&mut self, f: F) {
×
950
        for instructions in &mut self.instructions {
×
951
            f(instructions)
×
952
        }
953
    }
954

955
    // Apply the optimizations to raw bytecode
956
    pub(crate) fn apply_optimizations(&mut self) -> &mut Self {
765✔
957
        // if std::env::var("CODE_GEN_V2").is_err() {
958
        // Run down the optimizations here
959
        for instructions in &mut self.instructions {
72,759✔
960
            inline_num_operations(instructions);
23,998✔
961
            convert_call_globals(instructions);
23,998✔
962

963
            // gimmick_super_instruction(instructions);
964
            // move_read_local_call_global(instructions);
965
            specialize_read_local(instructions);
23,998✔
966

967
            merge_conditions_with_if(instructions);
23,998✔
968

969
            specialize_constants(instructions).unwrap();
23,998✔
970

971
            // Apply the super instruction tiling!
972
            tile_super_instructions(instructions);
23,998✔
973

974
            // specialize_exit_jmp(instructions);
975

976
            // loop_condition_local_const_arity_two(instructions);
977
        }
978

979
        self
765✔
980
    }
981

982
    pub fn debug_generate_instructions(
×
983
        mut self,
984
        symbol_map: &mut SymbolMap,
985
    ) -> Result<Vec<String>> {
986
        let mut interner = DebruijnIndicesInterner::default();
×
987

988
        for expression in &mut self.instructions {
×
989
            interner.collect_first_pass_defines(expression, symbol_map)?
×
990
        }
991

992
        for expression in &mut self.instructions {
×
993
            interner.collect_second_pass_defines(expression, symbol_map)?
×
994
        }
995

996
        // TODO try here - the loop condition local const arity two seems to rely on the
997
        // existence of having been already adjusted by the interner
998
        for instructions in &mut self.instructions {
×
999
            // loop_condition_local_const_arity_two(instructions);
1000
            specialize_constants(instructions)?;
×
1001
        }
1002

1003
        // Put the new struct functions at the front
1004
        // struct_instructions.append(&mut self.instructions);
1005
        // self.instructions = struct_instructions;
1006

1007
        Ok(self
×
1008
            .instructions
×
1009
            .into_iter()
×
1010
            .map(|i| crate::core::instructions::disassemble(&i))
×
1011
            .collect())
×
1012
    }
1013

1014
    pub fn debug_build(mut self, _name: String, symbol_map: &mut SymbolMap) -> Result<()> {
×
1015
        #[cfg(feature = "profiling")]
1016
        let now = Instant::now();
×
1017

1018
        // let mut struct_instructions = Vec::new();
1019

1020
        // for builder in &self.struct_functions {
1021
        //     // Add the eventual function names to the symbol map
1022
        //     let indices = symbol_map.insert_struct_function_names_from_concrete(builder);
1023

1024
        //     // Get the value we're going to add to the constant map for eventual use
1025
        //     // Throw the bindings in as well
1026
        //     let constant_values = builder.to_constant_val(indices);
1027
        //     let idx = self.constant_map.add_or_get(constant_values);
1028

1029
        //     struct_instructions.push(vec![Instruction::new_struct(idx), Instruction::new_pop()]);
1030
        // }
1031

1032
        let mut interner = DebruijnIndicesInterner::default();
×
1033

1034
        for expression in &mut self.instructions {
×
1035
            interner.collect_first_pass_defines(expression, symbol_map)?
×
1036
        }
1037

1038
        for expression in &mut self.instructions {
×
1039
            interner.collect_second_pass_defines(expression, symbol_map)?
×
1040
        }
1041

1042
        // TODO try here - the loop condition local const arity two seems to rely on the
1043
        // existence of having been already adjusted by the interner
1044
        for instructions in &mut self.instructions {
×
1045
            // loop_condition_local_const_arity_two(instructions);
1046
            specialize_constants(instructions)?;
×
1047
        }
1048

1049
        // Put the new struct functions at the front
1050
        // struct_instructions.append(&mut self.instructions);
1051
        // self.instructions = struct_instructions;
1052

1053
        self.instructions
×
1054
            .iter()
1055
            .for_each(|i| println!("{}\n\n", crate::core::instructions::disassemble(i)));
×
1056

1057
        #[cfg(feature = "profiling")]
×
1058
        if log_enabled!(target: "pipeline_time", log::Level::Debug) {
×
1059
            debug!(target: "pipeline_time", "Executable Build Time: {:?}", now.elapsed());
×
1060
        }
1061

1062
        // let mut sorted_symbol_map = symbol_map.map.iter().collect::<Vec<_>>();
1063
        // sorted_symbol_map.sort_by_key(|x| x.1);
1064

1065
        // println!("Symbol Map: {:#?}", sorted_symbol_map);
1066

1067
        Ok(())
×
1068
    }
1069

1070
    // TODO -> check out the spans part of this
1071
    // also look into having the constant map be correct mapping
1072
    // I think the run time will have to swap the constant map in and out
1073
    pub fn build(mut self, name: String, symbol_map: &mut SymbolMap) -> Result<Executable> {
764✔
1074
        #[cfg(feature = "profiling")]
1075
        let now = Instant::now();
764✔
1076

1077
        let mut interner = DebruijnIndicesInterner::default();
764✔
1078

1079
        for expression in &mut self.instructions {
48,732✔
1080
            interner.collect_first_pass_defines(expression, symbol_map)?
×
1081
        }
1082

1083
        for expression in &mut self.instructions {
48,702✔
1084
            interner.collect_second_pass_defines(expression, symbol_map)?
22✔
1085
        }
1086

1087
        // if std::env::var("CODE_GEN_V2").is_err() {
1088
        // TODO try here - the loop condition local const arity two seems to rely on the
1089
        // existence of having been already adjusted by the interner
1090
        for instructions in &mut self.instructions {
48,638✔
1091
            // TODO: Re-enable optimizations
1092
            // loop_condition_local_const_arity_two(instructions);
1093
            specialize_constants(instructions)?;
×
1094
            // gimmick_super_instruction(instructions);
1095
            // move_read_local_call_global(instructions);
1096
            specialize_read_local(instructions);
23,948✔
1097
        }
1098
        // }
1099

1100
        let (spans, instructions) = extract_spans(self.instructions);
742✔
1101

1102
        // let mut sorted_symbol_map = symbol_map.map.iter().collect::<Vec<_>>();
1103
        // sorted_symbol_map.sort_by_key(|x| x.1);
1104

1105
        // println!("Symbol Map: {:#?}", sorted_symbol_map);
1106

1107
        #[cfg(feature = "profiling")]
742✔
1108
        if log_enabled!(target: "pipeline_time", log::Level::Debug) {
742✔
1109
            debug!(target: "pipeline_time", "Executable Build Time: {:?}", now.elapsed());
742✔
1110
        }
1111

1112
        Ok(Executable {
742✔
1113
            name: Shared::new(name),
742✔
1114
            version: Shared::new(self.version),
742✔
1115
            #[cfg(not(target_arch = "wasm32"))]
742✔
1116
            time_stamp: Some(SystemTime::now()),
742✔
1117
            #[cfg(target_arch = "wasm32")]
742✔
1118
            time_stamp: None,
742✔
1119
            instructions: instructions
742✔
1120
                .into_iter()
742✔
1121
                .map(|x| Shared::from(x.into_boxed_slice()))
25,432✔
1122
                .collect(),
742✔
1123
            constant_map: self.constant_map,
742✔
1124
            spans,
742✔
1125
        })
1126
    }
1127
}
1128

1129
// TODO -> replace spans on instructions with index into span vector
1130
// this is kinda nasty but it _should_ work
1131
fn extract_spans(
742✔
1132
    instructions: Vec<Vec<Instruction>>,
1133
) -> (Vec<Shared<[Span]>>, Vec<Vec<DenseInstruction>>) {
1134
    // let mut span_vec = Vec::with_capacity(instructions.iter().map(|x| x.len()).sum());
1135

1136
    // for instruction_set in &instructions {
1137
    //     for instruction in instruction_set {
1138
    //         if let Some(syn) = &instruction.contents {
1139
    //             span_vec.push(syn.span)
1140
    //         } else {
1141
    //             span_vec.push(Span::default())
1142
    //         }
1143
    //     }
1144
    // }
1145

1146
    let span_vec = instructions
742✔
1147
        .iter()
1148
        .map(|x| {
24,690✔
1149
            x.iter()
23,948✔
1150
                .map(|x| {
1,767,250✔
1151
                    x.contents
1,743,302✔
1152
                        .as_ref()
1,743,302✔
1153
                        .map(|x| match x {
3,002,099✔
1154
                            Expr::Atom(a) => a.span,
1,128,950✔
1155
                            Expr::List(l) => get_span(l),
129,847✔
1156
                        })
1157
                        .unwrap_or_default()
1,743,302✔
1158
                })
1159
                .collect()
23,948✔
1160
        })
1161
        .collect();
1162

1163
    let instructions: Vec<_> = instructions
742✔
1164
        .into_iter()
1165
        .map(|x| {
24,690✔
1166
            // let len = x.len();
1167
            x.into_iter()
23,948✔
1168
                .map(|i| {
1,767,250✔
1169
                    DenseInstruction::new(
1,743,302✔
1170
                        i.op_code,
1,743,302✔
1171
                        i.payload_size.try_into().unwrap_or_else(|_| {
1,743,302✔
1172
                            println!("{:?}", i);
×
1173
                            panic!("Unable to lower instruction to bytecode!")
×
1174
                        }),
1175
                    )
1176
                })
1177
                .collect()
23,948✔
1178
        })
1179
        .collect();
1180

1181
    (span_vec, instructions)
742✔
1182
}
1183

1184
// A program stripped of its debug symbols, but only constructable by running a pass
1185
// over it with the symbol map to intern all of the symbols in the order they occurred
1186
#[allow(unused)]
1187
#[derive(Clone)]
1188
pub struct Executable {
1189
    pub(crate) name: Shared<String>,
1190
    pub(crate) version: Shared<String>,
1191
    pub(crate) time_stamp: Option<SystemTime>, // TODO -> don't use system time, probably not as portable, prefer date time
1192
    pub(crate) instructions: Vec<Shared<[DenseInstruction]>>,
1193
    pub(crate) constant_map: ConstantMap,
1194
    pub(crate) spans: Vec<Shared<[Span]>>,
1195
}
1196

1197
impl Executable {
1198
    pub fn name(&self) -> &str {
×
1199
        &self.name
×
1200
    }
1201

1202
    pub fn time_stamp(&self) -> &Option<SystemTime> {
×
1203
        &self.time_stamp
×
1204
    }
1205
}
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