📜 Add better documentation across the compiler. (#3)

These changes pay particular attention to API endpoints, to try to
ensure that any rustdocs generated are detailed and sensible. A good
next step, eventually, might be to include doctest examples, as well.
For the moment, it's not clear that they would provide a lot of value,
though.

In addition, this does a couple refactors to simplify the code base in
ways that make things clearer or, at least, briefer.
This commit is contained in:
2023-05-13 14:34:48 -05:00
parent f4594bf2cc
commit 1fbfd0c2d2
28 changed files with 1550 additions and 432 deletions

View File

@@ -8,9 +8,14 @@ use std::fmt::Write;
use target_lexicon::Triple;
use thiserror::Error;
/// An object for querying / using functions built into the runtime.
///
/// Right now, this is a quite a bit of boilerplate for very nebulous
/// value. However, as the number of built-in functions gets large, it's
/// nice to have a single point to register and query them, so here we
/// go.
pub struct RuntimeFunctions {
builtin_functions: HashMap<String, FuncId>,
_referenced_functions: Vec<String>,
}
#[derive(Debug, Error, PartialEq)]
@@ -19,25 +24,27 @@ pub enum RuntimeFunctionError {
CannotFindRuntimeFunction(String),
}
extern "C" fn runtime_print(output_buffer: *mut String, name: *const i8, value: i64) {
let cstr = unsafe { CStr::from_ptr(name) };
let reconstituted = cstr.to_string_lossy();
if let Some(output_buffer) = unsafe { output_buffer.as_mut() } {
writeln!(output_buffer, "{} = {}i64", reconstituted, value).unwrap();
} else {
println!("{} = {}", reconstituted, value);
}
}
impl RuntimeFunctions {
/// Generate a new runtime function table for the given platform, and
/// declare them within the provided Cranelift module.
///
/// Note that this is very conservative: it assumes that your module
/// will want to use every runtime function. Unless the Cranelift object
/// builder is smart, this might inject a bunch of references (and thus
/// linker requirements) that aren't actually needed by your program.
///
/// Then again, right now there's exactly one runtime function, so ...
/// not a big deal.
pub fn new<M: Module>(platform: &Triple, module: &mut M) -> ModuleResult<RuntimeFunctions> {
let mut builtin_functions = HashMap::new();
let _referenced_functions = Vec::new();
let string_param = AbiParam::new(types::I64);
let int64_param = AbiParam::new(types::I64);
// declare print for Cranelift; it's something we're going to import
// into the current module (it's compiled separately), and takes two
// strings and an integer. (Which ... turn out to all be the same
// underlying type, which is weird but the way it is.)
let print_id = module.declare_function(
"print",
Linkage::Import,
@@ -47,14 +54,19 @@ impl RuntimeFunctions {
call_conv: CallConv::triple_default(platform),
},
)?;
// Toss this function in our internal dictionary, as well.
builtin_functions.insert("print".to_string(), print_id);
Ok(RuntimeFunctions {
builtin_functions,
_referenced_functions,
})
Ok(RuntimeFunctions { builtin_functions })
}
/// Include the named runtime function into the current Function context.
///
/// This is necessary for every runtime function reference within each
/// function. The returned `FuncRef` can be used in `call` invocations.
/// The only reason for this function to error is if you pass a name that
/// the runtime isn't familiar with.
pub fn include_runtime_function<M: Module>(
&self,
name: &str,
@@ -69,7 +81,30 @@ impl RuntimeFunctions {
}
}
/// Register live, local versions of the runtime functions into the JIT.
///
/// Note that these implementations are *not* the same as the ones defined
/// in `CARGO_MANIFEST_DIR/runtime/`, for ... reasons. It might be a good
/// change, in the future, to find a way to unify these implementations into
/// one; both to reduce the chance that they deviate, and to reduce overall
/// maintenance burden.
pub fn register_jit_implementations(builder: &mut JITBuilder) {
builder.symbol("print", runtime_print as *const u8);
}
}
// Print! This implementation is used in the JIT compiler, to actually print data. We
// use the `output_buffer` argument as an aid for testing; if it's non-NULL, it's a string
// we extend with the output, so that multiple JIT'd `Program`s can run concurrently
// without stomping over each other's output. If `output_buffer` is NULL, we just print
// to stdout.
extern "C" fn runtime_print(output_buffer: *mut String, name: *const i8, value: i64) {
let cstr = unsafe { CStr::from_ptr(name) };
let reconstituted = cstr.to_string_lossy();
if let Some(output_buffer) = unsafe { output_buffer.as_mut() } {
writeln!(output_buffer, "{} = {}i64", reconstituted, value).unwrap();
} else {
println!("{} = {}", reconstituted, value);
}
}