Compare commits

...

4 commits

3 changed files with 342 additions and 117 deletions

View file

@ -3,10 +3,9 @@
# SPDX-License-Identifier: GPL-3.0 # SPDX-License-Identifier: GPL-3.0
wai: wai.c wai: wai.c
gcc -Wall -Wextra -o wai wai.c gcc -ggdb -Wall -Wextra -o wai wai.c
# tests/factorial.wasm: tests/factorial.wat # tests/factorial.wasm: tests/factorial.wat
.PHONY: tests .PHONY: tests
tests: tests:
wat2wasm -o tests/factorial.wasm tests/factorial.wat wat2wasm -o tests/factorial.wasm tests/factorial.wat

View file

@ -8,7 +8,7 @@ Dependencies: wat2wasm from [wabt](https://github.com/WebAssembly/wabt/tree/main
```sh ```sh
make make
make tests make tests
./wai tests/factorial.wasw ./wai tests/factorial.wasm fac 4
``` ```
## Resources ## Resources

454
wai.c
View file

@ -5,7 +5,7 @@
* *
* This program, named WAI, is a WebAssembly Interpreter. * This program, named WAI, is a WebAssembly Interpreter.
* Compile using make. * Compile using make.
* Usage: wai [FILE.wasm] * Usage: wai [file.wasm] [function name] [function arguments ...]
* *
* Copyright (C) 2025 orangrot <me@orangerot.dev> * Copyright (C) 2025 orangrot <me@orangerot.dev>
* *
@ -25,10 +25,18 @@
#include <endian.h> #include <endian.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#define STACK_CAPACITY 1024
#define MAX_FUNCTIONS 128
#define MAX_FUNCTION_PARAMETERS 32
#define MAX_FUNCTION_RESULTS 32
#define MAX_EXPORT_NAME_LENGTH 128
enum section { enum section {
Section_Custom, Section_Custom,
Section_Type, Section_Type,
@ -45,16 +53,52 @@ enum section {
Section_Data_Count, Section_Data_Count,
}; };
#define STACK_CAPACITY 1024 enum TYPE {
struct stack { TYPE_I32 = 0x7F,
double items[STACK_CAPACITY]; TYPE_I64 = 0x7E,
size_t count; TYPE_F32 = 0x7D,
TYPE_F64 = 0x7C,
TYPE_V128 = 0x7B,
TYPE_FUNCREF = 0x70,
TYPE_EXTERNREF = 0x6F
}; };
struct stack {
u_char items[STACK_CAPACITY];
size_t bytes;
};
struct func_type_t {
enum TYPE param[MAX_FUNCTION_PARAMETERS];
enum TYPE result[MAX_FUNCTION_RESULTS];
size_t num_params;
size_t num_results;
};
struct func_t {
size_t func_type_index;
size_t num_local_vars;
u_char *func_start_addr;
};
enum export_desc {
Export_Func,
Export_Table,
Export_Mem,
Export_Global,
};
struct export_t {
char name[MAX_EXPORT_NAME_LENGTH];
size_t name_length;
size_t func_index;
enum export_desc description;
};
struct module { struct module {
struct type_t *types; struct func_type_t func_types[MAX_FUNCTIONS]; // TYPE SECTION
u_char *funcs[128]; u_char *code[MAX_FUNCTIONS]; // CODE SECTION
size_t func_to_func_type[MAX_FUNCTIONS]; // FUNCTION SECTION
struct table_t *tables; struct table_t *tables;
struct mem_t *mems; struct mem_t *mems;
struct global_t *globals; struct global_t *globals;
@ -62,9 +106,10 @@ struct module {
struct data_t *datas; struct data_t *datas;
struct start_t *start; struct start_t *start;
struct import_t *imports; struct import_t *imports;
struct export_t *exports; struct export_t exports[MAX_FUNCTIONS];
u_char *binary; u_char *binary;
struct stack stack; struct stack stack;
size_t num_exports;
int scope; int scope;
}; };
@ -72,7 +117,6 @@ enum INSTRUCTION {
INSTR_CALL = 0x10, INSTR_CALL = 0x10,
INSTR_ELSE = 0x05, INSTR_ELSE = 0x05,
INSTR_END = 0x0b, INSTR_END = 0x0b,
INSTR_F64 = 0x7C,
INSTR_F64_CONST = 0x44, INSTR_F64_CONST = 0x44,
INSTR_F64_LT = 0x63, INSTR_F64_LT = 0x63,
INSTR_F64_MUL = 0xa2, INSTR_F64_MUL = 0xa2,
@ -81,123 +125,205 @@ enum INSTRUCTION {
INSTR_LOCAL_GET = 0x20, INSTR_LOCAL_GET = 0x20,
}; };
enum TYPE { static const int TYPE_SIZE[] = {
TYPE_I32 = 0x7F, [TYPE_I32] = 4,
TYPE_I64 = 0x7E, [TYPE_I64] = 8,
TYPE_F32 = 0x7D, [TYPE_F32] = 4,
TYPE_F64 = 0x7C, [TYPE_F64] = 8,
TYPE_V128= 0x7B, [TYPE_V128] = 16,
TYPE_FUNCREF = 0x70, [TYPE_FUNCREF] = 16,
TYPE_EXTERNREF = 0x6F [TYPE_EXTERNREF] = 16
};
static const char *TYPE_NAME[] = {
[TYPE_I32] = "I32",
[TYPE_I64] = "I64",
[TYPE_F32] = "F32",
[TYPE_F64] = "F64",
[TYPE_V128] = "V128",
[TYPE_FUNCREF] = "FREF",
[TYPE_EXTERNREF] = "EXTR"
};
struct value_t {
enum TYPE type;
union {
int32_t i32;
int64_t i64;
float f32;
double f64;
__int128 v128;
int64_t funcref;
int64_t extref;
} value;
}; };
#define incr(i, len) i++; if (i >= len) {return -1;} #define incr(i, len) i++; if (i >= len) {return -1;}
void stack_push(struct stack *s, double a) { void print_value(struct value_t *value) {
s->items[s->count++] = a; void *number = &value->value;
switch (value->type) {
case TYPE_I32:
printf("%d", *(int32_t*)number);
break;
case TYPE_I64:
printf("%ld", *(int64_t*)number);
break;
case TYPE_F32:
printf("%f", *(float*)number);
break;
case TYPE_F64:
printf("%f", *(double*)number);
break;
case TYPE_V128:
printf("%ld", *(__int128*)number);
break;
case TYPE_FUNCREF:
printf("%ld", *(int64_t*)number);
break;
case TYPE_EXTERNREF:
printf("%ld", *(int64_t*)number);
break;
}
printf(" (%s)", TYPE_NAME[value->type]);
}
void stack_peak(struct stack *s, struct value_t *value, size_t nth, size_t from) {
int byte_i = from - 1;
for (int element_i = 0; element_i < nth && byte_i > 0; element_i++) {
byte_i -= TYPE_SIZE[s->items[byte_i]];
}
value->type = s->items[byte_i];
memcpy(&value->value, &(s->items[byte_i - TYPE_SIZE[value->type]]), TYPE_SIZE[value->type]);
}
void stack_push(struct stack *s, const struct value_t *value) {
size_t type_size = TYPE_SIZE[value->type];
memcpy(&(s->items[s->bytes]), &value->value, type_size);
s->items[s->bytes + type_size] = value->type;
s->bytes += type_size + 1;
printf("stack: "); printf("stack: ");
for (int i = 0; i < s->count; i++) { for (int i = s->bytes - 1; i > 0; i -= TYPE_SIZE[s->items[i]] + 1) {
printf("%f, ", s->items[i]); enum TYPE t = s->items[i];
size_t type_size = TYPE_SIZE[t];
void *number = &s->items[i - type_size];
struct value_t stack_value = {0};
stack_peak(s, &stack_value, 0, i + 1);
print_value(&stack_value);
printf(", ");
} }
printf("\n"); printf("\n");
} }
double stack_pop(struct stack *s) { void stack_top(struct stack *s, struct value_t *value) {
s->count--; value->type = s->items[s->bytes-1];
return s->items[s->count]; memcpy(&value->value, &(s->items[s->bytes - 1 - TYPE_SIZE[value->type]]), TYPE_SIZE[value->type]);
}
double stack_top(struct stack *s) {
return s->items[s->count-1];
} }
int parse_type(u_char *binary, int len) { void stack_pop(struct stack *s, struct value_t *value) {
int i = 0; stack_top(s, value);
enum TYPE param = binary[i]; s->bytes -= TYPE_SIZE[value->type] + 1;
printf("type %x\n", param);
incr(i, len);
switch (param) {
case TYPE_I32:
case TYPE_I64:
case TYPE_F32:
case TYPE_F64:
case TYPE_V128:
case TYPE_FUNCREF:
case TYPE_EXTERNREF:
break;
default:
return -1;
}
return i;
} }
int parse_function(struct module *module, u_char *binary, double param, int len); int parse_function(struct module *module, size_t func_i, int len);
int parse_instruction(struct module *module, u_char *binary, double param, int len) { int parse_instruction(struct module *module, u_char *binary, size_t func_i, size_t func_stack_begin, int len) {
int i = 0; int i = 0;
enum INSTRUCTION instr = (u_char) binary[i]; enum INSTRUCTION instr = binary[i];
u_char *instr_addr = &binary[i]; u_char *instr_addr = &binary[i];
struct value_t a = {0};
struct value_t b = {0};
struct value_t result = {0};
incr(i, len); incr(i, len);
switch (instr) { switch (instr) {
case INSTR_CALL: case INSTR_CALL: {
int a = binary[i]; int func_index = binary[i];
incr(i, len); incr(i, len);
parse_function(module, module->funcs[a], stack_pop(&module->stack), len); // stack_pop(&module->stack, &a);
parse_function(module, func_index, len);
break; break;
}
case INSTR_ELSE: case INSTR_ELSE:
printf("reached else instruction: impossible!\n"); printf("reached else instruction: impossible!\n");
case INSTR_END: case INSTR_END:
break; break;
case INSTR_F64:
break;
case INSTR_F64_CONST: case INSTR_F64_CONST:
double k = *(double*)&binary[i]; result.type = TYPE_F64;
stack_push(&module->stack, k); result.value.f64 = *(double*)&binary[i];
i += 8; i += 8;
stack_push(&module->stack, &result);
break; break;
case INSTR_F64_LT: { case INSTR_F64_LT: {
double b = stack_pop(&module->stack); stack_pop(&module->stack, &a);
double a = stack_pop(&module->stack); stack_pop(&module->stack, &b);
stack_push(&module->stack, a < b); if (a.type != TYPE_F64 || b.type != TYPE_F64)
printf("Wrong types!\n");
result.type = TYPE_F64;
result.value.f64 = b.value.f64 < a.value.f64;
stack_push(&module->stack, &result);
break; break;
} }
case INSTR_F64_MUL: { case INSTR_F64_MUL: {
double b = stack_pop(&module->stack); stack_pop(&module->stack, &a);
double a = stack_pop(&module->stack); stack_pop(&module->stack, &b);
stack_push(&module->stack, a * b); if (a.type != TYPE_F64 || b.type != TYPE_F64)
printf("Wrong types!\n");
result.type = TYPE_F64;
result.value.f64 = a.value.f64 * b.value.f64;
stack_push(&module->stack, &result);
break; break;
} }
case INSTR_F64_SUB: { case INSTR_F64_SUB: {
double b = stack_pop(&module->stack); stack_pop(&module->stack, &a);
double a = stack_pop(&module->stack); stack_pop(&module->stack, &b);
stack_push(&module->stack, a - b); if (a.type != TYPE_F64 || b.type != TYPE_F64)
printf("Wrong types!\n");
result.type = TYPE_F64;
result.value.f64 = b.value.f64 - a.value.f64;
stack_push(&module->stack, &result);
break; break;
} }
case INSTR_IF: { case INSTR_IF: {
double a = stack_pop(&module->stack); stack_pop(&module->stack, &a);
enum TYPE condition_type = binary[i];
incr(i, len);
if (a.type != condition_type)
printf("Wrong types!\n");
while (binary[i] != INSTR_ELSE) { while (binary[i] != INSTR_ELSE) {
if (a) { // TODO test condition with correct type.
i += parse_instruction(module, &binary[i], param, len); // This might not matter since all types are false with 0x0
if (a.value.i64) {
i += parse_instruction(module, &binary[i], func_i, func_stack_begin, len);
} else { } else {
incr(i, len); incr(i, len);
} }
} }
incr(i, len); incr(i, len);
while (binary[i] != INSTR_END) { while (binary[i] != INSTR_END) {
if (a) { if (a.value.i64) {
incr(i, len); incr(i, len);
} else { } else {
i += parse_instruction(module, &binary[i], param, len); i += parse_instruction(module, &binary[i], func_i, func_stack_begin, len);
} }
} }
incr(i, len); incr(i, len);
break; break;
} }
case INSTR_LOCAL_GET: case INSTR_LOCAL_GET: {
int local_index = binary[i]; int local_index = binary[i];
incr(i, len); size_t func_type_i = module->func_to_func_type[func_i];
stack_push(&module->stack, param); struct func_type_t *func_type = &module->func_types[func_type_i];
// TODO: take local variables into account in addition to parameters
int num_locals = func_type->num_params;
printf("num locals %d, %d\n", num_locals, num_locals - 1 - local_index);
stack_peak(&module->stack, &result, num_locals - 1 - local_index, func_stack_begin);
incr(i, len);
stack_push(&module->stack, &result);
break; break;
}
default: default:
printf("unknown instruction! %x at %lx\n", instr, instr_addr - module->binary); printf("unknown instruction! %x at %lx\n", instr, instr_addr - module->binary);
exit(1); exit(1);
@ -205,17 +331,34 @@ int parse_instruction(struct module *module, u_char *binary, double param, int l
return i; return i;
} }
int parse_function(struct module *module, u_char *binary, double param, int len) { int parse_function(struct module *module, size_t func_i, int len) {
int i = 0; int i = 0;
u_char *binary = module->code[func_i];
size_t func_type_i = module->func_to_func_type[func_i];
struct func_type_t *func_type = &module->func_types[func_type_i];
int body_size = binary[i]; int body_size = binary[i];
size_t func_stack_begin = module->stack.bytes;
size_t func_stack_end;
struct value_t result = {0};
incr(i, len); incr(i, len);
// int local_decl_cound = binary[i]; // int local_decl_cound = binary[i];
incr(i, len); incr(i, len);
module->scope = 1; module->scope = 1;
while (binary[i] != INSTR_END) { while (binary[i] != INSTR_END) {
i += parse_instruction(module, &binary[i], param, len); i += parse_instruction(module, &binary[i], func_i, func_stack_begin, len);
} }
incr(i, len); incr(i, len);
func_stack_end = module->stack.bytes;
module->stack.bytes = func_stack_begin;
for (size_t param_i = 0; param_i < func_type->num_params; param_i++) {
stack_pop(&module->stack, &result);
}
for (size_t result_i = 0; result_i < func_type->num_results; result_i++) {
stack_peak(&module->stack, &result, func_type->num_results - 1 - result_i, func_stack_end);
stack_push(&module->stack, &result);
}
return i; return i;
} }
@ -232,23 +375,27 @@ int parse_section(struct module *module, u_char *binary, int len) {
break; break;
case Section_Type: case Section_Type:
printf("section: type\n"); printf("section: type\n");
int num_types = binary[i]; struct func_type_t *func_type;
size_t num_types = binary[i];
incr(i, len); incr(i, len);
for (int type_i = 0; type_i < num_types; type_i++) { for (size_t type_i = 0; type_i < num_types; type_i++) {
if (binary[i] != 0x60) { if (binary[i] != 0x60) {
printf("expected function type, found %x\n", binary[i]); printf("expected function type, found %x\n", binary[i]);
return -1; return -1;
} }
incr(i, len); incr(i, len);
int num_params = binary[i]; func_type = &module->func_types[type_i];
func_type->num_params = binary[i];
incr(i, len); incr(i, len);
for (int params_i = 0; params_i < num_params; params_i++) { for (size_t param_i = 0; param_i < func_type->num_params; param_i++) {
i += (parse_type(&binary[i], len)); func_type->param[param_i] = binary[i];
incr(i, len);
} }
int num_results = binary[i]; func_type->num_results = binary[i];
incr(i, len); incr(i, len);
for (int results_i = 0; results_i < num_results; results_i++) { for (size_t result_i = 0; result_i < func_type->num_results; result_i++) {
i += (parse_type(&binary[i], len)); func_type->result[result_i] = binary[i];
incr(i, len);
} }
} }
break; break;
@ -256,9 +403,10 @@ int parse_section(struct module *module, u_char *binary, int len) {
break; break;
case Section_Function: case Section_Function:
printf("section: function\n"); printf("section: function\n");
int num_functions = binary[i]; size_t num_functions = binary[i];
incr(i, len); incr(i, len);
for (int function_i = 0; function_i < num_functions; function_i++) { for (size_t function_i = 0; function_i < num_functions; function_i++) {
module->func_to_func_type[function_i] = binary[i];
incr(i, len); incr(i, len);
} }
break; break;
@ -270,20 +418,48 @@ int parse_section(struct module *module, u_char *binary, int len) {
break; break;
case Section_Export: case Section_Export:
printf("section: exports\n"); printf("section: exports\n");
int num_exports = binary[i]; module->num_exports = binary[i];
if(module->num_exports > MAX_FUNCTIONS) {
printf("Number of exports exceeds maximum number of functions in a module (%d)", MAX_FUNCTIONS);
return -1;
}
incr(i, len); incr(i, len);
for (int exports_i = 0; exports_i < num_exports; exports_i++) { for (size_t exports_i = 0; exports_i < module->num_exports; exports_i++) {
int string_len = binary[i]; struct export_t *export = &module->exports[exports_i];
export->name_length = binary[i];
incr(i, len); incr(i, len);
printf("export name: ");
for (int si = 0; si < string_len; si++) { for (size_t si = 0; si < export->name_length; si++) {
putchar(binary[i]); export->name[si] = (char) binary[i];
incr(i, len); incr(i, len);
} }
putchar('\n'); export->description = (enum export_desc) binary[i];
// export kind
incr(i, len); incr(i, len);
// export func index export->func_index = (size_t) binary[i];
printf("export name: %s of type %d\n", export->name, export->description);
if (export->description == Export_Func) {
printf("exported function %s(", export->name);
size_t func_type_index = module->func_to_func_type[export->func_index];
struct func_type_t *func_type = &module->func_types[func_type_index];
for (size_t param_i = 0; param_i < func_type->num_params; param_i++) {
printf("%s", TYPE_NAME[func_type->param[param_i]]);
if (param_i == func_type->num_params -1) {
printf(") -> ");
} else {
printf(",");
}
}
for (size_t result_i = 0; result_i < func_type->num_results; result_i++) {
printf("%s", TYPE_NAME[func_type->result[result_i]]);
if (result_i != func_type->num_params -1) {
printf(",");
}
}
printf("\n");
}
incr(i, len); incr(i, len);
} }
break; break;
@ -296,10 +472,12 @@ int parse_section(struct module *module, u_char *binary, int len) {
int num_functions2 = binary[i]; int num_functions2 = binary[i];
incr(i, len); incr(i, len);
for (int function_i = 0; function_i < num_functions2; function_i++) { for (int function_i = 0; function_i < num_functions2; function_i++) {
module->funcs[function_i] = &binary[i]; module->code[function_i] = &binary[i];
i += parse_function(module, &binary[i], 4, len); stack_push(&module->stack, &(struct value_t) {.type = TYPE_F64, .value.f64 = 1});
i += parse_function(module, function_i, len);
stack_pop(&module->stack, &(struct value_t) {0});
} }
printf("result: %f\n", module->stack.items[0]); // printf("result: %f\n", module->stack.items[0]);
break; break;
case Section_Data: case Section_Data:
break; break;
@ -314,11 +492,11 @@ int parse_section(struct module *module, u_char *binary, int len) {
return i; return i;
} }
int parse_module(u_char *binary, size_t len) { int parse_module(struct module *module, u_char *binary, size_t len) {
int i = 0; int i = 0;
u_char *magic = "\0asm"; char *magic = "\0asm";
while (i < 4) { while (i < 4) {
if (binary[i] != magic[i]) { if ((char) binary[i] != magic[i]) {
fprintf(stderr, "no wasm magic\n"); fprintf(stderr, "no wasm magic\n");
return 0; return 0;
} }
@ -329,41 +507,89 @@ int parse_module(u_char *binary, size_t len) {
i += 4; i += 4;
printf("addr %d\n", i); printf("addr %d\n", i);
struct module module = {0}; module->binary = binary;
module.binary = binary;
while (i < len) { while (i < len) {
i += parse_section(&module, &binary[i], len); i += parse_section(module, &binary[i], len);
} }
return i; return i;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
FILE *file;
u_char *binary;
struct stat st;
struct module module = {0};
if (argc != 2) { if (argc != 2) {
fprintf(stderr, "Usage: %s [file]\n", argv[0]); printf("Usage: %s [file] [function name] [function arguments ...]\n", argv[0]);
return 1;
}; };
FILE *file = fopen(argv[1], "r"); file = fopen(argv[1], "r");
if (file == NULL) { if (file == NULL) {
fprintf(stderr, "Failed to open file\n"); fprintf(stderr, "Failed to open file\n");
fclose(file); fclose(file);
return 1; return 1;
} }
struct stat st;
stat(argv[1], &st); stat(argv[1], &st);
printf("size: %ld\n", st.st_size); printf("size: %ld\n", st.st_size);
unsigned char *binary = malloc(st.st_size); binary = malloc(st.st_size);
fread(binary, st.st_size, st.st_size, file); fread(binary, st.st_size, 1, file);
fclose(file);
if (parse_module(binary, st.st_size) == -1) { if (parse_module(&module, binary, st.st_size) == -1) {
printf("error :(\n"); printf("error :(\n");
} }
printf("%zu\n", module.num_exports);
printf("%s\n", module.exports[0].name);
size_t export_search_i = 0;
while (export_search_i < module.num_exports &&
(strcmp(module.exports[export_search_i].name, argv[2]) != 0)) {
export_search_i++;
}
if (export_search_i == module.num_exports) {
printf("Provided function name %s not recognised. \n", argv[2]);
exit(1);
}
size_t function_search_i = module.exports[export_search_i].func_index;
struct func_type_t *func_type_search = &module.func_types[module.func_to_func_type[function_search_i]];
if (func_type_search->num_params != argc - 3) {
printf("Not enough function arguments provided. Got %d expected %zu. \n", argc - 3, func_type_search->num_params);
exit(1);
}
for (size_t param_i = 0; param_i < func_type_search->num_params; param_i++) {
enum TYPE param_type = func_type_search->param[param_i];
struct value_t param = {
.type = param_type,
.value = {0}
};
char *param_str = argv[param_i + 3];
switch (param_type) {
case TYPE_I32:
param.value.i32 = atoi(param_str);
break;
case TYPE_I64:
param.value.i64 = atoll(param_str);
break;
case TYPE_F32:
param.value.f32 = strtof(param_str, NULL);
break;
case TYPE_F64:
param.value.f64 = strtod(param_str, NULL);
break;
case TYPE_V128:
case TYPE_FUNCREF:
case TYPE_EXTERNREF:
default:
printf("%s, %s, %s unsupported\n", TYPE_NAME[TYPE_V128], TYPE_NAME[TYPE_FUNCREF], TYPE_NAME[TYPE_EXTERNREF]);
exit(1);
}
stack_push(&module.stack, &param);
}
parse_function(&module, function_search_i, 100);
free(binary); free(binary);
fclose(file);
return 0; return 0;
} }