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

mattwparas / steel / 11947280686

21 Nov 2024 05:34AM UTC coverage: 46.096% (-0.1%) from 46.234%
11947280686

Pull #291

github

web-flow
Merge 003f55f93 into 9a2bb2ef5
Pull Request #291: Add rustls library and fix free identifier error message

16 of 86 new or added lines in 6 files covered. (18.6%)

120 existing lines in 11 files now uncovered.

12349 of 26790 relevant lines covered (46.1%)

381398.76 hits per line

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

72.56
/crates/steel-core/src/compiler/compiler.rs
1
use crate::{
2
    compiler::{
3
        constants::ConstantMap,
4
        map::SymbolMap,
5
        modules::MANGLER_PREFIX,
6
        passes::{
7
            analysis::SemanticAnalysis, begin::flatten_begins_and_expand_defines,
8
            shadow::RenameShadowedVariables, VisitorMutRefUnit,
9
        },
10
    },
11
    core::{
12
        instructions::{disassemble, u24},
13
        labels::Expr,
14
    },
15
    parser::{
16
        expand_visitor::{expand_kernel_in_env, expand_kernel_in_env_with_change},
17
        interner::InternedString,
18
        kernel::Kernel,
19
        parser::{lower_entire_ast, lower_macro_and_require_definitions},
20
    },
21
    steel_vm::{cache::MemoizationTable, engine::ModuleContainer, primitives::constant_primitives},
22
};
23
use crate::{
24
    core::{instructions::Instruction, opcode::OpCode},
25
    parser::parser::Sources,
26
};
27

28
use std::{borrow::Cow, iter::Iterator};
29
use std::{
30
    collections::{HashMap, HashSet},
31
    path::PathBuf,
32
};
33

34
// TODO: Replace the usages of hashmap with this directly
35
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
36
use serde::{Deserialize, Serialize};
37
use steel_parser::ast::PROVIDE;
38

39
use crate::rvals::{Result, SteelVal};
40

41
use crate::parser::ast::ExprKind;
42
use crate::parser::expander::SteelMacro;
43
use crate::parser::parser::SyntaxObject;
44
use crate::parser::parser::{ParseError, Parser};
45
use crate::parser::tokens::TokenType;
46

47
// use crate::core::instructions::{densify, DenseInstruction};
48

49
use crate::stop;
50

51
use crate::steel_vm::const_evaluation::ConstantEvaluatorManager;
52

53
use super::{
54
    constants::SerializableConstantMap,
55
    modules::{CompiledModule, ModuleManager},
56
    passes::{analysis::Analysis, mangle::NameMangler},
57
    program::RawProgramWithSymbols,
58
};
59

60
use crate::values::HashMap as ImmutableHashMap;
61

62
#[cfg(feature = "profiling")]
63
use std::time::Instant;
64

65
#[derive(Default)]
66
pub struct DebruijnIndicesInterner {
67
    flat_defines: HashSet<InternedString>,
68
    flat_defines_idx: HashSet<usize>,
69
    second_pass_defines: HashSet<InternedString>,
70
    second_pass_defines_idx: HashSet<usize>,
71
}
72

73
impl DebruijnIndicesInterner {
74
    pub fn collect_first_pass_defines(
24,231✔
75
        &mut self,
76
        instructions: &mut [Instruction],
77
        symbol_map: &mut SymbolMap,
78
    ) -> Result<()> {
79
        let mut flat_defines_non_closure: HashSet<InternedString> = HashSet::default();
24,231✔
80

81
        for i in 2..instructions.len() {
1,739,247✔
82
            match (&instructions[i], &instructions[i - 1], &instructions[i - 2]) {
1,715,016✔
83
                (
84
                    Instruction {
85
                        op_code: OpCode::BIND,
86
                        contents:
87
                            Some(Expr::Atom(SyntaxObject {
88
                                ty: TokenType::Identifier(s),
17,302✔
89
                                // span,
90
                                ..
17,302✔
91
                            })),
92
                        ..
17,302✔
93
                    },
94
                    Instruction {
17,302✔
95
                        op_code: OpCode::EDEF,
17,302✔
96
                        ..
17,302✔
97
                    },
98
                    Instruction {
17,302✔
99
                        op_code: OpCode::ECLOSURE,
17,302✔
100
                        ..
17,302✔
101
                    },
102
                ) => {
17,302✔
103
                    // Exiting a definition, clear it
104
                    flat_defines_non_closure.clear();
17,302✔
105

106
                    let idx = symbol_map.add(s);
17,302✔
107
                    self.flat_defines.insert(s.to_owned());
17,302✔
108

109
                    if let Some(x) = instructions.get_mut(i) {
34,604✔
110
                        x.payload_size = u24::from_usize(idx);
111
                    }
112
                }
113
                (
114
                    Instruction {
115
                        op_code: OpCode::BIND,
116
                        contents:
117
                            Some(Expr::Atom(SyntaxObject {
118
                                ty: TokenType::Identifier(s),
36,014✔
119
                                span,
36,014✔
120
                                ..
36,014✔
121
                            })),
122
                        ..
36,014✔
123
                    },
124
                    Instruction {
36,014✔
125
                        op_code: OpCode::EDEF,
36,014✔
126
                        ..
36,014✔
127
                    },
128
                    ..,
36,014✔
129
                ) => {
36,014✔
130
                    let idx = symbol_map.add(s);
36,014✔
131
                    self.flat_defines.insert(s.to_owned());
36,014✔
132

133
                    if flat_defines_non_closure.contains(s) {
36,014✔
NEW
134
                        stop!(BadSyntax => format!("Cannot reference identifier before its definition: {}", s.resolve()); *span);
×
135
                    }
136

137
                    flat_defines_non_closure.clear();
36,014✔
138

139
                    // self.flat_defines_idx.insert(idx);
140
                    // flat_defines_non_closure.insert(s.to_owned());
141

142
                    if let Some(x) = instructions.get_mut(i) {
36,014✔
143
                        x.payload_size = u24::from_usize(idx);
144
                    }
145
                }
146
                // (
147
                //     Instruction {
148
                //         op_code: OpCode::EDEF,
149
                //         ..
150
                //     },
151
                //     ..,
152
                // ) => {
153
                //     flat_defines_non_closure.clear();
154
                // }
155
                (
156
                    Instruction {
157
                        op_code: OpCode::CALLGLOBAL,
158
                        contents:
159
                            Some(Expr::Atom(SyntaxObject {
160
                                ty: TokenType::Identifier(s),
237,184✔
161
                                span,
237,184✔
162
                                ..
237,184✔
163
                            })),
164
                        ..
237,184✔
165
                    },
166
                    ..,
237,184✔
167
                ) => {
237,184✔
168
                    // We're referencing things within the scope
169
                    flat_defines_non_closure.insert(*s);
237,184✔
170

171
                    // let idx = symbol_map.add(s);
172
                    // self.flat_defines.insert(s.to_owned());
173

174
                    // self.flat_defines_idx.insert(idx);
175

176
                    // if flat_defines_non_closure.contains(s) {
177
                    //     println!("{}", disassemble(instructions));
178
                    //     stop!(BadSyntax => format!("Cannot reference identifier before definition: {}", s.resolve()); *span);
179
                    // }
180
                }
181
                _ => {}
1,424,516✔
182
            }
183
        }
184

185
        Ok(())
24,231✔
186
    }
187

188
    pub fn collect_second_pass_defines(
24,227✔
189
        &mut self,
190
        instructions: &mut [Instruction],
191
        symbol_map: &mut SymbolMap,
192
    ) -> Result<()> {
193
        // let mut second_pass_defines: HashSet<String> = HashSet::new();
194

195
        let mut depth = 0;
24,227✔
196

197
        // name mangle
198
        // Replace all identifiers with indices
199
        for i in 0..instructions.len() {
1,787,610✔
200
            match &instructions[i] {
1,763,383✔
201
                Instruction {
202
                    op_code: OpCode::SCLOSURE | OpCode::NEWSCLOSURE | OpCode::PUREFUNC,
203
                    ..
204
                } => {
37,692✔
205
                    depth += 1;
37,692✔
206
                }
207
                Instruction {
208
                    op_code: OpCode::ECLOSURE,
209
                    ..
210
                } => {
37,692✔
211
                    depth -= 1;
37,692✔
212
                }
213
                Instruction {
214
                    op_code: OpCode::BEGINSCOPE,
215
                    ..
216
                } => {
24,607✔
217
                    depth += 1;
24,607✔
218
                }
219
                Instruction {
220
                    op_code: OpCode::LETENDSCOPE,
221
                    ..
222
                } => {
24,605✔
223
                    depth -= 1;
24,605✔
224
                }
225
                Instruction {
226
                    op_code: OpCode::BIND,
227
                    contents:
228
                        Some(Expr::Atom(SyntaxObject {
229
                            ty: TokenType::Identifier(s),
53,312✔
230
                            ..
53,312✔
231
                        })),
232
                    ..
53,312✔
233
                } => {
53,312✔
234
                    // Keep track of where the defines actually are in the process
235
                    self.second_pass_defines.insert(s.to_owned());
53,312✔
236

237
                    // let idx = symbol_map.get(s).unwrap();
238

239
                    // self.second_pass_defines_idx.insert(idx);
240
                }
241
                Instruction {
242
                    op_code: OpCode::PUSH,
243
                    contents:
244
                        Some(Expr::Atom(SyntaxObject {
245
                            ty: TokenType::Identifier(s),
53,266✔
246
                            span,
53,266✔
247
                            ..
248
                        })),
249
                    ..
250
                }
251
                | Instruction {
252
                    op_code: OpCode::SET,
253
                    contents:
254
                        Some(Expr::Atom(SyntaxObject {
255
                            ty: TokenType::Identifier(s),
9,010✔
256
                            span,
9,010✔
257
                            ..
258
                        })),
259
                    ..
260
                } => {
261
                    if self.flat_defines.get(s).is_some()
62,276✔
262
                        && self.second_pass_defines.get(s).is_none()
47,676✔
263
                        && depth == 0
203✔
264
                    {
265
                        let message =
1✔
266
                            format!("Cannot reference an identifier before its definition: {s}");
1✔
267
                        stop!(FreeIdentifier => message; *span);
1✔
268
                    }
269

270
                    let idx = symbol_map.get(s).map_err(|e| e.set_span(*span))?;
62,289✔
271

272
                    // TODO commenting this for now
273
                    if let Some(x) = instructions.get_mut(i) {
62,261✔
274
                        x.payload_size = u24::from_usize(idx);
275
                    }
276
                }
277
                Instruction {
278
                    op_code: OpCode::CALLGLOBAL,
279
                    contents:
280
                        Some(Expr::Atom(SyntaxObject {
281
                            ty: TokenType::Identifier(s),
238,238✔
282
                            span,
238,238✔
283
                            ..
284
                        })),
285
                    ..
286
                }
287
                | Instruction {
288
                    op_code: OpCode::CALLGLOBALTAIL,
289
                    contents:
290
                        Some(Expr::Atom(SyntaxObject {
291
                            ty: TokenType::Identifier(s),
38,074✔
292
                            span,
38,074✔
293
                            ..
294
                        })),
295
                    ..
296
                } => {
297
                    if self.flat_defines.get(s).is_some()
276,312✔
298
                        && self.second_pass_defines.get(s).is_none()
61,607✔
299
                        && depth == 0
11,071✔
300
                    {
301
                        let message =
1✔
302
                            format!("Cannot reference an identifier before its definition: {s}");
1✔
303
                        stop!(FreeIdentifier => message; *span);
1✔
304
                    }
305

306
                    if s.resolve() == "client-connection" {
276,311✔
UNCOV
307
                        println!(
×
UNCOV
308
                            "Flat defines: {:?}",
×
UNCOV
309
                            self.flat_defines
×
UNCOV
310
                                .iter()
×
UNCOV
311
                                .map(|x| x.resolve())
×
UNCOV
312
                                .collect::<Vec<_>>()
×
313
                        );
314

UNCOV
315
                        println!("Flat defines idx: {:?}", self.flat_defines_idx);
×
316

UNCOV
317
                        println!(
×
UNCOV
318
                            "Second pass defines: {:?}",
×
UNCOV
319
                            self.second_pass_defines
×
UNCOV
320
                                .iter()
×
UNCOV
321
                                .map(|x| x.resolve())
×
UNCOV
322
                                .collect::<Vec<_>>()
×
323
                        );
324

UNCOV
325
                        println!(
×
UNCOV
326
                            "Second pass defines idx: {:?}",
×
UNCOV
327
                            self.second_pass_defines_idx
×
328
                        );
329
                    }
330

331
                    let idx = symbol_map.get(s).map_err(|e| e.set_span(*span))?;
276,317✔
332

UNCOV
333
                    if s.resolve() == "client-connection" {
×
UNCOV
334
                        println!("idx: {} -> {}", idx, s.resolve());
×
335
                    }
336

337
                    // if self.flat_defines.get(s).is_some()
338
                    //     && self.second_pass_defines.get(s).is_some()
339
                    //     && self.second_pass_defines_idx.get(&idx).is_some()
340
                    // {
341
                    //     let message =
342
                    //         format!("Cannot reference an identifier before its definition: {s}");
343
                    //     stop!(FreeIdentifier => message; *span);
344
                    // }
345

346
                    // TODO commenting this for now
347
                    if let Some(x) = instructions.get_mut(i) {
276,305✔
348
                        x.payload_size = u24::from_usize(idx);
349
                    }
350
                }
351
                _ => {}
1,246,887✔
352
            }
353
        }
354

355
        Ok(())
24,205✔
356
    }
357
}
358

