Objc block invoke foreign - #537
TotallyGamerJet wants to merge 8 commits into
Conversation
On a cache miss, call the block's invoke pointer through the Blocks ABI, marshalling via RegisterFunc with a signature derived from the arguments (and T for InvokeBlock). InvokeBlock no longer copies before the lookup, which missed for stack blocks. Fixes ebitengine#536 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
The foreign path in purego/objc/objc_block_darwin.go Lines 258 to 261 in 9be702e When the block's C return type is a struct returned in memory (larger than 16 bytes), the caller has to pass a hidden pointer to the result buffer. No such pointer is passed here. On darwin/arm64 the block writes its result through whatever happens to be in x8. By the amd64 ABI (not tested) the block would take rdi, which holds the block pointer, as the result buffer. It would overwrite the block's isa/flags/invoke and read every argument shifted by one register. Repro on darwin/arm64, added to typedef struct { int64_t a, b, c, d; } Big;
void *purego_big_block(void) {
Big (^b)(int64_t) = ^Big(int64_t x) { Big r = {x, x, x, x}; return r; };
return Block_copy(b);
}lib := loadBlockFixture(t)
var bigBlock func() objc.Block
purego.RegisterLibFunc(&bigBlock, lib, "purego_big_block")
block := bigBlock()
block.Invoke(int64(1)) // SIGSEGV
Filed by Claude (Claude Code), on behalf of @hajimehoshi. |
|
Did we support structs for blocks btw? |
Use the block's type encoding to validate the argument count and the kinds of the arguments and result before calling, refuse Invoke on blocks that return a struct (a hidden result pointer is needed), and reject func arguments, which would consume a callback on every call. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Parse block signatures with extended block encodings (@?<...>) and quoted names, and compare arguments and results by size and field layout instead of only by kind, so a struct of the wrong size or an integer of the wrong width is refused rather than called. Move the signature parsing into its own file with unit tests, and reuse internal/strings.GoString. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if f.Type.Size() == 0 { | ||
| // structs.HostLayout and other zero-sized fields have no counterpart in C. | ||
| continue | ||
| } |
There was a problem hiding this comment.
A blank padding field such as _ [3]byte has a non-zero size, so it ends up in the layout string. The C encoding has no entry for padding, so the two sides never match. RegisterFunc's docs tell callers to add struct padding themselves, and purego's own struct tests do exactly that:
Lines 104 to 106 in 87dc024
Lines 613 to 618 in 87dc024
A Go mirror written that way is refused even though its layout matches C:
type BoolFloat struct {
_ structs.HostLayout
b bool
_ [3]byte
f float32
}goABI returns {1111f} for this type, but encodingABI("{BoolFloat=Bf}") returns {1f}. So InvokeBlock[BoolFloat] gives a mismatch error for a block that returns struct { bool b; float f; }.
Skipping blank fields isn't enough on its own, because a _ int32 can also stand in for a real C field the caller doesn't need. Treating blank fields as opaque bytes would handle both cases: require the same total size, and require every C field to either line up with a named Go field of the same kind at the same offset, or fall inside a blank Go field. The C offsets come from the encoding with natural alignment.
Filed by Claude (Claude Code), on behalf of @hajimehoshi.
…ures A Go struct with its padding written out as blank fields, as RegisterFunc asks for, was refused because a type encoding has no entry for padding. Compare the two sides by offset instead: the sizes must be equal and every named member must be at the same offset with the same kind, while a blank field may cover any integer members or none. Floating point members decide which registers a struct uses, so they must match even when blank. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What issue is this addressing?
Closes #536
What type of issue is this addressing?
bug
What this PR does | solves
This PR allows calling objc.Block with blocks that were created from other sources