1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
#ifndef AMALGAM_BYTECODE_H
#define AMALGAM_BYTECODE_H
#include "../std/defs.h"
#include "../std/misc.h"
#include "../std/buffer.h"
#include "../defs.h"
#include <setjmp.h>
/*doc(Opcode)
Variable length opcodes. Sizes range from 1 to 5 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)
7. 5 bytes: Opcode(u8) + index(u8) + index(u16) + num_args(u8)
# 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 onto stack */
AMAL_OP_PUSHI, /* pushi int - Push intermediate onto stack */
AMAL_OP_PUSHD, /* pushd data - Push data onto stack */
AMAL_OP_PUSH_RET, /* push_ret reg - Push register onto stack as a return value of the next function call */
AMAL_OP_CALL, /* call ii, fi, num_args - Call a function in imported file (ii, import index) using function index (fi) and num_args arguments. ii is u8, 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 ii, efi, num_args - Call an extern function in imported file (ii, import index) using extern function index (efi) and num_args arguments. ii is u8, 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, 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;
#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_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
} amal_func_flag;
typedef u8 AmalOpcodeType;
/* 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 {
u32 function_index;
#define parser_index function_index
u32 extern_function_index;
} BytecodeHeaderImport;
#pragma pack(pop)
struct Bytecode {
Buffer/*<headers + instruction data>*/ 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
|