Skip to content

Commit 9bbca54

Browse files
committed
Update mono to support Unsafe.BitCast
1 parent 98e7570 commit 9bbca54

File tree

6 files changed

+330
-11
lines changed

6 files changed

+330
-11
lines changed

src/mono/browser/runtime/jiterpreter-tables.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export const unopTable: { [opcode: number]: OpRec3 | undefined } = {
4545
[MintOpcode.MINT_NEG_R4]: [WasmOpcode.f32_neg, WasmOpcode.f32_load, WasmOpcode.f32_store],
4646
[MintOpcode.MINT_NEG_R8]: [WasmOpcode.f64_neg, WasmOpcode.f64_load, WasmOpcode.f64_store],
4747

48+
[MintOpcode.MINT_BITCAST_I4_R4]: [WasmOpcode.i32_reinterpret_f32, WasmOpcode.f32_load, WasmOpcode.i32_store],
49+
[MintOpcode.MINT_BITCAST_I8_R8]: [WasmOpcode.i64_reinterpret_f64, WasmOpcode.f64_load, WasmOpcode.i64_store],
50+
[MintOpcode.MINT_BITCAST_R4_I4]: [WasmOpcode.f32_reinterpret_i32, WasmOpcode.i32_load, WasmOpcode.f32_store],
51+
[MintOpcode.MINT_BITCAST_R8_I8]: [WasmOpcode.f64_reinterpret_i64, WasmOpcode.i64_load, WasmOpcode.f64_store],
52+
4853
[MintOpcode.MINT_CONV_R4_I4]: [WasmOpcode.f32_convert_s_i32, WasmOpcode.i32_load, WasmOpcode.f32_store],
4954
[MintOpcode.MINT_CONV_R8_I4]: [WasmOpcode.f64_convert_s_i32, WasmOpcode.i32_load, WasmOpcode.f64_store],
5055
[MintOpcode.MINT_CONV_R_UN_I4]: [WasmOpcode.f64_convert_u_i32, WasmOpcode.i32_load, WasmOpcode.f64_store],

src/mono/mono/mini/interp/interp.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5556,6 +5556,22 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
55565556
LOCAL_VAR (ip [1], gint64) = ~ LOCAL_VAR (ip [2], gint64);
55575557
ip += 3;
55585558
MINT_IN_BREAK;
5559+
MINT_IN_CASE(MINT_BITCAST_I4_R4)
5560+
memcpy (&LOCAL_VAR (ip [1], gint32), &LOCAL_VAR (ip [2], float), sizeof (float));
5561+
ip += 3;
5562+
MINT_IN_BREAK;
5563+
MINT_IN_CASE(MINT_BITCAST_I8_R8)
5564+
memcpy (&LOCAL_VAR (ip [1], gint64), &LOCAL_VAR (ip [2], double), sizeof (double));
5565+
ip += 3;
5566+
MINT_IN_BREAK;
5567+
MINT_IN_CASE(MINT_BITCAST_R4_I4)
5568+
memcpy (&LOCAL_VAR (ip [1], float), &LOCAL_VAR (ip [2], gint32), sizeof (gint32));
5569+
ip += 3;
5570+
MINT_IN_BREAK;
5571+
MINT_IN_CASE(MINT_BITCAST_R8_I8)
5572+
memcpy (&LOCAL_VAR (ip [1], double), &LOCAL_VAR (ip [2], gint64), sizeof (gint64));
5573+
ip += 3;
5574+
MINT_IN_BREAK;
55595575
MINT_IN_CASE(MINT_CONV_I1_I4)
55605576
// FIXME read casted var directly and remove redundant conv opcodes
55615577
LOCAL_VAR (ip [1], gint32) = (gint8)LOCAL_VAR (ip [2], gint32);

src/mono/mono/mini/interp/mintops.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ OPDEF(MINT_NEG_R8, "neg.r8", 3, 1, 1, MintOpNoArgs)
548548
OPDEF(MINT_NOT_I4, "not.i4", 3, 1, 1, MintOpNoArgs)
549549
OPDEF(MINT_NOT_I8, "not.i8", 3, 1, 1, MintOpNoArgs)
550550

551+
OPDEF(MINT_BITCAST_I4_R4, "bitcast.i4.r4", 3, 1, 1, MintOpNoArgs)
552+
OPDEF(MINT_BITCAST_I8_R8, "bitcast.i8.r8", 3, 1, 1, MintOpNoArgs)
553+
OPDEF(MINT_BITCAST_R4_I4, "bitcast.r4.i4", 3, 1, 1, MintOpNoArgs)
554+
OPDEF(MINT_BITCAST_R8_I8, "bitcast.r8.i8", 3, 1, 1, MintOpNoArgs)
555+
551556
OPDEF(MINT_CONV_R_UN_I4, "conv.r.un.i4", 3, 1, 1, MintOpNoArgs)
552557
OPDEF(MINT_CONV_R_UN_I8, "conv.r.un.i8", 3, 1, 1, MintOpNoArgs)
553558

src/mono/mono/mini/interp/transform.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,6 +2138,16 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
21382138
return TRUE;
21392139
}
21402140
}
2141+
} else if (in_corlib && !strcmp (klass_name_space, "System") && (!strcmp (klass_name, "BitConverter"))) {
2142+
if (!strcmp (tm, "DoubleToInt64Bits") || !strcmp (tm, "DoubleToUInt64Bits")) {
2143+
*op = MINT_BITCAST_I8_R8;
2144+
} else if (!strcmp (tm, "Int32BitsToSingle") || !strcmp (tm, "UInt32BitsToSingle")) {
2145+
*op = MINT_BITCAST_R4_I4;
2146+
} else if (!strcmp (tm, "Int64BitsToDouble") || !strcmp (tm, "UInt64BitsToDouble")) {
2147+
*op = MINT_BITCAST_R8_I8;
2148+
} else if (!strcmp (tm, "SingleToInt32Bits") || !strcmp (tm, "SingleToUInt32Bits")) {
2149+
*op = MINT_BITCAST_I4_R4;
2150+
}
21412151
} else if (in_corlib && !strcmp (klass_name_space, "System.Runtime.CompilerServices") && !strcmp (klass_name, "Unsafe")) {
21422152
if (!strcmp (tm, "AddByteOffset"))
21432153
#if SIZEOF_VOID_P == 4
@@ -2155,6 +2165,121 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
21552165
return TRUE;
21562166
} else if (!strcmp (tm, "AreSame")) {
21572167
*op = MINT_CEQ_P;
2168+
} else if (!strcmp (tm, "BitCast")) {
2169+
MonoGenericContext *ctx = mono_method_get_context (target_method);
2170+
g_assert (ctx);
2171+
g_assert (ctx->method_inst);
2172+
g_assert (ctx->method_inst->type_argc == 2);
2173+
g_assert (csignature->param_count == 1);
2174+
2175+
// We explicitly do not handle gsharedvt as it is meant as a slow fallback strategy
2176+
// instead we fallback to the managed implementation which will do the right things
2177+
2178+
MonoType *tfrom = ctx->method_inst->type_argv [0];
2179+
if (mini_is_gsharedvt_variable_type (tfrom)) {
2180+
return FALSE;
2181+
}
2182+
2183+
MonoType *tto = ctx->method_inst->type_argv [1];
2184+
if (mini_is_gsharedvt_variable_type (tto)) {
2185+
return FALSE;
2186+
}
2187+
2188+
// The underlying API always throws for reference type inputs, so we
2189+
// fallback to the managed implementation to let that handling occur
2190+
2191+
MonoTypeEnum tfrom_type = tfrom->type;
2192+
if (MONO_TYPE_IS_REFERENCE (tfrom)) {
2193+
return FALSE;
2194+
}
2195+
2196+
MonoTypeEnum tto_type = tto->type;
2197+
if (MONO_TYPE_IS_REFERENCE (tto)) {
2198+
return FALSE;
2199+
}
2200+
2201+
// We also always throw for Nullable<T> inputs, so fallback to the
2202+
// managed implementation here as well.
2203+
2204+
MonoClass *tfrom_klass = mono_class_from_mono_type_internal (tfrom);
2205+
if (mono_class_is_nullable (tfrom_klass)) {
2206+
return FALSE;
2207+
}
2208+
2209+
MonoClass *tto_klass = mono_class_from_mono_type_internal (tto);
2210+
if (mono_class_is_nullable (tto_klass)) {
2211+
return FALSE;
2212+
}
2213+
2214+
// The same applies for when the type sizes do not match, as this will always throw
2215+
// and so its not an expected case and we can fallback to the managed implementation
2216+
2217+
int tfrom_align, tto_align;
2218+
gint32 size = mono_type_size (tfrom, &tfrom_align);
2219+
2220+
if (size != mono_type_size (tto, &tto_align)) {
2221+
return FALSE;
2222+
}
2223+
g_assert (size < G_MAXUINT16);
2224+
2225+
// We have several different move opcodes to handle the data depending on the
2226+
// source and target types, so detect and optimize the most common ones falling
2227+
// back to what is effectively `ReadUnaligned<TTo>(ref As<TFrom, byte>(ref source))`
2228+
// for anything that can't be special cased as potentially zero-cost move.
2229+
2230+
bool tfrom_is_primitive_or_enum = false;
2231+
if (m_class_is_primitive(tfrom_klass)) {
2232+
tfrom_is_primitive_or_enum = true;
2233+
} else if (m_class_is_enumtype(tfrom_klass)) {
2234+
tfrom_is_primitive_or_enum = true;
2235+
tfrom_type = mono_class_enum_basetype_internal(tfrom_klass)->type;
2236+
}
2237+
2238+
bool tto_is_primitive_or_enum = false;
2239+
if (m_class_is_primitive(tto_klass)) {
2240+
tto_is_primitive_or_enum = true;
2241+
} else if (m_class_is_enumtype(tto_klass)) {
2242+
tto_is_primitive_or_enum = true;
2243+
tto_type = mono_class_enum_basetype_internal(tto_klass)->type;
2244+
}
2245+
2246+
if (tfrom_is_primitive_or_enum && tto_is_primitive_or_enum) {
2247+
if (size == 1) {
2248+
// *op = MINT_MOV_1;
2249+
return FALSE;
2250+
} else if (size == 2) {
2251+
// *op = MINT_MOV_2;
2252+
return FALSE;
2253+
} else if (size == 4) {
2254+
if ((tfrom_type == MONO_TYPE_R4) && ((tto_type == MONO_TYPE_I4) || (tto_type == MONO_TYPE_U4))) {
2255+
*op = MINT_BITCAST_I4_R4;
2256+
} else if ((tto_type == MONO_TYPE_R4) && ((tfrom_type == MONO_TYPE_I4) || (tfrom_type == MONO_TYPE_U4))) {
2257+
*op = MINT_BITCAST_R4_I4;
2258+
} else {
2259+
*op = MINT_MOV_4;
2260+
}
2261+
} else if (size == 8) {
2262+
if ((tfrom_type == MONO_TYPE_R8) && ((tto_type == MONO_TYPE_I8) || (tto_type == MONO_TYPE_U8))) {
2263+
*op = MINT_BITCAST_I8_R8;
2264+
} else if ((tto_type == MONO_TYPE_R8) && ((tfrom_type == MONO_TYPE_I8) || (tfrom_type == MONO_TYPE_U8))) {
2265+
*op = MINT_BITCAST_R8_I8;
2266+
} else {
2267+
*op = MINT_MOV_8;
2268+
}
2269+
}
2270+
}
2271+
2272+
if (*op == -1) {
2273+
// FIXME: This isn't quite right
2274+
//
2275+
// interp_add_ins (td, MINT_MOV_VT);
2276+
// interp_ins_set_sreg (td->last_ins, td->sp [-1].var);
2277+
// push_type_vt (td, tto_klass, size);
2278+
// interp_ins_set_dreg (td->last_ins, td->sp [-1].var);
2279+
// td->last_ins->data [0] = GINT32_TO_UINT16 (size);
2280+
// td->ip++;
2281+
// return TRUE;
2282+
}
21582283
} else if (!strcmp (tm, "ByteOffset")) {
21592284
#if SIZEOF_VOID_P == 4
21602285
interp_add_ins (td, MINT_SUB_I4);

src/mono/mono/mini/intrinsics.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,40 @@ emit_span_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature
427427
return NULL;
428428
}
429429