359
#[derive(Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
360
pub enum OptLevel {
361
    Zero = 0,
362
    One,
363
    Two,
364
    Three,
365
}
366

367
#[derive(Clone)]
368
pub(crate) struct KernelDefMacroSpec {
369
    pub(crate) _env: String,
370
    pub(crate) _exported: Option<HashSet<InternedString>>,
371
    pub(crate) name_mangler: NameMangler,
372
}
373

374
#[derive(Clone)]
375
pub struct Compiler {
376
    pub(crate) symbol_map: SymbolMap,
377
    pub(crate) constant_map: ConstantMap,
378
    pub(crate) macro_env: FxHashMap<InternedString, SteelMacro>,
379
    module_manager: ModuleManager,
380
    opt_level: OptLevel,
381
    pub(crate) kernel: Option<Kernel>,
382
    memoization_table: MemoizationTable,
383
    mangled_identifiers: FxHashSet<InternedString>,
384
    // Try this out?
385
    lifted_kernel_environments: HashMap<String, KernelDefMacroSpec>,
386
    // Macros that... we need to compile against directly at the top level
387
    // This is really just a hack, but it solves cases for interactively
388
    // running at the top level using private macros.
389
    lifted_macro_environments: HashSet<PathBuf>,
390

391
    analysis: Analysis,
392
    shadowed_variable_renamer: RenameShadowedVariables,
393

394
    search_dirs: Vec<PathBuf>,
395

396
    // Include all the sources, module container, and constants
397
    // so that we can reference those at runtime. We probably should
398
    // just ignore the constants function in general. This unfortunately,
399
    // is under the hood, shared references to the engine, since we
400
    // want to have the compiler share everything with the runtime.
401
    sources: Sources,
402
    builtin_modules: ModuleContainer,
403
}
404

405
#[derive(Serialize, Deserialize)]
406
pub struct SerializableCompiler {
407
    pub(crate) symbol_map: SymbolMap,
408
    pub(crate) constant_map: SerializableConstantMap,
409
    pub(crate) macro_env: FxHashMap<InternedString, SteelMacro>,
410
    pub(crate) opt_level: OptLevel,
411
    pub(crate) module_manager: ModuleManager,
412
}
413

414
impl SerializableCompiler {
415
    #[allow(unused)]
UNCOV
416
    pub(crate) fn into_compiler(self) -> Compiler {
×
UNCOV
417
        let mut compiler = Compiler::default();
×
418

UNCOV
419
        compiler.symbol_map = self.symbol_map;
×
UNCOV
420
        compiler.constant_map = ConstantMap::from_serialized(self.constant_map).unwrap();
×
UNCOV
421
        compiler.macro_env = self.macro_env;
×
UNCOV
422
        compiler.opt_level = self.opt_level;
×
UNCOV
423
        compiler.module_manager = self.module_manager;
×
424

UNCOV
425
        compiler
×
426
    }
427
}
428

429
impl Compiler {
430
    #[allow(unused)]
UNCOV
431
    pub(crate) fn into_serializable_compiler(self) -> Result<SerializableCompiler> {
×
UNCOV
432
        Ok(SerializableCompiler {
×
UNCOV
433
            symbol_map: self.symbol_map,
×
UNCOV
434
            constant_map: self.constant_map.into_serializable_map(),
×
UNCOV
435
            macro_env: self.macro_env,
×
UNCOV
436
            opt_level: self.opt_level,
×
UNCOV
437
            module_manager: self.module_manager,
×
438
        })
439
    }
440
}
441

442
impl Default for Compiler {
UNCOV
443
    fn default() -> Self {
×
444
        Compiler::new(
445
            SymbolMap::new(),
×
UNCOV
446
            ConstantMap::new(),
×
UNCOV
447
            FxHashMap::default(),
×
UNCOV
448
            ModuleManager::default(),
×
UNCOV
449
            Sources::default(),
×
UNCOV
450
            ModuleContainer::default(),
×
451
        )
452
    }
453
}
454

