Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e64dd5d
Initial plan
Copilot Dec 3, 2025
116ab85
Add partial constant emitting support to WebAssembly RyuJIT backend
Copilot Dec 4, 2025
e6d4abc
Back out some changes.
kg Dec 4, 2025
3368ede
Fill out more stuff
kg Dec 4, 2025
9425af2
Cleanup formatting
kg Dec 4, 2025
c84d652
Cleanups
kg Dec 4, 2025
dfd67ee
Checkpoint
kg Dec 4, 2025
7aae050
Checkpoint
kg Dec 4, 2025
c187ed4
Restore obliterated changes
kg Dec 4, 2025
71b7ca4
Checkpoint
kg Dec 4, 2025
0ed9418
Checkpoint
kg Dec 4, 2025
9601838
Checkpoint
kg Dec 4, 2025
f20b75b
Checkpoint
kg Dec 4, 2025
31817a4
Checkpoint
kg Dec 4, 2025
7215eec
Checkpoint
kg Dec 4, 2025
248c9ad
Checkpoint
kg Dec 4, 2025
f12c3cc
Checkpoint
kg Dec 4, 2025
fee66ff
Checkpoint
kg Dec 4, 2025
3c24bf3
Checkpoint
kg Dec 4, 2025
a7217ac
Checkpoint
kg Dec 4, 2025
540d2a4
Checkpoint
kg Dec 4, 2025
1ad2af5
Checkpoint
kg Dec 4, 2025
dcb37a5
Checkpoint
kg Dec 4, 2025
43fad5e
Checkpoint
kg Dec 4, 2025
3192443
Checkpoint
kg Dec 4, 2025
7cec96a
Checkpoint
kg Dec 4, 2025
5109126
Apply JIT format patch
kg Dec 4, 2025
eb825ac
Checkpoint
kg Dec 5, 2025
4f17ec8
Checkpoint
kg Dec 5, 2025
8c95bed
Emit SLEB and ULEB
kg Dec 5, 2025
f7c95e5
Fix comments
kg Dec 5, 2025
739b37d
Fix format strings
kg Dec 5, 2025
bcf219b
jit-format fixes
kg Dec 5, 2025
b5d1f6a
Use correct sizeof type
kg Dec 5, 2025
bebdcfc
Fix clang build
kg Dec 5, 2025
908a099
Address copilot feedback
kg Dec 5, 2025
0f5c953
Address PR feedback
kg Dec 5, 2025
47ccf9a
Address PR feedback
kg Dec 5, 2025
6042fff
Address PR feedback
kg Dec 5, 2025
66bbbb9
Address PR feedback
kg Dec 5, 2025
dd82888
Address PR feedback
kg Dec 5, 2025
4fbb81b
Address PR feedback
kg Dec 6, 2025
f3742a8
Apply JIT format patch
kg Dec 6, 2025
c97d2b0
Address PR feedback
kg Dec 6, 2025
a6b9d62
Address PR feedback
kg Dec 6, 2025
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
4 changes: 4 additions & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,10 @@ class CodeGen final : public CodeGenInterface
void genCodeForBinary(GenTreeOp* treeNode);
bool genIsSameLocalVar(GenTree* tree1, GenTree* tree2);

#if defined(TARGET_WASM)
void genCodeForConstant(GenTree* treeNode);
#endif

