@@ -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+
430464static MonoInst *
431465emit_unsafe_intrinsics (MonoCompile * cfg , MonoMethod * cmethod , MonoMethodSignature * fsig , MonoInst * * args )
432466{
@@ -488,6 +522,145 @@ 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+ // FIXME: This doesn't work
608+ //
609+ // opcode = OP_MOVE;
610+ // tto_stack = STACK_I4;
611+ } else if (size == 2 ) {
612+ // FIXME: This doesn't work
613+ //
614+ // opcode = OP_MOVE;
615+ // tto_stack = STACK_I4;
616+ } else if (size == 4 ) {
617+ if ((tfrom_type == MONO_TYPE_R4 ) && ((tto_type == MONO_TYPE_I4 ) || (tto_type == MONO_TYPE_U4 ))) {
618+ opcode = OP_MOVE_F_TO_I4 ;
619+ tto_stack = STACK_I4 ;
620+ } else if ((tto_type == MONO_TYPE_R4 ) && ((tfrom_type == MONO_TYPE_I4 ) || (tfrom_type == MONO_TYPE_U4 ))) {
621+ opcode = OP_MOVE_I4_TO_F ;
622+ tto_stack = STACK_R4 ;
623+ } else {
624+ opcode = OP_MOVE ;
625+ tto_stack = STACK_I4 ;
626+ }
627+ } else if (size == 8 ) {
628+ if ((tfrom_type == MONO_TYPE_R8 ) && ((tto_type == MONO_TYPE_I8 ) || (tto_type == MONO_TYPE_U8 ))) {
629+ opcode = OP_MOVE_F_TO_I8 ;
630+ tto_stack = STACK_I8 ;
631+ } else if ((tto_type == MONO_TYPE_R8 ) && ((tfrom_type == MONO_TYPE_I8 ) || (tfrom_type == MONO_TYPE_U8 ))) {
632+ opcode = OP_MOVE_I8_TO_F ;
633+ tto_stack = STACK_R8 ;
634+ } else {
635+ opcode = OP_MOVE ;
636+ tto_stack = STACK_I8 ;
637+ }
638+ }
639+ } else if (mini_class_is_simd (cfg , tfrom_klass ) && mini_class_is_simd (cfg , tto_klass )) {
640+ opcode = OP_XMOVE ;
641+ tto_stack = STACK_VTYPE ;
642+ }
643+
644+ if (opcode == OP_LDADDR ) {
645+ // FIXME: This isn't quite right
646+ //
647+ // MonoInst *addr;
648+ // EMIT_NEW_VARLOADA_VREG (cfg, addr, args [0]->dreg, tfrom);
649+ // addr->klass = tfrom_klass;
650+ //
651+ // // We don't need to call mini_get_underlying_type on tto
652+ // // since we have skipped handling for gsharedvt further up
653+ // assert(MONO_TYPE_ISSTRUCT (tto));
654+ //
655+ // return mini_emit_memory_load (cfg, tto, addr, 0, MONO_INST_UNALIGNED);
656+ return NULL ;
657+ }
658+
659+ int dreg = mono_alloc_dreg (cfg , tto_stack );
660+ EMIT_NEW_UNALU (cfg , ins , opcode , dreg , args [0 ]-> dreg );
661+ ins -> type = tto_stack ;
662+ ins -> klass = tto_klass ;
663+ return ins ;
491664 } else if (!strcmp (cmethod -> name , "IsAddressLessThan" )) {
492665 g_assert (ctx );
493666 g_assert (ctx -> method_inst );
@@ -2087,6 +2260,10 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
20872260 !strcmp (cmethod_klass_name_space , "System" ) &&
20882261 (!strcmp (cmethod_klass_name , "Span`1" ) || !strcmp (cmethod_klass_name , "ReadOnlySpan`1" ))) {
20892262 return emit_span_intrinsics (cfg , cmethod , fsig , args );
2263+ } else if (in_corlib &&
2264+ !strcmp (cmethod_klass_name_space , "System" ) &&
2265+ !strcmp (cmethod_klass_name , "BitConverter" )) {
2266+ return emit_bitconverter_intrinsics (cfg , cmethod , fsig , args );
20902267 } else if (in_corlib &&
20912268 !strcmp (cmethod_klass_name_space , "System.Runtime.CompilerServices" ) &&
20922269 !strcmp (cmethod_klass_name , "Unsafe" )) {
0 commit comments