Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
107 changes: 107 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
JITDUMP("Ignoring GT_MEMORYBARRIER; single-threaded codegen\n");
break;

case GT_INTRINSIC:
genIntrinsic(treeNode->AsIntrinsic());
break;

default:
#ifdef DEBUG
NYIRAW(GenTree::OpName(treeNode->OperGet()));
Expand Down Expand Up @@ -1471,6 +1475,109 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea)
WasmProduceReg(lea);
}

//------------------------------------------------------------------------
// PackIntrinsicAndType: Pack a intrinsic and var_types into a uint32_t
//
// Arguments:
// ni - a NamedIntrinsic to pack
// type - a var_types to pack
//
// Return Value:
// intrinsic and type packed into an integer that can be used as a switch value/case
//
static constexpr uint32_t PackIntrinsicAndType(NamedIntrinsic ni, var_types type)
{
if ((type == TYP_BYREF) || (type == TYP_REF))
{
type = TYP_I_IMPL;
}
const int shift1 = ConstLog2<TYP_COUNT>::value + 1;
return ((uint32_t)ni << shift1) | ((uint32_t)type);
}

//---------------------------------------------------------------------
// genIntrinsic - generate code for a given intrinsic
//
// Arguments
// treeNode - the GT_INTRINSIC node
//
// Return value:
// None
//
void CodeGen::genIntrinsic(GenTreeIntrinsic* treeNode)
{
genConsumeOperands(treeNode);

// Handle intrinsics that can be implemented by target-specific instructions
instruction ins;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

instruction ins; is left uninitialized and then assigned in the switch. Some toolchains warn on this pattern even with unreached() in the default case. Initializing ins to INS_invalid (or similar) avoids potential build breaks from -Wmaybe-uninitialized and matches the pattern used in other backends.

Suggested change
instruction ins;
instruction ins = INS_invalid;

Copilot uses AI. Check for mistakes.
switch (PackIntrinsicAndType(treeNode->gtIntrinsicName, treeNode->TypeGet()))
{
case PackIntrinsicAndType(NI_System_Math_Abs, TYP_FLOAT):
ins = INS_f32_abs;
break;
case PackIntrinsicAndType(NI_System_Math_Abs, TYP_DOUBLE):
ins = INS_f64_abs;
break;

case PackIntrinsicAndType(NI_System_Math_Ceiling, TYP_FLOAT):
ins = INS_f32_ceil;
break;
case PackIntrinsicAndType(NI_System_Math_Ceiling, TYP_DOUBLE):
ins = INS_f64_ceil;
break;

case PackIntrinsicAndType(NI_System_Math_Floor, TYP_FLOAT):
ins = INS_f32_floor;
break;
case PackIntrinsicAndType(NI_System_Math_Floor, TYP_DOUBLE):
ins = INS_f64_floor;
break;

case PackIntrinsicAndType(NI_System_Math_Max, TYP_FLOAT):
ins = INS_f32_max;
break;
case PackIntrinsicAndType(NI_System_Math_Max, TYP_DOUBLE):
ins = INS_f64_max;
break;

case PackIntrinsicAndType(NI_System_Math_Min, TYP_FLOAT):
ins = INS_f32_min;
break;
case PackIntrinsicAndType(NI_System_Math_Min, TYP_DOUBLE):
ins = INS_f64_min;
break;
Comment on lines +1536 to +1548
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Math.{F}.Min/Max are specified/implemented to return the NaN operand verbatim (preserving payload and, when both are NaN, preserving the first operand). Wasm f32.min/f32.max and f64.min/f64.max allow NaN canonicalization and generally won't preserve NaN payloads, so directly emitting these ops can change observable results. Consider either (a) not treating NI_System_Math_Min/Max as a target intrinsic on Wasm yet, or (b) lowering to a sequence that explicitly checks for NaN and selects the original NaN input to preserve payload semantics before/after using min/max.

Copilot uses AI. Check for mistakes.

case PackIntrinsicAndType(NI_System_Math_Round, TYP_FLOAT):
ins = INS_f32_nearest;
break;
case PackIntrinsicAndType(NI_System_Math_Round, TYP_DOUBLE):
ins = INS_f64_nearest;
break;

case PackIntrinsicAndType(NI_System_Math_Sqrt, TYP_FLOAT):
ins = INS_f32_sqrt;
break;
case PackIntrinsicAndType(NI_System_Math_Sqrt, TYP_DOUBLE):
ins = INS_f64_sqrt;
break;

case PackIntrinsicAndType(NI_System_Math_Truncate, TYP_FLOAT):
ins = INS_f32_trunc;
break;
case PackIntrinsicAndType(NI_System_Math_Truncate, TYP_DOUBLE):
ins = INS_f64_trunc;
break;

default:
assert(!"genIntrinsic: Unsupported intrinsic");
unreached();
}

GetEmitter()->emitIns(ins);

WasmProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForLclAddr: Generates the code for GT_LCL_ADDR.
//
Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8480,6 +8480,25 @@ bool Compiler::IsTargetIntrinsic(NamedIntrinsic intrinsicName)
default:
return false;
}

#elif defined(TARGET_WASM)

// TODO-WASM-CQ: we can likely support more intrinsics here
Copy link
Member

Choose a reason for hiding this comment

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

For example:

case NI_System_Math_CopySign:         // f32.copysign / f64.copysign
case NI_System_Math_MaxNative:        // f32.max / f64.max
case NI_System_Math_MinNative:        // f32.min / f64.min
case PackIntrinsicAndType(NI_System_Math_CopySign, TYP_FLOAT):
    ins = INS_f32_copysign;
    break;
case PackIntrinsicAndType(NI_System_Math_CopySign, TYP_DOUBLE):
    ins = INS_f64_copysign;
    break;

switch (intrinsicName)
{
case NI_System_Math_Abs:
case NI_System_Math_Ceiling:
case NI_System_Math_Floor:
case NI_System_Math_Max:
case NI_System_Math_Min:
case NI_System_Math_Round:
case NI_System_Math_Sqrt:
case NI_System_Math_Truncate:
return true;
Comment on lines +8486 to +8497
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

IsTargetIntrinsic now claims NI_System_Math_Min/Max are implemented by target instructions on Wasm. If codegen uses Wasm min/max, be aware that Wasm min/max do not preserve NaN payloads while System.Math/MathF implementations intentionally return a NaN argument verbatim; this can be an observable behavior difference. Consider dropping Min/Max from the Wasm target-intrinsic list until codegen preserves NaN payload semantics (or implement the required NaN handling sequence in codegen).

Copilot uses AI. Check for mistakes.

default:
return false;
}
#else
// TODO: This portion of logic is not implemented for other arch.
// The reason for returning true is that on all other arch the only intrinsic
Expand Down
Loading