Mockout inline asm for riscv

This commit is contained in:
gingerBill
2026-08-20 14:42:42 +01:00
parent 1009ab0c62
commit 84f9bc76f9
8 changed files with 71 additions and 27 deletions

View File

@@ -455,6 +455,10 @@ resolve_relocation_inline :: #force_inline proc(
return true
}
is_implicit_op_inline :: #force_inline proc "contextless" (op: Operand_Type) -> bool {
return op == .GPR_SP
}
// =============================================================================
// Scatter / gather helpers for B-type and J-type immediates
// =============================================================================

View File

@@ -52,11 +52,12 @@ Feature :: enum u8 {
}
Encoding_Flags :: bit_field u8 {
rv32_only: bool | 1, // RV32 base only
rv64_only: bool | 1, // RV64 base only (e.g. LD/SD/ADDIW/...)
branch: bool | 1, // changes PC
fp_round: bool | 1, // funct3 doubles as FP rounding-mode field
_: u8 | 4,
rv32_only: bool | 1, // RV32 base only
rv64_only: bool | 1, // RV64 base only (e.g. LD/SD/ADDIW/...)
branch: bool | 1, // changes PC
fp_round: bool | 1, // funct3 doubles as FP rounding-mode field
explicit_count: u8 | 3,
has_implicit: bool | 1,
}
// What the user passes in.
@@ -149,7 +150,7 @@ Encoding :: struct #packed {
enc: [4]Operand_Encoding, // 4
bits: u32, // 4 -- static bit pattern
mask: u32, // 4 -- which bits are static
feature: Feature, // 1
feature: Feature, // 1
flags: Encoding_Flags, // 1
}
#assert(size_of(Encoding) == 20)

View File

@@ -113,7 +113,22 @@ emit_encode_tables :: proc() -> (total: int) {
fmt.sbprintfln(&sb, "\t// .%v", m)
for &form in forms {
f := &form.encoding
write_row(&sb, f.mnemonic, f.ops, f.enc, f.bits, f.mask, f.feature, f.flags)
flags := f.flags
encoding_operand_count: u8 = 0
has_implicit := false
for op_type in f.ops {
if op_type == .NONE { break }
if lib.is_implicit_op_inline(op_type) {
has_implicit = true
} else {
encoding_operand_count += 1
}
}
flags.explicit_count = encoding_operand_count
flags.has_implicit = has_implicit
write_row(&sb, f.mnemonic, f.ops, f.enc, f.bits, f.mask, f.feature, flags)
}
}
strings.write_string(&sb, "}\n\n")

View File

