The roadmap for Emu is simple - support a larger subset of Rust. In what order, though do we want to expand the subset, though? Consider the following program-
#[macro_use]
extern crate em;
use em::*;
#[gpu_use]
fn main() {
let mut x = vec![0.0; 1000];
gpu_do!(load(x)); // move data to the GPU
gpu_do!(launch()); // off-load to run on the GPU
for i in 0..1000 {
x[i] = x[i] * 10.0;
}
gpu_do!(read(x)); // move data back from the GPU
println!("{:?}", x);
}
The order we want to expoand the subset is from outside to inside. From outside to inside, we will-
- work on
#[gpu_use]
- work on
gpu_do!()
- work on
gpu_do!(launch())
and support more kinds of for loops - work on supporting more kinds of stuff inside the for loops
- etc.
Some of the things that need to be worked on include but are not limited to-
- work on
#[gpu_use]
- support methods
- support
?
operator
- work on
gpu_do!()
- support
gpu_do!(load_async(data))
,gpu_do!(read_async(data))
,gpu_do!(launch_async())
,gpu_do!(wait())
for asynchronous usage of GPUs - support
gpu_do!(open("nvidia ti"))
,#[gpu_use("nvidia ti", "nvidia gt")]
for usage of multiple GPUs - support
gpu_do!(load_mut(data))
to optimize for data that isn't actually mutable
- support
- work on
gpu_do!(launch())
and support more kinds of for loops- support loops that declare block algorithms/blocked iteration/iteration over chunks
- support loops that
for x in data
- support loops that
for x in 0..(i * j)
- work on supporting more kinds of stuff inside the for loops
- support variables
- support all binary/unary operators
- support if/else-if/else
- support loops
- support more primitive types such as
char
,i32
,u128
- etc.
- support structures with
#[gpu_use_struct]
- support functions (that can be called from inside of for loop)
#[gpu_use_fn]
- support structures with
To be even more specific, here is what we want to do-
(Before you look through this list, please carefully read the docs.rs/em so that you understand the terms #[gpu_use]
, gpu_do!()
, "helper functions". You might also want to see this table of contents of code to understand how Emu code is structured.)
Currently, #[gpu_use]
modifies 5 things.
- the function signature to accept a
Gpu
(docs here) as input (if this is a helper function) (code here) - the function signature to return the modified inputted
Gpu
as output (modifying-> T
to-> (T, Gpu)
) (if this is a helper function) (code also here) - the body of the function to either use and mutate the
Gpu
as described by allgpu_do!()
s (if this is a helper function) or otherwise instantiate a newGpu
(code here and here) - the body of the function to use and mutate the
Gpu
by passing it into and out of all helper functions that are called in the body (code here) - the body of the function to return the mutated
Gpu
(if this is a helper function) (code here and here)
All of these modifications are great but they they don't support methods. Ideally we should support something like this.
struct MyVec {
data: Vec<f32>
// ... other fields
}
impl MyVec {
#[gpu_use(add)]
fn add(&mut self, other: Vec<f32>) {
// ... add the vectors and update
}
#[gpu_use(other_add)]
fn other_add(x: Vec<f32>, y: Vec<f32>) -> Vec<f32> {
// ... some stuff
}
}
#[gpu_use(add, other_add)]
fn main() {
// ... some initialization code
gpu_do!(load(data));
my_vec.add(data);
MyVec::other_add(data, vec![0.0; 1000]);
}
To make this code possible, there are 2 things we need to change.
- modifying signatures (
Gpu
parameter needs to be added at end not start and passed as argument at end not start) - modifying calls (we should support any call/method and when we see something like
MyVec::other_add
we should only look atother_add
These 2 changes will need to be made in the following places.
- modify
lib.rs
and some other places to not only parse forItemFn
but also methods - modify
passing.rs
to change how signatures are modified - modify
passing.rs
to change how calls are modified
Remember how I said #[gpu_use]
modifies things? Here was one thing I said.
- the body of the function to return the mutated
Gpu
(if this is a helper function) (code here and here)
There are 2 ways we do this.
- wrap the entire function body by transorming
body
to(body, gpu)
. - modify returns by transforming
return x
toreturn (x, gpu)
.
This handles 2 ways that stuff can be returned from functions.
- blocks as expressions with last expression being what gets returned
- return statements
But there are at least 2 more.
- macros
?
operator implicitly returning an error in case of error and otherwise unwrapping and evaluating to unwrapped value
We can ignore macros because we simply can't expand them at compile time on stable Rust from a proc macro. But the ?
is important. To support ?
operator, we need to add the following.
- finding
?
operator and modifying it
This entails 2 things.
- modify
passing.rs
to include a new function that usesFold
to modify?
to return theGpu
- modify
lib.rs
to call the function frompassing.rs
on code in the function
Of course, this isn't a complete list. This list will change. But unlike the list on the README, this list contains actual things that we are almost certain needs to be done. And the order of this list is also something we are almost certain about. If you have more questions about anything, please discuss at gitter.im/talk-about-emu/thoughts.