Skip to content

Commit

Permalink
Emit static funcs instead of C++ lambdas
Browse files Browse the repository at this point in the history
Summary:
In a production compiler, every function would probably be compiled into
IR separately, and there would be no need to match compilation order
with output order.

Approximate this model by maintaining multiple text buffers: one for the
file, one for main(), and one per emitted function. These buffers
(except the file one) can then be written out in any order.

Remove the unnecessary template from Compiler and use Box<dyn Write>
instead.

Reviewed By: avp

Differential Revision: D37260945

fbshipit-source-id: 7d1d565b22c732bee4e44543177769388944b468
  • Loading branch information
tmikov authored and facebook-github-bot committed Jul 29, 2022
1 parent 0dc6ff3 commit fa0dfdb
Showing 1 changed file with 115 additions and 38 deletions.
153 changes: 115 additions & 38 deletions unsupported/juno/crates/flow_native/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,31 +76,6 @@ impl Options {
}
}

/// Print to the output stream if no errors have been seen so far.
/// `$compiler` is a mutable reference to the Compiler struct.
/// `$arg` arguments follow the format pattern used by `format!`.
/// The output must be ASCII.
macro_rules! out {
($compiler:expr, $($arg:tt)*) => {{
$compiler.writer.write_ascii(format_args!($($arg)*));
}}
}

struct Writer<'w> {
out: BufWriter<&'w mut dyn Write>,
}

impl Writer<'_> {
/// Write to the `out` writer. Used via the `out!` macro. The output must be ASCII.
fn write_ascii(&mut self, args: fmt::Arguments<'_>) {
let buf = format!("{}", args);
debug_assert!(buf.is_ascii(), "Output must be ASCII");
if let Err(e) = self.out.write_all(buf.as_bytes()) {
panic!("Failed to write out program: {}", e)
}
}
}

struct FindEscapes {
sem: Rc<SemContext>,
escaped_decls: HashSet<DeclId>,
Expand Down Expand Up @@ -222,9 +197,63 @@ impl CompilerStrings {
}
}

/// Splitting the func storage out of the Compiler struct only to placate the
/// borrower. This struct may be mutably borrowed simultaneously with another
/// field in [`Compiler`].
struct Output {
/// The functions generated by the compiler.
chunks: Vec<Vec<u8>>,
/// The function currently being generated, index in [`funcs`].
cur_chunk: usize,
/// List of freed chunks.
free_chunks: Vec<usize>,
}

impl Output {
/// Write to the current chunk. Used via the `out!` macro. The output must be ASCII.
fn write_ascii(&mut self, args: fmt::Arguments<'_>) {
let buf = format!("{}", args);
debug_assert!(buf.is_ascii(), "Output must be ASCII");
self.chunks[self.cur_chunk]
.write_all(buf.as_bytes())
.unwrap();
}

/// Crete a new chunk, make it active, return its index.
fn new_chunk(&mut self) -> usize {
if let Some(freed) = self.free_chunks.pop() {
self.cur_chunk = freed;
} else {
self.cur_chunk = self.chunks.len();
self.chunks.push(Vec::new());
}
self.cur_chunk
}

/// Free a chunk when it is no longer needed.
#[allow(dead_code)]
fn free_chunk(&mut self, index: usize) {
self.chunks[index].clear();
self.free_chunks.push(index);
}

/// Append the context of chunk `src` into chunk `dest` and empty `src`.
#[allow(dead_code)]
fn append_chunk(&mut self, dest: usize, src: usize) {
let mut tmp = std::mem::take(&mut self.chunks[src]);
self.chunks[dest].append(&mut tmp);
}

/// Set the current chunk to the specified index.
fn set_chunk(&mut self, prev: usize) {
self.cur_chunk = prev;
}
}

