diff --git a/resources/examples/embedded/riscv-qemu/Makefile b/resources/examples/embedded/riscv-qemu/Makefile index 842b7e6cf..c70ab89a7 100644 --- a/resources/examples/embedded/riscv-qemu/Makefile +++ b/resources/examples/embedded/riscv-qemu/Makefile @@ -3,7 +3,7 @@ SRCS_C3 := $(wildcard *.c3) default: hello.elf hello.a: $(SRCS_C3) - $(C3C_PATH)c3c --use-stdlib=no --no-entry --target elf-riscv32 static-lib $(SRCS_C3) + $(C3C_PATH)c3c -g --use-stdlib=no --no-entry --target elf-riscv32 static-lib $(SRCS_C3) start.o: start.s riscv64-unknown-elf-as -g -march=rv32i -mabi=ilp32 -misa-spec=20191213 -o start.o start.s diff --git a/src/compiler/asm/riscv.h b/src/compiler/asm/riscv.h new file mode 100644 index 000000000..c63d69713 --- /dev/null +++ b/src/compiler/asm/riscv.h @@ -0,0 +1,164 @@ + +typedef enum +{ + RISCV_X0, + RISCV_X1, + RISCV_X2, + RISCV_X3, + RISCV_X4, + RISCV_X5, + RISCV_X6, + RISCV_X7, + RISCV_X8, + RISCV_X9, + RISCV_X10, + RISCV_X11, + RISCV_X12, + RISCV_X13, + RISCV_X14, + RISCV_X15, + RISCV_X16, + RISCV_X17, + RISCV_X18, + RISCV_X19, + RISCV_X20, + RISCV_X21, + RISCV_X22, + RISCV_X23, + RISCV_X24, + RISCV_X25, + RISCV_X26, + RISCV_X27, + RISCV_X28, + RISCV_X29, + RISCV_X30, + RISCV_X31, + RISCV_F0, + RISCV_F1, + RISCV_F2, + RISCV_F3, + RISCV_F4, + RISCV_F5, + RISCV_F6, + RISCV_F7, + RISCV_F8, + RISCV_F9, + RISCV_F10, + RISCV_F11, + RISCV_F12, + RISCV_F13, + RISCV_F14, + RISCV_F15, + RISCV_F16, + RISCV_F17, + RISCV_F18, + RISCV_F19, + RISCV_F20, + RISCV_F21, + RISCV_F22, + RISCV_F23, + RISCV_F24, + RISCV_F25, + RISCV_F26, + RISCV_F27, + RISCV_F28, + RISCV_F29, + RISCV_F30, + RISCV_F31, + RISCV_MIE, + RISCV_MSTATUS, + RISCV_MTVEC +} RISCVClobbers; + +static const char *RISCVClobberNames[] = { + [RISCV_X0] = "x0", + [RISCV_X1] = "x1", + [RISCV_X2] = "x2", + [RISCV_X3] = "x3", + [RISCV_X4] = "x4", + [RISCV_X5] = "x5", + [RISCV_X6] = "x6", + [RISCV_X7] = "x7", + [RISCV_X8] = "x8", + [RISCV_X9] = "x9", + [RISCV_X10] = "x10", + [RISCV_X11] = "x11", + [RISCV_X12] = "x12", + [RISCV_X13] = "x13", + [RISCV_X14] = "x14", + [RISCV_X15] = "x15", + [RISCV_X16] = "x16", + [RISCV_X17] = "x17", + [RISCV_X18] = "x18", + [RISCV_X19] = "x19", + [RISCV_X20] = "x20", + [RISCV_X21] = "x21", + [RISCV_X22] = "x22", + [RISCV_X23] = "x23", + [RISCV_X24] = "x24", + [RISCV_X25] = "x25", + [RISCV_X26] = "x26", + [RISCV_X27] = "x27", + [RISCV_X28] = "x28", + [RISCV_X29] = "x29", + [RISCV_X30] = "x30", + [RISCV_X31] = "x31", + [RISCV_F0] = "f0", + [RISCV_F1] = "f1", + [RISCV_F2] = "f2", + [RISCV_F3] = "f3", + [RISCV_F4] = "f4", + [RISCV_F5] = "f5", + [RISCV_F6] = "f6", + [RISCV_F7] = "f7", + [RISCV_F8] = "f8", + [RISCV_F9] = "f9", + [RISCV_F10] = "f10", + [RISCV_F11] = "f11", + [RISCV_F12] = "f12", + [RISCV_F13] = "f13", + [RISCV_F14] = "f14", + [RISCV_F15] = "f15", + [RISCV_F16] = "f16", + [RISCV_F17] = "f17", + [RISCV_F18] = "f18", + [RISCV_F19] = "f19", + [RISCV_F20] = "f20", + [RISCV_F21] = "f21", + [RISCV_F22] = "f22", + [RISCV_F23] = "f23", + [RISCV_F24] = "f24", + [RISCV_F25] = "f25", + [RISCV_F26] = "f26", + [RISCV_F27] = "f27", + [RISCV_F28] = "f28", + [RISCV_F29] = "f29", + [RISCV_F30] = "f30", + [RISCV_F31] = "f31", + [RISCV_MIE] = "mie", + [RISCV_MSTATUS] = "mstatus", + [RISCV_MTVEC] = "mtvec", +}; + +static const char *riscv_gp_integer_regs[] = { "$x0", "$x1", "$x2", "$x3", "$x4", + "$x5", "$x6", "$x7", "$x8", "$x9", + "$x10", "$x11", "$x12", "$x13", "$x14", + "$x15", "$x16", "$x17", "$x18", "$x19", + "$x20", "$x21", "$x22", "$x23", "$x24", + "$x25", "$x26", "$x27", "$x28", "$x29", + "$x30", "$x31" }; +static const char *riscv_arg_integer_regs[] = { "$a0", "$a1", "$a2", "$a3", "$a4", + "$a5", "$a6", "$a7" }; +static const char *riscv_temp_integer_regs[] = { "$t0", "$t1", "$t2", "$t3", "$t4", + "$t5", "$t6" }; +static const char *riscv_save_integer_regs[] = { "$s0", "$s1", "$s2", "$s3", "$s4", + "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11" }; +static const char *riscv_float_regs[] = { "$f0", "$f1", "$f2", "$f3", "$f4", + "$f5", "$f6", "$f7", "$f8", "$f9", + "$f10", "$f11", "$f12", "$f13", "$f14", + "$f15", "$f16", "$f17", "$f18", "$f19", + "$f20", "$f21", "$f22", "$f23", "$f24", + "$f25", "$f26", "$f27", "$f28", "$f29", + "$f30", "$f31" }; +static const char *riscv_machine_integer_regs[] = { "$mie", "$mstatus", "$mtvec" }; + diff --git a/src/compiler/asm_target.c b/src/compiler/asm_target.c index 00c644135..574ff6500 100644 --- a/src/compiler/asm_target.c +++ b/src/compiler/asm_target.c @@ -4,6 +4,7 @@ #include "compiler_internal.h" #include "compiler/asm/x86.h" #include "compiler/asm/aarch64.h" +#include "compiler/asm/riscv.h" #define ASM_PTR_HASH(name__) (uint32_t)(((uintptr_t)name__ * 31) ^ ((uintptr_t)name__ >> 15)) @@ -46,6 +47,11 @@ INLINE AsmArgBits parse_bits(const char **desc) *desc += 2; return ARG_BITS_16; } + if (memcmp("20", *desc, 2) == 0) + { + *desc += 2; + return ARG_BITS_20; + } if (memcmp("32", *desc, 2) == 0) { *desc += 2; @@ -71,6 +77,16 @@ INLINE AsmArgBits parse_bits(const char **desc) *desc += 3; return ARG_BITS_512; } + if (memcmp("5", *desc, 1) == 0) + { + *desc += 1; + return ARG_BITS_5; + } + if (memcmp("12", *desc, 2) == 0) + { + *desc += 2; + return ARG_BITS_12; + } error_exit("Invalid bits: %s.", *desc); } @@ -122,7 +138,7 @@ INLINE AsmArgType decode_arg_type(const char **desc) } if (c == 'u') { - desc++; + (*desc)++; arg_type.imm_arg_ubits |= parse_bits(desc); goto NEXT; } @@ -279,7 +295,165 @@ static void init_asm_arm(PlatformTarget *target) static void init_asm_riscv(PlatformTarget *target) { - error_exit("RISCV asm not complete."); + target->clobber_name_list = RISCVClobberNames; + target->extra_clobbers = NULL; + unsigned int bits = 0; + switch(target->arch) { + case ARCH_TYPE_RISCV64: + // math + reg_instr(target, "add", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "sub", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "addi", "w:r64/mem, r64/mem, immi12"); + reg_instr(target, "neg", "w:r64/mem, r64/mem"); + // bit + reg_instr(target, "and", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "or", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "xor", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "not", "w:r64/mem, r64/mem"); + reg_instr(target, "andi", "w:r64/mem, r64/mem, immi12"); + reg_instr(target, "ori", "w:r64/mem, r64/mem, immi12"); + reg_instr(target, "xori", "w:r64/mem, r64/mem, immi12"); + // shift + reg_instr(target, "sll", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "srl", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "sra", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "slli", "w:r64/mem, r64/mem, immu5"); + reg_instr(target, "srli", "w:r64/mem, r64/mem, immu5"); + reg_instr(target, "srai", "w:r64/mem, r64/mem, immu5"); + // load + reg_instr(target, "li", "w:r64/mem, immi64"); + reg_instr(target, "lui", "w:r64, immu20"); + reg_instr(target, "auipc", "w:r64, immi20"); + reg_instr(target, "mv", "w:r64/mem, r64/mem"); + reg_instr(target, "ld", "w:r64/mem, mem"); + reg_instr(target, "lw", "w:r64/mem, mem"); + reg_instr(target, "lh", "w:r64/mem, mem"); + reg_instr(target, "lhu", "w:r64/mem, mem"); + reg_instr(target, "lb", "w:r64/mem, mem"); + reg_instr(target, "lbu", "w:r64/mem, mem"); + // store + reg_instr(target, "sd", "r64/mem, w:mem"); + reg_instr(target, "sw", "r64/mem, w:mem"); + reg_instr(target, "sh", "r64/mem, w:mem"); + reg_instr(target, "sb", "r64/mem, w:mem"); + // Misc + reg_instr(target, "nop", NULL); + reg_instr(target, "ebreak", NULL); + reg_instr(target, "ecall", NULL); + reg_instr(target, "eret", NULL); + // Jump + reg_instr(target, "j", "immi20"); + reg_instr(target, "jal", "w:r64/mem, immi20"); + reg_instr(target, "jalr", "w:r64/mem, r64/mem, immi20"); + reg_instr(target, "ret", NULL); + // Set + reg_instr(target, "slt", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "slti", "w:r64/mem, r64/mem, immi12"); + reg_instr(target, "sltu", "w:r64/mem, r64/mem, r64/mem"); + reg_instr(target, "sltiu", "w:r64/mem, r64/mem, immu12"); + reg_instr(target, "seqz", "w:r64/mem, r64/mem"); + reg_instr(target, "snez", "w:r64/mem, r64/mem"); + reg_instr(target, "sltz", "w:r64/mem, r64/mem"); + reg_instr(target, "sgtz", "w:r64/mem, r64/mem"); + // CSR + reg_instr(target, "csrw", "w:r64, r64/mem"); + reg_instr(target, "csrr", "w:r64/mem, r64"); + reg_instr(target, "csrrw", "w:r64/mem, rw:r64, r64/mem"); + reg_instr(target, "csrrs", "w:r64/mem, rw:r64, r64/mem"); + reg_instr(target, "csrrc", "w:r64/mem, rw:r64, r64/mem"); + reg_instr(target, "csrrwi", "w:r64/mem, rw:r64, immu5"); + reg_instr(target, "csrrsi", "w:r64/mem, rw:r64, immu5"); + reg_instr(target, "csrrci", "w:r64/mem, rw:r64, immu5"); + // Interrupt + reg_instr(target, "wfi", NULL); + reg_instr(target, "mret", NULL); + + bits = ARG_BITS_64; + break; + case ARCH_TYPE_RISCV32: + // math + reg_instr(target, "add", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "sub", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "addi", "w:r32/mem, r32/mem, immi12"); + reg_instr(target, "neg", "w:r32/mem, r32/mem"); + // bit + reg_instr(target, "and", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "or", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "xor", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "not", "w:r32/mem, r32/mem"); + reg_instr(target, "andi", "w:r32/mem, r32/mem, immi12"); + reg_instr(target, "ori", "w:r32/mem, r32/mem, immi12"); + reg_instr(target, "xori", "w:r32/mem, r32/mem, immi12"); + // shift + reg_instr(target, "sll", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "srl", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "sra", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "slli", "w:r32/mem, r32/mem, immu5"); + reg_instr(target, "srli", "w:r32/mem, r32/mem, immu5"); + reg_instr(target, "srai", "w:r32/mem, r32/mem, immu5"); + // load + reg_instr(target, "li", "w:r32/mem, immi32"); + reg_instr(target, "lui", "w:r32, immu20"); + reg_instr(target, "auipc", "w:r32, immi20"); + reg_instr(target, "mv", "w:r32/mem, r32/mem"); + reg_instr(target, "lw", "w:r32/mem, mem"); + reg_instr(target, "lh", "w:r32/mem, mem"); + reg_instr(target, "lhu", "w:r32/mem, mem"); + reg_instr(target, "lb", "w:r32/mem, mem"); + reg_instr(target, "lbu", "w:r32/mem, mem"); + // store + reg_instr(target, "sw", "r32/mem, w:mem"); + reg_instr(target, "sh", "r32/mem, w:mem"); + reg_instr(target, "sb", "r32/mem, w:mem"); + // Misc + reg_instr(target, "nop", NULL); + reg_instr(target, "ebreak", NULL); + reg_instr(target, "ecall", NULL); + reg_instr(target, "eret", NULL); + // Jump + reg_instr(target, "j", "immi20"); + reg_instr(target, "jal", "w:r32/mem, immi20"); + reg_instr(target, "jalr", "w:r32/mem, r32/mem, immi20"); + reg_instr(target, "ret", NULL); + // Set + reg_instr(target, "slt", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "slti", "w:r32/mem, r32/mem, immi12"); + reg_instr(target, "sltu", "w:r32/mem, r32/mem, r32/mem"); + reg_instr(target, "sltiu", "w:r32/mem, r32/mem, immu12"); + reg_instr(target, "seqz", "w:r32/mem, r32/mem"); + reg_instr(target, "snez", "w:r32/mem, r32/mem"); + reg_instr(target, "sltz", "w:r32/mem, r32/mem"); + reg_instr(target, "sgtz", "w:r32/mem, r32/mem"); + // CSR + reg_instr(target, "csrw", "w:r32, r32/mem"); + reg_instr(target, "csrr", "w:r32/mem, r32"); + reg_instr(target, "csrrw", "w:r32/mem, rw:r32, r32/mem"); + reg_instr(target, "csrrs", "w:r32/mem, rw:r32, r32/mem"); + reg_instr(target, "csrrc", "w:r32/mem, rw:r32, r32/mem"); + reg_instr(target, "csrrwi", "w:r32/mem, rw:r32, immu5"); + reg_instr(target, "csrrsi", "w:r32/mem, rw:r32, immu5"); + reg_instr(target, "csrrci", "w:r32/mem, rw:r32, immu5"); + // Interrupt + reg_instr(target, "wfi", NULL); + reg_instr(target, "mret", NULL); + + bits = ARG_BITS_32; + break; + default: + UNREACHABLE + } + reg_register_list(target, riscv_gp_integer_regs, 32, ASM_REG_INT, bits, RISCV_X0); + reg_register_list(target, riscv_arg_integer_regs, 8, ASM_REG_INT, bits, RISCV_X10); + reg_register_list(target, riscv_temp_integer_regs, 3, ASM_REG_INT, bits, RISCV_X5); + reg_register_list(target, &riscv_temp_integer_regs[3], 4, ASM_REG_INT, bits, RISCV_X28); + reg_register_list(target, riscv_save_integer_regs, 2, ASM_REG_INT, bits, RISCV_X8); + reg_register_list(target, &riscv_save_integer_regs[2], 10, ASM_REG_INT, bits, RISCV_X18); + reg_register_list(target, riscv_machine_integer_regs, 3, ASM_REG_INT, bits, RISCV_MIE); + reg_register(target, "$ra", ASM_REG_INT, bits, RISCV_X1); + reg_register(target, "$sp", ASM_REG_INT, bits, RISCV_X2); + reg_register(target, "$gp", ASM_REG_INT, bits, RISCV_X3); + reg_register(target, "$tp", ASM_REG_INT, bits, RISCV_X4); + reg_register(target, "$zero", ASM_REG_INT, bits, RISCV_X0); } static void init_asm_ppc(PlatformTarget *target) diff --git a/src/compiler/codegen_asm.c b/src/compiler/codegen_asm.c index 363c3c7de..8b5654b71 100644 --- a/src/compiler/codegen_asm.c +++ b/src/compiler/codegen_asm.c @@ -3,6 +3,10 @@ // a copy of which can be found in the LICENSE file. #include "codegen_internal.h" +// Macro to cast an unsigned value `x` to a signed integer of `n` bits, then extend to int64_t +#define CAST_AND_EXTEND(x, n) \ + (((int64_t)((x) << (64 - (n)))) >> (64 - (n))) + static inline void codegen_create_x86att_arg(AsmInlineBlock *block, unsigned input_offset, Expr *expr) { ExprAsmArg *arg = &expr->expr_asm_arg; @@ -112,6 +116,57 @@ static inline void codegen_create_aarch64_arg(AsmInlineBlock *block, unsigned in UNREACHABLE } +static inline void codegen_create_riscv_arg(AsmInlineBlock *block, unsigned input_offset, Expr *expr) +{ + ExprAsmArg *arg = &expr->expr_asm_arg; + switch (arg->kind) + { + case ASM_ARG_INT: + if (arg->is_neg) + { + scratch_buffer_append_signed_int(CAST_AND_EXTEND(arg->value, arg->bits)); + } + else + { + scratch_buffer_append_unsigned_int(arg->value); + } + return; + case ASM_ARG_REG: + scratch_buffer_append(&arg->reg.ref->name[1]); + return; + case ASM_ARG_VALUE: + scratch_buffer_append_char('$'); + scratch_buffer_append_unsigned_int(arg->index + input_offset); + return; + case ASM_ARG_MEMVAR: + case ASM_ARG_REGVAR: + scratch_buffer_append_char('$'); + if (arg->ident.is_input && !arg->ident.copy_output) + { + scratch_buffer_append_unsigned_int(arg->index + input_offset); + } + else + { + scratch_buffer_append_unsigned_int(arg->index); + } + return; + case ASM_ARG_ADDR: + if (arg->idx || arg->offset_type) TODO; + if (arg->neg_offset) scratch_buffer_append_char('-'); + scratch_buffer_append_unsigned_int(arg->offset); + scratch_buffer_append_char('('); + if (arg->base) + { + codegen_create_riscv_arg(block, input_offset, exprptr(arg->base)); + } + scratch_buffer_append_char(')'); + return; + case ASM_ARG_ADDROF: + TODO + } + UNREACHABLE +} + static inline char *codegen_create_x86_att_asm(AsmInlineBlock *block) { @@ -161,6 +216,30 @@ static inline char *codegen_create_aarch64_asm(AsmInlineBlock *block) return scratch_buffer_to_string(); } +static inline char *codegen_create_riscv_asm(AsmInlineBlock *block) +{ + AstId next = block->asm_stmt; + scratch_buffer_clear(); + unsigned input_arg_offset = vec_size(block->output_vars); + while (next) + { + Ast *ast = astptr(next); + next = ast->next; + scratch_buffer_append(ast->asm_stmt.instruction); + Expr** args = ast->asm_stmt.args; + unsigned arg_count = vec_size(args); + scratch_buffer_append_char(' '); + for (unsigned i = 0; i < arg_count; i++) + { + if (i > 0) scratch_buffer_append(", "); + codegen_create_riscv_arg(block, input_arg_offset, args[i]); + } + scratch_buffer_append_char('\n'); + } + + return scratch_buffer_to_string(); +} + const char *codegen_create_asm(Ast *ast) { assert(ast->ast_kind == AST_ASM_BLOCK_STMT); @@ -174,5 +253,9 @@ const char *codegen_create_asm(Ast *ast) { return codegen_create_aarch64_asm(block); } + if (compiler.platform.arch == ARCH_TYPE_RISCV32 || compiler.platform.arch == ARCH_TYPE_RISCV64) + { + return codegen_create_riscv_asm(block); + } UNREACHABLE } \ No newline at end of file diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 74c1c001a..c5c15fef1 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -24,6 +24,16 @@ typedef uint32_t ArraySize; typedef uint64_t BitSize; typedef uint16_t FileId; +#define INT5_MAX 15 +#define INT12_MAX 2047 +#define INT20_MAX 524287 +#define INT5_MIN -16 +#define INT12_MIN -2048 +#define INT20_MIN (-INT20_MAX-1) +#define UINT5_MAX 31 +#define UINT12_MAX 4095 +#define UINT20_MAX 1048575U + #define MAX_ARRAYINDEX INT32_MAX #define MAX_FIXUPS 0xFFFFF #define MAX_HASH_SIZE (512 * 1024 * 1024) @@ -874,7 +884,11 @@ typedef struct ExprId idx; uint64_t offset; }; - uint64_t value; + struct { + uint64_t value; + unsigned bits; + bool is_neg; + }; union { const char *name; @@ -3583,8 +3597,11 @@ INLINE unsigned arg_bits_max(AsmArgBits bits, unsigned limit) if (limit >= 80 && (bits & ARG_BITS_80)) return 80; if (limit >= 64 && (bits & ARG_BITS_64)) return 64; if (limit >= 32 && (bits & ARG_BITS_32)) return 32; + if (limit >= 20 && (bits & ARG_BITS_20)) return 20; if (limit >= 16 && (bits & ARG_BITS_16)) return 16; + if (limit >= 12 && (bits & ARG_BITS_12)) return 12; if (limit >= 8 && (bits & ARG_BITS_8)) return 8; + if (limit >= 5 && (bits & ARG_BITS_5)) return 5; return 0; } @@ -3674,3 +3691,4 @@ INLINE bool check_module_name(Path *path) return true; } + diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 8628c7051..5c02eeef7 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -142,6 +142,9 @@ typedef enum FLAG_ATTR ARG_BITS_256 = 1 << 5, ARG_BITS_512 = 1 << 6, ARG_BITS_80 = 1 << 7, + ARG_BITS_5 = 1 << 8, + ARG_BITS_12 = 1 << 9, + ARG_BITS_20 = 1 << 10, } AsmArgBits; typedef enum diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 162af18a6..32bb0651c 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -330,6 +330,10 @@ static inline Expr *parse_asm_expr(ParseContext *c) return poisoned_expr; } return expr; + case TOKEN_MINUS: + expr->expr_asm_arg.kind = ASM_ARG_VALUE; + ASSIGN_EXPRID_OR_RET(expr->expr_asm_arg.expr_id, parse_expr(c), poisoned_expr); + return expr; case TOKEN_INTEGER: case TOKEN_CONST_IDENT: case TOKEN_REAL: diff --git a/src/compiler/sema_asm.c b/src/compiler/sema_asm.c index 59423a33c..4579c0341 100644 --- a/src/compiler/sema_asm.c +++ b/src/compiler/sema_asm.c @@ -22,11 +22,11 @@ static inline Type *max_supported_imm_int(bool is_signed, AsmArgType arg) { unsigned bits = arg_bits_max(arg.imm_arg_ibits, 64); if (!bits) return NULL; - return type_int_signed_by_bitsize(bits); + return type_int_signed_by_bitsize(next_highest_power_of_2(bits)); } unsigned bits = arg_bits_max(arg.imm_arg_ubits, 64); if (!bits) return NULL; - return type_int_unsigned_by_bitsize(bits); + return type_int_unsigned_by_bitsize(next_highest_power_of_2(bits)); } /* @@ -53,7 +53,7 @@ static inline bool sema_reg_int_suported_type(AsmArgType arg, Type *type) { assert(type_flatten(type) == type); unsigned bits = type_bit_size(type); - return arg_bits_max(arg.ireg_bits, bits) == bits; + return next_highest_power_of_2(arg_bits_max(arg.ireg_bits, bits)) == bits; } INLINE bool sema_reg_is_valid_in_slot(AsmRegister *reg, AsmArgType arg_type) @@ -75,7 +75,91 @@ static inline bool sema_reg_float_suported_type(AsmArgType arg, Type *type) { assert(type_flatten(type) == type); if (!arg.float_bits) return false; - return type_bit_size(type) == arg_bits_max(arg.float_bits, 0); + return type_bit_size(type) == next_highest_power_of_2(arg_bits_max(arg.float_bits, 0)); +} + +static inline bool sema_check_imm_fits(Int imm, AsmArgType arg_type) +{ + Int128 min = {0}; + Int128 max = {0}; + bool is_signed = false; + // Check if actually an immediate. If not, just move along. + if (arg_type.imm_arg_ibits == 0 && arg_type.imm_arg_ubits == 0) return true; + if (arg_type.imm_arg_ibits > 0) + { + if (arg_type.imm_arg_ibits & ARG_BITS_20) + { + min = i128_from_signed(INT20_MIN); + max = i128_from_signed(INT20_MAX); + is_signed = true; + } + else if (arg_type.imm_arg_ibits & ARG_BITS_12) + { + min = i128_from_signed(INT12_MIN); + max = i128_from_signed(INT12_MAX); + is_signed = true; + } + else if (arg_type.imm_arg_ibits & ARG_BITS_5) + { + min = i128_from_signed(INT5_MIN); + max = i128_from_signed(INT5_MAX); + is_signed = true; + } + else if (arg_type.imm_arg_ibits & (ARG_BITS_8|ARG_BITS_16|ARG_BITS_32|ARG_BITS_64|ARG_BITS_128|ARG_BITS_256|ARG_BITS_512|ARG_BITS_80)) + { + } + else + { + UNREACHABLE; + } + } + else + { + if (arg_type.imm_arg_ubits > 0) + { + if (arg_type.imm_arg_ubits & ARG_BITS_20) + { + max = (Int128){ 0, UINT20_MAX }; + } + else if (arg_type.imm_arg_ubits & ARG_BITS_12) + { + max = (Int128){ 0, UINT12_MAX }; + } + else if (arg_type.imm_arg_ubits & ARG_BITS_5) + { + max = (Int128){ 0, UINT5_MAX }; + } + else if (arg_type.imm_arg_ubits & (ARG_BITS_8|ARG_BITS_16|ARG_BITS_32|ARG_BITS_64|ARG_BITS_128|ARG_BITS_256|ARG_BITS_512|ARG_BITS_80)) + { + } + else + { + UNREACHABLE; + } + } + } + // Not an immediate of these off-brand types, so just move along. + if (i128_is_zero(min) && i128_is_zero(max)) return true; + bool op_is_signed = type_kind_is_signed(imm.type); + if (is_signed) + { + if (op_is_signed) + { + if (i128_scomp(imm.i, min) == CMP_LT) return false; + if (i128_scomp(imm.i, max) == CMP_GT) return false; + return true; + } + // In the unsigned case, we don't need to test the lower limit. + return i128_ucomp(imm.i, max) != CMP_GT; + } + if (op_is_signed) + { + if (i128_is_neg(imm.i)) return false; + if (i128_ucomp(imm.i, max) == CMP_GT) return false; + return true; + } + // In the unsigned case, we don't need to test the lower limit. + return i128_ucomp(imm.i, max) != CMP_GT; } static inline bool sema_check_asm_arg_const_int(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr, Expr *int_expr) @@ -88,14 +172,18 @@ static inline bool sema_check_asm_arg_const_int(SemaContext *context, AsmInlineB return false; } Int i = int_expr->const_expr.ixx; - if (!type || !int_fits(i, type->type_kind)) + unsigned max_bits = arg_bits_max(is_signed ? arg_type.imm_arg_ibits : arg_type.imm_arg_ubits, 0); + if (!type || !int_fits(i, type->type_kind) || !sema_check_imm_fits(i, arg_type)) { - SEMA_ERROR(expr, "'%s' expected %s.", instr->name, type_quoted_error_string(type)); + SEMA_ERROR(expr, "'%s' expected %s limited to %d bits.", instr->name, type_quoted_error_string(type), max_bits); return false; } // Because we assume max 64 bit imm, we can do this simple cast for signed values. + expr->expr_asm_arg.is_neg = false; + expr->expr_asm_arg.bits = max_bits; if (is_signed) { + expr->expr_asm_arg.is_neg = i128_is_neg(int_expr->const_expr.ixx.i); switch (type->type_kind) { case TYPE_I8: @@ -175,6 +263,18 @@ static inline bool sema_check_asm_arg_addr(SemaContext *context, AsmInlineBlock return false; } } + if ((compiler.platform.arch == ARCH_TYPE_RISCV32 || + compiler.platform.arch == ARCH_TYPE_RISCV64) && + asm_arg->offset) + { + if ((asm_arg->neg_offset && asm_arg->offset > abs(INT12_MIN)) || + (!asm_arg->neg_offset && asm_arg->offset > INT12_MAX)) + { + SEMA_ERROR(expr, "RISC-V offset limited to 12-bits signed."); + return false; + } + } + REMINDER("check if addressing mode is supported"); return true; } @@ -477,7 +577,10 @@ static inline bool sema_check_asm_arg(SemaContext *context, AsmInlineBlock *bloc } bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt) { - if (compiler.platform.arch != ARCH_TYPE_X86_64 && compiler.platform.arch != ARCH_TYPE_AARCH64) + if (compiler.platform.arch != ARCH_TYPE_X86_64 && + compiler.platform.arch != ARCH_TYPE_AARCH64 && + compiler.platform.arch != ARCH_TYPE_RISCV32 && + compiler.platform.arch != ARCH_TYPE_RISCV64) { SEMA_ERROR(asm_stmt, "Unsupported architecture for asm."); return false; diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 6104c12cb..af73a9f35 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -7,6 +7,16 @@ #include "compiler_internal.h" +#define INT5_MAX 15 +#define INT12_MAX 2047 +#define INT20_MAX 524287 +#define INT5_MIN -16 +#define INT12_MIN -2048 +#define INT20_MIN (-INT20_MAX-1) +#define UINT5_MAX 31 +#define UINT12_MAX 4095 +#define UINT20_MAX 1048575U + #define SEMA_ERROR(_node, ...) sema_error_at(context, (_node)->span, __VA_ARGS__) #define RETURN_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->span, __VA_ARGS__); return false; } while (0) diff --git a/test/test_suite/asm/asm_bit_rv.c3t b/test/test_suite/asm/asm_bit_rv.c3t new file mode 100644 index 000000000..56ba45552 --- /dev/null +++ b/test/test_suite/asm/asm_bit_rv.c3t @@ -0,0 +1,20 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + asm + { + and $t0, $t1, $t2; + or $a0, $a1, $a2; + xor $s7, $s6, $s5; + andi $s1, $s2, -2048; + ori $s3, $s4, 2047; + xori $t3, $t4, 1; + not $t3, $t4; + } +} + +/* #expect: test.ll + +"and t0, t1, t2\0Aor a0, a1, a2\0Axor s7, s6, s5\0Aandi s1, s2, -2048\0Aori s3, s4, 2047\0Axori t3, t4, 1\0Anot t3, t4\0A", "~{x5},~{x9},~{x10},~{x19},~{x23},~{x28}"() \ No newline at end of file diff --git a/test/test_suite/asm/asm_intr_rv.c3t b/test/test_suite/asm/asm_intr_rv.c3t new file mode 100644 index 000000000..0f4928505 --- /dev/null +++ b/test/test_suite/asm/asm_intr_rv.c3t @@ -0,0 +1,21 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + int x = 0; + void* fp; + asm + { + csrw $mstatus, $a0; + csrrsi $zero, $mstatus, 8u; + csrrci $zero, $mstatus, 31u; + csrrw $zero, $mtvec, fp; + mret; + wfi; + } +} + +/* #expect: test.ll + +"csrw mstatus, a0\0Acsrrsi zero, mstatus, 8\0Acsrrci zero, mstatus, 31\0Acsrrw zero, mtvec, $0\0Amret \0Awfi \0A", "r,~{x0},~{mstatus},~{mtvec}"(ptr %1) \ No newline at end of file diff --git a/test/test_suite/asm/asm_jump_rv.c3t b/test/test_suite/asm/asm_jump_rv.c3t new file mode 100644 index 000000000..ed0539fd5 --- /dev/null +++ b/test/test_suite/asm/asm_jump_rv.c3t @@ -0,0 +1,30 @@ +// #target: elf-riscv32 +module test; + +def Sum = fn int(int a, int b); + +fn int sum(int a, int b) +{ + return a + b; +} + +fn void main(String[] args) +{ + int a = 1; + int b = 2; + int result = 0; + Sum sum_ptr = ∑ + + asm + { + mv $a0, a; + mv $a1, b; + jalr $ra, sum_ptr, 0; + mv result, $a0; + } +} + + +/* #expect: test.ll + +"mv a0, $1\0Amv a1, $2\0Ajalr ra, $3, 0\0Amv $0, a0\0A", "=r,r,r,r,~{x1},~{x10},~{x11}"(i32 %1, i32 %2, ptr %3) \ No newline at end of file diff --git a/test/test_suite/asm/asm_load_rv.c3t b/test/test_suite/asm/asm_load_rv.c3t new file mode 100644 index 000000000..e06c0d2b8 --- /dev/null +++ b/test/test_suite/asm/asm_load_rv.c3t @@ -0,0 +1,20 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + int x = 2; + asm + { + li $s1, -65536; + lui $t0, 123456u; + auipc $x15, -123456; + mv $a0, $a1; + lw $a4, [&x]; + lb $a5, [$a7 - 4]; + } +} + +/* #expect: test.ll + +"li s1, -65536\0Alui t0, 123456\0Aauipc x15, -123456\0Amv a0, a1\0Alw a4, $0\0Alb a5, -4(a7)\0A", "*m,~{x5},~{x9},~{x10},~{x14},~{x15}"(ptr elementtype(i32) %x) \ No newline at end of file diff --git a/test/test_suite/asm/asm_load_rv64.c3t b/test/test_suite/asm/asm_load_rv64.c3t new file mode 100644 index 000000000..27dcd217f --- /dev/null +++ b/test/test_suite/asm/asm_load_rv64.c3t @@ -0,0 +1,15 @@ +// #target: elf-riscv64 +module test; + +fn void main(String[] args) +{ + int x = 2; + asm + { + li $s1, -9223372036854775808; + } +} + +/* #expect: test.ll + +"li s1, -9223372036854775808\0A", "~{x9}"() \ No newline at end of file diff --git a/test/test_suite/asm/asm_math_rv.c3t b/test/test_suite/asm/asm_math_rv.c3t new file mode 100644 index 000000000..ac6da66e1 --- /dev/null +++ b/test/test_suite/asm/asm_math_rv.c3t @@ -0,0 +1,19 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + int x = 0; + asm + { + add $t0, $t1, $t2; + add x, $t1, $t2; + sub $a0, $a1, $a2; + addi $s1, $s2, -2048; + neg $t3, $t4; + } +} + +/* #expect: test.ll + +"add t0, t1, t2\0Aadd $0, t1, t2\0Asub a0, a1, a2\0Aaddi s1, s2, -2048\0Aneg t3, t4\0A", "=r,~{x5},~{x9},~{x10},~{x28}"() \ No newline at end of file diff --git a/test/test_suite/asm/asm_set_rv.c3t b/test/test_suite/asm/asm_set_rv.c3t new file mode 100644 index 000000000..89295b1e2 --- /dev/null +++ b/test/test_suite/asm/asm_set_rv.c3t @@ -0,0 +1,18 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + int x = 2; + asm + { + slt $t0, $t1, x; + slti $a0, $a1, -2048; + sltiu $a0, $a1, 4095u; + seqz $s0, $zero; + } +} + +/* #expect: test.ll + +"slt t0, t1, $0\0Aslti a0, a1, -2048\0Asltiu a0, a1, 4095\0Aseqz s0, zero\0A", "r,~{x5},~{x8},~{x10}"(i32 %1) \ No newline at end of file diff --git a/test/test_suite/asm/asm_shift_rv.c3t b/test/test_suite/asm/asm_shift_rv.c3t new file mode 100644 index 000000000..21253b356 --- /dev/null +++ b/test/test_suite/asm/asm_shift_rv.c3t @@ -0,0 +1,19 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + asm + { + sll $t0, $t1, $t2; + slli $s1, $s2, 31u; + srl $a0, $a1, $a2; + srli $x10, $x11, 0u; + sra $t0, $t1, $t2; + srai $s1, $s2, 1u; + } +} + +/* #expect: test.ll + +"sll t0, t1, t2\0Aslli s1, s2, 31\0Asrl a0, a1, a2\0Asrli x10, x11, 0\0Asra t0, t1, t2\0Asrai s1, s2, 1\0A", "~{x5},~{x9},~{x10}"() \ No newline at end of file diff --git a/test/test_suite/asm/asm_store_rv.c3t b/test/test_suite/asm/asm_store_rv.c3t new file mode 100644 index 000000000..abff59560 --- /dev/null +++ b/test/test_suite/asm/asm_store_rv.c3t @@ -0,0 +1,17 @@ +// #target: elf-riscv32 +module test; + +fn void main(String[] args) +{ + int x = 2; + int y = 0; + asm + { + lw $a4, [&x]; + sw $a4, [&y]; + } +} + +/* #expect: test.ll + +"lw a4, $1\0Asw a4, $0\0A", "=*m,*m,~{x14}"(ptr elementtype(i32) %y, ptr elementtype(i32) %x) \ No newline at end of file