Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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
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
15 changes: 12 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 @@ -2480,6 +2486,7 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
m_extendSrcSize = srcSize;
}

#ifndef TARGET_WASM
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this ifdef? I think things will work correctly without it (since we don't actually do any containment for casts yet).

if (castIsLoad)
{
const var_types srcLoadType = src->TypeGet();
Expand Down Expand Up @@ -2521,8 +2528,10 @@ CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast)
unreached();
}
}
#endif // !TARGET_WASM
}

#ifndef TARGET_WASM
#if !defined(TARGET_64BIT)
//------------------------------------------------------------------------
// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar
Expand Down
245 changes: 234 additions & 11 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,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 @@ -447,22 +451,234 @@ 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);
}

//------------------------------------------------------------------------
// PackOperAndType: Pack a GenTreeOp* into a uint32_t
// PackOperAndType: Pack a genTreeOps and two var_types into a uint32_t
//
// Arguments:
// oper - a genTreeOps to pack
// toType - a var_types to pack
// fromType - a var_types to pack
//
// Return Value:
// oper and the types packed into an integer that can be used as a switch value/case
//
static constexpr uint32_t PackOperAndType(genTreeOps oper, var_types toType, var_types fromType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variant looks unused.

{
if (fromType == TYP_BYREF)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also convert TYP_REF

{
fromType = TYP_I_IMPL;
}
if (toType == TYP_BYREF)
{
toType = TYP_I_IMPL;
}
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
const int shift2 = shift1 + ConstLog2<GT_COUNT>::value + 1;
return ((uint32_t)oper << shift1) | ((uint32_t)fromType) | ((uint32_t)toType << shift2);
}

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

// Arguments:
// treeNode - a GenTreeOp to extract oper and type from
// toType - a var_types to pack
// fromType - a var_types to pack
//
// Return Value:
// the node's oper and type packed into an integer that can be used as a switch 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

{
toType = TYP_I_IMPL;
}
if (fromType == TYP_BYREF)
{
fromType = TYP_I_IMPL;
}
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
return ((uint32_t)toType) | ((uint32_t)fromType << shift1);
}

//------------------------------------------------------------------------
// genIntToIntCast: Generate code for an integer to integer cast
//
// Arguments:
// cast - The GT_CAST node for the integer cast operation
//
// Notes:
// Handles casts to and from small int, int, and long types
// including proper sign extension and truncation as needed.
//
void CodeGen::genIntToIntCast(GenTreeCast* cast)
{
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need an NYI for checked (gtOverflow) casts. Also applies to genFloatToIntCast.


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);
GetEmitter()->emitIns(INS_i32_and);
ins = (toType == TYP_LONG) ? INS_i64_extend_u_i32 : INS_none;
Comment on lines +554 to +555
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
GetEmitter()->emitIns(INS_i32_and);
ins = (toType == TYP_LONG) ? INS_i64_extend_u_i32 : INS_none;
ins = INS_i32_and;

This type of cast always produces a TYP_INT value.

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::ExtendKind::ZERO_EXTEND_INT:
{
ins = INS_i64_extend_u_i32;
break;
}
case GenIntCastDesc::ExtendKind::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)
{
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
//
static uint32_t PackOperAndType(GenTreeOp* treeNode)
// Notes:
// Handles casts from TYP_INT/TYP_LONG to TYP_FLOAT/TYP_DOUBLE.
// Currently not implemented (NYI_WASM).
//
void CodeGen::genIntToFloatCast(GenTree* tree)
{
return PackOperAndType(treeNode->OperGet(), treeNode->TypeGet());
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 @@ -476,7 +692,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 @@ -571,7 +787,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 @@ -689,7 +905,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 @@ -748,7 +964,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 @@ -843,6 +1059,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