Write some docs

This commit is contained in:
khyperia 2020-09-21 14:49:05 +02:00
parent 78f7d8f91c
commit ebff8a8f39
4 changed files with 51 additions and 15 deletions

View File

@ -1318,8 +1318,9 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
let (result_type, argument_types) = loop {
match self.lookup_type(llfn.ty) {
SpirvType::Pointer { pointee, .. } => {
if let Some(func) = self.cx.function_pointers.borrow().get(&llfn) {
llfn = *func;
// See comment on register_fn_ptr
if let Some(func) = self.lookup_fn_ptr(llfn) {
llfn = func;
} else {
llfn = self
.emit()

View File

@ -38,6 +38,7 @@ pub struct Builder<'a, 'tcx> {
}
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// See comment on BuilderCursor
pub fn emit(&self) -> std::cell::RefMut<rspirv::dr::Builder> {
self.emit_with_cursor(self.cursor)
}

View File

@ -36,6 +36,22 @@ pub enum SpirvConst {
Undef(Word),
}
/// Cursor system:
/// The LLVM module builder model (and therefore codegen_ssa) assumes that there is a central module object, then,
/// builder objects are created pointing at that central module object (e.g. for adding instructions to a basic block).
/// Several of these builder objects can be live at the same time, mutating the central module object all at once.
/// Unfortunately, rspirv doesn't work like that. Instead, there is a single builder object, which owns a module and a
/// "cursor". This cursor indicates to the builder where to append instructions when an instruction is added - e.g. if
/// add() is called, then OpAdd is appended to the basic block pointed to by the cursor.
/// So! We emulate the LLVM system by treating the rspirv Builder as the "central module object", then, when a "builder
/// object" is created, we store a reference to a RefCell<rspirv builder>, *as well as* a copy of the cursor for that
/// particular builder. Whenever the RefCell is borrowed, then we stomp over the rspirv cursor with our copy, causing the
/// duration of that RefCell borrow to use that cursor.
/// So, if you're writing code inside crate::builder::Builder, then self.emit() will use self.cursor (the current basic
/// block) as that "stomp-over" cursor and return a mutable reference to the rspirv builder. If you're writing code
/// elsewhere (codegen_cx::CodegenCx), then self.emit_global() will use the generic "global cursor" and return a mutable
/// reference to the rspirv builder with no basic block nor function selected, i.e. any instructions emitted will be in
/// the global section.
#[derive(Debug, Default, Copy, Clone)]
#[must_use = "BuilderCursor should usually be assigned to the Builder.cursor field"]
pub struct BuilderCursor {
@ -99,8 +115,7 @@ impl BuilderSpirv {
.unwrap();
}
/// Note: This Builder can be used by multiple things being built at the same time, so, we need to save and restore
/// the current cursor every time we do something.
/// See comment on BuilderCursor
pub fn builder(&self, cursor: BuilderCursor) -> RefMut<Builder> {
let mut builder = self.builder.borrow_mut();
// select_function does bounds checks and other relatively expensive things, so don't just call it

View File

@ -52,7 +52,7 @@ pub struct CodegenCx<'tcx> {
/// Cache of all the builtin symbols we need
pub sym: Box<Symbols>,
/// Functions created in get_fn_addr
pub function_pointers: RefCell<HashMap<SpirvValue, SpirvValue>>,
function_pointers: RefCell<HashMap<SpirvValue, SpirvValue>>,
}
impl<'tcx> CodegenCx<'tcx> {
@ -74,6 +74,7 @@ impl<'tcx> CodegenCx<'tcx> {
}
}
/// See comment on BuilderCursor
pub fn emit_global(&self) -> std::cell::RefMut<rspirv::dr::Builder> {
self.builder.builder(BuilderCursor {
function: None,
@ -81,6 +82,7 @@ impl<'tcx> CodegenCx<'tcx> {
})
}
/// See comment on BuilderCursor
pub fn emit_with_cursor(
&self,
cursor: BuilderCursor,
@ -131,6 +133,32 @@ impl<'tcx> CodegenCx<'tcx> {
once(Operand::LiteralString(name)).chain(once(Operand::LinkageType(linkage))),
)
}
/// Function pointer registration:
/// LLVM, and therefore codegen_ssa, is very murky with function values vs. function pointers. So, codegen_ssa has a
/// pattern where *even for direct function calls*, it uses get_fn_*addr*, and then uses that function *pointer* when
/// calling BuilderMethods::call(). However, spir-v doesn't support function pointers! So, instead, when get_fn_addr
/// is called, we register a "token" (via OpUndef), storing it in a dictionary. Then, when BuilderMethods::call() is
/// called, and it's calling a function pointer, we check the dictionary, and if it is, invoke the function directly.
/// It's kind of conceptually similar to a constexpr deref, except specialized to just functions.
pub fn register_fn_ptr(&self, function: SpirvValue) -> SpirvValue {
let ty = SpirvType::Pointer {
storage_class: StorageClass::Function,
pointee: function.ty,
}
.def(self);
// We want a unique ID for these undefs, so don't use the caching system.
let result = self.emit_global().undef(ty, None).with_type(ty);
// It's obviously invalid, so zombie it.
self.zombie(result.def, "get_fn_addr");
self.function_pointers.borrow_mut().insert(result, function);
result
}
/// See comment on register_fn_ptr
pub fn lookup_fn_ptr(&self, pointer: SpirvValue) -> Option<SpirvValue> {
self.function_pointers.borrow().get(&pointer).cloned()
}
}
impl<'tcx> BackendTypes for CodegenCx<'tcx> {
@ -189,16 +217,7 @@ impl<'tcx> MiscMethods<'tcx> for CodegenCx<'tcx> {
fn get_fn_addr(&self, instance: Instance<'tcx>) -> Self::Value {
let function = self.get_fn_ext(instance);
let ty = SpirvType::Pointer {
storage_class: StorageClass::Function,
pointee: function.ty,
}
.def(self);
// We want a unique ID for these undefs, so don't use the caching system.
let result = self.emit_global().undef(ty, None).with_type(ty);
self.zombie(result.def, "get_fn_addr");
self.function_pointers.borrow_mut().insert(result, function);
result
self.register_fn_ptr(function)
}
fn eh_personality(&self) -> Self::Value {