455
impl Compiler {
456
    fn new(
5✔
457
        symbol_map: SymbolMap,
458
        constant_map: ConstantMap,
459
        macro_env: FxHashMap<InternedString, SteelMacro>,
460
        module_manager: ModuleManager,
461
        sources: Sources,
462
        builtin_modules: ModuleContainer,
463
    ) -> Compiler {
464
        Compiler {
465
            symbol_map,
466
            constant_map,
467
            macro_env,
468
            module_manager,
469
            opt_level: OptLevel::Three,
470
            kernel: None,
471
            memoization_table: MemoizationTable::new(),
5✔
472
            mangled_identifiers: FxHashSet::default(),
5✔
473
            lifted_kernel_environments: HashMap::new(),
5✔
474
            lifted_macro_environments: HashSet::new(),
5✔
475
            analysis: Analysis::pre_allocated(),
5✔
476
            shadowed_variable_renamer: RenameShadowedVariables::default(),
5✔
477
            search_dirs: Vec::new(),
5✔
478
            sources,
479
            builtin_modules,
480
        }
481
    }
482

UNCOV
483
    fn new_with_kernel(
×
484
        symbol_map: SymbolMap,
485
        constant_map: ConstantMap,
486
        macro_env: FxHashMap<InternedString, SteelMacro>,
487
        module_manager: ModuleManager,
488
        kernel: Kernel,
489
        sources: Sources,
490
        builtin_modules: ModuleContainer,
491
    ) -> Compiler {
492
        Compiler {
493
            symbol_map,
494
            constant_map,
495
            macro_env,
496
            module_manager,
497
            opt_level: OptLevel::Three,
UNCOV
498
            kernel: Some(kernel),
×
UNCOV
499
            memoization_table: MemoizationTable::new(),
×
UNCOV
500
            mangled_identifiers: FxHashSet::default(),
×
UNCOV
501
            lifted_kernel_environments: HashMap::new(),
×
UNCOV
502
            lifted_macro_environments: HashSet::new(),
×
UNCOV
503
            analysis: Analysis::pre_allocated(),
×
UNCOV
504
            shadowed_variable_renamer: RenameShadowedVariables::default(),
×
UNCOV
505
            search_dirs: Vec::new(),
×
506
            sources,
507
            builtin_modules,
508
        }
509
    }
510

511
    pub fn default_with_kernel(sources: Sources, builtin_modules: ModuleContainer) -> Compiler {
×
512
        Compiler::new_with_kernel(
UNCOV
513
            SymbolMap::new(),
×
UNCOV
514
            ConstantMap::new(),
×
515
            FxHashMap::default(),
×
516
            ModuleManager::default(),
×
UNCOV
517
            Kernel::new(),
×
UNCOV
518
            sources,
×
UNCOV
519
            builtin_modules,
×
520
        )
521
    }
522

523
    pub(crate) fn default_without_kernel(
5✔
524
        sources: Sources,
525
        builtin_modules: ModuleContainer,
526
    ) -> Compiler {
527
        Compiler::new(
528
            SymbolMap::new(),
5✔
529
            ConstantMap::new(),
5✔
530
            FxHashMap::default(),
5✔
531
            ModuleManager::default(),
5✔
532
            sources,
5✔
533
            builtin_modules,
5✔
534
        )
535
    }
536

537
    /// Registers a name in the underlying symbol map and returns the idx that it maps to
538
    pub fn register(&mut self, name: &str) -> usize {
638✔
539
        let spur = name.into();
638✔
540
        self.symbol_map.add(&spur)
638✔
541
    }
542

543
    /// Get the index associated with a name in the underlying symbol map
544
    /// If the name hasn't been registered, this will return `None`
545
    pub fn get_idx(&self, name: &str) -> Option<usize> {
3,127✔
546
        self.symbol_map.get(&InternedString::try_get(name)?).ok()
6,254✔
547
    }
548

UNCOV
549
    pub fn register_builtin(&mut self, name: String, contents: String) {
×
UNCOV
550
        self.module_manager.add_builtin_module(name, contents);
×
551
    }
552

UNCOV
553
    pub fn add_search_directory(&mut self, dir: PathBuf) {
×
UNCOV
554
        self.search_dirs.push(dir);
×
555
    }
556

557
    pub fn compile_executable_from_expressions(
170✔
558
        &mut self,
559
        exprs: Vec<ExprKind>,
560
    ) -> Result<RawProgramWithSymbols> {
561
        self.compile_raw_program(exprs, None)
170✔
562
    }
563

564
    pub fn compile_executable<E: AsRef<str> + Into<Cow<'static, str>>>(
635✔
565
        &mut self,
566
        expr_str: E,
567
        path: Option<PathBuf>,
568
    ) -> Result<RawProgramWithSymbols> {
569
        #[cfg(feature = "profiling")]
570
        let now = Instant::now();
635✔
571

572
        let expr_str = expr_str.into();
635✔
573

574
        let id = self.sources.add_source(expr_str.clone(), path.clone());
635✔
575

576
        // Could fail here
577
        let parsed: std::result::Result<Vec<ExprKind>, ParseError> = path
635✔
578
            .as_ref()
579
            .map(|p| Parser::new_from_source(expr_str.as_ref(), p.clone(), Some(id)))
1,281✔
580
            .unwrap_or_else(|| Parser::new(expr_str.as_ref(), Some(id)))
1,894✔
581
            .without_lowering()
582
            .map(|x| x.and_then(lower_macro_and_require_definitions))
10,724✔
583
            .collect();
584

585
        #[cfg(feature = "profiling")]
635✔
586
        if log::log_enabled!(target: "pipeline_time", log::Level::Debug) {
635✔
587
            log::debug!(target: "pipeline_time", "Parsing Time: {:?}", now.elapsed());
635✔
588
        }
589

590
        // TODO fix this hack
591
        self.compile_raw_program(parsed?, path)
1,270✔
592
    }
593

594
    // TODO: Add a flag/function for parsing comments as well
595
    // Move the body of this function into the other one, so that way we have proper
596
    pub fn emit_expanded_ast(
×
597
        &mut self,
598
        expr_str: &str,
599
        path: Option<PathBuf>,
600
    ) -> Result<Vec<ExprKind>> {
UNCOV
601
        let id = self.sources.add_source(expr_str.to_string(), path.clone());
×
602

603
        // Could fail here
604
        let parsed: std::result::Result<Vec<ExprKind>, ParseError> =
×
605
            Parser::new(expr_str, Some(id))
×
606
                .without_lowering()
607
                .map(|x| x.and_then(lower_macro_and_require_definitions))
×
608
                .collect();
609

UNCOV
610
        let parsed = parsed?;
×
611

612
        self.lower_expressions_impl(parsed, path)
613
    }
614

615
    pub fn emit_expanded_ast_without_optimizations(
1✔
616
        &mut self,
617
        expr_str: &str,
618
        path: Option<PathBuf>,
619
    ) -> Result<Vec<ExprKind>> {
620
        let id = self.sources.add_source(expr_str.to_string(), path.clone());
1✔
621

622
        // Could fail here
623
        let parsed: std::result::Result<Vec<ExprKind>, ParseError> =
1✔
624
            Parser::new(expr_str, Some(id))
1✔
625
                .without_lowering()
626
                .map(|x| x.and_then(lower_macro_and_require_definitions))
3✔
627
                .collect();
628

629
        let parsed = parsed?;
2✔
630

631
        self.expand_ast(parsed, path)
632
    }
