Skip to content

Commit

Permalink
added defer
Browse files Browse the repository at this point in the history
  • Loading branch information
NotAFlyingGoose committed Jan 21, 2024
1 parent 6bbab63 commit 90ffde4
Show file tree
Hide file tree
Showing 20 changed files with 794 additions and 178 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,35 @@ core.println_any(core.Any {

In the future reflection will be made to embrace functions. When user-defined annotations are added, this will result in automation far more powerful than Rust macros.

### Defer

The defer statement allows you to code in the future by moving the given expression to the end of the current scope.

The expression in a defer is guarenteed to run, regardless of any breaks or returns.

```cpp
{
my_file := open_file("foo.txt");
defer close_file(my_file);

// .. do stuff with file

} // <- file gets freed here

```

Defers are "first in, last out". So later defers will run before earlier defers.

```cpp
file_manager := alloc_manager();
defer free_manager(file_manager);

file_manager.foo := open_file("foo.txt");
defer close_file(file_manager.foo);

// foo is freed, and then the file manager is freed
```
### Functions
Every Capy program must contain a `main` function. It is the entry point of the program.
Expand Down
9 changes: 6 additions & 3 deletions core/libc.capy
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ memcpy :: (dst: ^any, src: ^any, len: usize) extern;
// prints a string to the screen, adds a newline at the end
puts :: (text: str) extern;
// prints a char to the screen
putchar :: (char: char) extern;
putchar :: (ch: char) extern;

// opens a file for either reading "r", writing "w", appending "a",
// open a file to update both reading and writing "r+",
Expand All @@ -24,14 +24,17 @@ fopen :: (filename: str, mode: str) -> usize extern;
fclose :: (fp: usize) -> i32 extern;

// writes a char to a file
fputc :: (char: i8, fp: usize) -> i32 extern;
fputc :: (ch: char, fp: usize) -> i32 extern;
// writes a string to a file, doesn't add a newline at the end
fputs :: (text: str, fp: usize) -> i32 extern;

// read a char from a file
fgetc :: (fp: usize) -> u8 extern;
fgetc :: (fp: usize) -> char extern;
// read len char's from a file and store them in buf
fgets :: (buf: ^char, len: i32, fp: usize) -> str extern;

// tests the end-of-file indicator for the given file
feof :: (fp: usize) -> bool extern;

// exits the current program
exit :: (status: i32) extern;
21 changes: 21 additions & 0 deletions crates/ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def_multi_node! {
Return -> ReturnStmt
Break -> BreakStmt
Continue -> ContinueStmt
Defer -> DeferStmt
;
Define -> Define
;
Expand Down Expand Up @@ -371,6 +372,14 @@ impl ContinueStmt {
}
}

def_ast_node!(DeferStmt);

impl DeferStmt {
pub fn expr(self, tree: &SyntaxTree) -> Option<Expr> {
node(self, tree)
}
}

def_multi_node! {
Expr:
Cast -> CastExpr
Expand Down Expand Up @@ -1686,6 +1695,18 @@ mod tests {
assert!(continue_stmt.label(&tree).is_none());
}

#[test]
fn get_defer_expr() {
let (tree, root) = parse("defer foo();");
let statement = root.stmts(&tree).next().unwrap();
let defer_stmt = match statement {
Stmt::Defer(defer_stmt) => defer_stmt,
_ => unreachable!(),
};

assert!(matches!(defer_stmt.expr(&tree), Some(Expr::Call(_))));
}

#[test]
fn get_if_condition() {
let (tree, root) = parse("if true {}");
Expand Down
3 changes: 2 additions & 1 deletion crates/capy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ diagnostics = { path = "../diagnostics" }
interner = { path = "../interner" }
rustc-hash = "1.1"
supports-color = "2.0.0"
itertools = "0.11.0"
itertools = "0.12.0"
uid_gen = { path = "../uid_gen" }
path-clean = "1.0.1"
target-lexicon = "0.12.11"
serde_json = "1.0"
base64 = "0.21"
platform-dirs = "0.3.0"

[dependencies.reqwest]
version = "0.11"
Expand Down
7 changes: 6 additions & 1 deletion crates/capy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use hir::WorldIndex;
use itertools::Itertools;
use line_index::LineIndex;
use path_clean::PathClean;
use platform_dirs::AppDirs;
use rustc_hash::FxHashMap;
use std::fs;
use target_lexicon::Triple;
Expand Down Expand Up @@ -174,6 +175,8 @@ fn compile_file(

let mod_dir = if let Some(mod_dir) = mod_dir {
env::current_dir().unwrap().join(mod_dir).clean()
} else if let Some(mod_dir) = AppDirs::new(Some("capy"), false) {
mod_dir.data_dir.join("modules")
} else {
PathBuf::new()
.join(std::path::MAIN_SEPARATOR_STR)
Expand Down Expand Up @@ -240,6 +243,7 @@ fn compile_file(
interner.clone(),
bodies_map.clone(),
world_index.clone(),
&mod_dir,
verbose,
);

Expand Down Expand Up @@ -276,6 +280,7 @@ fn compile_file(
interner.clone(),
bodies_map.clone(),
world_index.clone(),
&mod_dir,
verbose,
);

Expand Down Expand Up @@ -308,7 +313,7 @@ fn compile_file(
)
.finish(entry_point);
if verbose >= 2 {
let debug = inference.debug(&mod_dir, &interner.borrow(), true);
let debug = inference.debug(&mod_dir, &interner.borrow(), verbose >= 4, true);
println!("=== types ===\n");
println!("{}", debug);
}
Expand Down
16 changes: 9 additions & 7 deletions crates/capy/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ impl SourceFile {
interner: Rc<RefCell<Interner>>,
bodies_map: Rc<RefCell<FxHashMap<hir::FileName, hir::Bodies>>>,
world_index: Rc<RefCell<hir::WorldIndex>>,
mod_dir: &std::path::Path,
verbose: u8,
) -> SourceFile {
if verbose >= 1 {
let module = hir::FileName(interner.borrow_mut().intern(&file_name.to_string_lossy()));

let is_mod = module.is_mod(mod_dir, &interner.borrow());

if (!is_mod && verbose >= 1) || (is_mod && verbose >= 4) {
println!("=== {} ===\n", file_name.display());
}

Expand All @@ -49,13 +54,8 @@ impl SourceFile {

let validation_diagnostics = ast::validation::validate(root, tree);

let module = hir::FileName(interner.borrow_mut().intern(&file_name.to_string_lossy()));
let (index, indexing_diagnostics) = hir::index(root, tree, &mut interner.borrow_mut());

if verbose >= 3 && !index.is_empty() {
println!("{}", index.debug(&interner.borrow()));
}

let mut res = Self {
file_name,
contents,
Expand Down Expand Up @@ -115,7 +115,9 @@ impl SourceFile {
.borrow_mut()
.add_file(self.module, self.index.clone());

if self.verbose >= 1 {
if (!self.module.is_mod(mod_dir, &self.interner.borrow()) && self.verbose >= 1)
|| self.verbose >= 4
{
let interner = self.interner.borrow();
let debug = bodies.debug(self.module, mod_dir, &interner, self.verbose >= 2);
if !debug.is_empty() {
Expand Down
109 changes: 94 additions & 15 deletions crates/codegen/src/compiler/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ use super::{
comptime::ComptimeResult, FunctionToCompile, MetaTyData, MetaTyInfoArrays, MetaTyLayoutArrays,
};

// represents a single block containing multiple defer statements
#[derive(Debug, Clone)]
pub(crate) struct DeferFrame {
id: Option<ScopeId>,
defers: Vec<Idx<hir::Expr>>,
}

pub(crate) struct FunctionCompiler<'a> {
pub(crate) file_name: hir::FileName,
pub(crate) signature: FinalSignature,
Expand Down Expand Up @@ -63,6 +70,7 @@ pub(crate) struct FunctionCompiler<'a> {
// for control flow (breaks and continues)
pub(crate) exits: FxHashMap<ScopeId, Block>,
pub(crate) continues: FxHashMap<ScopeId, Block>,
pub(crate) defer_stack: Vec<DeferFrame>,
}

impl FunctionCompiler<'_> {
Expand All @@ -72,6 +80,7 @@ impl FunctionCompiler<'_> {
return_ty: Intern<Ty>,
function_body: Idx<hir::Expr>,
new_idx_to_old_idx: FxHashMap<u64, u64>,
debug_print: bool,
) {
// Create the entry block, to start emitting code in.
let entry_block = self.builder.create_block();
Expand Down Expand Up @@ -165,6 +174,10 @@ impl FunctionCompiler<'_> {
None => self.builder.ins().return_(&[]),
};

if debug_print {
println!("{}", self.builder.func);
}

self.builder.seal_all_blocks();
self.builder.finalize();
}
Expand Down Expand Up @@ -531,19 +544,47 @@ impl FunctionCompiler<'_> {
} => {
let exit_block = self.exits[&label];

if let Some(value) = value {
let value = value.and_then(|value| {
let value_ty = self.tys[self.file_name][value];
let Some(value) = self.compile_expr(value) else {
self.builder.ins().jump(exit_block, &[]);
return;
};
let value = self.compile_expr(value)?;

let referenced_block_ty =
self.tys[self.file_name][self.bodies_map[&self.file_name][label]];

let value =
super::cast(&mut self.builder, value, value_ty, referenced_block_ty);
Some(super::cast(
&mut self.builder,
value,
value_ty,
referenced_block_ty,
))
});

// run all the defers from here, backwards to the one we are breaking out of

let mut used_frames = Vec::new();

while let Some(frame) = self.defer_stack.last().cloned() {
// the exit block of every Expr::Block contains the instructions for running
// the defers. This break instruction jumps to that exit block.
// therefore, we only need to insert extra defer handling for everything OTHER
// than the block we are breaking to.
if let Some(id) = frame.id {
if id == label {
break;
}
}

// do this in reverse for the reasons explained in the Expr::Block code
for defer in frame.defers.iter().rev() {
self.compile_expr(*defer);
}

used_frames.push(self.defer_stack.pop().unwrap());
}

self.defer_stack.extend(used_frames.into_iter().rev());

if let Some(value) = value {
self.builder.ins().jump(exit_block, &[value]);
} else {
self.builder.ins().jump(exit_block, &[]);
Expand All @@ -553,11 +594,22 @@ impl FunctionCompiler<'_> {
hir::Stmt::Continue {
label: Some(label), ..
} => {
let continue_block = self.exits[&label];
let continue_block = self.continues[&label];

self.builder.ins().jump(continue_block, &[]);
}
hir::Stmt::Continue { label: None, .. } => unreachable!(),
hir::Stmt::Defer { expr, .. } => {
// defer statements aren't actually compiled here, but only at the end of blocks,
// or during a break. We use stacks like this so breaks can execute all the defers
// between their location and the desired location.

self.defer_stack
.last_mut()
.expect("block didn't add to defer stack")
.defers
.push(expr);
}
}
}

Expand Down Expand Up @@ -1287,6 +1339,11 @@ impl FunctionCompiler<'_> {
self.exits.insert(scope_id, exit_block);
}

self.defer_stack.push(DeferFrame {
id: scope_id,
defers: Vec::new(),
});

self.builder.ins().jump(body_block, &[]);

self.builder.switch_to_block(body_block);
Expand Down Expand Up @@ -1333,21 +1390,29 @@ impl FunctionCompiler<'_> {
if !no_eval {
if let Some(value) = value {
self.builder.ins().jump(exit_block, &[value]);
} else if !expr_ty.is_zero_sized() && scope_id.is_some() {
// we know this block reaches it's end
// (it's not noeval)
} else if !expr_ty.is_void() && scope_id.is_some() {
// we know this block reaches it's end (it's !noeval)
//
// we also know it doesn't have a tail expression
// we also know it doesn't have a tail expression (because `value` was None)
//
// we also know it doesn't return void
// we also know it has a non-void type (no implicit tail expression)
//
// since it doesn't have a tail expression, the type checker has already
// confirmed that it *must* contain a break to it's own end
// confirmed that it *must* always reach a break to it's own end.
//
// this break has to be somewhere deep in an grandchild block. we know it
// exists, and we know that this exact point is unreachable because of that
// break, and because of the absence of a tail expression.
//
// therefore it's safe to trap *here*, at the end of the `body_block`
//
// but we can't trap in the `exit_block`, since the `exit_block` *is*
// reachable
// reachable.
//
// the reason we need to trap is because cranelift forces us to end all blocks with
// a "final" instruction (a jump or trap). we can't exactly jump to the exit
// because we don't have a value with which to jump (and remember the exit
// is expecting something non-void). so since it's safe to trap, we just trap.
self.builder.ins().trap(TrapCode::UnreachableCodeReached);
} else {
self.builder.ins().jump(exit_block, &[]);
Expand All @@ -1357,6 +1422,20 @@ impl FunctionCompiler<'_> {
self.builder.switch_to_block(exit_block);
self.builder.seal_block(exit_block);

// unwind our defers

let defer_frame = self.defer_stack.pop().expect("we just pushed this");

if !no_eval || scope_id.is_some() {
debug_assert_eq!(defer_frame.id, scope_id);

// do it in reverse to make sure later defers can still rely on the allocations of
// previous defers
for defer in defer_frame.defers.iter().rev() {
self.compile_expr(*defer);
}
}

if final_ty.into_real_type().is_some() {
Some(self.builder.block_params(exit_block)[0])
} else {
Expand Down
Loading

0 comments on commit 90ffde4

Please sign in to comment.