Verify the asm instruction operands against the Encoding.ops

This commit is contained in:
gingerBill
2026-08-11 11:47:48 +01:00
parent 5ca7638bb2
commit 400203c344
5 changed files with 194 additions and 8 deletions

View File

@@ -140,7 +140,7 @@ main :: proc() {
{
strings.write_string(&sb, "\tenum OperandEncoding : u8 {\n")
defer strings.write_string(&sb, "\t};\n")
for op in type_of(gen.Encoding{}.enc[0]) {
for op in Operand_Encoding {
fmt.sbprintf(&sb, "\t\tENC_%s,\n", op)
}
@@ -279,7 +279,41 @@ main :: proc() {
strings.write_string(&sb, "\t\treturn 0;\n")
strings.write_string(&sb, "\t}\n")
}
{
strings.write_string(&sb, "\tAsmOperandKind kind_from_operand_type(OperandType type) {\n")
strings.write_string(&sb, "\t\tswitch (type) {\n")
strings.write_string(&sb, "\t\tcase OP_R8: case OP_R16: case OP_R32: case OP_R64:\n")
strings.write_string(&sb, "\t\tcase OP_SREG: case OP_CR: case OP_DR:\n")
strings.write_string(&sb, "\t\tcase OP_XMM: case OP_YMM: case OP_ZMM:\n")
strings.write_string(&sb, "\t\tcase OP_MM: case OP_K: case OP_STI:\n")
strings.write_string(&sb, "\t\tcase OP_AL_IMPL: case OP_AX_IMPL: case OP_EAX_IMPL: case OP_RAX_IMPL:\n")
strings.write_string(&sb, "\t\tcase OP_CL_IMPL: case OP_DX_IMPL:\n")
strings.write_string(&sb, "\t\tcase OP_ST0_IMPL: case OP_XMM0_IMPL:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Register;\n")
strings.write_string(&sb, "\t\tcase OP_RM8: case OP_RM16: case OP_RM32: case OP_RM64:\n")
strings.write_string(&sb, "\t\tcase OP_XMM_M32: case OP_XMM_M64: case OP_XMM_M128:\n")
strings.write_string(&sb, "\t\tcase OP_YMM_M256: case OP_ZMM_M512:\n")
strings.write_string(&sb, "\t\tcase OP_MM_M64:\n")
strings.write_string(&sb, "\t\tcase OP_K_M8: case OP_K_M16: case OP_K_M32: case OP_K_M64:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Register_Or_Memory;\n")
strings.write_string(&sb, "\t\tcase OP_M: case OP_M8: case OP_M16: case OP_M32: case OP_M64:\n")
strings.write_string(&sb, "\t\tcase OP_M80: case OP_M128: case OP_M256: case OP_M512:\n")
strings.write_string(&sb, "\t\tcase OP_MOFFS8: case OP_MOFFS16: case OP_MOFFS32: case OP_MOFFS64:\n")
strings.write_string(&sb, "\t\tcase OP_M16_16: case OP_M16_32: case OP_M16_64:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Memory;\n")
strings.write_string(&sb, "\t\tcase OP_IMM8: case OP_IMM16: case OP_IMM32: case OP_IMM64:\n")
strings.write_string(&sb, "\t\tcase OP_IMM8SX:\n")
strings.write_string(&sb, "\t\tcase OP_ONE_IMPL:\n")
strings.write_string(&sb, "\t\tcase OP_PTR16_16: case OP_PTR16_32: case OP_PTR16_64:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Immediate;\n")
strings.write_string(&sb, "\t\tcase OP_REL8: case OP_REL32:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Label;\n")
strings.write_string(&sb, "\t\tcase OP_NONE:\n")
strings.write_string(&sb, "\t\tdefault:\n")
strings.write_string(&sb, "\t\t\treturn AsmOperand_Invalid;\n")
strings.write_string(&sb, "\t\t}\n")
strings.write_string(&sb, "\t}\n")
}
strings.write_string(&sb, "};\n")
@@ -447,6 +481,35 @@ main :: proc() {
}
}
Operand_Encoding :: type_of(gen.Encoding{}.enc[0])
Asm_Operand_Kind :: enum u8 {
Invalid,
Register,
Memory,
Register_Or_Memory,
Immediate,
Label,
}
@(rodata)
operand_encoding_to_kind := [Operand_Encoding]Asm_Operand_Kind{
.NONE = .Invalid,
.MR = .Register_Or_Memory,
.REG = .Register,
.VVVV = .Register,
.OP_R = .Register,
.IS4 = .Register,
.AAA = .Register,
.IMPL = .Register,
.IB = .Immediate,
.IW = .Immediate,
.ID = .Immediate,
.IQ = .Immediate,
}
Prefix :: enum u8 {
INVALID,
ES,

10
src/asm_tables.cpp Normal file
View File

@@ -0,0 +1,10 @@
enum AsmOperandKind : u8 {
AsmOperand_Invalid,
AsmOperand_Register,
AsmOperand_Memory,
AsmOperand_Register_Or_Memory,
AsmOperand_Immediate,
AsmOperand_Label,
};
#include "asm_tables_amd64.cpp"

View File

@@ -304,6 +304,39 @@ struct Asm_amd64 {
}
return 0;
}
AsmOperandKind kind_from_operand_type(OperandType type) {
switch (type) {
case OP_R8: case OP_R16: case OP_R32: case OP_R64:
case OP_SREG: case OP_CR: case OP_DR:
case OP_XMM: case OP_YMM: case OP_ZMM:
case OP_MM: case OP_K: case OP_STI:
case OP_AL_IMPL: case OP_AX_IMPL: case OP_EAX_IMPL: case OP_RAX_IMPL:
case OP_CL_IMPL: case OP_DX_IMPL:
case OP_ST0_IMPL: case OP_XMM0_IMPL:
return AsmOperand_Register;
case OP_RM8: case OP_RM16: case OP_RM32: case OP_RM64:
case OP_XMM_M32: case OP_XMM_M64: case OP_XMM_M128:
case OP_YMM_M256: case OP_ZMM_M512:
case OP_MM_M64:
case OP_K_M8: case OP_K_M16: case OP_K_M32: case OP_K_M64:
return AsmOperand_Register_Or_Memory;
case OP_M: case OP_M8: case OP_M16: case OP_M32: case OP_M64:
case OP_M80: case OP_M128: case OP_M256: case OP_M512:
case OP_MOFFS8: case OP_MOFFS16: case OP_MOFFS32: case OP_MOFFS64:
case OP_M16_16: case OP_M16_32: case OP_M16_64:
return AsmOperand_Memory;
case OP_IMM8: case OP_IMM16: case OP_IMM32: case OP_IMM64:
case OP_IMM8SX:
case OP_ONE_IMPL:
case OP_PTR16_16: case OP_PTR16_32: case OP_PTR16_64:
return AsmOperand_Immediate;
case OP_REL8: case OP_REL32:
return AsmOperand_Label;
case OP_NONE:
default:
return AsmOperand_Invalid;
}
}
};

View File

@@ -315,6 +315,7 @@ gb_internal CheckMnemomicResult check_mnemonic_name(AstAsmInstruction *instr, u1
}
auto p = g_asm_amd64.prefix_lookup(name);
if (p) {
if (mnemonic_) *mnemonic_ = cast(u16)p;
return CheckMnemomic_Prefix;
}
@@ -326,7 +327,38 @@ gb_internal CheckMnemomicResult check_mnemonic_name(AstAsmInstruction *instr, u1
return CheckMnemomic_Invalid;
}
gb_internal void check_mnemonic(CheckerContext *ctx, AstAsmInstruction *instr, u16 mnemonic, Slice<Operand> const &operands) {
gb_internal AsmOperandKind determine_asm_operand_kind(Operand const *operand) {
if (operand->mode == Addressing_Constant) {
return AsmOperand_Immediate;
}
Ast *expr = operand->expr;
switch (expr->kind) {
case_ast_node(label, AsmLabelDecl, expr);
return AsmOperand_Label;
case_end;
case_ast_node(reg, AsmRegister, expr);
return AsmOperand_Register;
case_end;
case_ast_node(reg, AsmMemoryOperand, expr);
return AsmOperand_Memory;
case_end;
case_ast_node(ident, Ident, expr);
// TODO(bill): Is this correct?
if (expr->tav.mode == Addressing_Constant) {
return AsmOperand_Immediate;
}
Entity *e = entity_of_node(expr);
if (e != nullptr && e->kind == Entity_Variable && (e->flags & EntityFlag_PolyConst) != 0) {
return AsmOperand_Immediate;
}
return AsmOperand_Register;
case_end;
}
return AsmOperand_Invalid;
}
gb_internal void check_mnemonic(CheckerContext *ctx, AstAsmInstruction *instr, u16 mnemonic, Slice<Operand> const &operands, u8 previous_prefix) {
GB_ASSERT(mnemonic > 0);
auto forms = g_asm_amd64.encoding_forms(mnemonic);
String name = g_asm_amd64.mnemonic_strings[mnemonic];
@@ -334,8 +366,20 @@ gb_internal void check_mnemonic(CheckerContext *ctx, AstAsmInstruction *instr, u
int min_count = I32_MAX;
int max_count = -1;
bool ok = false;
for (auto form : forms) {
int explicit_count = cast(int)form.explicit_count();
min_count = gb_min(min_count, explicit_count);
max_count = gb_max(min_count, explicit_count);
}
min_count = gb_max(min_count, 0);
max_count = gb_max(max_count, 0);
isize valid_form_index = -1;
auto valid_spots = slice_make<bool>(heap_allocator(), max_count);
defer (slice_free(&valid_spots, heap_allocator()));
bool ok = true;
for (auto form : forms) {
int explicit_count = cast(int)form.explicit_count();
min_count = gb_min(min_count, explicit_count);
@@ -343,7 +387,29 @@ gb_internal void check_mnemonic(CheckerContext *ctx, AstAsmInstruction *instr, u
if (operands.count != explicit_count) {
continue;
}
ok = true; // pretend for the time being
ok = true;
for_array(i, operands) {
auto type = form.ops[i];
Operand const *operand = &operands[i];
AsmOperandKind dst_kind = g_asm_amd64.kind_from_operand_type(type);
AsmOperandKind src_kind = determine_asm_operand_kind(operand);
if (dst_kind == src_kind) {
valid_spots[i] = true;
continue;
}
if (dst_kind == AsmOperand_Register_Or_Memory &&
(src_kind == AsmOperand_Register || src_kind == AsmOperand_Memory)) {
valid_spots[i] = true;
continue;
}
ok = false;
break;
}
if (ok) {
// the result has been found to be correct
break;
}
}
if (operands.count < min_count || operands.count > max_count) {
@@ -355,10 +421,21 @@ gb_internal void check_mnemonic(CheckerContext *ctx, AstAsmInstruction *instr, u
return;
}
if (ok) {
if (valid_form_index >= 0 && previous_prefix > 0) {
// TODO(bill): validate the prefix for the selected form
}
return;
}
error(instr->name, "The operands to '%.*s' matched non of the expected encoding forms", LIT(name));
{
error(instr->name, "The operands to '%.*s' matched non of the expected encoding forms", LIT(name));
for_array(i, valid_spots) {
if (!valid_spots[i] && i < operands.count) {
error(operands[i].expr, "Invalid operand kind for the asm instruction '%.*s'", LIT(name));
}
}
}
}
@@ -657,6 +734,8 @@ gb_internal void check_asm_template(CheckerContext *ctx, Entity *entity, DeclInf
defer (array_free(&operands));
for (Ast *instruction_ : at->instructions) {
u8 previous_prefix = 0;
switch (instruction_->kind) {
case_ast_node(instr, AsmInstruction, instruction_);
GB_ASSERT(instr->name->kind == Ast_Ident);
@@ -676,8 +755,9 @@ gb_internal void check_asm_template(CheckerContext *ctx, Entity *entity, DeclInf
if (instr->operands.count != 0) {
error(instr->name, "A prefix must not have any operands, and be separate from the instruction it is prefixing");
}
previous_prefix = cast(u8)mnemonic;
} else if (res == CheckMnemomic_Mnemonic) {
check_mnemonic(ctx, instr, mnemonic, slice_from_array(operands));
check_mnemonic(ctx, instr, mnemonic, slice_from_array(operands), previous_prefix);
}
case_end;

View File

@@ -68,7 +68,7 @@ gb_global Timings global_timings = {0};
#include "parser.hpp"
#include "checker.hpp"
#include "asm_tables_amd64.cpp"
#include "asm_tables.cpp"
#include "parser.cpp"
#include "checker.cpp"