#include "wasm_compile.h" #include "wasm.h" #include "stack_machine_instruction.h" struct types { struct types *prev; char type; /* should be one of VALTYPE_* constants from wasm.h */ }; struct translation { FILE *handle; struct function *function; struct module *module; struct types *types_stack; }; void free_types_stack(struct types *top) { struct types *tmp; while (top) { tmp = top->prev; free(top); top = tmp; } } /* All functions, that go into one of function pointer arrays, start with _ */ /** DEFINE TRANSLATION FUNCTIONS **/ /* Translate complex - those routines have to be defined manually */ #define TC(wasm_opcode, name, argtypes, restype) static int typecheck_call(struct translation *data, struct function *callee); static int _translate_call(struct translation *data) { uint32_t funcidx; struct function *func; struct instruction **target; if (leb_u32(data->handle, &funcidx)) { PRERR(MSG_BAD_NUM); return -1; } if (funcidx >= data->module->functions_count) { PRERR(MSG_BAD_IDX("funcidx")); return -1; } func = data->module->functions + funcidx; if (typecheck_call(data, func)) return -1; target = &func->translated_body; return i_call(ptr(target), &data->function->translated_body); } static int typecheck_local_get(struct translation *data, uint32_t localidx); static int _translate_local_get(struct translation *data) { uint32_t localidx; uint32_t args_count = data->function->type->args.count; uint32_t locals_count = data->function->locals_count; uint32_t all_locals_count = args_count + locals_count; uint64_t offset_on_frame; struct instruction **expr = &data->function->translated_body; if (leb_u32(data->handle, &localidx)) { PRERR(MSG_BAD_NUM); return -1; } if (localidx >= all_locals_count) { PRERR(MSG_BAD_IDX("localidx")); return -1; } if (typecheck_local_get(data, localidx)) return -1; offset_on_frame = all_locals_count - localidx + 1; if (localidx >= args_count) offset_on_frame -= 1; if (i_load (im(STACK_FRAME_BACKUP_ADDR), expr) || i_load_p(im(4 * offset_on_frame), expr)) return -1; return 0; } static int _translate_const(struct translation *data) { uint32_t constant; if (leb_u32(data->handle, &constant)) { PRERR(MSG_BAD_NUM); return -1; } return i_const(im(constant), &data->function->translated_body); } /* Translate Simple */ #define TS(wasm_opcode, sm_instr, argtypes, restype) \ static int _translate_##sm_instr(struct translation *data) \ { \ return i_##sm_instr(&data->function->translated_body); \ } /* Translate load/store */ static int translate_load_store(struct translation *data, int (*instr_routine) (struct instruction_data, struct instruction **)) { uint32_t align, offset; if (leb_u32(data->handle, &align) || leb_u32(data->handle, &offset)) { PRERR(MSG_BAD_NUM); return -1; } offset += MEMORY_BOTTOM_ADDR; return instr_routine(im(offset), &data->function->translated_body); } #define TLS(wasm_opcode, sm_instr, argtypes, restype) \ static int _translate_##sm_instr(struct translation *data) \ { \ return translate_load_store(data, i_##sm_instr); \ } /* This inclusion defines functions using macros above */ #include "translate_xmacro.h" #undef TS #undef TLS #undef TC /** DEFINE TRANSLATION FUNCTIONS POINTER ARRAY **/ /* Translate complex */ #define TC(wasm_opcode, name, argtypes, restype) \ [wasm_opcode] = _translate_##name, /* Translate Simple */ #define TS(wasm_opcode, sm_instr, argtypes, restype) \ TC(wasm_opcode, sm_instr, dummy, dummy) /* Translate load/store */ #define TLS(wasm_opcode, sm_instr, argtypes, restype) \ TC(wasm_opcode, sm_instr, dummy, dummy) /* The actual array of function pointers is defined here */ static int (*translation_routines[256]) (struct translation *) = { #include "translate_xmacro.h" }; #undef TS #undef TLS #undef TC /** DEFINE ARGUMENT TYPECHECK FUNCTIONS **/ static int _argcheck_empty(struct types **types_stack) { return 0; } static int argcheck_generic_noremove(struct types *types_stack, char expected) { char *name; name = expected == VALTYPE_F64 ? "f64" : expected == VALTYPE_F32 ? "f32" : expected == VALTYPE_I64 ? "i64" : "i32"; if (!types_stack) { PRERR("Expected %s on stack, got nothing\n", name); return -1; } if (types_stack->type != VALTYPE_I32) { PRERR("Expected %s (0x%02hhx) on stack, got 0x%02hhx\n", name, expected, types_stack->type); return -1; } return 0; } static int argcheck_generic(struct types **types_stack, char expected) { struct types *top_type; if (argcheck_generic_noremove(*types_stack, expected)) return -1; top_type = *types_stack; *types_stack = top_type->prev; free(top_type); return 0; } static int _argcheck_i32(struct types **types_stack) { return argcheck_generic(types_stack, VALTYPE_I32); } static int _argcheck_i32_i32(struct types **types_stack) { int i; for (i = 0; i < 2; i++) { if (argcheck_generic(types_stack, VALTYPE_I32)) return -1; } return 0; } static int _argcheck_custom(struct types **types_stack) { return 0; /* Translation function will handle argument checks */ } /** DEFINE RESULT TYPECHECK FUNCTIONS **/ static int _rescheck_empty(struct types **types_stack) { return 0; } static int rescheck_generic(struct types **types_stack, char returned) { struct types *top; top = malloc(sizeof(struct types)); if (!top) { PRERR(MSG_ALLOC_FAIL(sizeof(struct types))); return -1; } *top = (struct types) {.prev = *types_stack, .type = returned}; *types_stack = top; return 0; } static int _rescheck_i32(struct types **types_stack) { return rescheck_generic(types_stack, VALTYPE_I32); } static int _rescheck_custom(struct types **types_stack) { return 0; } /** DEFINE TYPECHECK FUNCTION POINTER ARRAY **/ /* Translate complex */ #define TC(wasm_opcode, name, argtypes, restype) \ [wasm_opcode] = {.argcheck = _argcheck_##argtypes, \ .rescheck = _rescheck_##restype}, /* Translate Simple */ #define TS(wasm_opcode, sm_instr, argtypes, restype) \ TC(wasm_opcode, dummy, argtypes, restype) /* Translate load/store */ #define TLS(wasm_opcode, sm_instr, argtypes, restype) \ TC(wasm_opcode, dummy, argtypes, restype) struct typecheck { int (*argcheck) (struct types **), (*rescheck) (struct types **); }; /*static*/ struct typecheck typecheck_routines[256] = { #include "translate_xmacro.h" }; #undef TS #undef TLS #undef TC /** DEFINE CUSTOM TYPECHECK FUNCTIONS **/ static int typecheck_call(struct translation *data, struct function *callee) { uint32_t i; i = callee->type->args.count; while (i--) { if (argcheck_generic(&data->types_stack, callee->type->args.types[i])) return -1; } for (i = 0; i < callee->type->results.count; i++) { if (rescheck_generic(&data->types_stack, callee->type->results.types[i])) return -1; } return 0; } static int typecheck_local_get(struct translation *data, uint32_t localidx) { uint32_t args_count = data->function->type->args.count; char type = localidx < args_count ? data->function->type->args.types[localidx] : data->function->locals[localidx - args_count]; if (rescheck_generic(&data->types_stack, type)) return -1; return 0; } /** REST OF THE CODE **/ static int translate_instr(struct translation *data, uint8_t wasm_opcode) { struct typecheck *tc_routines; if (!translation_routines[wasm_opcode]) { PRERR("Unknown Wasm opcode: 0x%02x\n", wasm_opcode); return -1; } tc_routines = typecheck_routines + wasm_opcode; if (tc_routines->argcheck(&data->types_stack) || tc_routines->rescheck(&data->types_stack) || translation_routines[wasm_opcode](data)) return -1; return 0; } static int translate_expr(struct translation *data, struct resulttype *args, struct resulttype *results) { struct types **tmp, *types_stack_rest; uint32_t i; int wasm_opcode; tmp = &data->types_stack; i = args ? args->count : 0; while (i--) { if (argcheck_generic_noremove(*tmp, args->types[i])) return -1; tmp = &(*tmp)->prev; } types_stack_rest = *tmp; *tmp = NULL; while (1) { wasm_opcode = fgetc(data->handle); if (wasm_opcode == EOF) { PRERR(MSG_EOF); goto fail; } if (wasm_opcode == WASM_END) break; if (translate_instr(data, wasm_opcode)) goto fail; } tmp = &data->types_stack; i = results ? results->count : 0; while (i--) { if (argcheck_generic_noremove(*tmp, results->types[i])) goto fail; tmp = &(*tmp)->prev; } if (*tmp) { PRERR("Expression produces too many result values\n"); goto fail; } *tmp = types_stack_rest; return 0; fail: free_types_stack(data->types_stack); data->types_stack = types_stack_rest; return -1; } int translate(FILE *handle, struct function *function, struct module *module) { struct instruction **expr = &function->translated_body; uint32_t args_count = function->type->args.count; uint32_t locals_count = function->locals_count; uint32_t all_locals_count = args_count + locals_count; size_t i; struct translation data = {.handle = handle, .function = function, .module = module, .types_stack = NULL}; if (locals_count + (uint64_t) args_count > STACK_TOP_ADDR * 4) { PRERR("Too many locals in a function\n"); goto fail; } for (i = locals_count + 3; i; i--) { if (i_const(im(0), expr)) goto fail; } /* function prologue */ if (i_get_frame( expr) || i_tee ( expr) || i_load (im(STACK_FRAME_BACKUP_ADDR), expr) || i_store_p (im(0x0), expr) || i_store (im(STACK_FRAME_BACKUP_ADDR), expr)) goto fail; /* actual function body */ if (translate_expr(&data, NULL, &function->type->results)) goto fail; /* function epilogue */ if (function->type->results.count) { if (i_load (im(STACK_FRAME_BACKUP_ADDR), expr) || i_swap ( expr) || i_store_p(im(4 * (2 + all_locals_count - 1)), expr) || i_load (im(STACK_FRAME_BACKUP_ADDR), expr) || i_tee ( expr) || i_tee ( expr) || i_load_p (im(4 * (1 + locals_count)), expr) || i_store_p(im(4 * (2 + all_locals_count - 2)), expr) || i_load_p (im(0), expr) || i_store (im(STACK_FRAME_BACKUP_ADDR), expr)) goto fail; } else { /* It's a bit shorter if we don't return anything */ if (i_load (im(STACK_FRAME_BACKUP_ADDR), expr) || i_tee ( expr) || i_tee ( expr) || i_load_p (im(4 * (1 + locals_count)), expr) || i_store_p(im(4 * (2 + all_locals_count - 1)), expr) || i_load_p (im(0), expr) || i_store (im(STACK_FRAME_BACKUP_ADDR), expr)) goto fail; } i = locals_count + args_count + 2; if (!function->type->results.count) i++; while (i--) { if (i_drop(expr)) goto fail; } if (i_ret(expr)) goto fail; return 0; fail: PRERR("Couldn't translate function to stack machine\n"); free_expr(*expr); function->translated_body = NULL; return -1; }