Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support declarative macros inside cxx::bridge #274

Closed
sbrocket opened this issue Sep 1, 2020 · 5 comments
Closed

Support declarative macros inside cxx::bridge #274

sbrocket opened this issue Sep 1, 2020 · 5 comments

Comments

@sbrocket
Copy link
Contributor

sbrocket commented Sep 1, 2020

It would be useful to be able to use declarative macros inside the bridge. In my use case, I have a large number of formulaic types and methods to declare in the bridge. (The blocks below are untested but should convey the general idea.)

macro_rules! declare_ffi_handle_type {
    ($t:ident) => {
        struct $t {
            raw: u32,
        };

        extern "C" {
            paste! {
                type [<Cxx $t>];
                type [<Unowned $t>];

                fn [<get_handle_ $t:lower>](handle: &[<Cxx $t>]>) -> u32;
                fn [<get_unowned_handle_ $t:lower>](handle: &[<Unowned $t>]) -> u32;
            }
        }
    };
}

I have 30 such blocks to define, and was actually going to use another declarative macro to invoke the first macro, e.g.:

macro_rules! invoke_for_handle_types {
    ($m:ident) => {
        invoke_for_handle_types! { $m, Handle, Process, Thread, Vmo, Channel, Event, Port, Interrupt, PciDevice, DebugLog, Socket, Resource, EventPair, Job, Vmar, Fifo, Guest, Vcpu, Timer, Iommu, Bti, Profile, Pmt, SuspendToken, Pager, Exception, Clock, Stream, MsiAllocation, MsiInterrupt };
    };
    ($m:ident, $t:ident, $($ts:ident),+) => {
        invoke_for_handle_types! { $m, $t }
        invoke_for_handle_types! { $m, $(ts),+ }
    };
    ($m:ident, $t:ident) => { $m! {$t} };

Obviously this would be/is going to be very tedious to expand manually, never mind that it will be much less maintainable that way.

@sbrocket
Copy link
Contributor Author

sbrocket commented Sep 1, 2020

There may be some (potentially convoluted) workaround here which I'm currently attempting to figure out, so if you have any such ideas I'd be very happy to hear them.

@dtolnay
Copy link
Owner

dtolnay commented Sep 1, 2020

The easiest workaround would be to codegen them from build.rs.

// build.rs

use std::env;
use std::fs;
use std::path::Path;

static HANDLE_TYPES: &[&str] = &[
    "Handle", "Process", "Thread", "Vmo", "Channel", "Event", "Port",
    "Interrupt", "PciDevice", "DebugLog", "Socket", "Resource", "EventPair",
    "Job", "Vmar", "Fifo", "Guest", "Vcpu", "Timer", "Iommu", "Bti", "Profile",
    "Pmt", "SuspendToken", "Pager", "Exception", "Clock", "Stream",
    "MsiAllocation", "MsiInterrupt",
];

fn main() {
    let mut bridge = String::new();
    bridge += "#[cxx::bridge]\n";
    bridge += "pub mod ffi {\n";

    for handle_type in HANDLE_TYPES {
        bridge += "struct ";
        bridge += handle_type;
        bridge += " { raw: u32 }\n";
    }

    bridge += "extern \"C\" {\n";

    for handle_type in HANDLE_TYPES {
        bridge += "type Cxx";
        bridge += handle_type;
        bridge += ";\n";

        bridge += "type Unowned";
        bridge += handle_type;
        bridge += ";\n";

        bridge += "fn get_handle_";
        bridge += &handle_type.to_lowercase();
        bridge += "(handle: &Cxx";
        bridge += handle_type;
        bridge += ") -> u32;\n";

        bridge += "fn get_unowned_handle_";
        bridge += &handle_type.to_lowercase();
        bridge += "(handle: &Unowned";
        bridge += handle_type;
        bridge += ") -> u32;\n";
    }

    bridge += "}\n"; // extern "C"
    bridge += "}\n"; // mod ffi

    let bridge = bridge.as_bytes();
    let out_dir = env::var("OUT_DIR").unwrap();
    let ref path = Path::new(&out_dir).join("bridge.rs");
    fs::write(path, bridge).unwrap();

    cxx_build::bridge(path).compile("sbrocket");
}
// src/lib.rs

include!(concat!(env!("OUT_DIR"), "/bridge.rs"));

@sbrocket
Copy link
Contributor Author

sbrocket commented Sep 1, 2020

Our project doesn't support build.rs, but sure, that's a good idea, I can generate the bridge module separately.

@dtolnay
Copy link
Owner

dtolnay commented Sep 1, 2020

Ok! I'll close the issue since I don't think this is possible to implement otherwise on our end.

@sbrocket
Copy link
Contributor Author

sbrocket commented Sep 18, 2020

If it's fine with you, can we keep this open as a "blocked feature request"? I realize this isn't possible today - not unless we were to do some crazy things like manually expand the macros similar to how your cargo expand command does, but inside the proc-macro - but there are open RFCs (that admittedly have been open for years) that would enable this, specifically rust-lang/rfcs#2320.

That "crazy idea" is actually kind of neat the more I think about it, even though it's a hack around proper support for this like that RFC proposes. Inside the bridge proc_macro, we could first send the token stream through rustc again (a la cargo expand) but with the proc_macro disabled through a cfg! option to prevent further recursion, where disabled means the macro would emit the token stream unmodified. Having a proc_macro invoke the compiler is pretty darn hacky though, and it would probably be difficult to allow for non-Cargo build system integration.

Repository owner locked and limited conversation to collaborators Aug 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants