Skip to content

Commit e8c7eb0

Browse files
authored
Merge pull request #241 from candy-lang/channels
2 parents 17c96b7 + f95e722 commit e8c7eb0

24 files changed

+1999
-417
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,20 +136,23 @@ We already have a language server that provides some tooling.
136136

137137
## Short-term TODOs
138138

139-
- implement fibers, channels, and nurseries
140-
- remove builtinPrint
139+
- fix fault attribution
140+
- rearchitect tracing
141+
- new name?
141142
- add caching while compile-time evaluating code
143+
- tags?
142144
- pattern matching
143145
- pipe operator
146+
- text interpolation
144147
- eliminate common subtrees
145148
- inline functions
146149
- minimize inputs found through fuzzing
147-
- make condition whether to keep running more granular
148150
- fuzz parser
149-
- support recursion
151+
- remove builtinPrint
150152
- tail call optimization
151-
- new name?
152153
- parse function declaration with doc comment but no code
154+
- tracing visualization
155+
- distinguish packages from normal modules
153156
- complain about comment lines with too much indentation
154157
- develop guidelines about how to format reasons
155158

compiler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "candy"
33
version = "0.1.0"
44
edition = "2021"
5+
rust-version = "1.56"
56

67
[profile.release]
78
debug = true

compiler/src/builtin_functions.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use strum_macros::EnumIter;
44

