Skip to content

Instantly share code, notes, and snippets.

@hrmsk66
Last active January 19, 2024 09:38
Show Gist options
  • Save hrmsk66/ae61c4e71b504a7110d4eaccb7195581 to your computer and use it in GitHub Desktop.
Save hrmsk66/ae61c4e71b504a7110d4eaccb7195581 to your computer and use it in GitHub Desktop.
Running C Code on Fastly Compute

Running C on Fastly Compute

1. Download the WASI SDK

curl -O -sSL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-macos.tar.gz
tar xf wasi-sdk-21.0-macos.tar.gz

This step is based on the documentation from Fermyon: https://www.fermyon.com/wasm-languages/c-lang

2. Prepare code

The fastly.h file contains only the necessary bindings to parse some information from the request and return Hello, world! in the Response body.

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fastly.h"

int main() {
    char *version = getenv("FASTLY_SERVICE_VERSION");
    if (version == NULL) {
        version = "";
    }
    printf("FASTLY_SERVICE_VERSION: %s\n", version);

    // Req
    uint32_t req_h = 0;
    uint32_t req_body_h = 0;
    req_body_downstream_get(&req_h, &req_body_h);

    // Parse URL
    uint32_t buf_size = 1024;
    char buf[buf_size];
    size_t nwritten = 0;
    req_uri_get(req_h, buf, buf_size, &nwritten);

    buf[nwritten] = '\0';
    printf("uri: %s\n", buf);

    // Body
    uint32_t resp_body_h = 0;
    body_new(&resp_body_h);

    char resp_buffer[] = "Hello, world!";
    nwritten = 0;
    body_write(resp_body_h, resp_buffer, strlen(resp_buffer), 0, &nwritten);

    // Resp
    uint32_t resp_h = 0;
    resp_new(&resp_h);
    resp_send_downstream(resp_h, resp_body_h, 0);

    return 0;
}

fastly.h

#include <stdlib.h>

#define WASM_IMPORT(module, name) __attribute__((import_module(module), import_name(name)))

// fastly_http_req
WASM_IMPORT("fastly_http_req", "body_downstream_get")
int req_body_downstream_get(uint32_t *req_handle_out, uint32_t *body_handle_out);

WASM_IMPORT("fastly_http_req", "uri_get")
int req_uri_get(uint32_t req_handle, char *uri, size_t uri_max_len, size_t *nwritten);

WASM_IMPORT("fastly_http_req", "method_get")
int req_method_get(uint32_t req_handle, char *method, size_t method_max_len, size_t *nwritten);

// fastly_http_resp
WASM_IMPORT("fastly_http_resp", "new")
int resp_new(uint32_t *resp_handle_out);

WASM_IMPORT("fastly_http_resp", "header_insert")
int resp_header_insert(uint32_t resp_handle, const char *name, size_t name_len, const char *value, size_t value_len);

WASM_IMPORT("fastly_http_resp", "send_downstream")
int resp_send_downstream(uint32_t resp_handle, uint32_t body_handle, uint32_t streaming);

// fastly_http_body
WASM_IMPORT("fastly_http_body", "new")
int body_new(uint32_t *handle_out);

WASM_IMPORT("fastly_http_body", "write")
int body_write(uint32_t body_handle, const char *buf,
               size_t buf_len, size_t end, size_t *nwritten);

The Canonical ABI can be found at the following link. It's not entirely clear how the types defined in witx are mapped to each language's types. https://github.com/fastly/Viceroy/tree/main/lib/compute-at-edge-abi

3. Compile them into wasm

wasi-sdk-21.0/bin/clang --target=wasm32-wasi --sysroot=wasi-sdk-21.0/share/wasi-sysroot/ main.c -o main.wasm

4. Test it locally

viceroy -v main.wasm

5. Deploy it to Fastly / Prepare fastly.toml

fastly.toml

authors = []
description = ""
language = "c"
manifest_version = 3
name = "hello-world"
service_id = ""

6. Deploy it to Fastly / Pack and deploy

fastly compute pack --wasm-binary main.wasm
fastly compute deploy -p pkg/package.tar.gz

Analyzing the wasm File

Viewing the wasm File in Text Format

We can convert the .wasm binary to a readable text format using the wasm-tools print command.

Example:

wasm-tools print main.wasm | grep -e "import\b"
  (import "fastly_http_req" "body_downstream_get" (func $req_body_downstream_get (;0;) (type 2)))
  (import "fastly_http_req" "uri_get" (func $req_uri_get (;1;) (type 3)))
  (import "fastly_http_body" "new" (func $body_new (;2;) (type 4)))
  (import "fastly_http_body" "write" (func $body_write (;3;) (type 5)))
  (import "fastly_http_resp" "new" (func $resp_new (;4;) (type 4)))
  (import "fastly_http_resp" "send_downstream" (func $resp_send_downstream (;5;) (type 0)))
  (import "wasi_snapshot_preview1" "environ_get" (func $__imported_wasi_snapshot_preview1_environ_get (;6;) (type 2)))
  (import "wasi_snapshot_preview1" "environ_sizes_get" (func $__imported_wasi_snapshot_preview1_environ_sizes_get (;7;) (type 2)))
  (import "wasi_snapshot_preview1" "fd_close" (func $__imported_wasi_snapshot_preview1_fd_close (;8;) (type 4)))
  (import "wasi_snapshot_preview1" "fd_fdstat_get" (func $__imported_wasi_snapshot_preview1_fd_fdstat_get (;9;) (type 2)))
  (import "wasi_snapshot_preview1" "fd_seek" (func $__imported_wasi_snapshot_preview1_fd_seek (;10;) (type 6)))
  (import "wasi_snapshot_preview1" "fd_write" (func $__imported_wasi_snapshot_preview1_fd_write (;11;) (type 3)))
  (import "wasi_snapshot_preview1" "proc_exit" (func $__imported_wasi_snapshot_preview1_proc_exit (;12;) (type 7)))

The output includes Fastly-specific host calls and some functions from WASI Preview 1. For instance, environ_get from the WASI API is used for accessing environment variables.

https://github.com/WebAssembly/WASI/blob/a7be582112b35e281058f1df7d8628bb30a69c3f/legacy/preview1/witx/wasi_snapshot_preview1.witx#L37-L44

Printing Messages to stdout (?)

It's not clear how Fastly Compute platform prints messages to the stdout log. In the wasm file compiled from C code, we see function calls like this:

(func $printf (;37;) (type 2) (param i32 i32) (result i32)

In comparison, a wasm file compiled from Rust, using the println! macro, shows different function calls:

(func $_ZN3std2io5stdio6_print17h993c40612de5425cE (;92;) (type 1) (param i32)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment