From 40eb2ce96f24f0ef71794a72e013efb6387ae678 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Thu, 21 Nov 2024 20:23:11 +0800 Subject: [PATCH 01/11] initial version of regalloc --- boot.c | 586 +++++++++++++++++++++++++++++++++++++------------------- boot.sh | 2 +- 2 files changed, 392 insertions(+), 196 deletions(-) diff --git a/boot.c b/boot.c index 5fbab63..3dbf328 100644 --- a/boot.c +++ b/boot.c @@ -429,8 +429,8 @@ int parse_type() { int epilog_label; int local_table[4096]; // id -> local id -int next_local_id = 2; -int max_local_id = 2; +int next_local_id = 12; +int max_local_id = 12; const int MARKER_TEMP = 0; const int MARKER_SCALAR = 1; @@ -441,47 +441,137 @@ int local_marker[4096]; int global_marker[4096]; int local_type[4096]; int global_type[4096]; + +int reg_type[4096]; +int next_reg_id = 18; +int max_reg_id = 18; int indirection[4096]; +int overflow[4096]; + +const int REG_ZERO = 0; +const int REG_RA = 1; +const int REG_SP = 2; +const int REG_GP = 3; +const int REG_TP = 4; +const int REG_T0 = 5; +const int REG_T1 = 6; +const int REG_T2 = 7; +const int REG_FP = 8; +const int REG_S1 = 9; +const int REG_A0 = 10; +const int REG_A1 = 11; +const int REG_A2 = 12; +const int REG_A3 = 13; +const int REG_A4 = 14; +const int REG_A5 = 15; +const int REG_A6 = 16; +const int REG_A7 = 17; +const int REG_S2 = 18; +const int REG_S3 = 19; +const int REG_S4 = 20; +const int REG_S5 = 21; +const int REG_S6 = 22; +const int REG_S7 = 23; +const int REG_S8 = 24; +const int REG_S9 = 25; +const int REG_S10 = 26; +const int REG_S11 = 27; +const int REG_T3 = 28; +const int REG_T4 = 29; +const int REG_T5 = 30; +const int REG_T6 = 31; + +void reset_reg() { + next_reg_id = REG_S2; + for (int i = 0; i < 4096; ++i) { + reg_type[i] = TYPE_VOID; + indirection[i] = 0; + overflow[i] = 0; + } +} + +const char* reg_name(int reg) { + if (reg == 0) return "zero"; + if (reg == 1) return "ra"; + if (reg == 2) return "sp"; + if (reg == 3) return "gp"; + if (reg == 4) return "tp"; + if (reg == 5) return "t0"; + if (reg == 6) return "t1"; + if (reg == 7) return "t2"; + if (reg == 8) return "fp"; + // reserved begin + if (reg == 9) return "s1"; + if (reg == 10) return "a0"; + if (reg == 11) return "a1"; + if (reg == 12) return "a2"; + if (reg == 13) return "a3"; + if (reg == 14) return "a4"; + if (reg == 15) return "a5"; + if (reg == 16) return "a6"; + if (reg == 17) return "a7"; + // allocation begin + if (reg == 18) return "s2"; + if (reg == 19) return "s3"; + if (reg == 20) return "s4"; + if (reg == 21) return "s5"; + if (reg == 22) return "s6"; + if (reg == 23) return "s7"; + if (reg == 24) return "s8"; + if (reg == 25) return "s9"; + if (reg == 26) return "s10"; + if (reg == 27) return "s11"; + // overflow begin + if (reg == 28) return "t3"; + if (reg == 29) return "t4"; + if (reg == 30) return "t5"; + if (reg == 31) return "t6"; + return 0; +} + +int is_overflow(int reg) { + return reg > REG_S11; +} void reset_local() { - next_local_id = 2; - max_local_id = 2; + next_local_id = 12; + max_local_id = 12; for (int i = 0; i < 4096; ++i) { local_table[i] = 0; local_marker[i] = MARKER_TEMP; local_type[i] = TYPE_VOID; - indirection[i] = 0; } + reset_reg(); } void reset_temp() { - while (next_local_id > 2 && local_marker[next_local_id - 1] == MARKER_TEMP) { + while (next_local_id > 12 && local_marker[next_local_id - 1] == MARKER_TEMP) { --next_local_id; } + reset_reg(); } -int next_reg(int type) { - int reg = next_local_id++; - local_type[reg] = type; - indirection[reg] = 0; +int next_local_slot(int type) { + int slot = next_local_id++; + local_type[slot] = type; if (next_local_id > max_local_id) { max_local_id = next_local_id; } - return reg; + return slot; } int declare_local(int id, int type) { if (local_table[id] != 0) return local_table[id]; - int reg = next_reg(type); - local_marker[reg] = MARKER_SCALAR; - return local_table[id] = reg; + int slot = next_local_slot(type); + local_marker[slot] = MARKER_SCALAR; + return local_table[id] = slot; } int declare_local_array(int id, int type, int size) { if (local_table[id] != 0) return local_table[id]; - int reg; - for (int i = 0; i < size; ++i) local_marker[reg = next_reg(type)] = MARKER_ARRAY; - return local_table[id] = reg; + int slot; + for (int i = 0; i < size; ++i) local_marker[slot = next_local_slot(type)] = MARKER_ARRAY; + return local_table[id] = slot; } void declare_global(int id, int marker, int type) { @@ -489,6 +579,23 @@ void declare_global(int id, int marker, int type) { global_type[id] = type; } +int next_reg(int type) { + int reg = next_reg_id++; + if (is_overflow(reg)) { + int slot = next_local_slot(type); + local_marker[slot] = MARKER_TEMP; + overflow[reg] = slot; + } + reg_type[reg] = type; + if (next_reg_id > max_reg_id) { + max_reg_id = next_reg_id; + } + return reg; +} + + +// prolog & epilog helpers + int check_itype_immediate(int value) { return value >= -2048 && value <= 2047; } @@ -522,74 +629,176 @@ void asm_addi(const char* rd, const char* rs, int imm) { } } -void load_address(int rd, int id) { - if (id == -1) { - eprintf("void cannot be arithmetically operated\n"); - exit(1); - } - int offset = -id * 8 - 8; - if (indirection[id]) { - if (check_itype_immediate(offset)) { - printf(" ld t%d, %d(fp) # indirection\n", rd, offset); - } else { - printf(" li t%d, %d\n", rd, offset); - printf(" add t%d, fp, t%d\n", rd, rd); - printf(" ld t%d, 0(t%d) # indirection\n", rd, rd); - } +// assembly helpers + +// address loaders +// rd must be one of 0, 1, 2 +void load_local_address(int rd, int slot_id) { + // TODO use sp instead + int offset = -slot_id * 8 - 8; + const char* rd_name = reg_name(rd); + if (check_itype_immediate(offset)) { + printf(" addi %s, fp, %d\n", rd_name, offset); } else { - if (check_itype_immediate(offset)) { - printf(" addi t%d, fp, %d\n", rd, offset); - } else { - printf(" li t%d, %d\n", rd, offset); - printf(" add t%d, fp, t%d\n", rd, rd); + printf(" li %s, %d\n", rd_name, offset); + printf(" add %s, fp, %s\n", rd_name, rd_name); + } +} + +const char* load_op(int reg) { + int type = reg_type[reg]; + if (type & TYPE_PTR_MASK) { + return "ld"; + } else if (type == TYPE_CHAR) { + return "lb"; + } else { // int + return "lw"; + } +} + +const char* store_op_of_type(int type) { + if (type & TYPE_PTR_MASK) { + return "sd"; + } else if (type == TYPE_CHAR) { + return "sb"; + } else { // int + return "sw"; + } +} + +const char* store_op(int reg) { + return store_op_of_type(reg_type[reg]); +} + +// load a non-trivial register into t0, t1 or t2 +// rd must be one of t0, t1, t2 +void load(int rd, int reg) { + const char* op = load_op(reg); + const char* rd_name = reg_name(rd); + if (is_overflow(reg)) { + load_local_address(rd, overflow[reg]); + if (indirection[reg]) { + printf(" ld %s, 0(%s)\n", rd_name, rd_name); } + reg = rd; + } + printf(" %s %s, 0(%s) # load non-trivial register\n", op, rd_name, reg_name(reg)); +} + +// store t0 into a non-trivial register +void store_t0(int reg) { + const char* op = store_op(reg); + if (is_overflow(reg)) { + load_local_address(REG_T2, overflow[reg]); + if (indirection[reg]) { + printf(" ld t2, 0(t2)\n"); + } + reg = REG_T2; + } + printf(" %s t0, 0(%s) # store non-trivial register\n", op, reg_name(reg)); +} + +int is_nontrivial(int reg) { + return is_overflow(reg) || indirection[reg]; +} + +void _asm_r(const char* op, int rd, int rs1) { + const char* rd_name = reg_name(rd); + const char* rs1_name = reg_name(rs1); + if (is_nontrivial(rd)) rd_name = "t0"; + if (is_nontrivial(rs1)) { + rs1_name = "t0"; + load(REG_T0, rs1); + } + printf(" %s %s, %s\n", op, rd_name, rs1_name); + if (is_nontrivial(rd)) { + store_t0(rd); } } -void load(int rd, int id) { - load_address(rd, id); - int type = local_type[id]; - const char* op = "lw"; // int - if (type == TYPE_CHAR) { - op = "lb"; - } else if (type & TYPE_PTR_MASK) { - op = "ld"; +void _asm_rr(const char* op, int rd, int rs1, int rs2) { + const char* rd_name = reg_name(rd); + const char* rs1_name = reg_name(rs1); + const char* rs2_name = reg_name(rs2); + if (is_nontrivial(rd)) rd_name = "t0"; + if (is_nontrivial(rs1)) { + rs1_name = "t0"; + load(REG_T0, rs1); + } + if (is_nontrivial(rs2)) { + rs2_name = "t1"; + load(REG_T1, rs2); + } + printf(" %s %s, %s, %s\n", op, rd_name, rs1_name, rs2_name); + if (is_nontrivial(rd)) { + store_t0(rd); } - printf(" %s t%d, 0(t%d) # id: type %d\n", op, rd, rd, type); } -void store_t0(int id) { - load_address(1, id); - int type = local_type[id]; - const char* op = "sw"; // int - if (type == TYPE_CHAR) { - op = "sb"; - } else if (type & TYPE_PTR_MASK) { - op = "sd"; +void _asm_ri(const char* op, int rd, int rs1, int imm) { + const char* rd_name = reg_name(rd); + const char* rs1_name = reg_name(rs1); + if (is_nontrivial(rd)) rd_name = "t0"; + if (is_nontrivial(rs1)) { + rs1_name = "t0"; + load(REG_T0, rs1); } - printf(" %s t0, 0(t1) # id: type %d\n", op, type); + printf(" %s %s, %s, %d\n", op, rd_name, rs1_name, imm); + if (is_nontrivial(rd)) { + store_t0(rd); + } +} + +void _asm_branch(const char* op, int rs1, int label) { + const char* rs1_name = reg_name(rs1); + if (is_nontrivial(rs1)) { + rs1_name = "t0"; + load(REG_T0, rs1); + } + printf(" %s %s, L%d\n", op, rs1_name, label); +} + +int asm_r(int type, const char* op, int rs1) { + int rd = next_reg(type); + _asm_r(op, rd, rs1); + return rd; +} + +int asm_rr(int type, const char* op, int rs1, int rs2) { + int rd = next_reg(type); + _asm_rr(op, rd, rs1, rs2); + return rd; +} + +void asm_mv(int rd, int rs1) { + _asm_r("mv", rd, rs1); } int materialize_t0(int type) { - int reg = next_reg(type); - store_t0(reg); - return reg; + int rd = next_reg(type); + asm_mv(rd, REG_T0); + return rd; } -int dereference(int reg) { - local_type[reg] = local_type[reg] & ~TYPE_PTR_MASK; - indirection[reg] = 1; - return reg; +int materialize_address(int type, int marker) { + if (marker == MARKER_ARRAY) { + type = type | TYPE_PTR_MASK; + } + int rd = materialize_t0(TYPE_VOID_PTR); + reg_type[rd] = type; + indirection[rd] = marker == MARKER_SCALAR; + return rd; +} + +int lookup_from_slot(int slot) { + load_local_address(REG_T0, slot); + return materialize_address(local_type[slot], local_marker[slot]); } int lookup(int id) { - int local = local_table[id]; - if (local) { - if (local_marker[local] == MARKER_ARRAY) { - load_address(0, local); - return materialize_t0(local_type[local] | TYPE_PTR_MASK); - } - return local; + int slot = local_table[id]; + if (slot) { + return lookup_from_slot(slot); } const char* name = id_table + id_lut[id]; if (global_marker[id]) { @@ -598,11 +807,7 @@ int lookup(int id) { exit(1); } printf(" la t0, %s # id: %d\n", name, id); - int reg = materialize_t0(global_type[id] | TYPE_PTR_MASK); - if (global_marker[id] == MARKER_SCALAR) { - reg = dereference(reg); - } - return reg; + return materialize_address(global_type[id], global_marker[id]); } eprintf("unresolved identifier: %s\n", name); exit(1); @@ -619,65 +824,33 @@ int asm_label(int label) { return label; } -int is_not_reusable(int rs1, int expected_type) { - return indirection[rs1] || local_marker[rs1] != MARKER_TEMP || local_type[rs1] != expected_type; -} - -int asm_r(const char* op, int rs1) { - load(0, rs1); - printf(" %s t0, t0\n", op); - int rd = rs1; - if (is_not_reusable(rs1, TYPE_INT)) { - rd = next_reg(TYPE_INT); - } - store_t0(rd); - return rd; -} - int asm_r_arith(const char* op, int rs1) { - if (local_type[rs1] & TYPE_PTR_MASK) { + if (reg_type[rs1] & TYPE_PTR_MASK) { eprintf("pointer cannot be arithmetically operated by %s\n", op); exit(1); } - return asm_r(op, rs1); -} - -int asm_rr(const char* op, int rs1, int rs2) { - load(0, rs1); - load(1, rs2); - printf(" %s t0, t0, t1\n", op); - int rd = rs1; - if (is_not_reusable(rd, TYPE_INT)) { - rd = rs2; - if (is_not_reusable(rd, TYPE_INT)) { - rd = next_reg(TYPE_INT); - } - } - store_t0(rd); - return rd; + return asm_r(TYPE_INT, op, rs1); } int asm_rr_arith(const char* op, int rs1, int rs2) { - if (local_type[rs1] & TYPE_PTR_MASK || local_type[rs2] & TYPE_PTR_MASK) { + if (reg_type[rs1] & TYPE_PTR_MASK || reg_type[rs2] & TYPE_PTR_MASK) { eprintf("pointer cannot be arithmetically operated by %s\n", op); exit(1); } - return asm_rr(op, rs1, rs2); + return asm_rr(TYPE_INT, op, rs1, rs2); } int asm_rr_cmp(const char* op, int rs1, int rs2) { - // since NULL is virtually 0, it is considered valid example of a pointer comparing with an integer - return asm_rr(op, rs1, rs2); + // since NULL is virtually 0, it is considered a valid example of a pointer comparing with an integer + return asm_rr(TYPE_INT, op, rs1, rs2); } void asm_beqz(int rs1, int label) { - load(0, rs1); - printf(" beqz t0, L%d\n", label); + _asm_branch("beqz", rs1, label); } void asm_bnez(int rs1, int label) { - load(0, rs1); - printf(" bnez t0, L%d\n", label); + _asm_branch("bnez", rs1, label); } void asm_j(int label) { @@ -714,15 +887,9 @@ int step_of(int type) { return 1; } -void asm_shift_t0(const char* op, int type) { - if (type == TYPE_INT_PTR) { - printf(" %s t0, t0, 2\n", op); - } -} - int asm_add(int lhs, int rhs) { - int type1 = local_type[lhs] & TYPE_PTR_MASK; - int type2 = local_type[rhs] & TYPE_PTR_MASK; + int type1 = reg_type[lhs] & TYPE_PTR_MASK; + int type2 = reg_type[rhs] & TYPE_PTR_MASK; if (type1 != type2) { int ptr; int idx; @@ -733,27 +900,26 @@ int asm_add(int lhs, int rhs) { ptr = rhs; idx = lhs; } - int ptr_type = local_type[ptr]; + int ptr_type = reg_type[ptr]; if (ptr_type == TYPE_VOID_PTR) { eprintf("void pointer cannot be arithmetically operated\n"); exit(1); } - load(0, idx); - load(1, ptr); - asm_shift_t0("slli", ptr_type); - printf(" add t0, t0, t1\n"); - return materialize_t0(ptr_type); + int offset = next_reg(TYPE_INT); + int shift = 2 * (ptr_type == TYPE_INT_PTR); + _asm_ri("slli", offset, idx, shift); + return asm_rr(ptr_type, "add", ptr, offset); } if (type1 && type2) { eprintf("operands of addition cannot be both pointers\n"); exit(1); } - return asm_rr("add", lhs, rhs); + return asm_rr(TYPE_INT, "add", lhs, rhs); } int asm_sub(int lhs, int rhs) { - int lhs_type = local_type[lhs]; - int rhs_type = local_type[rhs]; + int lhs_type = reg_type[lhs]; + int rhs_type = reg_type[rhs]; int type1 = lhs_type & TYPE_PTR_MASK; int type2 = rhs_type & TYPE_PTR_MASK; if (type1 && type2) { @@ -765,17 +931,36 @@ int asm_sub(int lhs, int rhs) { eprintf("void pointer cannot be arithmetically operated\n"); exit(1); } - load(0, lhs); - load(1, rhs); - printf(" sub t0, t0, t1\n"); - asm_shift_t0("srai", lhs_type); + int difference = asm_rr(TYPE_INT, "sub", lhs, rhs); + int shift = 2 * (lhs_type == TYPE_INT_PTR); + _asm_ri("slli", REG_T0, difference, shift); return materialize_t0(TYPE_INT); } if (type1) { int neg = asm_r_arith("neg", rhs); return asm_add(lhs, neg); } - return asm_rr("sub", lhs, rhs); + return asm_rr_arith("sub", lhs, rhs); +} + +int dereference(int reg) { + if (indirection[reg]) { + load(reg, reg); + } else { + indirection[reg] = 1; + } + reg_type[reg] = reg_type[reg] & ~TYPE_PTR_MASK; + return reg; +} + +int addressof(int reg) { + if (indirection[reg] && !(reg_type[reg] & TYPE_PTR_MASK)) { + reg_type[reg] = reg_type[reg] | TYPE_PTR_MASK; + indirection[reg] = 0; + } else { + printf("cannot take address of this expression"); + } + return reg; } // parser @@ -811,10 +996,11 @@ int parse_function_call(int id) { } } for (int i = 0; i < arg; ++i) { - load(0, args[i]); - printf(" mv a%d, t0\n", i); + asm_mv(i + REG_A0, args[i]); } + // TODO saved by caller printf(" call %s\n", name); + // TODO saved by caller int type = global_type[id]; if (type != TYPE_VOID) { printf(" mv t0, a0\n"); @@ -855,20 +1041,16 @@ int parse_postfix_expr() { while (1) { next_token(); if (token_type == TOKEN_INC) { - int type = local_type[lhs]; + int type = reg_type[lhs]; int reg = next_reg(type); - load(0, lhs); - store_t0(reg); - printf(" addi t0, t0, %d\n", step_of(type)); - store_t0(lhs); + asm_mv(reg, lhs); + _asm_ri("addi", lhs, lhs, step_of(type)); lhs = reg; } else if (token_type == TOKEN_DEC) { - int type = local_type[lhs]; + int type = reg_type[lhs]; int reg = next_reg(type); - load(0, lhs); - store_t0(reg); - printf(" addi t0, t0, -%d\n", step_of(type)); - store_t0(lhs); + asm_mv(reg, lhs); + _asm_ri("addi", lhs, lhs, -step_of(type)); lhs = reg; } else if (token_type == TOKEN_BRACKET_LEFT) { int rhs = parse_expr(); @@ -886,16 +1068,15 @@ int parse_prefix_expr() { next_token(); if (token_type == TOKEN_AND) { int reg = parse_postfix_expr(); - int type = local_type[reg]; + int type = reg_type[reg]; if (type & TYPE_PTR_MASK) { eprintf("cannot take address of a pointer\n"); exit(1); } - load_address(0, reg); - return materialize_t0(type | TYPE_PTR_MASK); + return addressof(reg); } else if (token_type == TOKEN_STAR) { int reg = parse_postfix_expr(); - int type = local_type[reg]; + int type = reg_type[reg]; if (!(type & TYPE_PTR_MASK)) { eprintf("cannot dereference a non-pointer\n"); exit(1); @@ -904,8 +1085,7 @@ int parse_prefix_expr() { eprintf("cannot dereference void pointer\n"); exit(1); } - load(0, reg); - return dereference(materialize_t0(type)); + return dereference(reg); } else if (token_type == TOKEN_MINUS) { int reg = parse_postfix_expr(); return asm_r_arith("neg", reg); @@ -914,18 +1094,14 @@ int parse_prefix_expr() { return asm_r_arith("not", reg); } else if (token_type == TOKEN_NOT) { int reg = parse_postfix_expr(); - return asm_r("seqz", reg); + return asm_r(TYPE_INT, "seqz", reg); } else if (token_type == TOKEN_INC) { int reg = parse_postfix_expr(); - load(0, reg); - printf(" addi t0, t0, %d\n", step_of(local_type[reg])); - store_t0(reg); + _asm_ri("addi", reg, reg, step_of(reg_type[reg])); return reg; } else if (token_type == TOKEN_DEC) { int reg = parse_postfix_expr(); - load(0, reg); - printf(" addi t0, t0, -%d\n", step_of(local_type[reg])); - store_t0(reg); + _asm_ri("addi", reg, reg, -step_of(reg_type[reg])); return reg; } else { unget_token(); @@ -1003,11 +1179,11 @@ int parse_cmp_expr() { } else if (token_type == TOKEN_LE) { int rhs = parse_shift_expr(); int sgt = asm_rr_cmp("sgt", lhs, rhs); - lhs = asm_r("seqz", sgt); + lhs = asm_r(TYPE_INT, "seqz", sgt); } else if (token_type == TOKEN_GE) { int rhs = parse_shift_expr(); int slt = asm_rr_cmp("slt", lhs, rhs); - lhs = asm_r("seqz", slt); + lhs = asm_r(TYPE_INT, "seqz", slt); } else { unget_token(); break; @@ -1023,11 +1199,11 @@ int parse_eq_expr() { if (token_type == TOKEN_EQ) { int rhs = parse_cmp_expr(); int xor0 = asm_rr_cmp("xor", lhs, rhs); - lhs = asm_r("seqz", xor0); + lhs = asm_r(TYPE_INT, "seqz", xor0); } else if (token_type == TOKEN_NE) { int rhs = parse_cmp_expr(); int xor0 = asm_rr_cmp("xor", lhs, rhs); - lhs = asm_r("snez", xor0); + lhs = asm_r(TYPE_INT, "snez", xor0); } else { unget_token(); break; @@ -1084,48 +1260,58 @@ int parse_bitwise_or_expr() { int parse_logical_and_expr() { int lhs = parse_bitwise_or_expr(); - int label = next_label(); - int label_used = 0; + int logical = 0; + int label; + int result; while (1) { next_token(); if (token_type == TOKEN_LAND) { - lhs = asm_r("snez", lhs); - asm_beqz(lhs, label); + if (!logical) { + logical = 1; + label = next_label(); + result = next_reg(TYPE_INT); + _asm_r("snez", result, lhs); + } + asm_beqz(result, label); int rhs = parse_bitwise_or_expr(); - rhs = asm_r("snez", rhs); - lhs = asm_rr("and", lhs, rhs); - label_used = 1; + _asm_r("snez", result, rhs); } else { unget_token(); break; } } - if (label_used) { + if (logical) { asm_label(label); + return result; } return lhs; } int parse_logical_or_expr() { int lhs = parse_logical_and_expr(); - int label = next_label(); - int label_used = 0; + int logical = 0; + int label; + int result; while (1) { next_token(); if (token_type == TOKEN_LOR) { - lhs = asm_r("snez", lhs); - asm_bnez(lhs, label); + if (!logical) { + logical = 1; + label = next_label(); + result = next_reg(TYPE_INT); + _asm_r("snez", result, lhs); + } + asm_bnez(result, label); int rhs = parse_logical_and_expr(); - rhs = asm_r("snez", rhs); - lhs = asm_rr("or", lhs, rhs); - label_used = 1; + _asm_r("snez", result, rhs); } else { unget_token(); break; } } - if (label_used) { + if (logical) { asm_label(label); + return result; } return lhs; } @@ -1135,8 +1321,7 @@ int parse_assign_expr() { next_token(); if (token_type == TOKEN_ASSIGN) { int rhs = parse_assign_expr(); - load(0, rhs); - store_t0(lhs); + asm_mv(lhs, rhs); return lhs; } else { unget_token(); @@ -1165,10 +1350,9 @@ void parse_local_variable(int type) { int size = token_data; expect_token(TOKEN_BRACKET_RIGHT); declare_local_array(id, type, size); - next_token(); - } else { - declare_local(id, type); - } + return; + } + int slot = declare_local(id, type); if (token_type == TOKEN_SEMICOLON) { unget_token(); return; @@ -1176,8 +1360,13 @@ void parse_local_variable(int type) { unget_token(); expect_token(TOKEN_ASSIGN); int reg = parse_expr(); - load(0, reg); - store_t0(local_table[id]); + if (type != reg_type[reg]) { + eprintf("type mismatch in assignment\n"); + exit(1); + } + asm_mv(REG_T0, reg); + load_local_address(REG_T2, slot); + printf(" %s t0, 0(t2)\n", store_op_of_type(type)); } void parse_stmt(); @@ -1290,9 +1479,8 @@ void parse_stmt() { return; } unget_token(); - int reg = parse_expr(); - load(0, reg); - printf(" mv a0, t0\n"); + int rs1 = parse_expr(); + asm_mv(REG_A0, rs1); asm_j(epilog_label); } else if (token_type == TOKEN_BREAK) { int label = asm_get_break_label(); @@ -1335,7 +1523,7 @@ void parse_function(const char* name) { } int arg_type = parse_type(); if (arg_type < 0 || arg_type == TYPE_VOID) { - eprintf("unexpected a non-void argument type"); + eprintf("unexpected a non-void argument type: %d\n", arg_type); exit(1); } expect_token(TOKEN_ID); @@ -1392,16 +1580,23 @@ void parse_function(const char* name) { asm_addi("sp", "sp", -frame_size); asm_sd("ra", frame_size - 8, "sp"); asm_sd("fp", frame_size - 16, "sp"); + for (int reg = REG_S2; reg <= REG_S11; ++reg) { + asm_sd(reg_name(reg), frame_size - reg * 8 + 15 * 8, "sp"); + } asm_addi("fp", "sp", frame_size); for (int i = 0; i < arg; ++i) { - printf(" mv t0, a%d\n", i); - store_t0(args[i]); + int slot = args[i]; + load_local_address(REG_T2, slot); + printf(" %s a%d, 0(t2)\n", store_op_of_type(local_type[slot]), i); } asm_j(label); // epilog asm_label(epilog_label); - asm_ld("fp", frame_size - 16, "sp"); asm_ld("ra", frame_size - 8, "sp"); + asm_ld("fp", frame_size - 16, "sp"); + for (int reg = REG_S2; reg <= REG_S11; ++reg) { + asm_ld(reg_name(reg), frame_size - reg * 8 + 15 * 8, "sp"); + } asm_addi("sp", "sp", frame_size); printf(" ret\n"); } @@ -1496,6 +1691,7 @@ void dump_string_table() { } int main() { + eprintf("start compiling...\n"); parse_top_level(); dump_string_table(); return 0; diff --git a/boot.sh b/boot.sh index 91909ec..c4a4ecb 100644 --- a/boot.sh +++ b/boot.sh @@ -5,7 +5,7 @@ gcc ../boot.c ../boot-lib.c -o gcc.out && riscv64-linux-gnu-gcc-12 -static boot1.s ../boot-lib.c -o boot1.out && qemu-riscv64 boot1.out < boot-all.c > boot2.s && riscv64-linux-gnu-gcc-12 -static boot2.s ../boot-lib.c -o boot2.out && -qemu-riscv64 boot2.out < boot-all.c > boot3.s && +qemu-riscv64 boot2.out < boot-all.c > boot3.s cmp --silent boot1.s boot2.s && echo "boot1.s == boot2.s" || echo "boot1.s != boot2.s" cmp --silent boot2.s boot3.s && echo "boot2.s == boot3.s" || echo "boot2.s != boot3.s" cmp --silent boot1.s boot3.s && echo "boot1.s == boot3.s" || echo "boot1.s != boot3.s" From 2097d9fd349e5d380a88ec4c099fb4d2a4b4ae29 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Thu, 28 Nov 2024 22:50:12 +0800 Subject: [PATCH 02/11] more demo --- demo/add.c | 9 +++++++++ demo/lut.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ demo/parse.c | 19 +++++++++++++++++++ demo/strcmp.c | 17 +++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 demo/add.c create mode 100644 demo/lut.c create mode 100644 demo/parse.c create mode 100644 demo/strcmp.c diff --git a/demo/add.c b/demo/add.c new file mode 100644 index 0000000..aa3f2f6 --- /dev/null +++ b/demo/add.c @@ -0,0 +1,9 @@ +int printf(const char format[], ...); +int scanf(const char format[], ...); +int putchar(int ch); + + +int main() { + int a = 1; + return (a+(a+(a+(a+(a+(a+(a+(a+(a+(a+(a))))))))))); +} \ No newline at end of file diff --git a/demo/lut.c b/demo/lut.c new file mode 100644 index 0000000..f5b23ba --- /dev/null +++ b/demo/lut.c @@ -0,0 +1,47 @@ +int printf(const char format[], ...); +int getchar(); + +char string_table[65536]; +int string_offset; +int string_lut[4096]; +int string_lut_size; + +int parse_string() { + int offset = string_offset; + int ch; + while ((ch = getchar()) != '"') { + if (ch == -1 || ch == '\n') { + printf("expecting '\"'\n"); + return 1; + } + string_table[string_offset++] = ch; + } + string_table[string_offset++] = 0; + string_lut[string_lut_size] = offset; + return string_lut_size++; +} + + +int streq(const char* s1, const char* s2) { + while (*s1 && *s2 && *s1 == *s2) { + s1++; + s2++; + } + return *s1 == *s2; +} + +void dump_string_table() { + printf(".data\n"); + for (int i = 0; i < string_lut_size; ++i) { + char* id = string_table + string_lut[i]; + printf(".LC%d: .string \"%s\", const: %d\n", + i, id, streq(id, "const")); + } +} + +int main() { + char ch; + while ((ch = getchar()) == '"') parse_string(); + dump_string_table(); + return 0; +} \ No newline at end of file diff --git a/demo/parse.c b/demo/parse.c new file mode 100644 index 0000000..98b7e9d --- /dev/null +++ b/demo/parse.c @@ -0,0 +1,19 @@ +int getchar(); + + +int is_digit(int ch) { + return '0' <= ch && ch <= '9'; +} + +int parse_int(int ch) { + int num = ch - '0'; + while (is_digit(ch = getchar())) { + num = num * 10; + num = num + ch - '0'; + } + return num; +} + +int main() { + return parse_int(getchar()); +} \ No newline at end of file diff --git a/demo/strcmp.c b/demo/strcmp.c new file mode 100644 index 0000000..6d4793e --- /dev/null +++ b/demo/strcmp.c @@ -0,0 +1,17 @@ +int printf(const char* format, ...); + +int strcmp(const char* s1, const char* s2) { + while (*s1 && *s2 && *s1 == *s2) { + s1++; + s2++; + } + return *s1 - *s2; +} + +int main() { + const char* s1 = "helloworld"; + const char* s2 = "world"; + printf("%d\n", strcmp(s1, s2)); + printf("%d\n", strcmp(s1 + 5, s2)); + return 0; +} \ No newline at end of file From 44051c7985a020bbe8d80e07cba18f92e64390c2 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Fri, 29 Nov 2024 09:49:57 +0800 Subject: [PATCH 03/11] use sp to find local --- boot.c | 143 ++++++++++++++++++++++++++++++----------------------- demo/add.c | 12 ++++- 2 files changed, 91 insertions(+), 64 deletions(-) diff --git a/boot.c b/boot.c index 3dbf328..a7b16de 100644 --- a/boot.c +++ b/boot.c @@ -429,8 +429,8 @@ int parse_type() { int epilog_label; int local_table[4096]; // id -> local id -int next_local_id = 12; -int max_local_id = 12; +int next_local_id = 1; +int max_local_id = 1; const int MARKER_TEMP = 0; const int MARKER_SCALAR = 1; @@ -534,8 +534,9 @@ int is_overflow(int reg) { } void reset_local() { - next_local_id = 12; - max_local_id = 12; + next_local_id = 1; + max_local_id = 1; + max_reg_id = REG_S2; for (int i = 0; i < 4096; ++i) { local_table[i] = 0; local_marker[i] = MARKER_TEMP; @@ -545,7 +546,7 @@ void reset_local() { } void reset_temp() { - while (next_local_id > 12 && local_marker[next_local_id - 1] == MARKER_TEMP) { + while (next_local_id > 1 && local_marker[next_local_id - 1] == MARKER_TEMP) { --next_local_id; } reset_reg(); @@ -569,8 +570,9 @@ int declare_local(int id, int type) { int declare_local_array(int id, int type, int size) { if (local_table[id] != 0) return local_table[id]; - int slot; - for (int i = 0; i < size; ++i) local_marker[slot = next_local_slot(type)] = MARKER_ARRAY; + int slot = next_local_slot(type); + local_marker[slot] = MARKER_ARRAY; + for (int i = 1; i < size; ++i) local_marker[next_local_slot(type)] = MARKER_ARRAY; return local_table[id] = slot; } @@ -632,21 +634,19 @@ void asm_addi(const char* rd, const char* rs, int imm) { // assembly helpers // address loaders -// rd must be one of 0, 1, 2 +// rd must be one of t0, t1, t2 void load_local_address(int rd, int slot_id) { - // TODO use sp instead - int offset = -slot_id * 8 - 8; + int offset = slot_id * 8 - 8; const char* rd_name = reg_name(rd); if (check_itype_immediate(offset)) { - printf(" addi %s, fp, %d\n", rd_name, offset); + printf(" addi %s, sp, %d\n", rd_name, offset); } else { printf(" li %s, %d\n", rd_name, offset); - printf(" add %s, fp, %s\n", rd_name, rd_name); + printf(" add %s, sp, %s\n", rd_name, rd_name); } } -const char* load_op(int reg) { - int type = reg_type[reg]; +const char* load_op_of_type(int type) { if (type & TYPE_PTR_MASK) { return "ld"; } else if (type == TYPE_CHAR) { @@ -666,14 +666,10 @@ const char* store_op_of_type(int type) { } } -const char* store_op(int reg) { - return store_op_of_type(reg_type[reg]); -} - // load a non-trivial register into t0, t1 or t2 // rd must be one of t0, t1, t2 void load(int rd, int reg) { - const char* op = load_op(reg); + const char* op = load_op_of_type(reg_type[reg]); const char* rd_name = reg_name(rd); if (is_overflow(reg)) { load_local_address(rd, overflow[reg]); @@ -687,7 +683,7 @@ void load(int rd, int reg) { // store t0 into a non-trivial register void store_t0(int reg) { - const char* op = store_op(reg); + const char* op = store_op_of_type(reg_type[reg]); if (is_overflow(reg)) { load_local_address(REG_T2, overflow[reg]); if (indirection[reg]) { @@ -710,7 +706,8 @@ void _asm_r(const char* op, int rd, int rs1) { rs1_name = "t0"; load(REG_T0, rs1); } - printf(" %s %s, %s\n", op, rd_name, rs1_name); + if (!(streq(op, "mv") && streq(rd_name, rs1_name))) + printf(" %s %s, %s\n", op, rd_name, rs1_name); if (is_nontrivial(rd)) { store_t0(rd); } @@ -758,14 +755,26 @@ void _asm_branch(const char* op, int rs1, int label) { printf(" %s %s, L%d\n", op, rs1_name, label); } +void _asm_i(const char* op, int rd, const char* prefix1, const char* prefix2, int imm) { + const char* rd_name = reg_name(rd); + if (is_nontrivial(rd)) rd_name = "t0"; + printf(" %s %s, %s%s%d\n", op, rd_name, prefix1, prefix2, imm); + if (is_nontrivial(rd)) { + store_t0(rd); + } +} + int asm_r(int type, const char* op, int rs1) { - int rd = next_reg(type); + int rd = rs1; + if (indirection[rs1] || reg_type[rs1] != type) rd = next_reg(type); _asm_r(op, rd, rs1); return rd; } int asm_rr(int type, const char* op, int rs1, int rs2) { - int rd = next_reg(type); + int rd = rs1; + if (indirection[rs1] || reg_type[rs1] != type) rd = rs2; + if (indirection[rs2] || reg_type[rs2] != type) rd = next_reg(type); _asm_rr(op, rd, rs1, rs2); return rd; } @@ -774,25 +783,34 @@ void asm_mv(int rd, int rs1) { _asm_r("mv", rd, rs1); } -int materialize_t0(int type) { - int rd = next_reg(type); - asm_mv(rd, REG_T0); - return rd; +void store_into_local(int rs1, int slot) { + const char* rs1_name = reg_name(rs1); + if (is_nontrivial(rs1)) { + rs1_name = "t0"; + load(REG_T0, rs1); + } + load_local_address(REG_T2, slot); + printf(" %s %s, 0(t2)\n", store_op_of_type(local_type[slot]), rs1_name); } -int materialize_address(int type, int marker) { +int materialize_address(int rd, int type, int marker) { if (marker == MARKER_ARRAY) { type = type | TYPE_PTR_MASK; } - int rd = materialize_t0(TYPE_VOID_PTR); reg_type[rd] = type; indirection[rd] = marker == MARKER_SCALAR; return rd; } int lookup_from_slot(int slot) { - load_local_address(REG_T0, slot); - return materialize_address(local_type[slot], local_marker[slot]); + int reg = next_reg(TYPE_VOID_PTR); + if (is_nontrivial(reg)) { + load_local_address(REG_T0, slot); + asm_mv(reg, REG_T0); + } else { + load_local_address(reg, slot); + } + return materialize_address(reg, local_type[slot], local_marker[slot]); } int lookup(int id) { @@ -805,9 +823,10 @@ int lookup(int id) { if (global_marker[id] == MARKER_FUNCTION) { eprintf("function name must not appear outside function call: %s\n", name); exit(1); - } - printf(" la t0, %s # id: %d\n", name, id); - return materialize_address(global_type[id], global_marker[id]); + } + int reg = next_reg(TYPE_VOID_PTR); + _asm_i("la", reg, name, " # id: ", id); + return materialize_address(reg, global_type[id], global_marker[id]); } eprintf("unresolved identifier: %s\n", name); exit(1); @@ -933,8 +952,8 @@ int asm_sub(int lhs, int rhs) { } int difference = asm_rr(TYPE_INT, "sub", lhs, rhs); int shift = 2 * (lhs_type == TYPE_INT_PTR); - _asm_ri("slli", REG_T0, difference, shift); - return materialize_t0(TYPE_INT); + _asm_ri("slli", difference, difference, shift); + return difference; } if (type1) { int neg = asm_r_arith("neg", rhs); @@ -998,13 +1017,12 @@ int parse_function_call(int id) { for (int i = 0; i < arg; ++i) { asm_mv(i + REG_A0, args[i]); } - // TODO saved by caller printf(" call %s\n", name); - // TODO saved by caller int type = global_type[id]; if (type != TYPE_VOID) { - printf(" mv t0, a0\n"); - return materialize_t0(type); + int rd = next_reg(type); + asm_mv(rd, REG_A0); + return rd; } return -1; } @@ -1014,8 +1032,9 @@ int parse_primary_expr() { if (token_type == TOKEN_EOF) { exit(1); } else if (token_type == TOKEN_NUMBER) { - printf(" li t0, %d\n", token_data); - return materialize_t0(TYPE_INT); + int reg = next_reg(TYPE_INT); + _asm_i("li", reg, "", "", token_data); + return reg; } else if (token_type == TOKEN_ID) { next_token(); if (token_type == TOKEN_PAREN_LEFT) { @@ -1024,8 +1043,9 @@ int parse_primary_expr() { unget_token(); return lookup(token_data); } else if (token_type == TOKEN_STRING) { - printf(" la t0, .LC%d\n", token_data); - return materialize_t0(TYPE_CHAR_PTR); + int reg = next_reg(TYPE_CHAR_PTR); + _asm_i("la", reg, ".LC", "", token_data); + return reg; } else if (token_type == TOKEN_PAREN_LEFT) { int reg = parse_expr(); expect_token(TOKEN_PAREN_RIGHT); @@ -1343,7 +1363,7 @@ void parse_local_variable(int type) { next_token(); if (token_type == TOKEN_BRACKET_LEFT) { if (type & TYPE_PTR_MASK) { - eprintf("local variable of array of pointers is not supported\n"); + eprintf("array of pointers is not supported\n"); exit(1); } expect_token(TOKEN_NUMBER); @@ -1364,9 +1384,7 @@ void parse_local_variable(int type) { eprintf("type mismatch in assignment\n"); exit(1); } - asm_mv(REG_T0, reg); - load_local_address(REG_T2, slot); - printf(" %s t0, 0(t2)\n", store_op_of_type(type)); + store_into_local(reg, slot); } void parse_stmt(); @@ -1533,7 +1551,7 @@ void parse_function(const char* name) { expect_token(TOKEN_BRACKET_RIGHT); next_token(); if (arg_type & TYPE_PTR_MASK) { - eprintf("local variable of array of pointers is not supported\n"); + eprintf("array of pointers is not supported\n"); exit(1); } arg_type = arg_type | TYPE_PTR_MASK; @@ -1571,7 +1589,9 @@ void parse_function(const char* name) { parse_stmt(); } asm_j(epilog_label); - int frame_size = max_local_id * 8; + int reg_used = max_reg_id - REG_S2; + if (reg_used > 10) reg_used = 10; + int frame_size = (max_local_id - 1 + reg_used + 2) * 8; if (frame_size % 16 != 0) { frame_size = frame_size + 8; } @@ -1580,22 +1600,22 @@ void parse_function(const char* name) { asm_addi("sp", "sp", -frame_size); asm_sd("ra", frame_size - 8, "sp"); asm_sd("fp", frame_size - 16, "sp"); - for (int reg = REG_S2; reg <= REG_S11; ++reg) { - asm_sd(reg_name(reg), frame_size - reg * 8 + 15 * 8, "sp"); + for (int i = 0; i < reg_used; ++i) { + int reg = REG_S2 + i; + asm_sd(reg_name(reg), frame_size - 24 - i * 8, "sp"); } asm_addi("fp", "sp", frame_size); for (int i = 0; i < arg; ++i) { - int slot = args[i]; - load_local_address(REG_T2, slot); - printf(" %s a%d, 0(t2)\n", store_op_of_type(local_type[slot]), i); + store_into_local(REG_A0 + i, args[i]); } asm_j(label); // epilog asm_label(epilog_label); asm_ld("ra", frame_size - 8, "sp"); asm_ld("fp", frame_size - 16, "sp"); - for (int reg = REG_S2; reg <= REG_S11; ++reg) { - asm_ld(reg_name(reg), frame_size - reg * 8 + 15 * 8, "sp"); + for (int i = 0; i < reg_used; ++i) { + int reg = REG_S2 + i; + asm_ld(reg_name(reg), frame_size - 24 - i * 8, "sp"); } asm_addi("sp", "sp", frame_size); printf(" ret\n"); @@ -1606,10 +1626,6 @@ void parse_global_variable(int id, const char* name, int type) { eprintf("global variable of void type is not supported\n"); exit(1); } - if (type & TYPE_PTR_MASK) { - eprintf("global variable of pointer is not supported\n"); - exit(1); - } printf(".data\n"); printf(".globl %s\n", name); printf(".align 5\n"); @@ -1618,6 +1634,10 @@ void parse_global_variable(int id, const char* name, int type) { expect_token(TOKEN_NUMBER); printf(" .word %d\n", token_data); } else if (token_type == TOKEN_BRACKET_LEFT) { + if (type & TYPE_PTR_MASK) { + eprintf("array of pointers is not supported\n"); + exit(1); + } expect_token(TOKEN_NUMBER); int size = token_data; expect_token(TOKEN_BRACKET_RIGHT); @@ -1691,7 +1711,6 @@ void dump_string_table() { } int main() { - eprintf("start compiling...\n"); parse_top_level(); dump_string_table(); return 0; diff --git a/demo/add.c b/demo/add.c index aa3f2f6..6c54b5f 100644 --- a/demo/add.c +++ b/demo/add.c @@ -2,8 +2,16 @@ int printf(const char format[], ...); int scanf(const char format[], ...); int putchar(int ch); +int* p; +int f1() { + int a = 1; + return *(a+(a+(a+(a+(a+(a+(a+(a+(a+(a+(p))))))))))); // a[10] +} + int main() { - int a = 1; - return (a+(a+(a+(a+(a+(a+(a+(a+(a+(a+(a))))))))))); + int a[15]; + p = a; + for (int i = 0; i < 15; ++i) a[i] = i; + return f1(); } \ No newline at end of file From 57837abbbae295ad83307f1a78ee515eddd5e521 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Fri, 29 Nov 2024 10:54:07 +0800 Subject: [PATCH 04/11] use t3-t6 --- boot.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/boot.c b/boot.c index a7b16de..1b51e0e 100644 --- a/boot.c +++ b/boot.c @@ -521,16 +521,16 @@ const char* reg_name(int reg) { if (reg == 25) return "s9"; if (reg == 26) return "s10"; if (reg == 27) return "s11"; - // overflow begin if (reg == 28) return "t3"; if (reg == 29) return "t4"; if (reg == 30) return "t5"; if (reg == 31) return "t6"; + // overflow begin return 0; } int is_overflow(int reg) { - return reg > REG_S11; + return reg > REG_T6; } void reset_local() { @@ -1017,7 +1017,17 @@ int parse_function_call(int id) { for (int i = 0; i < arg; ++i) { asm_mv(i + REG_A0, args[i]); } + for (int i = REG_T3; i <= REG_T6; ++i) { + if (i < max_reg_id) { + asm_sd(reg_name(i), (REG_S2 - i) * 8 - 24, "fp"); + } + } printf(" call %s\n", name); + for (int i = REG_T3; i <= REG_T6; ++i) { + if (i < max_reg_id) { + asm_ld(reg_name(i), (REG_S2 - i) * 8 - 24, "fp"); + } + } int type = global_type[id]; if (type != TYPE_VOID) { int rd = next_reg(type); @@ -1355,7 +1365,7 @@ int parse_expr() { void parse_local_variable(int type) { if (type == TYPE_VOID) { - eprintf("local variable of void type is not supported\n"); + eprintf("variable cannot be of void type\n"); exit(1); } expect_token(TOKEN_ID); @@ -1556,6 +1566,10 @@ void parse_function(const char* name) { } arg_type = arg_type | TYPE_PTR_MASK; } + if (arg >= 8) { + eprintf("too many arguments\n"); + exit(1); + } args[arg++] = declare_local(token_data, arg_type); if (token_type == TOKEN_COMMA) { // continue; @@ -1590,8 +1604,9 @@ void parse_function(const char* name) { } asm_j(epilog_label); int reg_used = max_reg_id - REG_S2; - if (reg_used > 10) reg_used = 10; + if (reg_used > 14) reg_used = 14; int frame_size = (max_local_id - 1 + reg_used + 2) * 8; + if (reg_used > 10) reg_used = 10; if (frame_size % 16 != 0) { frame_size = frame_size + 8; } @@ -1623,7 +1638,7 @@ void parse_function(const char* name) { void parse_global_variable(int id, const char* name, int type) { if (type == TYPE_VOID) { - eprintf("global variable of void type is not supported\n"); + eprintf("variable cannot be of void type\n"); exit(1); } printf(".data\n"); From 967a203414e528de7237fbfeb5c562010fd285d9 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Fri, 29 Nov 2024 11:16:58 +0800 Subject: [PATCH 05/11] string table and zero --- boot.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/boot.c b/boot.c index 1b51e0e..91f6486 100644 --- a/boot.c +++ b/boot.c @@ -145,6 +145,24 @@ int parse_string() { return string_lut_size++; } +void rewind_string(int new_data) { + string_offset = string_lut[token_data]; + token_data = new_data; + --string_lut_size; +} + +void dedup_string() { + int last_string = string_lut_size - 1; + char* latest = string_table + string_lut[last_string]; + for (int i = 0; i < last_string; i++) { + char* candidate = string_table + string_lut[i]; + if (streq(candidate, latest)) { + rewind_string(i); + return; + } + } +} + char id_table[65536]; int id_offset; int id_lut[4096]; @@ -361,6 +379,7 @@ void next_token() { } else if (ch == '"') { token_type = TOKEN_STRING; token_data = parse_string(); + dedup_string(); } else if (ch == '.') { int ch2 = getchar(); if (ch2 == '.') { @@ -488,6 +507,7 @@ void reset_reg() { indirection[i] = 0; overflow[i] = 0; } + reg_type[REG_ZERO] = TYPE_INT; } const char* reg_name(int reg) { @@ -764,17 +784,21 @@ void _asm_i(const char* op, int rd, const char* prefix1, const char* prefix2, in } } +int is_not_reusable(int rs1, int expected_type) { + return indirection[rs1] || reg_type[rs1] != expected_type || rs1 == REG_ZERO; +} + int asm_r(int type, const char* op, int rs1) { int rd = rs1; - if (indirection[rs1] || reg_type[rs1] != type) rd = next_reg(type); + if (is_not_reusable(rs1, type)) rd = next_reg(type); _asm_r(op, rd, rs1); return rd; } int asm_rr(int type, const char* op, int rs1, int rs2) { int rd = rs1; - if (indirection[rs1] || reg_type[rs1] != type) rd = rs2; - if (indirection[rs2] || reg_type[rs2] != type) rd = next_reg(type); + if (is_not_reusable(rs1, type)) rd = rs2; + if (is_not_reusable(rs2, type)) rd = next_reg(type); _asm_rr(op, rd, rs1, rs2); return rd; } @@ -1042,6 +1066,7 @@ int parse_primary_expr() { if (token_type == TOKEN_EOF) { exit(1); } else if (token_type == TOKEN_NUMBER) { + if (token_data == 0) return REG_ZERO; int reg = next_reg(TYPE_INT); _asm_i("li", reg, "", "", token_data); return reg; From 66505c1389f418523d28809083d695119e9b88a2 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Fri, 29 Nov 2024 16:00:22 +0800 Subject: [PATCH 06/11] const table --- README.md | 22 ++++++++++++++++------ boot.c | 52 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f2c2324..7b37e2a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RVBTCC -- 约 1500 行的轻量级自举编译器。 +- 约 1800 行的轻量级自举编译器。 - 编译器和自举编译器行为一致。 - 语法类似 C,输出 RISC-V 汇编。 - 依赖几个 libc 函数用于输入输出。 @@ -64,7 +64,21 @@ $ sh boot.sh 本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const`。 -`const` 关键字可以在类型中使用,但会被直接忽略。支持它是为了更好兼容 C 程序。 +### `const` 关键字 + +`const` 关键字可以在类型中使用,在大部分情况下会被直接忽略。支持它是为了更好兼容 C 程序。 + +但是当在出现 + +- 全局,标量(即不是数组) +- 类型为 `const int` 或 `const int const` +- 带有初始化 + +的声明时,将会被解析为整数常量。 + +整数常量在使用的时候会被直接替换为对应的右值,失去作为全局变量左值的性质。 + +使用 `int const` 或 `int` 可以避免这样的特殊处理。 ### 支持六个基本类型 @@ -75,13 +89,11 @@ $ sh boot.sh | `int` | `int*` | - 注意指针类型不是复合得来的,而是被视作整体。因此也不存在二重指针。 - - 函数和数组不是类型系统的一部分。 - 可以认为数组的类型就是其元素对应的指针类型。 - 函数的参数类型和个数不会检查,返回值会参与类型检查。 - 函数名只能被用于调用,函数调用被视为初等表达式。 - 数组只支持一维数组,且数组的元素不能是指针类型。 -- 全局变量不能是指针类型。 - 整数和字符字面量的类型是 `int`,字符串字面量的类型是 `char*` ### 支持的流程控制 @@ -116,8 +128,6 @@ $ sh boot.sh - 算术运算的结果总是被提升为 `int` 类型。布尔值用 `int` 类型表示。 - 由于空指针就是 `0`,因此指针和整数之间的比较运算没有禁止。 - 逻辑与和逻辑或支持短路求值。 -- 表达式没有左值和右值之分。可以认为右值总是存在一个临时的变量中。 -- 赋值不检查类型。强制类型转换可以用赋值给特定类型的变量实现。 ### 其它支持与不支持 diff --git a/boot.c b/boot.c index 91f6486..0586617 100644 --- a/boot.c +++ b/boot.c @@ -381,17 +381,14 @@ void next_token() { token_data = parse_string(); dedup_string(); } else if (ch == '.') { - int ch2 = getchar(); - if (ch2 == '.') { - int ch3 = getchar(); - if (ch3 == '.') { + token_type = 0; + if (getchar() == '.') { + if (getchar() == '.') { token_type = TOKEN_ELLIPSIS; - } else { - eprintf("unexpected character: %c\n", ch3); - exit(1); } - } else { - eprintf("unexpected character: %c\n", ch2); + } + if (token_type != TOKEN_ELLIPSIS) { + eprintf("expecting '...'\n"); exit(1); } } else if (is_digit(ch)) { @@ -467,6 +464,9 @@ int max_reg_id = 18; int indirection[4096]; int overflow[4096]; +int const_table[4096]; // id -> value +int is_const[4096]; + const int REG_ZERO = 0; const int REG_RA = 1; const int REG_SP = 2; @@ -837,10 +837,19 @@ int lookup_from_slot(int slot) { return materialize_address(reg, local_type[slot], local_marker[slot]); } +int load_imm(int imm) { + if (imm == 0) return REG_ZERO; + int reg = next_reg(TYPE_INT); + _asm_i("li", reg, "", "", imm); + return reg; +} + int lookup(int id) { - int slot = local_table[id]; - if (slot) { - return lookup_from_slot(slot); + if (local_table[id]) { + return lookup_from_slot(local_table[id]); + } + if (is_const[id]) { + return load_imm(const_table[id]); } const char* name = id_table + id_lut[id]; if (global_marker[id]) { @@ -1066,10 +1075,7 @@ int parse_primary_expr() { if (token_type == TOKEN_EOF) { exit(1); } else if (token_type == TOKEN_NUMBER) { - if (token_data == 0) return REG_ZERO; - int reg = next_reg(TYPE_INT); - _asm_i("li", reg, "", "", token_data); - return reg; + return load_imm(token_data); } else if (token_type == TOKEN_ID) { next_token(); if (token_type == TOKEN_PAREN_LEFT) { @@ -1695,16 +1701,28 @@ void parse_global_variable(int id, const char* name, int type) { } void parse_global_declaration() { + int is_const_int = 1; + if (token_type != TOKEN_CONST) { + is_const_int = 0; + } int type = parse_type(); if (type < 0) { eprintf("expecting type for global declaration\n"); exit(1); } + if (type != TYPE_INT) { + is_const_int = 0; + } expect_token(TOKEN_ID); int id = token_data; char* name = id_table + id_lut[id]; next_token(); - if (token_type == TOKEN_PAREN_LEFT) { + if (is_const_int && token_type == TOKEN_ASSIGN) { + expect_token(TOKEN_NUMBER); + const_table[id] = token_data; + is_const[id] = 1; + expect_token(TOKEN_SEMICOLON); + } else if (token_type == TOKEN_PAREN_LEFT) { declare_global(id, MARKER_FUNCTION, type); parse_function(name); } else { From 4893f0cd6865f022ad29961b198a05fa515c2747 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Sat, 30 Nov 2024 00:31:33 +0800 Subject: [PATCH 07/11] refactor mv --- boot.c | 187 ++++++++++++++++++++++++++------------------------------- 1 file changed, 84 insertions(+), 103 deletions(-) diff --git a/boot.c b/boot.c index 0586617..8aa7128 100644 --- a/boot.c +++ b/boot.c @@ -653,19 +653,6 @@ void asm_addi(const char* rd, const char* rs, int imm) { // assembly helpers -// address loaders -// rd must be one of t0, t1, t2 -void load_local_address(int rd, int slot_id) { - int offset = slot_id * 8 - 8; - const char* rd_name = reg_name(rd); - if (check_itype_immediate(offset)) { - printf(" addi %s, sp, %d\n", rd_name, offset); - } else { - printf(" li %s, %d\n", rd_name, offset); - printf(" add %s, sp, %s\n", rd_name, rd_name); - } -} - const char* load_op_of_type(int type) { if (type & TYPE_PTR_MASK) { return "ld"; @@ -686,23 +673,28 @@ const char* store_op_of_type(int type) { } } -// load a non-trivial register into t0, t1 or t2 +// address loaders // rd must be one of t0, t1, t2 -void load(int rd, int reg) { - const char* op = load_op_of_type(reg_type[reg]); - const char* rd_name = reg_name(rd); - if (is_overflow(reg)) { - load_local_address(rd, overflow[reg]); - if (indirection[reg]) { - printf(" ld %s, 0(%s)\n", rd_name, rd_name); - } - reg = rd; - } - printf(" %s %s, 0(%s) # load non-trivial register\n", op, rd_name, reg_name(reg)); +void load_local_address(int rd, int slot_id) { + asm_addi(reg_name(rd), "sp", slot_id * 8 - 8); } -// store t0 into a non-trivial register -void store_t0(int reg) { +// load a non-trivial register into trivial one +void load(int rd, int rs) { + const char* op = load_op_of_type(reg_type[rs]); + const char* rd_name = reg_name(rd); + if (is_overflow(rs)) { + load_local_address(rd, overflow[rs]); + if (indirection[rs]) { + printf(" ld %s, 0(%s)\n", rd_name, rd_name); + } + rs = rd; + } + printf(" %s %s, 0(%s) # load non-trivial register\n", op, rd_name, reg_name(rs)); +} + +// store a trivial register into a non-trivial one +void store(const char* rs, int reg) { const char* op = store_op_of_type(reg_type[reg]); if (is_overflow(reg)) { load_local_address(REG_T2, overflow[reg]); @@ -711,67 +703,65 @@ void store_t0(int reg) { } reg = REG_T2; } - printf(" %s t0, 0(%s) # store non-trivial register\n", op, reg_name(reg)); + printf(" %s %s, 0(%s) # store non-trivial register\n", op, rs, reg_name(reg)); } int is_nontrivial(int reg) { return is_overflow(reg) || indirection[reg]; } +const char* trivialize(int rs, int t) { + if (is_nontrivial(rs)) { + load(t, rs); + return reg_name(t); + } + return reg_name(rs); +} + void _asm_r(const char* op, int rd, int rs1) { const char* rd_name = reg_name(rd); - const char* rs1_name = reg_name(rs1); if (is_nontrivial(rd)) rd_name = "t0"; - if (is_nontrivial(rs1)) { - rs1_name = "t0"; - load(REG_T0, rs1); - } - if (!(streq(op, "mv") && streq(rd_name, rs1_name))) - printf(" %s %s, %s\n", op, rd_name, rs1_name); + const char* rs1_name = trivialize(rs1, REG_T0); + printf(" %s %s, %s\n", op, rd_name, rs1_name); if (is_nontrivial(rd)) { - store_t0(rd); + store("t0", rd); + } +} + +void asm_mv(int rd, int rs1) { + const char* rs1_name = trivialize(rs1, REG_T0); + if (is_nontrivial(rd)) { + store(rs1_name, rd); + } else { + const char* rd_name = reg_name(rd); + if (!streq(rd_name, rs1_name)) + printf(" mv %s, %s\n", rd_name, rs1_name); } } void _asm_rr(const char* op, int rd, int rs1, int rs2) { const char* rd_name = reg_name(rd); - const char* rs1_name = reg_name(rs1); - const char* rs2_name = reg_name(rs2); + const char* rs1_name = trivialize(rs1, REG_T0); + const char* rs2_name = trivialize(rs2, REG_T1); if (is_nontrivial(rd)) rd_name = "t0"; - if (is_nontrivial(rs1)) { - rs1_name = "t0"; - load(REG_T0, rs1); - } - if (is_nontrivial(rs2)) { - rs2_name = "t1"; - load(REG_T1, rs2); - } printf(" %s %s, %s, %s\n", op, rd_name, rs1_name, rs2_name); if (is_nontrivial(rd)) { - store_t0(rd); + store("t0", rd); } } void _asm_ri(const char* op, int rd, int rs1, int imm) { const char* rd_name = reg_name(rd); - const char* rs1_name = reg_name(rs1); if (is_nontrivial(rd)) rd_name = "t0"; - if (is_nontrivial(rs1)) { - rs1_name = "t0"; - load(REG_T0, rs1); - } + const char* rs1_name = trivialize(rs1, REG_T0); printf(" %s %s, %s, %d\n", op, rd_name, rs1_name, imm); if (is_nontrivial(rd)) { - store_t0(rd); + store("t0", rd); } } void _asm_branch(const char* op, int rs1, int label) { - const char* rs1_name = reg_name(rs1); - if (is_nontrivial(rs1)) { - rs1_name = "t0"; - load(REG_T0, rs1); - } + const char* rs1_name = trivialize(rs1, REG_T0); printf(" %s %s, L%d\n", op, rs1_name, label); } @@ -780,7 +770,7 @@ void _asm_i(const char* op, int rd, const char* prefix1, const char* prefix2, in if (is_nontrivial(rd)) rd_name = "t0"; printf(" %s %s, %s%s%d\n", op, rd_name, prefix1, prefix2, imm); if (is_nontrivial(rd)) { - store_t0(rd); + store("t0", rd); } } @@ -803,16 +793,8 @@ int asm_rr(int type, const char* op, int rs1, int rs2) { return rd; } -void asm_mv(int rd, int rs1) { - _asm_r("mv", rd, rs1); -} - void store_into_local(int rs1, int slot) { - const char* rs1_name = reg_name(rs1); - if (is_nontrivial(rs1)) { - rs1_name = "t0"; - load(REG_T0, rs1); - } + const char* rs1_name = trivialize(rs1, REG_T0); load_local_address(REG_T2, slot); printf(" %s %s, 0(t2)\n", store_op_of_type(local_type[slot]), rs1_name); } @@ -827,14 +809,14 @@ int materialize_address(int rd, int type, int marker) { } int lookup_from_slot(int slot) { - int reg = next_reg(TYPE_VOID_PTR); - if (is_nontrivial(reg)) { + int rd = next_reg(TYPE_VOID_PTR); + if (is_nontrivial(rd)) { load_local_address(REG_T0, slot); - asm_mv(reg, REG_T0); + asm_mv(rd, REG_T0); } else { - load_local_address(reg, slot); + load_local_address(rd, slot); } - return materialize_address(reg, local_type[slot], local_marker[slot]); + return materialize_address(rd, local_type[slot], local_marker[slot]); } int load_imm(int imm) { @@ -856,26 +838,15 @@ int lookup(int id) { if (global_marker[id] == MARKER_FUNCTION) { eprintf("function name must not appear outside function call: %s\n", name); exit(1); - } - int reg = next_reg(TYPE_VOID_PTR); - _asm_i("la", reg, name, " # id: ", id); - return materialize_address(reg, global_type[id], global_marker[id]); + } + int rd = next_reg(TYPE_VOID_PTR); + _asm_i("la", rd, name, " # id: ", id); + return materialize_address(rd, global_type[id], global_marker[id]); } eprintf("unresolved identifier: %s\n", name); exit(1); } - -int next_label_id = 0; -int next_label() { - return next_label_id++; -} - -int asm_label(int label) { - printf("L%d:\n", label); - return label; -} - int asm_r_arith(const char* op, int rs1) { if (reg_type[rs1] & TYPE_PTR_MASK) { eprintf("pointer cannot be arithmetically operated by %s\n", op); @@ -909,6 +880,16 @@ void asm_j(int label) { printf(" j L%d\n", label); } +int next_label_id = 0; +int next_label() { + return next_label_id++; +} + +int asm_label(int label) { + printf("L%d:\n", label); + return label; +} + int break_label_stack[4096]; int cont_label_stack[4096]; int break_label_stack_size; @@ -932,11 +913,12 @@ void asm_pop_label() { --cont_label_stack_size; } +int log_step_of(int type) { + return 2 * (type == TYPE_INT_PTR); +} + int step_of(int type) { - if (type == TYPE_INT_PTR) { - return 4; - } - return 1; + return 1 << log_step_of(type); } int asm_add(int lhs, int rhs) { @@ -958,8 +940,7 @@ int asm_add(int lhs, int rhs) { exit(1); } int offset = next_reg(TYPE_INT); - int shift = 2 * (ptr_type == TYPE_INT_PTR); - _asm_ri("slli", offset, idx, shift); + _asm_ri("slli", offset, idx, log_step_of(ptr_type)); return asm_rr(ptr_type, "add", ptr, offset); } if (type1 && type2) { @@ -984,8 +965,7 @@ int asm_sub(int lhs, int rhs) { exit(1); } int difference = asm_rr(TYPE_INT, "sub", lhs, rhs); - int shift = 2 * (lhs_type == TYPE_INT_PTR); - _asm_ri("slli", difference, difference, shift); + _asm_ri("slli", difference, difference, log_step_of(lhs_type)); return difference; } if (type1) { @@ -1016,6 +996,7 @@ int addressof(int reg) { } // parser + int parse_expr(); int parse_function_call(int id) { @@ -1412,7 +1393,7 @@ void parse_local_variable(int type) { expect_token(TOKEN_BRACKET_RIGHT); declare_local_array(id, type, size); return; - } + } int slot = declare_local(id, type); if (token_type == TOKEN_SEMICOLON) { unget_token(); @@ -1582,7 +1563,7 @@ void parse_function(const char* name) { } int arg_type = parse_type(); if (arg_type < 0 || arg_type == TYPE_VOID) { - eprintf("unexpected a non-void argument type: %d\n", arg_type); + eprintf("expecting a non-void argument type: %d\n", arg_type); exit(1); } expect_token(TOKEN_ID); @@ -1723,10 +1704,10 @@ void parse_global_declaration() { is_const[id] = 1; expect_token(TOKEN_SEMICOLON); } else if (token_type == TOKEN_PAREN_LEFT) { - declare_global(id, MARKER_FUNCTION, type); + declare_global(id, MARKER_FUNCTION, type); parse_function(name); } else { - declare_global(id, MARKER_SCALAR, type); + declare_global(id, MARKER_SCALAR, type); parse_global_variable(id, name, type); } } @@ -1743,9 +1724,9 @@ void dump_string_table() { printf(".data\n"); for (int i = 0; i < string_lut_size; ++i) { printf(".LC%d: .string \"", i); - int offset = string_lut[i]; - int ch; - while ((ch = string_table[offset++]) != 0) { + char* p = string_table + string_lut[i]; + int ch; + while ((ch = *p++) != 0) { if (ch == '\n') { printf("\\n"); } else if (ch == '\t') { From 50969c6bb9d7939b158cf2c97a34bd283b9bc995 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Sat, 30 Nov 2024 09:40:52 +0800 Subject: [PATCH 08/11] implicit return 0 for main --- README.md | 4 +++- boot.c | 54 +++++++++++++++++++++++++++++++-------------------- demo/hello.c | 1 - demo/lut.c | 1 - demo/queen.c | 1 - demo/sort.c | 1 - demo/strcmp.c | 1 - 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 7b37e2a..2b3abe6 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,9 @@ $ sh boot.sh - 不支持局部变量之间的遮挡,重名的局部变量为同一变量。 - 支持函数声明,可以通过函数声明来调用 C 语言库。不支持变量声明。 - 函数只支持最多八个参数。函数声明中支持可变参数,仅用于兼容 C 语言库。 -- 类型检查可能有遗漏,若 C 编译器报错,而本语言编译通过,就可以认为是 UB。 +- 类型检查有遗漏,若 C 编译器报错,而本语言编译通过,就可以认为是 UB。 + - 例如函数调用的参数和 `return` 语句不会检查类型。 + ## 限制 diff --git a/boot.c b/boot.c index 8aa7128..913acd2 100644 --- a/boot.c +++ b/boot.c @@ -430,20 +430,18 @@ int parse_type() { next_token(); ignore_const(); if (token_type == TOKEN_STAR) { + next_token(); ignore_const(); - return type | TYPE_PTR_MASK; + type = type | TYPE_PTR_MASK; } unget_token(); return type; - } else { - return -1; } + return -1; } // asm -int epilog_label; - int local_table[4096]; // id -> local id int next_local_id = 1; int max_local_id = 1; @@ -760,7 +758,7 @@ void _asm_ri(const char* op, int rd, int rs1, int imm) { } } -void _asm_branch(const char* op, int rs1, int label) { +void asm_branch(const char* op, int rs1, int label) { const char* rs1_name = trivialize(rs1, REG_T0); printf(" %s %s, L%d\n", op, rs1_name, label); } @@ -869,11 +867,11 @@ int asm_rr_cmp(const char* op, int rs1, int rs2) { } void asm_beqz(int rs1, int label) { - _asm_branch("beqz", rs1, label); + asm_branch("beqz", rs1, label); } void asm_bnez(int rs1, int label) { - _asm_branch("bnez", rs1, label); + asm_branch("bnez", rs1, label); } void asm_j(int label) { @@ -895,12 +893,20 @@ int cont_label_stack[4096]; int break_label_stack_size; int cont_label_stack_size; -int asm_get_break_label() { - return break_label_stack[break_label_stack_size - 1]; +void asm_break() { + if (break_label_stack_size == 0) { + eprintf("break without loop\n"); + exit(1); + } + asm_j(break_label_stack[break_label_stack_size - 1]); } -int asm_get_cont_label() { - return cont_label_stack[cont_label_stack_size - 1]; +void asm_continue() { + if (cont_label_stack_size == 0) { + eprintf("continue without loop\n"); + exit(1); + } + asm_j(cont_label_stack[cont_label_stack_size - 1]); } void asm_push_label(int break_label, int cont_label) { @@ -913,6 +919,12 @@ void asm_pop_label() { --cont_label_stack_size; } +int epilog_label; + +void asm_return() { + asm_j(epilog_label); +} + int log_step_of(int type) { return 2 * (type == TYPE_INT_PTR); } @@ -1048,7 +1060,7 @@ int parse_function_call(int id) { asm_mv(rd, REG_A0); return rd; } - return -1; + return REG_ZERO; } int parse_primary_expr() { @@ -1515,19 +1527,17 @@ void parse_stmt() { } else if (token_type == TOKEN_RETURN) { next_token(); if (token_type == TOKEN_SEMICOLON) { - asm_j(epilog_label); + asm_return(); return; } unget_token(); int rs1 = parse_expr(); asm_mv(REG_A0, rs1); - asm_j(epilog_label); + asm_return(); } else if (token_type == TOKEN_BREAK) { - int label = asm_get_break_label(); - asm_j(label); + asm_break(); } else if (token_type == TOKEN_CONTINUE) { - int label = asm_get_cont_label(); - asm_j(label); + asm_continue(); } else if (token_type == TOKEN_SEMICOLON) { unget_token(); } else if ((decl_type = parse_type()) >= 0) { @@ -1614,7 +1624,10 @@ void parse_function(const char* name) { unget_token(); parse_stmt(); } - asm_j(epilog_label); + if (streq(name, "main")) { + asm_mv(REG_A0, REG_ZERO); + } + asm_return(); int reg_used = max_reg_id - REG_S2; if (reg_used > 14) reg_used = 14; int frame_size = (max_local_id - 1 + reg_used + 2) * 8; @@ -1752,5 +1765,4 @@ void dump_string_table() { int main() { parse_top_level(); dump_string_table(); - return 0; } diff --git a/demo/hello.c b/demo/hello.c index 4000513..c94a263 100644 --- a/demo/hello.c +++ b/demo/hello.c @@ -2,5 +2,4 @@ int printf(const char* format, ...); int main() { printf("hello world %d\n", 42); - return 0; } \ No newline at end of file diff --git a/demo/lut.c b/demo/lut.c index f5b23ba..b4ed157 100644 --- a/demo/lut.c +++ b/demo/lut.c @@ -43,5 +43,4 @@ int main() { char ch; while ((ch = getchar()) == '"') parse_string(); dump_string_table(); - return 0; } \ No newline at end of file diff --git a/demo/queen.c b/demo/queen.c index ac4a310..2789ed3 100644 --- a/demo/queen.c +++ b/demo/queen.c @@ -43,5 +43,4 @@ void queen(int x) { int main() { queen(1); printf("solutions: %d\n", a[0]); - return 0; } \ No newline at end of file diff --git a/demo/sort.c b/demo/sort.c index b1db39d..8248c54 100644 --- a/demo/sort.c +++ b/demo/sort.c @@ -27,5 +27,4 @@ int main() { printf("%d ", a[i]); } printf("\n"); - return 0; } \ No newline at end of file diff --git a/demo/strcmp.c b/demo/strcmp.c index 6d4793e..50fe8aa 100644 --- a/demo/strcmp.c +++ b/demo/strcmp.c @@ -13,5 +13,4 @@ int main() { const char* s2 = "world"; printf("%d\n", strcmp(s1, s2)); printf("%d\n", strcmp(s1 + 5, s2)); - return 0; } \ No newline at end of file From 8114d04fb9291469ce2c5b6781776ec5c41b6949 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Sat, 30 Nov 2024 11:19:13 +0800 Subject: [PATCH 09/11] comma expr --- README.md | 1 + boot.c | 27 ++++++++++++++++++++------- demo/add.c | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b3abe6..86a2024 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ $ sh boot.sh | `&&` | 逻辑与 | 从左到右 | | || | 逻辑或 | 从左到右 | | `=` | 赋值 | 从右到左 | +| `,` | 逗号 | 从左到右 | - 同级表达式的求值顺序与结合性一致。 - 加减号支持整数之间,指针与整数,指针之间的运算。 diff --git a/boot.c b/boot.c index 913acd2..a56a1ac 100644 --- a/boot.c +++ b/boot.c @@ -22,7 +22,7 @@ const int TOKEN_EOF = 0; const int TOKEN_SEMICOLON = 1; const int TOKEN_ADD = 2; const int TOKEN_MINUS = 3; -const int TOKEN_STAR = 4; +const int TOKEN_MUL = 4; const int TOKEN_DIV = 5; const int TOKEN_REM = 6; const int TOKEN_ASSIGN = 7; @@ -277,7 +277,7 @@ void next_token() { token_type = TOKEN_MINUS; } } else if (ch == '*') { - token_type = TOKEN_STAR; + token_type = TOKEN_MUL; } else if (ch == '/') { int ch2 = getchar(); if (ch2 == '/') { @@ -429,7 +429,7 @@ int parse_type() { int type = token_type & ~TYPE_TOKEN_MASK; next_token(); ignore_const(); - if (token_type == TOKEN_STAR) { + if (token_type == TOKEN_MUL) { next_token(); ignore_const(); type = type | TYPE_PTR_MASK; @@ -1011,6 +1011,8 @@ int addressof(int reg) { int parse_expr(); +int parse_assign_expr(); + int parse_function_call(int id) { const char* name = id_table + id_lut[id]; if (global_marker[id] != MARKER_FUNCTION) { @@ -1029,7 +1031,7 @@ int parse_function_call(int id) { eprintf("too many arguments\n"); exit(1); } - args[arg++] = parse_expr(); + args[arg++] = parse_assign_expr(); next_token(); if (token_type == TOKEN_COMMA) { // continue; @@ -1128,7 +1130,7 @@ int parse_prefix_expr() { exit(1); } return addressof(reg); - } else if (token_type == TOKEN_STAR) { + } else if (token_type == TOKEN_MUL) { int reg = parse_postfix_expr(); int type = reg_type[reg]; if (!(type & TYPE_PTR_MASK)) { @@ -1167,7 +1169,7 @@ int parse_mul_expr() { int lhs = parse_prefix_expr(); while (1) { next_token(); - if (token_type == TOKEN_STAR) { + if (token_type == TOKEN_MUL) { int rhs = parse_prefix_expr(); lhs = asm_rr_arith("mul", lhs, rhs); } else if (token_type == TOKEN_DIV) { @@ -1384,7 +1386,18 @@ int parse_assign_expr() { } int parse_expr() { - return parse_assign_expr(); + int lhs = parse_assign_expr(); + while (1) { + next_token(); + if (token_type == TOKEN_COMMA) { + int rhs = parse_assign_expr(); + lhs = rhs; + } else { + unget_token(); + break; + } + } + return lhs; } void parse_local_variable(int type) { diff --git a/demo/add.c b/demo/add.c index 6c54b5f..f42e07e 100644 --- a/demo/add.c +++ b/demo/add.c @@ -12,6 +12,6 @@ int f1() { int main() { int a[15]; p = a; - for (int i = 0; i < 15; ++i) a[i] = i; + for (int i = 0; i < 15; a[i] = i, ++i); return f1(); } \ No newline at end of file From e18bf2c2d34be61943c7724d21e2e2d79ea3971d Mon Sep 17 00:00:00 2001 From: Yaossg Date: Sat, 30 Nov 2024 17:47:28 +0800 Subject: [PATCH 10/11] complete operators --- boot.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 160 insertions(+), 27 deletions(-) diff --git a/boot.c b/boot.c index a56a1ac..f5a33e7 100644 --- a/boot.c +++ b/boot.c @@ -21,7 +21,7 @@ int token_data; const int TOKEN_EOF = 0; const int TOKEN_SEMICOLON = 1; const int TOKEN_ADD = 2; -const int TOKEN_MINUS = 3; +const int TOKEN_SUB = 3; const int TOKEN_MUL = 4; const int TOKEN_DIV = 5; const int TOKEN_REM = 6; @@ -40,6 +40,18 @@ const int TOKEN_LOR = 18; const int TOKEN_ELLIPSIS = 19; const int TOKEN_INC = 20; const int TOKEN_DEC = 21; +const int TOKEN_ADD_ASSIGN = 22; +const int TOKEN_SUB_ASSIGN = 23; +const int TOKEN_MUL_ASSIGN = 24; +const int TOKEN_DIV_ASSIGN = 25; +const int TOKEN_REM_ASSIGN = 26; +const int TOKEN_AND_ASSIGN = 27; +const int TOKEN_OR_ASSIGN = 28; +const int TOKEN_XOR_ASSIGN = 29; +const int TOKEN_LSHIFT_ASSIGN = 30; +const int TOKEN_RSHIFT_ASSIGN = 31; +const int TOKEN_QUESTION = 32; +const int TOKEN_COLON = 33; const int TOKEN_EQ = 40; const int TOKEN_NE = 41; @@ -85,8 +97,8 @@ const int TYPE_TOKEN_MASK = 128; int parse_int(int ch) { int num = ch - '0'; while (is_digit(ch = getchar())) { - num = num * 10; - num = num + ch - '0'; + num *= 10; + num += ch - '0'; } ungetchar(ch); return num; @@ -264,6 +276,8 @@ void next_token() { int ch2 = getchar(); if (ch2 == '+') { token_type = TOKEN_INC; + } else if (ch2 == '=') { + token_type = TOKEN_ADD_ASSIGN; } else { ungetchar(ch2); token_type = TOKEN_ADD; @@ -272,15 +286,25 @@ void next_token() { int ch2 = getchar(); if (ch2 == '-') { token_type = TOKEN_DEC; + } else if (ch2 == '=') { + token_type = TOKEN_SUB_ASSIGN; } else { ungetchar(ch2); - token_type = TOKEN_MINUS; + token_type = TOKEN_SUB; } } else if (ch == '*') { - token_type = TOKEN_MUL; + int ch2 = getchar(); + if (ch2 == '=') { + token_type = TOKEN_MUL_ASSIGN; + } else { + ungetchar(ch2); + token_type = TOKEN_MUL; + } } else if (ch == '/') { int ch2 = getchar(); - if (ch2 == '/') { + if (ch2 == '=') { + token_type = TOKEN_DIV_ASSIGN; + } if (ch2 == '/') { do ch = getchar(); while (ch != -1 && ch != '\n'); next_token(); return; @@ -305,9 +329,19 @@ void next_token() { token_type = TOKEN_DIV; } } else if (ch == '%') { - token_type = TOKEN_REM; + int ch2 = getchar(); + if (ch2 == '=') { + token_type = TOKEN_REM_ASSIGN; + } else { + ungetchar(ch2); + token_type = TOKEN_REM; + } } else if (ch == ';') { token_type = TOKEN_SEMICOLON; + } else if (ch == '?') { + token_type = TOKEN_QUESTION; + } else if (ch == ':') { + token_type = TOKEN_COLON; } else if (ch == ',') { token_type = TOKEN_COMMA; } else if (ch == '<') { @@ -315,7 +349,13 @@ void next_token() { if (ch2 == '=') { token_type = TOKEN_LE; } else if (ch2 == '<') { - token_type = TOKEN_LSHIFT; + int ch3 = getchar(); + if (ch3 == '=') { + token_type = TOKEN_LSHIFT_ASSIGN; + } else { + ungetchar(ch3); + token_type = TOKEN_LSHIFT; + } } else { ungetchar(ch2); token_type = TOKEN_LT; @@ -325,7 +365,13 @@ void next_token() { if (ch2 == '=') { token_type = TOKEN_GE; } else if (ch2 == '>') { - token_type = TOKEN_RSHIFT; + int ch3 = getchar(); + if (ch3 == '=') { + token_type = TOKEN_RSHIFT_ASSIGN; + } else { + ungetchar(ch3); + token_type = TOKEN_RSHIFT; + } } else { ungetchar(ch2); token_type = TOKEN_GT; @@ -348,7 +394,9 @@ void next_token() { } } else if (ch == '&') { int ch2 = getchar(); - if (ch2 == '&') { + if (ch2 == '=') { + token_type = TOKEN_AND_ASSIGN; + } else if (ch2 == '&') { token_type = TOKEN_LAND; } else { ungetchar(ch2); @@ -356,14 +404,22 @@ void next_token() { } } else if (ch == '|') { int ch2 = getchar(); - if (ch2 == '|') { + if (ch2 == '=') { + token_type = TOKEN_OR_ASSIGN; + } else if (ch2 == '|') { token_type = TOKEN_LOR; } else { ungetchar(ch2); token_type = TOKEN_OR; } } else if (ch == '^') { - token_type = TOKEN_XOR; + int ch2 = getchar(); + if (ch2 == '=') { + token_type = TOKEN_XOR_ASSIGN; + } else { + ungetchar(ch2); + token_type = TOKEN_XOR; + } } else if (ch == '~') { token_type = TOKEN_COMPL; } else if (ch == '\'') { @@ -432,7 +488,7 @@ int parse_type() { if (token_type == TOKEN_MUL) { next_token(); ignore_const(); - type = type | TYPE_PTR_MASK; + type |= TYPE_PTR_MASK; } unget_token(); return type; @@ -799,7 +855,7 @@ void store_into_local(int rs1, int slot) { int materialize_address(int rd, int type, int marker) { if (marker == MARKER_ARRAY) { - type = type | TYPE_PTR_MASK; + type |= TYPE_PTR_MASK; } reg_type[rd] = type; indirection[rd] = marker == MARKER_SCALAR; @@ -926,7 +982,7 @@ void asm_return() { } int log_step_of(int type) { - return 2 * (type == TYPE_INT_PTR); + return type == TYPE_INT_PTR ? 2 : 0; } int step_of(int type) { @@ -976,9 +1032,9 @@ int asm_sub(int lhs, int rhs) { eprintf("void pointer cannot be arithmetically operated\n"); exit(1); } - int difference = asm_rr(TYPE_INT, "sub", lhs, rhs); - _asm_ri("slli", difference, difference, log_step_of(lhs_type)); - return difference; + int diff = asm_rr(TYPE_INT, "sub", lhs, rhs); + _asm_ri("slli", diff, diff, log_step_of(lhs_type)); + return diff; } if (type1) { int neg = asm_r_arith("neg", rhs); @@ -1142,7 +1198,7 @@ int parse_prefix_expr() { exit(1); } return dereference(reg); - } else if (token_type == TOKEN_MINUS) { + } else if (token_type == TOKEN_SUB) { int reg = parse_postfix_expr(); return asm_r_arith("neg", reg); } else if (token_type == TOKEN_COMPL) { @@ -1193,7 +1249,7 @@ int parse_add_expr() { if (token_type == TOKEN_ADD) { int rhs = parse_mul_expr(); lhs = asm_add(lhs, rhs); - } else if (token_type == TOKEN_MINUS) { + } else if (token_type == TOKEN_SUB) { int rhs = parse_mul_expr(); lhs = asm_sub(lhs, rhs); } else { @@ -1254,12 +1310,12 @@ int parse_eq_expr() { next_token(); if (token_type == TOKEN_EQ) { int rhs = parse_cmp_expr(); - int xor0 = asm_rr_cmp("xor", lhs, rhs); - lhs = asm_r(TYPE_INT, "seqz", xor0); + int xor = asm_rr_cmp("xor", lhs, rhs); + lhs = asm_r(TYPE_INT, "seqz", xor); } else if (token_type == TOKEN_NE) { int rhs = parse_cmp_expr(); - int xor0 = asm_rr_cmp("xor", lhs, rhs); - lhs = asm_r(TYPE_INT, "snez", xor0); + int xor = asm_rr_cmp("xor", lhs, rhs); + lhs = asm_r(TYPE_INT, "snez", xor); } else { unget_token(); break; @@ -1372,13 +1428,90 @@ int parse_logical_or_expr() { return lhs; } +int parse_conditional_expr() { + int cond = parse_logical_or_expr(); + next_token(); + if (token_type == TOKEN_QUESTION) { + int label1 = next_label(); + int label2 = next_label(); + asm_beqz(cond, label1); + int lhs = parse_expr(); + int result = next_reg(reg_type[lhs]); + asm_mv(result, lhs); + asm_j(label2); + expect_token(TOKEN_COLON); + asm_label(label1); + int rhs = parse_conditional_expr(); + if (reg_type[lhs] != reg_type[rhs]) { + eprintf("type mismatch in conditional expression\n"); + exit(1); + } + asm_mv(result, rhs); + asm_label(label2); + return result; + } else { + unget_token(); + return cond; + } +} + int parse_assign_expr() { - int lhs = parse_logical_or_expr(); + int lhs = parse_conditional_expr(); next_token(); if (token_type == TOKEN_ASSIGN) { int rhs = parse_assign_expr(); asm_mv(lhs, rhs); return lhs; + } else if (token_type == TOKEN_ADD_ASSIGN) { + int rhs = parse_assign_expr(); + int sum = asm_add(lhs, rhs); + asm_mv(lhs, sum); + return lhs; + } else if (token_type == TOKEN_SUB_ASSIGN) { + int rhs = parse_assign_expr(); + int diff = asm_sub(lhs, rhs); + asm_mv(lhs, diff); + return lhs; + } else if (token_type == TOKEN_MUL_ASSIGN) { + int rhs = parse_assign_expr(); + int prod = asm_rr_arith("mul", lhs, rhs); + asm_mv(lhs, prod); + return lhs; + } else if (token_type == TOKEN_DIV_ASSIGN) { + int rhs = parse_assign_expr(); + int quot = asm_rr_arith("div", lhs, rhs); + asm_mv(lhs, quot); + return lhs; + } else if (token_type == TOKEN_REM_ASSIGN) { + int rhs = parse_assign_expr(); + int rem = asm_rr_arith("rem", lhs, rhs); + asm_mv(lhs, rem); + return lhs; + } else if (token_type == TOKEN_LSHIFT_ASSIGN) { + int rhs = parse_assign_expr(); + int lshift = asm_rr_arith("sll", lhs, rhs); + asm_mv(lhs, lshift); + return lhs; + } else if (token_type == TOKEN_RSHIFT_ASSIGN) { + int rhs = parse_assign_expr(); + int rshift = asm_rr_arith("sra", lhs, rhs); + asm_mv(lhs, rshift); + return lhs; + } else if (token_type == TOKEN_AND_ASSIGN) { + int rhs = parse_assign_expr(); + int and = asm_rr_arith("and", lhs, rhs); + asm_mv(lhs, and); + return lhs; + } else if (token_type == TOKEN_XOR_ASSIGN) { + int rhs = parse_assign_expr(); + int xor = asm_rr_arith("xor", lhs, rhs); + asm_mv(lhs, xor); + return lhs; + } else if (token_type == TOKEN_OR_ASSIGN) { + int rhs = parse_assign_expr(); + int or = asm_rr_arith("or", lhs, rhs); + asm_mv(lhs, or); + return lhs; } else { unget_token(); return lhs; @@ -1599,7 +1732,7 @@ void parse_function(const char* name) { eprintf("array of pointers is not supported\n"); exit(1); } - arg_type = arg_type | TYPE_PTR_MASK; + arg_type |= TYPE_PTR_MASK; } if (arg >= 8) { eprintf("too many arguments\n"); @@ -1646,7 +1779,7 @@ void parse_function(const char* name) { int frame_size = (max_local_id - 1 + reg_used + 2) * 8; if (reg_used > 10) reg_used = 10; if (frame_size % 16 != 0) { - frame_size = frame_size + 8; + frame_size += 8; } // prolog asm_label(prolog_label); From 4dc291b27e857d1f02f55aa064c6c920151332c4 Mon Sep 17 00:00:00 2001 From: Yaossg Date: Sat, 30 Nov 2024 17:47:52 +0800 Subject: [PATCH 11/11] new readme --- README.md | 78 ++++++++++++++++++++++++++-------------------- bootstrapping.png | Bin 0 -> 31532 bytes 2 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 bootstrapping.png diff --git a/README.md b/README.md index 86a2024..91db7ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RVBTCC -- 约 1800 行的轻量级自举编译器。 +- 约 1900 行的轻量级自举编译器。 - 编译器和自举编译器行为一致。 - 语法类似 C,输出 RISC-V 汇编。 - 依赖几个 libc 函数用于输入输出。 @@ -12,7 +12,6 @@ ### 真机运行 - 编译运行程序,src 为本语言源代码。可以编译 demo 文件夹下的实例。 ```sh @@ -25,19 +24,6 @@ $ sh run-native.sh $ sh boot-native.sh ``` -输出六个文件: - -| 源代码 | 编译器 | 汇编 | 可执行 | 命名 | -| ----------------- | --------- | ------- | --------- | ---------------------- | -| boot.c boot-lib.c | gcc | | gcc.out | 自制编译器 | -| boot.c boot-lib.h | gcc.out | boot1.s | boot1.out | 自举自制编译器 | -| boot.c boot-lib.h | boot1.out | boot2.s | boot2.out | 自举自举自制编译器 | -| boot.c boot-lib.h | boot2.out | boot3.s | | 验证自举自举自制编译器 | - -后三次编译时,boot-lib.h 的内容被手动导入 boot.c 开头进行编译,boot-lib.c 提供的库通过链接引入。 - -自举的目标为 boot1.s == boot2.s == boot3.s - ### 模拟运行 安装以下依赖 @@ -58,27 +44,31 @@ $ sh run.sh $ sh boot.sh ``` +### 自举过程 + + +自举会输出六个文件,三个汇编文件和三个可执行文件: + +| 源代码 | 编译器 | 汇编 | 可执行 | 代号 | 命名 | +| ----------------- | --------- | ------- | --------- | ---- | ---------------------- | +| boot.c boot-lib.c | gcc | | gcc.out | G | 自制编译器 | +| boot.c boot-lib.h | gcc.out | boot1.s | boot1.out | B1 | 自举自制编译器 | +| boot.c boot-lib.h | boot1.out | boot2.s | boot2.out | B2 | 自举自举自制编译器 | +| boot.c boot-lib.h | boot2.out | boot3.s | | B3 | 验证自举自举自制编译器 | + +后三次编译时,boot-lib.h 的内容被手动导入 boot.c 开头进行编译,boot-lib.c 提供的库通过链接引入。 + +整个自举及其验证的过程如下图所示: + +![](bootstrapping.png) + +自举的目标为 G、B1、B2 的可执行文件行为一致,也就是说 B1、B2、B3 的汇编代码一致。 + ## 语言文档 -### 关键字 +### 注释 -本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const`。 - -### `const` 关键字 - -`const` 关键字可以在类型中使用,在大部分情况下会被直接忽略。支持它是为了更好兼容 C 程序。 - -但是当在出现 - -- 全局,标量(即不是数组) -- 类型为 `const int` 或 `const int const` -- 带有初始化 - -的声明时,将会被解析为整数常量。 - -整数常量在使用的时候会被直接替换为对应的右值,失去作为全局变量左值的性质。 - -使用 `int const` 或 `int` 可以避免这样的特殊处理。 +支持多行 `/* ... */` 和单行 `//` 两种注释 ### 支持六个基本类型 @@ -103,6 +93,25 @@ $ sh boot.sh - `break` `continue` - `return` +### 关键字 + +本语言包含的关键字即为支持的标量类型的关键字和流程控制的关键字,还有 `const`。 + +#### `const` 关键字 + +`const` 关键字可以在类型中使用,在大部分情况下会被直接忽略。支持它是为了更好兼容 C 程序。 + +但是当在出现 + +- 全局,标量(即不是数组) +- 类型为 `const int` 或 `const int const` +- 带有初始化 + +的声明时,将会被解析为整数常量。 + +整数常量在使用的时候会被直接替换为对应的右值,失去作为全局变量左值的性质。 + +使用 `int const` 或 `int` 可以避免这样的特殊处理。 ### 支持以下运算符 @@ -121,7 +130,8 @@ $ sh boot.sh | | | 按位或 | 从左到右 | | `&&` | 逻辑与 | 从左到右 | | || | 逻辑或 | 从左到右 | -| `=` | 赋值 | 从右到左 | +| `?:` | 条件 | 从右到左 | +| `=` `+=` `-=` `*=` `/=` `%=` `<<=` `>>=` `&=` `^=` |= | 赋值 | 从右到左 | | `,` | 逗号 | 从左到右 | - 同级表达式的求值顺序与结合性一致。 diff --git a/bootstrapping.png b/bootstrapping.png new file mode 100644 index 0000000000000000000000000000000000000000..221b03affe2ec7555f155b06d52e5e207d2c68f2 GIT binary patch literal 31532 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 literal 0 HcmV?d00001