Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9288c02
Checkpoint
kg Dec 8, 2025
85f2b80
Add unary opcodes
kg Dec 8, 2025
7dbe5ff
Checkpoint
kg Dec 8, 2025
124c8dd
Checkpoint
kg Dec 8, 2025
eb34147
Checkpoint
kg Dec 8, 2025
e3d59bc
Checkpoint
kg Dec 8, 2025
71eb61b
Apply JIT format patch
kg Dec 8, 2025
3988828
Update src/coreclr/jit/codegenwasm.cpp
kg Dec 8, 2025
afadd5d
Merge branch 'main' of github.com:dotnet/runtime into kg/ryujit-wasm-…
adamperlin Dec 17, 2025
83410d9
Fix some initial review feedback in wasm codegen for casts
adamperlin Dec 18, 2025
ba132f4
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/ryuj…
adamperlin Jan 6, 2026
f3e9e63
Re-use main genCodeForCast, genIntToIntCast implementation to handle …
adamperlin Jan 14, 2026
9b7f09b
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/ryuj…
adamperlin Jan 14, 2026
386f93a
Remove helper calls for double->(u)long conversions on Wasm, jit-format
adamperlin Jan 14, 2026
c88d66b
Remove stray print
adamperlin Jan 14, 2026
0247f91
Add unreached() in some cases for casts
adamperlin Jan 14, 2026
d7f643b
Make cast op size estimates more precise to include 2 byte float -> i…
adamperlin Jan 14, 2026
11ec0a8
Add missing genConsumeRegs calls
adamperlin Jan 14, 2026
2031845
Revert cpp.hint file changes
adamperlin Jan 14, 2026
4e8ec4a
Fix missing small int -> long case
adamperlin Jan 14, 2026
a4ad71a
jit-format
adamperlin Jan 14, 2026
44c87f4
Address some initial review feedback
adamperlin Jan 15, 2026
cc76584
Additional review feedback
adamperlin Jan 15, 2026
1b1865f
Add details to comment for cast operation costing under Wasm
adamperlin Jan 15, 2026
ec11645
jit-format
adamperlin Jan 15, 2026
b62b9ba
Re-insert some ifdefs instead of moving code location in codegenlinea…
adamperlin Jan 15, 2026
beb5049
Address more review feedback
adamperlin Jan 15, 2026
cfb2ce1
Update src/coreclr/jit/codegenwasm.cpp
adamperlin Jan 19, 2026
cae1c5f
Address some additional review feedback
adamperlin Jan 23, 2026
b20e4d9
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/ryuj…
adamperlin Jan 23, 2026
a0fce7d
Remove unnecessary ifdef for load containment in casts (we shouldn't …
adamperlin Jan 23, 2026
9b03be5
Merge branch 'adamperlin/ryujit-wasm-cast' of github.com:adamperlin/r…
adamperlin Jan 23, 2026
fcd500b
jit-format
adamperlin Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ var_types ABIPassingSegment::GetRegisterType() const
case 3:
case 4:
return TYP_INT;
#ifdef TARGET_64BIT
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
case 5:
case 6:
case 7:
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ class CodeGen final : public CodeGenInterface
CHECK_NONE,
CHECK_SMALL_INT_RANGE,
CHECK_POSITIVE,
#ifdef TARGET_64BIT
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
CHECK_UINT_RANGE,
CHECK_POSITIVE_INT_RANGE,
CHECK_INT_RANGE,
Expand All @@ -826,13 +826,13 @@ class CodeGen final : public CodeGenInterface
COPY,
ZERO_EXTEND_SMALL_INT,
SIGN_EXTEND_SMALL_INT,
#ifdef TARGET_64BIT
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
ZERO_EXTEND_INT,
SIGN_EXTEND_INT,
#endif
LOAD_ZERO_EXTEND_SMALL_INT,
LOAD_SIGN_EXTEND_SMALL_INT,
#ifdef TARGET_64BIT
#if defined(TARGET_64BIT) || defined(TARGET_WASM)
LOAD_ZERO_EXTEND_INT,
LOAD_SIGN_EXTEND_INT,
#endif
Expand Down
13 changes: 10 additions & 3 deletions src/coreclr/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,7 @@ void CodeGen::genTransferRegGCState(regNumber dst, regNumber src)
gcInfo.gcMarkRegSetNpt(dstMask);
}
}
#endif