#if defined(TARGET_X86)
void genCodeForLongUMod(GenTreeOp* node);
#endif // TARGET_X86
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2213,6 +2213,11 @@ void CodeGen::genEmitMachineCode()
//
void CodeGen::genEmitUnwindDebugGCandEH()
{
#ifdef TARGET_WASM
// TODO-WASM: Fix this phase causing an assertion failure even for methods with no GC locals or EH clauses
return;
#endif

/* Now that the code is issued, we can finalize and emit the unwind data */

compiler->unwindEmit(*codePtr, coldCodePtr);
Expand Down
60 changes: 60 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
// Do nothing; this node is a marker for debug info.
break;

case GT_CNS_INT:
case GT_CNS_LNG:
case GT_CNS_DBL:
genCodeForConstant(treeNode);
break;

default:
#ifdef DEBUG
NYIRAW(GenTree::OpName(treeNode->OperGet()));
Expand Down Expand Up @@ -537,6 +543,60 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
genProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForConstant: Generate code for an integer or floating point constant
//
// Arguments:
// treeNode - The constant.
//
void CodeGen::genCodeForConstant(GenTree* treeNode)
{
instruction ins;
cnsval_ssize_t bits;
var_types type = treeNode->TypeIs(TYP_REF, TYP_BYREF) ? TYP_I_IMPL : treeNode->TypeGet();
static_assert(sizeof(cnsval_ssize_t) >= sizeof(double));

switch (type)
{
case TYP_INT:
{
ins = INS_i32_const;
GenTreeIntConCommon* con = treeNode->AsIntConCommon();
bits = con->IntegralValue();
break;
}
case TYP_LONG:
{
ins = INS_i64_const;
GenTreeIntConCommon* con = treeNode->AsIntConCommon();
bits = con->IntegralValue();
break;
}
case TYP_FLOAT:
{
ins = INS_f32_const;
GenTreeDblCon* con = treeNode->AsDblCon();
double value = con->DconValue();
memcpy(&bits, &value, sizeof(double));
break;
}
case TYP_DOUBLE:
{
ins = INS_f64_const;
GenTreeDblCon* con = treeNode->AsDblCon();
double value = con->DconValue();
memcpy(&bits, &value, sizeof(double));
break;
}
default:
unreached();
}

// The IF_ for the selected instruction, i.e. IF_F64, determines how these bits are emitted
GetEmitter()->emitIns_I(ins, emitTypeSize(treeNode), bits);
genProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForShift: Generate code for a shift or rotate operator
//
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ IF_DEF(OPCODE, IS_NONE, NONE) // <opcode>
IF_DEF(BLOCK, IS_NONE, NONE) // <opcode> <0x40>
IF_DEF(LABEL, IS_NONE, NONE) // <ULEB128 immediate>
IF_DEF(ULEB128, IS_NONE, NONE) // <opcode> <ULEB128 immediate>
IF_DEF(SLEB128, IS_NONE, NONE) // <opcode> <LEB128 immediate (signed)>
IF_DEF(F32, IS_NONE, NONE) // <opcode> <f32 immediate (stored as 64-bit integer constant)>
IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bit integer constant)>
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)

#undef IF_DEF
Expand Down
168 changes: 153 additions & 15 deletions src/coreclr/jit/emitwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void emitter::emitIns(instruction ins)
//------------------------------------------------------------------------
// emitIns_I: Emit an instruction with an immediate operand.
//
void emitter::emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm)
void emitter::emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm)
{
instrDesc* id = emitNewInstrSC(attr, imm);
insFormat fmt = emitInsFormat(ins);
Expand Down Expand Up @@ -65,7 +65,7 @@ void emitter::emitIns_R(instruction ins, emitAttr attr, regNumber reg)
NYI_WASM("emitIns_R");
}

void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm)
void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm)
{
NYI_WASM("emitIns_R_I");
}
Expand Down Expand Up @@ -126,7 +126,13 @@ size_t emitter::emitSizeOfInsDsc(instrDesc* id) const
return sizeof(instrDesc);
}

static unsigned SizeOfULEB128(uint64_t value)
unsigned emitter::emitGetAlignHintLog2(const instrDesc* id)
{
// FIXME
return 0;
}

unsigned emitter::SizeOfULEB128(uint64_t value)
{
// bits_to_encode = (data != 0) ? 64 - CLZ(x) : 1 = 64 - CLZ(data | 1)
// bytes = ceil(bits_to_encode / 7.0); = (6 + bits_to_encode) / 7
Expand All @@ -136,6 +142,13 @@ static unsigned SizeOfULEB128(uint64_t value)
return (x * 37) >> 8;
}

