aboutsummaryrefslogtreecommitdiff
path: root/include/bytecode/bytecode.h
blob: a93fe4fcd1da407066c3873c17d11882dad4f3c3 (plain)
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#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(Instructions)
    Variable length instructions. Instruction size ranges 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)\
    6.5 Opcode(u8) + stack_offset(i24)
    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 {
    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;
} 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/*<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