Skip to content

Commit

Permalink
[interp] Optimize enum Hasflag (mono/mono#14173)
Browse files Browse the repository at this point in the history
* [interp] Optimize enum.HasFlag for csc pattern

Avoid boxing and slow call. Makes it 100x faster.

* [interp] Optimize enum.HasFlag for mcs pattern

* [interp] Transform ldc.i4 + conv.i8 into ldc.i8

Simplifies code, making enum.HasFlag optimization work for long enums.


Commit migrated from mono/mono@6d9c4c2
  • Loading branch information
BrzVlad authored Apr 27, 2019
1 parent 26cd59d commit 638a4c6
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 23 deletions.
11 changes: 11 additions & 0 deletions src/mono/mono/mini/interp/interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -6217,6 +6217,17 @@ interp_exec_method_full (InterpFrame *frame, ThreadContext *context, FrameClause
MINT_IN_CASE(MINT_TAN) MATH_UNOP(tan); MINT_IN_BREAK;
MINT_IN_CASE(MINT_TANH) MATH_UNOP(tanh); MINT_IN_BREAK;

MINT_IN_CASE(MINT_INTRINS_ENUM_HASFLAG) {
MonoClass *klass = (MonoClass*)imethod->data_items[* (guint16 *)(ip + 1)];
guint64 a_val = 0, b_val = 0;

stackval_to_data (m_class_get_byval_arg (klass), &sp [-2], &a_val, FALSE);
stackval_to_data (m_class_get_byval_arg (klass), &sp [-1], &b_val, FALSE);
sp--;
sp [-1].data.i = (a_val & b_val) == b_val;
ip += 2;
MINT_IN_BREAK;
}
MINT_IN_CASE(MINT_INTRINS_GET_HASHCODE) {
sp [-1].data.i = mono_object_hash_internal (sp [-1].data.o);
ip++;
Expand Down
5 changes: 3 additions & 2 deletions src/mono/mono/mini/interp/mintops.def
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,9 @@ OPDEF(MINT_TANH, "tanh", 1, MintOpNoArgs)
*/
OPDEF(MINT_PROF_ENTER, "prof_enter", 1, MintOpNoArgs)

OPDEF(MINT_INTRINS_GET_HASHCODE, "get.hashcode", 1, MintOpNoArgs)
OPDEF(MINT_INTRINS_GET_TYPE, "get.type", 1, MintOpNoArgs)
OPDEF(MINT_INTRINS_ENUM_HASFLAG, "intrins_enum_hasflag", 2, MintOpClassToken)
OPDEF(MINT_INTRINS_GET_HASHCODE, "intrins_get_hashcode", 1, MintOpNoArgs)
OPDEF(MINT_INTRINS_GET_TYPE, "intrins_get_type", 1, MintOpNoArgs)
OPDEF(MINT_INTRINS_BYREFERENCE_CTOR, "intrins_byreference_ctor", 2, MintOpClassToken)
OPDEF(MINT_INTRINS_BYREFERENCE_GET_VALUE, "intrins_byreference_get_value", 1, MintOpNoArgs)
OPDEF(MINT_INTRINS_UNSAFE_ADD_BYTE_OFFSET, "intrins_unsafe_add_byte_offset", 1, MintOpNoArgs)
Expand Down
190 changes: 169 additions & 21 deletions src/mono/mono/mini/interp/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,8 @@ static const MagicIntrinsic int_cmpop[] = {

static gboolean generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, MonoGenericContext *generic_context, MonoError *error);

// This version need to be used with switch opcode, which doesn't have constant length
static InterpInst*
interp_add_ins_explicit (TransformData *td, guint16 opcode, int len)
interp_new_ins (TransformData *td, guint16 opcode, int len)
{
InterpInst *new_inst;
g_assert (len);
Expand All @@ -299,6 +298,14 @@ interp_add_ins_explicit (TransformData *td, guint16 opcode, int len)
new_inst->il_offset = td->in_start - td->il_code;
else
new_inst->il_offset = -1;
return new_inst;
}

// This version need to be used with switch opcode, which doesn't have constant length
static InterpInst*
interp_add_ins_explicit (TransformData *td, guint16 opcode, int len)
{
InterpInst *new_inst = interp_new_ins (td, opcode, len);
new_inst->prev = td->last_ins;
if (td->last_ins)
td->last_ins->next = new_inst;
Expand All @@ -314,6 +321,36 @@ interp_add_ins (TransformData *td, guint16 opcode)
return interp_add_ins_explicit (td, opcode, mono_interp_oplen [opcode]);
}

// Inserts a new instruction inside the instruction stream
static InterpInst*
interp_insert_ins (TransformData *td, InterpInst *prev_ins, guint16 opcode)
{
InterpInst *new_inst = interp_new_ins (td, opcode, mono_interp_oplen [opcode]);
g_assert (prev_ins && prev_ins->next);

new_inst->prev = prev_ins;
new_inst->next = prev_ins->next;
prev_ins->next = new_inst;
new_inst->next->prev = new_inst;

return new_inst;
}

static void
interp_remove_ins (TransformData *td, InterpInst *ins)
{
if (ins->prev) {
ins->prev->next = ins->next;
} else {
td->first_ins = ins->next;
}
if (ins->next) {
ins->next->prev = ins->prev;
} else {
td->last_ins = ins->prev;
}
}

#define CHECK_STACK(td, n) \
do { \
int stack_size = (td)->sp - (td)->stack; \
Expand Down Expand Up @@ -923,9 +960,71 @@ emit_store_value_as_local (TransformData *td, MonoType *src)
PUSH_TYPE (td, STACK_TYPE_VT, NULL);
}

// Returns whether we can optimize away the instructions starting at start.
// If any instructions are part of a new basic block, we can't remove them.
static gboolean
interp_is_bb_start (TransformData *td, InterpInst *start, InterpInst *end)
{
InterpInst *ins = start;
while (ins != end) {
if (ins->il_offset != -1) {
if (td->is_bb_start [ins->il_offset])
return TRUE;
}
ins = ins->next;
}
return FALSE;
}

static gboolean
interp_ins_is_ldc (InterpInst *ins)
{
return ins->opcode >= MINT_LDC_I4_M1 && ins->opcode <= MINT_LDC_I8;
}

static gint32
interp_ldc_i4_get_const (InterpInst *ins)
{
switch (ins->opcode) {
case MINT_LDC_I4_M1: return -1;
case MINT_LDC_I4_0: return 0;
case MINT_LDC_I4_1: return 1;
case MINT_LDC_I4_2: return 2;
case MINT_LDC_I4_3: return 3;
case MINT_LDC_I4_4: return 4;
case MINT_LDC_I4_5: return 5;
case MINT_LDC_I4_6: return 6;
case MINT_LDC_I4_7: return 7;
case MINT_LDC_I4_8: return 8;
case MINT_LDC_I4_S: return (gint32)(gint8)ins->data [0];
case MINT_LDC_I4: return READ32 (&ins->data [0]);
default:
g_assert_not_reached ();
}
}

static int
interp_get_ldind_for_mt (int mt)
{
switch (mt) {
case MINT_TYPE_I1: return MINT_LDIND_I1_CHECK;
case MINT_TYPE_U1: return MINT_LDIND_U1_CHECK;
case MINT_TYPE_I2: return MINT_LDIND_I2_CHECK;
case MINT_TYPE_U2: return MINT_LDIND_U2_CHECK;
case MINT_TYPE_I4: return MINT_LDIND_I4_CHECK;
case MINT_TYPE_I8: return MINT_LDIND_I8_CHECK;
case MINT_TYPE_R4: return MINT_LDIND_R4_CHECK;
case MINT_TYPE_R8: return MINT_LDIND_R8_CHECK;
case MINT_TYPE_O: return MINT_LDIND_REF;
default:
g_assert_not_reached ();
}
return -1;
}

/* Return TRUE if call transformation is finished */
static gboolean
interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoMethodSignature *csignature, gboolean readonly, int *op)
interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClass *constrained_class, MonoMethodSignature *csignature, gboolean readonly, int *op)
{
const char *tm = target_method->name;
int i;
Expand Down Expand Up @@ -1294,6 +1393,50 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoMeth
else if (!strcmp (tm, "GetType"))
*op = MINT_INTRINS_GET_TYPE;
#endif
} else if (in_corlib && target_method->klass == mono_defaults.enum_class && !strcmp (tm, "HasFlag")) {
gboolean intrinsify = FALSE;
MonoClass *base_klass = NULL;
if (td->last_ins && td->last_ins->opcode == MINT_BOX &&
td->last_ins->prev && interp_ins_is_ldc (td->last_ins->prev) &&
td->last_ins->prev->prev && td->last_ins->prev->prev->opcode == MINT_BOX &&
td->sp [-2].klass == td->sp [-1].klass &&
!interp_is_bb_start (td, td->last_ins->prev->prev, NULL) &&
!td->is_bb_start [td->in_start - td->il_code]) {
// csc pattern : box, ldc, box, call HasFlag
g_assert (m_class_is_enumtype (td->sp [-2].klass));
MonoType *base_type = mono_type_get_underlying_type (m_class_get_byval_arg (td->sp [-2].klass));
base_klass = mono_class_from_mono_type_internal (base_type);

// Remove the boxing of valuetypes
interp_remove_ins (td, td->last_ins->prev->prev);
interp_remove_ins (td, td->last_ins);

intrinsify = TRUE;
} else if (td->last_ins && td->last_ins->opcode == MINT_BOX &&
td->last_ins->prev && interp_ins_is_ldc (td->last_ins->prev) &&
constrained_class && td->sp [-1].klass == constrained_class &&
!interp_is_bb_start (td, td->last_ins->prev, NULL) &&
!td->is_bb_start [td->in_start - td->il_code]) {
// mcs pattern : ldc, box, constrained Enum, call HasFlag
g_assert (m_class_is_enumtype (constrained_class));
MonoType *base_type = mono_type_get_underlying_type (m_class_get_byval_arg (constrained_class));
base_klass = mono_class_from_mono_type_internal (base_type);
int mt = mint_type (m_class_get_byval_arg (base_klass));

// Remove boxing and load the value of this
interp_remove_ins (td, td->last_ins);
interp_insert_ins (td, td->last_ins->prev, interp_get_ldind_for_mt (mt));

intrinsify = TRUE;
}
if (intrinsify) {
interp_add_ins (td, MINT_INTRINS_ENUM_HASFLAG);
td->last_ins->data [0] = get_data_item_index (td, base_klass);
td->sp -= 2;
PUSH_SIMPLE_TYPE (td, STACK_TYPE_I4);
td->ip += 5;
return TRUE;
}
}

return FALSE;
Expand Down Expand Up @@ -1692,7 +1835,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
}

