BeefRT: a dynamic call for wasm, so reflection Invoke works there - #2509
Merged
bfiete merged 1 commit intoSep 22, 2026
Merged
Conversation
Contributor
Author
|
I'll rebase this |
jayrulez
force-pushed
the
fix/wasm-reflection-invoke-has-no-ffi
branch
4 times, most recently
from
September 21, 2026 22:26
a100a94 to
d19e595
Compare
Collaborator
|
Well, I don't really love there being a big block of emscripten-specific code thrown into a core BeefRT file. At minimum it needs to be separated off into some wasm-specific support file. I don't love that I don't have any idea whether this is actually a full and proper implementation, as it seems there could easily be cases it doesn't cover... but I guess that's still better than zero invocation support. |
MethodInfo.Invoke is the only dynamic call Beef has, and it goes through FFILIB. The wasm runtime is built with BF_DISABLE_FFI, where PrepCif answered FFIResult_NoFFI and Call did nothing, so every Invoke on wasm failed. The visible symptom was not Invoke: Type.GetCustomAttribute has to CONSTRUCT the attribute to return it, so an attribute plainly present read back as absent, while HasCustomAttribute (a type id compare, no call) still said yes. wasm does not need libffi. A call through the function table takes one number per parameter, and corlib has already decided the calling convention before FFILIB sees anything: a struct parameter is splatted into its fields exactly as the compiler passed it, or handed over as a pointer, and a struct return becomes a void call with the destination pointer as a leading argument. So for Beef's own methods the layer only ever sees scalars and pointers, which is what wasmTable.get(fp).apply already takes. The implementation lives in BeefySysLib/platform/wasm/WasmFFI.cpp, beside WasmCommon.cpp, and Internal.cpp only dispatches to it under BF_DISABLE_FFI && __EMSCRIPTEN__. Its header comment states what it covers and what it does not: a Struct FFIType from user code driving System.FFI directly is passed by address per clang's wasm32 ABI, except that clang passes a single-scalar struct as the scalar, which is not special cased; LongDouble is treated as f64; wasm32 only. BuildContext exports wasmTable on every wasm link, without which the runtime's JS cannot see the table. ClosureAlloc stays unimplemented, as on every backend. BF_DISABLE_FFI still means no libffi rather than no FFI, so it keeps working for any other target without one. Tests cover i32, f32, f64, an i64 past 32 bits, a struct by value, a 24 byte struct by value, a struct returned by value, a void return, and the GetCustomAttribute case that surfaced it.
jayrulez
force-pushed
the
fix/wasm-reflection-invoke-has-no-ffi
branch
from
September 22, 2026 01:50
d19e595 to
50799e8
Compare
Contributor
Author
|
Refactored |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MethodInfo.Invoke is the only dynamic call Beef has, and it goes through FFILIB. The wasm runtime is built with BF_DISABLE_FFI, where PrepCif return FFIResult_NoFFI and Call is no-op, so every Invoke on wasm failed.
Symptom: Type.GetCustomAttribute has to construct the attribute to return it, so an attribute plainly present read back as absent, while HasCustomAttribute (a type id compare, no call) still said yes.
wasm does not need libffi. libffi is complicated because ABIs like SysV x86-64 scatter a struct's fields across registers, so a call has to be assembled per signature. clang's wasm32 ABI passes EVERY struct indirectly as one pointer and returns one through a hidden sret pointer prepended to the arguments: measured on the table entries, an 8 byte and a 24 byte struct both take arity 1, and returning either takes arity params+1. So every case is one number per argument, which is what a call through the function table already does.
PrepCif fills the cif record, Call maps each FFIType.TypeKind to a slot kind and dispatches through EM_JS to wasmTable.get(fp).apply. BuildContext exports wasmTable on every wasm link, without which the runtime's JS cannot see it.
BF_DISABLE_FFI still means no libffi rather than no FFI, so it keeps working for any other target without one. ClosureAlloc stays unimplemented, and its comment now says why: corlib binds the allocator and not ffi_prep_closure_loc.
Covers the shapes that can go wrong: i32, f32, f64, an i64 past 32 bits, a struct by value, a 24 byte struct by value, a struct returned by value, a void return, and the GetCustomAttribute case that surfaced it.