633

UNCOV
634
    pub fn compile_module(
×
635
        &mut self,
636
        path: PathBuf,
637
        sources: &mut Sources,
638
        builtin_modules: ModuleContainer,
639
    ) -> Result<()> {
UNCOV
640
        self.module_manager.add_module(
×
UNCOV
641
            path,
×
UNCOV
642
            &mut self.macro_env,
×
UNCOV
643
            &mut self.kernel,
×
UNCOV
644
            sources,
×
UNCOV
645
            builtin_modules,
×
646
        )
647
    }
648

UNCOV
649
    pub fn modules(&self) -> &FxHashMap<PathBuf, CompiledModule> {
×
UNCOV
650
        self.module_manager.modules()
×
651
    }
652

653
    pub fn expand_expressions(
803✔
654
        &mut self,
655
        exprs: Vec<ExprKind>,
656
        path: Option<PathBuf>,
657
    ) -> Result<Vec<ExprKind>> {
658
        // #[cfg(feature = "modules")]
659
        return self.module_manager.compile_main(
803✔
660
            &mut self.macro_env,
803✔
661
            &mut self.kernel,
803✔
662
            &mut self.sources,
803✔
663
            exprs,
803✔
664
            path,
803✔
665
            self.builtin_modules.clone(),
803✔
666
            &mut self.lifted_kernel_environments,
803✔
667
            &mut self.lifted_macro_environments,
803✔
668
            &self.search_dirs,
803✔
669
        );
670

671
        // #[cfg(not(feature = "modules"))]
672
        // self.module_manager
673
        //     .expand_expressions(&mut self.macro_env, exprs)
674
    }
675

676
    fn generate_instructions_for_executable(
779✔
677
        &mut self,
678
        expanded_statements: Vec<ExprKind>,
679
    ) -> Result<Vec<Vec<Instruction>>> {
680
        let mut results = Vec::with_capacity(expanded_statements.len());
779✔
681
        // let mut instruction_buffer = Vec::new();
682
        // let mut index_buffer = Vec::new();
683

684
        let analysis = {
779✔
685
            let mut analysis = std::mem::take(&mut self.analysis);
779✔
686

687
            analysis.fresh_from_exprs(&expanded_statements);
779✔
688
            analysis.populate_captures_twice(&expanded_statements);
779✔
689

690
            // let mut analysis = Analysis::from_exprs(&expanded_statements);
691
            // analysis.populate_captures(&expanded_statements);
692
            // analysis.populate_captures(&expanded_statements);
693
            analysis
779✔
694
        };
695

696
        for expr in expanded_statements {
49,269✔
697
            let instructions =
24,245✔
698
                super::code_gen::CodeGenerator::new(&mut self.constant_map, &analysis)
UNCOV
699
                    .top_level_compile(&expr)?;
×
700

701
            results.push(instructions);
24,245✔
702
        }
703

704
        // This... cannot be efficient?
705
        // for idx in index_buffer {
706
        //     let extracted: Vec<Instruction> = instruction_buffer.drain(0..idx).collect();
707
        //     results.push(extracted);
708
        // }
709

710
        self.analysis = analysis;
779✔
711

712
        Ok(results)
779✔
713
    }
714

715
    fn expand_ast(&mut self, exprs: Vec<ExprKind>, path: Option<PathBuf>) -> Result<Vec<ExprKind>> {
1✔
716
        let mut expanded_statements = self.expand_expressions(exprs, path)?;
2✔
717

UNCOV
718
        log::debug!(target: "expansion-phase", "Expanding macros -> phase 1");
×
719

720
        if let Some(kernel) = self.kernel.as_mut() {
2✔
721
            // Label anything at the top as well - top level
UNCOV
722
            kernel.load_syntax_transformers(&mut expanded_statements, "top-level".to_string())?;
×
723
        }
724

725
        for expr in expanded_statements.iter_mut() {
2✔
726
            expand_kernel_in_env(
727
                expr,
1✔
728
                self.kernel.as_mut(),
1✔
729
                self.builtin_modules.clone(),
1✔
730
                "top-level",
731
            )?;
732

733
            crate::parser::expand_visitor::expand(expr, &self.macro_env)?;
1✔
734
        }
735

736
        for expr in expanded_statements.iter_mut() {
2✔
737
            expand_kernel_in_env(
738
                expr,
1✔
739
                self.kernel.as_mut(),
1✔
740
                self.builtin_modules.clone(),
1✔
741
                "top-level",
742
            )?;
743
            crate::parser::expand_visitor::expand(expr, &self.macro_env)?;
1✔
744
            lower_entire_ast(expr)?;
1✔
745

746
            for module in &self.lifted_macro_environments {
11✔
747
                if let Some(macro_env) = self.modules().get(module).map(|x| &x.macro_map) {
10✔
748
                    let source_id = self.sources.get_source_id(module).unwrap();
749

750
                    // println!("Expanding macros from: {:?}", module);
751

752
                    crate::parser::expand_visitor::expand_with_source_id(
753
                        expr,
754
                        macro_env,
755
                        Some(source_id),
756
                    )?
757

758
                    // crate::parser::expand_visitor::expand(expr, macro_env)?
759
                }
760
            }
761

762
            // Lift all of the kernel macros as well?
763
            for (module, lifted_env) in &mut self.lifted_kernel_environments {
3✔
764
                let changed = expand_kernel_in_env_with_change(
765
                    expr,
766
                    self.kernel.as_mut(),
767
                    self.builtin_modules.clone(),
768
                    &module,
769
                )?;
770

UNCOV
771
                if changed {
×
UNCOV
772
                    lifted_env.name_mangler.visit(expr);
×
773
                }
774
            }
775

776
            expand_kernel_in_env(
777
                expr,
1✔
778
                self.kernel.as_mut(),
1✔
779
                self.builtin_modules.clone(),
1✔
780
                "top-level",
781
            )?;
782

783
            // TODO: If we have this, then we have to lower all of the expressions again
784
            crate::parser::expand_visitor::expand(expr, &self.macro_env)?;
1✔
785

786
            // for expr in expanded_statements.iter_mut() {
787
            lower_entire_ast(expr)?;
1✔
788
        }
789

790
        // @Matt 11/15/2024
791
        // TODO: At this point, we'll want to remove
792
        // any remaining provide statements.
793

794
        log::debug!(target: "expansion-phase", "Beginning constant folding");
1✔
795

796
        // Remove remaining provides from the top level.
797

798
        // let mut expanded_statements =
799
        // self.apply_const_evaluation(constants.clone(), expanded_statements, false)?;
800

801
        // expanded_statements.pretty_print();
802

803
        self.shadowed_variable_renamer
1✔
804
            .rename_shadowed_variables(&mut expanded_statements, true);
1✔
805

806
        let mut analysis = std::mem::take(&mut self.analysis);
1✔
807
        analysis.fresh_from_exprs(&expanded_statements);
1✔
808
        analysis.populate_captures(&expanded_statements);
1✔
809

810
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
1✔
811

812
        // This is definitely broken still
813
        semantic
1✔
814
            .elide_single_argument_lambda_applications()
815
            .replace_non_shadowed_globals_with_builtins(
816
                &mut self.macro_env,
1✔
817
                &mut self.module_manager,
1✔
818
                &mut self.mangled_identifiers,
1✔
819
            )
820
            // TODO: To get this to work, we have to check the macros to make sure those
821
            // are safe to eliminate. In interactive mode, we'll
822
            // be unable to optimize those away
823
            .remove_unused_globals_with_prefix(
824
                MANGLER_PREFIX,
1✔
825
                &self.macro_env,
1✔
826
                &self.module_manager,
1✔
827
            );
828
        // Don't do lambda lifting here
829
        // .lift_pure_local_functions()
830
        // .lift_all_local_functions();
831

832
        // debug!("About to expand defines");
833

834
        log::debug!(target: "expansion-phase", "Flattening begins, converting internal defines to let expressions");
1✔
835

836
        let mut analysis = semantic.into_analysis();
1✔
837

838
        let expanded_statements = flatten_begins_and_expand_defines(expanded_statements)?;
1✔
839

840
        let mut expanded_statements = filter_provides(expanded_statements);
841

842
        // After define expansion, we'll want this
843
        // RenameShadowedVariables::rename_shadowed_vars(&mut expanded_statements);
844

845
        self.shadowed_variable_renamer
846
            .rename_shadowed_variables(&mut expanded_statements, false);
847

848
        analysis.fresh_from_exprs(&expanded_statements);
849
        analysis.populate_captures(&expanded_statements);
850

851
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
852

UNCOV
853
        semantic.check_if_values_are_redefined()?;
×
854

855
        semantic.refresh_variables();
1✔
856
        semantic.flatten_anonymous_functions();
1✔
857
        semantic.refresh_variables();
1✔
858

859
        // Replace mutation with boxes
860

861
        semantic.populate_captures_twice();
1✔
862

863
        semantic.replace_mutable_captured_variables_with_boxes();
1✔
864

865
        log::debug!(target: "expansion-phase", "Expanding multiple arity functions");
1✔
866

867
        let mut analysis = semantic.into_analysis();
1✔
868

869
        // Rename them again
870
        // RenameShadowedVariables::rename_shadowed_vars(&mut expanded_statements);
871

872
        self.shadowed_variable_renamer
1✔
873
            .rename_shadowed_variables(&mut expanded_statements, false);
1✔
874

875
        // let mut expanded_statements =
876
        // log::info!(target: "expansion-phase", "Aggressive constant evaluation with memoization");
877

878
        // Begin lowering anonymous function calls to lets
879

880
        analysis.fresh_from_exprs(&expanded_statements);
1✔
881
        analysis.populate_captures(&expanded_statements);
1✔
882
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
1✔
883

884
        semantic.replace_anonymous_function_calls_with_plain_lets();
1✔
885

886
        self.analysis = semantic.into_analysis();
1✔
887

888
        Ok(expanded_statements)
1✔
889
    }
890