55
#[derive(Debug, EnumIter, PartialEq, Eq, Clone, Hash, Copy)]
66
pub enum BuiltinFunction {
7+
ChannelCreate, // capacity -> [sendPort, receivePort]
8+
ChannelSend, // channel any -> Nothing
9+
ChannelReceive, // channel -> any
710
Equals, // any any -> booleanSymbol
811
FunctionRun, // (lambdaWith0Arguments) -> (returnValue: any)
912
GetArgumentCount, // closure -> argumentCount
@@ -22,6 +25,7 @@ pub enum BuiltinFunction {
2225
IntShiftLeft, // (value: int) (amount: int) -> (shifted: int)
2326
IntShiftRight, // (value: int) (amount: int) -> (shifted: int)
2427
IntSubtract, // (minuend: int) (subtrahend: int) -> (difference: int)
28+
Parallel, // body: Closure -> returnValueOfClosure
2529
Print, // message -> Nothing
2630
StructGet, // struct key -> value
2731
StructGetKeys, // struct -> listOfKeys
@@ -36,6 +40,7 @@ pub enum BuiltinFunction {
3640
TextStartsWith, // text (pattern: text) -> booleanSymbol
3741
TextTrimEnd, // text -> text
3842
TextTrimStart, // text -> text
43+
Try, // closure -> okWithClosureResultOrErrorWithPanicReason
3944
TypeOf, // any -> typeSymbol
4045
}
4146
lazy_static! {

compiler/src/fuzzer/fuzzer.rs

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ use super::{generator::generate_n_values, utils::did_need_in_closure_cause_panic
22
use crate::{
33
compiler::hir,
44
database::Database,
5-
vm::{self, tracer::Tracer, use_provider::DbUseProvider, Closure, Heap, Pointer, Vm},
5+
vm::{
6+
self,
7+
context::{ExecutionController, UseProvider},
8+
tracer::Tracer,
9+
Closure, Heap, Pointer, TearDownResult, Vm,
10+
},
611
};
712
use std::mem;
813

@@ -32,7 +37,7 @@ pub enum Status {
3237
}
3338

3439
impl Status {
35-
fn new_fuzzing_attempt(db: &Database, closure_heap: &Heap, closure: Pointer) -> Status {
40+
fn new_fuzzing_attempt(closure_heap: &Heap, closure: Pointer) -> Status {
3641
let num_args = {
3742
let closure: Closure = closure_heap.get(closure).data.clone().try_into().unwrap();
3843
closure.num_args
@@ -42,19 +47,19 @@ impl Status {
4247
let closure = closure_heap.clone_single_to_other_heap(&mut vm_heap, closure);
4348
let arguments = generate_n_values(&mut vm_heap, num_args);
4449

45-
let use_provider = DbUseProvider { db };
46-
let vm = Vm::new_for_running_closure(vm_heap, &use_provider, closure, &arguments);
50+
let mut vm = Vm::new();
51+
vm.set_up_for_running_closure(vm_heap, closure, &arguments);
4752

4853
Status::StillFuzzing { vm, arguments }
4954
}
5055
}
5156
impl Fuzzer {
52-
pub fn new(db: &Database, closure_heap: &Heap, closure: Pointer, closure_id: hir::Id) -> Self {
57+
pub fn new(closure_heap: &Heap, closure: Pointer, closure_id: hir::Id) -> Self {
5358
// The given `closure_heap` may contain other fuzzable closures.
5459
let mut heap = Heap::default();
5560
let closure = closure_heap.clone_single_to_other_heap(&mut heap, closure);
5661

57-
let status = Status::new_fuzzing_attempt(db, &heap, closure);
62+
let status = Status::new_fuzzing_attempt(&heap, closure);
5863
Self {
5964
closure_heap: heap,
6065
closure,
@@ -67,62 +72,53 @@ impl Fuzzer {
6772
self.status.as_ref().unwrap()
6873
}
6974

70-
pub fn run(&mut self, db: &Database, mut num_instructions: usize) {
75+
pub fn run<U: UseProvider, E: ExecutionController>(
76+
&mut self,
77+
db: &Database,
78+
use_provider: &mut U,
79+
execution_controller: &mut E,
80+
) {
7181
let mut status = mem::replace(&mut self.status, None).unwrap();
72-
while matches!(status, Status::StillFuzzing { .. }) {
73-
let (new_status, num_instructions_executed) =
74-
self.map_status(db, status, num_instructions);
75-
status = new_status;
76-
77-
if num_instructions_executed >= num_instructions {
78-
break;
79-
} else {
80-
num_instructions -= num_instructions_executed;
81-
}
82+
while matches!(status, Status::StillFuzzing { .. })
83+
&& execution_controller.should_continue_running()
84+
{
85+
status = self.map_status(status, db, use_provider, execution_controller);
8286
}
8387
self.status = Some(status);
8488
}
85-
fn map_status(
89+
fn map_status<U: UseProvider, E: ExecutionController>(
8690
&self,
87-
db: &Database,
8891
status: Status,
89-
num_instructions: usize,
90-
) -> (Status, usize) {
92+
db: &Database,
93+
use_provider: &mut U,
94+
execution_controller: &mut E,
95+
) -> Status {
9196
match status {
92-
Status::StillFuzzing { mut vm, arguments } => match &vm.status {
93-
vm::Status::Running => {
94-
let use_provider = DbUseProvider { db };
95-
let num_instructions_executed_before = vm.num_instructions_executed;
96-
vm.run(&use_provider, num_instructions);
97-
let num_instruction_executed =
98-
vm.num_instructions_executed - num_instructions_executed_before;
99-
(
100-
Status::StillFuzzing { vm, arguments },
101-
num_instruction_executed,
102-
)
97+
Status::StillFuzzing { mut vm, arguments } => match vm.status() {
98+
vm::Status::CanRun => {
99+
vm.run(use_provider, execution_controller);
100+
Status::StillFuzzing { vm, arguments }
103101
}
102+
vm::Status::WaitingForOperations => panic!("Fuzzing should not have to wait on channel operations because arguments were not channels."),
104103
// The VM finished running without panicking.
105-
vm::Status::Done => (
106-
Status::new_fuzzing_attempt(db, &self.closure_heap, self.closure),
107-
0,
108-
),
104+
vm::Status::Done => Status::new_fuzzing_attempt(&self.closure_heap, self.closure),
109105
vm::Status::Panicked { reason } => {
110106
// If a `needs` directly inside the tested closure was not
111107
// satisfied, then the panic is not closure's fault, but our
112108
// fault.
109+
let TearDownResult { heap, tracer, .. } = vm.tear_down();
113110
let is_our_fault =
114-
did_need_in_closure_cause_panic(db, &self.closure_id, &vm.tracer);
115-
let status = if is_our_fault {
116-
Status::new_fuzzing_attempt(db, &self.closure_heap, self.closure)
111+
did_need_in_closure_cause_panic(db, &self.closure_id, &tracer);
112+
if is_our_fault {
113+
Status::new_fuzzing_attempt(&self.closure_heap, self.closure)
117114
} else {
118115
Status::PanickedForArguments {
119-
heap: vm.heap,
116+
heap,
120117
arguments,
121-
reason: reason.clone(),
122-
tracer: vm.tracer.clone(),
118+
reason,
119+
tracer,
123120
}
124-
};
125-
(status, 0)
121+
}
126122
}
127123
},
128124
// We already found some arguments that caused the closure to panic,
@@ -132,15 +128,12 @@ impl Fuzzer {
132128
arguments,
133129
reason,
134130
tracer,
135-
} => (
136-
Status::PanickedForArguments {
137-
heap,
138-
arguments,
139-
reason,
140-
tracer,
141-
},
142-
0,
143-
),
131+
} => Status::PanickedForArguments {
132+
heap,
133+
arguments,
134+
reason,
135+
tracer,
136+
},
144137
}
145138
}
146139
}

compiler/src/fuzzer/mod.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ pub use self::fuzzer::{Fuzzer, Status};
66
use crate::{
77
database::Database,
88
module::Module,
9-
vm::{use_provider::DbUseProvider, Closure, Vm},
9+
vm::{
10+
context::{DbUseProvider, RunForever, RunLimitedNumberOfInstructions},
11+
Closure, Vm,
12+
},
1013
};
1114
use itertools::Itertools;
1215
use tracing::info;
1316

1417
pub async fn fuzz(db: &Database, module: Module) {
1518
let (fuzzables_heap, fuzzables) = {
16-
let result = Vm::new_for_running_module_closure(
17-
&DbUseProvider { db },
18-
Closure::of_module(db, module.clone()).unwrap(),
19-
)
20-
.run_synchronously_until_completion(db);
19+
let mut vm = Vm::new();
20+
vm.set_up_for_running_module_closure(Closure::of_module(db, module.clone()).unwrap());
21+
vm.run(&mut DbUseProvider { db }, &mut RunForever);
22+
let result = vm.tear_down();
2123
(result.heap, result.fuzzable_closures)
2224
};
2325

@@ -27,8 +29,13 @@ pub async fn fuzz(db: &Database, module: Module) {
2729
);
2830

2931
for (id, closure) in fuzzables {
30-
let mut fuzzer = Fuzzer::new(db, &fuzzables_heap, closure, id.clone());
31-
fuzzer.run(db, 1000);
32+
info!("Fuzzing {id}.");
33+
let mut fuzzer = Fuzzer::new(&fuzzables_heap, closure, id.clone());
34+
fuzzer.run(
35+
db,
36+
&mut DbUseProvider { db },
37+
&mut RunLimitedNumberOfInstructions::new(1000),
38+
);
3239
match fuzzer.status() {
3340
Status::StillFuzzing { .. } => {}
3441
Status::PanickedForArguments {
@@ -48,7 +55,7 @@ pub async fn fuzz(db: &Database, module: Module) {
4855
info!("This was the stack trace:");
4956
tracer.dump_stack_trace(db, heap);
5057

51-
module.dump_associated_debug_file("trace", &tracer.format_call_tree(heap));
58+
module.dump_associated_debug_file("trace", &tracer.full_trace().format(heap));
5259
}
5360
}
5461
}

compiler/src/fuzzer/utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
use crate::{
22
compiler::hir::{self, Expression, HirDb, Lambda},
33
database::Database,
4-
vm::tracer::{TraceEntry, Tracer},
4+
vm::tracer::{EventData, Tracer},
55
};
66

77
pub fn did_need_in_closure_cause_panic(
88
db: &Database,
99
closure_id: &hir::Id,
1010
tracer: &Tracer,
1111
) -> bool {
12-
let entry = if let Some(entry) = tracer.log().last() {
12+
let entry = if let Some(entry) = tracer.events.last() {
1313
entry
1414
} else {
1515
// The only way there's no trace log before the panic is when there's an
1616
// error from an earlier compilation stage that got lowered into the
1717
// LIR. That's also definitely the fault of the function.
1818
return false;
1919
};
20-
if let TraceEntry::NeedsStarted { id, .. } = entry {
20+
if let EventData::NeedsStarted { id, .. } = &entry.data {
2121
let mut id = id.parent().unwrap();
2222
loop {
2323
if &id == closure_id {

0 commit comments

Comments
 (0)