Skip to content

Commit ef2aefa

Browse files
adamperlinkgCopilotSingleAccretion
authored
[RyuJIT Wasm] Cast Operations Follow Up (#122862)
This PR addresses some review feedback in #122301 and fills in more codegen for casts on Wasm. Notably: - Casts to and from all int types (byte, short) are now supported with proper sign extension - Some generic casting code, in particular `GenIntCastDesc` was moved out of ifdefs and adapted for Wasm to support this. We don't yet support containment of casts in loads (i.e., the `GenIntCastDesc::LOAD_*` type casts). - float->int casts are implemented using the saturating non-trapping conversion instructions - This required changing the type of our Wasm opcodes to be 2 bytes instead of 1 --------- Co-authored-by: Katelyn Gadd <kg@luminance.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
1 parent bfd9567 commit ef2aefa

File tree

9 files changed

+366
-75
lines changed

9 files changed

+366
-75
lines changed

src/coreclr/jit/abi.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ var_types ABIPassingSegment::GetRegisterType() const
142142
case 3:
143143
case 4:
144144
return TYP_INT;
145-
#ifdef TARGET_64BIT
145+
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
146146
case 5:
147147
case 6:
148148
case 7:

src/coreclr/jit/codegen.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ class CodeGen final : public CodeGenInterface
814814
CHECK_NONE,
815815
CHECK_SMALL_INT_RANGE,
816816
CHECK_POSITIVE,
817-
#ifdef TARGET_64BIT
817+
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
818818
CHECK_UINT_RANGE,
819819
CHECK_POSITIVE_INT_RANGE,
820820
CHECK_INT_RANGE,
@@ -826,13 +826,13 @@ class CodeGen final : public CodeGenInterface
826826
COPY,
827827
ZERO_EXTEND_SMALL_INT,
828828
SIGN_EXTEND_SMALL_INT,
829-
#ifdef TARGET_64BIT
829+
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
830830
ZERO_EXTEND_INT,
831831
SIGN_EXTEND_INT,
832832
#endif
833833
LOAD_ZERO_EXTEND_SMALL_INT,
834834
LOAD_SIGN_EXTEND_SMALL_INT,
835-
#ifdef TARGET_64BIT
835+
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
836836
LOAD_ZERO_EXTEND_INT,
837837
LOAD_SIGN_EXTEND_INT,
838838
#endif

src/coreclr/jit/codegenlinear.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,7 @@ void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
23092309
gcInfo.gcMarkRegSetNpt(dstMask);
23102310
}
23112311
}
2312+
#endif
23122313

23132314
//------------------------------------------------------------------------
23142315
// genCodeForCast: Generates the code for GT_CAST.
@@ -2337,12 +2338,12 @@ void CodeGen::genCodeForCast(GenTreeOp* tree)
23372338
// Casts int32/uint32/int64/uint64 --> float/double
23382339
genIntToFloatCast(tree);
23392340
}
2340-
#ifndef TARGET_64BIT
2341+
#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
23412342
else if (varTypeIsLong(tree->gtOp1))
23422343
{
23432344
genLongToIntCast(tree);
23442345
}
2345-
#endif // !TARGET_64BIT
2346+
#endif // !TARGET_64BIT && !TARGET_WASM
23462347
else
23472348
{
23482349
// Casts int <--> int
@@ -2366,8 +2367,13 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
23662367
const bool castIsLoad = !src->isUsedFromReg();
23672368

23682369
assert(castIsLoad == src->isUsedFromMemory());
2370+
#ifndef TARGET_WASM
23692371
assert((srcSize == 4) || (srcSize == genTypeSize(TYP_I_IMPL)));
23702372
assert((dstSize == 4) || (dstSize == genTypeSize(TYP_I_IMPL)));
2373+
#else
2374+
assert((srcSize == 4) || (srcSize == 8));
2375+
assert((dstSize == 4) || (dstSize == 8));
2376+
#endif
23712377

23722378
assert(dstSize == genTypeSize(genActualType(castType)));
23732379

@@ -2395,7 +2401,7 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
23952401
m_extendSrcSize = castSize;
23962402
}
23972403
}
2398-
#ifdef TARGET_64BIT
2404+
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
23992405
// castType cannot be (U)LONG on 32 bit targets, such casts should have been decomposed.
24002406
// srcType cannot be a small int type since it's the "actual type" of the cast operand.
24012407
// This means that widening casts do not occur on 32 bit targets.
@@ -2523,6 +2529,7 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
25232529
}
25242530
}
25252531

2532+
#ifndef TARGET_WASM
25262533
#if !defined(TARGET_64BIT)
25272534
//------------------------------------------------------------------------
25282535
// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar

src/coreclr/jit/codegenwasm.cpp

Lines changed: 219 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
486486
genCodeForConstant(treeNode);
487487
break;
488488

489+
case GT_CAST:
490+
genCodeForCast(treeNode->AsOp());
491+
break;
492+
489493
case GT_NEG:
490494
case GT_NOT:
491495
genCodeForNegNot(treeNode->AsOp());
@@ -593,22 +597,218 @@ static constexpr uint32_t PackOperAndType(genTreeOps oper, var_types type)
593597
{
594598
type = TYP_I_IMPL;
595599
}
596-
static_assert((ssize_t)GT_COUNT > (ssize_t)TYP_COUNT);
597-
return ((uint32_t)oper << (ConstLog2<GT_COUNT>::value + 1)) | ((uint32_t)type);
600+
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
601+
return ((uint32_t)oper << shift1) | ((uint32_t)type);
602+
}
603+
604+
// ------------------------------------------------------------------------
605+
// PackTypes: Pack two var_types together into a uint32_t
606+
607+
// Arguments:
608+
// toType - a var_types to pack
609+
// fromType - a var_types to pack
610+
//
611+
// Return Value:
612+
// The two types packed together into an integer that can be used as a switch/value,
613+
// the primary use case being the handling of operations with two-type variants such
614+
// as casts.
615+
//
616+
static constexpr uint32_t PackTypes(var_types toType, var_types fromType)
617+
{
618+
if (toType == TYP_BYREF || toType == TYP_REF)
619+
{
620+
toType = TYP_I_IMPL;
621+
}
622+
if (fromType == TYP_BYREF || fromType == TYP_REF)
623+
{
624+
fromType = TYP_I_IMPL;
625+
}
626+
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
627+
return ((uint32_t)toType) | ((uint32_t)fromType << shift1);
598628
}
599629

600630
//------------------------------------------------------------------------
601-
// PackOperAndType: Pack a GenTreeOp* into a uint32_t
631+
// genIntToIntCast: Generate code for an integer to integer cast
602632
//
603633
// Arguments:
604-
// treeNode - a GenTreeOp to extract oper and type from
634+
// cast - The GT_CAST node for the integer cast operation
605635
//
606-
// Return Value:
607-
// the node's oper and type packed into an integer that can be used as a switch value
636+
// Notes:
637+
// Handles casts to and from small int, int, and long types
638+
// including proper sign extension and truncation as needed.
608639
//
609-
static uint32_t PackOperAndType(GenTreeOp* treeNode)
640+
void CodeGen::genIntToIntCast(GenTreeCast* cast)
610641
{
611-
return PackOperAndType(treeNode->OperGet(), treeNode->TypeGet());
642+
if (cast->gtOverflow())
643+
{
644+
NYI_WASM("Overflow checks");
645+
}
646+
647+
GenIntCastDesc desc(cast);
648+
var_types toType = genActualType(cast->CastToType());
649+
var_types fromType = genActualType(cast->CastOp());
650+
int extendSize = desc.ExtendSrcSize();
651+
instruction ins = INS_none;
652+
assert(fromType == TYP_INT || fromType == TYP_LONG);
653+
654+
genConsumeOperands(cast);
655+
656+
// TODO-WASM: Handle load containment GenIntCastDesc::LOAD_* cases once we mark containment for loads
657+
switch (desc.ExtendKind())
658+
{
659+
case GenIntCastDesc::COPY:
660+
{
661+
if (toType == TYP_INT && fromType == TYP_LONG)
662+
{
663+
ins = INS_i32_wrap_i64;
664+
}
665+
else
666+
{
667+
assert(toType == fromType);
668+
ins = INS_none;
669+
}
670+
break;
671+
}
672+
case GenIntCastDesc::ZERO_EXTEND_SMALL_INT:
673+
{
674+
int andAmount = extendSize == 1 ? 255 : 65535;
675+
if (fromType == TYP_LONG)
676+
{
677+
GetEmitter()->emitIns(INS_i32_wrap_i64);
678+
}
679+
GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, andAmount);
680+
ins = INS_i32_and;
681+
break;
682+
}
683+
case GenIntCastDesc::SIGN_EXTEND_SMALL_INT:
684+
{
685+
if (fromType == TYP_LONG)
686+
{
687+
GetEmitter()->emitIns(INS_i32_wrap_i64);
688+
}
689+
ins = (extendSize == 1) ? INS_i32_extend8_s : INS_i32_extend16_s;
690+
691+
break;
692+
}
693+
case GenIntCastDesc::ZERO_EXTEND_INT:
694+
{
695+
ins = INS_i64_extend_u_i32;
696+
break;
697+
}
698+
case GenIntCastDesc::SIGN_EXTEND_INT:
699+
{
700+
ins = INS_i64_extend_s_i32;
701+
break;
702+
}
703+
default:
704+
unreached();
705+
}
706+
707+
if (ins != INS_none)
708+
{
709+
GetEmitter()->emitIns(ins);
710+
}
711+
genProduceReg(cast);
712+
}
713+
714+
//------------------------------------------------------------------------
715+
// genFloatToIntCast: Generate code for a floating point to integer cast
716+
//
717+
// Arguments:
718+
// tree - The GT_CAST node for the float-to-int cast operation
719+
//
720+
// Notes:
721+
// Handles casts from TYP_FLOAT/TYP_DOUBLE to TYP_INT/TYP_LONG.
722+
// Uses saturating truncation instructions (trunc_sat) which clamp
723+
// out-of-range values rather than trapping.
724+
//
725+
void CodeGen::genFloatToIntCast(GenTree* tree)
726+
{
727+
if (tree->gtOverflow())
728+
{
729+
NYI_WASM("Overflow checks");
730+
}
731+
732+
var_types toType = tree->TypeGet();
733+
var_types fromType = tree->AsCast()->CastOp()->TypeGet();
734+
bool isUnsigned = varTypeIsUnsigned(tree->AsCast()->CastToType());
735+
instruction ins = INS_none;
736+
assert(varTypeIsFloating(fromType) && (toType == TYP_INT || toType == TYP_LONG));
737+
738+
genConsumeOperands(tree->AsCast());
739+
740+
switch (PackTypes(fromType, toType))
741+
{
742+
case PackTypes(TYP_FLOAT, TYP_INT):
743+
ins = isUnsigned ? INS_i32_trunc_sat_f32_u : INS_i32_trunc_sat_f32_s;
744+
break;
745+
case PackTypes(TYP_DOUBLE, TYP_INT):
746+
ins = isUnsigned ? INS_i32_trunc_sat_f64_u : INS_i32_trunc_sat_f64_s;
747+
break;
748+
case PackTypes(TYP_FLOAT, TYP_LONG):
749+
ins = isUnsigned ? INS_i64_trunc_sat_f32_u : INS_i64_trunc_sat_f32_s;
750+
break;
751+
case PackTypes(TYP_DOUBLE, TYP_LONG):
752+
ins = isUnsigned ? INS_i64_trunc_sat_f64_u : INS_i64_trunc_sat_f64_s;
753+
break;
754+
default:
755+
unreached();
756+
}
757+
758+
GetEmitter()->emitIns(ins);
759+
genProduceReg(tree);
760+
}
761+
762+
//------------------------------------------------------------------------
763+
// genIntToFloatCast: Generate code for an integer to floating point cast
764+
//
765+
// Arguments:
766+
// tree - The GT_CAST node for the int-to-float cast operation
767+
//
768+
// Notes:
769+
// Handles casts from TYP_INT/TYP_LONG to TYP_FLOAT/TYP_DOUBLE.
770+
// Currently not implemented (NYI_WASM).
771+
//
772+
void CodeGen::genIntToFloatCast(GenTree* tree)
773+
{
774+
NYI_WASM("genIntToFloatCast");
775+
}
776+
777+
//------------------------------------------------------------------------
778+
// genFloatToFloatCast: Generate code for a float to float cast
779+
//
780+
// Arguments:
781+
// tree - The GT_CAST node for the float-to-float cast operation
782+
//
783+
void CodeGen::genFloatToFloatCast(GenTree* tree)
784+
{
785+
var_types toType = tree->TypeGet();
786+
var_types fromType = tree->AsCast()->CastOp()->TypeGet();
787+
instruction ins = INS_none;
788+
789+
genConsumeOperands(tree->AsCast());
790+
791+
switch (PackTypes(toType, fromType))
792+
{
793+
case PackTypes(TYP_FLOAT, TYP_DOUBLE):
794+
ins = INS_f32_demote_f64;
795+
break;
796+
case PackTypes(TYP_DOUBLE, TYP_FLOAT):
797+
ins = INS_f64_promote_f32;
798+
break;
799+
case PackTypes(TYP_FLOAT, TYP_FLOAT):
800+
case PackTypes(TYP_DOUBLE, TYP_DOUBLE):
801+
ins = INS_none;
802+
break;
803+
default:
804+
unreached();
805+
}
806+
807+
if (ins != INS_none)
808+
{
809+
GetEmitter()->emitIns(ins);
810+
}
811+
genProduceReg(tree);
612812
}
613813