/* Intrinsics */
if (target_method && interp_handle_intrinsics (td, target_method, csignature, readonly, &op))
if (target_method && interp_handle_intrinsics (td, target_method, constrained_class, csignature, readonly, &op))
return TRUE;

if (constrained_class) {
Expand Down Expand Up @@ -1721,7 +1864,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
g_print (" : %s::%s. %s (%p)\n", target_method->klass->name, target_method->name, mono_signature_full_name (target_method->signature), target_method);
#endif
/* Intrinsics: Try again, it could be that `mono_get_method_constrained_with_method` resolves to a method that we can substitute */
if (target_method && interp_handle_intrinsics (td, target_method, csignature, readonly, &op))
if (target_method && interp_handle_intrinsics (td, target_method, constrained_class, csignature, readonly, &op))
return TRUE;

return_val_if_nok (error, FALSE);
Expand Down Expand Up @@ -2496,19 +2639,7 @@ interp_emit_ldobj (TransformData *td, MonoClass *klass)
WRITE32_INS (td->last_ins, 0, &size);
PUSH_VT (td, size);
} else {
int opcode;
switch (mt) {
case MINT_TYPE_I1: opcode = MINT_LDIND_I1_CHECK; break;
case MINT_TYPE_U1: opcode = MINT_LDIND_U1_CHECK; break;
case MINT_TYPE_I2: opcode = MINT_LDIND_I2_CHECK; break;
case MINT_TYPE_U2: opcode = MINT_LDIND_U2_CHECK; break;
case MINT_TYPE_I4: opcode = MINT_LDIND_I4_CHECK; break;
case MINT_TYPE_I8: opcode = MINT_LDIND_I8_CHECK; break;
case MINT_TYPE_R4: opcode = MINT_LDIND_R4_CHECK; break;
case MINT_TYPE_R8: opcode = MINT_LDIND_R8_CHECK; break;
case MINT_TYPE_O: opcode = MINT_LDIND_REF; break;
default: g_assert_not_reached (); break;
}
int opcode = interp_get_ldind_for_mt (mt);
interp_add_ins (td, opcode);
}

