diff options
author | dec05eba <dec05eba@protonmail.com> | 2019-09-17 01:28:55 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2020-07-25 14:36:46 +0200 |
commit | b095aedd386e076d1f5a56b7384b836e653387d1 (patch) | |
tree | a9dd7d7cbcfba19005ce8aa9486a70d31091d5c3 | |
parent | 2928e5f74983f5dd33bc65f192298af87996a037 (diff) |
Add support for r8-r15 registers, pass args to registers directly (sys-v)
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | doc/Documentation.md | 5 | ||||
-rw-r--r-- | executor/executor.h | 5 | ||||
-rw-r--r-- | executor/x86_64/asm.c | 203 | ||||
-rw-r--r-- | executor/x86_64/asm.h | 28 | ||||
-rw-r--r-- | executor/x86_64/executor.c | 65 | ||||
-rw-r--r-- | include/bytecode/bytecode.h | 21 | ||||
-rw-r--r-- | include/ssa/ssa.h | 7 | ||||
-rw-r--r-- | src/bytecode/bytecode.c | 23 | ||||
-rw-r--r-- | src/parser.c | 12 | ||||
-rw-r--r-- | src/program.c | 27 | ||||
-rw-r--r-- | src/ssa/ssa.c | 48 | ||||
-rw-r--r-- | tests/glfw.amal | 1 |
13 files changed, 264 insertions, 183 deletions
@@ -31,7 +31,7 @@ Amalgam places limits on code for performance reasons. These are the limits: * One file can't have more than 254 imports, have more than 2^16 functions or use more than 2^16 functions. * Every function can only use up to 2^16 registers and parameters (combined). -* Every function can only have up to 128 parameters and 128 return values. +* Every function can only have up to 128 parameters, 128 return values and accept 128 arguments. * Exported and external function can only have 0 or 1 return values, as that is what C supports. # TODO diff --git a/doc/Documentation.md b/doc/Documentation.md index 88647a7..afc890c 100644 --- a/doc/Documentation.md +++ b/doc/Documentation.md @@ -1,5 +1,5 @@ # Instructions -Variable length instructions. Instruction size ranges from 1 to 5 bytes. +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) @@ -16,8 +16,7 @@ Instructions can be in 7 different formats: 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) +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). diff --git a/executor/executor.h b/executor/executor.h index 743500c..7f9793e 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -39,10 +39,11 @@ CHECK_RESULT int amal_exec_div(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 CHECK_RESULT int amal_exec_push(amal_executor *self, i8 reg); CHECK_RESULT int amal_exec_pushi(amal_executor *self, i64 imm); CHECK_RESULT int amal_exec_pushd(amal_executor *self, BufferView data); -CHECK_RESULT int amal_exec_call(amal_executor *self, u32 code_offset, u8 num_args, i8 dst_reg); +CHECK_RESULT int amal_exec_call_start(amal_executor *self, u8 num_args); +CHECK_RESULT int amal_exec_call(amal_executor *self, u32 code_offset, i8 dst_reg); void amal_exec_call_overwrite(amal_executor *self, u32 call_code_offset, i32 new_target_rel32); /*CHECK_RESULT int amal_exec_callr(i8 dst_reg, BufferView data);*/ -CHECK_RESULT int amal_exec_calle(amal_executor *self, void *func, u8 num_args, i8 dst_reg); +CHECK_RESULT int amal_exec_calle(amal_executor *self, void *func, i8 dst_reg); CHECK_RESULT int amal_exec_cmp(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2); CHECK_RESULT int amal_exec_jz(amal_executor *self, i8 reg, u16 target_label); CHECK_RESULT int amal_exec_jmp(amal_executor *self, u16 target_label); diff --git a/executor/x86_64/asm.c b/executor/x86_64/asm.c index c633db8..60b1752 100644 --- a/executor/x86_64/asm.c +++ b/executor/x86_64/asm.c @@ -8,7 +8,31 @@ #include <sys/mman.h> -#define REX_W 0x48 +/* REX documentation: https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding */ +#define REX_W 0x48 /* 0 = 32-bit operand size, 1 = 64-bit operand size - For most operations... */ + +static u8 rex_rr(Reg64 dst, Reg64 src) { + u8 rex = REX_W; + rex |= ((dst & REG64_EXTENDED_REG_BIT) >> 3); /* REX.B */ + rex |= ((src & REG64_EXTENDED_REG_BIT) >> 1); /* REX.R */ + assert(REG64_EXTENDED_REG_BIT == (1<<3)); + return rex; +} + +static u8 rex_sib(Reg64 dst, Reg64 src) { + u8 rex = REX_W; + rex |= ((src & REG64_EXTENDED_REG_BIT) >> 3); /* REX.B */ + rex |= ((dst & REG64_EXTENDED_REG_BIT) >> 2); /* REX.X */ + assert(REG64_EXTENDED_REG_BIT == (1<<3)); + return rex; +} + +static u8 rex_rm(AsmPtr *dst, Reg64 src) { + assert(REG64_EXTENDED_REG_BIT == (1<<3)); + if(dst->index == 0 && (dst->base & REG64_REG_BITS) != RBP) + return rex_rr(dst->base, src); + return rex_sib(dst->base, src); +} #ifdef DEBUG #include <stdarg.h> @@ -84,6 +108,14 @@ static const char* reg64_to_str(Reg64 reg) { case RBP: return "rbp"; case RSI: return "rsi"; case RDI: return "rdi"; + case R8: return "r8"; + case R9: return "r9"; + case R10: return "r10"; + case R11: return "r11"; + case R12: return "r12"; + case R13: return "r13"; + case R14: return "r14"; + case R15: return "r15"; } assert(bool_false); return NULL; @@ -270,7 +302,7 @@ static void asm_rm(Asm *self, AsmPtr *mem, Reg64 reg) { u8 rm_byte; u8 disp_bytes; assert(asm_get_capacity_left(self) >= 6); - if((int)mem->index != -1) { + if((int)mem->index != -1) { /* SIB */ u8 sib_offset; if(mem->disp == 0) { rm_byte = 0x04; @@ -293,31 +325,33 @@ static void asm_rm(Asm *self, AsmPtr *mem, Reg64 reg) { } #endif assert(mem->base != RBP && "TODO: Implement RBP base for sib byte. RBP is special and requires different logic"); - sib_offset = (mem->scale << 5) + 8*mem->index + mem->base; + assert(mem->index != R13 && "TODO: Implement R13 base for sib byte. R13 is special and requires different logic"); + sib_offset = (mem->scale << 5) + 8*(mem->index & REG64_REG_BITS) + (mem->base & REG64_REG_BITS); *self->code_it++ = rm_byte; *self->code_it++ = sib_offset; } else { + u8 base = (mem->base & REG64_REG_BITS); assert(mem->scale == 0); /* Scale isn't valid without index reg */ if(mem->disp == 0) { - if(mem->base == RBP) { + if(base == RBP) { rm_byte = 0x45; - disp_bytes = 1; + disp_bytes = 1; /* RBP requires use of disp byte, even if it's not used */ } else { - rm_byte = mem->base; + rm_byte = base; disp_bytes = 0; } } else if(abs_i32(mem->disp) <= INT8_MAX) { - rm_byte = 0x40 + mem->base; + rm_byte = 0x40 + base; disp_bytes = 1; } else { - rm_byte = 0x80 + mem->base; + rm_byte = 0x80 + base; disp_bytes = 4; } - *self->code_it++ = (reg << 3) | rm_byte; + *self->code_it++ = ((reg & REG64_REG_BITS) << 3) | rm_byte; /* RSP requires SIB byte */ - if(mem->base == RSP) + if(base == RSP) *self->code_it++ = 0x24; } @@ -328,13 +362,13 @@ static void asm_rm(Asm *self, AsmPtr *mem, Reg64 reg) { /* There has to be at least 1 byte left in the asm buffer before calling this function. */ static void asm_rr(Asm *self, Reg64 dst, Reg64 src) { assert(asm_get_capacity_left(self) >= 1); - *self->code_it++ = 0xC0 + dst + 8*src; + *self->code_it++ = 0xC0 + (dst & REG64_REG_BITS) + 8*(src & REG64_REG_BITS); } /* TODO: Implement 1 and 2 byte immediate? */ void asm_mov_mi(Asm *self, AsmPtr *dst, i32 immediate) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rm(dst, 0); *self->code_it++ = 0xC7; asm_rm(self, dst, 0); am_memcpy(self->code_it, &immediate, sizeof(immediate)); @@ -344,7 +378,7 @@ void asm_mov_mi(Asm *self, AsmPtr *dst, i32 immediate) { void asm_mov_mr(Asm *self, AsmPtr *dst, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rm(dst, src); *self->code_it++ = 0x89; asm_rm(self, dst, src); ins_end(self, "mov %s, %s", asm_ptr_to_string(dst), reg64_to_str(src)); @@ -352,7 +386,7 @@ void asm_mov_mr(Asm *self, AsmPtr *dst, Reg64 src) { void asm_mov_rm(Asm *self, Reg64 dst, AsmPtr *src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rm(src, dst); *self->code_it++ = 0x8B; asm_rm(self, src, dst); ins_end(self, "mov %s, %s", reg64_to_str(dst), asm_ptr_to_string(src)); @@ -361,7 +395,7 @@ void asm_mov_rm(Asm *self, Reg64 dst, AsmPtr *src) { /* Note: This shows as instruction movabs in intel assembly format */ void asm_mov_ri(Asm *self, Reg64 dst, i64 immediate) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(dst, 0); *self->code_it++ = 0xB8 + dst; am_memcpy(self->code_it, &immediate, sizeof(immediate)); self->code_it += sizeof(immediate); @@ -370,7 +404,7 @@ void asm_mov_ri(Asm *self, Reg64 dst, i64 immediate) { void asm_mov_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(dst, src); *self->code_it++ = 0x89; asm_rr(self, dst, src); ins_end(self, "mov %s, %s", reg64_to_str(dst), reg64_to_str(src)); @@ -378,7 +412,7 @@ void asm_mov_rr(Asm *self, Reg64 dst, Reg64 src) { void asm_add_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(dst, src); *self->code_it++ = 0x01; asm_rr(self, dst, src); ins_end(self, "add %s, %s", reg64_to_str(dst), reg64_to_str(src)); @@ -386,7 +420,7 @@ void asm_add_rr(Asm *self, Reg64 dst, Reg64 src) { void asm_sub_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(dst, src); *self->code_it++ = 0x29; asm_rr(self, dst, src); ins_end(self, "sub %s, %s", reg64_to_str(dst), reg64_to_str(src)); @@ -394,7 +428,7 @@ void asm_sub_rr(Asm *self, Reg64 dst, Reg64 src) { void asm_imul_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(dst, src); *self->code_it++ = 0x0F; *self->code_it++ = 0xAF; asm_rr(self, dst, src); @@ -410,7 +444,7 @@ void asm_cqo(Asm *self) { void asm_idiv_rr(Asm *self, Reg64 src) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(src, 0); *self->code_it++ = 0xF7; asm_rr(self, src, 0x7); ins_end(self, "idiv %s", reg64_to_str(src)); @@ -430,7 +464,7 @@ void asm_popr(Asm *self, Reg64 reg) { void asm_callr(Asm *self, Reg64 reg) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rr(reg, 0); *self->code_it++ = 0xFF; asm_rr(self, reg, 0x2); ins_end(self, "call %s", reg64_to_str(reg)); @@ -457,7 +491,7 @@ void asm_overwrite_call_rel32(Asm *self, u32 asm_index, i32 new_relative) { void asm_cmp_rm(Asm *self, Reg64 reg1, AsmPtr *reg2) { ins_start(self); - *self->code_it++ = REX_W; + *self->code_it++ = rex_rm(reg2, reg1); *self->code_it++ = 0x3B; asm_rm(self, reg2, reg1); ins_end(self, "cmp %s, %s", reg64_to_str(reg1), asm_ptr_to_string(reg2)); @@ -546,25 +580,20 @@ void asm_overwrite_jmp_rel32(Asm *self, u32 asm_index, i32 new_relative) { } /* TODO: Remove these !*/ - /* /r */ -#define DEFINE_INS_RM(mnemonic, opcode) \ -void asm_##mnemonic##_rmb(Asm *self, Reg32 dst, Reg32 src) { \ - *self->code_it++ = opcode; \ - *self->code_it++ = 0xC0 + 8*dst + src; \ -} \ - \ -void asm_##mnemonic##_rm32(Asm *self, Reg32 dst, Reg32 src) { \ - ins_start(self); \ - asm_##mnemonic##_rmb(self, (Reg32)dst, (Reg32)src); \ - ins_end(self, #mnemonic" %s, %s", reg32_to_str(dst), reg32_to_str(src)); \ -} \ - \ -void asm_##mnemonic##_rm64(Asm *self, Reg64 dst, Reg64 src) { \ - ins_start(self); \ - *self->code_it++ = REX_W; \ - asm_##mnemonic##_rmb(self, (Reg32)dst, (Reg32)src); \ - ins_end(self, #mnemonic" %s, %s", reg64_to_str(dst), reg64_to_str(src)); \ +#define DEFINE_INS_RM(mnemonic, opcode) \ +void asm_##mnemonic##_rm32(Asm *self, Reg32 dst, Reg32 src) { \ + ins_start(self); \ + *self->code_it++ = opcode; \ + asm_rr(self, (Reg64)src, (Reg64)dst); \ + ins_end(self, #mnemonic" %s, %s", reg32_to_str(dst), reg32_to_str(src)); \ +} \ + \ +void asm_##mnemonic##_rm64(Asm *self, Reg64 dst, Reg64 src) { \ + ins_start(self); \ + *self->code_it++ = opcode; \ + asm_rr(self, src, dst); \ + ins_end(self, #mnemonic" %s, %s", reg64_to_str(dst), reg64_to_str(src)); \ } DEFINE_INS_RM(mov, 0x8B) @@ -581,31 +610,31 @@ DEFINE_INS_RM(cmp, 0x3B) It's a number used to extend the opcode type, since the instruction only uses one register the other register can be encoded for that. */ -#define DEFINE_INS_EXT_IMM(mnemonic, extension) \ -void asm_##mnemonic##_rmb_imm(Asm *self, Reg32 reg, i32 immediate) { \ - if(abs_i32(immediate) <= INT8_MAX) { \ - *self->code_it++ = 0x83; \ - *self->code_it++ = 0xC0 + 8*extension + reg; \ - *self->code_it++ = (u8)immediate; \ - } else { \ - *self->code_it++ = 0x81; \ - *self->code_it++ = 0xC0 + 8*extension + reg; \ - am_memcpy(self->code_it, &immediate, sizeof(immediate)); \ - self->code_it += sizeof(immediate); \ - } \ -} \ - \ -void asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i32 immediate) { \ - ins_start(self); \ - asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ - ins_end(self, #mnemonic" %s, 0x%x", reg32_to_str(reg), immediate); \ -}\ - \ -void asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i32 immediate) { \ - ins_start(self); \ - *self->code_it++ = REX_W; \ - asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ - ins_end(self, #mnemonic" %s, 0x%x", reg64_to_str(reg), immediate); \ +#define DEFINE_INS_EXT_IMM(mnemonic, extension) \ +void asm_##mnemonic##_rmb_imm(Asm *self, Reg32 reg, i32 immediate) { \ + if(abs_i32(immediate) <= INT8_MAX) { \ + *self->code_it++ = 0x83; \ + asm_rr(self, (Reg64)reg, (Reg64)(extension)); \ + *self->code_it++ = (u8)immediate; \ + } else { \ + *self->code_it++ = 0x81; \ + asm_rr(self, (Reg64)reg, (Reg64)(extension)); \ + am_memcpy(self->code_it, &immediate, sizeof(immediate)); \ + self->code_it += sizeof(immediate); \ + } \ +} \ + \ +void asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i32 immediate) { \ + ins_start(self); \ + asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ + ins_end(self, #mnemonic" %s, 0x%x", reg32_to_str(reg), immediate); \ +} \ + \ +void asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i32 immediate) { \ + ins_start(self); \ + *self->code_it++ = rex_rr(reg, 0); \ + asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ + ins_end(self, #mnemonic" %s, 0x%x", reg64_to_str(reg), immediate); \ } DEFINE_INS_EXT_IMM(add, 0) @@ -623,28 +652,28 @@ DEFINE_INS_EXT_IMM(cmp, 7) It's a number used to extend the opcode type, since the instruction only uses one register the other register can be encoded for that. */ -#define DEFINE_INS_SHIFT_IMM8(mnemonic, extension) \ -void asm_##mnemonic##_rmb_imm(Asm *self, Reg32 reg, i8 immediate) { \ - if(immediate == 1) { \ - *self->code_it++ = 0xC1; \ - *self->code_it++ = 0xC0 + 8*reg + extension; \ - } else { \ - *self->code_it++ = 0xD1; \ - *self->code_it++ = 0xC0 + 8*reg + extension; \ - *self->code_it++ = immediate; \ - } \ -} \ - \ -void asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i8 immediate) { \ - ins_start(self); \ - ins_end(self, #mnemonic" %s, 0x%x", reg32_to_str(reg), immediate); \ -} \ - \ -void asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i8 immediate) { \ - ins_start(self); \ - *self->code_it++ = REX_W; \ - asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ - ins_end(self, #mnemonic" %s, 0x%x", reg64_to_str(reg), immediate); \ +#define DEFINE_INS_SHIFT_IMM8(mnemonic, extension) \ +void asm_##mnemonic##_rmb_imm(Asm *self, Reg32 reg, i8 immediate) { \ + if(immediate == 1) { \ + *self->code_it++ = 0xC1; \ + asm_rr(self, (Reg64)reg, (Reg64)(extension)); \ + } else { \ + *self->code_it++ = 0xD1; \ + asm_rr(self, (Reg64)reg, (Reg64)(extension)); \ + *self->code_it++ = immediate; \ + } \ +} \ + \ +void asm_##mnemonic##_rm32_imm(Asm *self, Reg32 reg, i8 immediate) { \ + ins_start(self); \ + ins_end(self, #mnemonic" %s, 0x%x", reg32_to_str(reg), immediate); \ +} \ + \ +void asm_##mnemonic##_rm64_imm(Asm *self, Reg64 reg, i8 immediate) { \ + ins_start(self); \ + *self->code_it++ = rex_rr(reg, 0); \ + asm_##mnemonic##_rmb_imm(self, (Reg32)reg, immediate); \ + ins_end(self, #mnemonic" %s, 0x%x", reg64_to_str(reg), immediate); \ } DEFINE_INS_SHIFT_IMM8(rol, 0) diff --git a/executor/x86_64/asm.h b/executor/x86_64/asm.h index 6ac74f4..b82a63e 100644 --- a/executor/x86_64/asm.h +++ b/executor/x86_64/asm.h @@ -21,15 +21,27 @@ typedef enum { EDI } Reg32; +#define REG64_EXTENDED_REG_BIT (1 << 3) +#define REG64_REG_BITS 0x7 + typedef enum { - RAX, - RCX, - RDX, - RBX, - RSP, - RBP, - RSI, - RDI + RAX = 0, + RCX = 1, + RDX = 2, + RBX = 3, + RSP = 4, + RBP = 5, + RSI = 6, + RDI = 7, + + R8 = REG64_EXTENDED_REG_BIT | RAX, + R9 = REG64_EXTENDED_REG_BIT | RCX, + R10 = REG64_EXTENDED_REG_BIT | RDX, + R11 = REG64_EXTENDED_REG_BIT | RBX, + R12 = REG64_EXTENDED_REG_BIT | RSP, + R13 = REG64_EXTENDED_REG_BIT | RBP, + R14 = REG64_EXTENDED_REG_BIT | RSI, + R15 = REG64_EXTENDED_REG_BIT | RDI } Reg64; typedef struct { diff --git a/executor/x86_64/executor.c b/executor/x86_64/executor.c index f747e4a..c918c13 100644 --- a/executor/x86_64/executor.c +++ b/executor/x86_64/executor.c @@ -30,6 +30,8 @@ typedef struct { Buffer/*JumpDefer*/ jump_defer; u32 label_asm_index[MAX_LABELS]; int label_counter; + int num_args; + int num_pushed_values; } amal_executor_impl; #define ASM_ENSURE_CAPACITY return_if_error(asm_ensure_capacity(&impl->asm, 256)); @@ -54,14 +56,19 @@ static i64 abs_i64(i64 value) { return value >= 0 ? value : -value; } +const Reg64 SYS_V_REG_PARAMS[] = { RDI, RSI, RDX, RCX, R8, R9, R10, R11 }; +const int NUM_REG_PARAMS = 8; + int amal_executor_init(amal_executor **self) { amal_executor_impl **impl; impl = (amal_executor_impl**)self; *impl = NULL; return_if_error(am_malloc(sizeof(amal_executor_impl), (void**)impl)); (*impl)->func_counter = 0; - ignore_result_int(buffer_init(&(*impl)->jump_defer, NULL)); (*impl)->label_counter = 0; + (*impl)->num_args = 0; + (*impl)->num_pushed_values = 0; + ignore_result_int(buffer_init(&(*impl)->jump_defer, NULL)); return asm_init(&(*impl)->asm); } @@ -234,7 +241,6 @@ int amal_exec_idiv(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2) { asm_mov_rm(&impl->asm, RAX, ®1); asm_mov_rm(&impl->asm, RCX, ®2); asm_cqo(&impl->asm); - /* TODO: Preserve RDX if needed, since it's also used as a parameter in system-v x86_64 abi */ asm_idiv_rr(&impl->asm, RCX); asm_mov_mr(&impl->asm, &dst, RAX); return 0; @@ -255,8 +261,13 @@ int amal_exec_push(amal_executor *self, i8 reg) { IMPL_START asm_ptr_init_disp(®_ptr, RBP, get_register_stack_offset(reg)); - asm_mov_rm(&impl->asm, RAX, ®_ptr); - asm_pushr(&impl->asm, RAX); + if(impl->num_pushed_values < NUM_REG_PARAMS) { + asm_mov_rm(&impl->asm, SYS_V_REG_PARAMS[impl->num_pushed_values], ®_ptr); + } else { + asm_mov_rm(&impl->asm, RAX, ®_ptr); + asm_pushr(&impl->asm, RAX); + } + ++impl->num_pushed_values; return 0; } @@ -276,15 +287,22 @@ int amal_exec_pushd(amal_executor *self, BufferView data) { return 0; } -int amal_exec_call(amal_executor *self, u32 code_offset, u8 num_args, i8 dst_reg) { +int amal_exec_call_start(amal_executor *self, u8 num_args) { + amal_executor_impl *impl = (amal_executor_impl*)self; + impl->num_args = num_args; + return 0; +} + +int amal_exec_call(amal_executor *self, u32 code_offset, i8 dst_reg) { amal_executor_impl *impl = (amal_executor_impl*)self; /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ /* Do the function call */ isize asm_offset = asm_get_size(&impl->asm); + int num_pushed_stack = impl->num_pushed_values - (int)NUM_REG_PARAMS; ASM_ENSURE_CAPACITY - assert(num_args % 2 == 0 && "TODO: Align stack to 16-bytes before calling functions"); + assert((num_pushed_stack <= 0 || num_pushed_stack % 2 == 0) && "TODO: Align stack to 16-bytes before calling functions"); assert(code_offset < asm_offset); asm_call_rel32(&impl->asm, (isize)code_offset - asm_offset); @@ -295,8 +313,9 @@ int amal_exec_call(amal_executor *self, u32 code_offset, u8 num_args, i8 dst_reg /* TODO: Make this work when result is not stored in RAX (multiple return results) */ asm_mov_mr(&impl->asm, &dst, RAX); } - if(num_args > 0) - asm_add_rm64_imm(&impl->asm, RSP, num_args * sizeof(isize)); + if(num_pushed_stack > 0) + asm_add_rm64_imm(&impl->asm, RSP, num_pushed_stack * sizeof(isize)); + impl->num_pushed_values = 0; return 0; } @@ -305,8 +324,6 @@ void amal_exec_call_overwrite(amal_executor *self, u32 call_code_offset, i32 new asm_overwrite_call_rel32(&impl->asm, call_code_offset, new_target_rel32); } -const Reg64 SYS_V_PARAM_REGS[] = { RDI, RSI, RDX, RCX }; - /* TODO: Make argument passing work for different calling conventions and different ABI. This currently assumes x86_64 system v abi. @@ -315,26 +332,13 @@ const Reg64 SYS_V_PARAM_REGS[] = { RDI, RSI, RDX, RCX }; The rest are passed in the stack. */ /* TODO: Make this work when function returns something else than a POD */ -int amal_exec_calle(amal_executor *self, void *func, u8 num_args, i8 dst_reg) { +int amal_exec_calle(amal_executor *self, void *func, i8 dst_reg) { AsmPtr dst; - IMPL_START + amal_executor_impl *impl = (amal_executor_impl*)self; + int num_pushed_stack = impl->num_pushed_values - (int)NUM_REG_PARAMS; + ASM_ENSURE_CAPACITY - assert(num_args % 2 == 0 && "TODO: Align stack to 16-bytes before calling functions"); - /* TODO: Support R and XMM registers so more than 4 arguments can be used for functions */ - assert(num_args < 5); - { - /* - TODO: Do this directly in @PUSH instruction instead. For now we copy - the pushed data to the registers that need to be set for the specific abi for parameters - */ - int i; - AsmPtr src; - asm_ptr_init_disp(&src, RSP, 0); - for(i = num_args - 1; i >= 0; --i) { - asm_mov_rm(&impl->asm, SYS_V_PARAM_REGS[i], &src); - src.disp += 0x8; - } - } + assert((num_pushed_stack <= 0 || num_pushed_stack % 2 == 0) && "TODO: Align stack to 16-bytes before calling functions"); /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ @@ -342,8 +346,9 @@ int amal_exec_calle(amal_executor *self, void *func, u8 num_args, i8 dst_reg) { asm_callr(&impl->asm, RAX); asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_mr(&impl->asm, &dst, RAX); - if(num_args > 0) - asm_add_rm64_imm(&impl->asm, RSP, num_args * sizeof(isize)); + if(num_pushed_stack > 0) + asm_add_rm64_imm(&impl->asm, RSP, num_pushed_stack * sizeof(isize)); + impl->num_pushed_values = 0; return 0; } diff --git a/include/bytecode/bytecode.h b/include/bytecode/bytecode.h index 346455c..8f786a2 100644 --- a/include/bytecode/bytecode.h +++ b/include/bytecode/bytecode.h @@ -9,7 +9,7 @@ #include <setjmp.h> /*doc(Instructions) - Variable length instructions. Instruction size ranges from 1 to 5 bytes. + 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) @@ -26,8 +26,7 @@ 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) + 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). @@ -48,13 +47,14 @@ typedef enum { 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_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 */ @@ -64,6 +64,7 @@ typedef enum { 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 diff --git a/include/ssa/ssa.h b/include/ssa/ssa.h index 0b6501b..0ec43b0 100644 --- a/include/ssa/ssa.h +++ b/include/ssa/ssa.h @@ -26,6 +26,7 @@ typedef enum { SSA_FUNC_END, SSA_PUSH, SSA_PUSH_RET, + SSA_CALL_START, SSA_CALL, SSA_CALL_EXTERN, SSA_JUMP_ZERO, @@ -109,18 +110,20 @@ typedef struct { } SsaInsFuncStart; typedef struct { - u8 num_args; FunctionDecl *func_decl; u8 import_index; } SsaInsFuncCall; typedef struct { - u8 num_args; LhsExpr *func_decl_lhs; int import_index; } SsaInsFuncCallExtern; typedef struct { + u8 num_args; +} SsaInsCallStart; + +typedef struct { SsaRegister condition_reg; SsaLabelIndex target_label; } SsaInsJumpZero; diff --git a/src/bytecode/bytecode.c b/src/bytecode/bytecode.c index 3b6d980..7dbc305 100644 --- a/src/bytecode/bytecode.c +++ b/src/bytecode/bytecode.c @@ -432,19 +432,6 @@ static void add_ins6(BytecodeCompilerContext *self, AmalOpcode opcode, i8 dst_re fputc('\n', stderr); } -static void add_ins7(BytecodeCompilerContext *self, AmalOpcode opcode, u8 import_index, u16 func_index, i8 num_args, const char *fmt) { - Buffer *instructions = &self->bytecode->data; - size_t index = instructions->size; - - throw_if_error(buffer_append_empty(instructions, sizeof(AmalOpcodeType) + sizeof(import_index) + sizeof(func_index) + sizeof(num_args))); - instructions->data[index] = opcode; - memcpy(instructions->data + index + sizeof(AmalOpcodeType), &import_index, sizeof(import_index)); - memcpy(instructions->data + index + sizeof(AmalOpcodeType) + sizeof(import_index), &func_index, sizeof(func_index)); - memcpy(instructions->data + index + sizeof(AmalOpcodeType) + sizeof(import_index) + sizeof(func_index), &num_args, sizeof(num_args)); - fprintf(stderr, fmt, import_index, func_index, num_args); - fputc('\n', stderr); -} - static void add_instructions(BytecodeCompilerContext *self) { /*doc(Bytecode instructions) # Instructions layout @@ -459,6 +446,7 @@ static void add_instructions(BytecodeCompilerContext *self) { SsaInsFuncStart ssa_ins_func_start; SsaInsFuncCall ssa_ins_func_call; SsaInsFuncCallExtern ssa_ins_func_call_extern; + SsaInsCallStart ssa_ins_call_start; SsaInsJumpZero ssa_ins_jump_zero; SsaInsJump ssa_ins_jump; @@ -547,14 +535,19 @@ static void add_instructions(BytecodeCompilerContext *self) { add_ins2(self, AMAL_OP_PUSH_RET, reg, "push_ret r%d"); break; } + case SSA_CALL_START: { + instruction += ssa_extract_data(instruction, &ssa_ins_call_start, sizeof(ssa_ins_call_start)); + add_ins2(self, AMAL_OP_CALL_START, ssa_ins_call_start.num_args, "call_start %d"); + break; + } case SSA_CALL: { instruction += ssa_extract_data(instruction, &ssa_ins_func_call, sizeof(ssa_ins_func_call)); - add_ins7(self, AMAL_OP_CALL, ssa_ins_func_call.import_index, ssa_ins_func_call.func_decl->ssa_func_index, ssa_ins_func_call.num_args, "call f(%d,%d), %d"); + add_ins6(self, AMAL_OP_CALL, ssa_ins_func_call.import_index, ssa_ins_func_call.func_decl->ssa_func_index, "call f(%d,%d)"); break; } case SSA_CALL_EXTERN: { instruction += ssa_extract_data(instruction, &ssa_ins_func_call_extern, sizeof(ssa_ins_func_call_extern)); - add_ins7(self, AMAL_OP_CALLE, ssa_ins_func_call_extern.import_index, ssa_ins_func_call_extern.func_decl_lhs->extern_index, ssa_ins_func_call_extern.num_args, "calle ef(%d,%d), %d"); + add_ins6(self, AMAL_OP_CALLE, ssa_ins_func_call_extern.import_index, ssa_ins_func_call_extern.func_decl_lhs->extern_index, "calle ef(%d,%d)"); break; } case SSA_JUMP_ZERO: { diff --git a/src/parser.c b/src/parser.c index ed99eb0..9da9147 100644 --- a/src/parser.c +++ b/src/parser.c @@ -24,6 +24,7 @@ do { \ #define VAR_MAX_LEN UINT8_MAX #define FUNC_MAX_PARAMS 128 #define FUNC_MAX_RETURN_TYPES 128 +#define FUNC_MAX_ARGS FUNC_MAX_PARAMS static CHECK_RESULT Ast* parser_parse_rhs(Parser *self); static CHECK_RESULT Ast* parser_parse_body(Parser *self); @@ -425,8 +426,8 @@ static CHECK_RESULT StructDecl* parser_parse_struct_decl(Parser *self) { FUNC_ARGS = (RHS_START)? (',' RHS_START)* ')' */ static void parser_parse_function_args(Parser *self, FunctionCall *func_call) { - bool first_arg; - first_arg = bool_true; + bool first_arg = bool_true; + int arg_index = 0; for(;;) { Ast *arg_expr; @@ -442,6 +443,13 @@ static void parser_parse_function_args(Parser *self, FunctionCall *func_call) { arg_expr = parser_parse_rhs(self); throw_if_error(buffer_append(&func_call->args, &arg_expr, sizeof(arg_expr))); + ++arg_index; + if (arg_index > FUNC_MAX_ARGS) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "A closure can't take more than %d arguments", FUNC_MAX_ARGS); + throw(PARSER_ERR); + } } } diff --git a/src/program.c b/src/program.c index 63d2b6e..128f0f9 100644 --- a/src/program.c +++ b/src/program.c @@ -570,26 +570,30 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ self->read_index += 1; break; } + case AMAL_OP_CALL_START: { + u8 num_args = self->data.data[self->read_index]; + return_if_error(amal_exec_call_start(executor, num_args)); + self->read_index += 1; + break; + } case AMAL_OP_CALL: { u8 import_index; u16 func_index; - u8 num_args; BytecodeHeaderFunction func_def; i8 dst_reg; am_memcpy(&import_index, self->data.data + self->read_index, sizeof(import_index)); am_memcpy(&func_index, self->data.data + self->read_index + sizeof(import_index), sizeof(func_index)); - am_memcpy(&num_args, self->data.data + self->read_index + sizeof(import_index) + sizeof(func_index), sizeof(num_args)); amal_program_get_header_function_by_index(self, import_index, func_index, &func_def); assert(func_def.num_return_types == 1 && "TODO: Support 0 and more than 1 return values"); assert(self->return_value_index == 1); dst_reg = self->return_values_stack[0]; - self->return_value_index -= func_def.num_return_types; + self->return_value_index = 0; /* func_offset will only be non-zero when the function has been decoded (FUNC_START) */ if(func_def.func_offset != 0) { /* TODO: Instead of pushing num args, push the sum of sizeof the last num_args */ - return_if_error(amal_exec_call(executor, func_def.func_offset, num_args, dst_reg)); + return_if_error(amal_exec_call(executor, func_def.func_offset, dst_reg)); } else { /* The code for the function has not been generated yet (the function is defined after the current location). @@ -609,24 +613,23 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ return_if_error(hash_map_insert(&self->deferred_func_calls, key_mem, &new_deferred_call_list)); } /* Dummy call to offset 0, offset will be replace later when the target function hits AMAL_OP_FUNC_START */ - return_if_error(amal_exec_call(executor, 0, num_args, dst_reg)); + return_if_error(amal_exec_call(executor, 0, dst_reg)); } - self->read_index += 4; + self->read_index += 3; break; } - case AMAL_OP_CALLR: + case AMAL_OP_CALLR: { assert(bool_false && "TODO: Implement CALLR"); - self->read_index += 2; + self->read_index += 1; break; + } case AMAL_OP_CALLE: { u8 import_index; u16 extern_func_index; - u8 num_args; i8 dst_reg; am_memcpy(&import_index, self->data.data + self->read_index, sizeof(import_index)); am_memcpy(&extern_func_index, self->data.data + self->read_index + sizeof(import_index), sizeof(extern_func_index)); - am_memcpy(&num_args, self->data.data + self->read_index + sizeof(import_index) + sizeof(extern_func_index), sizeof(num_args)); assert(self->return_value_index == 1 && "TODO: Support extern functions that don't return any value"); dst_reg = self->return_values_stack[0]; self->return_value_index = 0; @@ -634,9 +637,9 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ { ProgramExternFunc extern_func; return_if_error(amal_program_get_extern_func_by_index(self, import_index, extern_func_index, &extern_func)); - return_if_error(amal_exec_calle(executor, extern_func.func, num_args, dst_reg)); + return_if_error(amal_exec_calle(executor, extern_func.func, dst_reg)); } - self->read_index += 4; + self->read_index += 3; break; } case AMAL_OP_CMP: { diff --git a/src/ssa/ssa.c b/src/ssa/ssa.c index 19aa036..13f19a9 100644 --- a/src/ssa/ssa.c +++ b/src/ssa/ssa.c @@ -25,6 +25,7 @@ do { \ /* Max length of a string that fits in u16 */ #define MAX_STRING_LENGTH UINT16_MAX +#define FUNC_MAX_ARGS 128 static CHECK_RESULT SsaRegister variable_generate_ssa(Variable *self, SsaCompilerContext *context); @@ -323,24 +324,30 @@ static CHECK_RESULT int ssa_ins_push_ret(Ssa *self, SsaRegister reg) { return buffer_append(&self->instructions, ®, sizeof(reg)); } -static CHECK_RESULT int ssa_ins_call(Ssa *self, int import_index, FunctionDecl *func_decl, u8 num_args) { +static CHECK_RESULT int ssa_ins_call_start(Ssa *self, u8 num_args) { + const u8 ins_type = SSA_CALL_START; + SsaInsCallStart ins_call_start; + ins_call_start.num_args = num_args; + return_if_error(buffer_append(&self->instructions, &ins_type, 1)); + return buffer_append(&self->instructions, &ins_call_start, sizeof(ins_call_start)); +} + +static CHECK_RESULT int ssa_ins_call(Ssa *self, int import_index, FunctionDecl *func_decl) { const u8 ins_type = SSA_CALL; SsaInsFuncCall ins_func_call; - ins_func_call.num_args = num_args; ins_func_call.func_decl = func_decl; ins_func_call.import_index = import_index; - amal_log_debug("CALL %d, f(%d,%p)", num_args, import_index, func_decl); + amal_log_debug("CALL f(%d,%p)", import_index, func_decl); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_func_call, sizeof(ins_func_call)); } -static CHECK_RESULT int ssa_ins_call_extern(Ssa *self, int import_index, LhsExpr *func_decl_lhs, u8 num_args) { +static CHECK_RESULT int ssa_ins_call_extern(Ssa *self, int import_index, LhsExpr *func_decl_lhs) { const u8 ins_type = SSA_CALL_EXTERN; SsaInsFuncCallExtern ins_func_call_extern; - ins_func_call_extern.num_args = num_args; ins_func_call_extern.func_decl_lhs = func_decl_lhs; ins_func_call_extern.import_index = import_index; - amal_log_debug("CALL_EXTERN %d, ef(%d,%p)", num_args, import_index, func_decl_lhs); + amal_log_debug("CALL_EXTERN ef(%d,%p)", import_index, func_decl_lhs); return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_func_call_extern, sizeof(ins_func_call_extern)); } @@ -635,14 +642,16 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom static CHECK_RESULT SsaRegister funccall_generate_ssa(FunctionCall *self, AstResolveData *resolve_data, SsaCompilerContext *context) { SsaRegister reg; + FunctionSignature *func_sig; FunctionDecl *func_decl; LhsExpr *func_lhs_expr; int import_index = context->import_index; context->import_index = 0; throw_if_error(ssa_get_unique_reg(context->ssa, ®)); - func_decl = resolve_data->type.value.func_sig->func_decl; assert(resolve_data->type.type == RESOLVED_TYPE_FUNC_SIG); + func_sig = resolve_data->type.value.func_sig; + func_decl = func_sig->func_decl; func_lhs_expr = NULL; if(self->func.resolved_var.type == NAMED_OBJECT_LHS_EXPR) func_lhs_expr = self->func.resolved_var.value.lhs_expr; @@ -654,24 +663,41 @@ static CHECK_RESULT SsaRegister funccall_generate_ssa(FunctionCall *self, AstRes all of them into account. Right now it only uses one return type. It should also take into account the size of the type. */ + assert(buffer_get_size(&func_sig->return_types, FunctionReturnType) <= 1); throw_if_error(ssa_ins_push_ret(context->ssa, reg)); } /* Push parameter arguments */ + assert(buffer_get_size(&self->args, Ast*) <= FUNC_MAX_ARGS); { + SsaRegister arg_regs[FUNC_MAX_ARGS]; + Ast **arg = buffer_begin(&self->args); Ast **arg_end = buffer_end(&self->args); for(; arg != arg_end; ++arg) { - SsaRegister arg_reg = ast_generate_ssa(*arg, context); - throw_if_error(ssa_ins_push(context->ssa, arg_reg)); + arg_regs[arg_end - arg] = ast_generate_ssa(*arg, context); + } + + /* + This is done in two steps since first we want the argument expressions to be generated + and then at the end, push the registers. + This allows Amalgam to push arguments directly to registers on abi that uses registers as function arguments (system-v x86_64) + instead of first pushing the registers to stack and then moving them to registers. + */ + + arg = buffer_begin(&self->args); + throw_if_error(ssa_ins_call_start(context->ssa, arg_end - arg)); + for(; arg != arg_end; ++arg) { + throw_if_error(ssa_ins_push(context->ssa, arg_regs[arg_end - arg])); } } if(func_lhs_expr && LHS_EXPR_IS_EXTERN(func_lhs_expr)) { - throw_if_error(ssa_ins_call_extern(context->ssa, import_index, func_lhs_expr, buffer_get_size(&self->args, Ast*))); + throw_if_error(ssa_ins_call_extern(context->ssa, import_index, func_lhs_expr)); } else { + assert(func_decl); /* rhs wont be null here because only extern variable can't have rhs */ - throw_if_error(ssa_ins_call(context->ssa, import_index, func_decl, buffer_get_size(&self->args, Ast*))); + throw_if_error(ssa_ins_call(context->ssa, import_index, func_decl)); } return reg; diff --git a/tests/glfw.amal b/tests/glfw.amal index 8957127..b888dac 100644 --- a/tests/glfw.amal +++ b/tests/glfw.amal @@ -6,4 +6,5 @@ const main = fn { glfwInit(); glfwCreateWindow(50, 50, "hello, world", 0, 0); glfwTerminate(); + //const a = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1; }
\ No newline at end of file |