614814
//------------------------------------------------------------------------
@@ -622,7 +822,7 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
622822
genConsumeOperands(treeNode);
623823

624824
instruction ins;
625-
switch (PackOperAndType(treeNode))
825+
switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet()))
626826
{
627827
case PackOperAndType(GT_ADD, TYP_INT):
628828
if (treeNode->gtOverflow())
@@ -717,7 +917,7 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
717917
genConsumeOperands(treeNode);
718918

719919
instruction ins;
720-
switch (PackOperAndType(treeNode))
920+
switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet()))
721921
{
722922
case PackOperAndType(GT_DIV, TYP_INT):
723923
ins = INS_i32_div_s;
@@ -835,7 +1035,7 @@ void CodeGen::genCodeForShift(GenTree* tree)
8351035
// for both the shift and shiftee. So the shift may need to be extended (zero-extended) for TYP_LONG.
8361036

8371037
instruction ins;
838-
switch (PackOperAndType(treeNode))
1038+
switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet()))
8391039
{
8401040
case PackOperAndType(GT_LSH, TYP_INT):
8411041
ins = INS_i32_shl;
@@ -894,7 +1094,7 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree)
8941094
genConsumeOperands(tree);
8951095

8961096
instruction ins;
897-
switch (PackOperAndType(tree))
1097+
switch (PackOperAndType(tree->OperGet(), tree->TypeGet()))
8981098
{
8991099
case PackOperAndType(GT_NOT, TYP_INT):
9001100
GetEmitter()->emitIns_I(INS_i32_const, emitTypeSize(tree), -1);
@@ -995,6 +1195,13 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree)
9951195
assert(genIsValidReg(varDsc->GetRegNum()));
9961196
unsigned wasmLclIndex = WasmRegToIndex(varDsc->GetRegNum());
9971197
GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex);
1198+
// In this case, the resulting tree type may be different from the local var type where the value originates,
1199+
// and so we need an explicit conversion since we can't "load"
1200+
// the value with a different type like we can if the value is on the shadow stack.
1201+
if (tree->TypeIs(TYP_INT) && varDsc->TypeIs(TYP_LONG))
1202+
{
1203+
GetEmitter()->emitIns(INS_i32_wrap_i64);
1204+
}
9981205
}
9991206
}
10001207

0 commit comments

Comments
 (0)