gnzlbg / ctest

Automatic testing of FFI bindings for Rust

Home Page:https://docs.rs/ctest

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Alignment checks are broken for u64 on i686-unknown-linux-gnu

sfackler opened this issue · comments

Probably a conflation between preferred and minimal alignment? First seen in rust-openssl builds: https://circleci.com/gh/sfackler/rust-openssl/15593

Minimal example:

build.rs

fn main() {
    ctest::TestGenerator::new()
        .header("foo.h")
        .include("src")
        .generate("src/foo.rs", "all.rs");
}

src/foo.h

#define SHA_LONG64 unsigned long long

src/foo.rs

pub type SHA_LONG64 = u64;

src/main.rs

use libc::*;

use crate::foo::*;

mod foo;

include!(concat!(env!("OUT_DIR"), "/all.rs"));
$ cargo run --target i686-unknown-linux-gnu
   Compiling foo v0.1.0 (/home/sfackler/foo)
warning: unused import: `libc::*`
 --> src/main.rs:1:5
  |
1 | use libc::*;
  |     ^^^^^^^
  |
  = note: #[warn(unused_imports)] on by default

warning: type `SHA_LONG64` should have an upper camel case name
 --> src/foo.rs:1:10
  |
1 | pub type SHA_LONG64 = u64;
  |          ^^^^^^^^^^ help: convert the identifier to upper camel case: `ShaLong64`
  |
  = note: #[warn(non_camel_case_types)] on by default

warning: unused macro definition
  --> /home/sfackler/foo/target/i686-unknown-linux-gnu/debug/build/foo-0432c371df727992/out/all.rs:58:13
   |
58 | /             macro_rules! offset_of {
59 | |                 ($ty:ident, $field:ident) => (
60 | |                     (&((*(0 as *const $ty)).$field)) as *const _ as u64
61 | |                 )
62 | |             }
   | |_____________^
   |
   = note: #[warn(unused_macros)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
     Running `target/i686-unknown-linux-gnu/debug/foo`
RUNNING ALL TESTS
bad SHA_LONG64 align: rust: 4 (0x4) != c 8 (0x8)
thread 'main' panicked at 'some tests failed', /home/sfackler/foo/target/i686-unknown-linux-gnu/debug/build/foo-0432c371df727992/out/all.rs:13:21
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

What happens if you add #include<cstdint.h> and use uint64_t in the C side ?

#define SHA_LONG64 uint64_t

or if instead of u64 you use libc::c_ulonglong in the Rust side ?

That test just checks whether mem::align_of and C's alignof return the same value for SHA_LONG64.

That does not appear to be a valid comparison you can make:

$ cat foo.c
#include <stdint.h>
#include <stdio.h>

int main() {
    printf("uint64_t: %u\n", __alignof(uint64_t));
    printf("unsigned long long: %u\n", __alignof(unsigned long long));
}
$ gcc -m32 foo.c
$ ./a.out
uint64_t: 8
unsigned long long: 8
$ cat foo.rs
fn main() {
    println!("u64: {}", std::mem::align_of::<u64>());
}
$ rustc --target i686-unknown-linux-gnu foo.rs
$ ./foo
u64: 4

u64 bit value alignments are weird in 32 bit linux - a struct containing a uin64_t is only 4-byte aligned:

$ cat foo.c
#include <stdint.h>
#include <stdio.h>

struct {
    uint64_t field;
} foo;

int main() {
    printf("unsigned long long: %u\n", __alignof(unsigned long long));
    printf("uint64_t: %u\n", __alignof(uint64_t));
    printf("foo: %u\n", __alignof(foo));
}
$ gcc -m32 foo.c
$ ./a.out
unsigned long long: 8
uint64_t: 8
foo: 4

@sfackler

The layout of u64 is guaranteed to be the same as that of uint64_t. If that's not the case, you should fill a rust-lang/rust bug. The layout of unsigned long long is guaranteed to be the same as that of libc::c_ulonglong. If that's not the case, you should fill a libc bug.

EDIT: where by layout I mean Rust layout: size + alignment + niches + fields + call_abi

The gcc authors do not interpret the intended behavior of __alignof in the same way you do: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54188. There are no scalar types in the i386 System V ABI that require 8 byte alignment: http://www.sco.com/developers/devspecs/abi386-4.pdf.

As noted in the example I posed above, the required alignment of uint64_t cannot possibly be 8, because a struct containing a uint64_t is 4-byte aligned!

Interesting! Which version of gcc are you using ?

AFAICT C's and C++ _Alignof and alignof are required by the ISO standards to return the required alignment of a type, not the recommended one.

GCC 8 returns 4 for the alignment of int64_t (https://gcc.godbolt.org/z/sVUF5N), but GCC 7 returns 8 (https://gcc.godbolt.org/z/-TbHEE). Is there a chance this is a bug in your C compiler implementation?

I'm open to suggestions about how we could fix this.


If this is a bug in the C compiler implementation of _Alignof/alignof, I don't think we can compensate for that, at least not with the current ctest architecture. ctest works at the AST level, without any type information. For this type alias, we just "know" that it's called SHA_LONG64, but we don't know whether that's a struct, an array, a scalar, etc. We just "know" that its size, alignment, ABI, has to match with the one from C, so we generate functions in Rust and C to "compute" that, and then compare the results.

If you have verified this manually, you maybe can do something like this:

cfg.skip_type(|y| {
   match y {
      "SHA_LONG64" if gcc_version < 8 => true,
      _ => false,
   }
}

and manually work around the bug in your build.rs.

Also, as mentioned, if you are interfacing with unsigned long long, you should really use libc::c_ulonglong.

gcc (GCC) 9.1.1 20190503 (Red Hat 9.1.1-1)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ctest doesn't use alignof, it uses __alignof:

ctest/src/lib.rs

Line 1343 in f8c43dc

"__alignof"
, which still returns 8 on modern gccs.