Skip to content

Instantly share code, notes, and snippets.

@flaki
Created June 23, 2022 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flaki/53f91c3eabda528e7e544feec2bb850b to your computer and use it in GitHub Desktop.
Save flaki/53f91c3eabda528e7e544feec2bb850b to your computer and use it in GitHub Desktop.
Dissecting a generated JS/TS binding in Reactr
this.env.cacheSet(key, bytes, ttl, this.ident);

Generated bindings.js:

// The original generated binding:
cacheSet(arg0, arg1, arg2, arg3) {
    const memory = this._exports.memory;
    const realloc = this._exports["canonical_abi_realloc"];
    const ptr0 = utf8_encode(arg0, realloc, memory);
    const len0 = UTF8_ENCODED_LEN;
    const val1 = arg1;
    const len1 = val1.length;
    const ptr1 = realloc(0, 0, 1, len1 * 1);
    (new Uint8Array(memory.buffer, ptr1, len1 * 1)).set(new Uint8Array(val1.buffer, val1.byteOffset, len1 * 1));
    const ret = this._exports['cache-set'](ptr0, len0, ptr1, len1, clamp_host(arg2, 0, 4294967295), clamp_host(arg3, 0, 4294967295));
    return ret;
}

/*
    The binding is invoked as:
 
        this.env.cacheSet(key, bytes|value, ttl, this.ident);

    so we use this to dissect the generated JS and make it a bit more readable/understandable what's going on:
*/
cacheSet(key, bytes, TTL, IDENT) {
    // The WebAssembly memory object
    const memory = this._exports.memory;
    // The "realloc" function from the WebAssembly ABI, we use this to ask the allocator to reserve a portion of the Wasm memory
    const realloc = this._exports["canonical_abi_realloc"];
    // For more information on the canonical_abi_realloc function, read the Component Model/Interface Types "Canonical ABI" explainer:
    // https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#canonical-definitions

    // arg0 => key
    // this is the parameter for the cache key we are trying to set, it's a string
    // so we use the utf8_encode() utility from intrinsics.js to encode it into UTF-8 and place it in the Wasm memory
    // this is why we pass in the string itself, the memory reference and allocator function, and will get back the pointer where the bytes ended up in Wasm memory
    const ptr_key = utf8_encode(key, realloc, memory);
    // this is a "second return value", the UTF-8-encoded length of the string, it's a live binding to a global variable exported from intrinsics.js
    const len_key = UTF8_ENCODED_LEN;

    // arg1 => bytes
    // this is the parameter for the value we are trying to store
    // in the cache API this is already in a byte-array form so we don't have to deal with encoding it, just place it into the memory
    const val_bytes = bytes;
    const len_bytes = val_bytes.length;
    const ptr_bytes = realloc(0, 0, 1, len_bytes * 1);
    // By setting the first two arguments (originalPtr, originalSize) to zero, we are telling realloc that we want a brand new allocation to be made
    // The third argument is an alignment, it does not make any difference here, specified as 1
    // Finally the last argument specifies the chunk of memory we are trying to allocate. Realloc will give us a pointer pointing to an allocated memory of this byte-size 

    // We create a new byte-array view into the Wasm memory buffer, starting at the pointer and being allocation-long,
    // so basically this gives us JS access to the chunk of memory we just allocated
    (new Uint8Array(
        memory.buffer,
        ptr_bytes,
        len_bytes * 1
    // We use .set() on the chunk of memory - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set
    // this practically takes the contents of the object passed into it as an argument and copies it into the underlying array
    )).set(
        // We create a view into the bytes (cacheSet value argument) we received and copy these bytes into the Wasm memory via the .set() call
        new Uint8Array(val_bytes.buffer, val_bytes.byteOffset, len_bytes * 1)
    );

    // We call the cache-set function on the other side of the ABI (passed into WebAssembly by the runtime) with the pointers/locations of the data
    // https://github.com/suborbital/reactr/blob/main/engine/capabilities/cache.go#L65
    const ret = this._exports['cache-set'](
        // pointer and length to the UTF-8-encoded key bytes in Wasm memory
        ptr_key, len_key,
        // pointer and length to the value-bytes in Wasm memory
        ptr_bytes, len_bytes,
        // arg2 => TTL
        // this is the TTL (time-to-live, or expiration) value specified in the third function argument
        // we use the clamp_host utility function from intrinsics.js to ensure this is within the integer bounds of the host system
        clamp_host(TTL, 0, 4294967295),
        // arg3 => IDENT
        // this is a function call instance identifier automatically provided by Reactr, we are passing it back to identify the current invocation
        clamp_host(IDENT, 0, 4294967295)
    );

    // return the result of the cache-set FFI call
    return ret;
}
fn cache_set(
    key_pointer: *const u8,
    key_size: i32,

    value_pointer: *const u8,
    value_size: i32,

    ttl: i32,

    ident: i32,
) -> i32;
fn resp_set_header(
    key_pointer: *const u8,
    key_size: i32,
    
    val_pointer: *const u8,
    val_size: i32,
    
    ident: i32
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment