diff --git a/src/backend/codegen.c b/src/backend/codegen.c index 060c3b3..7ca73a2 100644 --- a/src/backend/codegen.c +++ b/src/backend/codegen.c @@ -152,6 +152,10 @@ int CODEGEN_write_function( case IR_ALLOC: target->alloc_memory(sb, (*it)->alloc_size); break; + case IR_DEALLOC: + const char * src = CODEGEN_get_reg(target, (*it)->src, false); + target->dealloc_memory(sb, src, (*it)->src.size); + break; case IR_MOV_OFFSET: if ((*it)->offset.timing == IR_PRE_OFFSET) { const char* dst = CODEGEN_get_reg(target, (*it)->dest, false); diff --git a/src/backend/target.h b/src/backend/target.h index f1ee7a9..9ef8e20 100644 --- a/src/backend/target.h +++ b/src/backend/target.h @@ -140,6 +140,11 @@ typedef struct { (*emit_mov_offset_post) (string_builder_t*, const char* dst, size_t size, const char* src); + + void + (*dealloc_memory) + (string_builder_t*, + const char* src, size_t size); } target_t; #endif // TARGET_H diff --git a/src/backend/x86_64.c b/src/backend/x86_64.c index 674b3e8..a6ebcf4 100644 --- a/src/backend/x86_64.c +++ b/src/backend/x86_64.c @@ -271,6 +271,15 @@ static void x86_alloc_memory(string_builder_t* sb, int size) sb_append_fmt(sb, " syscall\n"); } +static void x86_dealloc_memory( + string_builder_t* sb, const char* src, size_t size) +{ + sb_append_fmt(sb, " mov rax, 11\n"); + sb_append_fmt(sb, " mov rdi, %s\n", src); + sb_append_fmt(sb, " mov rsi, %zu\n", size); + sb_append_fmt(sb, " syscall\n"); +} + static void x86_emit_mov_offset_pre(string_builder_t* sb, const char* dst, size_t size, const char* src) { @@ -322,4 +331,5 @@ const target_t x86_64_target = { .alloc_memory = x86_alloc_memory, .emit_mov_offset_pre = x86_emit_mov_offset_pre, .emit_mov_offset_post = x86_emit_mov_offset_post, + .dealloc_memory = x86_dealloc_memory, }; diff --git a/src/frontend/ast.c b/src/frontend/ast.c index 9d9c311..33e5d68 100644 --- a/src/frontend/ast.c +++ b/src/frontend/ast.c @@ -131,6 +131,10 @@ void free_statement(statement_t* s) free(s->asm_stmt.args); } + if (s->type == STATEMENT_FREE) { + free_expression(s->free_stmt.expr); + } + free(s); } @@ -1759,6 +1763,33 @@ statement_t* ast_parse_asm_stmt(parser_t* p) return stmt; } +statement_t* ast_parse_free_stmt(parser_t* p) +{ + statement_t* stmt = calloc(1, sizeof(statement_t)); + if (!stmt) { + error_report_general(ERROR_SEVERITY_ERROR, "out of memory"); + return NULL; + } + stmt->type = STATEMENT_FREE; + + // consume 'free' + advance(p); + + expect(p, '(', "expect '(' after 'free' token'"); + + stmt->free_stmt.expr = parse_expression(p); + if (!stmt->free_stmt.expr) { + // TODO: maybe make error stack + free_statement(stmt); + return NULL; + } + + expect(p, ')', "expect ')' after free statement definition"); + expect(p, ';', "expect ';' at the and of statement"); + + return stmt; +} + statement_t* parse_statement(parser_t* p) { if (check(p, LEXER_token_id) && @@ -1786,6 +1817,11 @@ statement_t* parse_statement(parser_t* p) return ast_parse_asm_stmt(p); } + if (check(p, LEXER_token_id) && + strcmp(peek(p)->string_value, "free") == 0) { + return ast_parse_free_stmt(p); + } + // WARNING: this can be unsafe if string_value is NULL // TODO: keep an eye on this if (check(p, LEXER_token_id) && ((check_is_type(p)) || diff --git a/src/frontend/ast.h b/src/frontend/ast.h index db6be2d..d9dd8ba 100644 --- a/src/frontend/ast.h +++ b/src/frontend/ast.h @@ -57,6 +57,7 @@ statement_t* ast_parse_if_stmt(parser_t* p); statement_t* ast_parse_while_stmt(parser_t* p); statement_t* ast_parse_for_stmt(parser_t* p); statement_t* ast_parse_asm_stmt(parser_t* p); +statement_t* ast_parse_free_stmt(parser_t*p); statement_t* parse_statement(parser_t* p); expression_t* parse_expression(parser_t* p); diff --git a/src/frontend/ast_definition.h b/src/frontend/ast_definition.h index 5b2aece..7a64a03 100644 --- a/src/frontend/ast_definition.h +++ b/src/frontend/ast_definition.h @@ -24,6 +24,7 @@ typedef enum STATEMENT_WHILE, STATEMENT_FOR, STATEMENT_ASM, + STATEMENT_FREE, } statement_kind; typedef enum @@ -157,6 +158,7 @@ struct statement_t struct { expression_t* value; } ret; struct { expression_t* expr; } expr_stmt; struct { declaration_t* decl; } decl_stmt; + struct { expression_t* expr; } free_stmt; struct { char** instr; expression_t** args; diff --git a/src/frontend/ast_printer.c b/src/frontend/ast_printer.c index 28df559..45e730f 100644 --- a/src/frontend/ast_printer.c +++ b/src/frontend/ast_printer.c @@ -236,6 +236,12 @@ static void print_statement(statement_t* s, const char* prefix, bool is_last) break; } + case STATEMENT_FREE: + printf(CLR_STMT "FreeStmt\n" CLR_RESET); + if (s->free_stmt.expr) + print_expression(s->free_stmt.expr, cp, true); + break; + case STATEMENT_ASM: { printf(CLR_STMT "AsmStmt" CLR_RESET " (%zu instr%s)\n", s->asm_stmt.instr_count, diff --git a/src/frontend/scope.h b/src/frontend/scope.h index 60d7891..ca7cb79 100644 --- a/src/frontend/scope.h +++ b/src/frontend/scope.h @@ -24,7 +24,8 @@ static inline scope_t* scope_enter(scope_t* parent) return s; } -static inline void scope_exit(scope_t* scope) { +static inline void scope_exit(scope_t* scope) +{ if (scope) { if (scope->symbols) { hashmap_free(scope->symbols, 1); @@ -34,7 +35,8 @@ static inline void scope_exit(scope_t* scope) { } } -static inline void* scope_resolve(scope_t* scope, const char* name) { +static inline void* scope_resolve(scope_t* scope, const char* name) +{ for (scope_t* s = scope; s != NULL; s = s->parent) { void* sym = hashmap_get(s->symbols, name); if (sym) @@ -43,4 +45,15 @@ static inline void* scope_resolve(scope_t* scope, const char* name) { return NULL; } +static inline int scope_remove(scope_t* scope, const char* name) +{ + for (scope_t* s = scope; s != NULL; s = s->parent) { + if (hashmap_remove(s->symbols, name)) { + return 1; + } + } + + return 0; +} + #endif // SCOPE_H diff --git a/src/frontend/semantic.c b/src/frontend/semantic.c index 15a2c73..6955460 100644 --- a/src/frontend/semantic.c +++ b/src/frontend/semantic.c @@ -704,11 +704,35 @@ void semantic_check_scope(semantic_analyzer_t* analyzer, if (stmt->type == STATEMENT_EXPR) semantic_check_expression( analyzer, stmt->expr_stmt.expr, local_scope); + + if (stmt->type == STATEMENT_FREE) + semantic_check_free_statement(analyzer, stmt, local_scope); } scope_exit(local_scope); } +void semantic_check_free_statement( + semantic_analyzer_t* analyzer, + statement_t* stmt, + scope_t* scope) +{ + known_type_t t = + semantic_check_expression(analyzer, stmt->free_stmt.expr, scope); + + if (t.kind != TYPE_CUSTOM) { + semantic_error_register( + analyzer, stmt->free_stmt.expr->source_pos - 1, + "you are tryning to free unallocated memory. Please not that only stuct typed variable and arrays are allocated in the heap"); + return; + } + + variable_symbol_t* sym = (variable_symbol_t*) + scope_resolve(scope, stmt->free_stmt.expr->var.ident.ident_name); + free(sym); + scope_remove(scope, stmt->free_stmt.expr->var.ident.ident_name); +} + void semantic_check_asm_statement( semantic_analyzer_t* analyzer, statement_t* stmt, diff --git a/src/frontend/semantic.h b/src/frontend/semantic.h index 1bb5fb3..c1148eb 100644 --- a/src/frontend/semantic.h +++ b/src/frontend/semantic.h @@ -96,6 +96,11 @@ void semantic_check_asm_statement( statement_t* stmt, scope_t* scope); +void semantic_check_free_statement( + semantic_analyzer_t* analyzer, + statement_t* stmt, + scope_t* scope); + void semantic_check_var_declaration( semantic_analyzer_t* analyzer, declaration_t* decl, diff --git a/src/middleend/hir.c b/src/middleend/hir.c index 64ee6c8..2f5d9da 100644 --- a/src/middleend/hir.c +++ b/src/middleend/hir.c @@ -557,6 +557,28 @@ int IR_lower_expression(HIR_parser_t* hir, return 1; } +int IR_lower_free_statement( + HIR_parser_t* hir, + statement_t* stmt, + IR_function_t* func) +{ + IR_instruction_t* instr = calloc(1, sizeof(IR_instruction_t)); + if (!instr) { + error_report_general(ERROR_SEVERITY_ERROR, "out if memory"); + return 1; + } + + instr->kind = IR_DEALLOC; + + IR_lower_expression(hir, stmt->free_stmt.expr, func); + + instr->src.id = func->next_temp_id; + instr->src.size = stmt->free_stmt.expr->var.ident.type.size; + + da_append(func->code, instr); + return 0; +} + int IR_lower_asm_statement( HIR_parser_t* hir, statement_t* stmt, @@ -966,6 +988,9 @@ int IR_lower_statement(HIR_parser_t* hir, if (stmt->type == STATEMENT_ASM) { return IR_lower_asm_statement(hir, stmt, func); } + if (stmt->type == STATEMENT_FREE) { + return IR_lower_free_statement(hir, stmt, func); + } error_report_general(ERROR_SEVERITY_NOT_IMPLEMENTED, "unknown statement instruction"); @@ -1192,6 +1217,10 @@ char* IR_generate_string_program(IR_function_t* function) continue; } + if (instr->kind == IR_DEALLOC) { + sb_append_fmt(&sb, "DEALLOC %c%d, %zu\n", TEMP_STR(instr->src), instr->src.size); + } + if (instr->kind == IR_ASM) { sb_append_fmt(&sb, "ASM ["); for (size_t j = 0; j < instr->asm_data.string_count; j++) { diff --git a/src/middleend/hir.h b/src/middleend/hir.h index 5a843fb..2d0d083 100644 --- a/src/middleend/hir.h +++ b/src/middleend/hir.h @@ -21,12 +21,15 @@ typedef struct IR_function_array* hir_program; } HIR_parser_t; -int IR_lower_function(HIR_parser_t* hir, +int IR_lower_function( + HIR_parser_t* hir, declaration_t* function); -int IR_lower_statement(HIR_parser_t* hir, +int IR_lower_statement( + HIR_parser_t* hir, statement_t* stmt, IR_function_t* func); -int IR_lower_expression(HIR_parser_t* hir, +int IR_lower_expression( + HIR_parser_t* hir, expression_t* expr, IR_function_t* func); void IR_display_function(IR_function_t* function); @@ -65,5 +68,9 @@ int IR_lower_asm_statement( HIR_parser_t* hir, statement_t* stmt, IR_function_t* func); +int IR_lower_free_statement( + HIR_parser_t* hir, + statement_t* stmt, + IR_function_t* func); #endif // IR_H diff --git a/src/middleend/ir_definition.h b/src/middleend/ir_definition.h index 4a52b06..11145ee 100644 --- a/src/middleend/ir_definition.h +++ b/src/middleend/ir_definition.h @@ -46,6 +46,7 @@ typedef enum IR_EXIT, IR_ALLOC, + IR_DEALLOC, IR_CALL, diff --git a/src/thirdparty/hashmap.h b/src/thirdparty/hashmap.h index 0e366ad..990d691 100644 --- a/src/thirdparty/hashmap.h +++ b/src/thirdparty/hashmap.h @@ -57,6 +57,28 @@ inline static void* hashmap_get(hashmap_t* map, const char* key) return NULL; } +inline static int hashmap_remove(hashmap_t* map, const char* key) +{ + if (!map || !key) return 0; + + unsigned idx = hashmap_hash(key); + hashmap_entry_t* e = map->buckets[idx]; + hashmap_entry_t* prev = NULL; + + for (; e; prev = e, e = e->next) { + if (strcmp(key, e->key) != 0) continue; + if (prev) + prev->next = e->next; + else + map->buckets[idx] = e->next; + free(e->key); + free(e); + return 1; + } + + return 0; +} + inline static void hashmap_free(hashmap_t* map, int pointer_value) { if (!map) return; diff --git a/test/ast_test.c b/test/ast_test.c index ffbc8c6..f0c3ace 100644 --- a/test/ast_test.c +++ b/test/ast_test.c @@ -606,3 +606,34 @@ ct_test(ast, char_var_decl, "char a = 'a';") { free_declaration(decl); da_free(&parser); } + +// === FREE STATEMENTS === + +ct_test(ast, free_stmt_var, "free(a);") +{ + statement_t* s = parse_statement(&parser); + + ct_assert_not_null(s, "free statement should not be NULL"); + ct_assert_eq(s->type, STATEMENT_FREE, "statement type should be STATEMENT_FREE"); + ct_assert_not_null(s->free_stmt.expr, "free expression should not be NULL"); + ct_assert_eq(s->free_stmt.expr->type, EXPRESSION_VAR, "free expression should be VAR"); + ct_assert_eq(s->free_stmt.expr->var.ident.ident_name, "a", "freed var name should be 'a'"); + + free_statement(s); + da_free(&parser); +} + +ct_test(ast, free_stmt_call, "free(get_ptr());") +{ + statement_t* s = parse_statement(&parser); + + ct_assert_not_null(s, "free statement should not be NULL"); + ct_assert_eq(s->type, STATEMENT_FREE, "statement type should be STATEMENT_FREE"); + ct_assert_not_null(s->free_stmt.expr, "free expression should not be NULL"); + ct_assert_eq(s->free_stmt.expr->type, EXPRESSION_CALL, "free expression should be a CALL"); + ct_assert_eq(s->free_stmt.expr->call.callee, "get_ptr", "callee should be 'get_ptr'"); + ct_assert_eq((int)s->free_stmt.expr->call.arg_count, 0, "call should have 0 args"); + + free_statement(s); + da_free(&parser); +} diff --git a/test/codegen_case/free_stmt.asm b/test/codegen_case/free_stmt.asm new file mode 100644 index 0000000..8dfe269 --- /dev/null +++ b/test/codegen_case/free_stmt.asm @@ -0,0 +1,26 @@ +section .text +global _start +_start: + push rbp + mov rbp, rsp + sub rsp, 8 + mov rax, 9 + mov rdi, 0 + mov rsi, 8 + mov rdx, 0x01 | 0x02 + mov r10, 0x22 + mov r8, -1 + mov r9, 0 + syscall + mov [rbp - 8], rax + mov r11, [rbp - 8] + mov rax, 11 + mov rdi, r11 + mov rsi, 8 + syscall + mov r12, 0 + add rsp, 8 + pop rbp + mov rax, 60 + mov rdi, r12 + syscall diff --git a/test/codegen_case/free_stmt.clf b/test/codegen_case/free_stmt.clf new file mode 100644 index 0000000..7ef2d5a --- /dev/null +++ b/test/codegen_case/free_stmt.clf @@ -0,0 +1,10 @@ +struct v2 { + int a; + int b; +} + +fn main(): int { + v2 a; + free(a); + return 0; +} diff --git a/test/codegen_test.c b/test/codegen_test.c index 4645cdc..c9d6086 100644 --- a/test/codegen_test.c +++ b/test/codegen_test.c @@ -257,3 +257,7 @@ ct_test(codegen_test, asm_with_arg, "test/codegen_case/asm_with_arg.clf", "test/ ct_test(codegen_test, char_var_declaration, "test/codegen_case/char_var_declaration.clf", "test/codegen_case/char_var_declaration.asm") { ct_assert_eq(result, 0, "codegen gives right output for char var declaration"); } + +ct_test(codegen_test, free_stmt, "test/codegen_case/free_stmt.clf", "test/codegen_case/free_stmt.asm") { + ct_assert_eq(result, 0, "codegen gives right output for free statement"); +} diff --git a/test/hir_case/free_stmt.clf b/test/hir_case/free_stmt.clf new file mode 100644 index 0000000..7ef2d5a --- /dev/null +++ b/test/hir_case/free_stmt.clf @@ -0,0 +1,10 @@ +struct v2 { + int a; + int b; +} + +fn main(): int { + v2 a; + free(a); + return 0; +} diff --git a/test/hir_case/free_stmt.res b/test/hir_case/free_stmt.res new file mode 100644 index 0000000..18efdf6 --- /dev/null +++ b/test/hir_case/free_stmt.res @@ -0,0 +1,7 @@ +Function main +0: ALLOC 8 +1: STR slot(a), q-1 +2: LOAD q1, slot(a) +3: DEALLOC q1, 8 +4: t2 = INT_CONST 0 +5: EXIT t2 diff --git a/test/hir_test.c b/test/hir_test.c index e9073e5..ee6aed2 100644 --- a/test/hir_test.c +++ b/test/hir_test.c @@ -250,3 +250,7 @@ ct_test(hir_test, asm_with_arg, "test/hir_case/asm_with_arg.clf", "test/hir_case ct_test(hir_test, char_var_declaration, "test/hir_case/char_var_declaration.clf", "test/hir_case/char_var_declaration.res") { ct_assert_eq(result, 0, "hir gives right output for char var declaration"); } + +ct_test(hir_test, free_stmt, "test/hir_case/free_stmt.clf", "test/hir_case/free_stmt.res") { + ct_assert_eq(result, 0, "hir gives right output for free statement"); +} diff --git a/test/semantic_case/free_int_error.clf b/test/semantic_case/free_int_error.clf new file mode 100644 index 0000000..76c21c3 --- /dev/null +++ b/test/semantic_case/free_int_error.clf @@ -0,0 +1,5 @@ +fn main(): int { + int x = 5; + free(x); + return 0; +} diff --git a/test/semantic_case/free_struct_ok.clf b/test/semantic_case/free_struct_ok.clf new file mode 100644 index 0000000..d9d4f81 --- /dev/null +++ b/test/semantic_case/free_struct_ok.clf @@ -0,0 +1,10 @@ +struct v2 { + int x; + int y; +} + +fn main(): int { + v2 a = { .x = 3, .y = 7 }; + free(a); + return 0; +} diff --git a/test/semantic_case/free_u8_error.clf b/test/semantic_case/free_u8_error.clf new file mode 100644 index 0000000..051e73d --- /dev/null +++ b/test/semantic_case/free_u8_error.clf @@ -0,0 +1,5 @@ +fn main(): int { + u8 x = 5; + free(x); + return 0; +} diff --git a/test/semantic_case/free_undef_error.clf b/test/semantic_case/free_undef_error.clf new file mode 100644 index 0000000..264faf8 --- /dev/null +++ b/test/semantic_case/free_undef_error.clf @@ -0,0 +1,4 @@ +fn main(): int { + free(undef_var); + return 0; +} diff --git a/test/semantic_case/free_use_after_free.clf b/test/semantic_case/free_use_after_free.clf new file mode 100644 index 0000000..87631c8 --- /dev/null +++ b/test/semantic_case/free_use_after_free.clf @@ -0,0 +1,10 @@ +struct v2 { + int x; + int y; +} + +fn main(): int { + v2 a = { .x = 3, .y = 7 }; + free(a); + return a.x; +} diff --git a/test/semantic_test.c b/test/semantic_test.c index b0c6fe8..9ded34f 100644 --- a/test/semantic_test.c +++ b/test/semantic_test.c @@ -695,3 +695,30 @@ ct_test(semantic_case, char_declaration, "test/semantic_case/char_declaration.cl ct_assert_eq(analyzer.error_count, 2, "Should have 2 errors"); free_analyzer(&analyzer); } + +// --- free statement tests --- + +ct_test(semantic_case, free_struct_ok, "test/semantic_case/free_struct_ok.clf") { + ct_assert_eq(analyzer.error_count, 0, "Should have no errors when freeing a struct variable"); + free_analyzer(&analyzer); +} + +ct_test(semantic_case, free_int_error, "test/semantic_case/free_int_error.clf") { + ct_assert_eq(analyzer.error_count, 1, "Should have 1 error when freeing a non-struct (int) variable"); + free_analyzer(&analyzer); +} + +ct_test(semantic_case, free_u8_error, "test/semantic_case/free_u8_error.clf") { + ct_assert_eq(analyzer.error_count, 1, "Should have 1 error when freeing a non-struct (u8) variable"); + free_analyzer(&analyzer); +} + +ct_test(semantic_case, free_use_after_free, "test/semantic_case/free_use_after_free.clf") { + ct_assert_eq(analyzer.error_count, 1, "Should have 1 error when using a variable after it has been freed"); + free_analyzer(&analyzer); +} + +ct_test(semantic_case, free_undef_error, "test/semantic_case/free_undef_error.clf") { + ct_assert_eq(analyzer.error_count, 2, "Should have 2 errors when freeing an undefined variable (undefined var + free of unallocated)"); + free_analyzer(&analyzer); +} diff --git a/test/valgrind_case/full.clf b/test/valgrind_case/full.clf index c6e35f8..da245fa 100644 --- a/test/valgrind_case/full.clf +++ b/test/valgrind_case/full.clf @@ -7,6 +7,7 @@ fn main(): int { var a = bar(0); int b = bar(0); v2 x; + free(x); v2 z = { .x = 3, . y = 4 }; int! constant = 56;