891
    pub(crate) fn lower_expressions_impl(
802✔
892
        &mut self,
893
        exprs: Vec<ExprKind>,
894
        path: Option<PathBuf>,
895
    ) -> Result<Vec<ExprKind>> {
896
        #[cfg(feature = "profiling")]
897
        let now = Instant::now();
898

899
        // println!("Before expanding macros");
900
        // exprs.pretty_print();
901

902
        let mut expanded_statements = self.expand_expressions(exprs, path)?;
1,604✔
903

904
        // println!("After expanding macros");
905
        // expanded_statements.pretty_print();
906

907
        #[cfg(feature = "profiling")]
908
        log::debug!(target: "pipeline_time", "Phase 1 module expansion time: {:?}", now.elapsed());
909

910
        #[cfg(feature = "profiling")]
911
        let now = Instant::now();
912

913
        log::debug!(target: "expansion-phase", "Expanding macros -> phase 1");
×
914

915
        if let Some(kernel) = self.kernel.as_mut() {
1,242✔
916
            // Label anything at the top as well - top level
UNCOV
917
            kernel.load_syntax_transformers(&mut expanded_statements, "top-level".to_string())?;
×
918
        }
919

920
        for expr in expanded_statements.iter_mut() {
22,525✔
921
            expand_kernel_in_env(
922
                expr,
21,736✔
923
                self.kernel.as_mut(),
21,736✔
924
                self.builtin_modules.clone(),
21,736✔
925
                "top-level",
926
            )?;
927
            crate::parser::expand_visitor::expand(expr, &self.macro_env)?;
21,735✔
928
            lower_entire_ast(expr)?;
21,744✔
929

930
            for module in &self.lifted_macro_environments {
205,304✔
931
                if let Some(macro_env) = self.modules().get(module).map(|x| &x.macro_map) {
183,578✔
932
                    let source_id = self.sources.get_source_id(module).unwrap();
933

934
                    crate::parser::expand_visitor::expand_with_source_id(
935
                        expr,
936
                        macro_env,
937
                        Some(source_id),
938
                    )?;
939
                }
940
            }
941

942
            // Lift all of the kernel macros as well?
943
            for (module, lifted_env) in &mut self.lifted_kernel_environments {
57,150✔
944
                let changed = expand_kernel_in_env_with_change(
945
                    expr,
946
                    self.kernel.as_mut(),
947
                    self.builtin_modules.clone(),
948
                    &module,
949
                )?;
950

UNCOV
951
                if changed {
×
UNCOV
952
                    lifted_env.name_mangler.visit(expr);
×
953
                }
954
            }
955

956
            expand_kernel_in_env(
957
                expr,
21,726✔
958
                self.kernel.as_mut(),
21,726✔
959
                self.builtin_modules.clone(),
21,726✔
960
                "top-level",
961
            )?;
962

963
            // TODO: If we have this, then we have to lower all of the expressions again
964
            crate::parser::expand_visitor::expand(expr, &self.macro_env)?;
21,726✔
965

966
            // for expr in expanded_statements.iter_mut() {
967
            lower_entire_ast(expr)?;
21,726✔
968
        }
969

970
        // TODO: Check that defines are in legal positions, post expansion.
971

972
        #[cfg(feature = "profiling")]
973
        log::debug!(target: "pipeline_time", "Top level macro expansion time: {:?}", now.elapsed());
974

975
        log::debug!(target: "expansion-phase", "Beginning constant folding");
779✔
976

977
        // steel_parser::ast::AstTools::pretty_print(&expanded_statements);
978

979
        // println!(
980
        //     "Modules: {:#?}",
981
        //     self.module_manager.modules().keys().collect::<Vec<_>>()
982
        // );
983

984
        // self.sources.debug_sources();
985

986
        let expanded_statements =
779✔
987
            self.apply_const_evaluation(constant_primitives(), expanded_statements, false)?;
779✔
988

989
        let expanded_statements = flatten_begins_and_expand_defines(expanded_statements)?;
779✔
990

991
        // TODO: Move this to its own function
992
        let mut expanded_statements = filter_provides(expanded_statements);
993

994
        #[cfg(feature = "profiling")]
995
        let now = Instant::now();
996

997
        // println!("--- Before renaming: ----");
998
        // expanded_statements.pretty_print();
999

1000
        // TODO: Probably lift this above the const evaluation anyway
1001
        self.shadowed_variable_renamer
1002
            .rename_shadowed_variables(&mut expanded_statements, true);
1003

1004
        // println!("--- After renaming: ----");
1005
        // expanded_statements.pretty_print();
1006

1007
        // let mut expanded_statements =
1008
        //     self.apply_const_evaluation(constants.clone(), expanded_statements, false)?;
1009

1010
        let mut analysis = std::mem::take(&mut self.analysis);
1011

1012
        // Pre populate the analysis here
1013
        analysis.fresh_from_exprs(&expanded_statements);
1014

1015
        // let mut analysis = Analysis::from_exprs(&expanded_statements);
1016
        analysis.populate_captures(&expanded_statements);
1017

1018
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
1019

1020
        // This is definitely broken still
1021
        semantic
1022
            .elide_single_argument_lambda_applications()
1023
            .replace_non_shadowed_globals_with_builtins(
1024
                &mut self.macro_env,
1025
                &mut self.module_manager,
1026
                &mut self.mangled_identifiers,
1027
            )
1028
            // TODO: To get this to work, we have to check the macros to make sure those
1029
            // are safe to eliminate. In interactive mode, we'll
1030
            // be unable to optimize those away
1031
            .remove_unused_globals_with_prefix(
1032
                MANGLER_PREFIX,
1033
                &self.macro_env,
1034
                &self.module_manager,
1035
            )
1036
            .lift_pure_local_functions()
1037
            .lift_all_local_functions();
1038

1039
        // debug!("About to expand defines");
1040

UNCOV
1041
        log::debug!(target: "expansion-phase", "Flattening begins, converting internal defines to let expressions");
×
1042

1043
        let mut analysis = semantic.into_analysis();
779✔
1044

1045
        let mut expanded_statements = flatten_begins_and_expand_defines(expanded_statements)?;
779✔
1046

1047
        self.shadowed_variable_renamer
1048
            .rename_shadowed_variables(&mut expanded_statements, false);
1049

1050
        // After define expansion, we'll want this
1051
        // RenameShadowedVariables::rename_shadowed_vars(&mut expanded_statements);
1052

1053
        analysis.fresh_from_exprs(&expanded_statements);
1054
        analysis.populate_captures(&expanded_statements);
1055

1056
        // expanded_statements.pretty_print();
1057

1058
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
1059

1060
        // Check if the values are redefined!
UNCOV
1061
        semantic.check_if_values_are_redefined()?;
×
1062

1063
        // semantic.refresh_variables();
1064
        semantic.flatten_anonymous_functions();
779✔
1065
        semantic.refresh_variables();
779✔
1066

1067
        // Replace mutation with boxes
1068
        semantic.populate_captures();
779✔
1069
        semantic.populate_captures();
779✔
1070

1071
        semantic.replace_mutable_captured_variables_with_boxes();
779✔
1072

1073
        log::debug!(target: "expansion-phase", "Expanding multiple arity functions");
779✔
1074

1075
        let mut analysis = semantic.into_analysis();
779✔
1076

1077
        // Rename them again
1078
        // RenameShadowedVariables::rename_shadowed_vars(&mut expanded_statements);
1079
        self.shadowed_variable_renamer
779✔
1080
            .rename_shadowed_variables(&mut expanded_statements, false);
779✔
1081

1082
        // TODO - make sure I want to keep this
1083
        // let mut expanded_statements =
1084

1085
        // log::info!(target: "expansion-phase", "Aggressive constant evaluation with memoization");
1086

1087
        // Begin lowering anonymous function calls to lets
1088
        analysis.fresh_from_exprs(&expanded_statements);
779✔
1089
        analysis.populate_captures(&expanded_statements);
779✔
1090
        let mut semantic = SemanticAnalysis::from_analysis(&mut expanded_statements, analysis);
779✔
1091
        // semantic.populate_captures();
1092

1093
        semantic.replace_anonymous_function_calls_with_plain_lets();
779✔
1094

1095
        #[cfg(feature = "profiling")]
1096
        log::info!(target: "pipeline_time", "CAT time: {:?}", now.elapsed());
779✔
1097

1098
        self.analysis = semantic.into_analysis();
779✔
1099

1100
        // We don't want to leave this allocate memory just hanging around, but leave enough for
1101
        // interactive usages
1102
        self.analysis.shrink_capacity();
779✔
1103

1104
        // steel_parser::ast::AstTools::pretty_print(&expanded_statements);
1105

1106
        Ok(expanded_statements)
779✔
1107

1108
        // Done lowering anonymous function calls to let
1109
        // TODO: Re-enable this, but not in the repl. This repl causes... issues with the implementation
1110
        // self.apply_const_evaluation(constant_primitives(), expanded_statements, true)
1111
    }
1112

1113
    // TODO
1114
    // figure out how the symbols will work so that a raw program with symbols
1115
    // can be later pulled in and symbols can be interned correctly
1116
    fn compile_raw_program(
802✔
1117
        &mut self,
1118
        exprs: Vec<ExprKind>,
1119
        path: Option<PathBuf>,
1120
    ) -> Result<RawProgramWithSymbols> {
1121
        log::debug!(target: "expansion-phase", "Expanding macros -> phase 0");
802✔
1122

1123
        let expanded_statements = self.lower_expressions_impl(exprs, path)?;
1,604✔
1124

1125
        // println!("--- Final AST ---");
1126
        // println!("");
1127
        // steel_parser::ast::AstTools::pretty_print(&expanded_statements);
1128

1129
        // log::info!(
1130
        //     "{}",
1131
        //     expanded_statements
1132
        //         .iter()
1133
        //         .map(|x| x.to_pretty(60))
1134
        //         .join("\n\n")
1135
        // );
1136

1137
        log::debug!(target: "expansion-phase", "Generating instructions");
×
1138

1139
        let instructions = self.generate_instructions_for_executable(expanded_statements)?;
1,558✔
1140

1141
        let mut raw_program = RawProgramWithSymbols::new(
1142
            instructions,
779✔
1143
            self.constant_map.clone(),
779✔
1144
            "0.1.0".to_string(),
779✔
1145
        );
1146

1147
        // Make sure to apply the peephole optimizations
1148
        raw_program.apply_optimizations();
779✔
1149

1150
        // Lets see everything that gets run!
1151
        // raw_program.debug_print_log();
1152

1153
        Ok(raw_program)
779✔
1154
    }
1155

UNCOV
1156
    fn _run_const_evaluation_with_memoization(
×
1157
        &mut self,
1158
        mut _expanded_statements: Vec<ExprKind>,
1159
    ) -> Result<Vec<ExprKind>> {
1160
        todo!("Implement kernel level const evaluation here!")
1161
    }
1162

1163
    fn apply_const_evaluation(
779✔
1164
        &mut self,
1165
        constants: ImmutableHashMap<InternedString, SteelVal, FxBuildHasher>,
1166
        mut expanded_statements: Vec<ExprKind>,
1167
        use_kernel: bool,
1168
    ) -> Result<Vec<ExprKind>> {
1169
        #[cfg(feature = "profiling")]
1170
        let opt_time = Instant::now();
779✔
1171

1172
        let mut maybe_kernel = None;
779✔
1173

1174
        if use_kernel {
779✔
1175
            if let Some(kernel) = self.kernel.as_mut() {
×
UNCOV
1176
                kernel.load_program_for_comptime(constants.clone(), &mut expanded_statements)?;
×
1177
            }
1178
        }
1179

1180
        let mut manager = ConstantEvaluatorManager::new(
1181
            &mut self.memoization_table,
1182
            constants.clone(),
1183
            self.opt_level,
1184
            if use_kernel {
UNCOV
1185
                &mut self.kernel
×
1186
            } else {
1187
                &mut maybe_kernel
779✔
1188
            },
1189
        );
1190

1191
        match self.opt_level {
1192
            // TODO
1193
            // Cut this off at 10 iterations no matter what
1194
            OptLevel::Three => {
1195
                // for _ in 0..10 {
1196
                expanded_statements = manager.run(expanded_statements)?;
1,558✔
1197

1198
                // if !manager.changed {
1199
                //     break;
1200
                // }
1201

1202
                // manager.changed = false;
1203
                // }
1204
            }
1205
            OptLevel::Two => {
UNCOV
1206
                expanded_statements = ConstantEvaluatorManager::new(
×
UNCOV
1207
                    &mut self.memoization_table,
×
UNCOV
1208
                    constants,
×
UNCOV
1209
                    self.opt_level,
×
UNCOV
1210
                    if use_kernel {
×
UNCOV
1211
                        &mut self.kernel
×
1212
                    } else {
UNCOV
1213
                        &mut maybe_kernel
×
1214
                    },
1215
                )
UNCOV
1216
                .run(expanded_statements)?;
×
1217
            }
UNCOV
1218
            _ => {}
×
1219
        }
1220

1221
        #[cfg(feature = "profiling")]
1222
        if log::log_enabled!(target: "pipeline_time", log::Level::Debug) {
1223
            log::debug!(
1224
                target: "pipeline_time",
1225
                "Const Evaluation Time: {:?}",
1226
                opt_time.elapsed()
1227
            );
1228
        };
1229

1230
        Ok(expanded_statements)
779✔
1231
    }
1232
}
1233

1234
fn filter_provides(expanded_statements: Vec<ExprKind>) -> Vec<ExprKind> {
780✔
1235
    expanded_statements
780✔
1236
        .into_iter()
1237
        .filter_map(|expr| match expr {
22,507✔
1238
            ExprKind::Begin(mut b) => {
2,171✔
1239
                let exprs = std::mem::take(&mut b.exprs);
2,171✔
1240
                b.exprs = exprs
2,171✔
1241
                    .into_iter()
2,171✔
1242
                    .filter_map(|e| match e {
230,318✔
1243
                        ExprKind::List(l) => {
2,499✔
1244
                            if l.first_ident().copied() == Some(*PROVIDE) {
2,499✔
1245
                                return None;
4✔
1246
                            }
1247
                            return Some(ExprKind::List(l));
2,495✔
1248
                        }
1249
                        other => return Some(other),
225,648✔
1250
                    })
1251
                    .collect();
1252

1253
                Some(ExprKind::Begin(b))
1254
            }
1255
            ExprKind::List(l) => {
1,061✔
1256
                if l.first_ident().copied() == Some(*PROVIDE) {
1,061✔
UNCOV
1257
                    return None;
×
1258
                }
1259
                return Some(ExprKind::List(l));
1,061✔
1260
            }
1261
            other => Some(other),
18,495✔
1262
        })
1263
        .collect()
1264
}
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