From 7212ea877ed85d3b85af90c902639df44fc493f2 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 24 Aug 2019 00:48:40 +0200 Subject: Add exported variable (only functions for now), export main func, start execution from main func --- doc/Documentation.md | 20 ++++- executor/executor.h | 3 +- executor/x86_64/asm.c | 4 +- executor/x86_64/asm.h | 2 +- executor/x86_64/executor.c | 9 +- include/bytecode/bytecode.h | 60 ++++++++----- include/program.h | 16 ++-- include/ssa/ssa.h | 11 ++- include/tokenizer.h | 1 + src/bytecode/bytecode.c | 185 ++++++++++++++++++++++----------------- src/compiler.c | 21 ++++- src/parser.c | 38 +++++--- src/program.c | 135 +++++++++++++++++----------- src/ssa/ssa.c | 87 ++++++++++++------ src/tokenizer.c | 6 ++ tests/bytecode.amal | 10 +-- tests/errors/incorrect_main.amal | 1 + tests/main.c | 1 + 18 files changed, 394 insertions(+), 216 deletions(-) create mode 100644 tests/errors/incorrect_main.amal diff --git a/doc/Documentation.md b/doc/Documentation.md index 79e81aa..8b2133e 100644 --- a/doc/Documentation.md +++ b/doc/Documentation.md @@ -15,7 +15,7 @@ Instructions can be in 7 different formats: 6.1 Opcode(u8) + register(i8) + offset(i16)\ 6.2 Opcode(u8) + register(i8) + intermediate(u16)\ 6.3 Opcode(u8) + register(i8) + data(u16)\ -6.4 Opcode(u8) + num_param_reg(u8) + num_local_var_reg(u16) +6.4 Opcode(u8) + flags(u8) + num_local_var_reg(u16) 7. 5 bytes: Opcode(u8) + index(u16) + num_args(u8) + register(i8) ## Registers Registers have a range of 128. Local variables start from register 0 and increment while parameters start from -1 @@ -29,7 +29,7 @@ and writing it to a file, which is an IO bottlenecked operation and it won't ben and may even lose performance because of it. # Bytecode -The layout of the full bytecode is: Header (Intermediates Strings Functions External_Functions Instructions)* +The layout of the full bytecode is: Header (Intermediates Strings Functions External_Functions Exported_Functions Instructions)* # Bytecode header ## Header layout @@ -90,6 +90,22 @@ The versions in the header only changes for every release, not every change. |u8 |name_len|The length of the external function name, in bytes. Excluding the null-terminate character. | |u8* |name |The name of the external function, where the size is defined by @name_len. Names are null-terminated.| +# Bytecode exported functions +## Exported functions layout +|Type |Field |Description | +|-----------------|------------------|-----------------------------------------------------------------------------------------| +|u16 |num_export_func |The number of exported functions. | +|u32 |export_funcs_size |The size of the exported functions section, in bytes. | +|Exported function|Exported functions|Multiple exported functions, where the number of functions is defined by @num_export_func| + +## Exported function +|Type|Field |Description | +|----|------------------|--------------------------------------------------------------------------------------------------------------------------| +|u32 |instruction_offset|The offset in the instruction data where the exported function is defined. Is always 0 until the program has been started.| +|u8 |num_args |The number of arguments the functions has. | +|u8 |name_len |The length of the exported function name, in bytes. Excluding the null-terminate character. | +|u8* |name |The name of the exported function, where the size is defined by @name_len. Names are null-terminated. | + # Bytecode instructions ## Instructions layout |Type |Field |Description | diff --git a/executor/executor.h b/executor/executor.h index 979784b..df6c0d8 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -18,7 +18,8 @@ typedef struct amal_executor amal_executor; CHECK_RESULT int amal_executor_init(amal_executor **self); void amal_executor_deinit(amal_executor *self); -CHECK_RESULT int amal_executor_run(amal_executor *self); +CHECK_RESULT int amal_executor_run(amal_executor *self, u32 offset); +u32 amal_exec_get_code_offset(amal_executor *self); /* These functions are called for every file in the program. Every file has its own list of strings, intermediates, functions and external functions */ CHECK_RESULT int amal_executor_instructions_start(amal_executor *self, u16 num_functions); diff --git a/executor/x86_64/asm.c b/executor/x86_64/asm.c index c741f15..f2bb801 100644 --- a/executor/x86_64/asm.c +++ b/executor/x86_64/asm.c @@ -210,7 +210,7 @@ static void asm_print_code_hex(Asm *self) { } #endif -int asm_execute(Asm *self) { +int asm_execute(Asm *self, u32 offset) { void (*func)(); if(mprotect(self->code, self->allocated_size, PROT_READ | PROT_EXEC) != 0) return -errno; @@ -218,7 +218,7 @@ int asm_execute(Asm *self) { /*asm_print_code_hex(self);*/ /* TODO: Verify if this is valid on all platforms. According to ISO C standard it isn't? */ - *(void**)(&func) = self->code; + *(void**)(&func) = self->code + offset; func(); return 0; } diff --git a/executor/x86_64/asm.h b/executor/x86_64/asm.h index 51f2d84..ace1ecf 100644 --- a/executor/x86_64/asm.h +++ b/executor/x86_64/asm.h @@ -49,7 +49,7 @@ void asm_deinit(Asm *self); usize asm_get_size(Asm *self); -CHECK_RESULT int asm_execute(Asm *self); +CHECK_RESULT int asm_execute(Asm *self, u32 offset); CHECK_RESULT int asm_nop(Asm *self); diff --git a/executor/x86_64/executor.c b/executor/x86_64/executor.c index fbe227a..335790a 100644 --- a/executor/x86_64/executor.c +++ b/executor/x86_64/executor.c @@ -64,9 +64,14 @@ void amal_executor_deinit(amal_executor *self) { am_free(impl); } -int amal_executor_run(amal_executor *self) { +int amal_executor_run(amal_executor *self, u32 offset) { IMPL - return asm_execute(&impl->asm); + return asm_execute(&impl->asm, offset); +} + +u32 amal_exec_get_code_offset(amal_executor *self) { + IMPL + return asm_get_size(&impl->asm); } int amal_executor_instructions_start(amal_executor *self, u16 num_functions) { diff --git a/include/bytecode/bytecode.h b/include/bytecode/bytecode.h index 1e4c35d..64b54b4 100644 --- a/include/bytecode/bytecode.h +++ b/include/bytecode/bytecode.h @@ -25,7 +25,7 @@ 6.1 Opcode(u8) + register(i8) + offset(i16)\ 6.2 Opcode(u8) + register(i8) + intermediate(u16)\ 6.3 Opcode(u8) + register(i8) + data(u16)\ - 6.4 Opcode(u8) + num_param_reg(u8) + num_local_var_reg(u16) + 6.4 Opcode(u8) + flags(u8) + num_local_var_reg(u16) 7. 5 bytes: Opcode(u8) + index(u16) + num_args(u8) + register(i8) # Registers Registers have a range of 128. Local variables start from register 0 and increment while parameters start from -1 @@ -33,30 +33,42 @@ */ typedef enum { AMAL_OP_NOP, /* No operation (do nothing). This can be used for patching code */ - AMAL_OP_SETZ, /* setz reg - Set register value to 0 */ - AMAL_OP_MOV, /* mov dst, src - Move src register to dst register */ - AMAL_OP_MOVI, /* movi dst, src - Move src intermediate to dst register */ - AMAL_OP_MOVD, /* movd dst, src - Move src data to dst register */ - AMAL_OP_ADD, /* add dst, reg1, reg2 - Add reg1 and reg2 and put the result in dst */ - AMAL_OP_SUB, /* sub dst, reg1, reg2 - Substract reg2 from reg1 and put the result in dst */ - AMAL_OP_IMUL, /* imul dst, reg1, reg2 - Signed multiplication */ - AMAL_OP_MUL, /* mul dst, reg1, reg2 - Unsigned multiplication */ - AMAL_OP_IDIV, /* idiv dst, reg1, reg2 - Signed division */ - AMAL_OP_DIV, /* div dst, reg1, reg2 - Unsigned division */ - AMAL_OP_PUSH, /* push reg - Push register onto stack */ - AMAL_OP_PUSHI, /* pushi int - Push intermediate onto stack */ - AMAL_OP_PUSHD, /* pushd data - Push data onto stack */ - AMAL_OP_CALL, /* call fi, num_args, dst - Call a function using function index (fi) and num_args arguments. The result is stored in register dst. fi is u16 and num_args is u8 */ - AMAL_OP_CALLR, /* callr reg, num_args - Call a function using a register. Used for function pointers. num_args is u8 */ - AMAL_OP_CALLE, /* calle efi, num_args, dst - Call an extern function using extern function index (efi) and num_args arguments. The result is stored in register dst. efi is u16 and num_args is u8 */ - AMAL_OP_CMP, /* cmp dst, reg1, reg2 - Set dst to 1 if reg1 equals reg2, otherwise set it to 0 */ - AMAL_OP_JZ, /* jz reg, offset - Jump to offset if reg is zero. offset is i16 */ - AMAL_OP_JMP, /* jmp offset - Unconditional jump to offset. offset is i16 */ - AMAL_OP_RET, /* ret reg - Return from the function with reg result */ - AMAL_OP_FUNC_START, /* func_start num_param_reg, num_local_var_reg - Start of a function which has num_param_reg parameter registers allocated and num_local_var_reg local variable registers allocated. num_param_reg is u8 and num_local_var_reg is u16 */ - AMAL_OP_FUNC_END /* func_end - End of a function. Implementation should do a ret here */ + AMAL_OP_SETZ, /* setz reg - Set register value to 0 */ + AMAL_OP_MOV, /* mov dst, src - Move src register to dst register */ + AMAL_OP_MOVI, /* movi dst, src - Move src intermediate to dst register */ + AMAL_OP_MOVD, /* movd dst, src - Move src data to dst register */ + AMAL_OP_ADD, /* add dst, reg1, reg2 - Add reg1 and reg2 and put the result in dst */ + AMAL_OP_SUB, /* sub dst, reg1, reg2 - Substract reg2 from reg1 and put the result in dst */ + AMAL_OP_IMUL, /* imul dst, reg1, reg2 - Signed multiplication */ + AMAL_OP_MUL, /* mul dst, reg1, reg2 - Unsigned multiplication */ + AMAL_OP_IDIV, /* idiv dst, reg1, reg2 - Signed division */ + AMAL_OP_DIV, /* div dst, reg1, reg2 - Unsigned division */ + AMAL_OP_PUSH, /* push reg - Push register onto stack */ + AMAL_OP_PUSHI, /* pushi int - Push intermediate onto stack */ + AMAL_OP_PUSHD, /* pushd data - Push data onto stack */ + AMAL_OP_CALL, /* call fi, num_args, dst - Call a function using function index (fi) and num_args arguments. The result is stored in register dst. fi is u16 and num_args is u8 */ + AMAL_OP_CALLR, /* callr reg, num_args - Call a function using a register. Used for function pointers. num_args is u8 */ + AMAL_OP_CALLE, /* calle efi, num_args, dst - Call an extern function using extern function index (efi) and num_args arguments. The result is stored in register dst. efi is u16 and num_args is u8 */ + AMAL_OP_CMP, /* cmp dst, reg1, reg2 - Set dst to 1 if reg1 equals reg2, otherwise set it to 0 */ + AMAL_OP_JZ, /* jz reg, offset - Jump to offset if reg is zero. offset is i16 */ + AMAL_OP_JMP, /* jmp offset - Unconditional jump to offset. offset is i16 */ + AMAL_OP_RET, /* ret reg - Return from the function with reg result */ + AMAL_OP_FUNC_START, /* func_start flags, num_local_var_reg - Start of a function which has @num_local_var_reg local variable registers allocated and has the flag @flag. @flag is u8 and @num_local_var_reg is u16 */ + AMAL_OP_FUNC_END /* func_end - End of a function. Implementation should do a ret here */ } AmalOpcode; +#define AMAL_BYTECODE_MAGIC_NUMBER (u32)0xdec05eba +#define AMAL_BYTECODE_MAJOR_VERSION 1 +#define AMAL_BYTECODE_MINOR_VERSION 0 +#define AMAL_BYTECODE_PATCH_VERSION 0 + +#define AMAL_BYTECODE_NUM_REGISTERS 256 + +typedef enum { + FUNC_FLAG_NONE = 0, + FUNC_FLAG_EXPORTED = (1 << 1) +} amal_func_flag; + typedef u8 AmalOpcodeType; typedef struct { @@ -71,6 +83,8 @@ typedef struct { CHECK_RESULT int bytecode_init(Bytecode *self, ArenaAllocator *allocator); +CHECK_RESULT int buffer_append_header(Buffer *program_data); + /* longjump to self->env on failure */ void generate_bytecode_from_ssa(BytecodeCompilerContext *self); diff --git a/include/program.h b/include/program.h index 075bfca..e3a4ac9 100644 --- a/include/program.h +++ b/include/program.h @@ -31,13 +31,10 @@ #define AMAL_PROGRAM_INSTRUCTION_INVALID_EXTERN_FUNC_INDEX -19 #define AMAL_PROGRAM_NO_SUCH_EXTERNAL_FUNCTION -20 #define AMAL_PROGRAM_EXTERN_FUNC_ALREADY_EXISTS -21 - -#define AMAL_PROGRAM_MAGIC_NUMBER (u32)0xdec05eba -#define AMAL_PROGRAM_MAJOR_VERSION 1 -#define AMAL_PROGRAM_MINOR_VERSION 0 -#define AMAL_PROGRAM_PATCH_VERSION 0 - -#define AMAL_PROGRAM_NUM_REGISTERS 256 +#define AMAL_PROGRAM_INVALID_EXPORTED_FUNCTIONS -22 +#define AMAL_PROGRAM_INVALID_EXPORTED_FUNCTIONS_SIZE -23 +#define AMAL_PROGRAM_INSTRUCTION_INVALID_EXPORTED_FUNC_INDEX -24 +#define AMAL_PROGRAM_NO_MAIN_FUNC -25 #define AMAL_PROGRAM_ARGS_SIZE_VARARGS -1 @@ -53,12 +50,17 @@ typedef struct { u8 *intermediates_start; /* Reference inside @data */ u8 *strings_start; /* Reference inside @data */ u8 *extern_funcs_start; /* Reference inside @data */ + u8 *exported_funcs; /* Reference inside @data */ + u8 *exported_funcs_end; /* Reference inside @data */ + usize read_index; + u32 main_func_instruction_offset; u16 num_intermediates; u16 num_strings; u16 num_functions; u16 num_extern_functions; + u16 num_exported_functions; ArenaAllocator allocator; /* Owned. Used by @extern_funcs_map */ HashMapType(BufferView, ProgramExternFunc) extern_funcs_map; diff --git a/include/ssa/ssa.h b/include/ssa/ssa.h index b2c8065..ed147bf 100644 --- a/include/ssa/ssa.h +++ b/include/ssa/ssa.h @@ -49,11 +49,17 @@ typedef struct { BufferView name; } SsaExternFunc; +typedef struct { + FunctionSignature *func_sig; + BufferView name; +} SsaExportFunc; + typedef i16 JumpOffset; typedef i16 SsaRegister; typedef u16 SsaIntermediateIndex; typedef u16 SsaStringIndex; typedef u16 SsaExternFuncIndex; +typedef u16 SsaExportFuncIndex; typedef u16 SsaFuncIndex; typedef struct { @@ -64,9 +70,11 @@ typedef struct { Buffer/*BufferView*/ strings; HashMapType(BufferView, SsaExternFuncIndex) extern_funcs_map; Buffer/*SsaExternFunc*/ extern_funcs; + Buffer/*SsaExportFunc*/ export_funcs; SsaIntermediateIndex intermediate_counter; SsaStringIndex string_counter; SsaExternFuncIndex extern_func_counter; + SsaExportFuncIndex export_func_counter; SsaRegister reg_counter; SsaRegister param_counter; SsaFuncIndex func_counter; @@ -85,8 +93,7 @@ typedef struct { } SsaInsForm2; typedef struct { - SsaFuncIndex func_index; - u16 num_params_regs; + u8 flags; u16 num_local_vars_regs; } SsaInsFuncStart; diff --git a/include/tokenizer.h b/include/tokenizer.h index 16123cc..57ed9de 100644 --- a/include/tokenizer.h +++ b/include/tokenizer.h @@ -38,6 +38,7 @@ typedef enum { TOK_ELSE, TOK_WHILE, TOK_EXTERN, + TOK_EXPORT, TOK_RETURN } Token; diff --git a/src/bytecode/bytecode.c b/src/bytecode/bytecode.c index 80cc95b..fe3dc0f 100644 --- a/src/bytecode/bytecode.c +++ b/src/bytecode/bytecode.c @@ -23,6 +23,36 @@ int bytecode_init(Bytecode *self, ArenaAllocator *allocator) { return buffer_init(&self->data, allocator); } +/*doc(Bytecode) +The layout of the full bytecode is: Header (Intermediates Strings Functions External_Functions Exported_Functions Instructions)* +*/ + +CHECK_RESULT int buffer_append_header(Buffer *program_data) { + /*doc(Bytecode header) + # Header layout + |Type|Field |Description | + |----|-------------|----------------------------------------------------------------------------| + |u32 |Magic number |The magic number used to identify an amalgam bytecode file. | + |u8 |Major version|The major version of the bytecode. Updates in this is a breaking change. | + |u8 |Minor version|The minor version of the bytecode. Updates in this are backwards compatible.| + |u8 |Patch version|The patch version of the bytecode. Updates in this are only minor bug fixes.| + + The versions in the header only changes for every release, not every change. + */ + + const u32 magic_number = AMAL_BYTECODE_MAGIC_NUMBER; + const u8 major_version = AMAL_BYTECODE_MAJOR_VERSION; + const u8 minor_version = AMAL_BYTECODE_MINOR_VERSION; + const u8 patch_version = AMAL_BYTECODE_PATCH_VERSION; + + return_if_error(buffer_append(program_data, &magic_number, 4)); + return_if_error(buffer_append(program_data, &major_version, 1)); + return_if_error(buffer_append(program_data, &minor_version, 1)); + return_if_error(buffer_append(program_data, &patch_version, 1)); + + return 0; +} + static CHECK_RESULT usize ssa_extract_data(u8 *instruction_data, void *result, usize size) { am_memcpy(result, instruction_data, size); return size; @@ -43,18 +73,12 @@ static void add_intermediates(BytecodeCompilerContext *self) { |u64 |Value|The type of the value depends on the value of @Type.| */ - Ssa *ssa; - Buffer *instructions; - SsaNumber *intermediate; - SsaNumber *intermediates_end; - u32 intemediates_size; - - ssa = self->parser->ssa; - instructions = &self->bytecode.data; - intermediate = buffer_begin(&ssa->intermediates); - intermediates_end = buffer_end(&ssa->intermediates); + Ssa *ssa = self->parser->ssa; + Buffer *instructions = &self->bytecode.data; + SsaNumber *intermediate = buffer_begin(&ssa->intermediates); + SsaNumber *intermediates_end = buffer_end(&ssa->intermediates); - intemediates_size = (sizeof(u8) + sizeof(u64)) * buffer_get_size(&ssa->intermediates, SsaNumber); + u32 intemediates_size = (sizeof(u8) + sizeof(u64)) * buffer_get_size(&ssa->intermediates, SsaNumber); throw_if_error(buffer_expand(instructions, sizeof(u32) + intemediates_size)); throw_if_error(buffer_append(instructions, &intemediates_size, sizeof(u32))); for(; intermediate != intermediates_end; ++intermediate) { @@ -80,17 +104,11 @@ static void add_strings(BytecodeCompilerContext *self) { |u8* |Data|The data of the string, where the size is defined by @Size. Strings are null-terminated.| */ - Ssa *ssa; - Buffer *instructions; - BufferView *string; - BufferView *strings_end; - u32 strings_size; - - ssa = self->parser->ssa; - instructions = &self->bytecode.data; - string = buffer_begin(&ssa->strings); - strings_end = buffer_end(&ssa->strings); - strings_size = 0; + Ssa *ssa = self->parser->ssa; + Buffer *instructions = &self->bytecode.data; + BufferView *string = buffer_begin(&ssa->strings); + BufferView *strings_end = buffer_end(&ssa->strings); + u32 strings_size = 0; for(; string != strings_end; ++string) { strings_size += sizeof(u16) + string->size + 1; /* +1 for null-termination of string */ @@ -136,17 +154,11 @@ static void add_extern_functions(BytecodeCompilerContext *self) { |u8 |name_len|The length of the external function name, in bytes. Excluding the null-terminate character. | |u8* |name |The name of the external function, where the size is defined by @name_len. Names are null-terminated.| */ - - Ssa *ssa; - Buffer *instructions; - SsaExternFunc *extern_func, *extern_func_end; - u32 extern_funcs_size; - - ssa = self->parser->ssa; - instructions = &self->bytecode.data; - extern_func = buffer_begin(&ssa->extern_funcs); - extern_func_end = buffer_end(&ssa->extern_funcs); - extern_funcs_size = 0; + Ssa *ssa = self->parser->ssa; + Buffer *instructions = &self->bytecode.data; + SsaExternFunc *extern_func = buffer_begin(&ssa->extern_funcs); + SsaExternFunc *extern_func_end = buffer_end(&ssa->extern_funcs); + u32 extern_funcs_size = 0; for(; extern_func != extern_func_end; ++extern_func) { extern_funcs_size += sizeof(u8) + sizeof(u8) + extern_func->name.size + 1; /* +1 for null-termination of string */ @@ -158,9 +170,9 @@ static void add_extern_functions(BytecodeCompilerContext *self) { throw_if_error(buffer_append(instructions, &extern_funcs_size, sizeof(u32))); for(; extern_func != extern_func_end; ++extern_func) { const char null_s = '\0'; - u8 num_args; - num_args = buffer_get_size(&extern_func->func_sig->parameters, FunctionParameter); + u8 num_args = buffer_get_size(&extern_func->func_sig->parameters, FunctionParameter); throw_if_error(buffer_append(instructions, &num_args, sizeof(num_args))); + /* TODO: Add namespace to the function name */ throw_if_error(buffer_append(instructions, &extern_func->name.size, sizeof(u8))); throw_if_error(buffer_append(instructions, extern_func->name.data, extern_func->name.size)); throw_if_error(buffer_append(instructions, &null_s, sizeof(char))); @@ -169,6 +181,51 @@ static void add_extern_functions(BytecodeCompilerContext *self) { assert(sizeof(SsaExternFuncIndex) == sizeof(u16) && "Program decoder needs to be updated since size of extern func index has changed"); } +static void add_export_functions(BytecodeCompilerContext *self) { + /*doc(Bytecode exported functions) + # Exported functions layout + |Type |Field |Description | + |-----------------|------------------|-----------------------------------------------------------------------------------------| + |u16 |num_export_func |The number of exported functions. | + |u32 |export_funcs_size |The size of the exported functions section, in bytes. | + |Exported function|Exported functions|Multiple exported functions, where the number of functions is defined by @num_export_func| + + # Exported function + |Type|Field |Description | + |----|------------------|--------------------------------------------------------------------------------------------------------------------------| + |u32 |instruction_offset|The offset in the instruction data where the exported function is defined. Is always 0 until the program has been started.| + |u8 |num_args |The number of arguments the functions has. | + |u8 |name_len |The length of the exported function name, in bytes. Excluding the null-terminate character. | + |u8* |name |The name of the exported function, where the size is defined by @name_len. Names are null-terminated. | + */ + Ssa *ssa = self->parser->ssa; + Buffer *instructions = &self->bytecode.data; + SsaExportFunc *export_func = buffer_begin(&ssa->export_funcs); + SsaExportFunc *export_func_end = buffer_end(&ssa->export_funcs); + u32 export_funcs_size = 0; + + for(; export_func != export_func_end; ++export_func) { + export_funcs_size += sizeof(u32) + sizeof(u8) + sizeof(u8) + export_func->name.size + 1; /* +1 for null-termination of string */ + } + export_func = buffer_begin(&ssa->export_funcs); + + throw_if_error(buffer_expand(instructions, sizeof(u16) + sizeof(u32) + export_funcs_size)); + throw_if_error(buffer_append(instructions, &ssa->export_func_counter, sizeof(u16))); + throw_if_error(buffer_append(instructions, &export_funcs_size, sizeof(u32))); + for(; export_func != export_func_end; ++export_func) { + const char null_s = '\0'; + const u32 instruction_offset = 0; + u8 num_args = buffer_get_size(&export_func->func_sig->parameters, FunctionParameter); + throw_if_error(buffer_append(instructions, &instruction_offset, sizeof(instruction_offset))); + throw_if_error(buffer_append(instructions, &num_args, sizeof(num_args))); + throw_if_error(buffer_append(instructions, &export_func->name.size, sizeof(u8))); + throw_if_error(buffer_append(instructions, export_func->name.data, export_func->name.size)); + throw_if_error(buffer_append(instructions, &null_s, sizeof(char))); + } + + assert(sizeof(SsaExportFuncIndex) == sizeof(u16) && "Program decoder needs to be updated since size of export func index has changed"); +} + static void add_ins1(BytecodeCompilerContext *self, AmalOpcode opcode, const char *fmt) { throw_if_error(buffer_append(&self->bytecode.data, &opcode, sizeof(AmalOpcodeType))); fprintf(stderr, fmt); @@ -177,10 +234,8 @@ static void add_ins1(BytecodeCompilerContext *self, AmalOpcode opcode, const cha static void add_ins2(BytecodeCompilerContext *self, AmalOpcode opcode, i8 reg, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(reg))); instructions->data[index] = opcode; @@ -190,10 +245,8 @@ static void add_ins2(BytecodeCompilerContext *self, AmalOpcode opcode, i8 reg, c } static void add_ins3(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_reg, i8 src_reg, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(dst_reg) + sizeof(src_reg))); instructions->data[index] = opcode; @@ -204,10 +257,8 @@ static void add_ins3(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_re } static void add_ins4(BytecodeCompilerContext *self, AmalOpcode opcode, u16 data, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(data))); instructions->data[index] = opcode; @@ -217,10 +268,8 @@ static void add_ins4(BytecodeCompilerContext *self, AmalOpcode opcode, u16 data, } static void add_ins5(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_reg, i8 reg1, i8 reg2, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(dst_reg) + sizeof(reg1) + sizeof(reg2))); instructions->data[index] = opcode; @@ -232,10 +281,8 @@ static void add_ins5(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_re } static void add_ins6(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_reg, u16 data, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(dst_reg) + sizeof(data))); instructions->data[index] = opcode; @@ -246,10 +293,8 @@ static void add_ins6(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_re } static void add_ins7(BytecodeCompilerContext *self, AmalOpcode opcode, u16 idx, i8 num_args, i8 dst_reg, const char *fmt) { - Buffer *instructions; - size_t index; - instructions = &self->bytecode.data; - index = instructions->size; + Buffer *instructions = &self->bytecode.data; + size_t index = instructions->size; throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(idx) + sizeof(num_args) + sizeof(dst_reg))); instructions->data[index] = opcode; @@ -260,25 +305,6 @@ static void add_ins7(BytecodeCompilerContext *self, AmalOpcode opcode, u16 idx, fputc('\n', stderr); } -#if 0 -#define NUM_MAX_REGS 256 -#define NUM_MAX_FUNC_ARGS 32 - -static const char* lhs_expr_get_c_name(BytecodeCompilerContext *self, LhsExpr *lhs_expr) { - if(lhs_expr == self->parser->compiler->default_types.i64) { - return "i64"; - } else if(lhs_expr == self->parser->compiler->default_types.f64) { - return "f64"; - } else if(lhs_expr == self->parser->compiler->default_types.str) { - return"const char*"; - } else { - amal_log_error("Invalid rhs type %p", lhs_expr); - assert(bool_false && "TODO: Implement"); - return ""; - } -} -#endif - static void add_instructions(BytecodeCompilerContext *self) { /*doc(Bytecode instructions) # Instructions layout @@ -359,7 +385,7 @@ static void add_instructions(BytecodeCompilerContext *self) { } case SSA_FUNC_START: { instruction += ssa_extract_data(instruction, &ssa_ins_func_start, sizeof(ssa_ins_func_start)); - add_ins6(self, AMAL_OP_FUNC_START, ssa_ins_func_start.num_params_regs, ssa_ins_func_start.num_local_vars_regs, "func_start %d, %u"); + add_ins6(self, AMAL_OP_FUNC_START, ssa_ins_func_start.flags, ssa_ins_func_start.num_local_vars_regs, "func_start 0x%02x, %u"); break; } case SSA_FUNC_END: { @@ -424,5 +450,6 @@ void generate_bytecode_from_ssa(BytecodeCompilerContext *self) { add_strings(self); add_functions(self); add_extern_functions(self); + add_export_functions(self); add_instructions(self); } diff --git a/src/compiler.c b/src/compiler.c index fd9e4ae..3ccb86a 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -373,14 +373,17 @@ int amal_compiler_load_file(amal_compiler_options *options, amal_program *progra return result; } -/* TODO: Verify main func has correct signature */ +/* + TODO: Instead of using amal_log_error, print error message with tokenizer to show where the error is. + This requires finding tokenizer by code reference. +*/ static CHECK_RESULT int validate_main_func(FileScopeReference *main_file_scope, LhsExpr **main_func) { const BufferView main_func_name = { "main", 4 }; LhsExpr *main_func_expr; main_func_expr = structdecl_get_field_by_name(&main_file_scope->parser->struct_decl, main_func_name); if(!main_func_expr) { - amal_log_error("main function missing from start file \"%.*s\"", main_file_scope->canonical_path.size, main_file_scope->canonical_path.data); + amal_log_error("main function missing from start file \"%.*s\". Note: the main function has to be in the file scope.", main_file_scope->canonical_path.size, main_file_scope->canonical_path.data); return AMAL_COMPILER_ERR; } *main_func = main_func_expr; @@ -400,6 +403,19 @@ static CHECK_RESULT int validate_main_func(FileScopeReference *main_file_scope, return AMAL_COMPILER_ERR; } + { + const FunctionDecl *func_decl = main_func_expr->rhs_expr->value.func_decl; + if(buffer_get_size(&func_decl->signature->parameters, FunctionParameter) != 0) { + amal_log_error("main function in start file \"%.*s\" has to be a closure with no parameters", main_file_scope->canonical_path.size, main_file_scope->canonical_path.data); + return AMAL_COMPILER_ERR; + } + + if(buffer_get_size(&func_decl->signature->return_types, FunctionReturnType) != 0) { + amal_log_error("main function in start file \"%.*s\" has to be a closure that doesn't return anything", main_file_scope->canonical_path.size, main_file_scope->canonical_path.data); + return AMAL_COMPILER_ERR; + } + } + return 0; } @@ -437,6 +453,7 @@ int amal_compiler_internal_load_file(amal_compiler *self, const char *filepath, */ LhsExpr *main_func; + /* Wait for all parsing to be done */ if(!thread_pool_join_all_tasks(&self->stage_task_thread_pool)) return -1; amal_log_info("Finished parsing all files, resolving AST"); diff --git a/src/parser.c b/src/parser.c index 85165d7..ff34663 100644 --- a/src/parser.c +++ b/src/parser.c @@ -267,12 +267,13 @@ void parser_parse_var_type_def(Parser *self, VariableType *result) { } /* -LHS_DECLARATION = 'extern'? 'pub'? 'const'|'var' TOK_IDENTIFIER VAR_TYPE_DEF? +LHS_DECLARATION = ('extern'|'export')? 'pub'? 'const'|'var' TOK_IDENTIFIER VAR_TYPE_DEF? */ static CHECK_RESULT LhsExpr* parser_parse_declaration_lhs(Parser *self) { LhsExpr *result; DeclFlag decl_flag; bool is_extern; + bool is_export; bool is_pub; bool is_const; BufferView var_name; @@ -283,10 +284,21 @@ static CHECK_RESULT LhsExpr* parser_parse_declaration_lhs(Parser *self) { decl_flag |= DECL_FLAG_EXTERN; if(self->has_func_parent) { self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + tokenizer_get_error_index(&self->tokenizer), "Only declarations in global structs can be extern"); throw(PARSER_UNEXPECTED_TOKEN); } + } else { + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_EXPORT, &is_export)); + if(is_export) { + decl_flag |= DECL_FLAG_EXPORT; + if(self->has_func_parent) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "Only declarations in global structs can be exported"); + throw(PARSER_UNEXPECTED_TOKEN); + } + } } throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_PUB, &is_pub)); @@ -294,7 +306,7 @@ static CHECK_RESULT LhsExpr* parser_parse_declaration_lhs(Parser *self) { decl_flag |= DECL_FLAG_PUB; if(self->has_func_parent) { self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + tokenizer_get_error_index(&self->tokenizer), "Only declarations in global structs can be public"); throw(PARSER_UNEXPECTED_TOKEN); } @@ -306,10 +318,10 @@ static CHECK_RESULT LhsExpr* parser_parse_declaration_lhs(Parser *self) { } else { bool isVar; - if(is_extern) { + if(is_extern || is_export) { self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), - "Extern variable have to be declared with \"const\""); + tokenizer_get_error_index(&self->tokenizer), + "Extern and exported variables have to be declared with \"const\""); throw(PARSER_UNEXPECTED_TOKEN); } @@ -732,10 +744,10 @@ static Ast* parser_parse_lhs_rhs(Parser *self, LhsExpr *lhs_expr) { func_decl = parser_parse_closure(self); if(func_decl) { - if(buffer_get_size(&func_decl->signature->return_types, FunctionReturnType) > 1 && LHS_EXPR_IS_EXTERN(lhs_expr)) { + if(buffer_get_size(&func_decl->signature->return_types, FunctionReturnType) > 1 && (LHS_EXPR_IS_EXTERN(lhs_expr) || LHS_EXPR_IS_EXPORT(lhs_expr))) { self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, lhs_expr->var_name.data), - "Extern closure can only have one return value"); + "Extern and export closures can only have one return value"); throw(PARSER_ERR); } throw_if_error(ast_create(self->allocator, func_decl, AST_FUNCTION_DECL, &result)); @@ -796,7 +808,7 @@ Ast* parser_parse_body(Parser *self) { if(LHS_EXPR_IS_EXTERN(lhs_expr)) { throw_if_error(tokenizer_accept(&self->tokenizer, TOK_SEMICOLON)); if (lhs_expr->type.type == VARIABLE_TYPE_NONE) { - self->error = tokenizer_create_error(&self->tokenizer, self->tokenizer.prev_index, "A variable can't be declared without a type or assignment"); + self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, lhs_expr->var_name.data), "An extern variable can't be declared without a type"); throw(PARSER_UNEXPECTED_TOKEN); } return result; @@ -804,10 +816,14 @@ Ast* parser_parse_body(Parser *self) { throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_SEMICOLON, &match)); if(match) { if(lhs_expr->type.type == VARIABLE_TYPE_SIGNATURE) { - self->error = tokenizer_create_error(&self->tokenizer, self->tokenizer.prev_index, "Expected function declaration. Only extern functions can have empty declarations."); + self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, lhs_expr->var_name.data), "Expected function declaration. Only extern functions can have empty declarations."); throw(PARSER_UNEXPECTED_TOKEN); } else if (lhs_expr->type.type == VARIABLE_TYPE_NONE) { - self->error = tokenizer_create_error(&self->tokenizer, self->tokenizer.prev_index, "A variable can't be declared without a type or assignment"); + /* TODO: Allow this for non-const variables */ + self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, lhs_expr->var_name.data), "A variable can't be declared without a type or assignment"); + throw(PARSER_UNEXPECTED_TOKEN); + } else if(LHS_EXPR_IS_CONST(lhs_expr)) { + self->error = tokenizer_create_error(&self->tokenizer, tokenizer_get_code_reference_index(&self->tokenizer, lhs_expr->var_name.data), "A const variable can't be declared without assignment"); throw(PARSER_UNEXPECTED_TOKEN); } return result; diff --git a/src/program.c b/src/program.c index 082f9fd..8a17ac9 100644 --- a/src/program.c +++ b/src/program.c @@ -26,52 +26,26 @@ typedef struct { NumberUnion value; } Number; -/*doc(Bytecode) -The layout of the full bytecode is: Header (Intermediates Strings Functions External_Functions Instructions)* -*/ - -static CHECK_RESULT int amal_program_append_header(amal_program *self) { - /*doc(Bytecode header) - # Header layout - |Type|Field |Description | - |----|-------------|----------------------------------------------------------------------------| - |u32 |Magic number |The magic number used to identify an amalgam bytecode file. | - |u8 |Major version|The major version of the bytecode. Updates in this is a breaking change. | - |u8 |Minor version|The minor version of the bytecode. Updates in this are backwards compatible.| - |u8 |Patch version|The patch version of the bytecode. Updates in this are only minor bug fixes.| - - The versions in the header only changes for every release, not every change. - */ - - const u32 magic_number = AMAL_PROGRAM_MAGIC_NUMBER; - const u8 major_version = AMAL_PROGRAM_MAJOR_VERSION; - const u8 minor_version = AMAL_PROGRAM_MINOR_VERSION; - const u8 patch_version = AMAL_PROGRAM_PATCH_VERSION; - - return_if_error(buffer_append(&self->data, &magic_number, 4)); - return_if_error(buffer_append(&self->data, &major_version, 1)); - return_if_error(buffer_append(&self->data, &minor_version, 1)); - return_if_error(buffer_append(&self->data, &patch_version, 1)); - - return 0; -} - int amal_program_init(amal_program *self) { ignore_result_int(buffer_init(&self->data, NULL)); - self->string_indices = NULL; - self->extern_func_indices = NULL; - self->intermediates_start = NULL; - self->strings_start = NULL; - self->extern_funcs_start = NULL; - self->read_index = 0; - self->num_intermediates = 0; - self->num_strings = 0; - self->num_functions = 0; - self->num_extern_functions = 0; + self->string_indices = NULL; + self->extern_func_indices = NULL; + self->intermediates_start = NULL; + self->strings_start = NULL; + self->extern_funcs_start = NULL; + self->exported_funcs = NULL; + self->exported_funcs_end = NULL; + self->read_index = 0; + self->main_func_instruction_offset = ~0U; + self->num_intermediates = 0; + self->num_strings = 0; + self->num_functions = 0; + self->num_extern_functions = 0; + self->num_exported_functions = 0; cleanup_if_error(arena_allocator_init(&self->allocator)); cleanup_if_error(hash_map_init(&self->extern_funcs_map, &self->allocator, sizeof(ProgramExternFunc), hash_map_compare_string, amal_hash_string)); - cleanup_if_error(amal_program_append_header(self)); + cleanup_if_error(buffer_append_header(&self->data)); return 0; cleanup: @@ -131,6 +105,36 @@ static CHECK_RESULT int amal_program_get_extern_func_by_index(amal_program *self return 0; } +static CHECK_RESULT int amal_program_set_exported_function_instruction_offset_advance(amal_program *self, u32 instruction_offset) { + if(self->exported_funcs >= self->exported_funcs_end) { + amal_log_error("The number of exported functions in the instructions is more than the number of exported instructions in the header"); + return AMAL_PROGRAM_INSTRUCTION_INVALID_EXPORTED_FUNC_INDEX; + } + + { + u8 num_args; + u8 func_name_size; + + if((usize)(self->exported_funcs_end - self->exported_funcs) < sizeof(instruction_offset) + sizeof(num_args) + sizeof(func_name_size)) + return AMAL_PROGRAM_INSTRUCTION_INVALID_EXPORTED_FUNC_INDEX; + + am_memcpy(self->exported_funcs, &instruction_offset, sizeof(instruction_offset)); + num_args = self->exported_funcs[sizeof(instruction_offset)]; + func_name_size = self->exported_funcs[sizeof(instruction_offset) + sizeof(num_args)]; + self->exported_funcs += sizeof(instruction_offset) + sizeof(num_args) + sizeof(func_name_size); + if(self->main_func_instruction_offset == ~0U && am_memeql(self->exported_funcs, "main", 4)) + self->main_func_instruction_offset = instruction_offset; + + /* +1 to skip null-termination character */ + if((usize)(self->exported_funcs_end - self->exported_funcs) < func_name_size + 1U) + return AMAL_PROGRAM_INSTRUCTION_INVALID_EXPORTED_FUNC_INDEX; + + self->exported_funcs += func_name_size + 1; /* +1 to skip null-termination character */ + } + + return 0; +} + int amal_program_append_bytecode(amal_program *self, Bytecode *bytecode) { return buffer_append(&self->data, bytecode->data.data, bytecode->data.size); } @@ -158,14 +162,14 @@ static CHECK_RESULT int amal_program_read_header(amal_program *self) { am_memcpy(&patch_version, &self->data.data[self->read_index], sizeof(patch_version)); self->read_index += sizeof(u8); - if(magic_number != AMAL_PROGRAM_MAGIC_NUMBER) + if(magic_number != AMAL_BYTECODE_MAGIC_NUMBER) return AMAL_PROGRAM_INVALID_MAGIC_NUMBER; /* A program is only incompatible if the major version is newer than the version that is used to run it. TODO: Implement backwards compatible reads, starting from when the program bytecode breaks backwards compatibility */ - if(major_version > AMAL_PROGRAM_MAJOR_VERSION) + if(major_version > AMAL_BYTECODE_MAJOR_VERSION) return AMAL_PROGRAM_INCOMPATIBLE; return AMAL_PROGRAM_OK; @@ -237,7 +241,7 @@ static CHECK_RESULT int amal_program_read_strings(amal_program *self) { am_memcpy(&string_size, &self->data.data[self->read_index], sizeof(string_size)); self->read_index += sizeof(string_size); - /* +1 to skip null-termination character */ + /* +1 to skip null-termination character */ if(bytes_left_to_read(self) < string_size + 1U) return AMAL_PROGRAM_INVALID_STRINGS; @@ -291,9 +295,9 @@ static CHECK_RESULT int amal_program_read_external_functions(amal_program *self) func_name_size = self->data.data[self->read_index + sizeof(num_args)]; self->read_index += sizeof(num_args) + sizeof(func_name_size); - /* +1 to skip null-termination character */ + /* +1 to skip null-termination character */ if(bytes_left_to_read(self) < func_name_size + 1U) - return AMAL_PROGRAM_INVALID_STRINGS; + return AMAL_PROGRAM_INVALID_EXTERNAL_FUNCTIONS; self->read_index += func_name_size + 1; /* +1 to skip null-termination character */ } @@ -303,6 +307,28 @@ static CHECK_RESULT int amal_program_read_external_functions(amal_program *self) return AMAL_PROGRAM_OK; } +static CHECK_RESULT int amal_program_read_exported_functions(amal_program *self) { + u32 export_funcs_size; + + if(!amal_program_read_advance(self, &self->num_exported_functions, sizeof(u16))) + return AMAL_PROGRAM_INVALID_EXPORTED_FUNCTIONS; + + if(!amal_program_read_advance(self, &export_funcs_size, sizeof(export_funcs_size))) + return AMAL_PROGRAM_INVALID_EXPORTED_FUNCTIONS; + + if(bytes_left_to_read(self) < export_funcs_size) + return AMAL_PROGRAM_INVALID_EXPORTED_FUNCTIONS_SIZE; + + /* + Exported functions doesn't need list of indices to the data, since exported functions + are always sorted in the instructions in the same order as list of exported functions + */ + self->exported_funcs = (u8*)(self->data.data + self->read_index); + self->exported_funcs_end = self->exported_funcs + export_funcs_size; + self->read_index += export_funcs_size; + return AMAL_PROGRAM_OK; +} + static CHECK_RESULT int amal_program_get_intermediate_by_index(amal_program *self, u16 index, Number *result) { if(index >= self->num_intermediates) return AMAL_PROGRAM_INSTRUCTION_INVALID_INTERMEDIATE_INDEX; @@ -507,7 +533,7 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ break; } case AMAL_OP_FUNC_START: { - u8 func_num_param_regs; + u8 func_flags; u16 func_num_local_var_regs; assert(!inside_func); @@ -515,9 +541,10 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ assert(func_counter < self->num_functions); ++func_counter; - func_num_param_regs = self->data.data[self->read_index]; - (void)func_num_param_regs; - am_memcpy(&func_num_local_var_regs, self->data.data + self->read_index + sizeof(func_num_param_regs), sizeof(func_num_local_var_regs)); + func_flags = self->data.data[self->read_index]; + am_memcpy(&func_num_local_var_regs, self->data.data + self->read_index + sizeof(func_flags), sizeof(func_num_local_var_regs)); + if(func_flags & FUNC_FLAG_EXPORTED) + return_if_error(amal_program_set_exported_function_instruction_offset_advance(self, amal_exec_get_code_offset(executor))); return_if_error(amal_exec_func_start(executor, func_num_local_var_regs)); self->read_index += 3; break; @@ -549,9 +576,15 @@ int amal_program_run(amal_program *self) { cleanup_if_error(amal_program_read_strings(self)); cleanup_if_error(amal_program_read_functions(self)); cleanup_if_error(amal_program_read_external_functions(self)); + cleanup_if_error(amal_program_read_exported_functions(self)); cleanup_if_error(amal_program_read_instructions(self, executor)); } - result = amal_executor_run(executor); + if(self->main_func_instruction_offset == ~0U) { + amal_log_error("The program is missing a main function"); + result = AMAL_PROGRAM_NO_MAIN_FUNC; + goto cleanup; + } + result = amal_executor_run(executor, self->main_func_instruction_offset); cleanup: amal_executor_deinit(executor); diff --git a/src/ssa/ssa.c b/src/ssa/ssa.c index 500555a..940f636 100644 --- a/src/ssa/ssa.c +++ b/src/ssa/ssa.c @@ -61,9 +61,11 @@ int ssa_init(Ssa *self, Parser *parser) { 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)); self->intermediate_counter = 0; self->string_counter = 0; self->extern_func_counter = 0; + self->export_func_counter = 0; self->reg_counter = 0; self->param_counter = 0; self->func_counter = 0; @@ -192,6 +194,29 @@ static CHECK_RESULT int ssa_try_add_extern_func(Ssa *self, FunctionSignature *fu } } +/* + 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("ef%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; @@ -247,7 +272,7 @@ static CHECK_RESULT int ssa_ins_binop(Ssa *self, SsaInstruction binop_type, SsaR return ssa_add_ins_form2(self, binop_type, lhs, rhs, result); } -static CHECK_RESULT int ssa_ins_func_start(Ssa *self, SsaRegister num_reg_params, SsaFuncIndex *result, usize *func_metadata_index) { +static CHECK_RESULT int ssa_ins_func_start(Ssa *self, u8 func_flags, SsaFuncIndex *result, usize *func_metadata_index) { const u8 ins_type = SSA_FUNC_START; SsaInsFuncStart ins_func_start; @@ -258,14 +283,13 @@ static CHECK_RESULT int ssa_ins_func_start(Ssa *self, SsaRegister num_reg_params } *result = self->func_counter++; - ins_func_start.func_index = *result; - ins_func_start.num_params_regs = num_reg_params; + 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", *result, num_reg_params); + amal_log_debug("FUNC_START f%u", *result); return 0; } @@ -428,6 +452,16 @@ static CHECK_RESULT SsaRegister lhsexpr_extern_generate_ssa(LhsExpr *self, SsaCo 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; @@ -438,6 +472,10 @@ static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(LhsExpr *self, AstResolveDa 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. @@ -513,15 +551,13 @@ 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 + Reset reg counter in each function, because each function has a separate register context + that is reset after function end */ - SsaRegister prev_reg_counter; - SsaRegister prev_param_counter; usize func_metadata_index; - - prev_reg_counter = context->ssa->reg_counter; - prev_param_counter = context->ssa->param_counter; + SsaRegister prev_reg_counter = context->ssa->reg_counter; + SsaRegister prev_param_counter = context->ssa->param_counter; + u8 func_flags = 0; context->ssa->reg_counter = 0; /* @@ -533,7 +569,12 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom context->ssa->reg_counter = 0; amal_log_debug("SSA funcdecl %p", self); - throw_if_error(ssa_ins_func_start(context->ssa, context->ssa->param_counter, &self->ssa_func_index, &func_metadata_index)); + /* Anonymous closure doesn't have lhs_expr, and neither can it have any flags (extern, export etc) */ + if(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->ssa_func_index, &func_metadata_index)); scope_generate_ssa(&self->body, context); throw_if_error(ssa_ins_func_end(context->ssa)); @@ -585,7 +626,7 @@ static CHECK_RESULT SsaRegister structdecl_generate_ssa(StructDecl *self, SsaCom static CHECK_RESULT SsaRegister structfield_generate_ssa(StructField *self, SsaCompilerContext *context) { /* TODO: Implement */ - /*assert(bool_false);*/ + assert(bool_false); (void)self; (void)context; return 0; @@ -644,8 +685,7 @@ static CHECK_RESULT SsaRegister binop_generate_ssa(Binop *self, SsaCompilerConte } static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCompilerContext *context) { - usize jump_ins_index; - jump_ins_index = 0; + usize jump_ins_index = 0; if(else_if_stmt->condition) { SsaRegister condition_reg; condition_reg = ast_generate_ssa(else_if_stmt->condition, context); @@ -660,11 +700,8 @@ static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCom } static void if_statement_generate_ssa(IfStatement *if_stmt, SsaCompilerContext *context) { - SsaRegister condition_reg; - usize jump_ins_index; - - condition_reg = ast_generate_ssa(if_stmt->condition, context); - jump_ins_index = ssa_ins_get_index(context->ssa); + SsaRegister condition_reg = ast_generate_ssa(if_stmt->condition, context); + usize jump_ins_index = ssa_ins_get_index(context->ssa); throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); scope_generate_ssa(&if_stmt->body, context); throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_ins_index)); @@ -673,14 +710,10 @@ static void if_statement_generate_ssa(IfStatement *if_stmt, SsaCompilerContext * } static void while_statement_generate_ssa(WhileStatement *while_stmt, SsaCompilerContext *context) { - SsaRegister condition_reg; - usize jump_back_ins_index; - usize jump_condition_ins_index; isize jump_offset; - - jump_back_ins_index = ssa_ins_get_index(context->ssa); - condition_reg = ast_generate_ssa(while_stmt->condition, context); - jump_condition_ins_index = ssa_ins_get_index(context->ssa); + usize jump_back_ins_index = ssa_ins_get_index(context->ssa); + SsaRegister condition_reg = ast_generate_ssa(while_stmt->condition, context); + usize jump_condition_ins_index = ssa_ins_get_index(context->ssa); throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); scope_generate_ssa(&while_stmt->body, context); /* Jump back and check condition again before running the content of the loop again */ diff --git a/src/tokenizer.c b/src/tokenizer.c index 556a20b..82ea0f6 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -217,6 +217,9 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { } else if(am_memeql(self->value.identifier.data, "extern", 6)) { *token = TOK_EXTERN; return TOKENIZER_OK; + } else if(am_memeql(self->value.identifier.data, "export", 6)) { + *token = TOK_EXPORT; + return TOKENIZER_OK; } else if(am_memeql(self->value.identifier.data, "return", 6)) { *token = TOK_RETURN; return TOKENIZER_OK; @@ -496,6 +499,9 @@ static BufferView tokenizer_expected_token_as_string(Token token) { case TOK_EXTERN: str = "extern"; break; + case TOK_EXPORT: + str = "export"; + break; case TOK_RETURN: str = "return"; break; diff --git a/tests/bytecode.amal b/tests/bytecode.amal index a8ea53b..32c1ccb 100644 --- a/tests/bytecode.amal +++ b/tests/bytecode.amal @@ -1,8 +1,10 @@ -const b = @import("b.amal"); - extern const print_extern: fn; extern const print_extern_num: fn(num: i32); +const print = fn { + +} + const main = fn { var value = 23; value = 34; @@ -15,10 +17,6 @@ const main = fn { const result = print_num(1337); } -const print = fn { - -} - const print_num = fn(num: i32) i32 { print_extern(); print_extern_num(num); diff --git a/tests/errors/incorrect_main.amal b/tests/errors/incorrect_main.amal new file mode 100644 index 0000000..9d2a61f --- /dev/null +++ b/tests/errors/incorrect_main.amal @@ -0,0 +1 @@ +const main = fn(arg: i32) {} \ No newline at end of file diff --git a/tests/main.c b/tests/main.c index 3573f9c..b2b53cc 100644 --- a/tests/main.c +++ b/tests/main.c @@ -279,6 +279,7 @@ static void run_all_tests() { test_load_error("tests/errors/closure_duplicate_param_name.amal", "TODO: Add expected error here"); test_load_error("tests/errors/extern_closure_one_return_value.amal", "TODO: Add expected error here"); test_load_error("tests/errors/too_long_var_name.amal", "TODO: Add expected error here"); + test_load_error("tests/errors/incorrect_main.amal", "TODO: Add expected error here"); } /* TODO: Restrict variables in global scope to const */ -- cgit v1.2.3