unsigned emitter::SizeOfSLEB128(int64_t value)
{
// The same as SizeOfULEB128 calculation but we have to account for the sign bit.
unsigned x = 1 + 6 + 64 - (unsigned)BitOperations::LeadingZeroCount((uint64_t)(value ^ (value >> 63)) | 1UL);
return (x * 37) >> 8;
}

unsigned emitter::instrDesc::idCodeSize() const
{
#ifdef TARGET_WASM32
Expand All @@ -156,15 +169,28 @@ unsigned emitter::instrDesc::idCodeSize() const
break;
case IF_LABEL:
assert(!idIsCnsReloc());
size = SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
size = SizeOfULEB128(emitGetInsSC(this));
break;
case IF_ULEB128:
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this));
break;
case IF_SLEB128:
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfSLEB128(emitGetInsSC(this));
break;
case IF_F32:
size += 4;
break;
case IF_F64:
size += 8;
break;
case IF_MEMARG:
size += 1; // The alignment hint byte.
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
{
uint64_t align = emitGetAlignHintLog2(this);
assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories.
size += SizeOfULEB128(align);
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this));
break;
}
default:
unreached();
}
Expand All @@ -176,6 +202,56 @@ void emitter::emitSetShortJump(instrDescJmp* id)
NYI_WASM("emitSetShortJump");
}

size_t emitter::emitOutputULEB128(uint8_t* destination, uint64_t value)
{
uint8_t* buffer = destination + writeableOffset;
if (value >= 0x80)
{
int pos = 0;
do
{
buffer[pos++] = (uint8_t)((value & 0x7F) | ((value >= 0x80) ? 0x80u : 0));
value >>= 7;
} while (value > 0);

return pos;
}
else
{
buffer[0] = (uint8_t)value;
return 1;
}
}

size_t emitter::emitOutputSLEB128(uint8_t* destination, int64_t value)
{
uint8_t* buffer = destination + writeableOffset;
bool cont = true;
int pos = 0;
while (cont)
{
uint8_t b = ((uint8_t)value & 0x7F);
value >>= 7;
bool isSignBitSet = (b & 0x40) != 0;
if ((value == 0 && !isSignBitSet) || (value == -1 && isSignBitSet))
{
cont = false;
}
else
{
b |= 0x80;
}
buffer[pos++] = b;
}
return pos;
}

size_t emitter::emitRawBytes(uint8_t* destination, const void* source, size_t count)
{
memcpy(destination + writeableOffset, source, count);
return count;
}

size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
{
BYTE* dst = *dp;
Expand All @@ -191,16 +267,61 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
break;
case IF_BLOCK:
dst += emitOutputByte(dst, opcode);
dst += emitOutputByte(dst, 0x40);
dst += emitOutputByte(dst, 0x40 /* block type of void */);
break;
case IF_ULEB128:
{
dst += emitOutputByte(dst, opcode);
cnsval_ssize_t constant = emitGetInsSC(id);
dst += emitOutputULEB128(dst, (uint64_t)constant);
break;
}
case IF_SLEB128:
{
dst += emitOutputByte(dst, opcode);
cnsval_ssize_t constant = emitGetInsSC(id);
dst += emitOutputSLEB128(dst, (int64_t)constant);
break;
}
case IF_F32:
{
dst += emitOutputByte(dst, opcode);
// Reinterpret the bits as a double constant and then truncate it to f32,
// then finally copy the raw truncated f32 bits to the output.
cnsval_ssize_t bits = emitGetInsSC(id);
double value;
float truncated;
memcpy(&value, &bits, sizeof(double));
truncated = FloatingPointUtils::convertToSingle(value);
dst += emitRawBytes(dst, &truncated, sizeof(float));
break;
}
case IF_F64:
{
dst += emitOutputByte(dst, opcode);
// TODO-WASM: emit uleb128
// The int64 bits are actually a double constant we can copy directly
// to the output stream.
cnsval_ssize_t bits = emitGetInsSC(id);
dst += emitRawBytes(dst, &bits, sizeof(cnsval_ssize_t));
break;
}
case IF_LABEL:
// TODO-WASM: emit uleb128
NYI_WASM("emitOutputInstr IF_LABEL");
break;
case IF_MEMARG:
{
dst += emitOutputByte(dst, opcode);
uint64_t align = emitGetAlignHintLog2(id);
uint64_t offset = emitGetInsSC(id);
assert(align <= UINT32_MAX); // spec says memarg alignment is u32
assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories.
dst += emitOutputULEB128(dst, align);
dst += emitOutputULEB128(dst, offset);
break;
}
default:
NYI_WASM("emitOutputInstr");
break;
}

