#ifndef AMALGAM_BYTECODE_H #define AMALGAM_BYTECODE_H #include "../std/defs.h" #include "../std/misc.h" #include "../std/buffer.h" #include "../defs.h" #include /*doc(Instructions) Variable length instructions. Instruction size ranges from 1 to 4 bytes. # Instruction formats Instructions can be in 7 different formats: 1. 1 byte: Opcode(u8) 2. 2 bytes: Opcode(u8) + register(i8) 3. 3 bytes: Opcode(u8) + register(i8) + register(i8) 4. 3 bytes:\ 4.1 Opcode(u8) + intermediate(u16)\ 4.2 Opcode(u8) + data(u16)\ 4.3 Opcode(u8) + label(i16)\ 4.4 Opcode(u8) + register(i8) + num_args(u8) 5. 4 bytes: Opcode(u8) + register(i8) + register(i8) + register(i8) 6. 4 bytes:\ 6.1 Opcode(u8) + register(i8) + label(i16)\ 6.2 Opcode(u8) + register(i8) + intermediate(u16)\ 6.3 Opcode(u8) + register(i8) + data(u16)\ 6.4 Opcode(u8) + flags(u8) + num_local_var_reg(u16)\ 6.5 Opcode(u8) + index(u8) + index(u16) # Registers Registers have a range of 128. Local variables start from register 0 and increment while parameters start from -1 and decrement. Registers have the scope of functions and reset after instructions reach a new function (AMAL_OP_FUNC_START). If import index for call and calle is 0, then that means the function resides in the same file the function call is being called from. Which means that import index 1 is actually import index 0 into the import list. */ /* Important: The number of fields in this enum can't exceed 255 */ 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 for CALL. Values are pushed right before a CALL, with no other instructions between */ AMAL_OP_PUSHI, /* pushi int - Push intermediate for CALL. Values are pushed right before a CALL, with no other instructions between */ AMAL_OP_PUSHD, /* pushd data - Push data for CALL. Values are pushed right before a CALL, with no other instructions between */ AMAL_OP_PUSH_RET, /* push_ret reg - Push register as a return value of the next function call */ AMAL_OP_CALL_START, /* call_start num_args - Start of a CALL with @num_args number of arguments. Arguments for the next CALL is pushed immediately after this, followed by a CALL. @num_args is u8 */ AMAL_OP_CALL, /* call ii, fi - Call a function in imported file (ii, import index) using function index (fi). The number of arguments is the number of values pushed to stack. ii is u8, fi is u16 */ AMAL_OP_CALLR, /* callr reg - Call a function using a register. Used for function pointers. The number of arguments is the number of values pushed to stack */ AMAL_OP_CALLE, /* calle ii, efi - Call an extern function in imported file (ii, import index) using extern function index (efi). The number of arguments is the number of values pushed to stack. ii is u8, efi is u16 */ 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, label - Jump to label in the current function if reg is zero. label is u16 */ AMAL_OP_JMP, /* jmp label - Unconditional jump to label in the current function. label is u16 */ 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 */ AMAL_OP_LABEL /* label - Label. This is the target of a jump instruction. Jump instructions only jump to labels in the same function scope */ } AmalOpcode; /* dec05eba = ba5ec0de = basecode */ #define AMAL_BYTECODE_MAGIC_NUMBER "\xde\xc0\x5e\xba" #define AMAL_BYTECODE_MAGIC_NUMBER_SIZE 4 #define AMAL_BYTECODE_MAJOR_VERSION 1 #define AMAL_BYTECODE_MINOR_VERSION 0 #define AMAL_BYTECODE_PATCH_VERSION 0 #define AMAL_BYTECODE_SECTION_MAGIC_NUMBER (u32)0x004005e4 /* "section\0" in ascii */ #define AMAL_BYTECODE_NUM_REGISTERS 256 typedef enum { FUNC_FLAG_NONE = 0, FUNC_FLAG_EXPORTED = 1 << 0, FUNC_FLAG_VARARGS = 1 << 1 } amal_func_flag; typedef u8 AmalOpcodeType; /* TODO: Make sure this pragma pack works on all platforms */ #pragma pack(push, 1) typedef struct { u8 magic_number[AMAL_BYTECODE_MAGIC_NUMBER_SIZE]; /* @AMAL_BYTECODE_MAGIC_NUMBER */ u8 major_version; u8 minor_version; u8 patch_version; } BytecodeHeader; #pragma pack(pop) /* TODO: Make sure this pragma pack works on all platforms */ #pragma pack(push, 1) typedef struct { u32 func_offset; u8 num_params; u32 params_num_pointers; u32 params_fixed_size; u8 num_return_types; u32 return_types_num_pointers; u32 return_types_fixed_size; } BytecodeHeaderFunction; #pragma pack(pop) /* TODO: Make sure this pragma pack works on all platforms */ #pragma pack(push, 1) typedef struct { u8 num_params; u8 num_return_types; u8 name_len; u8 flags; u32 params_num_pointers; u32 params_fixed_size; u32 return_types_num_pointers; u32 return_types_fixed_size; } BytecodeHeaderExternFunction; #pragma pack(pop) /* TODO: Make sure this pragma pack works on all platforms */ #pragma pack(push, 1) typedef struct { u32 function_index; #define parser_index function_index u32 extern_function_index; } BytecodeHeaderImport; #pragma pack(pop) struct Bytecode { Buffer/**/ data; usize import_index; /* Reference inside @data where imports start */ u32 funcs_index; /* Reference inside @data where funcs start */ u32 extern_funcs_index; /* Reference inside @data where extern funcs start */ u32 offset; /* Offset that this bytecode starts from in the final program (all bytecodes combined) */ }; typedef struct { jmp_buf env; Bytecode *bytecode; Parser *parser; /* borrowed */ } BytecodeCompilerContext; 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); #endif