struct Compiler<'w> {
writer: Writer<'w>,
writer: BufWriter<&'w mut dyn Write>,
sem: Rc<SemContext>,
output: Output,
/// Whether to emit line statements or just comments.
debug: bool,
/// File of last emitted statement.
Expand All @@ -238,6 +267,16 @@ struct Compiler<'w> {
compiler_strings: CompilerStrings,
}

/// Print to the current function.
/// `$compiler` is a mutable reference to the Compiler struct.
/// `$arg` arguments follow the format pattern used by `format!`.
/// The output must be ASCII.
macro_rules! out {
($compiler:expr, $($arg:tt)*) => {{
$compiler.output.write_ascii(format_args!($($arg)*));
}}
}

impl Compiler<'_> {
fn find_escapes<'gc>(
sem: Rc<SemContext>,
Expand All @@ -263,13 +302,15 @@ impl Compiler<'_> {
sem: Rc<SemContext>,
) {
let lock = ast::GCLock::new(&mut ctx);
let writer = Writer {
out: BufWriter::new(out),
};
let escaped_decls = Self::find_escapes(Rc::clone(&sem), ast.node(&lock), &lock);
let mut comp = Compiler {
writer,
writer: BufWriter::new(out),
sem,
output: Output {
chunks: vec![Vec::new()],
cur_chunk: 0,
free_chunks: Default::default(),
},
debug,
last_stmt_file: SourceId::INVALID,
last_stmt_line: 0,
Expand All @@ -291,6 +332,9 @@ impl Compiler<'_> {
use ast::*;
out!(self, "#include \"FNRuntime.h\"\n");
self.gen_context();

let main_chunk = self.output.new_chunk();
debug_assert!(main_chunk == 1);
out!(self, "static void init_strings();\n");
out!(self, "int main(){{\n");
out!(self, " init_strings();\n");
Expand All @@ -308,6 +352,22 @@ impl Compiler<'_> {
out!(self, " fnAddCompilerString({:?}, {});\n", str, index);
}
out!(self, "}}\n");
self.output.set_chunk(0);

self.writer
.write_all(self.output.chunks[0].as_slice())
.expect("Error writing out program");

for i in 2..self.output.chunks.len() {
self.writer
.write_all(self.output.chunks[i].as_slice())
.expect("Error writing out program");
}

// Output the chunk containing main() last.
self.writer
.write_all(self.output.chunks[1].as_slice())
.expect("Error writing out program");
}

fn gen_context(&mut self) {
Expand Down Expand Up @@ -401,14 +461,21 @@ impl Compiler<'_> {
lock: &'gc ast::GCLock,
) -> ValueId {
use ast::*;
let result = self.new_value();
out!(
self,
"FNValue {}=FNValue::encodeClosure(new FNClosure{{(void(*)(void))(+[](",
result
);
// Allocate a new buffer for this function.
let prev_chunk = self.output.cur_chunk;
let this_chunk = self.output.new_chunk();

// Emit the declaration.
self.output.set_chunk(0);
out!(self, "static FNValue func_{}(", this_chunk);
self.param_list_for_arg_count(params.len());
out!(self, ");\n");

// Emit the definition.
self.output.set_chunk(this_chunk);
out!(self, "static FNValue func_{}(", this_chunk);
self.param_list_for_arg_count(params.len());
out!(self, ") -> FNValue {{");
out!(self, ") {{\n");
out!(
self,
"\nScope{scope} *scope{scope} = (Scope{scope}*)parent_scope;"
Expand All @@ -426,7 +493,17 @@ impl Compiler<'_> {
self.gen_stmt(stmt, fn_scope, lock);
}
out!(self, "\n return FNValue::encodeUndefined();\n");
out!(self, "}}), scope{scope}}});");
out!(self, "}}\n");

self.output.set_chunk(prev_chunk);

let result = self.new_value();
out!(
self,
"FNValue {}=FNValue::encodeClosure(new FNClosure{{(void(*)(void))func_{}, scope{scope}}});",
result,
this_chunk
);
result
}

Expand Down

0 comments on commit fa0dfdb

Please sign in to comment.