#include "../../include/ssa/ssa.h" #include "../../include/std/mem.h" #include "../../include/std/log.h" #include "../../include/std/hash.h" #include "../../include/std/thread.h" #include "../../include/ast.h" #include "../../include/compiler.h" #include "../../include/parser.h" #include /* TODO: Instead of using memcpy to copy data to the ssa, make it cleaner by defining all the data in structs and copying the structs. Even if it takes more space, it might even be faster. */ #define throw(result) do { longjmp(context->env, (result)); } while(0) #define throw_if_error(result) \ do { \ int return_if_result; \ return_if_result = (result); \ if((return_if_result) != 0) \ throw(return_if_result); \ } while(0) /* Max length of a string that fits in u16 */ #define MAX_STRING_LENGTH UINT16_MAX #define FUNC_MAX_ARGS 128 static CHECK_RESULT SsaRegister variable_generate_ssa(Variable *self, SsaCompilerContext *context); static int compare_number(const void *a, const void *b) { const SsaNumber *lhs = a; const SsaNumber *rhs = b; return (rhs->type == lhs->type && rhs->value.integer == lhs->value.integer); } static usize hash_number(const u8 *data, usize size) { SsaNumber number; assert(size == sizeof(SsaNumber)); am_memcpy(&number, data, size); return number.value.integer; } SsaNumber create_ssa_integer(i64 value) { SsaNumber result; result.value.integer = value; result.type = SSA_NUMBER_TYPE_INTEGER; return result; } SsaNumber create_ssa_float(f64 value) { SsaNumber result; result.value.floating = value; result.type = SSA_NUMBER_TYPE_FLOAT; return result; } int ssa_init(Ssa *self, Parser *parser) { return_if_error(buffer_init(&self->instructions, parser->allocator)); return_if_error(hash_map_init(&self->intermediates_map, parser->allocator, sizeof(SsaIntermediateIndex), compare_number, hash_number)); return_if_error(buffer_init(&self->intermediates, parser->allocator)); return_if_error(hash_map_init(&self->strings_map, parser->allocator, sizeof(SsaStringIndex), hash_map_compare_string, amal_hash_string)); return_if_error(buffer_init(&self->strings, parser->allocator)); return_if_error(hash_map_init(&self->extern_funcs_map, parser->allocator, sizeof(SsaExternFuncIndex), hash_map_compare_string, amal_hash_string)); return_if_error(buffer_init(&self->extern_funcs, parser->allocator)); return_if_error(buffer_init(&self->export_funcs, parser->allocator)); return_if_error(buffer_init(&self->funcs, parser->allocator)); self->intermediate_counter = 0; self->string_counter = 0; self->extern_func_counter = 0; self->export_func_counter = 0; self->func_counter = 0; self->reg_counter = 0; self->param_counter = 0; self->label_counter = 0; self->parser = parser; return 0; } static CHECK_RESULT int ssa_get_unique_reg(Ssa *self, SsaRegister *result) { assert(result); /* Overflow */ if((u16)self->reg_counter + self->param_counter + 1 > INT16_MAX) { amal_log_error("Ssa too many registers!"); return -1; } assert(self->reg_counter <= INT8_MAX && "TODO: Implement usage of reg higher than 128"); *result = self->reg_counter++; return 0; } SsaNumber ssa_get_intermediate(Ssa *self, SsaIntermediateIndex index) { SsaNumber result; assert(index < buffer_get_size(&self->intermediates, SsaNumber)); am_memcpy(&result, buffer_get(&self->intermediates, index, sizeof(SsaNumber)), sizeof(SsaNumber)); return result; } BufferView ssa_get_string(Ssa *self, SsaStringIndex index) { BufferView result; assert(index < buffer_get_size(&self->strings, BufferView)); am_memcpy(&result, buffer_get(&self->strings, index, sizeof(BufferView)), sizeof(BufferView)); return result; } static CHECK_RESULT int ssa_try_add_intermediate(Ssa *self, SsaNumber number, SsaIntermediateIndex *result_index) { bool exists; BufferView key; assert(result_index); key = create_buffer_view((const char*)&number, sizeof(number)); exists = hash_map_get(&self->intermediates_map, key, result_index); if(exists) return 0; /* Overflow */ if(self->intermediate_counter + 1 <= self->intermediate_counter) { amal_log_error("Ssa too many intermediates!"); return -1; } *result_index = self->intermediate_counter; ++self->intermediate_counter; switch(number.type) { case SSA_NUMBER_TYPE_INTEGER: { amal_log_debug("i%u = %lld", *result_index, number.value.integer); break; } case SSA_NUMBER_TYPE_FLOAT: { amal_log_debug("i%u = %f", *result_index, number.value.floating); break; } } return_if_error(buffer_append(&self->intermediates, &number, sizeof(number))); return hash_map_insert(&self->intermediates_map, key, result_index); } static CHECK_RESULT int ssa_try_add_string(Ssa *self, BufferView str, SsaStringIndex *result_index) { bool exists; assert(result_index); exists = hash_map_get(&self->strings_map, str, result_index); if(exists) return 0; /* Overflow */ if(self->string_counter + 1 <= self->string_counter) { amal_log_error("Ssa too many strings!"); return -1; } if(str.size > MAX_STRING_LENGTH) { amal_log_error("String \"%.*s\" is longer than %d\n", str.size, str.data, MAX_STRING_LENGTH); return -2; } *result_index = self->string_counter; ++self->string_counter; amal_log_debug("s%u = \"%.*s\"", *result_index, str.size, str.data); return_if_error(buffer_append(&self->strings, &str, sizeof(str))); return hash_map_insert(&self->strings_map, str, result_index); } /* TODO: Right now this has the same scope as a file. This should be global, otherwise you could define multiple extern func with the same name but different signature as long as they are defined in different files */ static CHECK_RESULT int ssa_try_add_extern_func(Ssa *self, FunctionSignature *func_sig, BufferView name, SsaExternFuncIndex *result_index, BufferView *existing_func) { bool exists; assert(result_index); assert(existing_func); exists = hash_map_get(&self->extern_funcs_map, name, result_index); if(exists) { const SsaExternFunc *existing_extern_func = buffer_get(&self->extern_funcs, *result_index, sizeof(SsaExternFunc)); *existing_func = existing_extern_func->name; if(!function_signature_equals(func_sig, existing_extern_func->func_sig)) return SSA_ERR_EXTERN_FUNC_SIG_MISMATCH; return 0; } /* Overflow */ if(self->extern_func_counter + 1 <= self->extern_func_counter) { amal_log_error("Ssa too many extern closures!"); return -1; } *result_index = self->extern_func_counter; ++self->extern_func_counter; amal_log_debug("extern_func%u = %.*s", *result_index, name.size, name.data); { SsaExternFunc extern_func; extern_func.func_sig = func_sig; extern_func.name = name; return_if_error(buffer_append(&self->extern_funcs, &extern_func, sizeof(extern_func))); return hash_map_insert(&self->extern_funcs_map, name, result_index); } } /* Exported functions with the same name are allowed to exist in different files, but not in the same file. There is no need to check if there are two exported functions with the same name in the same file as that is already checked in the AST stage, as you are not allowed to have multiple variables of the same name in the same scope, exported or not. */ static CHECK_RESULT int ssa_try_add_export_func(Ssa *self, FunctionSignature *func_sig, BufferView name) { /* Overflow */ if(self->export_func_counter + 1 <= self->export_func_counter) { amal_log_error("Ssa too many exported closures!"); return -1; } amal_log_debug("exported_func%u = %.*s", self->export_func_counter, name.size, name.data); ++self->export_func_counter; { SsaExportFunc export_func; export_func.func_sig = func_sig; export_func.name = name; return buffer_append(&self->export_funcs, &export_func, sizeof(export_func)); } } static CHECK_RESULT int ssa_add_ins_form1(Ssa *self, SsaInstruction ins_type, SsaRegister lhs, u16 rhs) { SsaInsForm1 ins_form_1; ins_form_1.lhs = lhs; ins_form_1.rhs = rhs; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_form_1, sizeof(ins_form_1)); } static const char* binop_type_to_string(SsaInstruction binop_type) { assert(binop_type >= SSA_ADD && binop_type <= SSA_EQUALS); switch(binop_type) { case SSA_ADD: return "+"; case SSA_SUB: return "-"; case SSA_MUL: return "*"; case SSA_DIV: return "/"; case SSA_EQUALS: return "=="; default: return ""; } } static CHECK_RESULT int ssa_add_ins_form2(Ssa *self, SsaInstruction ins_type, SsaRegister lhs, SsaRegister rhs, SsaRegister *result) { SsaInsForm2 ins_form_2; return_if_error(ssa_get_unique_reg(self, result)); ins_form_2.result = *result; ins_form_2.lhs = lhs; ins_form_2.rhs = rhs; amal_log_debug("r%d = r%d %s r%d", *result, lhs, binop_type_to_string(ins_type), rhs); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_form_2, sizeof(ins_form_2)); } static CHECK_RESULT int ssa_ins_assign_inter(Ssa *self, SsaRegister dest, SsaNumber number) { SsaIntermediateIndex index; return_if_error(ssa_try_add_intermediate(self, number, &index)); amal_log_debug("r%d = i%u", dest, index); return ssa_add_ins_form1(self, SSA_ASSIGN_INTER, dest, index); } static CHECK_RESULT int ssa_ins_assign_string(Ssa *self, SsaRegister dest, BufferView str) { SsaStringIndex index; return_if_error(ssa_try_add_string(self, str, &index)); amal_log_debug("r%d = s%u", dest, index); return ssa_add_ins_form1(self, SSA_ASSIGN_STRING, dest, index); } static CHECK_RESULT int ssa_ins_assign_reg(Ssa *self, SsaRegister dest, SsaRegister src) { amal_log_debug("r%d = r%d", dest, src); return ssa_add_ins_form1(self, SSA_ASSIGN_REG, dest, src); } static CHECK_RESULT int ssa_ins_binop(Ssa *self, SsaInstruction binop_type, SsaRegister lhs, SsaRegister rhs, SsaRegister *result) { assert(binop_type >= SSA_ADD && binop_type <= SSA_EQUALS); return ssa_add_ins_form2(self, binop_type, lhs, rhs, result); } static CHECK_RESULT int ssa_ins_func_start(Ssa *self, u8 func_flags, FunctionSignature *func_sig, SsaFuncIndex *result, usize *func_metadata_index) { const u8 ins_type = SSA_FUNC_START; SsaInsFuncStart ins_func_start; /* Overflow */ if(self->func_counter + 1 <= self->func_counter) { amal_log_error("Ssa too many closures!"); return -1; } *result = self->func_counter++; { SsaFunc func; func.func_sig = func_sig; return_if_error(buffer_append(&self->funcs, &func, sizeof(func))); } ins_func_start.flags = func_flags; /* Dont set number of local registers yet. That will be set by @func_metadata_index later when it's known */ /*ins_func_start.num_local_vars_regs = ---*/ return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return_if_error(buffer_append(&self->instructions, &ins_func_start, sizeof(ins_func_start))); *func_metadata_index = self->instructions.size - sizeof(ins_func_start.num_local_vars_regs); amal_log_debug("FUNC_START f%u(%d) %d", *result, buffer_get_size(&func_sig->parameters, FunctionParameter), buffer_get_size(&func_sig->return_types, FunctionReturnType)); return 0; } static CHECK_RESULT int ssa_ins_func_end(Ssa *self) { const u8 ins_type = SSA_FUNC_END; amal_log_debug("FUNC_END"); return buffer_append(&self->instructions, &ins_type, 1); } static CHECK_RESULT int ssa_ins_push(Ssa *self, SsaRegister reg) { const u8 ins_type = SSA_PUSH; amal_log_debug("PUSH r%d", reg); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, ®, sizeof(reg)); } static CHECK_RESULT int ssa_ins_push_ret(Ssa *self, SsaRegister reg) { const u8 ins_type = SSA_PUSH_RET; amal_log_debug("PUSH RET r%d", reg); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, ®, sizeof(reg)); } static CHECK_RESULT int ssa_ins_call_start(Ssa *self, u8 num_args) { const u8 ins_type = SSA_CALL_START; SsaInsCallStart ins_call_start; ins_call_start.num_args = num_args; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_call_start, sizeof(ins_call_start)); } static CHECK_RESULT int ssa_ins_call(Ssa *self, int import_index, FunctionDecl *func_decl) { const u8 ins_type = SSA_CALL; SsaInsFuncCall ins_func_call; ins_func_call.func_decl = func_decl; ins_func_call.import_index = import_index; amal_log_debug("CALL f(%d,%p)", import_index, func_decl); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_func_call, sizeof(ins_func_call)); } static CHECK_RESULT int ssa_ins_call_extern(Ssa *self, int import_index, LhsExpr *func_decl_lhs) { const u8 ins_type = SSA_CALL_EXTERN; SsaInsFuncCallExtern ins_func_call_extern; ins_func_call_extern.func_decl_lhs = func_decl_lhs; ins_func_call_extern.import_index = import_index; amal_log_debug("CALL_EXTERN ef(%d,%p)", import_index, func_decl_lhs); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_func_call_extern, sizeof(ins_func_call_extern)); } static CHECK_RESULT int ssa_ins_jumpzero(Ssa *self, SsaRegister condition_reg, SsaLabelIndex target_label, usize *instruction_offset) { const u8 ins_type = SSA_JUMP_ZERO; SsaInsJumpZero ins_jump_zero; ins_jump_zero.condition_reg = condition_reg; ins_jump_zero.target_label = target_label; if(target_label == 0) amal_log_debug("JUMP_ZERO r%d, DUMMY", condition_reg); else amal_log_debug("JUMP_ZERO r%d, l%d", condition_reg, target_label); *instruction_offset = self->instructions.size; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_jump_zero, sizeof(ins_jump_zero)); } static CHECK_RESULT int ssa_ins_jump(Ssa *self, SsaLabelIndex target_label, usize *instruction_offset) { const u8 ins_type = SSA_JUMP; SsaInsJump ins_jump; ins_jump.target_label = target_label; if(target_label == 0) amal_log_debug("JUMP DUMMY"); else amal_log_debug("JUMP l%d", target_label); if(instruction_offset) *instruction_offset = self->instructions.size; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_jump, sizeof(ins_jump)); } static CHECK_RESULT int ssa_ins_return(Ssa *self, SsaRegister reg) { const u8 ins_type = SSA_RET; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, ®, sizeof(reg)); } static CHECK_RESULT int ssa_ins_label(Ssa *self, u16 *label_index) { const u8 ins_type = SSA_LABEL; /* Overflow */ if(self->label_counter + 1 <= self->label_counter) { amal_log_error("Ssa too many labels!"); return -1; } *label_index = self->label_counter; ++self->label_counter; return buffer_append(&self->instructions, &ins_type, 1); } static CHECK_RESULT int ssa_set_jump_label(Ssa *self, usize jump_ins_index, SsaLabelIndex label) { switch(self->instructions.data[jump_ins_index]) { case SSA_JUMP_ZERO: { /* +1 to skip instruction opcode */ am_memcpy(self->instructions.data + jump_ins_index + 1 + offsetof(SsaInsJumpZero, target_label), &label, sizeof(SsaLabelIndex)); break; } case SSA_JUMP: { /* +1 to skip instruction opcode */ am_memcpy(self->instructions.data + jump_ins_index + 1 + offsetof(SsaInsJump, target_label), &label, sizeof(SsaLabelIndex)); break; } default: assert(bool_false && "Unexpected error... jump_ins_index doesn't point to a valid index to a jump instruction"); break; } return 0; } static CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context); static CHECK_RESULT SsaRegister scope_named_object_generate_ssa(ScopeNamedObject *self, SsaCompilerContext *context); #if 0 static bool ast_resolved_type_is_decl(AstResolvedType *self) { /* TODO: Add more types as they are introduced */ LhsExpr *lhs_expr; if(self->type != RESOLVED_TYPE_LHS_EXPR) return bool_false; lhs_expr = self->value.lhs_expr; switch(lhs_expr->type.type) { case VARIABLE_TYPE_NONE: break; case VARIABLE_TYPE_VARIABLE: return bool_false; case VARIABLE_TYPE_SIGNATURE: /* TODO: This should return bool_false when it's possible to use signature in expressions */ return bool_true; } assert(lhs_expr->rhs_expr); return lhs_expr->rhs_expr->type == AST_FUNCTION_DECL || lhs_expr->rhs_expr->type == AST_STRUCT_DECL; } #endif static bool lhs_expr_is_decl(LhsExpr *self) { if(self->rhs_expr) { return self->rhs_expr->type == AST_FUNCTION_DECL || self->rhs_expr->type == AST_STRUCT_DECL; } else { switch(self->type.type) { case VARIABLE_TYPE_NONE: assert(bool_false); return 0; case VARIABLE_TYPE_VARIABLE: return bool_false; case VARIABLE_TYPE_SIGNATURE: /* TODO: This should return bool_false when it's possible to use signature in expressions */ return bool_true; } return 0; } } static CHECK_RESULT SsaRegister number_generate_ssa(Number *self, SsaCompilerContext *context) { SsaRegister reg; SsaNumber number; if(self->is_integer) number = create_ssa_integer(self->value.integer); else number = create_ssa_float(self->value.floating); throw_if_error(ssa_get_unique_reg(context->ssa, ®)); throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); return reg; } static CHECK_RESULT SsaRegister lhsexpr_extern_generate_ssa(LhsExpr *self, SsaCompilerContext *context) { /* TODO: SsaRegister should be extended to include static and extern data */ if(self->type.type == VARIABLE_TYPE_SIGNATURE) { int err; BufferView existing_func; err = ssa_try_add_extern_func(context->ssa, self->type.value.signature, self->var_name, &self->extern_index, &existing_func); if(err == SSA_ERR_EXTERN_FUNC_SIG_MISMATCH) { Tokenizer *tokenizer; tokenizer = &context->ssa->parser->tokenizer; tokenizer_print_error(tokenizer, tokenizer_get_code_reference_index(tokenizer, self->var_name.data), "Extern closure defined here with the name %.*s doesn't match extern closure with the same name defined in another location", self->var_name.size, self->var_name.data); /* TODO: This wont work right now since the other location might belong to another parser. There should be a function to get a parser from a code reference (loop all tokens and check code range). Then the parsers that belong to scopes can also be removed. This is fine, since the lookup is only done on error. */ #if 0 tokenizer_print_error(tokenizer, tokenizer_get_code_reference_index(tokenizer, self->var_name.data), "Extern closure defined here with the name %.*s doesn't match extern closure with the same name defined in another location", self->var_name.size, self->var_name.data); #endif throw(err); } if(err != 0) throw(err); } else { assert(bool_false && "TODO: Implement lhsexpr_extern_generate_ssa for other data than functions"); } return 0; } static CHECK_RESULT SsaRegister lhsexpr_export_generate_ssa(LhsExpr *self, SsaCompilerContext *context) { /* TODO: SsaRegister should be extended to include static and export data */ if(self->rhs_expr->type == AST_FUNCTION_DECL) { throw_if_error(ssa_try_add_export_func(context->ssa, self->rhs_expr->value.func_decl->signature, self->var_name)); } else { assert(bool_false && "TODO: Implement lhsexpr_export_generate_ssa for other data than functions"); } return 0; } static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(LhsExpr *self, AstResolveData *resolve_data, SsaCompilerContext *context) { SsaRegister reg; if(LHS_EXPR_IS_EXTERN(self)) return lhsexpr_extern_generate_ssa(self, context); if(self->rhs_expr) { Ast *rhs_expr = self->rhs_expr; SsaRegister rhs_reg; rhs_reg = ast_generate_ssa(rhs_expr, context); if(LHS_EXPR_IS_EXPORT(self)) return lhsexpr_export_generate_ssa(self, context); /* Declarations (struct and function declaration) resolves to itself and in that case this expression is just a compile-time name for the declaration. Import expression also has no meaning in SSA until it's used. TODO: Shouldn't lhsexpr that have struct/function declaration as rhs be different ast expression types? */ if(lhs_expr_is_decl(self) || rhs_expr->type == AST_IMPORT) { /*assert(bool_false);*/ return 0; } return rhs_reg; } else { /* TODO: Do not assign if we dont want default value */ if(resolve_data->type.type == RESOLVED_TYPE_LHS_EXPR) { SsaNumber number; if(resolve_data->type.value.lhs_expr == (LhsExpr*)context->compiler->default_types.i64) number = create_ssa_integer(0); else if(resolve_data->type.value.lhs_expr == (LhsExpr*)context->compiler->default_types.f64) number = create_ssa_float(0.0); else assert(bool_false && "TODO: assign default value to reg depending on LhsExpr type"); throw_if_error(ssa_get_unique_reg(context->ssa, ®)); throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); } else if(resolve_data->type.type == RESOLVED_TYPE_FUNC_SIG) { assert(bool_false && "TODO: Implement this when variable can be null. Then the function pointer should be null"); } else { assert(bool_false); } } return reg; } static CHECK_RESULT SsaRegister assignmentexpr_generate_ssa(AssignmentExpr *self, SsaCompilerContext *context) { SsaRegister lhs_reg, rhs_reg; lhs_reg = ast_generate_ssa(self->lhs_expr, context); rhs_reg = ast_generate_ssa(self->rhs_expr, context); throw_if_error(ssa_ins_assign_reg(context->ssa, lhs_reg, rhs_reg)); return lhs_reg; } static CHECK_RESULT SsaRegister function_parameter_generate_ssa(FunctionParameter *self, SsaCompilerContext *context) { SsaRegister reg; if(self->resolve_data.status == AST_SSA_RESOLVED) return self->resolve_data.ssa_reg; throw_if_error(ssa_get_unique_reg(context->ssa, ®)); /* Parameters start at -1 and decrement */ if((u16)reg - 1 >= (u16)reg) { amal_log_error("Ssa too many parameters!"); throw(-1); } reg = -1 - reg; assert(reg >= INT8_MIN && "TODO: Implement more than 128 params"); self->resolve_data.status = AST_SSA_RESOLVED; self->resolve_data.ssa_reg = reg; return reg; } static CHECK_RESULT void function_signature_generate_params_ssa(FunctionSignature *self, SsaCompilerContext *context) { FunctionParameter *param, *param_end; param = buffer_begin(&self->parameters); param_end = buffer_end(&self->parameters); for(; param != param_end; ++param) { SsaRegister reg; reg = function_parameter_generate_ssa(param, context); (void)reg; } } /* TODO: Each function declaration should be in separate SSA instances so ast can be converted into ssa in any order. */ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCompilerContext *context) { /* TODO: Implement */ /* Reset reg counter in each function, because each function has a separate register context that is reset after function end */ usize func_metadata_index; u8 func_flags = 0; context->ssa->reg_counter = 0; context->ssa->label_counter = 0; /* Parameters need to have generated ssa so the first ssa registers belong to the function. This way we can know if a register access is for a parameter or not by checking the number */ function_signature_generate_params_ssa(self->signature, context); context->ssa->param_counter = context->ssa->reg_counter; context->ssa->reg_counter = 0; amal_log_debug("SSA funcdecl %p", self); /* Anonymous closure doesn't have lhs_expr, and neither can it have any flags (extern, export etc) */ if(self->lhs_expr) { assert(!LHS_EXPR_IS_EXTERN(self->lhs_expr)); if(LHS_EXPR_IS_EXPORT(self->lhs_expr)) func_flags |= FUNC_FLAG_EXPORTED; } throw_if_error(ssa_ins_func_start(context->ssa, func_flags, self->signature, &self->ssa_func_index, &func_metadata_index)); scope_generate_ssa(&self->body, context); throw_if_error(ssa_ins_func_end(context->ssa)); /* Add the number of registers used to the function metadata (FUNC_START) */ am_memcpy(context->ssa->instructions.data + func_metadata_index, &context->ssa->reg_counter, sizeof(u16)); return 0; } static CHECK_RESULT SsaRegister funccall_generate_ssa(FunctionCall *self, SsaCompilerContext *context) { SsaRegister reg; FunctionSignature *func_sig; FunctionDecl *func_decl; LhsExpr *func_lhs_expr; int import_index = context->import_index; context->import_index = 0; throw_if_error(ssa_get_unique_reg(context->ssa, ®)); assert(self->func.resolved_var.type == NAMED_OBJECT_LHS_EXPR); func_lhs_expr = self->func.resolved_var.value.lhs_expr; if(func_lhs_expr->type.type == VARIABLE_TYPE_SIGNATURE) { func_sig = func_lhs_expr->type.value.signature; } else if(func_lhs_expr->type.type == VARIABLE_TYPE_VARIABLE) { AstResolveData *resolve_data = func_lhs_expr->type.value.variable->resolved_var.resolve_data; assert(resolve_data->type.type == RESOLVED_TYPE_FUNC_SIG); func_sig = resolve_data->type.value.func_sig; } else { assert(func_lhs_expr->rhs_expr && func_lhs_expr->rhs_expr->resolve_data.type.type == RESOLVED_TYPE_FUNC_SIG); func_sig = func_lhs_expr->rhs_expr->resolve_data.type.value.func_sig; } func_decl = func_sig->func_decl; /* Push return arguments */ { /* TODO: When amalgam supports multiple return types in assignment/declaration, update this to take all of them into account. Right now it only uses one return type. It should also take into account the size of the type. */ assert(buffer_get_size(&func_sig->return_types, FunctionReturnType) <= 1); throw_if_error(ssa_ins_push_ret(context->ssa, reg)); } /* Push parameter arguments */ assert(buffer_get_size(&self->args, Ast*) <= FUNC_MAX_ARGS); { SsaRegister arg_regs[FUNC_MAX_ARGS]; Ast **arg = buffer_begin(&self->args); Ast **arg_end = buffer_end(&self->args); for(; arg != arg_end; ++arg) { arg_regs[arg_end - arg] = ast_generate_ssa(*arg, context); } /* This is done in two steps since first we want the argument expressions to be generated and then at the end, push the registers. This allows Amalgam to push arguments directly to registers on abi that uses registers as function arguments (system-v x86_64) instead of first pushing the registers to stack and then moving them to registers. */ arg = buffer_begin(&self->args); throw_if_error(ssa_ins_call_start(context->ssa, arg_end - arg)); for(; arg != arg_end; ++arg) { throw_if_error(ssa_ins_push(context->ssa, arg_regs[arg_end - arg])); } } if(func_lhs_expr && LHS_EXPR_IS_EXTERN(func_lhs_expr)) { throw_if_error(ssa_ins_call_extern(context->ssa, import_index, func_lhs_expr)); } else { assert(func_decl); /* rhs wont be null here because only extern variable can't have rhs */ throw_if_error(ssa_ins_call(context->ssa, import_index, func_decl)); } return reg; } static CHECK_RESULT SsaRegister structdecl_generate_ssa(StructDecl *self, SsaCompilerContext *context) { /* TODO: Implement */ /*assert(bool_false);*/ scope_generate_ssa(&self->body, context); return 0; } static CHECK_RESULT SsaRegister structfield_generate_ssa(StructField *self, SsaCompilerContext *context) { /* TODO: Implement */ assert(bool_false); (void)self; (void)context; return 0; } static CHECK_RESULT SsaRegister string_generate_ssa(String *self, SsaCompilerContext *context) { SsaRegister reg; throw_if_error(ssa_get_unique_reg(context->ssa, ®)); throw_if_error(ssa_ins_assign_string(context->ssa, reg, self->str)); return reg; } SsaRegister variable_generate_ssa(Variable *self, SsaCompilerContext *context) { /* TODO: If resolved_var refers to a variable in another file, use a cross file reference that requires no locking (not yet implemented) */ /* This is not thread-safe:*/ assert(self->resolved_var.type != NAMED_OBJECT_NONE); return scope_named_object_generate_ssa(&self->resolved_var, context); } static SsaInstruction binop_type_to_ssa_type(BinopType binop_type, amal_default_type *type) { switch(binop_type) { case BINOP_ADD: return SSA_ADD; case BINOP_SUB: return SSA_SUB; case BINOP_MUL: return type->is_signed ? SSA_IMUL : SSA_MUL; case BINOP_DIV: return type->is_signed ? SSA_IDIV : SSA_DIV; case BINOP_DOT: assert(bool_false && "Binop dot not valid for arithmetic operation and requires special functionality"); return 0; case BINOP_EQUALS: return SSA_EQUALS; } return 0; } /* Returns the import statement for lhs of binop dot expression, where lhs is a variable name */ static Import* binop_lhs_get_import_or_null(Binop *self) { if(self->lhs->type == AST_VARIABLE) { ScopeNamedObject *resolved_var = &self->lhs->value.variable->resolved_var; if(resolved_var->type == NAMED_OBJECT_LHS_EXPR && resolved_var->value.lhs_expr->rhs_expr && resolved_var->value.lhs_expr->rhs_expr->type == AST_IMPORT) return resolved_var->value.lhs_expr->rhs_expr->value.import; } return NULL; } static CHECK_RESULT SsaRegister binop_generate_ssa(Binop *self, SsaCompilerContext *context) { SsaRegister reg; /* Syntax example for binop dot + func_decl expr const std = @import("std.amal"); std.printf */ if(self->type == BINOP_DOT && self->rhs->type == AST_FUNCTION_CALL) { Import *lhs_import = binop_lhs_get_import_or_null(self); if(lhs_import) context->import_index = 1 + lhs_import->file_scope->import_index; reg = ast_generate_ssa(self->rhs, context); context->import_index = 0; } else { const SsaRegister lhs_reg = ast_generate_ssa(self->lhs, context); const SsaRegister rhs_reg = ast_generate_ssa(self->rhs, context); assert(self->lhs->resolve_data.type.type == RESOLVED_TYPE_LHS_EXPR && "TODO: Implement binop_generate_ssa for function signature"); throw_if_error(ssa_ins_binop(context->ssa, binop_type_to_ssa_type(self->type, (amal_default_type*)self->lhs->resolve_data.type.value.lhs_expr), lhs_reg, rhs_reg, ®)); } return reg; } static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCompilerContext *context, SsaLabelIndex *skip_other_else_statements_label) { if(else_if_stmt->condition) { usize jump_ins_index; usize jump_skip_else_index; SsaLabelIndex skip_body_label; SsaRegister condition_reg = ast_generate_ssa(else_if_stmt->condition, context); throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_ins_index)); scope_generate_ssa(&else_if_stmt->body, context); if(else_if_stmt->next_else_if_stmt) throw_if_error(ssa_ins_jump(context->ssa, 0, &jump_skip_else_index)); throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); throw_if_error(ssa_set_jump_label(context->ssa, jump_ins_index, skip_body_label)); if(else_if_stmt->next_else_if_stmt) { else_if_statement_generate_ssa(else_if_stmt->next_else_if_stmt, context, skip_other_else_statements_label); /* Skip over all other else if statements, since else_if_statement_generate_ssa is recursive */ throw_if_error(ssa_set_jump_label(context->ssa, jump_skip_else_index, *skip_other_else_statements_label)); return; } } else { assert(!else_if_stmt->next_else_if_stmt); scope_generate_ssa(&else_if_stmt->body, context); } /* Note: The last else if statement doesn't need a jump */ throw_if_error(ssa_ins_label(context->ssa, skip_other_else_statements_label)); } static void if_statement_generate_ssa(IfStatement *if_stmt, SsaCompilerContext *context) { usize jump_ins_index; usize jump_skip_else_index; SsaLabelIndex skip_body_label; SsaLabelIndex skip_else_statements_label; SsaRegister condition_reg = ast_generate_ssa(if_stmt->condition, context); throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_ins_index)); scope_generate_ssa(&if_stmt->body, context); if(if_stmt->else_if_stmt) throw_if_error(ssa_ins_jump(context->ssa, 0, &jump_skip_else_index)); throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); throw_if_error(ssa_set_jump_label(context->ssa, jump_ins_index, skip_body_label)); if(if_stmt->else_if_stmt) { else_if_statement_generate_ssa(if_stmt->else_if_stmt, context, &skip_else_statements_label); /* Skip over all else if statements, since else_if_statement_generate_ssa is recursive. We want to jump since we want to skip the else if statements if we are inside the first if-statement */ throw_if_error(ssa_set_jump_label(context->ssa, jump_skip_else_index, skip_else_statements_label)); } } static void while_statement_generate_ssa(WhileStatement *while_stmt, SsaCompilerContext *context) { SsaLabelIndex before_condition_label; SsaLabelIndex skip_body_label; usize jump_after_condition_index; SsaRegister condition_reg; throw_if_error(ssa_ins_label(context->ssa, &before_condition_label)); condition_reg = ast_generate_ssa(while_stmt->condition, context); throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_after_condition_index)); scope_generate_ssa(&while_stmt->body, context); throw_if_error(ssa_ins_jump(context->ssa, before_condition_label, NULL)); throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); throw_if_error(ssa_set_jump_label(context->ssa, jump_after_condition_index, skip_body_label)); } static void return_expr_generate_ssa(ReturnExpr *self, SsaCompilerContext *context) { const SsaRegister reg = ast_generate_ssa(self->rhs_expr, context); throw_if_error(ssa_ins_return(context->ssa, reg)); } static CHECK_RESULT SsaRegister ast_generate_ssa_resolve_data(void *ast_data, AstType ast_type, AstResolveData *resolve_data, SsaCompilerContext *context) { if(resolve_data->status == AST_SSA_RESOLVED) return resolve_data->ssa_reg; switch(ast_type) { case AST_NUMBER: resolve_data->ssa_reg = number_generate_ssa(ast_data, context); break; case AST_FUNCTION_DECL: resolve_data->ssa_reg = funcdecl_generate_ssa(ast_data, context); break; case AST_FUNCTION_CALL: resolve_data->ssa_reg = funccall_generate_ssa(ast_data, context); break; case AST_STRUCT_DECL: resolve_data->ssa_reg = structdecl_generate_ssa(ast_data, context); break; case AST_STRUCT_FIELD: resolve_data->ssa_reg = structfield_generate_ssa(ast_data, context); break; case AST_LHS: resolve_data->ssa_reg = lhsexpr_generate_ssa(ast_data, resolve_data, context); break; case AST_ASSIGN: resolve_data->ssa_reg = assignmentexpr_generate_ssa(ast_data, context); break; case AST_IMPORT: /* TODO: Implement cross file references */ resolve_data->ssa_reg = 0; break; case AST_STRING: resolve_data->ssa_reg = string_generate_ssa(ast_data, context); break; case AST_VARIABLE: resolve_data->ssa_reg = variable_generate_ssa(ast_data, context); break; case AST_BINOP: resolve_data->ssa_reg = binop_generate_ssa(ast_data, context); break; case AST_IF_STATEMENT: if_statement_generate_ssa(ast_data, context); break; case AST_WHILE_STATEMENT: while_statement_generate_ssa(ast_data, context); break; case AST_RETURN: return_expr_generate_ssa(ast_data, context); resolve_data->ssa_reg = 0; break; } resolve_data->status = AST_SSA_RESOLVED; return resolve_data->ssa_reg; } CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context) { assert(self); #ifdef DEBUG if(self->resolve_data.status != AST_RESOLVED && self->resolve_data.status != AST_SSA_RESOLVED) { amal_log_error("Ast type not resolved: %d", self->type); assert(bool_false); } #endif return ast_generate_ssa_resolve_data(self->value.data, self->type, &self->resolve_data, context); } CHECK_RESULT SsaRegister scope_named_object_generate_ssa(ScopeNamedObject *self, SsaCompilerContext *context) { switch(self->type) { case NAMED_OBJECT_NONE: assert(bool_false); return 0; case NAMED_OBJECT_LHS_EXPR: return ast_generate_ssa_resolve_data(self->value.lhs_expr, AST_LHS, self->resolve_data, context); case NAMED_OBJECT_FUNC_PARAM: return function_parameter_generate_ssa(self->value.func_param, context); } return 0; } void scope_generate_ssa(Scope *self, SsaCompilerContext *context) { Ast **ast = buffer_begin(&self->ast_objects); Ast **ast_end = buffer_end(&self->ast_objects); for(; ast != ast_end; ++ast) { ignore_result_int(ast_generate_ssa(*ast, context)); } }