#ifdef DEBUG
Expand Down Expand Up @@ -303,17 +424,34 @@ void emitter::emitDispIns(
case IF_LABEL:
case IF_ULEB128:
{
target_size_t imm = emitGetInsSC(id);
printf(" %u", imm);
cnsval_ssize_t imm = emitGetInsSC(id);
printf(" %llu", (uint64_t)imm);
}
break;

case IF_SLEB128:
{
cnsval_ssize_t imm = emitGetInsSC(id);
printf(" %lli", (int64_t)imm);
}
break;

case IF_F32:
case IF_F64:
{
cnsval_ssize_t bits = emitGetInsSC(id);
double value;
memcpy(&value, &bits, sizeof(double));
printf(" %f", value);
}
break;

case IF_MEMARG:
{
// TODO-WASM: decide what our strategy for alignment hints is and display these accordingly.
unsigned log2align = 1;
target_size_t offset = emitGetInsSC(id);
printf(" %u %u", log2align, offset);
unsigned log2align = emitGetAlignHintLog2(id) + 1;
cnsval_ssize_t offset = emitGetInsSC(id);
printf(" %u %llu", log2align, (uint64_t)offset);
}
break;

Expand Down
13 changes: 11 additions & 2 deletions src/coreclr/jit/emitwasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ void emitDispInst(instruction ins);

public:
void emitIns(instruction ins);
void emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm);
void emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm);
void emitIns_S(instruction ins, emitAttr attr, int varx, int offs);
void emitIns_R(instruction ins, emitAttr attr, regNumber reg);

void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm);
void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm);
void emitIns_Mov(instruction ins, emitAttr attr, regNumber dstReg, regNumber srcReg, bool canSkip);
void emitIns_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2);

void emitIns_S_R(instruction ins, emitAttr attr, regNumber ireg, int varx, int offs);

static unsigned SizeOfULEB128(uint64_t value);
static unsigned SizeOfSLEB128(int64_t value);

static unsigned emitGetAlignHintLog2(const instrDesc* id);

/************************************************************************/
/* Private members that deal with target-dependent instr. descriptors */
/************************************************************************/
Expand All @@ -51,3 +56,7 @@ instrDesc* emitNewInstrCallInd(int argCnt,
bool emitInsIsStore(instruction ins);

insFormat emitInsFormat(instruction ins);

size_t emitOutputULEB128(uint8_t* destination, uint64_t value);
size_t emitOutputSLEB128(uint8_t* destination, int64_t value);
size_t emitRawBytes(uint8_t* destination, const void* source, size_t count);
10 changes: 6 additions & 4 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5175,11 +5175,13 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)

case GT_CNS_LNG:
case GT_CNS_INT:
// TODO-WASM: needs tuning based on the [S]LEB128 encoding size.
NYI_WASM("GT_CNS_LNG/GT_CNS_INT costing");
costEx = 0;
costSz = 0;
{
GenTreeIntConCommon* con = tree->AsIntConCommon();
int64_t imm = con->IntegralValue();
costEx = 1;
costSz = 1 + (int)emitter::SizeOfSLEB128(imm);
goto COMMON_CNS;
}
#else
case GT_CNS_STR:
case GT_CNS_LNG:
Expand Down
Loading
Loading