-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
ffi_spec fails with --release #12592
Comments
Oh, that's an interesting one. Rare to catch because specs usually don't run in release mode ;) A bit simplified reproduction step: bin/crystal spec spec/compiler/ffi/ffi_spec.cr --release And I don't think the compiler itself needs to be built in release mode. (though it might be relevant which LLVM version it uses) I have no idea what this might be about. But considering that the Crystal compiler doesn't do anything special in release mode, except for telling LLVM to optimize the generated code. This suggests it might be an LLVM bug. |
I just tried a program that uses ffi with the same shape, so that it compiles faster (I don't know why that |
Ah, yes, it seems |
Also for me on Mac it works fine compiled in release mode. So maybe it's a problem with how the FFI bindings are defined for linux. |
Yes, I noticed that too. In theory it shouldn't use most of the compiler code, so that shouldn't really matter for compilation complexity. 🤔 |
In theory, yes. In practice, instance variable initializers are always type-checked, even if the type doesn't end up being used, and that might end up requiring the entire compiler's source code. Definitely something to improve... though I have a feeling that that would be impossible to change at this point. |
FWIW, I'm getting the same spec failure on FreeBSD (llvm 14.0.6):
|
I've managed to reduce the problem to the following pure Crystal program. It works without require "compiler/crystal/ffi"
fun set_pointer(p : Pointer(Void)) : Int32
p.as(Int32*).value = 123
42
end
call_interface = Crystal::FFI::CallInterface.new Crystal::FFI::Type.sint32, [
Crystal::FFI::Type.pointer,
] of Crystal::FFI::Type
# Locate function pointer to set_pointer in the current program
handle = LibC.dlopen(nil, LibC::RTLD_LAZY | LibC::RTLD_GLOBAL)
function_pointer = LibC.dlsym(handle, "set_pointer")
pointer_value = 11_i32
arg_pointers = Slice[
Pointer(Int32*).malloc(1, pointerof(pointer_value)).as(Void*),
Pointer(Void).null,
]
return_value = 0i32
call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*))
p! pointer_value
p! return_value There seems to be some connection to accessing the return value. Even just commenting out the last line ( |
Bold guess: you are not supposed to pass pointer_value = Pointer(Int32).malloc(1, 11_i32)
arg_pointers = Slice[
Pointer(Int32*).malloc(1, pointer_value).as(Void*),
Pointer(Void).null,
]
return_value = 0i32
call_interface.call(function_pointer, arg_pointers.to_unsafe, pointerof(return_value).as(Void*))
p! pointer_value.value |
Another test someone could do is do the same program in C and see if it works. |
I came up with the following C program: // $ clang .test/ffi.c -O3 -lffi
#include <stdio.h>
#include <stdlib.h>
#include <ffi.h>
int set_pointer(int* p){
*p = 123;
return 42;
}
int main(){
ffi_cif cif;
ffi_type *args[1];
void *values[1];
ffi_arg rc;
int value = 11;
int** pointer = malloc(1);
*pointer = &value;
/* Initialize the argument info vectors */
args[0] = &ffi_type_pointer;
values[0] = pointer;
/* Initialize the cif */
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_sint, args);
ffi_call(&cif, FFI_FN(set_pointer), &rc, values);
printf("%d\n", value);
printf("%ld\n", rc);
return 0;
} It does not fail in release mode (O3). |
The equivalent Crystal program of that C program is using StaticArray. And if you do that, it works. So there's something fishy about using a Slice. |
Hm, that's strange because the original code actually uses |
I can't get the C program to work locally (setup issues), but what happens if you use |
Needless to say, I have no idea why it doesn't work with heap memory. |
It works well with |
So strange! The next thing I did was using |
What I could gather on pointerof(pointer_value) # => 0x16b683184
pointerof(return_value) # => 0x16b683180
# FFI does the equivalent of the following:
pointerof(return_value).as(Int64*).value = Int64.new!(set_pointer(...)) This is documented behavior:
The reason we don't see this on non-release mode is because there is extra stack space between the two variables. On the other hand the CIF call will still corrupt something else on the stack. This C program above works because the return value is properly declared as an
I believe the interpreter is unaffected since the return value is always placed on the top of the interpreter stack, according to |
Oh, great find. I know I've read that before and I've had it somewhere in my head, but couldn't connect the dots. |
Bug Report
Running:
works as expected and spec passes.
Running:
fails with:
Crystal:
This affects the Nix builds: NixOS/nixpkgs#173928
and possibly the solus ones: https://dev.getsol.us/source/crystal/browse/master/package.yml$36 (they remove
spec/compiler/ffi/ffi_spec.cr
)The text was updated successfully, but these errors were encountered: