Skip to content

Commit

Permalink
Merge pull request #241 from candy-lang/channels
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcelGarus committed Oct 24, 2022
2 parents 17c96b7 + f95e722 commit e8c7eb0
Show file tree
Hide file tree
Showing 24 changed files with 1,999 additions and 417 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,23 @@ We already have a language server that provides some tooling.

## Short-term TODOs

- implement fibers, channels, and nurseries
- remove builtinPrint
- fix fault attribution
- rearchitect tracing
- new name?
- add caching while compile-time evaluating code
- tags?
- pattern matching
- pipe operator
- text interpolation
- eliminate common subtrees
- inline functions
- minimize inputs found through fuzzing
- make condition whether to keep running more granular
- fuzz parser
- support recursion
- remove builtinPrint
- tail call optimization
- new name?
- parse function declaration with doc comment but no code
- tracing visualization
- distinguish packages from normal modules
- complain about comment lines with too much indentation
- develop guidelines about how to format reasons

Expand Down
1 change: 1 addition & 0 deletions compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "candy"
version = "0.1.0"
edition = "2021"
rust-version = "1.56"

[profile.release]
debug = true
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/builtin_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use strum_macros::EnumIter;

#[derive(Debug, EnumIter, PartialEq, Eq, Clone, Hash, Copy)]
pub enum BuiltinFunction {
ChannelCreate, // capacity -> [sendPort, receivePort]
ChannelSend, // channel any -> Nothing
ChannelReceive, // channel -> any
Equals, // any any -> booleanSymbol
FunctionRun, // (lambdaWith0Arguments) -> (returnValue: any)
GetArgumentCount, // closure -> argumentCount
Expand All @@ -22,6 +25,7 @@ pub enum BuiltinFunction {
IntShiftLeft, // (value: int) (amount: int) -> (shifted: int)
IntShiftRight, // (value: int) (amount: int) -> (shifted: int)
IntSubtract, // (minuend: int) (subtrahend: int) -> (difference: int)
Parallel, // body: Closure -> returnValueOfClosure
Print, // message -> Nothing
StructGet, // struct key -> value
StructGetKeys, // struct -> listOfKeys
Expand All @@ -36,6 +40,7 @@ pub enum BuiltinFunction {
TextStartsWith, // text (pattern: text) -> booleanSymbol
TextTrimEnd, // text -> text
TextTrimStart, // text -> text
Try, // closure -> okWithClosureResultOrErrorWithPanicReason
TypeOf, // any -> typeSymbol
}
lazy_static! {
Expand Down
99 changes: 46 additions & 53 deletions compiler/src/fuzzer/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use super::{generator::generate_n_values, utils::did_need_in_closure_cause_panic
use crate::{
compiler::hir,
database::Database,
vm::{self, tracer::Tracer, use_provider::DbUseProvider, Closure, Heap, Pointer, Vm},
vm::{
self,
context::{ExecutionController, UseProvider},
tracer::Tracer,
Closure, Heap, Pointer, TearDownResult, Vm,
},
};
use std::mem;

Expand Down Expand Up @@ -32,7 +37,7 @@ pub enum Status {
}

impl Status {
fn new_fuzzing_attempt(db: &Database, closure_heap: &Heap, closure: Pointer) -> Status {
fn new_fuzzing_attempt(closure_heap: &Heap, closure: Pointer) -> Status {
let num_args = {
let closure: Closure = closure_heap.get(closure).data.clone().try_into().unwrap();
closure.num_args
Expand All @@ -42,19 +47,19 @@ impl Status {
let closure = closure_heap.clone_single_to_other_heap(&mut vm_heap, closure);
let arguments = generate_n_values(&mut vm_heap, num_args);

let use_provider = DbUseProvider { db };
let vm = Vm::new_for_running_closure(vm_heap, &use_provider, closure, &arguments);
let mut vm = Vm::new();
vm.set_up_for_running_closure(vm_heap, closure, &arguments);

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

let status = Status::new_fuzzing_attempt(db, &heap, closure);
let status = Status::new_fuzzing_attempt(&heap, closure);
Self {
closure_heap: heap,
closure,
Expand All @@ -67,62 +72,53 @@ impl Fuzzer {
self.status.as_ref().unwrap()
}

pub fn run(&mut self, db: &Database, mut num_instructions: usize) {
pub fn run<U: UseProvider, E: ExecutionController>(
&mut self,
db: &Database,
use_provider: &mut U,
execution_controller: &mut E,
) {
let mut status = mem::replace(&mut self.status, None).unwrap();
while matches!(status, Status::StillFuzzing { .. }) {
let (new_status, num_instructions_executed) =
self.map_status(db, status, num_instructions);
status = new_status;

if num_instructions_executed >= num_instructions {
break;
} else {
num_instructions -= num_instructions_executed;
}
while matches!(status, Status::StillFuzzing { .. })
&& execution_controller.should_continue_running()
{
status = self.map_status(status, db, use_provider, execution_controller);
}
self.status = Some(status);
}
fn map_status(
fn map_status<U: UseProvider, E: ExecutionController>(
&self,
db: &Database,
status: Status,
num_instructions: usize,
) -> (Status, usize) {
db: &Database,
use_provider: &mut U,
execution_controller: &mut E,
) -> Status {
match status {
Status::StillFuzzing { mut vm, arguments } => match &vm.status {
vm::Status::Running => {
let use_provider = DbUseProvider { db };
let num_instructions_executed_before = vm.num_instructions_executed;
vm.run(&use_provider, num_instructions);
let num_instruction_executed =
vm.num_instructions_executed - num_instructions_executed_before;
(
Status::StillFuzzing { vm, arguments },
num_instruction_executed,
)
Status::StillFuzzing { mut vm, arguments } => match vm.status() {
vm::Status::CanRun => {
vm.run(use_provider, execution_controller);
Status::StillFuzzing { vm, arguments }
}
vm::Status::WaitingForOperations => panic!("Fuzzing should not have to wait on channel operations because arguments were not channels."),
// The VM finished running without panicking.
vm::Status::Done => (
Status::new_fuzzing_attempt(db, &self.closure_heap, self.closure),
0,
),
vm::Status::Done => Status::new_fuzzing_attempt(&self.closure_heap, self.closure),
vm::Status::Panicked { reason } => {
// If a `needs` directly inside the tested closure was not
// satisfied, then the panic is not closure's fault, but our
// fault.
let TearDownResult { heap, tracer, .. } = vm.tear_down();
let is_our_fault =
did_need_in_closure_cause_panic(db, &self.closure_id, &vm.tracer);
let status = if is_our_fault {
Status::new_fuzzing_attempt(db, &self.closure_heap, self.closure)
did_need_in_closure_cause_panic(db, &self.closure_id, &tracer);
if is_our_fault {
Status::new_fuzzing_attempt(&self.closure_heap, self.closure)
} else {
Status::PanickedForArguments {
heap: vm.heap,
heap,
arguments,
reason: reason.clone(),
tracer: vm.tracer.clone(),
reason,
tracer,
}
};
(status, 0)
}
}
},
// We already found some arguments that caused the closure to panic,
Expand All @@ -132,15 +128,12 @@ impl Fuzzer {
arguments,
reason,
tracer,
} => (
Status::PanickedForArguments {
heap,
arguments,
reason,
tracer,
},
0,
),
} => Status::PanickedForArguments {
heap,
arguments,
reason,
tracer,
},
}
}
}
25 changes: 16 additions & 9 deletions compiler/src/fuzzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ pub use self::fuzzer::{Fuzzer, Status};
use crate::{
database::Database,
module::Module,
vm::{use_provider::DbUseProvider, Closure, Vm},
vm::{
context::{DbUseProvider, RunForever, RunLimitedNumberOfInstructions},
Closure, Vm,
},
};
use itertools::Itertools;
use tracing::info;

pub async fn fuzz(db: &Database, module: Module) {
let (fuzzables_heap, fuzzables) = {
let result = Vm::new_for_running_module_closure(
&DbUseProvider { db },
Closure::of_module(db, module.clone()).unwrap(),
)
.run_synchronously_until_completion(db);
let mut vm = Vm::new();
vm.set_up_for_running_module_closure(Closure::of_module(db, module.clone()).unwrap());
vm.run(&mut DbUseProvider { db }, &mut RunForever);
let result = vm.tear_down();
(result.heap, result.fuzzable_closures)
};

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

for (id, closure) in fuzzables {
let mut fuzzer = Fuzzer::new(db, &fuzzables_heap, closure, id.clone());
fuzzer.run(db, 1000);
info!("Fuzzing {id}.");
let mut fuzzer = Fuzzer::new(&fuzzables_heap, closure, id.clone());
fuzzer.run(
db,
&mut DbUseProvider { db },
&mut RunLimitedNumberOfInstructions::new(1000),
);
match fuzzer.status() {
Status::StillFuzzing { .. } => {}
Status::PanickedForArguments {
Expand All @@ -48,7 +55,7 @@ pub async fn fuzz(db: &Database, module: Module) {
info!("This was the stack trace:");
tracer.dump_stack_trace(db, heap);

module.dump_associated_debug_file("trace", &tracer.format_call_tree(heap));
module.dump_associated_debug_file("trace", &tracer.full_trace().format(heap));
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/fuzzer/utils.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use crate::{
compiler::hir::{self, Expression, HirDb, Lambda},
database::Database,
vm::tracer::{TraceEntry, Tracer},
vm::tracer::{EventData, Tracer},
};

pub fn did_need_in_closure_cause_panic(
db: &Database,
closure_id: &hir::Id,
tracer: &Tracer,
) -> bool {
let entry = if let Some(entry) = tracer.log().last() {
let entry = if let Some(entry) = tracer.events.last() {
entry
} else {
// The only way there's no trace log before the panic is when there's an
// error from an earlier compilation stage that got lowered into the
// LIR. That's also definitely the fault of the function.
return false;
};
if let TraceEntry::NeedsStarted { id, .. } = entry {
if let EventData::NeedsStarted { id, .. } = &entry.data {
let mut id = id.parent().unwrap();
loop {
if &id == closure_id {
Expand Down
Loading

0 comments on commit e8c7eb0

Please sign in to comment.