summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Maniero <carlos@maniero.me>2023-05-10 22:24:14 -0300
committerCarlos Maniero <carlos@maniero.me>2023-05-10 22:45:31 -0300
commit5042a4ffc1363d6f0f99a3afd79f76cf2da738d6 (patch)
tree90c31d77ddf6b9051669fafdc6dfe0fc3b1f35eb
parent6f187a71cbe3aa4ebb32ba287c75562d96c7a3f4 (diff)
gas: implement function calls
For now function calls are following the C's calling convention, which means they are using the following registers to pass functions' arguments: rdi, rsi, rdx, rcx, r8, r9 If a function has more then 6 parameters, the compilation will fail. To enable function with more than 6 parameters we will need to save the extra arguments on stack. Naming: parameters: function parameters are the variables a function receives. arguments: Arguments are the values passed to a function when calling it. Calling mechanism: When a function is called, all the expressions passed as argument are evaluated, after the evaluation, the result is stored on the register that represents its argument position, the first argument will be stored on rdi, the second on rsi and so on. Receiving mechanism: When a function starts, the first thing it does is store all the registers onto the stack. So rdi will be stored on -8(rbp), rsi on -16(rbp) and so on. And, a ref_entry is created making the relationship parameter-stack_offset. Signed-off-by: Carlos Maniero <carlos@maniero.me>
-rw-r--r--examples/function_call.pipa11
-rw-r--r--examples/main.pipa6
-rw-r--r--src/ast.c54
-rw-r--r--src/ast.h33
-rw-r--r--src/ast_pretty_printer.c6
-rw-r--r--src/gas_assembly_generator.c50
-rw-r--r--src/lexer.c8
-rw-r--r--src/lexer.h1
-rw-r--r--src/parser.c95
-rw-r--r--test/integration_test.c1
-rw-r--r--test/parser_test.c6
11 files changed, 241 insertions, 30 deletions
diff --git a/examples/function_call.pipa b/examples/function_call.pipa
new file mode 100644
index 0000000..833c195
--- /dev/null
+++ b/examples/function_call.pipa
@@ -0,0 +1,11 @@
+fn add(n: i32, n2: i32): i32 {
+ return n + n2;
+}
+
+fn id(n: i32): i32 {
+ return n;
+}
+
+fn main(): i32 {
+ return id(add(40, 44)) / 2;
+}
diff --git a/examples/main.pipa b/examples/main.pipa
index 5ea0077..2da2231 100644
--- a/examples/main.pipa
+++ b/examples/main.pipa
@@ -1,7 +1,3 @@
-fn give_me_the_number(): i32 {
- return 69;
-}
-
fn main(): i32 {
- return give_me_the_number();
+ return 69;
}
diff --git a/src/ast.c b/src/ast.c
index e9aa677..f6f8f08 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -49,6 +49,7 @@ ast_node_destroy(ast_node_t *node)
break;
case AST_FUNCTION_DECLARATION:
ast_node_destroy(node->data.function.body);
+ ast_node_destroy_vector(node->data.function.prototype.parameters);
break;
case AST_IF_STMT:
ast_node_destroy(node->data.if_stmt.condition);
@@ -69,6 +70,7 @@ ast_node_destroy(ast_node_t *node)
break;
case AST_VARIABLE_ASSIGNMENT:
ast_node_destroy(node->data.variable_assignment.expression);
+ case AST_FUNCTION_PARAMETER:
case AST_LITERAL:
case AST_UNKOWN_NODE:
case AST_VARIABLE:
@@ -93,7 +95,30 @@ ast_node_new_return_stmt(ast_node_t *argument)
}
ast_node_t *
-ast_node_new_function_declaration(string_view_t function_name, type_t return_type, ast_node_t *body)
+ast_node_new_function_parameter(string_view_t name, type_t type)
+{
+ ast_node_t *node = ast_node_new();
+
+ *node = (ast_node_t){
+ .kind = AST_FUNCTION_PARAMETER,
+ .data = {
+ .function_parameter = {
+ .identifier = {
+ .name = name,
+ },
+ .type = type,
+ },
+ },
+ };
+
+ return node;
+}
+
+ast_node_t *
+ast_node_new_function_declaration(string_view_t function_name,
+ type_t return_type,
+ vector_t *parameters,
+ ast_node_t *body)
{
ast_node_t *node = ast_node_new();
@@ -101,8 +126,11 @@ ast_node_new_function_declaration(string_view_t function_name, type_t return_typ
.kind = AST_FUNCTION_DECLARATION,
.data = {
.function = {
- .identifier = { .name = function_name },
- .return_type = return_type,
+ .prototype = {
+ .identifier = { .name = function_name },
+ .return_type = return_type,
+ .parameters = parameters,
+ },
.body = body,
}
},
@@ -277,14 +305,14 @@ ast_node_new_variable(ast_identifier_t *identifier, type_t result_type)
}
ast_node_t *
-ast_node_new_function_call(ast_identifier_t *identifier, type_t result_type)
+ast_node_new_function_call(ast_identifier_t *identifier, type_t result_type, vector_t *arguments)
{
ast_node_t *node = ast_node_new();
*node = (ast_node_t){
.kind = AST_FUNCTION_CALL,
.result_type = result_type,
- .data = { .function_call = { .identifier = identifier } },
+ .data = { .function_call = { .identifier = identifier, .arguments = arguments } },
};
return node;
@@ -298,7 +326,7 @@ ast_node_ns_get_function_node_by_sv(ast_node_t *ns, string_view_t name)
for (size_t i = 0; i < ns->data.ns.nodes->size; i++) {
ast_node_t *node = vector_at(ns->data.ns.nodes, i);
- if (node->kind == AST_FUNCTION_DECLARATION && string_view_eq(node->data.function.identifier.name, name)) {
+ if (node->kind == AST_FUNCTION_DECLARATION && string_view_eq(node->data.function.prototype.identifier.name, name)) {
return node;
}
}
@@ -323,6 +351,20 @@ ast_node_is_function_declaration(ast_node_t *node)
return node->kind == AST_FUNCTION_DECLARATION;
}
+ast_identifier_t *
+ast_node_function_declaration_identifier(ast_node_t *node)
+{
+ assert(node->kind == AST_FUNCTION_DECLARATION);
+
+ return &node->data.function.prototype.identifier;
+}
+
+string_view_t
+ast_node_function_declaration_name(ast_node_t *node)
+{
+ return ast_node_function_declaration_identifier(node)->name;
+}
+
char *
ast_type_to_str(type_t type)
{
diff --git a/src/ast.h b/src/ast.h
index 315de1e..9e0e514 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -57,12 +57,25 @@ typedef struct ast_variable_t
typedef struct ast_function_call_t
{
ast_identifier_t *identifier;
+ vector_t *arguments;
} ast_function_call_t;
-typedef struct ast_function_declaration_t
+typedef struct ast_function_parameter_t
+{
+ ast_identifier_t identifier;
+ type_t type;
+} ast_function_parameter_t;
+
+typedef struct ast_function_prototype_t
{
ast_identifier_t identifier;
type_t return_type;
+ vector_t *parameters;
+} ast_function_prototype_t;
+
+typedef struct ast_function_declaration_t
+{
+ ast_function_prototype_t prototype;
ast_node_t *body;
} ast_function_declaration_t;
@@ -133,6 +146,7 @@ typedef enum
AST_BINARY_OPERATION,
AST_BLOCK,
AST_FUNCTION_DECLARATION,
+ AST_FUNCTION_PARAMETER,
AST_FUNCTION_CALL,
AST_LITERAL,
AST_RETURN_STMT,
@@ -148,6 +162,7 @@ typedef union
ast_namespace_t ns;
ast_binary_operation_t binary_operation;
ast_function_declaration_t function;
+ ast_function_parameter_t function_parameter;
ast_function_call_t function_call;
ast_literal_t literal;
ast_block_t block;
@@ -187,10 +202,16 @@ ast_node_new_binary_operation(ast_binary_operation_kind_t kind,
type_t result_type);
ast_node_t *
-ast_node_new_function_declaration(string_view_t function_name, type_t return_type, ast_node_t *body);
+ast_node_new_function_declaration(string_view_t function_name,
+ type_t return_type,
+ vector_t *parameters,
+ ast_node_t *body);
+
+ast_node_t *
+ast_node_new_function_parameter(string_view_t name, type_t type);
ast_node_t *
-ast_node_new_function_call(ast_identifier_t *identifier, type_t result_type);
+ast_node_new_function_call(ast_identifier_t *identifier, type_t result_type, vector_t *arguments);
ast_node_t *
ast_node_new_return_stmt(ast_node_t *argument);
@@ -228,4 +249,10 @@ ast_node_is_variable_declaration(ast_node_t *node);
bool
ast_node_is_function_declaration(ast_node_t *node);
+ast_identifier_t *
+ast_node_function_declaration_identifier(ast_node_t *node);
+
+string_view_t
+ast_node_function_declaration_name(ast_node_t *node);
+
#endif /* AST_H */
diff --git a/src/ast_pretty_printer.c b/src/ast_pretty_printer.c
index 0d8b66c..a211fa4 100644
--- a/src/ast_pretty_printer.c
+++ b/src/ast_pretty_printer.c
@@ -65,6 +65,8 @@ ast_pretty_printer_print_ast(ast_pretty_printer_t *printer, ast_node_t *ast)
assert(ast);
switch (ast->kind) {
+ case AST_FUNCTION_PARAMETER:
+ break;
case AST_NAMESPACE:
ast_pretty_printer_printf(printer, "Namespace\n");
ast_pretty_printer_add_indentation(printer);
@@ -136,8 +138,8 @@ ast_pretty_printer_print_ast(ast_pretty_printer_t *printer, ast_node_t *ast)
break;
}
case AST_FUNCTION_DECLARATION: {
- ast_function_declaration_t function = ast->data.function;
- ast_pretty_printer_printf(printer, "FunctionDecl name='" SVFMT "'\n", SVARG(&function.identifier.name));
+ string_view_t function_name = ast_node_function_declaration_name(ast);
+ ast_pretty_printer_printf(printer, "FunctionDecl name='" SVFMT "'\n", SVARG(&function_name));
ast_pretty_printer_add_indentation(printer);
{
diff --git a/src/gas_assembly_generator.c b/src/gas_assembly_generator.c
index 2d6b993..26bf3f9 100644
--- a/src/gas_assembly_generator.c
+++ b/src/gas_assembly_generator.c
@@ -22,6 +22,9 @@
#include <stdlib.h>
#include <string.h>
+size_t available_calling_registers = 6;
+char *calling_registers[] = { "rdi", "rsi", "rdx", "rcx", "r8", "r9" };
+
static void
gas_assembly_generator_binary_operation(gas_assembly_generator_t *gen, ast_binary_operation_t *binary_operation);
@@ -183,6 +186,7 @@ gas_assembly_generator_compile(gas_assembly_generator_t *gen, ast_node_t *ast)
case AST_IF_STMT:
gas_assembly_generator_compile_if_stmt(gen, &ast->data.if_stmt);
break;
+ case AST_FUNCTION_PARAMETER:
case AST_UNKOWN_NODE:
assert(false && "unreachable");
}
@@ -204,10 +208,27 @@ gas_assembly_generator_compile_function(gas_assembly_generator_t *gen, ast_funct
uint64_t previous_index = gen->return_label_index;
uint64_t return_label_index = gen->return_label_index = gen->counter_label_index++;
- fprintf(gen->stream, SVFMT ":\n", SVARG(&func->identifier.name));
+ assert((func->prototype.parameters->size <= available_calling_registers) &&
+ "Not enough registers to process this function. Stack parameters will come soon.");
+
+ fprintf(gen->stream, SVFMT ":\n", SVARG(&func->prototype.identifier.name));
fprintf(gen->stream, " push %%rbp\n");
fprintf(gen->stream, " mov %%rsp, %%rbp\n");
+ for (size_t i = 0; i < func->prototype.parameters->size; i++) {
+ char *reg = calling_registers[i];
+ ast_node_t *parameter = vector_at(func->prototype.parameters, i);
+
+ assert(parameter->kind == AST_FUNCTION_PARAMETER);
+
+ gen->stack_offset -= 8;
+
+ fprintf(gen->stream, " mov %%%s, %d(%%rbp)\n", reg, gen->stack_offset);
+
+ ref_set_variable_reference_stack_offset(
+ gen->refs, &parameter->data.function_parameter.identifier, gen->stack_offset);
+ }
+
gas_assembly_generator_compile(gen, func->body);
fprintf(gen->stream, ".L%ld:\n", return_label_index);
@@ -215,11 +236,38 @@ gas_assembly_generator_compile_function(gas_assembly_generator_t *gen, ast_funct
fprintf(gen->stream, " ret\n");
gen->return_label_index = previous_index;
+ gen->stack_offset = 0;
}
static void
gas_assembly_generator_compile_function_call(gas_assembly_generator_t *gen, ast_function_call_t *func_call)
{
+ assert((func_call->arguments->size <= available_calling_registers) &&
+ "Not enough registers to process this function. Stack parameters will come soon.");
+
+ for (size_t i = 0; i < func_call->arguments->size; i++) {
+ char *reg = calling_registers[i];
+ ast_node_t *argument = vector_at(func_call->arguments, i);
+
+ gas_assembly_generator_compile(gen, argument);
+
+ switch (gen->latest_evaluation.kind) {
+ case EVALUATION_RESULT_AS_LITERAL_INTEGER:
+ fprintf(gen->stream, " mov $%ld, %%%s\n", gen->latest_evaluation.data.literal_int, reg);
+ break;
+ case EVALUATION_RESULT_ON_RAX:
+ fprintf(gen->stream, " mov %%rax, %%%s\n", reg);
+ break;
+ case EVALUATION_RESULT_ON_STACK:
+ fprintf(gen->stream, " mov %d(%%rbp), %%%s\n", gen->latest_evaluation.data.stack_offset, reg);
+ break;
+ case EVALUATION_RESULT_AS_LITERAL_BOOL:
+ fprintf(gen->stream, " mov %d(%%rbp), %%%s\n", gen->latest_evaluation.data.literal_bool, reg);
+ break;
+ case EVALUATION_RESULT_VOID:
+ assert(false && "Unexpected");
+ }
+ }
fprintf(gen->stream, " call " SVFMT "\n", SVARG(&func_call->identifier->name));
gas_assembly_generator_set_latest_evaluation_to_rax(gen);
}
diff --git a/src/lexer.c b/src/lexer.c
index a6c8ae2..7c668d3 100644
--- a/src/lexer.c
+++ b/src/lexer.c
@@ -165,6 +165,12 @@ lexer_next_token(lexer_t *lexer, token_t *token)
return;
}
+ if (lexer_current_char(lexer) == ',') {
+ lexer_define_literal_token_props(lexer, token, TOKEN_COMMA);
+ lexer_drop_char(lexer);
+ return;
+ }
+
if (lexer_current_char(lexer) == ';') {
lexer_define_literal_token_props(lexer, token, TOKEN_SEMICOLON);
lexer_drop_char(lexer);
@@ -412,6 +418,8 @@ token_kind_to_str(token_kind_t kind)
return ")";
case TOKEN_COLON:
return ":";
+ case TOKEN_COMMA:
+ return ",";
case TOKEN_SEMICOLON:
return ";";
case TOKEN_OCURLY:
diff --git a/src/lexer.h b/src/lexer.h
index 0e36ada..46912dc 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -32,6 +32,7 @@ typedef enum
// Literal Tokens
TOKEN_OPAREN,
TOKEN_CPAREN,
+ TOKEN_COMMA,
TOKEN_COLON,
TOKEN_SEMICOLON,
TOKEN_OCURLY,
diff --git a/src/parser.c b/src/parser.c
index 377ff73..fe2a2f0 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -33,6 +33,9 @@ parser_expression_matches_the_expected_type(parser_t *parser, ast_node_t *expres
static ast_node_t *
parser_parse_block_declarations(parser_t *parser, type_t result_type);
+static bool
+is_next_token_cparen(parser_t *parser);
+
void
parser_init(parser_t *parser, lexer_t *lexer, scope_t *scope)
{
@@ -354,16 +357,40 @@ parser_parse_factor(parser_t *parser)
referenced_node->data.variable_declaration.type);
}
+ // Function parameters are also variables.
+ if (referenced_node->kind == AST_FUNCTION_PARAMETER) {
+ return ast_node_new_variable(&referenced_node->data.function_parameter.identifier,
+ referenced_node->data.function_parameter.type);
+ }
+
// Parse function parameters
if (!drop_expected_token(parser, TOKEN_OPAREN)) {
return NULL;
}
- if (!drop_expected_token(parser, TOKEN_CPAREN)) {
- return NULL;
+
+ vector_t *arguments = vector_new();
+
+ while (!is_next_token_cparen(parser)) {
+ ast_node_t *argument = parser_parse_expression(parser);
+
+ if (argument == NULL) {
+ ast_node_destroy_vector(arguments);
+ return NULL;
+ }
+
+ vector_push_back(arguments, argument);
+
+ if (!is_next_token_cparen(parser) && !drop_expected_token(parser, TOKEN_COMMA)) {
+ ast_node_destroy_vector(arguments);
+ return NULL;
+ }
}
- return ast_node_new_function_call(&referenced_node->data.function.identifier,
- referenced_node->data.function.return_type);
+ drop_expected_token(parser, TOKEN_CPAREN);
+
+ return ast_node_new_function_call(ast_node_function_declaration_identifier(referenced_node),
+ referenced_node->data.function.prototype.return_type,
+ arguments);
}
default: {
parser_error_t error;
@@ -591,6 +618,14 @@ is_next_function_declaration(parser_t *parser)
}
static bool
+is_next_token_cparen(parser_t *parser)
+{
+ token_t token;
+ lexer_peek_next_token(parser->lexer, &token);
+ return token.kind == TOKEN_CPAREN;
+}
+
+static bool
is_next_token_eof(parser_t *parser)
{
token_t token;
@@ -722,10 +757,48 @@ parser_parse_block_declarations(parser_t *parser, type_t result_type)
return ast_node_new_block(body);
}
-static bool
-parser_parse_function_arguments(parser_t *parser)
+static vector_t *
+parser_parse_function_parameters(parser_t *parser)
{
- return drop_expected_token(parser, TOKEN_OPAREN) && drop_expected_token(parser, TOKEN_CPAREN);
+ if (!drop_expected_token(parser, TOKEN_OPAREN)) {
+ return NULL;
+ }
+
+ vector_t *vector = vector_new();
+
+ while (!is_next_token_cparen(parser)) {
+ token_t token_name;
+ type_t type;
+
+ if (!expected_token(&token_name, parser, TOKEN_NAME)) {
+ ast_node_destroy_vector(vector);
+ return NULL;
+ }
+ if (!drop_expected_token(parser, TOKEN_COLON)) {
+ ast_node_destroy_vector(vector);
+ return NULL;
+ }
+
+ if (!parser_parse_type(parser, &type)) {
+ ast_node_destroy_vector(vector);
+ return NULL;
+ }
+
+ ast_node_t *parameter = ast_node_new_function_parameter(token_name.value, type);
+
+ scope_push(parser->scope, &parameter->data.function_parameter.identifier, parameter);
+
+ vector_push_back(vector, parameter);
+
+ if (!is_next_token_cparen(parser) && !drop_expected_token(parser, TOKEN_COMMA)) {
+ ast_node_destroy_vector(vector);
+ return NULL;
+ }
+ }
+
+ drop_expected_token(parser, TOKEN_CPAREN);
+
+ return vector;
}
ast_node_t *
@@ -741,7 +814,9 @@ parser_parse_function_declaration(parser_t *parser)
return NULL;
}
- if (!parser_parse_function_arguments(parser)) {
+ vector_t *parameters = parser_parse_function_parameters(parser);
+
+ if (parameters == NULL) {
return NULL;
}
@@ -766,11 +841,11 @@ parser_parse_function_declaration(parser_t *parser)
return NULL;
}
- ast_node_t *node = ast_node_new_function_declaration(func_name_token.value, return_type, body);
+ ast_node_t *node = ast_node_new_function_declaration(func_name_token.value, return_type, parameters, body);
// TODO: When implementing function calls the scope must be pushed before the
// body to be parsed, otherwise recursion not gonna work.
- scope_push(parser->scope, &node->data.function.identifier, node);
+ scope_push(parser->scope, &node->data.function.prototype.identifier, node);
return node;
}
diff --git a/test/integration_test.c b/test/integration_test.c
index 985e574..608764a 100644
--- a/test/integration_test.c
+++ b/test/integration_test.c
@@ -44,6 +44,7 @@ test_examples(const MunitParameter params[], void *user_data_or_fixture)
assert_exit_status("../examples/arithmetics.pipa", 13);
assert_exit_status("../examples/variables.pipa", 28);
assert_exit_status("../examples/if.pipa", 42);
+ assert_exit_status("../examples/function_call.pipa", 42);
return MUNIT_OK;
}
diff --git a/test/parser_test.c b/test/parser_test.c
index ebb917c..44ffd57 100644
--- a/test/parser_test.c
+++ b/test/parser_test.c
@@ -104,7 +104,7 @@ test_parse_variable_definition(const MunitParameter params[], void *user_data_or
char actual[5];
- string_view_to_str(&ast_function->data.function.identifier.name, actual);
+ string_view_to_str(&ast_function->data.function.prototype.identifier.name, actual);
assert_string_equal("main", actual);
assert_int(AST_FUNCTION_DECLARATION, ==, ast_function->kind);
@@ -141,7 +141,7 @@ test_parse_boolean(const MunitParameter params[], void *user_data_or_fixture)
assert_true(ast_function != NULL);
- assert_string_view_equal("my_bool_fn", ast_function->data.function.identifier.name);
+ assert_string_view_equal("my_bool_fn", ast_node_function_declaration_name(ast_function));
assert_int(AST_FUNCTION_DECLARATION, ==, ast_function->kind);
ast_node_t *ast_variable = vector_at(ast_function->data.function.body->data.block.body, 0);
@@ -397,7 +397,7 @@ test_parse_basic_syntax_errors(const MunitParameter params[], void *user_data_or
assert_parser_error("main(): i32 { return 42; }", "Unexpected token 'main'");
assert_parser_error("fn (): i32 { return 42; }", "expected 'TOKEN_NAME' but got '('");
assert_parser_error("fn main): i32 { return 42; }", "expected '(' but got ')'");
- assert_parser_error("fn main(: i32 { return 42; }", "expected ')' but got ':'");
+ assert_parser_error("fn main(: i32 { return 42; }", "expected 'TOKEN_NAME' but got ':'");
assert_parser_error("fn main() i32 { return 42; }", "expected ':' but got 'TOKEN_NAME'");
assert_parser_error("fn main(): { return 42; }", "expected 'TOKEN_NAME' but got '{'");
assert_parser_error("fn main(): i32 return 42; }", "expected '{' but got 'return'");