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

Implement Support For repr(int) non-C-like enums #78

Closed
Gankra opened this issue Nov 1, 2017 · 6 comments · Fixed by #118
Closed

Implement Support For repr(int) non-C-like enums #78

Gankra opened this issue Nov 1, 2017 · 6 comments · Fixed by #118

Comments

@Gankra
Copy link
Contributor

Gankra commented Nov 1, 2017

It turns out repr(u32, i8, etc...)on non-C-like enums has had an unofficially specified and C(++)-compatible layout for a good while. I have filed an RFC to make it officially specified, but in the mean time we can set up cbindgen to generate this layout, which will let us use native Rust tagged unions in our bindings, and with today's stable Rust!

You can check out the RFC for details (it's not quite the obvious layout), but here's a test Rust program to prove that it works:

works_for_me.rs
use std::time::Duration;
use std::mem;

#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum MyEnum {
    A(u32),
    B { x: u8, y: i16 },
    C,
    D(Option<u32>),
    E(Duration),
}

#[allow(non_snake_case)]
#[repr(C)]
union MyEnumRepr {
    A: MyEnumVariantA,
    B: MyEnumVariantB,
    C: MyEnumVariantC,
    D: MyEnumVariantD,
    E: MyEnumVariantE,
}

#[repr(u8)] #[derive(Copy, Clone)] enum MyEnumTag { A, B, C, D, E }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantA(MyEnumTag, u32);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantB { tag: MyEnumTag, x: u8, y: i16 }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantC(MyEnumTag);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantD(MyEnumTag, Option<u32>);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantE(MyEnumTag, Duration);

fn main() {
    let result: Vec<Result<MyEnum, ()>> = vec![
        Ok(MyEnum::A(17)),
        Ok(MyEnum::B { x: 206, y: 1145 }),
        Ok(MyEnum::C),
        Err(()),
        Ok(MyEnum::D(Some(407))),
        Ok(MyEnum::D(None)),
        Ok(MyEnum::E(Duration::from_secs(100))),
        Err(()),
    ];
        
    let input: Vec<u8> = vec![
        0,  17, 0, 0, 0,
        1,  206,  121, 4,
        2,
        8,
        3,  0,  151, 1, 0, 0,
        3,  1,
        4,  100, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
        0,
    ];
    
    let mut output = vec![];
    let mut buf = &input[..];
    
    unsafe {
        let mut dest: MyEnum = mem::uninitialized();
        while buf.len() > 0 {
            match parse_my_enum(&mut dest, &mut buf) {
                Ok(()) => output.push(Ok(dest)),
                Err(()) => output.push(Err(())),
            }
        }
    }
    
    assert_eq!(output, result);
}

#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum MyEnum {
    A(u32),
    B { x: u8, y: i16 },
    C,
    D(Option<u32>),
    E(Duration),
}

fn parse_my_enum<'a>(dest: &'a mut MyEnum, buf: &mut &[u8]) -> Result<(), ()> {
    unsafe {
        let dest: &'a mut MyEnumRepr = mem::transmute(dest);
        let tag = read_u8(buf)?;
        
        dest.A.0 = match tag {
            0 => MyEnumTag::A,
            1 => MyEnumTag::B,
            2 => MyEnumTag::C,
            3 => MyEnumTag::D,
            4 => MyEnumTag::E,
            _ => return Err(()),
        };
                
        match dest.B.tag {
            MyEnumTag::A => {
                dest.A.1 = read_u32_le(buf)?;
            }
            MyEnumTag::B => {
                dest.B.x = read_u8(buf)?;
                dest.B.y = read_u16_le(buf)? as i16;
            }
            MyEnumTag::C => {
                /* do nothing */
            }
            MyEnumTag::D => {
                let is_some = read_u8(buf)? == 0;
                if is_some {
                    dest.D.1 = Some(read_u32_le(buf)?);
                } else {
                    dest.D.1 = None;
                }
            }
            MyEnumTag::E => {
                let secs = read_u64_le(buf)?;
                let nanos = read_u32_le(buf)?;
                dest.E.1 = Duration::new(secs, nanos);
            }
        }
        Ok(())
    }
}

fn read_u64_le(buf: &mut &[u8]) -> Result<u64, ()> {
    if buf.len() < 8 { return Err(()) }
    let val = (buf[0] as u64) << 0 
            | (buf[1] as u64) << 8
            | (buf[2] as u64) << 16
            | (buf[3] as u64) << 24
            | (buf[4] as u64) << 32
            | (buf[5] as u64) << 40
            | (buf[6] as u64) << 48
            | (buf[7] as u64) << 56;
    *buf = &buf[8..];
    Ok(val)
}

fn read_u32_le(buf: &mut &[u8]) -> Result<u32, ()> {
    if buf.len() < 4 { return Err(()) }
    let val = (buf[0] as u32) << 0 
            | (buf[1] as u32) << 8
            | (buf[2] as u32) << 16
            | (buf[3] as u32) << 24;
    *buf = &buf[4..];
    Ok(val)
}

fn read_u16_le(buf: &mut &[u8]) -> Result<u16, ()> {
    if buf.len() < 2 { return Err(()) }
    let val = (buf[0] as u16) << 0 
            | (buf[1] as u16) << 8;
    *buf = &buf[2..];
    Ok(val)
}

fn read_u8(buf: &mut &[u8]) -> Result<u8, ()> {
    if buf.len() < 1 { return Err(()) }
    let val = buf[0];
    *buf = &buf[1..];
    Ok(val)
}
@Gankra
Copy link
Contributor Author

Gankra commented Nov 1, 2017

CC @jrmuizel @mstange

@eqrion
Copy link
Collaborator

eqrion commented Nov 1, 2017

Cool, this sounds really useful. The cbindgen work shouldn't be too difficult, just adding a new item and sharing some code with structs.

@RReverser
Copy link
Contributor

Just so that we don't duplicate the work - I'm trying to implement this now.

@Gankra
Copy link
Contributor Author

Gankra commented Nov 20, 2017

Relevant: rust-lang/rust#46123

@Gankra
Copy link
Contributor Author

Gankra commented Nov 28, 2017

Above PR was merged, so now repr(C, int) and repr(C) should work as spec'd in the next nightly.

@RReverser
Copy link
Contributor

I was stalled on refactoring generics in #85 (which affects tagged enums too), but happy to PR current state of things soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants