py/emitinlinerv32: Refactor opcode arguments validation.

This commit simplifies the way arguments are validated when processing
RV32 inline assembler opcodes.

Opcode arguments were handled in two separate passes, one that performed
a pure validation (with an early rejection in case of errors), and
another that converted the parse node into a serialised value but
without any error checking.

Considering that the validation pass effectively performed the parse
node conversion and then discarded its result once validated, it is
preferable to hold onto the serialised result to reuse it later at
opcode generation time.

With these changes, those two passes are merged into one single
operation when applicable (basically any opcode that doesn't use an
integer offset), removing a fair amount of duplicate code.  The size
savings should be around half a kilobyte, with no other changes in the
assembler's behaviour.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit is contained in:
Alessandro Gatti
2025-10-25 18:39:08 +02:00
committed by Damien George
parent 27b7bf36a3
commit 3da529554d

View File

@@ -196,7 +196,6 @@ typedef enum {
CALL_R, // Opcode Register CALL_R, // Opcode Register
CALL_RL, // Opcode Register, Label CALL_RL, // Opcode Register, Label
CALL_N, // Opcode CALL_N, // Opcode
CALL_I, // Opcode Immediate
CALL_RII, // Opcode Register, Register, Immediate CALL_RII, // Opcode Register, Register, Immediate
CALL_RIR, // Opcode Register, Immediate(Register) CALL_RIR, // Opcode Register, Immediate(Register)
CALL_COUNT CALL_COUNT
@@ -210,7 +209,6 @@ typedef enum {
#define U (1 << 2) // Unsigned immediate #define U (1 << 2) // Unsigned immediate
#define Z (1 << 3) // Non-zero #define Z (1 << 3) // Non-zero
typedef void (*call_l_t)(asm_rv32_t *state, mp_uint_t label_index);
typedef void (*call_ri_t)(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate); typedef void (*call_ri_t)(asm_rv32_t *state, mp_uint_t rd, mp_int_t immediate);
typedef void (*call_rri_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_int_t immediate); typedef void (*call_rri_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs1, mp_int_t immediate);
typedef void (*call_rii_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t immediate1, mp_int_t immediate2); typedef void (*call_rii_t)(asm_rv32_t *state, mp_uint_t rd, mp_uint_t immediate1, mp_int_t immediate2);
@@ -439,8 +437,7 @@ static bool validate_integer(mp_uint_t value, mp_uint_t mask, mp_uint_t flags) {
#define ET_WRONG_ARGUMENTS_COUNT MP_ERROR_TEXT("opcode '%q': expecting %d arguments") #define ET_WRONG_ARGUMENTS_COUNT MP_ERROR_TEXT("opcode '%q': expecting %d arguments")
#define ET_OUT_OF_RANGE MP_ERROR_TEXT("opcode '%q' argument %d: out of range") #define ET_OUT_OF_RANGE MP_ERROR_TEXT("opcode '%q' argument %d: out of range")
static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr, static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index, mp_uint_t *serialised) {
const opcode_t *opcode, mp_parse_node_t node, mp_uint_t node_index) {
assert((node_index < 3) && "Invalid argument node number."); assert((node_index < 3) && "Invalid argument node number.");
uint32_t kind = 0; uint32_t kind = 0;
@@ -470,17 +467,19 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
break; break;
} }
mp_uint_t serialised_value = 0;
switch (kind & 0x03) { switch (kind & 0x03) {
case N: case N:
assert(mask == OPCODE_MASKS[MASK_NOT_USED] && "Invalid mask index for missing operand."); assert(mask == OPCODE_MASKS[MASK_NOT_USED] && "Invalid mask index for missing operand.");
return true; break;
case R: { case R: {
mp_uint_t register_index; mp_uint_t register_index;
if (!parse_register_node(node, &register_index, false)) { if (!parse_register_node(node, &register_index, false)) {
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_register)); ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_register));
return false; return false;
} }
@@ -488,11 +487,11 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
MP_ERROR_TEXT("opcode '%q' argument %d: unknown register"), MP_ERROR_TEXT("opcode '%q' argument %d: unknown register"),
opcode_qstr, node_index + 1)); opcode->qstring, node_index + 1));
return false; return false;
} }
return true; serialised_value = (kind & C) ? RV32_MAP_IN_C_REGISTER_WINDOW(register_index) : register_index;
} }
break; break;
@@ -501,7 +500,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
if (!mp_parse_node_get_int_maybe(node, &object)) { if (!mp_parse_node_get_int_maybe(node, &object)) {
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_integer)); ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_integer));
return false; return false;
} }
@@ -520,7 +519,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
goto zero_immediate; goto zero_immediate;
} }
return true; serialised_value = immediate;
} }
break; break;
@@ -528,7 +527,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
if (!MP_PARSE_NODE_IS_ID(node)) { if (!MP_PARSE_NODE_IS_ID(node)) {
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
ET_WRONG_ARGUMENT_KIND, opcode_qstr, node_index + 1, MP_QSTR_label)); ET_WRONG_ARGUMENT_KIND, opcode->qstring, node_index + 1, MP_QSTR_label));
return false; return false;
} }
@@ -538,7 +537,7 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
MP_ERROR_TEXT("opcode '%q' argument %d: undefined label '%q'"), MP_ERROR_TEXT("opcode '%q' argument %d: undefined label '%q'"),
opcode_qstr, node_index + 1, qstring)); opcode->qstring, node_index + 1, qstring));
return false; return false;
} }
@@ -552,27 +551,33 @@ static bool validate_argument(emit_inline_asm_t *emit, qstr opcode_qstr,
goto out_of_range; goto out_of_range;
} }
} }
return true;
serialised_value = displacement;
} }
break; break;
default: default:
assert(!"Unknown argument kind"); assert(!"Unknown argument kind");
MP_UNREACHABLE;
break; break;
} }
return false; if (serialised != NULL) {
*serialised = serialised_value;
}
return true;
out_of_range: out_of_range:
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode_qstr, node_index + 1)); mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, ET_OUT_OF_RANGE, opcode->qstring, node_index + 1));
return false; return false;
zero_immediate: zero_immediate:
emit_inline_rv32_error_exc(emit, emit_inline_rv32_error_exc(emit,
mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError,
MP_ERROR_TEXT("opcode '%q' argument %d: must not be zero"), MP_ERROR_TEXT("opcode '%q' argument %d: must not be zero"),
opcode_qstr, node_index + 1)); opcode->qstring, node_index + 1));
return false; return false;
} }
@@ -618,14 +623,14 @@ static bool parse_register_offset_node(emit_inline_asm_t *emit, qstr opcode_qstr
return false; return false;
} }
} else { } else {
if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 1)) { if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 1, NULL)) {
return false; return false;
} }
} }
*offset_node = node_struct->nodes[0]; *offset_node = node_struct->nodes[0];
node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1]; node_struct = (mp_parse_node_struct_t *)node_struct->nodes[1];
if (!validate_argument(emit, opcode_qstr, opcode_data, node_struct->nodes[0], 2)) { if (!serialise_argument(emit, opcode_data, node_struct->nodes[0], 2, NULL)) {
return false; return false;
} }
*register_node = node_struct->nodes[0]; *register_node = node_struct->nodes[0];
@@ -638,115 +643,58 @@ invalid_structure:
return false; return false;
} }
static void handle_opcode(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *arguments) { static void handle_opcode(emit_inline_asm_t *emit, const opcode_t *opcode_data, mp_uint_t *arguments) {
mp_uint_t rd = 0;
mp_uint_t rs1 = 0;
mp_uint_t rs2 = 0;
switch (opcode_data->calling_convention) { switch (opcode_data->calling_convention) {
case CALL_RRR: { case CALL_RRR:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_rrr_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]);
parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C);
parse_register_node(arguments[2], &rs2, opcode_data->argument3_kind & C);
((call_rrr_t)opcode_data->emitter)(&emit->as, rd, rs1, rs2);
break; break;
}
case CALL_RR: { case CALL_RR:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_rr_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1]);
parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C);
((call_rr_t)opcode_data->emitter)(&emit->as, rd, rs1);
break; break;
}
case CALL_RRI: { case CALL_RRI:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_rri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]);
parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C);
mp_obj_t object;
mp_parse_node_get_int_maybe(arguments[2], &object);
mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift;
((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, immediate);
break; break;
}
case CALL_RI: { case CALL_RI:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_ri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1]);
mp_obj_t object;
mp_parse_node_get_int_maybe(arguments[1], &object);
mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift;
((call_ri_t)opcode_data->emitter)(&emit->as, rd, immediate);
break; break;
}
case CALL_R: { case CALL_R:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_r_t)opcode_data->emitter)(&emit->as, arguments[0]);
((call_r_t)opcode_data->emitter)(&emit->as, rd);
break; break;
}
case CALL_RRL: { case CALL_RRL:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_rri_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], (ptrdiff_t)arguments[2]);
parse_register_node(arguments[1], &rs1, opcode_data->argument2_kind & C);
qstr qstring;
mp_uint_t label_index = lookup_label(emit, arguments[2], &qstring);
ptrdiff_t displacement = label_code_offset(emit, label_index);
((call_rri_t)opcode_data->emitter)(&emit->as, rd, rs1, displacement);
break; break;
}
case CALL_RL: { case CALL_RL:
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C); ((call_ri_t)opcode_data->emitter)(&emit->as, arguments[0], (ptrdiff_t)arguments[1]);
qstr qstring;
mp_uint_t label_index = lookup_label(emit, arguments[1], &qstring);
ptrdiff_t displacement = label_code_offset(emit, label_index);
((call_ri_t)opcode_data->emitter)(&emit->as, rd, displacement);
break; break;
}
case CALL_L: { case CALL_L:
qstr qstring; ((call_i_t)opcode_data->emitter)(&emit->as, (ptrdiff_t)arguments[0]);
mp_uint_t label_index = lookup_label(emit, arguments[0], &qstring);
ptrdiff_t displacement = label_code_offset(emit, label_index);
((call_i_t)opcode_data->emitter)(&emit->as, displacement);
break; break;
}
case CALL_N: case CALL_N:
((call_n_t)opcode_data->emitter)(&emit->as); ((call_n_t)opcode_data->emitter)(&emit->as);
break; break;
case CALL_I: { case CALL_RII:
mp_obj_t object; ((call_rii_t)opcode_data->emitter)(&emit->as, arguments[0], arguments[1], arguments[2]);
mp_parse_node_get_int_maybe(arguments[0], &object);
mp_uint_t immediate = mp_obj_get_int_truncated(object) << opcode_data->argument1_shift;
((call_i_t)opcode_data->emitter)(&emit->as, immediate);
break;
}
case CALL_RII: {
parse_register_node(arguments[0], &rd, opcode_data->argument1_kind & C);
mp_obj_t object;
mp_parse_node_get_int_maybe(arguments[1], &object);
mp_uint_t immediate1 = mp_obj_get_int_truncated(object) << opcode_data->argument2_shift;
mp_parse_node_get_int_maybe(arguments[2], &object);
mp_uint_t immediate2 = mp_obj_get_int_truncated(object) << opcode_data->argument3_shift;
((call_rii_t)opcode_data->emitter)(&emit->as, rd, immediate1, immediate2);
break;
}
case CALL_RIR:
assert(!"Should not get here.");
break; break;
default: default:
assert(!"Unhandled call convention."); assert(!"Unhandled call convention.");
MP_UNREACHABLE;
break; break;
} }
} }
static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *argument_nodes) { static bool handle_load_store_opcode_with_offset(emit_inline_asm_t *emit, qstr opcode, const opcode_t *opcode_data, mp_parse_node_t *argument_nodes) {
mp_parse_node_t nodes[3] = {0}; mp_parse_node_t nodes[3] = {0};
if (!validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { if (!serialise_argument(emit, opcode_data, argument_nodes[0], 0, NULL)) {
return false; return false;
} }
nodes[0] = argument_nodes[0]; nodes[0] = argument_nodes[0];
@@ -806,16 +754,17 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin
ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->arguments_count)); ET_WRONG_ARGUMENTS_COUNT, opcode, opcode_data->arguments_count));
return; return;
} }
if (opcode_data->arguments_count >= 1 && !validate_argument(emit, opcode, opcode_data, argument_nodes[0], 0)) { mp_uint_t serialised_arguments[3] = { 0 };
if (opcode_data->arguments_count >= 1 && !serialise_argument(emit, opcode_data, argument_nodes[0], 0, &serialised_arguments[0])) {
return; return;
} }
if (opcode_data->arguments_count >= 2 && !validate_argument(emit, opcode, opcode_data, argument_nodes[1], 1)) { if (opcode_data->arguments_count >= 2 && !serialise_argument(emit, opcode_data, argument_nodes[1], 1, &serialised_arguments[1])) {
return; return;
} }
if (opcode_data->arguments_count >= 3 && !validate_argument(emit, opcode, opcode_data, argument_nodes[2], 2)) { if (opcode_data->arguments_count >= 3 && !serialise_argument(emit, opcode_data, argument_nodes[2], 2, &serialised_arguments[2])) {
return; return;
} }
handle_opcode(emit, opcode, opcode_data, argument_nodes); handle_opcode(emit, opcode_data, serialised_arguments);
return; return;
} }