//------------------------------------------------------------------------
// genCodeForCast: Generates the code for GT_CAST.
Expand Down Expand Up @@ -2337,12 +2338,12 @@ void CodeGen::genCodeForCast(GenTreeOp* tree)
// Casts int32/uint32/int64/uint64 --> float/double
genIntToFloatCast(tree);
}
#ifndef TARGET_64BIT
#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
else if (varTypeIsLong(tree->gtOp1))
{
genLongToIntCast(tree);
}
#endif // !TARGET_64BIT
#endif // !TARGET_64BIT && !TARGET_WASM
else
{
// Casts int <--> int
Expand All @@ -2366,8 +2367,13 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
const bool castIsLoad = !src->isUsedFromReg();

assert(castIsLoad == src->isUsedFromMemory());
#ifndef TARGET_WASM
assert((srcSize == 4) || (srcSize == genTypeSize(TYP_I_IMPL)));
assert((dstSize == 4) || (dstSize == genTypeSize(TYP_I_IMPL)));
#else
assert((srcSize == 4) || (srcSize == 8));
assert((dstSize == 4) || (dstSize == 8));
#endif

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

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

#ifndef TARGET_WASM
#if !defined(TARGET_64BIT)
//------------------------------------------------------------------------
// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar
Expand Down
231 changes: 219 additions & 12 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genCodeForConstant(treeNode);
break;

case GT_CAST:
genCodeForCast(treeNode->AsOp());
break;

case GT_NEG:
case GT_NOT:
genCodeForNegNot(treeNode->AsOp());
Expand Down Expand Up @@ -593,22 +597,218 @@ static constexpr uint32_t PackOperAndType(genTreeOps oper, var_types type)
{
type = TYP_I_IMPL;
}
static_assert((ssize_t)GT_COUNT > (ssize_t)TYP_COUNT);
return ((uint32_t)oper << (ConstLog2<GT_COUNT>::value + 1)) | ((uint32_t)type);
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
return ((uint32_t)oper << shift1) | ((uint32_t)type);
}

// ------------------------------------------------------------------------
// PackTypes: Pack two var_types together into a uint32_t

// Arguments:
// toType - a var_types to pack
// fromType - a var_types to pack
//
// Return Value:
// The two types packed together into an integer that can be used as a switch/value,
// the primary use case being the handling of operations with two-type variants such
// as casts.
//
static constexpr uint32_t PackTypes(var_types toType, var_types fromType)
{
if (toType == TYP_BYREF || toType == TYP_REF)
{
toType = TYP_I_IMPL;
}
if (fromType == TYP_BYREF || fromType == TYP_REF)
{
fromType = TYP_I_IMPL;
}
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
return ((uint32_t)toType) | ((uint32_t)fromType << shift1);
}

//------------------------------------------------------------------------
// PackOperAndType: Pack a GenTreeOp* into a uint32_t
// genIntToIntCast: Generate code for an integer to integer cast
//
// Arguments:
// treeNode - a GenTreeOp to extract oper and type from
// cast - The GT_CAST node for the integer cast operation
//
// Return Value:
// the node's oper and type packed into an integer that can be used as a switch value
// Notes:
// Handles casts to and from small int, int, and long types
// including proper sign extension and truncation as needed.
//
static uint32_t PackOperAndType(GenTreeOp* treeNode)
void CodeGen::genIntToIntCast(GenTreeCast* cast)
{
return PackOperAndType(treeNode->OperGet(), treeNode->TypeGet());
if (cast->gtOverflow())
{
NYI_WASM("Overflow checks");
}

GenIntCastDesc desc(cast);
var_types toType = genActualType(cast->CastToType());
var_types fromType = genActualType(cast->CastOp());
int extendSize = desc.ExtendSrcSize();
instruction ins = INS_none;
assert(fromType == TYP_INT || fromType == TYP_LONG);

genConsumeOperands(cast);

// TODO-WASM: Handle load containment GenIntCastDesc::LOAD_* cases once we mark containment for loads
switch (desc.ExtendKind())
{
case GenIntCastDesc::COPY:
{
if (toType == TYP_INT && fromType == TYP_LONG)
{
ins = INS_i32_wrap_i64;
}
else
{
assert(toType == fromType);
ins = INS_none;
}
break;
}
case GenIntCastDesc::ZERO_EXTEND_SMALL_INT:
{
int andAmount = extendSize == 1 ? 255 : 65535;
if (fromType == TYP_LONG)
{
GetEmitter()->emitIns(INS_i32_wrap_i64);
}
GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, andAmount);
ins = INS_i32_and;
break;
}
case GenIntCastDesc::SIGN_EXTEND_SMALL_INT:
{
if (fromType == TYP_LONG)
{
GetEmitter()->emitIns(INS_i32_wrap_i64);
}
ins = (extendSize == 1) ? INS_i32_extend8_s : INS_i32_extend16_s;

break;
}
case GenIntCastDesc::ZERO_EXTEND_INT:
{
ins = INS_i64_extend_u_i32;
break;
}
case GenIntCastDesc::SIGN_EXTEND_INT:
{
ins = INS_i64_extend_s_i32;
break;
}
default:
unreached();
}

if (ins != INS_none)
{
GetEmitter()->emitIns(ins);
}
genProduceReg(cast);
}

