├── examples ├── rust │ ├── example.fut │ ├── build.rs │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs └── ocaml │ ├── test │ ├── dune │ └── test.ml │ ├── src │ ├── dune │ └── example.fut │ ├── dune-project │ ├── README.md │ └── futhark-bindgen-example.opam ├── src ├── generate │ ├── templates │ │ ├── ocaml │ │ │ ├── record.mli │ │ │ ├── record_project.mli │ │ │ ├── entry.mli │ │ │ ├── opaque.mli │ │ │ ├── bindings.mli │ │ │ ├── entry.ml │ │ │ ├── record_project.ml │ │ │ ├── record.ml │ │ │ ├── opaque.ml │ │ │ ├── context.mli │ │ │ ├── array.mli │ │ │ ├── array.ml │ │ │ ├── bindings.ml │ │ │ └── context.ml │ │ └── rust │ │ │ ├── entry.rs │ │ │ ├── record.rs │ │ │ ├── opaque.rs │ │ │ ├── record_project.rs │ │ │ ├── array.rs │ │ │ └── context.rs │ ├── mod.rs │ ├── rust.rs │ └── ocaml.rs ├── error.rs ├── package.rs ├── compiler.rs ├── bin │ └── main.rs ├── manifest.rs └── lib.rs ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── clippy.yml │ ├── build.yml │ └── examples.yml ├── Makefile ├── LICENSE.md ├── Cargo.toml ├── CHANGES.md └── README.md /examples/rust/example.fut: -------------------------------------------------------------------------------- 1 | ../ocaml/src/example.fut -------------------------------------------------------------------------------- /examples/ocaml/test/dune: -------------------------------------------------------------------------------- 1 | (test 2 | (name test) 3 | (libraries example)) 4 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/record.mli: -------------------------------------------------------------------------------- 1 | val v: Context.t -> {new_arg_types} -> t 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | example.c 4 | example.h 5 | example.json 6 | _build 7 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/record_project.mli: -------------------------------------------------------------------------------- 1 | 2 | val get_{name}: t -> {out_type} 3 | (** Get field: {name} *) -------------------------------------------------------------------------------- /src/generate/templates/ocaml/entry.mli: -------------------------------------------------------------------------------- 1 | (** Entry point: {name} *) 2 | val {name}: Context.t -> {arg_types} -> ({return_type}) 3 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/opaque.mli: -------------------------------------------------------------------------------- 1 | type t 2 | (** Futhark type *) 3 | 4 | val free: t -> unit 5 | (** Free function *) 6 | -------------------------------------------------------------------------------- /examples/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | futhark_bindgen::build( 3 | futhark_bindgen::Backend::from_env().unwrap_or(futhark_bindgen::Backend::C), 4 | "example.fut", 5 | "example.rs", 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/bindings.mli: -------------------------------------------------------------------------------- 1 | open! Signed 2 | open! Unsigned 3 | 4 | type error = 5 | | InvalidShape of int * int 6 | | NullPtr 7 | | Code of int 8 | | UseAfterFree of [`context | `array | `opaque] 9 | 10 | exception Error of error 11 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/entry.ml: -------------------------------------------------------------------------------- 1 | let {name} ctx {entry_params} = 2 | check_use_after_free `context ctx.Context.context_free; 3 | {out_decl} 4 | let rc = Bindings.futhark_entry_{name} ctx.Context.handle {call_args} in 5 | if rc <> 0 then raise (Error (Code rc)); 6 | ({out_return}) 7 | -------------------------------------------------------------------------------- /examples/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "futhark-bindgen-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [build-dependencies] 9 | futhark-bindgen = {path = "../..", default-features=false, features=["build"]} 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: ndarray 11 | versions: 12 | - 0.15.0 13 | - dependency-name: ocaml-interop 14 | versions: 15 | - 0.6.0 16 | - 0.7.0 17 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/record_project.ml: -------------------------------------------------------------------------------- 1 | let get_{name} t = 2 | check_use_after_free `context t.opaque_ctx.Context.context_free; 3 | let out = allocate_n ~count:1 {s} in 4 | let rc = Bindings.{project} t.opaque_ctx.Context.handle out (get_opaque_ptr t) in 5 | if rc <> 0 then raise (Error (Code rc)); 6 | Context.auto_sync t.opaque_ctx; 7 | {out} 8 | 9 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/record.ml: -------------------------------------------------------------------------------- 1 | let v ctx {new_params} = 2 | check_use_after_free `context ctx.Context.context_free; 3 | let ptr = allocate ~finalise:(free' ctx) (ptr void) null in 4 | let rc = Bindings.{new_fn} ctx.Context.handle ptr {new_call_args} in 5 | if rc <> 0 then raise (Error (Code rc)); 6 | Context.auto_sync ctx; 7 | {{ opaque_ptr = ptr; opaque_ctx = ctx }} 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=$(HOME)/.local 2 | 3 | build: 4 | cargo build 5 | 6 | test: test-rust test-ocaml 7 | 8 | test-rust: 9 | cd examples/rust && cargo test 10 | 11 | test-ocaml: 12 | cd examples/ocaml && dune clean && dune runtest 13 | 14 | install: 15 | cargo build --release 16 | install -D target/release/futhark-bindgen $(PREFIX)/bin/futhark-bindgen 17 | 18 | uninstall: 19 | rm -f $(PREFIX)/bin/futhark-bindgen 20 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | clippy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Stable 16 | run: rustup toolchain install stable --profile=default 17 | 18 | - name: Run clippy 19 | run: cargo clippy --all -- -D warnings 20 | -------------------------------------------------------------------------------- /examples/ocaml/src/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (targets example.c example.h example.ml example.mli) 3 | (deps example.fut) 4 | (action 5 | (run cargo run -- run example.fut example.ml))) 6 | (library 7 | (name example) 8 | (public_name futhark-bindgen-example) 9 | (modules example) 10 | (libraries ctypes ctypes.foreign) 11 | (library_flags -linkall) 12 | (foreign_stubs (language c) (names example))) 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | run: 11 | name: Build 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | os: [macos-latest, ubuntu-latest] 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | - name: Build 21 | run: cargo build --features=build 22 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /// Errors 2 | #[derive(Debug)] 3 | pub enum Error { 4 | /// Compilation failed 5 | CompilationFailed, 6 | 7 | /// Json decoding error 8 | Json(serde_json::Error), 9 | 10 | /// std::io::Error 11 | Io(std::io::Error), 12 | } 13 | 14 | impl From for Error { 15 | fn from(e: serde_json::Error) -> Self { 16 | Error::Json(e) 17 | } 18 | } 19 | 20 | impl From for Error { 21 | fn from(e: std::io::Error) -> Self { 22 | Error::Io(e) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/opaque.ml: -------------------------------------------------------------------------------- 1 | type t = opaque 2 | let t = Bindings.{name} 3 | let _ = t 4 | 5 | let free' ctx ptr = 6 | let is_null = Ctypes.is_null ptr || Ctypes.is_null (!@ptr) in 7 | if not ctx.Context.context_free && not is_null then 8 | let () = ignore (Bindings.{free_fn} ctx.Context.handle (!@ptr)) in 9 | ptr <-@ Ctypes.null 10 | 11 | let of_ptr ctx ptr = 12 | if is_null ptr then raise (Error NullPtr); 13 | {{ opaque_ptr = allocate ~finalise:(free' ctx) (Ctypes.ptr Ctypes.void) ptr; opaque_ctx = ctx }} 14 | 15 | let free t = free' t.opaque_ctx t.opaque_ptr 16 | 17 | let _ = of_ptr 18 | -------------------------------------------------------------------------------- /examples/ocaml/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.2) 2 | 3 | (name futhark-bindgen-example) 4 | 5 | (generate_opam_files true) 6 | 7 | (source 8 | (github username/reponame)) 9 | 10 | (authors "Author Name") 11 | 12 | (maintainers "Maintainer Name") 13 | 14 | (license LICENSE) 15 | 16 | (documentation https://url/to/documentation) 17 | 18 | (package 19 | (name futhark-bindgen-example) 20 | (synopsis "A short synopsis") 21 | (description "A longer description") 22 | (depends ocaml dune) 23 | (tags 24 | (topics "to describe" your project))) 25 | 26 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project 27 | -------------------------------------------------------------------------------- /src/generate/templates/rust/entry.rs: -------------------------------------------------------------------------------- 1 | impl Context {{ 2 | /// Entry point: {entry_name} 3 | pub fn {entry_name}(&self, {entry_params}) -> Result<{entry_return_type}, Error> {{ 4 | {out_decl} 5 | let rc = unsafe {{ 6 | futhark_entry_{entry_name}(self.context, {call_args}) 7 | }}; 8 | if rc != 0 {{ return Err(Error::Code(rc)); }} 9 | 10 | #[allow(unused_unsafe)] 11 | unsafe {{ 12 | Ok({entry_return}) 13 | }} 14 | }} 15 | }} 16 | 17 | extern "C" {{ 18 | fn {entry_fn}( 19 | _: *mut futhark_context, 20 | {futhark_entry_params} 21 | ) -> std::os::raw::c_int; 22 | }} -------------------------------------------------------------------------------- /src/generate/templates/rust/record.rs: -------------------------------------------------------------------------------- 1 | impl<'a> {rust_type}<'a> {{ 2 | /// Create new {rust_type} 3 | pub fn new(ctx: &'a Context, {new_params}) -> std::result::Result {{ 4 | unsafe {{ 5 | let mut out = std::ptr::null_mut(); 6 | let rc = {new_fn}(ctx.context, &mut out, {new_call_args}); 7 | if rc != 0 {{ return Err(Error::Code(rc)); }} 8 | ctx.auto_sync(); 9 | Ok(Self {{ data: out, ctx }}) 10 | }} 11 | }} 12 | }} 13 | 14 | extern "C" {{ 15 | fn {new_fn}( 16 | _: *mut futhark_context, 17 | _: *mut *mut {futhark_type}, 18 | {new_extern_params} 19 | ) -> std::os::raw::c_int; 20 | }} -------------------------------------------------------------------------------- /src/generate/templates/ocaml/context.mli: -------------------------------------------------------------------------------- 1 | module Context: sig 2 | type t 3 | (** Futhark context *) 4 | 5 | val v: ?debug:bool -> ?log:bool -> ?profile:bool -> ?cache_file:string -> ?auto_sync:bool -> {extra_mli} unit -> t 6 | (** Create a new context *) 7 | 8 | val sync: t -> unit 9 | (** Sync the context, if auto_sync is enabled this is not needed *) 10 | 11 | val free: t -> unit 12 | (** Free the context *) 13 | 14 | val clear_caches: t -> unit 15 | (** Clear Futhark caches *) 16 | 17 | val get_error: t -> string option 18 | (** Get last error message or None *) 19 | 20 | val report: t -> string option 21 | val pause_profiling: t -> unit 22 | val unpause_profiling: t -> unit 23 | end -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Zach Shipko 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /examples/ocaml/README.md: -------------------------------------------------------------------------------- 1 | # Rust futhark-bindgen example 2 | 3 | Notes about using `futhark-bindgen` with OCaml and `dune`: 4 | 5 | - Add your futhark source file to your source directory 6 | - Update your dune file to include the rule for generating the OCaml files and linking 7 | the C source and the required libraries: 8 | ``` 9 | (rule 10 | (targets example.c example.ml example.mli) 11 | (deps example.fut) 12 | (action 13 | (run futhark-bindgen run --backend opencl example.fut example.ml))) 14 | 15 | (library 16 | (name example) 17 | (public_name futhark-bindgen-example) 18 | (modules example) 19 | (libraries ctypes ctypes.foreign) 20 | (library_flags -linkall -cclib -lOpenCL) 21 | (foreign_stubs (language c) (names example))) 22 | ``` -------------------------------------------------------------------------------- /src/generate/templates/rust/opaque.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[allow(non_camel_case_types)] 3 | struct {futhark_type} {{ 4 | _private: [u8; 0] 5 | }} 6 | 7 | extern "C" {{ 8 | fn {free_fn}( 9 | _: *mut futhark_context, 10 | _: *mut {futhark_type} 11 | ) -> std::os::raw::c_int; 12 | }} 13 | 14 | /// Futhark type 15 | pub struct {rust_type}<'a> {{ 16 | data: *mut {futhark_type}, 17 | ctx: &'a Context, 18 | }} 19 | 20 | impl<'a> {rust_type}<'a> {{ 21 | #[allow(unused)] 22 | fn from_ptr(ctx: &'a Context, data: *mut {futhark_type}) -> Self {{ 23 | Self {{ ctx, data }} 24 | }} 25 | }} 26 | 27 | impl<'a> Drop for {rust_type}<'a> {{ 28 | fn drop(&mut self) {{ 29 | unsafe {{ 30 | {free_fn}(self.ctx.context, self.data); 31 | }} 32 | }} 33 | }} 34 | -------------------------------------------------------------------------------- /src/generate/templates/rust/record_project.rs: -------------------------------------------------------------------------------- 1 | impl<'a> {rust_type}<'a> {{ 2 | /// Get {field_name} field 3 | pub fn get_{field_name}(&self) -> Result<{rust_field_type}, Error> {{ 4 | let mut out = std::mem::MaybeUninit::zeroed(); 5 | let rc = unsafe {{ 6 | {project_fn}( 7 | self.ctx.context, 8 | out.as_mut_ptr(), 9 | self.data 10 | ) 11 | }}; 12 | if rc != 0 {{ return Err(Error::Code(rc)); }} 13 | self.ctx.auto_sync(); 14 | let out = unsafe {{ out.assume_init() }}; 15 | {output} 16 | }} 17 | }} 18 | 19 | extern "C" {{ 20 | fn {project_fn}( 21 | _: *mut futhark_context, 22 | _: *mut {futhark_field_type}, 23 | _: *const {futhark_type} 24 | ) -> std::os::raw::c_int; 25 | }} 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "futhark-bindgen" 3 | version = "0.2.8" 4 | edition = "2021" 5 | authors = ["Zach Shipko "] 6 | license = "ISC" 7 | keywords = ["futhark", "bindings", "bindgen"] 8 | repository = "https://github.com/zshipko/futhark-bindgen" 9 | documentation = "https://docs.rs/futhark-bindgen" 10 | description = "Futhark binding generator" 11 | readme = "README.md" 12 | default-run = "futhark-bindgen" 13 | 14 | [dependencies] 15 | serde = {version = "1", features=["derive"]} 16 | serde_json = "1" 17 | argh = {version = "0.1", optional=true} 18 | cc = {version = "1", optional=true} 19 | 20 | [features] 21 | default = ["bin"] 22 | build = ["cc"] 23 | bin = ["argh"] 24 | 25 | [package.metadata.docs.rs] 26 | features = ["build"] 27 | 28 | [[bin]] 29 | name = "futhark-bindgen" 30 | required-features = ["bin"] 31 | path = "src/bin/main.rs" 32 | -------------------------------------------------------------------------------- /examples/ocaml/futhark-bindgen-example.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "A short synopsis" 4 | description: "A longer description" 5 | maintainer: ["Maintainer Name"] 6 | authors: ["Author Name"] 7 | license: "LICENSE" 8 | tags: ["topics" "to describe" "your" "project"] 9 | homepage: "https://github.com/username/reponame" 10 | doc: "https://url/to/documentation" 11 | bug-reports: "https://github.com/username/reponame/issues" 12 | depends: [ 13 | "ocaml" 14 | "dune" {>= "3.2"} 15 | "odoc" {with-doc} 16 | ] 17 | build: [ 18 | ["dune" "subst"] {dev} 19 | [ 20 | "dune" 21 | "build" 22 | "-p" 23 | name 24 | "-j" 25 | jobs 26 | "@install" 27 | "@runtest" {with-test} 28 | "@doc" {with-doc} 29 | ] 30 | ] 31 | dev-repo: "git+https://github.com/username/reponame.git" 32 | -------------------------------------------------------------------------------- /examples/rust/README.md: -------------------------------------------------------------------------------- 1 | # Rust futhark-bindgen example 2 | 3 | Notes about using `futhark-bindgen` with Rust: 4 | 5 | - Add the futhark file to your project 6 | - Add `futhark-bindgen` to the `[build-dependencies]` section of your `Cargo.toml` 7 | with the `build` feature enabled 8 | ```toml 9 | [build-dependencies] 10 | futhark-bindgen = {path = "../..", default-features=false, features=["build"]} 11 | ``` 12 | 13 | - Create `build.rs` with a call to `futhark_bindgen::build` 14 | ```rust 15 | // example.fut could also be something like lib/example.fut, but the output name 16 | // is always relative to $OUT_DIR 17 | fn main() { 18 | futhark_bindgen::build(futhark_bindgen::Backend::C, "example.fut", "example.rs") 19 | } 20 | ``` 21 | 22 | - Include the generated code in your project 23 | ```rust 24 | include!(concat!(env!("OUT_DIR"), "/example.rs")); 25 | ``` 26 | 27 | - When using `f16` you need to add the `half` crate to your dependencies -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.2.8 2 | 3 | - Improved handling of C pointers in OCaml finalizers 4 | 5 | ## 0.2.7 6 | 7 | - Fixed possible double frees when GC is triggered after `free` has already been called 8 | 9 | ## 0.2.6 10 | 11 | - Implement `std::error::Error` for generated `Error` type 12 | - Added support for HIP backend 13 | 14 | ## 0.2.5 15 | 16 | - Add `Backend::from_env` 17 | 18 | ## 0.2.4 19 | 20 | - Use `-O3` flag when compiling C files 21 | 22 | ## 0.2.3 23 | 24 | - Add `of_array1`, `get_array1` and `values_array1` to OCaml Array bindings 25 | 26 | ## 0.2.2 27 | 28 | - Automatically add `rerun-if-changed` to Rust build script 29 | - Add `of_array` to generated OCaml `Array_*_*` types 30 | 31 | ## 0.2.1 32 | 33 | - Fix capitalization of Bool in Rust generated code 34 | 35 | ## 0.2.0 36 | 37 | - Added more methods to `Generate` trait to make it easier to add new 38 | languages 39 | - Fix OCaml codegen for `u8` arrays 40 | - Support boolean arrays in OCaml 41 | - Support `f16` values in Rust using `half` crate 42 | 43 | ## 0.1.1 44 | 45 | - Fix codegen for entries that return no value 46 | - Fix codegen for structs and enums 47 | 48 | ## 0.1.0 49 | 50 | - Initial release 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # futhark-bindgen 2 | 3 | 4 | 5 | 6 | 7 | A [Futhark](https://futhark-lang.org) binding generator. 8 | 9 | `futhark-bindgen` uses the [manifest](https://futhark.readthedocs.io/en/latest/c-api.html#manifest) created by Futhark 10 | to generate bindings for multiple languages. 11 | 12 | Supported languages: 13 | 14 | - Rust 15 | - OCaml 16 | 17 | ## Installation 18 | 19 | With `cargo`: 20 | 21 | ``` 22 | $ cargo install futhark-bindgen 23 | ``` 24 | 25 | From source: 26 | 27 | ``` 28 | $ make install PREFIX=~/.local/bin 29 | ``` 30 | 31 | ## Command-line usage 32 | 33 | ``` 34 | $ futhark-bindgen run test.fut test.rs # Rust output to ./test.rs 35 | $ futhark-bindgen run test.fut test.ml # OCaml output to ./test.ml 36 | ``` 37 | 38 | The `--backend` flag can be used to select which Futhark backend to use: `c`, `multicore`, 39 | `cuda`, `opencl` or `ispc` 40 | 41 | See the output of `futhark-bindgen --help` for more information 42 | 43 | ## Example projects 44 | 45 | - [Rust](https://github.com/zshipko/futhark-bindgen/tree/main/examples/rust) 46 | - [OCaml](https://github.com/zshipko/futhark-bindgen/tree/main/examples/ocaml) 47 | -------------------------------------------------------------------------------- /examples/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/example.rs")); 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use crate::*; 6 | #[test] 7 | fn binary_search() { 8 | let ctx = Context::new_with_options(Options::new().debug().log().profile()).unwrap(); 9 | let data = &[1, 2, 3, 4, 5, 7, 8]; 10 | let arr = ArrayI64D1::new(&ctx, [data.len() as i64], data).unwrap(); 11 | let index = ctx.binary_search(&arr, 6).unwrap(); 12 | assert_eq!(index, 5); 13 | } 14 | 15 | #[test] 16 | fn tuple() { 17 | let ctx = Context::new_with_options(Options::new().debug().log().profile()).unwrap(); 18 | let data = &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 19 | let number = Number::new(&ctx, 2.5).unwrap(); 20 | let arr = ArrayF32D1::new(&ctx, [data.len() as i64], data).unwrap(); 21 | 22 | let t = Tup::new(&ctx, &number, &arr).unwrap(); 23 | let out = ctx.tup_mul(&t).unwrap(); 24 | let data1 = out.get().unwrap(); 25 | 26 | for i in 0..10 { 27 | assert_eq!(data1[i], data[i] * t.get_0().unwrap().get_x().unwrap()); 28 | } 29 | } 30 | 31 | #[test] 32 | fn count_lines() { 33 | let ctx = Context::new_with_options(Options::new().debug().log().profile()).unwrap(); 34 | 35 | let data = String::from("this\nis\na\ntest\n").into_bytes(); 36 | let data = ArrayU8D1::new(&ctx, [data.len() as i64], &data).unwrap(); 37 | let n = ctx.count_lines(&data).unwrap(); 38 | assert_eq!(n, 4); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/array.mli: -------------------------------------------------------------------------------- 1 | module {module_name}: sig 2 | type t 3 | (** Futhark array *) 4 | 5 | type kind = ({ocaml_elemtype}, {ba_elemtype}) Bigarray.kind 6 | (** The Bigarray kind that matches the correct element type for this array *) 7 | 8 | val kind: kind 9 | 10 | val shape: t -> int array 11 | (** Array shape *) 12 | 13 | val v: Context.t -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Genarray.t -> t 14 | (** Initialize an array with the data from the provided bigarray *) 15 | 16 | val values: t -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Genarray.t -> unit 17 | (** Load the values into the provided bigarray *) 18 | 19 | val values_array1: t -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Array1.t -> unit 20 | (** Similar to [values] but takes an [Array1] instead of [Genarray] *) 21 | 22 | val get: t -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Genarray.t 23 | (** Get a new bigarray with the values loaded *) 24 | 25 | val get_array1: t -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Array1.t 26 | (** Similar to [get] but returns an [Array1] *) 27 | 28 | val of_array: Context.t -> int array -> ({ocaml_elemtype}) array -> t 29 | (** Create [t] from an array of values *) 30 | 31 | val of_array1: Context.t -> int array -> ({ocaml_elemtype}, {ba_elemtype}, Bigarray.c_layout) Bigarray.Array1.t-> t 32 | (** Create [t] from an [Array1] instead of [Genarray] *) 33 | 34 | val free: t -> unit 35 | (** Free the array *) 36 | end 37 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | name: Examples 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | linux: 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | os: [ubuntu-latest] 15 | ocaml-compiler: ["5.0.0"] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 20 | uses: avsm/setup-ocaml@v2 21 | with: 22 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 23 | 24 | - name: Install deps 25 | run: curl https://futhark-lang.org/releases/futhark-nightly-linux-x86_64.tar.xz | tar xJ 26 | 27 | - name: Set Opam env 28 | run: opam env | tr '\n' ' ' >> $GITHUB_ENV 29 | 30 | - name: Add Opam switch to PATH 31 | run: opam var bin >> $GITHUB_PATH 32 | 33 | - run: opam install dune ctypes-foreign 34 | 35 | - name: Run examples 36 | run: PATH="$PATH:$PWD/futhark-nightly-linux-x86_64/bin" opam exec -- make test 37 | 38 | macos: 39 | strategy: 40 | fail-fast: true 41 | matrix: 42 | os: [macos-latest] 43 | ocaml-compiler: ["5.0.0"] 44 | runs-on: macos-latest 45 | steps: 46 | - uses: actions/checkout@v2 47 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 48 | uses: avsm/setup-ocaml@v2 49 | with: 50 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 51 | 52 | - name: Install deps 53 | run: brew update && brew install futhark libffi 54 | 55 | - name: Set Opam env 56 | run: opam env | tr '\n' ' ' >> $GITHUB_ENV 57 | 58 | - name: Add Opam switch to PATH 59 | run: opam var bin >> $GITHUB_PATH 60 | 61 | - run: opam install dune ctypes-foreign 62 | 63 | - name: Run examples 64 | run: opam exec -- make test 65 | -------------------------------------------------------------------------------- /examples/ocaml/test/test.ml: -------------------------------------------------------------------------------- 1 | open Example 2 | open Bigarray 3 | 4 | (* Try to trigger garbage collector *) 5 | let () = 6 | let ctx = Context.v 7 | ~debug:false ~log:false ~profile:false ~auto_sync:true () in 8 | for i = 0 to 1000 do 9 | let a = Genarray.init Float64 c_layout [| 100; 100 |] (fun _ -> Float.of_int i) in 10 | let p = Array_f64_2d.v ctx a in 11 | Array_f64_2d.free p 12 | done 13 | 14 | let () = 15 | (* binary_search *) 16 | let ctx = Context.v ~debug:true ~profile:true ~log:true () in 17 | let data = [| 1L; 2L; 3L; 4L; 5L; 7L; 8L |] in 18 | let arr = Array_i64_1d.of_array ctx [| 7 |] data in 19 | let index = binary_search ctx arr 6L in 20 | assert (Int64.equal index 5L); 21 | Array_i64_1d.free arr; 22 | 23 | (* mul2 *) 24 | let data2 = [| 1.0; 2.0; 3.0; 4.0; 5.0; 6.0 |] in 25 | let arr = Array_f64_2d.of_array ctx [| 2; 3 |] data2 in 26 | Printf.printf "%f\n" (data2.(0)); 27 | let out = mul2 ctx arr in 28 | let out' = Array_f64_2d.get out in 29 | Printf.printf "%f\n" (Genarray.get out' [| 0; 0 |]); 30 | assert (Genarray.get out' [| 0; 0 |] = 2.0); 31 | assert (Genarray.get out' [| 1; 2 |] = 12.0); 32 | Array_f64_2d.free out; 33 | Array_f64_2d.free arr; 34 | 35 | let () = 36 | try 37 | let _ = Array_f64_2d.get out in 38 | assert false 39 | with Error (UseAfterFree `array) -> print_endline "Detected array use after free" 40 | in 41 | 42 | (* tup_mul *) 43 | let number = Number.v ctx 2.5 in 44 | let data3 = [| 0.0; 1.0; 2.0; 3.0; 4.0; 5.0; 6.0; 7.0; 8.0; 9.0 |] in 45 | let arr = Array_f32_1d.of_array ctx [| Array.length data3 |] data3 in 46 | let tup = Tup.v ctx number arr in 47 | let out = tup_mul ctx tup in 48 | let out' = Array_f32_1d.get_array1 out in 49 | for i = 0 to 9 do 50 | assert (out'.{i} = Array.get data3 i *. (Number.get_x (Tup.get_0 tup))) 51 | done; 52 | 53 | Tup.free tup; 54 | let () = 55 | try 56 | let _ = Tup.get_0 tup in 57 | assert false 58 | with Error (UseAfterFree `opaque) -> print_endline "Detected opaque use after free" 59 | in 60 | 61 | (* count_lines *) 62 | let text = "this\nis\na\ntest\n" in 63 | let arr = Array.init (String.length text) (fun i -> String.get text i |> int_of_char) in 64 | let data = Array1.of_array Int8_unsigned C_layout arr in 65 | let arr = Array_u8_1d.of_array1 ctx [|Array.length arr|] data in 66 | let n = count_lines ctx arr in 67 | assert (n = 4L); 68 | 69 | (* count_true *) 70 | let b = Array.init 10 (fun i -> if i mod 2 = 0 then 1 else 0) in 71 | let arr = Array_bool_1d.of_array ctx [| Array.length b |] b in 72 | let n = count_true ctx arr in 73 | assert (n = Int64.of_int @@ Array.fold_left (+) 0 b) 74 | -------------------------------------------------------------------------------- /src/package.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Compiled Futhark package 4 | #[derive(Debug, Clone)] 5 | pub struct Package { 6 | /// Manifest, parsed from the manifest file 7 | pub manifest: Manifest, 8 | 9 | /// Path to the generated C file 10 | pub c_file: std::path::PathBuf, 11 | 12 | /// Path to the generate C header file 13 | pub h_file: std::path::PathBuf, 14 | 15 | /// Source file 16 | pub src: std::path::PathBuf, 17 | } 18 | 19 | impl Package { 20 | #[cfg(feature = "build")] 21 | fn build(&self, libname: &str) { 22 | if self.manifest.backend == Backend::ISPC { 23 | let kernels = self.c_file.with_extension("kernels.ispc"); 24 | let dest = kernels.with_extension("o"); 25 | std::process::Command::new("ispc") 26 | .arg(&kernels) 27 | .arg("-o") 28 | .arg(&dest) 29 | .arg("--pic") 30 | .arg("--addressing=64") 31 | .arg("--target=host") 32 | .arg("-O3") 33 | .status() 34 | .expect("Unable to run ispc"); 35 | 36 | cc::Build::new() 37 | .file(&self.c_file) 38 | .object(&dest) 39 | .flag("-fPIC") 40 | .flag("-pthread") 41 | .flag("-lm") 42 | .flag("-std=c99") 43 | .flag("-O3") 44 | .extra_warnings(false) 45 | .warnings(false) 46 | .compile(libname); 47 | } else { 48 | cc::Build::new() 49 | .flag("-std=c99") 50 | .flag("-Wno-unused-parameter") 51 | .flag("-O3") 52 | .file(&self.c_file) 53 | .extra_warnings(false) 54 | .warnings(false) 55 | .compile(libname); 56 | } 57 | } 58 | /// Link the package 59 | /// 60 | /// Note: This should only be used in `build.rs` 61 | #[cfg(feature = "build")] 62 | pub fn link(&self) { 63 | let project = std::env::var("CARGO_PKG_NAME").unwrap(); 64 | let name = format!("futhark_generate_{project}"); 65 | self.build(&name); 66 | 67 | println!("cargo:rerun-if-changed={}", self.src.display()); 68 | println!("cargo:rustc-link-lib={name}"); 69 | 70 | let libs = self.manifest.backend.required_c_libs(); 71 | 72 | for lib in libs { 73 | if cfg!(target_os = "macos") && lib == &"OpenCL" { 74 | println!("cargo:rustc-link-lib=framework={}", lib); 75 | } else { 76 | println!("cargo:rustc-link-lib={}", lib); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Wrapper around the Futhark compiler 4 | #[derive(Debug, Clone)] 5 | pub struct Compiler { 6 | exe: String, 7 | backend: Backend, 8 | src: std::path::PathBuf, 9 | extra_args: Vec, 10 | output_dir: std::path::PathBuf, 11 | } 12 | 13 | impl Compiler { 14 | /// Create a new `Compiler` instance with the selected backend and Futhark source file 15 | pub fn new(backend: Backend, src: impl AsRef) -> Compiler { 16 | Compiler { 17 | exe: String::from("futhark"), 18 | src: src.as_ref().to_path_buf(), 19 | extra_args: Vec::new(), 20 | output_dir: src 21 | .as_ref() 22 | .canonicalize() 23 | .unwrap() 24 | .parent() 25 | .unwrap() 26 | .to_path_buf(), 27 | backend, 28 | } 29 | } 30 | 31 | /// By default the executable name is set to `futhark`, this function can be 32 | /// used to set a different name or path 33 | pub fn with_executable_name(mut self, name: impl AsRef) -> Self { 34 | self.exe = name.as_ref().into(); 35 | self 36 | } 37 | 38 | /// Supply additional arguments to be passed to the `futhark` executable 39 | pub fn with_extra_args(mut self, args: Vec) -> Self { 40 | self.extra_args = args; 41 | self 42 | } 43 | 44 | /// Set the output directory where the C files and manifest will be created 45 | pub fn with_output_dir(mut self, dir: impl AsRef) -> Self { 46 | self.output_dir = dir.as_ref().to_path_buf(); 47 | self 48 | } 49 | 50 | /// Compile the package 51 | /// 52 | /// This will generate a C file, C header file and manifest 53 | pub fn compile(&self) -> Result { 54 | // Create -o argument 55 | let output = &self 56 | .output_dir 57 | .join(self.src.with_extension("").file_name().unwrap()); 58 | 59 | let ok = std::process::Command::new(&self.exe) 60 | .arg(self.backend.to_str()) 61 | .args(&self.extra_args) 62 | .args(["-o", &output.to_string_lossy()]) 63 | .arg("--lib") 64 | .arg(&self.src) 65 | .status()? 66 | .success(); 67 | 68 | if !ok { 69 | return Err(Error::CompilationFailed); 70 | } 71 | 72 | // Load manifest after successful compilation 73 | let manifest = Manifest::parse_file(output.with_extension("json"))?; 74 | let c_file = output.with_extension("c"); 75 | let h_file = output.with_extension("h"); 76 | Ok(Package { 77 | manifest, 78 | c_file, 79 | h_file, 80 | src: self.src.clone(), 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/array.ml: -------------------------------------------------------------------------------- 1 | module {module_name} = struct 2 | type t = futhark_array 3 | 4 | type kind = ({ocaml_elemtype}, {ba_elemtype}) Bigarray.kind 5 | 6 | let kind = {ba_kind} 7 | 8 | let free ctx ptr = 9 | let is_null = Ctypes.is_null ptr || Ctypes.is_null (!@ptr) in 10 | if not ctx.Context.context_free && not is_null then 11 | let () = ignore (Bindings.futhark_free_{elemtype}_{rank}d ctx.Context.handle (!@ptr)) in 12 | ptr <-@ Ctypes.null 13 | 14 | let cast x = 15 | coerce (ptr void) (ptr {ocaml_ctype}) (to_voidp x) 16 | 17 | let v ctx ba = 18 | check_use_after_free `context ctx.Context.context_free; 19 | let dims = Genarray.dims ba in 20 | let ptr = Bindings.futhark_new_{elemtype}_{rank}d ctx.Context.handle (cast @@ bigarray_start genarray ba) {dim_args} in 21 | if is_null ptr then raise (Error NullPtr); 22 | Context.auto_sync ctx; 23 | {{ ptr = Ctypes.allocate ~finalise:(free ctx) (Ctypes.ptr Ctypes.void) ptr; ctx; shape = dims }} 24 | 25 | let values t ba = 26 | check_use_after_free `context t.ctx.Context.context_free; 27 | let dims = Genarray.dims ba in 28 | let a = Array.fold_left ( * ) 1 t.shape in 29 | let b = Array.fold_left ( * ) 1 dims in 30 | if (a <> b) then raise (Error (InvalidShape (a, b))); 31 | let rc = Bindings.futhark_values_{elemtype}_{rank}d t.ctx.Context.handle (get_ptr t) (cast @@ bigarray_start genarray ba) in 32 | Context.auto_sync t.ctx; 33 | if rc <> 0 then raise (Error (Code rc)) 34 | 35 | let values_array1 t ba = 36 | let ba = genarray_of_array1 ba in 37 | let ba = reshape ba t.shape in 38 | values t ba 39 | 40 | let get t = 41 | let dims = t.shape in 42 | let g = Genarray.create kind C_layout dims in 43 | values t g; 44 | g 45 | 46 | let get_array1 t = 47 | let len = Array.fold_left ( * ) 1 t.shape in 48 | let g = Array1.create kind C_layout len in 49 | values_array1 t g; 50 | g 51 | 52 | let shape t = t.shape 53 | 54 | let of_array1 ctx dims arr = 55 | let len = Array.fold_left ( * ) 1 dims in 56 | assert (len = Array1.dim arr); 57 | let g = genarray_of_array1 arr in 58 | let g = reshape g dims in 59 | v ctx g 60 | 61 | let of_array ctx dims arr = 62 | let arr = Array1.of_array kind C_layout arr in 63 | of_array1 ctx dims arr 64 | 65 | let ptr_shape ctx ptr = 66 | let s = Bindings.futhark_shape_{elemtype}_{rank}d ctx ptr in 67 | Array.init {rank} (fun i -> Int64.to_int !@ (s +@ i)) 68 | 69 | let of_ptr ctx ptr = 70 | check_use_after_free `context ctx.Context.context_free; 71 | if is_null ptr then raise (Error NullPtr); 72 | let shape = ptr_shape ctx.Context.handle ptr in 73 | {{ ptr = Ctypes.allocate ~finalise:(free ctx) (Ctypes.ptr Ctypes.void) ptr; ctx; shape }} 74 | 75 | let free t = free t.ctx t.ptr 76 | 77 | let _ = of_ptr 78 | end 79 | 80 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/bindings.ml: -------------------------------------------------------------------------------- 1 | open Ctypes 2 | open! Unsigned 3 | open! Signed 4 | 5 | module Bindings = struct 6 | external _stub: unit -> unit = "futhark_context_new" 7 | 8 | let fn = Foreign.foreign ~release_runtime_lock:true 9 | let context = typedef (ptr void) "context" 10 | let context_config = typedef (ptr void) "context_config" 11 | let futhark_context_new = fn "futhark_context_new" (context_config @-> returning context) 12 | let futhark_context_free = fn "futhark_context_free" (context @-> returning int) 13 | let futhark_context_sync = fn "futhark_context_sync" (context @-> returning int) 14 | let futhark_context_config_new = fn "futhark_context_config_new" (void @-> returning context_config) 15 | let futhark_context_config_free = fn "futhark_context_config_free" (context_config @-> returning int) 16 | let futhark_context_config_set_profiling = fn "futhark_context_config_set_profiling" (context_config @-> int @-> returning void) 17 | let futhark_context_config_set_debugging = fn "futhark_context_config_set_debugging" (context_config @-> int @-> returning void) 18 | let futhark_context_config_set_logging = fn "futhark_context_config_set_logging" (context_config @-> int @-> returning void) 19 | let futhark_context_config_set_cache_file = fn "futhark_context_config_set_cache_file" (context_config @-> string @-> returning void) 20 | let futhark_context_pause_profiling = fn "futhark_context_pause_profiling" (context @-> returning void) 21 | let futhark_context_unpause_profiling = fn "futhark_context_unpause_profiling" (context @-> returning void) 22 | let futhark_context_clear_caches = fn "futhark_context_clear_caches" (context @-> returning int) 23 | let futhark_context_get_error = fn "futhark_context_get_error" (context @-> returning (ptr char)) 24 | let futhark_context_report = fn "futhark_context_report" (context @-> returning (ptr char)) 25 | let free = fn "free" (ptr void @-> returning void) 26 | let strlen = fn "strlen" (ptr char @-> returning size_t) 27 | 28 | {generated_foreign_functions} 29 | end 30 | 31 | type error = 32 | | InvalidShape of int * int 33 | | NullPtr 34 | | Code of int 35 | | UseAfterFree of [`context | `array | `opaque] 36 | 37 | exception Error of error 38 | 39 | let set_managed (p: 'a Ctypes_static.ptr) x = 40 | match p with 41 | | Ctypes_static.CPointer fat -> Ctypes_ptr.Fat.set_managed fat (Some (Obj.repr x)) 42 | 43 | let check_use_after_free t b = if b then raise (Error (UseAfterFree t)) 44 | 45 | let () = Printexc.register_printer (function 46 | | Error (InvalidShape (a, b)) -> Some (Printf.sprintf "futhark error: invalid shape, expected %d but got %d" a b) 47 | | Error NullPtr -> Some "futhark error: null pointer" 48 | | Error (Code c) -> Some (Printf.sprintf "futhark error: code %d" c) 49 | | Error (UseAfterFree `context) -> Some "futhark: context used after beeing freed" 50 | | Error (UseAfterFree `array) -> Some "futhark: array used after beeing freed" 51 | | Error (UseAfterFree `opaque) -> Some "futhark: opaque value used after beeing freed" 52 | | _ -> None) 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use futhark_bindgen::*; 2 | 3 | use argh::FromArgs; 4 | 5 | fn parse_backend(s: &str) -> Result { 6 | let mut s = s.to_string(); 7 | s.make_ascii_lowercase(); 8 | let x = serde_json::from_str(&format!("\"{}\"", s)).expect("Invalid backend"); 9 | Ok(x) 10 | } 11 | 12 | #[derive(Debug, FromArgs)] 13 | #[argh(description = "futhark binding generator")] 14 | struct Main { 15 | #[argh(subcommand)] 16 | command: Commands, 17 | } 18 | 19 | #[derive(Debug, FromArgs)] 20 | #[argh(subcommand)] 21 | enum Commands { 22 | Run(Run), 23 | Libs(Libs), 24 | } 25 | 26 | #[derive(Debug, FromArgs)] 27 | #[argh( 28 | name = "libs", 29 | description = "List libraries for the selected backend", 30 | subcommand 31 | )] 32 | 33 | struct Libs { 34 | #[argh( 35 | option, 36 | default = "Backend::C", 37 | from_str_fn(parse_backend), 38 | description = "futhark backend: c, cuda, opencl, multicore" 39 | )] 40 | backend: Backend, 41 | } 42 | 43 | #[derive(Debug, FromArgs)] 44 | #[argh(name = "run", description = "generate bindings", subcommand)] 45 | struct Run { 46 | #[argh(positional, description = "futhark input file")] 47 | input: std::path::PathBuf, 48 | 49 | #[argh(positional, description = "output file")] 50 | output: std::path::PathBuf, 51 | 52 | #[argh( 53 | option, 54 | default = "Backend::C", 55 | from_str_fn(parse_backend), 56 | description = "futhark backend: c, cuda, opencl, multicore" 57 | )] 58 | backend: Backend, 59 | 60 | #[argh(option, description = "path to futhark compiler")] 61 | compiler: Option, 62 | 63 | #[argh( 64 | option, 65 | long = "futhark-arg", 66 | short = 'f', 67 | description = "arguments to be passed to the futhark compiler" 68 | )] 69 | futhark_args: Vec, 70 | } 71 | 72 | fn main() -> Result<(), Error> { 73 | let args: Main = argh::from_env(); 74 | 75 | match args.command { 76 | Commands::Run(mut args) => { 77 | if args.output.is_relative() { 78 | args.output = std::path::PathBuf::from(".").join(args.output); 79 | } 80 | let out_dir = args.output.parent().unwrap().canonicalize().unwrap(); 81 | let mut compiler = Compiler::new(args.backend, &args.input) 82 | .with_extra_args(args.futhark_args) 83 | .with_output_dir(out_dir); 84 | if let Some(exe) = args.compiler { 85 | compiler = compiler.with_executable_name(exe); 86 | } 87 | let pkg = compiler.compile()?; 88 | let mut config = Config::new(args.output)?; 89 | let mut gen = config.detect().expect("Unable to detect output language"); 90 | gen.generate(&pkg, &mut config)?; 91 | } 92 | Commands::Libs(args) => { 93 | args.backend 94 | .required_c_libs() 95 | .iter() 96 | .for_each(|x| print!("-l{x} ")); 97 | println!(); 98 | } 99 | } 100 | 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /src/generate/templates/ocaml/context.ml: -------------------------------------------------------------------------------- 1 | open Bigarray 2 | 3 | module Context = struct 4 | [@@@ocaml.warning "-69"] 5 | type t = {{ handle: unit ptr; config: unit ptr; cache_file: string option; auto_sync: bool; mutable context_free: bool }} 6 | [@@@ocaml.warning "+69"] 7 | 8 | let free t = 9 | if not t.context_free then 10 | let () = ignore (Bindings.futhark_context_sync t.handle) in 11 | let () = ignore (Bindings.futhark_context_free t.handle) in 12 | let () = ignore (Bindings.futhark_context_config_free t.config) in 13 | t.context_free <- true 14 | 15 | let v ?(debug = false) ?(log = false) ?(profile = false) ?cache_file ?(auto_sync = true) {extra_param} () = 16 | let config = Bindings.futhark_context_config_new () in 17 | if is_null config then raise (Error NullPtr); 18 | Bindings.futhark_context_config_set_debugging config (if debug then 1 else 0); 19 | Bindings.futhark_context_config_set_profiling config (if profile then 1 else 0); 20 | Bindings.futhark_context_config_set_logging config (if log then 1 else 0); 21 | {extra_line} 22 | Option.iter (Bindings.futhark_context_config_set_cache_file config) cache_file; 23 | let handle = Bindings.futhark_context_new config in 24 | if is_null handle then 25 | let () = ignore @@ Bindings.futhark_context_config_free config in 26 | raise (Error NullPtr) 27 | else 28 | let t = {{ handle; config; cache_file; auto_sync; context_free = false }} in 29 | set_managed handle t; 30 | let () = Gc.finalise free t in 31 | t 32 | 33 | let sync t = 34 | check_use_after_free `context t.context_free; 35 | let rc = Bindings.futhark_context_sync t.handle in 36 | if rc <> 0 then raise (Error (Code rc)) 37 | 38 | let auto_sync t = 39 | if t.auto_sync then sync t 40 | 41 | let clear_caches t = 42 | check_use_after_free `context t.context_free; 43 | let rc = Bindings.futhark_context_clear_caches t.handle in 44 | if rc <> 0 then raise (Error (Code rc)) 45 | 46 | let string_opt_of_ptr ptr = 47 | if is_null ptr then None 48 | else 49 | let len = Bindings.strlen ptr |> Unsigned.Size_t.to_int in 50 | let s = String.init len (fun i -> !@(ptr +@ i)) in 51 | let () = Bindings.free (coerce (Ctypes.ptr Ctypes.char) (Ctypes.ptr void) ptr) in Some s 52 | 53 | let get_error t = 54 | check_use_after_free `context t.context_free; 55 | let ptr = Bindings.futhark_context_get_error t.handle in string_opt_of_ptr ptr 56 | 57 | let report t = 58 | check_use_after_free `context t.context_free; 59 | let ptr = Bindings.futhark_context_report t.handle in string_opt_of_ptr ptr 60 | 61 | let pause_profiling t = 62 | check_use_after_free `context t.context_free; 63 | Bindings.futhark_context_pause_profiling t.handle 64 | 65 | let unpause_profiling t = 66 | check_use_after_free `context t.context_free; 67 | Bindings.futhark_context_unpause_profiling t.handle 68 | end 69 | 70 | [@@@ocaml.warning "-34"] 71 | [@@@ocaml.warning "-69"] 72 | type futhark_array = {{ mutable ptr: unit ptr ptr; shape: int array; ctx: Context.t }} 73 | type opaque = {{ mutable opaque_ptr: unit ptr ptr; opaque_ctx: Context.t }} 74 | [@@@ocaml.warning "+34"] 75 | [@@@ocaml.warning "+69"] 76 | 77 | [@@@ocaml.warning "-32"] 78 | let get_ptr t = 79 | let x = !@(t.ptr) in 80 | check_use_after_free `array (Ctypes.is_null x); 81 | x 82 | 83 | let get_opaque_ptr t = 84 | let x = !@(t.opaque_ptr) in 85 | check_use_after_free `opaque (Ctypes.is_null x); 86 | x 87 | [@@@ocaml.warning "+32"] 88 | 89 | -------------------------------------------------------------------------------- /src/generate/templates/rust/array.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[allow(non_camel_case_types)] 3 | struct {futhark_type} {{ 4 | _private: [u8; 0] 5 | }} 6 | 7 | /// Array type with {rank} dimensions and {elemtype} elements 8 | pub struct {rust_type}<'a> {{ 9 | ptr: *mut {futhark_type}, 10 | pub shape: [i64; {rank}], 11 | ctx: &'a Context, 12 | }} 13 | 14 | impl<'a> {rust_type}<'a> {{ 15 | /// Create a new array of `dims` dimensions and initialize it with the values from `data` 16 | pub fn new(ctx: &'a Context, dims: [i64; {rank}], data: impl AsRef<[{elemtype}]>) -> std::result::Result {{ 17 | let size: i64 = dims.iter().product(); 18 | let data = data.as_ref(); 19 | if data.len() as i64 != size {{ 20 | return Err(Error::InvalidShape) 21 | }} 22 | let ptr = unsafe {{ 23 | {new_fn}(ctx.context, data.as_ptr(), {dim_params}) 24 | }}; 25 | if ptr.is_null() {{ return Err(Error::NullPtr); }} 26 | ctx.auto_sync(); 27 | Ok(Self {{ 28 | ptr: ptr as *mut _, 29 | shape: dims, 30 | ctx, 31 | }}) 32 | }} 33 | 34 | /// Get the array shape 35 | pub fn shape(&self) -> &[i64; {rank}] {{ 36 | &self.shape 37 | }} 38 | 39 | /// Load values back into a slice 40 | pub fn values(&self, mut data: impl AsMut<[{elemtype}]>) -> std::result::Result<(), Error> {{ 41 | let size: i64 = self.shape.iter().product(); 42 | let data = data.as_mut(); 43 | if data.len() as i64 != size {{ 44 | return Err(Error::InvalidShape); 45 | }} 46 | let rc = unsafe {{ 47 | {values_fn}(self.ctx.context, self.ptr, data.as_mut_ptr()) 48 | }}; 49 | if rc != 0 {{ 50 | return Err(Error::Code(rc)); 51 | }} 52 | self.ctx.auto_sync(); 53 | Ok(()) 54 | }} 55 | 56 | /// Load values into a `Vec` 57 | pub fn get(&self) -> std::result::Result, Error> {{ 58 | let size: i64 = self.shape.iter().product(); 59 | let mut vec = vec![{elemtype}::default(); size as usize]; 60 | self.values(&mut vec)?; 61 | Ok(vec) 62 | }} 63 | 64 | 65 | #[allow(unused)] 66 | fn from_ptr(ctx: &'a Context, ptr: *mut {futhark_type}) -> Self {{ 67 | let len_ptr = unsafe {{ futhark_shape_{elemtype}_{rank}d(ctx.context, ptr) }}; 68 | let mut shape = [0i64; {rank}]; 69 | unsafe {{ 70 | for (i, s) in shape.iter_mut().enumerate() {{ 71 | *s = *len_ptr.add(i); 72 | }} 73 | }} 74 | Self {{ ctx, shape, ptr }} 75 | }} 76 | }} 77 | 78 | 79 | impl<'a> Drop for {rust_type}<'a> {{ 80 | fn drop(&mut self){{ 81 | unsafe {{ 82 | futhark_free_{elemtype}_{rank}d(self.ctx.context, self.ptr as *mut _); 83 | }} 84 | }} 85 | }} 86 | 87 | #[allow(unused)] 88 | extern "C" {{ 89 | fn {shape_fn}( 90 | _: *mut futhark_context, 91 | _: *mut {futhark_type} 92 | ) -> *const i64; 93 | 94 | fn {new_fn}( 95 | _: *mut futhark_context, 96 | _: *const {elemtype}, 97 | {new_dim_args} 98 | ) -> *mut {futhark_type}; 99 | 100 | fn {free_fn}( 101 | _: *mut futhark_context, 102 | _: *mut {futhark_type} 103 | ) -> std::os::raw::c_int; 104 | 105 | fn {values_fn}( 106 | _: *mut futhark_context, 107 | _: *mut {futhark_type}, 108 | _: *mut {elemtype} 109 | ) -> std::os::raw::c_int; 110 | }} 111 | -------------------------------------------------------------------------------- /src/generate/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | mod ocaml; 4 | mod rust; 5 | 6 | pub use ocaml::OCaml; 7 | pub use rust::Rust; 8 | 9 | pub(crate) fn first_uppercase(s: &str) -> String { 10 | let mut s = s.to_string(); 11 | if let Some(r) = s.get_mut(0..1) { 12 | r.make_ascii_uppercase(); 13 | } 14 | s 15 | } 16 | 17 | pub(crate) fn convert_struct_name(s: &str) -> &str { 18 | s.strip_prefix("struct") 19 | .unwrap() 20 | .strip_suffix('*') 21 | .unwrap() 22 | .strip_prefix(|x: char| x.is_ascii_whitespace()) 23 | .unwrap() 24 | .strip_suffix(|x: char| x.is_ascii_whitespace()) 25 | .unwrap() 26 | } 27 | 28 | /// Code generation config 29 | pub struct Config { 30 | /// Output file 31 | pub output_path: std::path::PathBuf, 32 | 33 | /// Path to output file 34 | pub output_file: std::fs::File, 35 | } 36 | 37 | impl Config { 38 | /// Create a new config using the provided output file path 39 | pub fn new(output: impl AsRef) -> Result { 40 | Ok(Config { 41 | output_path: output.as_ref().to_path_buf(), 42 | output_file: std::fs::File::create(output)?, 43 | }) 44 | } 45 | } 46 | 47 | pub trait Generate { 48 | /// Iterates through the manifest and generates code 49 | fn generate(&mut self, pkg: &Package, config: &mut Config) -> Result<(), Error> { 50 | self.bindings(pkg, config)?; 51 | for (name, ty) in &pkg.manifest.types { 52 | match ty { 53 | manifest::Type::Array(ty) => { 54 | self.array_type(pkg, config, name, ty)?; 55 | } 56 | manifest::Type::Opaque(ty) => { 57 | self.opaque_type(pkg, config, name, ty)?; 58 | } 59 | } 60 | } 61 | 62 | for (name, entry) in &pkg.manifest.entry_points { 63 | self.entry(pkg, config, name, entry)?; 64 | } 65 | self.format(&config.output_path)?; 66 | Ok(()) 67 | } 68 | 69 | /// Step 1: generate any setup code or low-level bindings 70 | fn bindings(&mut self, _pkg: &Package, _config: &mut Config) -> Result<(), Error>; 71 | 72 | /// Step 2: generate code for array types 73 | fn array_type( 74 | &mut self, 75 | pkg: &Package, 76 | config: &mut Config, 77 | name: &str, 78 | ty: &manifest::ArrayType, 79 | ) -> Result<(), Error>; 80 | 81 | /// Step 3: generate code for opaque types 82 | fn opaque_type( 83 | &mut self, 84 | pkg: &Package, 85 | config: &mut Config, 86 | name: &str, 87 | ty: &manifest::OpaqueType, 88 | ) -> Result<(), Error>; 89 | 90 | /// Step 4: generate code for entry points 91 | fn entry( 92 | &mut self, 93 | pkg: &Package, 94 | config: &mut Config, 95 | name: &str, 96 | entry: &manifest::Entry, 97 | ) -> Result<(), Error>; 98 | 99 | /// Step 5: Optionally, run any formatting program or post-processing on the output file 100 | fn format(&mut self, _output: &std::path::Path) -> Result<(), Error> { 101 | Ok(()) 102 | } 103 | } 104 | 105 | fn rust() -> Box { 106 | Box::::default() 107 | } 108 | 109 | fn ocaml(config: &Config) -> Box { 110 | Box::new(OCaml::new(config).unwrap()) 111 | } 112 | 113 | impl Config { 114 | /// Automatically detect output language 115 | pub fn detect(&self) -> Option> { 116 | match self 117 | .output_path 118 | .extension() 119 | .map(|x| x.to_str().expect("Invalid extension")) 120 | { 121 | Some("rs") => Some(rust()), 122 | Some("ml") => Some(ocaml(self)), 123 | _ => None, 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/manifest.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Scalar types 4 | #[derive(Clone, Debug, serde::Deserialize)] 5 | pub enum ElemType { 6 | /// Signed 8 bit integer 7 | #[serde(rename = "i8")] 8 | I8, 9 | 10 | /// Signed 16 bit integer 11 | #[serde(rename = "i16")] 12 | I16, 13 | 14 | /// Signed 32 bit integer 15 | #[serde(rename = "i32")] 16 | I32, 17 | 18 | /// Signed 64 bit integer 19 | #[serde(rename = "i64")] 20 | I64, 21 | 22 | /// Unsigned 8 bit integer 23 | #[serde(rename = "u8")] 24 | U8, 25 | 26 | /// Unsigned 16 bit integer 27 | #[serde(rename = "u16")] 28 | U16, 29 | 30 | /// Unsigned 32 bit integer 31 | #[serde(rename = "u32")] 32 | U32, 33 | 34 | /// Unsigned 64 bit integer 35 | #[serde(rename = "u64")] 36 | U64, 37 | 38 | /// 16 bit float 39 | #[serde(rename = "f16")] 40 | F16, 41 | 42 | /// 32 bit float 43 | #[serde(rename = "f32")] 44 | F32, 45 | 46 | /// 64 bit float 47 | #[serde(rename = "f64")] 48 | F64, 49 | 50 | /// Boolean 51 | #[serde(rename = "bool")] 52 | Bool, 53 | } 54 | 55 | impl ElemType { 56 | pub fn to_str(&self) -> &'static str { 57 | match self { 58 | ElemType::I8 => "i8", 59 | ElemType::I16 => "i16", 60 | ElemType::I32 => "i32", 61 | ElemType::I64 => "i64", 62 | ElemType::U8 => "u8", 63 | ElemType::U16 => "u16", 64 | ElemType::U32 => "u32", 65 | ElemType::U64 => "u64", 66 | ElemType::F16 => "f16", 67 | ElemType::F32 => "f32", 68 | ElemType::F64 => "f64", 69 | ElemType::Bool => "bool", 70 | } 71 | } 72 | } 73 | 74 | #[derive(Clone, Debug, serde::Deserialize)] 75 | pub struct Output { 76 | pub r#type: String, 77 | pub unique: bool, 78 | } 79 | 80 | #[derive(Clone, Debug, serde::Deserialize)] 81 | pub struct Input { 82 | pub name: String, 83 | pub r#type: String, 84 | pub unique: bool, 85 | } 86 | 87 | #[derive(Clone, Debug, serde::Deserialize)] 88 | pub struct Entry { 89 | pub cfun: String, 90 | pub outputs: Vec, 91 | pub inputs: Vec, 92 | } 93 | 94 | #[derive(Clone, Debug, serde::Deserialize)] 95 | pub struct ArrayOps { 96 | pub free: String, 97 | pub shape: String, 98 | pub values: String, 99 | pub new: String, 100 | } 101 | 102 | #[derive(Clone, Debug, serde::Deserialize)] 103 | pub struct ArrayType { 104 | pub ctype: String, 105 | pub rank: i32, 106 | pub elemtype: ElemType, 107 | pub ops: ArrayOps, 108 | } 109 | 110 | #[derive(Clone, Debug, serde::Deserialize)] 111 | pub struct OpaqueOps { 112 | pub free: String, 113 | pub store: String, 114 | pub restore: String, 115 | } 116 | 117 | #[derive(Clone, Debug, serde::Deserialize)] 118 | pub struct Field { 119 | pub name: String, 120 | pub project: String, 121 | pub r#type: String, 122 | } 123 | 124 | #[derive(Clone, Debug, serde::Deserialize)] 125 | pub struct Record { 126 | pub new: String, 127 | pub fields: Vec, 128 | } 129 | 130 | #[derive(Clone, Debug, serde::Deserialize)] 131 | pub struct OpaqueType { 132 | pub ctype: String, 133 | pub ops: OpaqueOps, 134 | pub record: Option, 135 | } 136 | 137 | #[derive(Clone, Debug, serde::Deserialize)] 138 | #[serde(tag = "kind")] 139 | pub enum Type { 140 | #[serde(rename = "array")] 141 | Array(ArrayType), 142 | #[serde(rename = "opaque")] 143 | Opaque(OpaqueType), 144 | } 145 | 146 | /// A Rust encoding of the Futhark manifest file 147 | #[derive(Clone, Debug, serde::Deserialize)] 148 | pub struct Manifest { 149 | pub backend: Backend, 150 | pub version: String, 151 | pub entry_points: BTreeMap, 152 | pub types: BTreeMap, 153 | } 154 | 155 | impl Manifest { 156 | /// Parse the manifest file 157 | pub fn parse_file(filename: impl AsRef) -> Result { 158 | let r = std::fs::File::open(filename)?; 159 | let manifest = serde_json::from_reader(r)?; 160 | Ok(manifest) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use std::collections::BTreeMap; 2 | 3 | mod compiler; 4 | mod error; 5 | pub(crate) mod generate; 6 | pub mod manifest; 7 | mod package; 8 | 9 | pub use compiler::Compiler; 10 | pub use error::Error; 11 | pub use generate::{Config, Generate, OCaml, Rust}; 12 | pub use manifest::Manifest; 13 | pub use package::Package; 14 | 15 | /// `Backend` is used to select a backend when running the `futhark` executable 16 | #[derive(Debug, serde::Deserialize, PartialEq, Eq, Clone, Copy)] 17 | pub enum Backend { 18 | /// Sequential C backend: `futhark c` 19 | /// 20 | /// Requires a C compiler 21 | #[serde(rename = "c")] 22 | C, 23 | 24 | /// CUDA backend: `futhark cuda` 25 | /// 26 | /// Requires the CUDA runtime and a C compiler 27 | #[serde(rename = "cuda")] 28 | CUDA, 29 | 30 | /// OpenCL backend: `futhark opencl` 31 | /// 32 | /// Requires OpenCL and a C compiler 33 | #[serde(rename = "opencl")] 34 | OpenCL, 35 | 36 | /// Multicore C backend: `futhark multicore` 37 | /// 38 | /// Requires a C compiler 39 | #[serde(rename = "multicore")] 40 | Multicore, 41 | 42 | /// ISPC backend: `futhark ispc` 43 | /// 44 | /// Requires the `ispc` compiler in your `$PATH` 45 | /// and a C compiler 46 | #[serde(rename = "ispc")] 47 | ISPC, 48 | 49 | /// HIP backend: `futhark hip` 50 | /// 51 | /// Requires a C compiler 52 | #[serde(rename = "hip")] 53 | HIP, 54 | } 55 | 56 | impl Backend { 57 | /// Get the name of a backend 58 | pub fn to_str(&self) -> &'static str { 59 | match self { 60 | Backend::C => "c", 61 | Backend::CUDA => "cuda", 62 | Backend::OpenCL => "opencl", 63 | Backend::Multicore => "multicore", 64 | Backend::ISPC => "ispc", 65 | Backend::HIP => "hip", 66 | } 67 | } 68 | 69 | /// Return the backend specified by the given name if valid 70 | pub fn from_name(name: &str) -> Option { 71 | match name.to_ascii_lowercase().as_str() { 72 | "c" => Some(Backend::C), 73 | "cuda" => Some(Backend::CUDA), 74 | "opencl" => Some(Backend::OpenCL), 75 | "multicore" => Some(Backend::Multicore), 76 | "ispc" => Some(Backend::ISPC), 77 | _ => None, 78 | } 79 | } 80 | 81 | /// Get the backend from the `FUTHARK_BACKEND` environment variable 82 | pub fn from_env() -> Option { 83 | match std::env::var("FUTHARK_BACKEND") { 84 | Ok(name) => Backend::from_name(&name), 85 | Err(_) => None, 86 | } 87 | } 88 | 89 | /// Returns the C libraries that need to be linked for a backend 90 | pub fn required_c_libs(&self) -> &'static [&'static str] { 91 | match self { 92 | Backend::CUDA => &["cuda", "cudart", "nvrtc", "m"], 93 | Backend::OpenCL => &["OpenCL", "m"], 94 | Backend::Multicore | Backend::ISPC => &["pthread", "m"], 95 | Backend::HIP => &["hiprtc", "amdhip64"], 96 | _ => &[], 97 | } 98 | } 99 | } 100 | 101 | #[cfg(feature = "build")] 102 | /// Generate the bindings and link the Futhark C code 103 | /// 104 | /// `backend` selects the backend to use when generating C code: `futhark $backend --lib` 105 | /// 106 | /// `src` is the full path to your Futhark code 107 | /// 108 | /// `dest` is expected to be a relative path that will 109 | /// be appended to `$OUT_DIR` 110 | pub fn build( 111 | backend: Backend, 112 | src: impl AsRef, 113 | dest: impl AsRef, 114 | ) { 115 | let out = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); 116 | let dest = std::path::PathBuf::from(&out).join(dest); 117 | let lib = Compiler::new(backend, src) 118 | .with_output_dir(out) 119 | .compile() 120 | .expect("Compilation failed"); 121 | 122 | let mut config = Config::new(&dest).expect("Unable to configure codegen"); 123 | let mut gen = config.detect().expect("Invalid output language"); 124 | gen.generate(&lib, &mut config) 125 | .expect("Code generation failed"); 126 | lib.link(); 127 | } 128 | -------------------------------------------------------------------------------- /examples/ocaml/src/example.fut: -------------------------------------------------------------------------------- 1 | -- Simple game of life implementation with a donut world. Tested with 2 | -- a glider running for four iterations. 3 | -- 4 | -- http://rosettacode.org/wiki/Conway's_Game_of_Life 5 | -- 6 | -- == 7 | -- input { 8 | -- [[0, 0, 0, 0, 0], 9 | -- [0, 0, 1, 0, 0], 10 | -- [0, 0, 0, 1, 0], 11 | -- [0, 1, 1, 1, 0], 12 | -- [0, 0, 0, 0, 0]] 13 | -- 4 14 | -- } 15 | -- output { 16 | -- [[0, 0, 0, 0, 0], 17 | -- [0, 0, 0, 0, 0], 18 | -- [0, 0, 0, 1, 0], 19 | -- [0, 0, 0, 0, 1], 20 | -- [0, 0, 1, 1, 1]] 21 | -- } 22 | -- input { 23 | -- [[0, 0, 0, 0, 0], 24 | -- [0, 0, 1, 0, 0], 25 | -- [0, 0, 0, 1, 0], 26 | -- [0, 1, 1, 1, 0], 27 | -- [0, 0, 0, 0, 0]] 28 | -- 8 29 | -- } 30 | -- output { 31 | -- [[1, 0, 0, 1, 1], 32 | -- [0, 0, 0, 0, 0], 33 | -- [0, 0, 0, 0, 0], 34 | -- [0, 0, 0, 0, 1], 35 | -- [1, 0, 0, 0, 0]] 36 | -- } 37 | 38 | def bint: bool -> i32 = i32.bool 39 | def intb : i32 -> bool = bool.i32 40 | 41 | def to_bool_board(board: [][]i32): [][]bool = 42 | map (\r -> map intb r) board 43 | 44 | def to_int_board(board: [][]bool): [][]i32 = 45 | map (\r -> map bint r) board 46 | 47 | entry all_neighbours [n][m] (world: [n][m]bool): [n][m]i32 = 48 | let ns = map (rotate (-1)) world 49 | let ss = map (rotate 1) world 50 | let ws = rotate (-1) world 51 | let es = rotate 1 world 52 | let nws = map (rotate (-1)) ws 53 | let nes = map (rotate (-1)) es 54 | let sws = map (rotate 1) ws 55 | let ses = map (rotate 1) es 56 | in map3 (\(nws_r, ns_r, nes_r) (ws_r, world_r, es_r) (sws_r, ss_r, ses_r) -> 57 | map3 (\(nw,n,ne) (w,_,e) (sw,s,se) -> 58 | bint nw + bint n + bint ne + 59 | bint w + bint e + 60 | bint sw + bint s + bint se) 61 | (zip3 nws_r ns_r nes_r) (zip3 ws_r world_r es_r) (zip3 sws_r ss_r ses_r)) 62 | (zip3 nws ns nes) (zip3 ws world es) (zip3 sws ss ses) 63 | 64 | entry iteration [n][m] (board: [n][m]bool): [n][m]bool = 65 | let lives = all_neighbours(board) in 66 | map2 (\(lives_r: []i32) (board_r: []bool) -> 67 | map2 (\(neighbors: i32) (alive: bool): bool -> 68 | if neighbors < 2 69 | then false 70 | else if neighbors == 3 then true 71 | else if alive && neighbors < 4 then true 72 | else false) 73 | lives_r board_r) 74 | lives board 75 | 76 | entry life (int_board: [][]i32) (iterations: i32): [][]i32 = 77 | -- We accept the board as integers for convenience, and then we 78 | -- convert to booleans here. 79 | let board = to_bool_board int_board 80 | in to_int_board (loop board for _i < iterations do iteration board) 81 | 82 | -- Random tests 83 | type number = {x: f32} 84 | 85 | type point = {x: f32, y: f32} 86 | 87 | entry distance (a: point) (b: point) : f32 = 88 | let dx = b.x - a.x in 89 | let dy = b.y - a.y in 90 | f32.sqrt ((dx * dx) + (dy * dy)) 91 | 92 | type~ tup = (number, []f32) 93 | 94 | -- Check struct argument with numeric return 95 | entry test (x: number) = 96 | x.x * 2 97 | 98 | -- Check tuple argument with array return value 99 | entry tup_mul (x: tup): []f32 = 100 | map (\a -> x.0.x * a) x.1 101 | 102 | entry binary_search [n] (xs: [n]i64) (x: i64) : i64 = 103 | let (l, _) = 104 | loop (l, r) = (0, n-1) while l < r do 105 | let t = l + (r - l) / 2 in 106 | if x <= xs[t] then (l, t) 107 | else (t+1, r) 108 | in l 109 | 110 | type option = #some i64 | #none 111 | 112 | -- Check bool type 113 | entry is_none (x: option): bool = 114 | match x 115 | case #some _ -> false 116 | case #none -> true 117 | 118 | -- Check entry point with sum-type argument 119 | entry option_get (x: option) : i64 = 120 | match x 121 | case #some x -> x 122 | case #none -> -1 123 | 124 | -- Check entry point with sum-type argument and return value 125 | entry return_option (x: option): option = x 126 | 127 | -- Check input and output array with 2 dimensions 128 | entry mul2 (a: [][]f64) : [][]f64 = 129 | map (map (\b -> b * 2.0)) a 130 | 131 | -- Check lots of arguments 132 | entry sinking1 (as: []i32) (bs: []i32) (cs: []i32) (ds: []i32) (es: []i32) = 133 | map5 (\a b c d e -> if a == 0 then 0 else b + c + d + e) as bs cs ds es 134 | 135 | entry count_lines (input: []u8) : i64 = 136 | map (\x -> i64.bool (x == 10)) input |> i64.sum 137 | 138 | entry count_true (input: []bool) : i64 = 139 | map i64.bool input |> i64.sum 140 | 141 | -------------------------------------------------------------------------------- /src/generate/templates/rust/context.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error {{ 3 | Code(std::os::raw::c_int), 4 | NullPtr, 5 | InvalidShape, 6 | }} 7 | 8 | impl std::fmt::Display for Error {{ 9 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {{ 10 | match self {{ 11 | Error::Code(code) => write!(fmt, "Futhark error code: {{code}}"), 12 | Error::NullPtr => write!(fmt, "NULL pointer encountered"), 13 | Error::InvalidShape => write!(fmt, "Invalid image shape"), 14 | }} 15 | }} 16 | }} 17 | 18 | impl std::error::Error for Error {{}} 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct Options {{ 22 | debug: bool, 23 | profile: bool, 24 | logging: bool, 25 | num_threads: u32, 26 | cache_file: std::option::Option, 27 | device: std::option::Option, 28 | auto_sync: bool, 29 | }} 30 | 31 | impl Default for Options {{ 32 | fn default() -> Self {{ 33 | Options::new() 34 | }} 35 | }} 36 | 37 | impl Options {{ 38 | /// Create new `Options` with default settings 39 | pub fn new() -> Self {{ 40 | Options {{ 41 | debug: false, 42 | profile: false, 43 | logging: false, 44 | num_threads: 0, 45 | cache_file: None, 46 | device: None, 47 | auto_sync: true, 48 | }} 49 | }} 50 | 51 | /// Enable debug 52 | pub fn debug(mut self) -> Self {{ 53 | self.debug = true; 54 | self 55 | }} 56 | 57 | /// Enable profiling 58 | pub fn profile(mut self) -> Self {{ 59 | self.profile = true; 60 | self 61 | }} 62 | 63 | /// Enable logging 64 | pub fn log(mut self) -> Self {{ 65 | self.logging = true; 66 | self 67 | }} 68 | 69 | /// Set Futhark cache file 70 | pub fn cache_file(mut self, s: impl AsRef) -> Self {{ 71 | self.cache_file = Some(std::ffi::CString::new(s.as_ref()).expect("Invalid cache file")); 72 | self 73 | }} 74 | 75 | pub fn auto_sync(mut self, sync: bool) -> Self {{ 76 | self.auto_sync = sync; 77 | self 78 | }} 79 | 80 | 81 | {backend_options} 82 | }} 83 | 84 | /// Futhark context 85 | pub struct Context {{ 86 | config: *mut futhark_context_config, 87 | context: *mut futhark_context, 88 | auto_sync: bool, 89 | _cache_file: std::option::Option, 90 | }} 91 | 92 | impl Context {{ 93 | /// Create a new context with default options 94 | pub fn new() -> std::result::Result {{ 95 | unsafe {{ 96 | let config = futhark_context_config_new(); 97 | if config.is_null() {{ return Err(Error::NullPtr) }} 98 | let context = futhark_context_new(config); 99 | if context.is_null() {{ 100 | futhark_context_config_free(config); 101 | return Err(Error::NullPtr); 102 | }} 103 | Ok(Context {{ config, context, auto_sync: true, _cache_file: None }}) 104 | }} 105 | }} 106 | 107 | /// Create a new context with custom options 108 | pub fn new_with_options(options: Options) -> std::result::Result {{ 109 | unsafe {{ 110 | let config = futhark_context_config_new(); 111 | if config.is_null() {{ return Err(Error::NullPtr) }} 112 | 113 | futhark_context_config_set_debugging(config, options.debug as std::os::raw::c_int); 114 | futhark_context_config_set_profiling(config, options.profile as std::os::raw::c_int); 115 | futhark_context_config_set_logging(config, options.logging as std::os::raw::c_int); 116 | 117 | if let Some(c) = &options.cache_file {{ 118 | futhark_context_config_set_cache_file(config, c.as_ptr()); 119 | }} 120 | 121 | {configure_num_threads} 122 | {configure_set_device} 123 | 124 | let context = futhark_context_new(config); 125 | if context.is_null() {{ 126 | futhark_context_config_free(config); 127 | return Err(Error::NullPtr); 128 | }} 129 | Ok(Context {{ config, context, auto_sync: options.auto_sync, _cache_file: options.cache_file }}) 130 | }} 131 | }} 132 | 133 | /// Sync the context, if `auto_sync` is enabled this shouldn't be needed 134 | pub fn sync(&self) {{ 135 | unsafe {{ futhark_context_sync(self.context); }} 136 | }} 137 | 138 | /// Sync if `auto_sync` is enabled, otherwise this is a noop 139 | pub fn auto_sync(&self) {{ 140 | if self.auto_sync {{ 141 | self.sync(); 142 | }} 143 | }} 144 | 145 | /// Clear Futhark caches 146 | pub fn clear_caches(&self) -> std::result::Result<(), Error> {{ 147 | let rc = unsafe {{ 148 | futhark_context_clear_caches(self.context) 149 | }}; 150 | if rc != 0 {{ return Err(Error::Code(rc)) }} 151 | Ok(()) 152 | }} 153 | 154 | /// Pause Futhark profiling 155 | pub fn pause_profiling(&self) {{ 156 | unsafe {{ 157 | futhark_context_pause_profiling(self.context); 158 | }} 159 | }} 160 | 161 | /// Resume profiling 162 | pub fn unpause_profiling(&self) {{ 163 | unsafe {{ 164 | futhark_context_unpause_profiling(self.context); 165 | }} 166 | }} 167 | 168 | /// Get the last error message or None 169 | pub fn get_error(&self) -> std::option::Option {{ 170 | unsafe {{ 171 | let s = futhark_context_get_error(self.context); 172 | if s.is_null() {{ return None }} 173 | let r = std::ffi::CStr::from_ptr(s).to_string_lossy().to_string(); 174 | free(s as *mut _); 175 | Some(r) 176 | }} 177 | }} 178 | 179 | pub fn report(&self) -> std::option::Option {{ 180 | unsafe {{ 181 | let s = futhark_context_report(self.context); 182 | if s.is_null() {{ return None }} 183 | let r = std::ffi::CStr::from_ptr(s).to_string_lossy().to_string(); 184 | free(s as *mut _); 185 | Some(r) 186 | }} 187 | }} 188 | }} 189 | 190 | impl Drop for Context {{ 191 | fn drop(&mut self) {{ 192 | unsafe {{ 193 | futhark_context_sync(self.context); 194 | futhark_context_free(self.context); 195 | futhark_context_config_free(self.config); 196 | }} 197 | }} 198 | }} 199 | 200 | #[repr(C)] 201 | #[allow(non_camel_case_types)] 202 | struct futhark_context_config {{ 203 | _private: [u8; 0] 204 | }} 205 | 206 | #[repr(C)] 207 | #[allow(non_camel_case_types)] 208 | struct futhark_context {{ 209 | _private: [u8; 0] 210 | }} 211 | 212 | extern "C" {{ 213 | fn futhark_context_config_new() -> *mut futhark_context_config; 214 | fn futhark_context_config_free( 215 | _: *mut futhark_context_config 216 | ); 217 | fn futhark_context_config_set_debugging( 218 | _: *mut futhark_context_config, 219 | _: std::os::raw::c_int 220 | ); 221 | 222 | fn futhark_context_config_set_profiling( 223 | _: *mut futhark_context_config, 224 | _: std::os::raw::c_int 225 | ); 226 | 227 | fn futhark_context_config_set_logging( 228 | _: *mut futhark_context_config, 229 | _: std::os::raw::c_int 230 | ); 231 | 232 | fn futhark_context_config_set_cache_file( 233 | _: *mut futhark_context_config, 234 | _: *const std::os::raw::c_char, 235 | ); 236 | 237 | fn futhark_context_new( 238 | _: *mut futhark_context_config 239 | ) -> *mut futhark_context; 240 | 241 | fn futhark_context_free( 242 | _: *mut futhark_context 243 | ); 244 | 245 | fn futhark_context_sync( 246 | _: *mut futhark_context, 247 | ) -> std::os::raw::c_int; 248 | 249 | fn futhark_context_clear_caches( 250 | _: *mut futhark_context, 251 | ) -> std::os::raw::c_int; 252 | 253 | fn futhark_context_pause_profiling( 254 | _: *mut futhark_context 255 | ); 256 | 257 | fn futhark_context_unpause_profiling( 258 | _: *mut futhark_context 259 | ); 260 | 261 | fn futhark_context_get_error( 262 | _: *mut futhark_context 263 | ) -> *mut std::os::raw::c_char; 264 | 265 | fn futhark_context_report( 266 | _: *mut futhark_context 267 | ) -> *mut std::os::raw::c_char; 268 | 269 | fn free(_: *mut std::ffi::c_void); 270 | 271 | {backend_extern_functions} 272 | }} 273 | -------------------------------------------------------------------------------- /src/generate/rust.rs: -------------------------------------------------------------------------------- 1 | use crate::generate::{convert_struct_name, first_uppercase}; 2 | use crate::*; 3 | use std::io::Write; 4 | 5 | /// Rust codegen 6 | pub struct Rust { 7 | typemap: BTreeMap, 8 | } 9 | 10 | fn type_is_array(t: &str) -> bool { 11 | t.contains("ArrayF") || t.contains("ArrayI") || t.contains("ArrayU") || t.contains("ArrayB") 12 | } 13 | 14 | fn type_is_opaque(a: &str) -> bool { 15 | a.contains("futhark_opaque_") 16 | } 17 | 18 | // Rust `f16` codgen requires the `half` crate 19 | const RUST_TYPE_MAP: &[(&str, &str)] = &[("f16", "half::f16")]; 20 | 21 | impl Default for Rust { 22 | fn default() -> Self { 23 | let typemap = RUST_TYPE_MAP 24 | .iter() 25 | .map(|(a, b)| (a.to_string(), b.to_string())) 26 | .collect(); 27 | Rust { typemap } 28 | } 29 | } 30 | 31 | struct ArrayInfo { 32 | futhark_type: String, 33 | rust_type: String, 34 | #[allow(unused)] 35 | elem: String, 36 | } 37 | 38 | impl Rust { 39 | fn get_type(typemap: &BTreeMap, t: &str) -> String { 40 | let a = typemap.get(t); 41 | let x = match a { 42 | Some(t) => t.clone(), 43 | None => t.to_string(), 44 | }; 45 | if x.is_empty() { 46 | panic!("Unsupported type: {t}"); 47 | } 48 | x 49 | } 50 | } 51 | 52 | impl Generate for Rust { 53 | fn array_type( 54 | &mut self, 55 | _pkg: &Package, 56 | config: &mut Config, 57 | name: &str, 58 | a: &manifest::ArrayType, 59 | ) -> Result<(), Error> { 60 | let elemtype = a.elemtype.to_str(); 61 | let rank = a.rank; 62 | 63 | let futhark_type = convert_struct_name(&a.ctype).to_string(); 64 | let rust_type = format!("Array{}D{rank}", first_uppercase(elemtype)); 65 | let info = ArrayInfo { 66 | futhark_type, 67 | rust_type, 68 | elem: elemtype.to_string(), 69 | }; 70 | 71 | let mut dim_params = Vec::new(); 72 | let mut new_dim_args = Vec::new(); 73 | 74 | for i in 0..a.rank { 75 | let dim = format!("dims[{i}]"); 76 | dim_params.push(dim); 77 | new_dim_args.push(format!("dim{i}: i64")); 78 | } 79 | 80 | writeln!( 81 | config.output_file, 82 | include_str!("templates/rust/array.rs"), 83 | futhark_type = info.futhark_type, 84 | rust_type = info.rust_type, 85 | rank = a.rank, 86 | elemtype = info.elem, 87 | new_fn = a.ops.new, 88 | free_fn = a.ops.free, 89 | values_fn = a.ops.values, 90 | shape_fn = a.ops.shape, 91 | dim_params = dim_params.join(", "), 92 | new_dim_args = new_dim_args.join(", ") 93 | )?; 94 | 95 | self.typemap 96 | .insert(name.to_string(), info.futhark_type.clone()); 97 | self.typemap.insert(info.futhark_type, info.rust_type); 98 | Ok(()) 99 | } 100 | 101 | fn opaque_type( 102 | &mut self, 103 | _pkg: &Package, 104 | config: &mut Config, 105 | name: &str, 106 | ty: &manifest::OpaqueType, 107 | ) -> Result<(), Error> { 108 | let futhark_type = convert_struct_name(&ty.ctype).to_string(); 109 | let mut rust_type = first_uppercase(futhark_type.strip_prefix("futhark_opaque_").unwrap()); 110 | if rust_type.chars().next().unwrap().is_numeric() || name.contains(' ') { 111 | rust_type = format!("Type{}", rust_type); 112 | } 113 | 114 | writeln!( 115 | config.output_file, 116 | include_str!("templates/rust/opaque.rs"), 117 | futhark_type = futhark_type, 118 | rust_type = rust_type, 119 | free_fn = ty.ops.free, 120 | )?; 121 | 122 | let record = match &ty.record { 123 | Some(r) => r, 124 | None => { 125 | self.typemap.insert(name.to_string(), futhark_type.clone()); 126 | self.typemap.insert(futhark_type, rust_type); 127 | return Ok(()); 128 | } 129 | }; 130 | 131 | let mut new_call_args = vec![]; 132 | let mut new_params = vec![]; 133 | let mut new_extern_params = vec![]; 134 | for field in record.fields.iter() { 135 | // Build new function 136 | let a = Self::get_type(&self.typemap, &field.r#type); 137 | let t = Self::get_type(&self.typemap, &a); 138 | 139 | let u = if t == field.r#type { 140 | t.to_string() 141 | } else { 142 | format!("&{t}") 143 | }; 144 | 145 | if type_is_opaque(&a) { 146 | new_call_args.push(format!("field{}.data", field.name)); 147 | new_extern_params.push(format!("field{}: *const {a}", field.name)); 148 | } else if type_is_array(&t) { 149 | new_call_args.push(format!("field{}.ptr", field.name)); 150 | new_extern_params.push(format!("field{}: *const {a}", field.name)); 151 | } else { 152 | new_call_args.push(format!("field{}", field.name)); 153 | new_extern_params.push(format!("field{}: {a}", field.name)); 154 | } 155 | 156 | new_params.push(format!("field{}: {u}", field.name)); 157 | 158 | // Implement get function 159 | 160 | // If the output type is an array or opaque type then we need to wrap the return value 161 | let (output, futhark_field_type) = if type_is_opaque(&a) || type_is_array(&t) { 162 | ( 163 | format!("Ok({t}::from_ptr(self.ctx, out))"), 164 | format!("*mut {a}"), 165 | ) 166 | } else { 167 | ("Ok(out)".to_string(), a) 168 | }; 169 | 170 | writeln!( 171 | config.output_file, 172 | include_str!("templates/rust/record_project.rs"), 173 | project_fn = field.project, 174 | rust_type = rust_type, 175 | futhark_type = futhark_type, 176 | field_name = field.name, 177 | futhark_field_type = futhark_field_type, 178 | rust_field_type = t, 179 | output = output 180 | )?; 181 | } 182 | 183 | writeln!( 184 | config.output_file, 185 | include_str!("templates/rust/record.rs"), 186 | rust_type = rust_type, 187 | futhark_type = futhark_type, 188 | new_fn = record.new, 189 | new_params = new_params.join(", "), 190 | new_call_args = new_call_args.join(", "), 191 | new_extern_params = new_extern_params.join(", "), 192 | )?; 193 | 194 | self.typemap.insert(name.to_string(), futhark_type.clone()); 195 | self.typemap.insert(futhark_type, rust_type); 196 | 197 | Ok(()) 198 | } 199 | 200 | fn entry( 201 | &mut self, 202 | _pkg: &Package, 203 | config: &mut Config, 204 | name: &str, 205 | entry: &manifest::Entry, 206 | ) -> Result<(), Error> { 207 | let mut call_args = Vec::new(); 208 | let mut entry_params = Vec::new(); 209 | let mut return_type = Vec::new(); 210 | let mut out_decl = Vec::new(); 211 | let mut futhark_entry_params = Vec::new(); 212 | let mut entry_return = Vec::new(); 213 | 214 | // Output arguments 215 | for (i, arg) in entry.outputs.iter().enumerate() { 216 | let a = Self::get_type(&self.typemap, &arg.r#type); 217 | 218 | let name = format!("out{i}"); 219 | 220 | let t = Self::get_type(&self.typemap, &a); 221 | 222 | if type_is_array(&t) || type_is_opaque(&a) { 223 | futhark_entry_params.push(format!("{name}: *mut *mut {a}")); 224 | } else { 225 | futhark_entry_params.push(format!("{name}: *mut {a}")); 226 | } 227 | 228 | if type_is_array(&t) || type_is_opaque(&a) { 229 | entry_return.push(format!("{t}::from_ptr(self, {name}.assume_init())",)); 230 | } else { 231 | entry_return.push(format!("{name}.assume_init()")); 232 | } 233 | 234 | out_decl.push(format!("let mut {name} = std::mem::MaybeUninit::zeroed();")); 235 | call_args.push(format!("{name}.as_mut_ptr()")); 236 | return_type.push(t); 237 | } 238 | 239 | // Input arguments 240 | for (i, arg) in entry.inputs.iter().enumerate() { 241 | let a = Self::get_type(&self.typemap, &arg.r#type); 242 | let name = format!("input{i}"); 243 | 244 | let t = Self::get_type(&self.typemap, &a); 245 | 246 | if type_is_array(&t) { 247 | futhark_entry_params.push(format!("{name}: *const {a}")); 248 | 249 | entry_params.push(format!("{name}: &{t}")); 250 | call_args.push(format!("{name}.ptr as *mut _")); 251 | } else if type_is_opaque(&a) { 252 | futhark_entry_params.push(format!("{name}: *const {a}")); 253 | 254 | entry_params.push(format!("{name}: &{t}")); 255 | call_args.push(format!("{name}.data as *mut _")); 256 | } else { 257 | futhark_entry_params.push(format!("{name}: {a}")); 258 | entry_params.push(format!("{name}: {t}")); 259 | call_args.push(name); 260 | } 261 | } 262 | 263 | let (entry_return_type, entry_return) = match entry.outputs.len() { 264 | 0 => ("()".to_string(), "()".to_string()), 265 | 1 => (return_type.join(", "), entry_return.join(", ")), 266 | _ => ( 267 | format!("({})", return_type.join(", ")), 268 | format!("({})", entry_return.join(", ")), 269 | ), 270 | }; 271 | 272 | writeln!( 273 | config.output_file, 274 | include_str!("templates/rust/entry.rs"), 275 | entry_fn = entry.cfun, 276 | entry_name = name, 277 | entry_params = entry_params.join(", "), 278 | entry_return_type = entry_return_type, 279 | out_decl = out_decl.join(";\n"), 280 | call_args = call_args.join(", "), 281 | entry_return = entry_return, 282 | futhark_entry_params = futhark_entry_params.join(", "), 283 | )?; 284 | 285 | Ok(()) 286 | } 287 | 288 | fn bindings(&mut self, pkg: &Package, config: &mut Config) -> Result<(), Error> { 289 | writeln!(config.output_file, "// Generated by futhark-bindgen\n")?; 290 | let backend_extern_functions = match &pkg.manifest.backend { 291 | Backend::Multicore => { 292 | "fn futhark_context_config_set_num_threads(_: *mut futhark_context_config, _: std::os::raw::c_int);" 293 | } 294 | Backend::OpenCL | Backend::CUDA => { 295 | "fn futhark_context_config_set_device(_: *mut futhark_context_config, _: *const std::os::raw::c_char);" 296 | } 297 | _ => "", 298 | }; 299 | 300 | let backend_options = match pkg.manifest.backend { 301 | Backend::Multicore => { 302 | "pub fn threads(mut self, n: u32) -> Options { self.num_threads = n as u32; self }" 303 | } 304 | Backend::CUDA | Backend::OpenCL => { 305 | "pub fn device(mut self, s: impl AsRef) -> Options { self.device = Some(std::ffi::CString::new(s.as_ref()).expect(\"Invalid device\")); self }" 306 | } 307 | _ => "", 308 | }; 309 | 310 | let configure_num_threads = if pkg.manifest.backend == Backend::Multicore { 311 | "futhark_context_config_set_num_threads(config, options.num_threads as std::os::raw::c_int);" 312 | } else { 313 | "let _ = &options.num_threads;" 314 | }; 315 | 316 | let configure_set_device = if matches!( 317 | pkg.manifest.backend, 318 | Backend::CUDA | Backend::OpenCL 319 | ) { 320 | "if let Some(d) = &options.device { futhark_context_config_set_device(config, d.as_ptr()); }" 321 | } else { 322 | "let _ = &options.device;" 323 | }; 324 | 325 | writeln!( 326 | config.output_file, 327 | include_str!("templates/rust/context.rs"), 328 | backend_options = backend_options, 329 | configure_num_threads = configure_num_threads, 330 | configure_set_device = configure_set_device, 331 | backend_extern_functions = backend_extern_functions, 332 | )?; 333 | 334 | Ok(()) 335 | } 336 | 337 | fn format(&mut self, path: &std::path::Path) -> Result<(), Error> { 338 | let _ = std::process::Command::new("rustfmt").arg(path).status(); 339 | Ok(()) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/generate/ocaml.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::generate::{convert_struct_name, first_uppercase}; 4 | use crate::*; 5 | 6 | /// OCaml codegen 7 | pub struct OCaml { 8 | typemap: BTreeMap, 9 | ctypes_map: BTreeMap, 10 | ba_map: BTreeMap, 11 | mli_file: std::fs::File, 12 | } 13 | 14 | const OCAML_CTYPES_MAP: &[(&str, &str)] = &[ 15 | ("i8", "char"), 16 | ("u8", "uint8_t"), 17 | ("i16", "int16_t"), 18 | ("u16", "uint16_t"), 19 | ("i32", "int32_t"), 20 | ("u32", "uint32_t"), 21 | ("i64", "int64_t"), 22 | ("u64", "uint64_t"), 23 | ("f16", ""), // No half type in OCaml 24 | ("f32", "float"), 25 | ("f64", "double"), 26 | ("bool", "bool"), 27 | ]; 28 | 29 | const OCAML_TYPE_MAP: &[(&str, &str)] = &[ 30 | ("i8", "char"), 31 | ("u8", "UInt8.t"), 32 | ("i16", "int"), 33 | ("u16", "UInt16.t"), 34 | ("i32", "int32"), 35 | ("i64", "int64"), 36 | ("u32", "UInt32.t"), 37 | ("u64", "UInt64.t"), 38 | ("f16", ""), // No half type in OCaml 39 | ("f32", "float"), 40 | ("f64", "float"), 41 | ("bool", "bool"), 42 | ]; 43 | 44 | const OCAML_BA_TYPE_MAP: &[(&str, (&str, &str))] = &[ 45 | ("i8", ("int", "Bigarray.int8_signed_elt")), 46 | ("u8", ("int", "Bigarray.int8_unsigned_elt")), 47 | ("i16", ("int", "Bigarray.int16_signed_elt")), 48 | ("u16", ("int", "Bigarray.int16_unsigned_elt")), 49 | ("i32", ("int32", "Bigarray.int32_elt")), 50 | ("i64", ("int64", "Bigarray.int64_elt")), 51 | ("u32", ("int32", "Bigarray.int32_elt")), 52 | ("u64", ("int64", "Bigarray.int64_elt")), 53 | ("f16", ("", "")), // No half Bigarray kind 54 | ("f32", ("float", "Bigarray.float32_elt")), 55 | ("f64", ("float", "Bigarray.float64_elt")), 56 | ("bool", ("int", "Bigarray.int8_unsigned_elt")), 57 | ]; 58 | 59 | fn type_is_array(t: &str) -> bool { 60 | t.contains("array_f") || t.contains("array_i") || t.contains("array_u") || t.contains("array_b") 61 | } 62 | 63 | fn type_is_opaque(t: &str) -> bool { 64 | t.contains(".t") 65 | } 66 | 67 | fn ba_kind(t: &str) -> String { 68 | let mut s = t.strip_suffix("_elt").unwrap().to_string(); 69 | 70 | if let Some(r) = s.get_mut(8..9) { 71 | r.make_ascii_uppercase(); 72 | } 73 | 74 | s 75 | } 76 | 77 | impl OCaml { 78 | /// Create new OCaml codegen instance 79 | pub fn new(config: &Config) -> Result { 80 | let typemap = OCAML_TYPE_MAP 81 | .iter() 82 | .map(|(a, b)| (a.to_string(), b.to_string())) 83 | .collect(); 84 | 85 | let ba_map = OCAML_BA_TYPE_MAP 86 | .iter() 87 | .map(|(a, (b, c))| (a.to_string(), (b.to_string(), c.to_string()))) 88 | .collect(); 89 | 90 | let ctypes_map = OCAML_CTYPES_MAP 91 | .iter() 92 | .map(|(a, b)| (a.to_string(), b.to_string())) 93 | .collect(); 94 | 95 | let mli_path = config.output_path.with_extension("mli"); 96 | let mli_file = std::fs::File::create(mli_path)?; 97 | Ok(OCaml { 98 | typemap, 99 | ba_map, 100 | ctypes_map, 101 | mli_file, 102 | }) 103 | } 104 | 105 | fn foreign_function(&mut self, name: &str, ret: &str, args: Vec<&str>) -> String { 106 | format!( 107 | "let {name} = fn \"{name}\" ({} @-> returning ({ret}))", 108 | args.join(" @-> ") 109 | ) 110 | } 111 | 112 | fn get_ctype(&self, t: &str) -> String { 113 | let x = self 114 | .ctypes_map 115 | .get(t) 116 | .cloned() 117 | .unwrap_or_else(|| t.to_string()); 118 | if x.is_empty() { 119 | panic!("Unsupported type: {t}"); 120 | } 121 | x 122 | } 123 | 124 | fn get_type(&self, t: &str) -> String { 125 | let x = self 126 | .typemap 127 | .get(t) 128 | .cloned() 129 | .unwrap_or_else(|| t.to_string()); 130 | if x.is_empty() { 131 | panic!("Unsupported type: {t}"); 132 | } 133 | x 134 | } 135 | 136 | fn get_ba_type(&self, t: &str) -> (String, String) { 137 | let x = self.ba_map.get(t).cloned().unwrap(); 138 | if x.0.is_empty() { 139 | panic!("Unsupported type: {t}"); 140 | } 141 | x 142 | } 143 | } 144 | 145 | impl Generate for OCaml { 146 | fn bindings(&mut self, pkg: &Package, config: &mut Config) -> Result<(), Error> { 147 | writeln!(self.mli_file, "(* Generated by futhark-bindgen *)\n")?; 148 | writeln!(config.output_file, "(* Generated by futhark-bindgen *)\n")?; 149 | 150 | let mut generated_foreign_functions = Vec::new(); 151 | match pkg.manifest.backend { 152 | Backend::Multicore => { 153 | generated_foreign_functions.push(format!( 154 | " {}", 155 | self.foreign_function( 156 | "futhark_context_config_set_num_threads", 157 | "void", 158 | vec!["context_config", "int"] 159 | ) 160 | )); 161 | } 162 | Backend::CUDA | Backend::OpenCL => { 163 | generated_foreign_functions.push(format!( 164 | " {}", 165 | self.foreign_function( 166 | "futhark_context_config_set_device", 167 | "void", 168 | vec!["context_config", "string"] 169 | ) 170 | )); 171 | } 172 | _ => (), 173 | } 174 | 175 | for (name, ty) in &pkg.manifest.types { 176 | match ty { 177 | manifest::Type::Array(a) => { 178 | let elemtype = a.elemtype.to_str().to_string(); 179 | let ctypes_elemtype = self.get_ctype(&elemtype); 180 | let rank = a.rank; 181 | let ocaml_name = format!("array_{elemtype}_{rank}d"); 182 | self.typemap.insert(name.clone(), ocaml_name.clone()); 183 | self.ctypes_map.insert(name.clone(), ocaml_name.clone()); 184 | let elem_ptr = format!("ptr {ctypes_elemtype}"); 185 | generated_foreign_functions.push(format!( 186 | " let {ocaml_name} = typedef (ptr void) \"{ocaml_name}\"" 187 | )); 188 | let mut new_args = vec!["context", &elem_ptr]; 189 | new_args.resize(rank as usize + 2, "int64_t"); 190 | generated_foreign_functions.push(format!( 191 | " {}", 192 | self.foreign_function(&a.ops.new, &ocaml_name, new_args) 193 | )); 194 | generated_foreign_functions.push(format!( 195 | " {}", 196 | self.foreign_function( 197 | &a.ops.values, 198 | "int", 199 | vec!["context", &ocaml_name, &elem_ptr] 200 | ) 201 | )); 202 | generated_foreign_functions.push(format!( 203 | " {}", 204 | self.foreign_function(&a.ops.free, "int", vec!["context", &ocaml_name]) 205 | )); 206 | generated_foreign_functions.push(format!( 207 | " {}", 208 | self.foreign_function( 209 | &a.ops.shape, 210 | "ptr int64_t", 211 | vec!["context", &ocaml_name] 212 | ) 213 | )); 214 | } 215 | manifest::Type::Opaque(ty) => { 216 | let futhark_name = convert_struct_name(&ty.ctype); 217 | let mut ocaml_name = futhark_name 218 | .strip_prefix("futhark_opaque_") 219 | .unwrap() 220 | .to_string(); 221 | if ocaml_name.chars().next().unwrap().is_numeric() || name.contains(' ') { 222 | ocaml_name = format!("type_{ocaml_name}"); 223 | } 224 | 225 | self.typemap 226 | .insert(name.clone(), format!("{}.t", first_uppercase(&ocaml_name))); 227 | self.ctypes_map.insert(name.to_string(), ocaml_name.clone()); 228 | generated_foreign_functions.push(format!( 229 | " let {ocaml_name} = typedef (ptr void) \"{futhark_name}\"" 230 | )); 231 | 232 | let free_fn = &ty.ops.free; 233 | generated_foreign_functions.push(format!( 234 | " {}", 235 | self.foreign_function(free_fn, "int", vec!["context", &ocaml_name]) 236 | )); 237 | 238 | let record = match &ty.record { 239 | Some(r) => r, 240 | None => continue, 241 | }; 242 | 243 | let new_fn = &record.new; 244 | let mut args = vec!["context".to_string(), format!("ptr {ocaml_name}")]; 245 | for f in record.fields.iter() { 246 | let cty = self 247 | .ctypes_map 248 | .get(&f.r#type) 249 | .cloned() 250 | .unwrap_or_else(|| f.r#type.clone()); 251 | 252 | // project function 253 | generated_foreign_functions.push(format!( 254 | " {}", 255 | self.foreign_function( 256 | &f.project, 257 | "int", 258 | vec!["context", &format!("ptr {cty}"), &ocaml_name] 259 | ) 260 | )); 261 | 262 | args.push(cty); 263 | } 264 | let args = args.iter().map(|x| x.as_str()).collect(); 265 | generated_foreign_functions 266 | .push(format!(" {}", self.foreign_function(new_fn, "int", args))); 267 | } 268 | } 269 | } 270 | 271 | for entry in pkg.manifest.entry_points.values() { 272 | let mut args = vec!["context".to_string()]; 273 | 274 | for out in &entry.outputs { 275 | let t = self.get_ctype(&out.r#type); 276 | 277 | args.push(format!("ptr {t}")); 278 | } 279 | 280 | for input in &entry.inputs { 281 | let t = self.get_ctype(&input.r#type); 282 | args.push(t); 283 | } 284 | 285 | let args = args.iter().map(|x| x.as_str()).collect(); 286 | generated_foreign_functions.push(format!( 287 | " {}", 288 | self.foreign_function(&entry.cfun, "int", args) 289 | )); 290 | } 291 | 292 | let generated_foreign_functions = generated_foreign_functions.join("\n"); 293 | 294 | writeln!( 295 | config.output_file, 296 | include_str!("templates/ocaml/bindings.ml"), 297 | generated_foreign_functions = generated_foreign_functions 298 | )?; 299 | 300 | writeln!(self.mli_file, include_str!("templates/ocaml/bindings.mli"))?; 301 | 302 | let (extra_param, extra_line, extra_mli) = match pkg.manifest.backend { 303 | Backend::Multicore => ( 304 | "?(num_threads = 0)", 305 | " Bindings.futhark_context_config_set_num_threads config num_threads;", 306 | "?num_threads:int ->", 307 | ), 308 | 309 | Backend::CUDA | Backend::OpenCL => ( 310 | "?device", 311 | " Option.iter (Bindings.futhark_context_config_set_device config) device;", 312 | "?device:string ->", 313 | ), 314 | _ => ("", "", ""), 315 | }; 316 | 317 | writeln!( 318 | config.output_file, 319 | include_str!("templates/ocaml/context.ml"), 320 | extra_param = extra_param, 321 | extra_line = extra_line 322 | )?; 323 | writeln!( 324 | self.mli_file, 325 | include_str!("templates/ocaml/context.mli"), 326 | extra_mli = extra_mli 327 | )?; 328 | 329 | Ok(()) 330 | } 331 | 332 | fn array_type( 333 | &mut self, 334 | _pkg: &Package, 335 | config: &mut Config, 336 | name: &str, 337 | ty: &manifest::ArrayType, 338 | ) -> Result<(), Error> { 339 | let rank = ty.rank; 340 | let elemtype = ty.elemtype.to_str().to_string(); 341 | let ocaml_name = self.typemap.get(name).unwrap(); 342 | let module_name = first_uppercase(ocaml_name); 343 | let mut dim_args = Vec::new(); 344 | for i in 0..rank { 345 | dim_args.push(format!("(Int64.of_int dims.({i}))")); 346 | } 347 | 348 | let (ocaml_elemtype, ba_elemtype) = self.get_ba_type(&elemtype); 349 | let ocaml_ctype = self.get_ctype(&elemtype); 350 | 351 | writeln!( 352 | config.output_file, 353 | include_str!("templates/ocaml/array.ml"), 354 | module_name = module_name, 355 | elemtype = elemtype, 356 | rank = rank, 357 | dim_args = dim_args.join(" "), 358 | ocaml_elemtype = ocaml_elemtype, 359 | ba_elemtype = ba_elemtype, 360 | ba_kind = ba_kind(&ba_elemtype), 361 | ocaml_ctype = ocaml_ctype, 362 | )?; 363 | 364 | writeln!( 365 | self.mli_file, 366 | include_str!("templates/ocaml/array.mli"), 367 | module_name = module_name, 368 | ocaml_elemtype = ocaml_elemtype, 369 | ba_elemtype = ba_elemtype, 370 | )?; 371 | 372 | Ok(()) 373 | } 374 | 375 | fn opaque_type( 376 | &mut self, 377 | _pkg: &Package, 378 | config: &mut Config, 379 | name: &str, 380 | ty: &manifest::OpaqueType, 381 | ) -> Result<(), Error> { 382 | let futhark_name = convert_struct_name(&ty.ctype); 383 | let mut ocaml_name = futhark_name 384 | .strip_prefix("futhark_opaque_") 385 | .unwrap() 386 | .to_string(); 387 | if ocaml_name.chars().next().unwrap().is_numeric() || name.contains(' ') { 388 | ocaml_name = format!("type_{ocaml_name}"); 389 | } 390 | let module_name = first_uppercase(&ocaml_name); 391 | self.typemap 392 | .insert(ocaml_name.clone(), format!("{module_name}.t")); 393 | 394 | let free_fn = &ty.ops.free; 395 | 396 | writeln!(config.output_file, "module {module_name} = struct")?; 397 | writeln!(self.mli_file, "module {module_name} : sig")?; 398 | 399 | writeln!( 400 | config.output_file, 401 | include_str!("templates/ocaml/opaque.ml"), 402 | free_fn = free_fn, 403 | name = ocaml_name, 404 | )?; 405 | writeln!(self.mli_file, include_str!("templates/ocaml/opaque.mli"),)?; 406 | 407 | let record = match &ty.record { 408 | Some(r) => r, 409 | None => { 410 | writeln!(config.output_file, "end")?; 411 | writeln!(self.mli_file, "end")?; 412 | return Ok(()); 413 | } 414 | }; 415 | 416 | let mut new_params = Vec::new(); 417 | let mut new_call_args = Vec::new(); 418 | let mut new_arg_types = Vec::new(); 419 | for f in record.fields.iter() { 420 | let t = self.get_type(&f.r#type); 421 | 422 | new_params.push(format!("field{}", f.name)); 423 | 424 | if type_is_array(&t) { 425 | new_call_args.push(format!("(get_ptr field{})", f.name)); 426 | new_arg_types.push(format!("{}.t", first_uppercase(&t))); 427 | } else if type_is_opaque(&t) { 428 | new_call_args.push(format!("(get_opaque_ptr field{})", f.name)); 429 | new_arg_types.push(t.to_string()); 430 | } else { 431 | new_call_args.push(format!("field{}", f.name)); 432 | new_arg_types.push(t.to_string()); 433 | } 434 | } 435 | 436 | writeln!( 437 | config.output_file, 438 | include_str!("templates/ocaml/record.ml"), 439 | new_params = new_params.join(" "), 440 | new_fn = record.new, 441 | new_call_args = new_call_args.join(" "), 442 | )?; 443 | 444 | writeln!( 445 | self.mli_file, 446 | include_str!("templates/ocaml/record.mli"), 447 | new_arg_types = new_arg_types.join(" -> ") 448 | )?; 449 | 450 | for f in record.fields.iter() { 451 | let t = self.get_type(&f.r#type); 452 | let name = &f.name; 453 | let project = &f.project; 454 | 455 | let (out, out_type) = if type_is_opaque(&t) { 456 | let call = t.replace(".t", ".of_ptr"); 457 | (format!("{call} t.opaque_ctx !@out"), t.to_string()) 458 | } else if type_is_array(&t) { 459 | let array = first_uppercase(&t); 460 | ( 461 | format!("{array}.of_ptr t.opaque_ctx !@out"), 462 | format!("{}.t", first_uppercase(&t)), 463 | ) 464 | } else { 465 | ("!@out".to_string(), t.to_string()) 466 | }; 467 | 468 | let alloc_type = if type_is_array(&t) { 469 | format!("Bindings.{t}") 470 | } else if type_is_opaque(&t) { 471 | t 472 | } else { 473 | self.get_ctype(&f.r#type) 474 | }; 475 | 476 | writeln!( 477 | config.output_file, 478 | include_str!("templates/ocaml/record_project.ml"), 479 | name = name, 480 | s = alloc_type, 481 | project = project, 482 | out = out 483 | )?; 484 | writeln!( 485 | self.mli_file, 486 | include_str!("templates/ocaml/record_project.mli"), 487 | name = name, 488 | out_type = out_type 489 | )?; 490 | } 491 | 492 | writeln!(config.output_file, "end\n")?; 493 | writeln!(self.mli_file, "end\n")?; 494 | 495 | Ok(()) 496 | } 497 | 498 | fn entry( 499 | &mut self, 500 | _pkg: &Package, 501 | config: &mut Config, 502 | name: &str, 503 | entry: &manifest::Entry, 504 | ) -> Result<(), Error> { 505 | let mut arg_types = Vec::new(); 506 | let mut return_type = Vec::new(); 507 | let mut entry_params = Vec::new(); 508 | let mut call_args = Vec::new(); 509 | let mut out_return = Vec::new(); 510 | let mut out_decl = Vec::new(); 511 | 512 | for (i, out) in entry.outputs.iter().enumerate() { 513 | let t = self.get_type(&out.r#type); 514 | let ct = self.get_ctype(&out.r#type); 515 | 516 | let mut ocaml_elemtype = t.clone(); 517 | 518 | // Transform into `Module.t` 519 | if ocaml_elemtype.contains("array_") { 520 | ocaml_elemtype = first_uppercase(&ocaml_elemtype) + ".t" 521 | } 522 | 523 | return_type.push(ocaml_elemtype); 524 | 525 | let i = if entry.outputs.len() == 1 { 526 | String::new() 527 | } else { 528 | i.to_string() 529 | }; 530 | 531 | if type_is_array(&t) || type_is_opaque(&t) { 532 | out_decl.push(format!(" let out{i}_ptr = allocate (ptr void) null in")); 533 | } else { 534 | out_decl.push(format!(" let out{i}_ptr = allocate_n {ct} ~count:1 in")); 535 | } 536 | 537 | call_args.push(format!("out{i}_ptr")); 538 | 539 | if type_is_array(&t) { 540 | let m = first_uppercase(&t); 541 | out_return.push(format!("({m}.of_ptr ctx !@out{i}_ptr)")); 542 | } else if type_is_opaque(&t) { 543 | let m = first_uppercase(&t); 544 | let m = m.strip_suffix(".t").unwrap_or(&m); 545 | out_return.push(format!("({m}.of_ptr ctx !@out{i}_ptr)")); 546 | } else { 547 | out_return.push(format!("!@out{i}_ptr")); 548 | } 549 | } 550 | 551 | for (i, input) in entry.inputs.iter().enumerate() { 552 | entry_params.push(format!("input{i}")); 553 | 554 | let mut ocaml_elemtype = self.get_type(&input.r#type); 555 | 556 | // Transform into `Module.t` 557 | if type_is_array(&ocaml_elemtype) { 558 | ocaml_elemtype = first_uppercase(&ocaml_elemtype) + ".t" 559 | } 560 | 561 | arg_types.push(ocaml_elemtype); 562 | 563 | let t = self.get_type(&input.r#type); 564 | if type_is_array(&t) { 565 | call_args.push(format!("(get_ptr input{i})")); 566 | } else if type_is_opaque(&t) { 567 | call_args.push(format!("(get_opaque_ptr input{i})")); 568 | } else { 569 | call_args.push(format!("input{i}")); 570 | } 571 | } 572 | 573 | writeln!( 574 | config.output_file, 575 | include_str!("templates/ocaml/entry.ml"), 576 | name = name, 577 | entry_params = entry_params.join(" "), 578 | out_decl = out_decl.join("\n"), 579 | call_args = call_args.join(" "), 580 | out_return = out_return.join(", ") 581 | )?; 582 | 583 | let return_type = if return_type.is_empty() { 584 | "unit".to_string() 585 | } else { 586 | return_type.join(" * ") 587 | }; 588 | writeln!( 589 | self.mli_file, 590 | include_str!("templates/ocaml/entry.mli"), 591 | name = name, 592 | arg_types = arg_types.join(" -> "), 593 | return_type = return_type, 594 | )?; 595 | 596 | Ok(()) 597 | } 598 | } 599 | --------------------------------------------------------------------------------