Description
The newtype pattern is useful on FFI structs to have strongly-typed variations of a base integer type when calling FFI functions:
#[repr(C)]
struct MySpecialId(u32);
extern {
fn get_special_id() -> MySpecialId;
fn do_smth_with_special_id(id: MySpecialId);
}
pub fn main() {
unsafe {
let id = get_special_id();
do_smth_with_special_id(id); // passes
do_smth_with_special_id(100); // typecheck fails
}
}
This works well with native targets, where such calls get lowered to just passing inner value on the stack:
#[repr(C)]
struct MySpecialId(u32);
extern {
fn do_smth_with_special_id(id: MySpecialId);
}
pub fn main() {
unsafe {
do_smth_with_special_id(MySpecialId(100));
}
}
example::main:
push rbp
mov rbp, rsp
mov edi, 100
pop rbp
jmp do_smth_with_special_id@PLT
However, when targeting Emscripten (e.g. asmjs), it changes behaviour by stack-allocating the structure and passing a pointer to it:
function __ZN4temp4main17h3462209f0fd3a45cE() {
var $_2 = 0, $_2$byval_copy = 0, sp = 0;
sp = STACKTOP;
STACKTOP = STACKTOP + 16 | 0;
$_2$byval_copy = sp + 4 | 0;
$_2 = sp;
HEAP32[$_2 >> 2] = 100;
HEAP32[$_2$byval_copy >> 2] = HEAP32[$_2 >> 2];
_do_smth_with_special_id($_2$byval_copy | 0);
STACKTOP = sp;
return;
}
So, for example, linking with a static library generated from the following C code:
#include <stdio.h>
extern void do_smth_with_special_id(unsigned id) {
printf("%u\n", id);
}
results in printing 100
on every other target, but prints the integer value of a stack pointer (e.g. 15812
) on Emscripten targets.
Same happens when linking with Emscripten JS libraries (using --js-library
or upcoming #41409).
Of course, as a workaround, I can dereference pointer on callee side every time, but this is both inefficient, and also seems that this inconsistent behaviour is rather a bug that needs to be fixed.
I can see that newtypes actually map to LLVM structures and not integers:
declare void @do_smth_with_special_id({ i64 })
so this might be considered as a bug on Emscripten side which doesn't lower such structures, or lowering could happen on Rust side to ensure consistency - not sure which side is appropriate.