@@ -309,6 +309,20 @@ main :: proc() {
// NOTE: SideEffectFlag_HINT deliberately excluded — inert, may be DCE'd.
return ((side_effects & VOLATILE_SE) != 0);
}
u8 is_call_or_mem() const {
return (cast(u16)side_effects & SideEffectFlag_CONTROL) != 0 ||
(cast(u16)implicit_wr & ClobberReg_RSP) != 0;
}
bool has_control() const {
return (cast(u16)side_effects & SideEffectFlag_CONTROL) != 0;
}
bool has_halt() const {
return (cast(u16)side_effects & SideEffectFlag_HALT) != 0;
}
bool is_conditional() const {
return has_control() && (cast(u16)flags_rd != 0);
}
};
void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) {
@@ -381,13 +395,6 @@ main :: proc() {
fmt.sbprintf(&sb, "return cast(u8)((flags>>%du)&((1u<<%d)-1));", bit_offset, bit_size)
strings.write_string(&sb, " }\n")
}
{
bit_offset := intrinsics.type_field_bit_offset(Encoding_Flags, "op_count")
bit_size := intrinsics.type_field_bit_size(Encoding_Flags, "op_count")
strings.write_string(&sb, "\t\tu8 op_count () const { ")
fmt.sbprintf(&sb, "return cast(u8)((flags>>%du)&((1u<<%d)-1));", bit_offset, bit_size)
strings.write_string(&sb, " }\n")
}
{
bit_offset := intrinsics.type_field_bit_offset(Encoding_Flags, "lock_ok")
strings.write_string(&sb, "\t\tbool lock_ok () const { ")
@@ -421,7 +428,8 @@ main :: proc() {
strings.write_string(&sb, """
bool init() {
bool init(i64 word_size) {
gb_unused(word_size);
string_map_init(&mnemonic_map, MNEMONIC_COUNT*2);
for (u16 m = M_INVALID+1; m < MNEMONIC_COUNT; m++) {
string_map_set(&mnemonic_map, mnemonic_strings[m], cast(Mnemonic)m);

View File

@@ -54,7 +54,9 @@ gb_global String const asm_operand_kind_expected_strings[AsmOperand_COUNT] = {
};
#include "asm_tables_amd64.cpp"
#include "asm_tables_riscv.cpp"
void init_asm_tables() {
g_asm_amd64.init();
void init_asm_tables(i64 word_size) {
g_asm_amd64.init(word_size);
g_asm_riscv.init(word_size);
}

View File

@@ -312,6 +312,20 @@ struct Asm_amd64 {
// NOTE: SideEffectFlag_HINT deliberately excluded — inert, may be DCE'd.
return ((side_effects & VOLATILE_SE) != 0);
}
u8 is_call_or_mem() const {
return (cast(u16)side_effects & SideEffectFlag_CONTROL) != 0 ||
(cast(u16)implicit_wr & ClobberReg_RSP) != 0;
}
bool has_control() const {
return (cast(u16)side_effects & SideEffectFlag_CONTROL) != 0;
}
bool has_halt() const {
return (cast(u16)side_effects & SideEffectFlag_HALT) != 0;
}
bool is_conditional() const {
return has_control() && (cast(u16)flags_rd != 0);
}
};
void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) {
@@ -424,7 +438,6 @@ struct Asm_amd64 {
bool has_implicit () const { return ((flags>>21u)&1) != 0; }
u8 explicit_count() const { return cast(u8)((flags>>18u)&((1u<<3)-1)); }
u8 op_count () const { return cast(u8)((flags>>22u)&((1u<<3)-1)); }
bool lock_ok () const { return ((flags>>14u)&1) != 0; }
bool rep_ok () const { return ((flags>>15u)&1) != 0; }
};
@@ -447,7 +460,8 @@ struct Asm_amd64 {
StringMap<Prefix> prefix_map;
StringMap<Register> register_map;
bool init() {
bool init(i64 word_size) {
gb_unused(word_size);
string_map_init(&mnemonic_map, MNEMONIC_COUNT*2);
for (u16 m = M_INVALID+1; m < MNEMONIC_COUNT; m++) {
string_map_set(&mnemonic_map, mnemonic_strings[m], cast(Mnemonic)m);

View File

@@ -1080,8 +1080,7 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
// aligned at the call boundary) or manipulates RSP directly. Plain memory access
// through a parameter pointer does NOT require stack realignment, so
// implies_clobber_memory() is intentionally NOT used here.
if ((cast(u16)clobber.side_effects & asm_ctx->SideEffectFlag_CONTROL) != 0 ||
(cast(u16)clobber.implicit_wr & asm_ctx->ClobberReg_RSP) != 0) {
if (clobber.is_call_or_mem()) {
asm_acc->saw_call_or_mem = true;
}
@@ -1159,17 +1158,16 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
// is not merely CONTROL-with-fallthrough. We approximate "unconditional" as
// CONTROL|HALT with no explicit label/operand fallthrough below.
{
u16 se = cast(u16)clobber.side_effects;
bool control = (se & asm_ctx->SideEffectFlag_CONTROL) != 0;
bool halt = (se & asm_ctx->SideEffectFlag_HALT) != 0;
bool control = clobber.has_control();
bool halt = clobber.has_halt();
// A conditional branch reads a flag and can fall through -> not terminal.
bool conditional = control && (cast(u16)clobber.flags_rd != 0);
bool conditional = clobber.is_conditional();
asm_acc->last_is_terminal = halt || (control && !conditional);
}
// A branch/call inside the template means subsequent instructions may be reached
// out of textual order; stop trusting the linear def model past this point.
if (cast(u16)clobber.side_effects & asm_ctx->SideEffectFlag_CONTROL) {
if (clobber.has_control()) {
asm_acc->straight_line = false;
}
asm_ctx->clobber_implicit_regs(&tmpl_entity->AsmTemplate.clobber_registers_set, produced);
@@ -2126,6 +2124,8 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
gb_internal void check_asm_template_from_entity(CheckerContext *c, Entity *e, DeclInfo *d) {
if (build_context.metrics.arch == TargetArch_amd64) {
check_asm_template(&g_asm_amd64, c, e, d);
} else if (build_context.metrics.arch == TargetArch_riscv64) {
check_asm_template(&g_asm_riscv, c, e, d);
} else {
error(e->token, "asm templates are not currently supported for this target");
}

View File

@@ -4321,7 +4321,7 @@ int main(int arg_count, char const **arg_ptr) {
bool failed_to_cache_parsing = false;
TIME_SECTION("init asm tables");
init_asm_tables();
init_asm_tables(build_context.metrics.ptr_size);
MAIN_TIME_SECTION("parse files");