//go:build wasip1 package wasmguest import ( abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1" "unsafe" ) // pinned keeps host-visible buffers reachable so the Go GC never frees (or, // under a future moving GC, relocates) memory the host holds a raw pointer // into. Keyed by the buffer's linear-memory address. Wasm instances are // single-threaded per the ABI (one bn_invoke at a time), so no locking. var pinned = make(map[uint32][]byte) // lastResponse pins the most recent bn_invoke response buffer; per the ABI // it stays valid until the next bn_invoke on this instance. var lastResponse []byte // bn_alloc allocates size bytes of guest memory for the host to write into. // The region stays valid until the current bn_invoke call returns (request // buffers) or until released with bn_free / consumed by the shim (host-call // responses). // //go:wasmexport bn_alloc func bnAlloc(size uint32) uint32 { if size == 0 { return 0 } buf := make([]byte, size) ptr := bufPtr(buf) pinned[ptr] = buf return ptr } // bn_free releases a buffer previously returned by bn_alloc. Unknown // pointers are ignored (the shim may have already released the buffer). // //go:wasmexport bn_free func bnFree(ptr uint32) { delete(pinned, ptr) } // bn_invoke dispatches one hook call. (ptr, len) frames a serialized // InvokeRequest written by the host into bn_alloc'd memory; the packed // return value ((ptr << 32) | len) frames a serialized InvokeResponse valid // until the next bn_invoke. A return of 0 means the guest could not even // produce an error envelope; the host treats it as INTERNAL and discards // the instance. // //go:wasmexport bn_invoke func bnInvoke(hookID uint32, ptr uint32, length uint32) uint64 { var ( req []byte known bool ) if buf, ok := pinned[ptr]; ok && uint32(len(buf)) >= length { req = buf[:length:length] known = true } var resp []byte switch { case !known: // The ABI requires request buffers to come from bn_alloc; anything // else is a protocol violation, not readable guest memory. resp = errorResponse(abiv1.AbiErrorCode_ABI_ERROR_CODE_DECODE, "bn_invoke request pointer was not allocated via bn_alloc") case current == nil: resp = errorResponse(abiv1.AbiErrorCode_ABI_ERROR_CODE_INTERNAL, "wasmguest.Serve was never called — plugin main package is missing the init boilerplate (see wasmguest doc.go)") default: resp = current.invoke(hookID, req) } // Request buffer ownership ends with this call (ABI: valid until // bn_invoke returns). Release it whether or not the host calls bn_free. delete(pinned, ptr) // A logically-empty InvokeResponse (Error nil + empty payload — e.g. // LoadResponse/UnloadResponse) proto-marshals to ZERO bytes. That is a // valid success envelope, not the "couldn't produce an envelope" sentinel, // so frame it as (ptr, 0) with a real pointer. Returning packed 0 here // would make the host mistake every empty-response hook (notably a // successful LOAD) for an INTERNAL failure and discard the instance. A // 1-byte backing keeps the pointer valid while the reported length stays 0 // (the host reads zero bytes → an empty InvokeResponse, which is correct). if len(resp) == 0 { lastResponse = []byte{0} return uint64(bufPtr(lastResponse)) << 32 } lastResponse = resp return uint64(bufPtr(resp))<<32 | uint64(uint32(len(resp))) } func bufPtr(b []byte) uint32 { return uint32(uintptr(unsafe.Pointer(&b[0]))) }