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.wata 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
- this then needs to be compiled, e.g. with
source-files:
Links
Let's Talk About WebAssembly and WASI - tsoding - YouTube video Source of the code/ Inspiration from him


