Compare commits

..

12 Commits

Author SHA1 Message Date
George Thomas
352bf8c286 basic enum example 2026-03-30 13:07:41 +01:00
George Thomas
091c70815c wip file dependency reload stuff 2026-03-30 13:07:41 +01:00
George Thomas
111b65c708 format 2026-03-30 13:07:16 +01:00
George Thomas
a06f3076c7 clippy fixes 2026-03-30 13:07:16 +01:00
George Thomas
7d9982112a maybe/optional example 2026-03-30 13:07:16 +01:00
George Thomas
31ae16784a minor refactor for consistency (maybe we should go the other way given sumSlice...) 2026-03-30 13:07:16 +01:00
George Thomas
b788d6685d vector/slice example 2026-03-30 13:07:16 +01:00
George Thomas
63efd03382 pass by (immutable) reference for all non-primitive types 2026-03-30 13:07:16 +01:00
George Thomas
bf0fc90272 dedup base dep 2026-03-30 13:07:16 +01:00
George Thomas
6028db040d Simplify build script
Seeing as we now no longer need to modify generated header files.
2026-03-30 13:07:16 +01:00
George Thomas
7ca469a62d use upstream fix to avoid header patching hack...
bump - no tag for libclang and alpha1 leads to build errors

drop dep

argh, unrelated breaking change, and bad names

exts
2026-03-30 13:07:16 +01:00
George Thomas
b5fd53b5ec stuff from Yuri (maybe squash?)...
things I've changed from the PR:
- dropped legacy (i.e. non-flake) compat stuff, which turns out to account for most of the diff
- dropped `packages.garnet` since it doesn't work with `nix build .#garnet`
- back to using Rust-extended packages everywhere, which _seems_ fine
the rest is just re-inlining things and other refactors

note that changes we keep are essentially:
- not using Crane's extended packages everywhere
- bumping `nix-haskell` to avoid shell hook workaround
- various changes in how we call `nix-haskell`
- using `libCgarnet_rs` name, which Cabal expects
- adding proper non-dev-shell targets, so that e.g. `nix run` works

stuff we should still take a look at:
- seems a bit weird that `garnet-rs` arg to `project.nix` was always same (and therefore now we always pass the ) - might be a mistake, and we're supposed to be using local for local build?
- similar `libCgarnet_rs.a` logic appears in three places - see if we can deduplicate to some extent somehow
- I haven't even checked all steps are necessary
2026-03-30 12:08:05 +01:00
8 changed files with 76 additions and 18 deletions

View File

@ -9,6 +9,8 @@ main = do
hello "Haskell" hello "Haskell"
helloStruct T{a = True, b = 42} helloStruct T{a = True, b = 42}
helloStruct T{a = False, b = maxBound} helloStruct T{a = False, b = maxBound}
helloEnum E1
helloEnum E3
helloShape $ Circle 3.14 helloShape $ Circle 3.14
helloShape $ Rectangle 10.0 5.0 helloShape $ Rectangle 10.0 5.0
putStrLn $ "3 + 4 = " <> show (add 3 4) putStrLn $ "3 + 4 = " <> show (add 3 4)

13
flake.lock generated
View File

@ -527,15 +527,16 @@
"hs-bindgen-src": { "hs-bindgen-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1774379170, "lastModified": 1774599157,
"narHash": "sha256-lZV6IdCBf8uCt21qB5mfgLaP9CNgho/HsqjvkulDR2Q=", "narHash": "sha256-jgV67xhWzxMwyiyy5RPtu+VQvGTt+FoMXCWJcZ7lczY=",
"owner": "well-typed", "owner": "well-typed",
"repo": "hs-bindgen", "repo": "hs-bindgen",
"rev": "4b6febb5cc6196835bd2890a3ab27a88dab1806b", "rev": "3c4af10590d0d09e825a9735e9a03d7f60914e21",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "well-typed", "owner": "well-typed",
"ref": "release-0.1-alpha2",
"repo": "hs-bindgen", "repo": "hs-bindgen",
"type": "github" "type": "github"
} }
@ -560,11 +561,11 @@
"libclang-src": { "libclang-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1774018529, "lastModified": 1774600891,
"narHash": "sha256-Bo5wvityXKCmlnrobtI9WkA1maR7sfkDXo1VOZvrPLk=", "narHash": "sha256-LTAyNMY4Vu0vPeEq2wXB0KWY4kGtqtHTRmADjLdkv78=",
"owner": "well-typed", "owner": "well-typed",
"repo": "libclang", "repo": "libclang",
"rev": "83387d72a8dfae9f75d27db6b32ea37afab06268", "rev": "1054474fae403bfb52c7919680cac03d3d3d4237",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -3,7 +3,7 @@
nix-haskell.url = "github:reflex-frp/nix-haskell"; nix-haskell.url = "github:reflex-frp/nix-haskell";
nixpkgs.follows = "nix-haskell/nixpkgs"; nixpkgs.follows = "nix-haskell/nixpkgs";
hls-src = { url = "github:haskell/haskell-language-server/2.13.0.0"; flake = false; }; hls-src = { url = "github:haskell/haskell-language-server/2.13.0.0"; flake = false; };
hs-bindgen-src = { url = "github:well-typed/hs-bindgen"; flake = false; }; hs-bindgen-src = { url = "github:well-typed/hs-bindgen/release-0.1-alpha2"; flake = false; };
libclang-src = { url = "github:well-typed/libclang"; flake = false; }; libclang-src = { url = "github:well-typed/libclang"; flake = false; };
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
crane.url = "github:ipetkov/crane"; crane.url = "github:ipetkov/crane";

View File

@ -6,6 +6,13 @@ author: Patrick Aldis
maintainer: maintainer:
george.thomas@obsidian.systems george.thomas@obsidian.systems
patrick.aldis@obsidian.systems patrick.aldis@obsidian.systems
-- aha, nice, this does fix recompilation checking
extra-source-files:
rust/target/debug/garnet_rs.h
-- that could be problematic given file is gitignored? unfortunately this doesn't work
-- tbf, I haven't even looked up the docs, just saw the autocompletion
-- extra-tmp-files:
-- rust/target/debug/garnet_rs.h
common common common common
default-language: GHC2024 default-language: GHC2024
@ -43,11 +50,13 @@ library
GarnetRs.Wrapped GarnetRs.Wrapped
hs-source-dirs: lib hs-source-dirs: lib
include-dirs: rust/target/debug include-dirs: rust/target/debug
-- HLS gives up entirely when the header is malformed if we do this
-- and anyway, I don't think it gives us dependency tracking like `extra-source-files` does
-- includes: garnet_rs.h
extra-bundled-libraries: Cgarnet_rs extra-bundled-libraries: Cgarnet_rs
build-depends: build-depends:
hs-bindgen, hs-bindgen,
hs-bindgen-runtime, hs-bindgen-runtime,
primitive,
template-haskell, template-haskell,
executable garnet executable garnet

View File

@ -1,6 +1,5 @@
{-# LANGUAGE CApiFFI #-} {-# LANGUAGE CApiFFI #-}
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FieldSelectors #-}
{-# LANGUAGE MagicHash #-} {-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
@ -30,7 +29,15 @@ import HsBindgen.TH
import Language.Haskell.TH import Language.Haskell.TH
import System.Process import System.Process
import Control.Monad.IO.Class (MonadIO (liftIO))
import Language.Haskell.TH.Syntax (addDependentFile)
import System.Directory.Extra (getCurrentDirectory)
do do
-- not sure this does anything - hs-bindgen should already be doing the tracking, and the issues are from elsewhere
-- dir <- liftIO getCurrentDirectory
-- liftIO $ print dir
-- addDependentFile $ dir <> "/rust/target/debug/garnet_rs.h"
systemDirs <- -- TODO bit of a hack systemDirs <- -- TODO bit of a hack
map (Dir . T.unpack . T.strip) map (Dir . T.unpack . T.strip)
. concatMap (takeWhile (maybe False ((== ' ') . fst) . T.uncons) . dropWhile T.null . T.lines) . concatMap (takeWhile (maybe False ((== ' ') . fst) . T.uncons) . dropWhile T.null . T.lines)

View File

@ -1,11 +1,20 @@
{-# LANGUAGE PatternSynonyms #-}
-- TODO automate this sort of high level wrapper boilerplate -- TODO automate this sort of high level wrapper boilerplate
-- or look at upstream plans: https://github.com/well-typed/hs-bindgen/issues?q=state%3Aopen%20label%3A%22highlevel%22 -- or look at upstream plans: https://github.com/well-typed/hs-bindgen/issues?q=state%3Aopen%20label%3A%22highlevel%22
module GarnetRs.Wrapped ( module GarnetRs.Wrapped (
T (..), T (..),
Raw.E (..),
-- TODO hmm, we don't really want to have to list all of these...
-- is there an option to make them not be patterns at all?
pattern Raw.E1,
pattern Raw.E2,
pattern Raw.E3,
Shape (..), Shape (..),
BTree (..), BTree (..),
hello, hello,
helloStruct, helloStruct,
helloEnum,
helloShape, helloShape,
add, add,
sumTree, sumTree,
@ -38,7 +47,6 @@ data Shape
convertShape :: Shape -> Raw.Shape convertShape :: Shape -> Raw.Shape
convertShape = \case convertShape = \case
Circle r -> Raw.Shape Raw.Circle $ Raw.set_shape_circle_circle $ Raw.Circle_Body r Circle r -> Raw.Shape Raw.Circle $ Raw.set_shape_circle_circle $ Raw.Circle_Body r
-- hmm, unintuitive name
Rectangle w h -> Raw.Shape Raw.Rectangle $ Raw.set_shape_circle_rectangle $ Raw.Rectangle_Body w h Rectangle w h -> Raw.Shape Raw.Rectangle $ Raw.set_shape_circle_rectangle $ Raw.Rectangle_Body w h
data BTree a data BTree a
@ -63,6 +71,9 @@ hello = flip useAsCString $ Raw.hello . unsafeFromPtr
helloStruct :: T -> IO () helloStruct :: T -> IO ()
helloStruct = flip with (Raw.hello_struct . unsafeFromPtr) . convertT helloStruct = flip with (Raw.hello_struct . unsafeFromPtr) . convertT
helloEnum :: Raw.E -> IO ()
helloEnum = flip with (Raw.hello_enum . unsafeFromPtr)
helloShape :: Shape -> IO () helloShape :: Shape -> IO ()
helloShape = flip with (Raw.hello_shape . unsafeFromPtr) . convertShape helloShape = flip with (Raw.hello_shape . unsafeFromPtr) . convertShape

View File

@ -2,6 +2,22 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
fn main() { fn main() {
// this doesn't make _much_ difference really, since this is our only Rust source file
// but it seems it's probably better than not having it
// what we really want is to tell Rust to only regenerate the header file if the Rust code actually compiles
// but we don't have that flexibility
// and it's an issue because cbindgen tries to be fault-tolerant in some ways that don't even seem to make sense
//
// e.g. mis-spell "Option" as "Option" and you get
// void print_optional(Optio<const int8_t*> x);
// instead of
// void print_optional(const int8_t *x);
// and that's only an issue because in HLS TH dependent-file watching gives up after an error
// i.e. once the containing splice has thrown an exception once, the containing file needs a manual edit to kick it
// P.S. strings to stdout?! what a terrible API
// don't get me started in the discoverability of actually then using the terminal for debugging:
// println!("cargo::warning={:?}", env::var("OUT_DIR"));
println!("cargo::rerun-if-changed=lib.rs");
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let profile = env::var("PROFILE").unwrap(); let profile = env::var("PROFILE").unwrap();
cbindgen::Builder::new() cbindgen::Builder::new()

View File

@ -11,7 +11,7 @@ fn say_hello(name: &str) {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn hello(c: *const c_char) -> () { extern "C" fn hello(c: *const c_char) {
say_hello(unsafe { CStr::from_ptr(c) }.to_str().unwrap()) say_hello(unsafe { CStr::from_ptr(c) }.to_str().unwrap())
} }
@ -23,10 +23,23 @@ struct T {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn hello_struct(t: &T) -> () { extern "C" fn hello_struct(t: &T) {
say_hello(&format!("{:?}", t)) say_hello(&format!("{:?}", t))
} }
#[repr(C)]
#[derive(Debug)]
enum E {
E1,
E2,
E3,
}
#[unsafe(no_mangle)]
extern "C" fn hello_enum(e: &E) {
say_hello(&format!("{:?}", e))
}
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
enum Shape { enum Shape {
@ -35,7 +48,7 @@ enum Shape {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn hello_shape(s: &Shape) -> () { extern "C" fn hello_shape(s: &Shape) {
say_hello(&format!("{:?}", s)) say_hello(&format!("{:?}", s))
} }
@ -80,7 +93,7 @@ enum BTreeC {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn sum_tree(t: &BTreeC) -> i64 { extern "C" fn sum_tree(t: &BTreeC) -> i64 {
(unsafe { std::mem::transmute::<_, &BTree<i64>>(t) }).sum() (unsafe { std::mem::transmute::<&BTreeC, &BTree<i64>>(t) }).sum()
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
@ -89,9 +102,8 @@ extern "C" fn sum_slice(v: *const i64, s: usize) -> i64 {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn print_optional(x: Option<&i8>) -> () { extern "C" fn print_optional(x: Option<&i8>) {
match x { if let Some(x) = x {
Some(x) => println!("{}", x / 2), println!("{}", x / 2)
None => {}
} }
} }