Expand Down Expand Up @@ -3638,9 +3769,18 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header,
case STACK_TYPE_R8:
interp_add_ins (td, MINT_CONV_I8_R8);
break;
case STACK_TYPE_I4:
interp_add_ins (td, MINT_CONV_I8_I4);
case STACK_TYPE_I4: {
if (interp_ins_is_ldc (td->last_ins) && !td->is_bb_start [in_offset]) {
gint64 ct = interp_ldc_i4_get_const (td->last_ins);
interp_remove_ins (td, td->last_ins);

interp_add_ins (td, MINT_LDC_I8);
WRITE64_INS (td->last_ins, 0, &ct);
} else {
interp_add_ins (td, MINT_CONV_I8_I4);
}
break;
}
case STACK_TYPE_I8:
break;
case STACK_TYPE_MP:
Expand Down Expand Up @@ -3699,7 +3839,15 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header,
CHECK_STACK (td, 1);
switch (td->sp [-1].type) {
case STACK_TYPE_I4:
interp_add_ins (td, MINT_CONV_U8_I4);
if (interp_ins_is_ldc (td->last_ins) && !td->is_bb_start [in_offset]) {
gint64 ct = (guint32)interp_ldc_i4_get_const (td->last_ins);
interp_remove_ins (td, td->last_ins);

interp_add_ins (td, MINT_LDC_I8);
WRITE64_INS (td->last_ins, 0, &ct);
} else {
interp_add_ins (td, MINT_CONV_U8_I4);
}
break;
case STACK_TYPE_I8:
break;
Expand Down

0 comments on commit 638a4c6

Please sign in to comment.