exploring_wasm

exploring_wasm

The current browser world

  • writing your frontend in some framework generating a ginormous website contain never enough Javascript especially from external sources, 'CDNs', or using the package manager with the most supply chain attacks
  • using electron or a webkit to ship(t) a browser for 'desktop' or 'native' apps

An alternative world

  • You could develop something in a nicer compiled language, build it for the web and also use it natively.
  • And only use some 'Glue-Code' in Javascript and create a frontend for different platforms (I'm currently not sure, what the best approach there currently is)

WASM

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

—- webassembly.org

Stack Machine

  • very simple machine, uses a stack (FIFO List) for all values used to call function or operators (operators are also only operators)

    • stack simulates local operation state
    • also includes a store for the global operation state
    • has only simple instructions to manipulate stack and store state

      • typical stack operations (push, pop, …)
      • mathematical operands using values on the stack
      • jumps to other targets/ traps into function/blocks
  • operations are modelled with 'rules' (written as prose text or a mathematical notation)
  • for more, see WASM Execution
Example simple stack machine
#include <assert.h>
#include <stdio.h>

#define STACK_CAPACITY 1024

/*! \enum
*
*  Instruction types implemented in the stack-machine
*/
#include <stddef.h>
typedef enum {
  INST_PUSH, /*! Push element onto the stack */
  INST_POP,  /*! Remove the top most element from stack */
  INST_ADD,  /*! Add both top most elements of stack and write on top */
  INST_SUB, /*! Subtract second- from first-top-most elements of stack and write
on top */
  INST_PRINT /*! Print out top most element */
} InstType;

/*! \struct
*  \brief Instruction
*
*  General Structure of an instruction
*/
typedef struct {
  InstType type; /*!< General Structure of an instruction */
  int operant;   /*!< Operant to use instruction on*/
} Inst;

// A simple program, using all operands
Inst programm[] = {{.type = INST_PUSH, .operant = 20},
  {.type = INST_PUSH, .operant = 22},
  {.type = INST_ADD},
  {.type = INST_PRINT},
  {.type = INST_PUSH, .operant = 40},
  {.type = INST_SUB},
  {.type = INST_PRINT},
  {.type = INST_POP},
  {.type = INST_PRINT}};

// use the size of the allocated list 
//(itemsize * items_in_list) and divide with the size of one element
#define NUM_OF_INSTR (sizeof(programm) / sizeof(programm[0]))

/*! \struct Stack
*  \brief Stack data-structure to work on
*
*  Has the actual stack and a pointer pointing to the next free element
*/
typedef struct {
  int st_data[STACK_CAPACITY]; /*!< The stack data */
  size_t st_pointer;           /*!< Current top free element of the stack */
} Stack;

/*! \brief Implementation of the stack-push operation
*
* \param [out] stack stack top work on
* \param [in] value integer to put on top of the stack
*/
void stack_push(Stack *stack, int value) {
  assert(stack->st_pointer < STACK_CAPACITY - 1);
  stack->st_data[stack->st_pointer++] = value;
}

/*! \brief Implementation of the stack-pop operation
*
* Removes the element by JUST decreasing the pointer, so not actually erased!
*
* \param [out] stack stack top work on
*
* \return the top most element
*/
int stack_pop(Stack *stack) {
  assert(stack->st_pointer > 0);
  return stack->st_data[--stack->st_pointer];
}

/*! \brief Implementation of the stack-top operation
*
* \param [out] stack stack top work on
*
* \return the top most element
*/
int stack_top(Stack *stack) {
  assert(stack->st_pointer >= 0);
  return stack->st_data[stack->st_pointer - 1];
}

/*! \brief Saves the programm to a binary file
*/
void save_programm_to_file(const char *filename) {
  FILE* file = fopen(filename, "wb");
  fwrite(programm, sizeof(programm[0]), NUM_OF_INSTR, file);
  fclose(file);
}

int main(int argc, char *argv[]) {
  Stack stack;

  save_programm_to_file("programm.bin");

  // implements the stack operations and executes the program
  // so the execution environment
  for (size_t ip = 0; ip < NUM_OF_INSTR; ++ip) {
    switch (programm[ip].type) {
      case INST_PUSH:
        stack_push(&stack, programm[ip].operant);
        break;
      case INST_POP:
        stack_pop(&stack);
        break;
      case INST_ADD: {
        int a = stack_pop(&stack);
        int b = stack_pop(&stack);
        stack_push(&stack, a + b);
      } break;
      case INST_SUB: {
        int a = stack_pop(&stack);
        int b = stack_pop(&stack);
        stack_push(&stack, b - a);
      } break;
      case INST_PRINT:
        printf("%d\n", stack_top(&stack));
        break;
      default:
        assert(0 && "Invalid instruction");
    }
  }
}
# compile
gcc -o stack_machine.out stack_machine.c

# start and run VM with programm initial programm
./stack_machine.out
42
2
0

# look into hexcode output of programm run in VM
xxd programm.bin

#         _________ First instruction Enum (INST_PUSH)
#                   _________ Operant for first instruction in little endian (20)
#                                       _________ Second Operant for second operation (22)
00000000: 0000 0000 1400 0000 0000 0000 1600 0000  ................
00000010: 0200 0000 0000 0000 0400 0000 0000 0000  ................
00000020: 0000 0000 2800 0000 0300 0000 0000 0000  ....(...........
00000030: 0400 0000 0000 0000 0100 0000 0000 0000  ................
00000040: 0400 0000 0000 0000                      ........
source_files

WebAssembly

  • WAT … Textual Representation of WebAssembly code (like Assembly)
  • WASM … 'Assembled' version of WAT (like Machine-Code), uses a kind of transpiler like the 'assembler'
  • WASI … Like POSIX for the web, so how to interact with the filesystem, start processes,

    # convert .wat textual respresentation into actual WebAssembly bytes
    wat2wasm main.wat
  • a simple WASM program example:

    (module
      ;; specify functions to import from the wasm runtime
      (func $print (import "js" "print") (param i32))
      ;; define an function to export with 2 parameters
      (func (export "add") (param i32) (param i32)
            local.get 0
            local.get 1
            i32.add
            ;; takes the top most argument and pass to the imported function
            call $print))
    • this then needs to be compiled, e.g. with wat2wasm
    • then the compiled binary can
    async function start() {
      const wasm = await WebAssembly.instantiateStreaming(
        fetch("./main.wasm"),
        // provide functions that can be 'imported' into WebAssembly
        {
          "js": {
            "print": (x) => console.log(`A WebAssembly function call with argument ${x}, yay!`)
          }
        },
      );
      // call the exported function from the wasm file
      console.log(wasm.instance.exports.add(22, 20));
    }
    start().catch((e) => console.error(e));
    • the Javascript gets the file, provide the 'runtime functions' for it and can then call the exported functions from it
    • when testing out:

      • use e.g.:

        #      ______________ user the module http.server (no library needed)
        #                     _________________ and call the main with those arguments
        python -m http.server -b 127.0.0.1 -d .
        #                                  ^^^^ use all the files in the current directory
        #                        ^^^^^^^^^ address to bind to
      • look in the console of your browser to see the result of the add operation
source-files:

Links

Let's Talk About WebAssembly and WASI - tsoding - YouTube video Source of the code/ Inspiration from him

Tags: