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

feat: ffi string and buffer support #11648

Closed
wants to merge 23 commits into from
Closed

feat: ffi string and buffer support #11648

wants to merge 23 commits into from

Conversation

eliassjogreen
Copy link
Contributor

@eliassjogreen eliassjogreen commented Aug 11, 2021

Follow up on #11152 adding support for strings and buffer arguments and returns (and a tests for other argument types)

@bartlomieju bartlomieju added this to the 1.14.0 milestone Aug 11, 2021
ext/ffi/00_ffi.js Outdated Show resolved Hide resolved
}

#[no_mangle]
pub unsafe extern "C" fn print_buffer(ptr: *const u8, len: usize) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've probably overlooked it, but wouldn't it be necessary with a bound check on the buffer to prevent someone from passing a invalid length for the raw pointer?

ReddikHaien
ReddikHaien approved these changes Sep 3, 2021
Comment on lines -79 to -84
foreign_fn
.parameters
.clone()
.into_iter()
.map(libffi::middle::Type::from),
foreign_fn.result.into(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this code threw TypeError previously in dlopenInvalidArguments but now it only throws Error

@konsumer
Copy link

Anything I can do to help get this in? I am not super-strong with rust, but I have several projects that need mem-buffers, in deno FFI, so I am super-motivated. I can verify this PR works on Windows, Linux, and OSX (all on x86_64.)

@cztomsik
Copy link

FYI I have added few comments here https://github.com/denoland/deno/pull/11648/files#r713404236
I believe it would be nice to support *mut c_char result type even with into_raw() leaking memory because you can always wrap the return in finally { lib.free_string(s) }

@rracariu
Copy link

rracariu commented Oct 5, 2021

Looking fwd. to get this PR in. But it is stale for a long time.

Can it be simplified to only deal with buffers for now? This (with #12274 merged) would unlock the bare minimum for replacing the plugin API.

@kriszyp
Copy link

kriszyp commented Oct 5, 2021

I'll just add another vote that I think the most essential elements for really opening the doors for native/foreign plugins are passing buffers and async support (some way to enqueue a callback on the event queue). Strings are not necessary; maybe a convenience, but JS already has facilities for string encoding/decoding that work fine. To me, adding support for buffers & async to FFI is the most critical/valuable outstanding feature/issue in Deno.

@cztomsik
Copy link

cztomsik commented Oct 5, 2021

Oh, I just asked this on discord - I'd be okay with buffers as well (if they can be passed as mut) because then it's possible to have at least hacky String <-> String communication in a form of strlen(string_id) -> usize and copy(string_id, dest_buf)

@rotu
Copy link
Contributor

rotu commented Oct 5, 2021

I think allowing strings also opens up another can of worms. As shown in #12226, a Javascript DOMString can legally contain lone surrogates. This is okay in C++ and Python, but not in Rust and Swift. Buffers don't have this issue.

@bartlomieju
Copy link
Member

I forked off this PR to #12335, we're gonna support only buffer-as-arg in the first pass

@bartlomieju
Copy link
Member

@rracariu @kriszyp @cztomsik @rotu buffer-as-arg is now available on canary, see #12335 for details. I'll work on documentation as per denoland/manual#106

@rracariu
Copy link

rracariu commented Oct 7, 2021

@rracariu @kriszyp @cztomsik @rotu buffer-as-arg is now available on canary, see #12335 for details. I'll work on documentation as per denoland/manual#106

Without return buffers one would not be able to return complex structures, which severely limits the things you could do.

In C there is no buffer type - so strictly speaking this concept is mapped to u8* by Deno, but that is a convention.
So this means that the buffer type could expand on this convention until proper pointer types (in/out) are supported by the FFI interface.

What I mean by that is that the FFI return type could be a buffer_return FFI type which is a struct {size_t size; size_t ptr} that is mapped as a FfiBuffer type in Deno/Typescript. This FfiBuffer would have the byte contents for the pointer, but expose the pointer value as well, like interface FfiBuffer {readonly data: ArrayBuffer; readonly ptr: BigInt | number }. Using the ptr value one could call a free function to free the memory over in the FFI land.

@kriszyp
Copy link

kriszyp commented Oct 8, 2021

Without return buffers one would not be able to return complex structures, which severely limits the things you could do.

I was assuming, at least, that passing in a buffer as an argument was by reference (I hope this doesn't copy the buffer before handing it to C function), so the called function would be able to freely modify the contents of the buffer to construct/represent/communicate complex structures back to JS. If my understanding is correct (and certainly correct me if I am wrong), the limitation would be that allocation can't be done on the C, but needs to be done in JS (creating an TypedArray/ArrayBuffer), that can then be used for JS/C interaction. I would consider that to be a very reasonable limitation.

@bartlomieju
Copy link
Member

Without return buffers one would not be able to return complex structures, which severely limits the things you could do.

I was assuming, at least, that passing in a buffer as an argument was by reference (I hope this doesn't copy the buffer before handing it to C function), so the called function would be able to freely modify the contents of the buffer to construct/represent/communicate complex structures back to JS. If my understanding is correct (and certainly correct me if I am wrong), the limitation would be that allocation can't be done on the C, but needs to be done in JS (creating an TypedArray/ArrayBuffer), that can then be used for JS/C interaction. I would consider that to be a very reasonable limitation.

That is correct. @rracariu has valid concerns and we'll try to address them for v1.16, having to explicitly call foreign lib to free a buffer is certainly better than leaking it.

@kriszyp
Copy link

kriszyp commented Oct 8, 2021

BTW, the current ffi API (even including the buffer return limitation), almost exactly matches the V8 fast-api-calls interface. Was that intentional; and does deno/rusty use or intend to use V8's fast-api-calls interface (https://github.com/v8/v8/blob/master/include/v8-fast-api-calls.h)?

@bartlomieju
Copy link
Member

BTW, the current ffi API (even including the buffer return limitation), almost exactly matches the V8 fast-api-calls interface. Was that intentional; and does deno/rusty use or intend to use V8's fast-api-calls interface (https://github.com/v8/v8/blob/master/include/v8-fast-api-calls.h)?

Yes, we plan to use V8's Fast API but for our internal "op" mechanism

@rracariu
Copy link

rracariu commented Oct 8, 2021

I was assuming, at least, that passing in a buffer as an argument was by reference

I assumed the contrary given that the code is actually using const* u8 as the argument (https://github.com/denoland/deno/pull/12335/files#diff-5cab5c857e7d36ba5f5b617a7c541b75980f1ed6460ad00706b45b446346f339R221).

But a quick test proved that you can actually change the buffer from the C function. @bartlomieju Is this intentional?

@bartlomieju
Copy link
Member

I was assuming, at least, that passing in a buffer as an argument was by reference

I assumed the contrary given that the code is actually using const* u8 as the argument (https://github.com/denoland/deno/pull/12335/files#diff-5cab5c857e7d36ba5f5b617a7c541b75980f1ed6460ad00706b45b446346f339R221).

But a quick test proved that you can actually change the buffer from the C function. @bartlomieju Is this intentional?

That is the type that is handled by Deno's FFI API, however when the boundary is crossed it actually passes a pointer. I will clean up the code before 1.15 as there's one bug there anyway.

@piscisaureus
Copy link
Member

I was assuming, at least, that passing in a buffer as an argument was by reference

Yes, passing buffers should be done by reference.

@bartlomieju bartlomieju removed this from the 1.15.0 milestone Oct 8, 2021
@bartlomieju
Copy link
Member

I'm going to close this PR now as it has become quite stale. We will try to get buffer-as-return-value support for v1.16

@bartlomieju bartlomieju closed this Oct 8, 2021
@wobsoriano
Copy link

Is it possible to return strings as value now?

@cztomsik
Copy link

You can return CString::new("foo").unwrap().into_raw() and then in JS new UnsafePointerView(ptr).getCString() to decode that string but you also have to free that memory eventually or it will leak.

Either you need extra ffi function which will free the pointer when you're done or you can reuse the same (mutex or thread-local) CString and kind of "reuse" it every time and never really free it

@wobsoriano
Copy link

You can return CString::new("foo").unwrap().into_raw() and then in JS new UnsafePointerView(ptr).getCString() to decode that string but you also have to free that memory eventually or it will leak.

Either you need extra ffi function which will free the pointer when you're done or you can reuse the same (mutex or thread-local) CString and kind of "reuse" it every time and never really free it

That worked! Thanks. Didn't know UnsafePointerView returns methods

@cztomsik
Copy link

I found out recently as well, inspecting the test cases actually :)
https://github.com/denoland/deno/tree/main/test_ffi

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 this pull request may close these issues.