//------------------------------------------------------------------------
// genFloatToIntCast: Generate code for a floating point to integer cast
//
// Arguments:
// tree - The GT_CAST node for the float-to-int cast operation
//
// Notes:
// Handles casts from TYP_FLOAT/TYP_DOUBLE to TYP_INT/TYP_LONG.
// Uses saturating truncation instructions (trunc_sat) which clamp
// out-of-range values rather than trapping.
//
void CodeGen::genFloatToIntCast(GenTree* tree)
{
if (tree->gtOverflow())
{
NYI_WASM("Overflow checks");
}

var_types toType = tree->TypeGet();
var_types fromType = tree->AsCast()->CastOp()->TypeGet();
bool isUnsigned = varTypeIsUnsigned(tree->AsCast()->CastToType());
instruction ins = INS_none;
assert(varTypeIsFloating(fromType) && (toType == TYP_INT || toType == TYP_LONG));

genConsumeOperands(tree->AsCast());

switch (PackTypes(fromType, toType))
{
case PackTypes(TYP_FLOAT, TYP_INT):
ins = isUnsigned ? INS_i32_trunc_sat_f32_u : INS_i32_trunc_sat_f32_s;
break;
case PackTypes(TYP_DOUBLE, TYP_INT):
ins = isUnsigned ? INS_i32_trunc_sat_f64_u : INS_i32_trunc_sat_f64_s;
break;
case PackTypes(TYP_FLOAT, TYP_LONG):
ins = isUnsigned ? INS_i64_trunc_sat_f32_u : INS_i64_trunc_sat_f32_s;
break;
case PackTypes(TYP_DOUBLE, TYP_LONG):
ins = isUnsigned ? INS_i64_trunc_sat_f64_u : INS_i64_trunc_sat_f64_s;
break;
default:
unreached();
}

GetEmitter()->emitIns(ins);
genProduceReg(tree);
}

//------------------------------------------------------------------------
// genIntToFloatCast: Generate code for an integer to floating point cast
//
// Arguments:
// tree - The GT_CAST node for the int-to-float cast operation
//
// Notes:
// Handles casts from TYP_INT/TYP_LONG to TYP_FLOAT/TYP_DOUBLE.
// Currently not implemented (NYI_WASM).
//
void CodeGen::genIntToFloatCast(GenTree* tree)
{
NYI_WASM("genIntToFloatCast");
}

//------------------------------------------------------------------------
// genFloatToFloatCast: Generate code for a float to float cast
//
// Arguments:
// tree - The GT_CAST node for the float-to-float cast operation
//
void CodeGen::genFloatToFloatCast(GenTree* tree)
{
var_types toType = tree->TypeGet();
var_types fromType = tree->AsCast()->CastOp()->TypeGet();
instruction ins = INS_none;

genConsumeOperands(tree->AsCast());

switch (PackTypes(toType, fromType))
{
case PackTypes(TYP_FLOAT, TYP_DOUBLE):
ins = INS_f32_demote_f64;
break;
case PackTypes(TYP_DOUBLE, TYP_FLOAT):
ins = INS_f64_promote_f32;
break;
case PackTypes(TYP_FLOAT, TYP_FLOAT):
case PackTypes(TYP_DOUBLE, TYP_DOUBLE):
ins = INS_none;
break;
default:
unreached();
}

if (ins != INS_none)
{
GetEmitter()->emitIns(ins);
}
genProduceReg(tree);
}

//------------------------------------------------------------------------
Expand All @@ -622,7 +822,7 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
genConsumeOperands(treeNode);

instruction ins;
switch (PackOperAndType(treeNode))
switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet()))
{
case PackOperAndType(GT_ADD, TYP_INT):
if (treeNode->gtOverflow())
Expand Down Expand Up @@ -717,7 +917,7 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
genConsumeOperands(treeNode);

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

instruction ins;
switch (PackOperAndType(treeNode))
switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet()))
{
case PackOperAndType(GT_LSH, TYP_INT):
ins = INS_i32_shl;
Expand Down Expand Up @@ -894,7 +1094,7 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree)
genConsumeOperands(tree);

instruction ins;
switch (PackOperAndType(tree))
switch (PackOperAndType(tree->OperGet(), tree->TypeGet()))
{
case PackOperAndType(GT_NOT, TYP_INT):
GetEmitter()->emitIns_I(INS_i32_const, emitTypeSize(tree), -1);
Expand Down Expand Up @@ -995,6 +1195,13 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree)
assert(genIsValidReg(varDsc->GetRegNum()));
unsigned wasmLclIndex = WasmRegToIndex(varDsc->GetRegNum());
GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex);
// In this case, the resulting tree type may be different from the local var type where the value originates,
// and so we need an explicit conversion since we can't "load"
// the value with a different type like we can if the value is on the shadow stack.
if (tree->TypeIs(TYP_INT) && varDsc->TypeIs(TYP_LONG))
{
GetEmitter()->emitIns(INS_i32_wrap_i64);
}
}
}

Expand Down
Loading
Loading