Skip to content

Design of APIs between XCQ host and guest program

Jiyuan Zheng edited this page Jul 30, 2024 · 3 revisions

Make APIs more ergonomic

Feature Host Side Guest Side Frontend
Feature Discovery hasExtension in ExtensionCore prelude APIs(1) -
Passing input data Reading the extension methods set and query the runtime metadata to construct input data generate a set of used extension methods when compiling or embed the information in blob same as host side(2)
Decoding single method call result - decode it (where does the type information come from) -
Display XCQ result Query the metadata and decode - same as host side(2)

(1) means every guest program will have this api (2) means we can do it either in guest side(Rust API) or frondend side(js/ts API)

Example:

// TODO: this function is automatically generated, and shouldn't be displayed/moved/deleted
#[polkavm_derive::polkavm_import]
extern "C" {
    fn call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64;
}
#[polkavm_derive::polkavm_export]
extern "C" fn main(ptr: u32, size: u32) -> u64 {
    // TODO: make manually reading extension_id not required
    // TODO: make manually pointer moving not required
    let extension_id = unsafe { core::ptr::read_volatile(ptr as *const u64) };
    let num_query = unsafe { core::ptr::read_volatile((ptr + 8) as *const u8) };
    let query_size = (size - 9) / num_query as u32;
    let mut sum = 0u64;
    for i in 0..num_query { 
        // TODO: single call need to be wrapped in a semantic context 
        let res = unsafe { call(extension_id, ptr + 9 + query_size * i as u32, query_size) };
        let res_len = (res >> 32) as u32;
        let res_ptr = (res & 0xffffffff) as *const u8;
        let res_bytes = unsafe { core::slice::from_raw_parts(res_ptr, res_len as usize) };
        // TODO: there needs a codec::Decode call(doing in guest)
        sum += u64::from_le_bytes(res_bytes.try_into().unwrap());
    }
    let sum_bytes = sum.to_le_bytes();
    let ptr = polkavm_derive::sbrk(sum_bytes.len());
    if ptr.is_null() {
        return 0;
    }
    unsafe {
        core::ptr::copy_nonoverlapping(sum_bytes.as_ptr(), ptr, sum_bytes.len());
    }
    (sum_bytes.len() as u64) << 32 | (ptr as u64)
}

parameters passing convention

The interface between xcq-executor and polkavm program is: (ptr: *u8, length: u32) -> u64. It takes a pointer to an u8 array and its length as an argument. This u8 array is expected to be the SCALE encoded parameters of the function as defined in the trait. The return value is an u64 that represents length << 32 | pointer of an u8 array. This return value u8 array contains the SCALE encoded return value as defined by the trait function. This convention is the same as sp_api