aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2019-08-24 00:48:40 +0200
committerdec05eba <dec05eba@protonmail.com>2020-07-25 14:36:46 +0200
commit7212ea877ed85d3b85af90c902639df44fc493f2 (patch)
treec76d73e9882a832f82cef977efb6fb26fb0e4984
parentd6f368a3f400fea3e89280262a8147e7ce5d855c (diff)
Add exported variable (only functions for now), export main func, start execution from main func
-rw-r--r--doc/Documentation.md20
-rw-r--r--executor/executor.h3
-rw-r--r--executor/x86_64/asm.c4
-rw-r--r--executor/x86_64/asm.h2
-rw-r--r--executor/x86_64/executor.c9
-rw-r--r--include/bytecode/bytecode.h60
-rw-r--r--include/program.h16
-rw-r--r--include/ssa/ssa.h11
-rw-r--r--include/tokenizer.h1
-rw-r--r--src/bytecode/bytecode.c185
-rw-r--r--src/compiler.c21
-rw-r--r--src/parser.c38
-rw-r--r--src/program.c135
-rw-r--r--src/ssa/ssa.c87
-rw-r--r--src/tokenizer.c6
-rw-r--r--tests/bytecode.amal10
-rw-r--r--tests/errors/incorrect_main.amal1
-rw-r--r--tests/main.c1
18 files changed, 394 insertions, 216 deletions
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 */