430+
static MonoInst*
431+
emit_bitconverter_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args)
432+
{
433+
MonoInst *ins;
434+
435+
if (!strcmp (cmethod->name, "DoubleToInt64Bits") || !strcmp (cmethod->name, "DoubleToUInt64Bits")) {
436+
g_assert (fsig->param_count == 1);
437+
int dreg = mono_alloc_dreg (cfg, STACK_I8);
438+
EMIT_NEW_UNALU (cfg, ins, OP_MOVE_F_TO_I8, dreg, args [0]->dreg);
439+
ins->type = STACK_I8;
440+
return ins;
441+
} else if (!strcmp (cmethod->name, "Int32BitsToSingle") || !strcmp (cmethod->name, "UInt32BitsToSingle")) {
442+
g_assert (fsig->param_count == 1);
443+
int dreg = mono_alloc_dreg (cfg, STACK_R4);
444+
EMIT_NEW_UNALU (cfg, ins, OP_MOVE_I4_TO_F, dreg, args [0]->dreg);
445+
ins->type = STACK_R4;
446+
return ins;
447+
} else if (!strcmp (cmethod->name, "Int64BitsToDouble") || !strcmp (cmethod->name, "UInt64BitsToDouble")) {
448+
g_assert (fsig->param_count == 1);
449+
int dreg = mono_alloc_dreg (cfg, STACK_R8);
450+
EMIT_NEW_UNALU (cfg, ins, OP_MOVE_I8_TO_F, dreg, args [0]->dreg);
451+
ins->type = STACK_R8;
452+
return ins;
453+
} else if (!strcmp (cmethod->name, "SingleToInt32Bits") || !strcmp (cmethod->name, "SingleToUInt32Bits")) {
454+
g_assert (fsig->param_count == 1);
455+
int dreg = mono_alloc_dreg (cfg, STACK_I4);
456+
EMIT_NEW_UNALU (cfg, ins, OP_MOVE_F_TO_I4, dreg, args [0]->dreg);
457+
ins->type = STACK_I4;
458+
return ins;
459+
}
460+
461+
return NULL;
462+
}
463+
430464
static MonoInst*
431465
emit_unsafe_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args)
432466
{
@@ -488,6 +522,147 @@ emit_unsafe_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignatu
488522
EMIT_NEW_BIALU (cfg, ins, OP_COMPARE, -1, args [0]->dreg, args [1]->dreg);
489523
EMIT_NEW_UNALU (cfg, ins, OP_PCEQ, dreg, -1);
490524
return ins;
525+
} else if (!strcmp (cmethod->name, "BitCast")) {
526+
g_assert (ctx);
527+
g_assert (ctx->method_inst);
528+
g_assert (ctx->method_inst->type_argc == 2);
529+
g_assert (fsig->param_count == 1);
530+
531+
// We explicitly do not handle gsharedvt as it is meant as a slow fallback strategy
532+
// instead we fallback to the managed implementation which will do the right things
533+
534+
MonoType *tfrom = ctx->method_inst->type_argv [0];
535+
if (mini_is_gsharedvt_variable_type (tfrom)) {
536+
return NULL;
537+
}
538+
539+
MonoType *tto = ctx->method_inst->type_argv [1];
540+
if (mini_is_gsharedvt_variable_type (tto)) {
541+
return NULL;
542+
}
543+
544+
// The underlying API always throws for reference type inputs, so we
545+
// fallback to the managed implementation to let that handling occur
546+
547+
MonoTypeEnum tfrom_type = tfrom->type;
548+
if (MONO_TYPE_IS_REFERENCE (tfrom)) {
549+
return NULL;
550+
}
551+
552+
MonoTypeEnum tto_type = tto->type;
553+
if (MONO_TYPE_IS_REFERENCE (tto)) {
554+
return NULL;
555+
}
556+
557+
// We also always throw for Nullable<T> inputs, so fallback to the
558+
// managed implementation here as well.
559+
560+
MonoClass *tfrom_klass = mono_class_from_mono_type_internal (tfrom);
561+
if (mono_class_is_nullable (tfrom_klass)) {
562+
return NULL;
563+
}
564+
565+
MonoClass *tto_klass = mono_class_from_mono_type_internal (tto);
566+
if (mono_class_is_nullable (tto_klass)) {
567+
return NULL;
568+
}
569+
570+
// The same applies for when the type sizes do not match, as this will always throw
571+
// and so its not an expected case and we can fallback to the managed implementation
572+
573+
int tfrom_align, tto_align;
574+
gint32 size = mono_type_size (tfrom, &tfrom_align);
575+
576+
if (size != mono_type_size (tto, &tto_align)) {
577+
return FALSE;
578+
}
579+
g_assert (size < G_MAXUINT16);
580+
581+
// We have several different move opcodes to handle the data depending on the
582+
// source and target types, so detect and optimize the most common ones falling
583+
// back to what is effectively `ReadUnaligned<TTo>(ref As<TFrom, byte>(ref source))`
584+
// for anything that can't be special cased as potentially zero-cost move.
585+
586+
guint32 opcode = OP_LDADDR;
587+
MonoStackType tto_stack = STACK_OBJ;
588+
589+
bool tfrom_is_primitive_or_enum = false;
590+
if (m_class_is_primitive(tfrom_klass)) {
591+
tfrom_is_primitive_or_enum = true;
592+
} else if (m_class_is_enumtype(tfrom_klass)) {
593+
tfrom_is_primitive_or_enum = true;
594+
tfrom_type = mono_class_enum_basetype_internal(tfrom_klass)->type;
595+
}
596+
597+
bool tto_is_primitive_or_enum = false;
598+
if (m_class_is_primitive(tto_klass)) {
599+
tto_is_primitive_or_enum = true;
600+
} else if (m_class_is_enumtype(tto_klass)) {
601+
tto_is_primitive_or_enum = true;
602+
tto_type = mono_class_enum_basetype_internal(tto_klass)->type;
603+
}
604+
605+
if (tfrom_is_primitive_or_enum && tto_is_primitive_or_enum) {
606+
if (size == 1) {
607+
// opcode = OP_MOVE;
608+
// tto_stack = STACK_I4;
609+
return NULL;
610+
} else if (size == 2) {
611+
// opcode = OP_MOVE;
612+
// tto_stack = STACK_I4;
613+
return NULL;
614+
} else if (size == 4) {
615+
if ((tfrom_type == MONO_TYPE_R4) && ((tto_type == MONO_TYPE_I4) || (tto_type == MONO_TYPE_U4))) {
616+
opcode = OP_MOVE_F_TO_I4;
617+
tto_stack = STACK_I4;
618+
} else if ((tto_type == MONO_TYPE_R4) && ((tfrom_type == MONO_TYPE_I4) || (tfrom_type == MONO_TYPE_U4))) {
619+
opcode = OP_MOVE_I4_TO_F;
620+
tto_stack = STACK_R4;
621+
} else {
622+
opcode = OP_MOVE;
623+
tto_stack = STACK_I4;
624+
}
625+
} else if (size == 8) {
626+
if ((tfrom_type == MONO_TYPE_R8) && ((tto_type == MONO_TYPE_I8) || (tto_type == MONO_TYPE_U8))) {
627+
opcode = OP_MOVE_F_TO_I8;
628+
tto_stack = STACK_I8;
629+
} else if ((tto_type == MONO_TYPE_R8) && ((tfrom_type == MONO_TYPE_I8) || (tfrom_type == MONO_TYPE_U8))) {
630+
opcode = OP_MOVE_I8_TO_F;
631+
tto_stack = STACK_R8;
632+
} else {
633+
opcode = OP_MOVE;
634+
tto_stack = STACK_I8;
635+
}
636+
}
637+
} else if (mini_class_is_simd (cfg, tfrom_klass) && mini_class_is_simd (cfg, tto_klass)) {
638+
// int dreg = mono_alloc_dreg (cfg, STACK_VTYPE);
639+
// EMIT_NEW_UNALU (cfg, ins, OP_XMOVE, dreg, args [0]->dreg);
640+
// ins->type = STACK_VTYPE;
641+
// ins->klass = tto_klass;
642+
// return ins;
643+
return NULL;
644+
}
645+
646+
if (opcode == OP_LDADDR) {
647+
// FIXME: This isn't quite right
648+
//
649+
// MonoInst *addr;
650+
// EMIT_NEW_VARLOADA_VREG (cfg, addr, args [0]->dreg, tfrom);
651+
// addr->klass = tfrom_klass;
652+
//
653+
// // We don't need to call mini_get_underlying_type on tto
654+
// // since we have skipped handling for gsharedvt further up
655+
// assert(MONO_TYPE_ISSTRUCT (tto));
656+
//
657+
// return mini_emit_memory_load (cfg, tto, addr, 0, MONO_INST_UNALIGNED);
658+
return NULL;
659+
}
660+
661+
int dreg = mono_alloc_dreg (cfg, tto_stack);
662+
EMIT_NEW_UNALU (cfg, ins, opcode, dreg, args [0]->dreg);
663+
ins->type = tto_stack;
664+
ins->klass = tto_klass;
665+
return ins;
491666
} else if (!strcmp (cmethod->name, "IsAddressLessThan")) {
492667
g_assert (ctx);
493668
g_assert (ctx->method_inst);
@@ -2087,6 +2262,10 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
20872262
!strcmp (cmethod_klass_name_space, "System") &&
20882263
(!strcmp (cmethod_klass_name, "Span`1") || !strcmp (cmethod_klass_name, "ReadOnlySpan`1"))) {
20892264
return emit_span_intrinsics (cfg, cmethod, fsig, args);
2265+
} else if (in_corlib &&
2266+
!strcmp (cmethod_klass_name_space, "System") &&
2267+
!strcmp (cmethod_klass_name, "BitConverter")) {
2268+
return emit_bitconverter_intrinsics (cfg, cmethod, fsig, args);
20902269
} else if (in_corlib &&
20912270
!strcmp (cmethod_klass_name_space, "System.Runtime.CompilerServices") &&
20922271
!strcmp (cmethod_klass_name, "Unsafe")) {

0 commit comments

Comments
 (0)