refactor: have instructions in array instead of switch-case

This commit is contained in:
Orangerot 2025-05-20 14:44:29 +02:00
parent 311010f4e6
commit d8c0b2bc2c

192
wai.c
View file

@ -54,6 +54,7 @@ enum section {
};
enum TYPE {
TYPE_ANY = 0,
TYPE_I32 = 0x7F,
TYPE_I64 = 0x7E,
TYPE_F32 = 0x7D,
@ -111,7 +112,7 @@ struct module {
size_t num_exports;
};
enum INSTRUCTION {
enum OP_CODES {
INSTR_CALL = 0x10,
INSTR_ELSE = 0x05,
INSTR_END = 0x0b,
@ -140,7 +141,8 @@ static const char *TYPE_NAME[] = {
[TYPE_F64] = "F64",
[TYPE_V128] = "V128",
[TYPE_FUNCREF] = "FREF",
[TYPE_EXTERNREF] = "EXTR"
[TYPE_EXTERNREF] = "EXTR",
[TYPE_ANY] = "ANY",
};
struct value_t {
@ -156,6 +158,12 @@ struct value_t {
} value;
};
struct context {
struct module *module;
size_t func_i;
size_t func_stack_begin;
};
#define incr(i, len) i++; if (i >= len) {return 0;}
void print_value(struct value_t *value) {
@ -222,107 +230,94 @@ void stack_pop(struct stack *s, struct value_t *value) {
}
int parse_function(struct module *module, size_t func_i, size_t len);
int parse_instruction(struct module *module, u_char *binary, size_t func_i, size_t func_stack_begin, size_t len) {
int parse_instruction(struct context context, u_char *binary, size_t len);
#define OP(name, body) int name(struct context context, struct value_t *a, struct value_t *b, void *immidiate, struct value_t *result, u_char *binary, size_t len) { body; return 0;}
OP(instr_f64_mul_exec, result->type = TYPE_F64; result->value.f64 = a->value.f64 * b->value.f64)
OP(instr_f64_sub_exec, result->type = TYPE_F64; result->value.f64 = b->value.f64 - a->value.f64)
OP(instr_f64_lt_exec, result->type = TYPE_F64; result->value.f64 = b->value.f64 < a->value.f64)
OP(instr_f64_const_exec, result->type = TYPE_F64; result->value.f64 = *(double*)immidiate)
OP(instr_local_get_exec,
size_t func_type_i = context.module->func[context.func_i].func_type_index;
struct func_type_t *func_type = &context.module->func_types[func_type_i];
int num_locals = func_type->num_params + context.module->func[context.func_i].num_local_vars;
printf("num locals %d, %d\n", num_locals, num_locals - 1 - *(u_char*)immidiate);
stack_peak(&context.module->stack, result, num_locals - 1 - *(u_char*)immidiate, context.func_stack_begin);
)
OP(instr_call_exec, parse_function(context.module, *(u_char*)immidiate, len))
OP(instr_if_exec,
size_t i = 0;
enum TYPE condition_type = *(u_char*) immidiate;
if (a->type != condition_type)
printf("Wrong types!\n");
while (binary[i] != INSTR_ELSE) {
// TODO test condition with correct type.
// This might not matter since all types are false with 0x0
if (a->value.i64) {
i += parse_instruction(context, &binary[i], len);
} else {
incr(i, len);
}
}
incr(i, len);
while (binary[i] != INSTR_END) {
if (a->value.i64) {
incr(i, len);
} else {
i += parse_instruction(context, &binary[i], len);
}
}
incr(i, len);
return i;
)
struct instruction {
size_t num_param;
enum TYPE params[2];
size_t len_immidiate;
size_t num_results;
int (*exec) (struct context context, struct value_t *a, struct value_t *b, void *immidiate, struct value_t *result, u_char *binary, size_t len);
};
struct instruction INSTRUCTIONS[] = {
[INSTR_F64_MUL] = {.num_param = 2, .params = {TYPE_F64, TYPE_F64}, .num_results = 1, .len_immidiate = 0, .exec = &instr_f64_mul_exec},
[INSTR_F64_SUB] = {.num_param = 2, .params = {TYPE_F64, TYPE_F64}, .num_results = 1, .len_immidiate = 0, .exec = &instr_f64_sub_exec},
[INSTR_F64_LT] = {.num_param = 2, .params = {TYPE_F64, TYPE_F64}, .num_results = 1, .len_immidiate = 0, .exec = &instr_f64_lt_exec},
[INSTR_F64_CONST] = {.num_param = 0, .params = {}, .num_results = 1, .len_immidiate = 8, .exec = &instr_f64_const_exec},
[INSTR_LOCAL_GET] = {.num_param = 0, .params = {}, .num_results = 1, .len_immidiate = 1, .exec = &instr_local_get_exec},
[INSTR_CALL] = {.num_param = 0, .params = {}, .num_results = 0, .len_immidiate = 1, .exec = &instr_call_exec},
[INSTR_IF] = {.num_param = 1, .params = {TYPE_ANY}, .num_results = 0, .len_immidiate = 1, .exec = &instr_if_exec},
};
int parse_instruction(struct context context, u_char *binary, size_t len) {
size_t i = 0;
enum INSTRUCTION instr = binary[i];
enum OP_CODES op_code = binary[i];
u_char *instr_addr = &binary[i];
struct value_t a = {0};
struct value_t b = {0};
struct value_t result = {0};
struct value_t arguments[2];
incr(i, len);
switch (instr) {
case INSTR_CALL: {
int func_index = binary[i];
incr(i, len);
// stack_pop(&module->stack, &a);
parse_function(module, func_index, len);
break;
}
case INSTR_ELSE:
printf("reached else instruction: impossible!\n");
case INSTR_END:
break;
case INSTR_F64_CONST:
result.type = TYPE_F64;
result.value.f64 = *(double*)&binary[i];
i += 8;
stack_push(&module->stack, &result);
break;
case INSTR_F64_LT: {
stack_pop(&module->stack, &a);
stack_pop(&module->stack, &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;
}
case INSTR_F64_MUL: {
stack_pop(&module->stack, &a);
stack_pop(&module->stack, &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;
}
case INSTR_F64_SUB: {
stack_pop(&module->stack, &a);
stack_pop(&module->stack, &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;
}
case INSTR_IF: {
stack_pop(&module->stack, &a);
enum TYPE condition_type = binary[i];
incr(i, len);
if (a.type != condition_type)
printf("Wrong types!\n");
struct instruction *instr = &INSTRUCTIONS[op_code];
if (instr->exec == NULL) {
printf("not implemented/illegal instruction %x at %lx\n", op_code, instr_addr - context.module->binary);
exit(1);
};
while (binary[i] != INSTR_ELSE) {
// TODO test condition with correct type.
// 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 {
incr(i, len);
}
}
incr(i, len);
while (binary[i] != INSTR_END) {
if (a.value.i64) {
incr(i, len);
} else {
i += parse_instruction(module, &binary[i], func_i, func_stack_begin, len);
}
}
incr(i, len);
break;
}
case INSTR_LOCAL_GET: {
int local_index = binary[i];
size_t func_type_i = module->func[func_i].func_type_index;
struct func_type_t *func_type = &module->func_types[func_type_i];
int num_locals = func_type->num_params + module->func[func_i].num_local_vars;
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;
}
default:
printf("unknown instruction! %x at %lx\n", instr, instr_addr - module->binary);
exit(1);
for (size_t param_i = 0; param_i < instr->num_param; param_i++) {
stack_pop(&context.module->stack, &arguments[param_i]);
if (instr->params[param_i] != TYPE_ANY && arguments[param_i].type != instr->params[param_i]) {
printf("wrong type! %x\n", op_code);
}
}
i += instr->exec(context, &arguments[0], &arguments[1], &binary[i], &result, &binary[i + instr->len_immidiate], len);
i += instr->len_immidiate;
if (instr->num_results) {
stack_push(&context.module->stack, &result);
}
return i;
}
@ -336,6 +331,11 @@ int parse_function(struct module *module, size_t func_i, size_t len) {
size_t func_stack_begin = module->stack.bytes;
size_t func_stack_end;
struct value_t result = {0};
struct context context = {
.module = module,
.func_i = func_i,
.func_stack_begin = func_stack_begin
};
incr(i, len);
func->num_local_vars = binary[i];
@ -345,7 +345,7 @@ int parse_function(struct module *module, size_t func_i, size_t len) {
incr(i, len);
}
while (binary[i] != INSTR_END) {
i += parse_instruction(module, &binary[i], func_i, func_stack_begin, len);
i += parse_instruction(context, &binary[i], len);
}
incr(i, len);