Repository: rust
Issue: Cannot pass small struct by value across FFI on linux-gnu
Labels: O-linux,A-FFI,O-x86_64,T-compiler,C-bug

I am creating an FFI function that takes a tagged union as a parameter. It corresponds to a Rust enum with variant. The tagged union definition for the C++ code was generated with `cbindgen`. The problem is that the calling convention between Rust's `extern "C"` and the actual C calling convention aren't matching up, so it gets faulty values for the argument.

This looks like it's similar to #5744.

I tried this code:

Rust:

```rust
#[repr(C)]
#[no_mangle]
pub enum SLCArgs {
  Add(*const c_char), // borrowed
  Count,
  Print,
}

#[no_mangle]
// works if changed to extern "Rust"
pub unsafe extern "C" fn StringListContainer_do(slc_p : *mut StringListContainer, m : SLCArgs) {
  let slc = &mut *(slc_p);
  match m {
    SLCArgs::Count => println!("{}", slc.Count()),
    SLCArgs::Add(cstr) => StringListContainer_Add(slc_p, cstr),
    SLCArgs::Print => slc.print(),
  }
} 
```

bindings.h:
```cpp
struct StringListContainer;

struct SLCArgs {
  enum class Tag {
    Add,
    Count,
    Print,
  };

  struct Add_Body {
    const char *_0;
  };

  Tag tag;
  union {
    Add_Body add;
  };
};

extern "C" {
void StringListContainer_do(StringListContainer *slc_p, SLCArgs m);
//...
} // extern "C"
```

C++:
```cpp
int main() {
  StringListContainer* slc = new_StringListContainer();

  StringListContainer_do(slc, {SLCArgs::Tag::Add, {"top text"}});
  StringListContainer_do(slc, {SLCArgs::Tag::Add, {"middle text"}});
  StringListContainer_do(slc, {SLCArgs::Tag::Add, {"bottom text"}});
  StringListContainer_do(slc, {SLCArgs::Tag::Count, {}});
  StringListContainer_do(slc, {SLCArgs::Tag::Print, {}});
}
```

I expected to see this happen: 

```
3
top text
middle text
bottom text
```

Instead, this happened: 

It prints nothing out because Rust believes that the struct was placed in the argument build area of the stack, just above the return address. In reality, since this is a small struct, it was placed in the second and third registers `%rsi` `%rdx`. 

[According to this interpretation](https://cs61.seas.harvard.edu/site/2018/Asm2/) of the System V AMD64 ABI, structs between 2-4 words are passed sequentially through registers. Although, according to [some experimentation](https://www.godbolt.org/z/GSRD1g), it looks like any struct over 2 words is also passed on the stack.

Looking at the assembly, the first SLCArgs was initialized on the stack in the argument build area just by chance, so the values that Rust reads when it looks for the struct is `Add("top text")`. However, this happens every time we call `StringListContainer_do`, so we never print anything out, just keep adding the first string.

However, this does work on Windows-MSVC and on Linux-GNU *but only* if you mark StringListContainer_do as `extern "Rust"`. Just `extern` and `extern "C"` will not work.

The repo for this code is [here](https://github.com/andrewhu-uw/rust_rr_test), if that helps

## Meta

`rustc --version --verbose`:

```
rustc 1.33.0 (2aa4c46cf 2019-02-28)
binary: rustc
commit-hash: 2aa4c46cfdd726e97360c2734835aa3515e8c858
commit-date: 2019-02-28
host: x86_64-unknown-linux-gnu
release: 1.33.0
LLVM version: 8.0
```