WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 74cf618

Browse files
Copilotkg
andauthored
[Wasm RyuJit] Add constant emitting support (#122162)
Adds infrastructure for emitting integer and floating-point constants in the WebAssembly RyuJIT backend. This enables the backend to generate constant loading instructions (i32.const, i64.const, f32.const, f64.const) when constant nodes appear in the IR. - Fixes #122161 Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Katelyn Gadd <[email protected]>
1 parent 04df455 commit 74cf618

File tree

9 files changed

+252
-26
lines changed

9 files changed

+252
-26
lines changed

src/coreclr/jit/codegen.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,10 @@ class CodeGen final : public CodeGenInterface
759759
void genCodeForBinary(GenTreeOp* treeNode);
760760
bool genIsSameLocalVar(GenTree* tree1, GenTree* tree2);
761761

762+
#if defined(TARGET_WASM)
763+
void genCodeForConstant(GenTree* treeNode);
764+
#endif
765+
762766
#if defined(TARGET_X86)
763767
void genCodeForLongUMod(GenTreeOp* node);
764768
#endif // TARGET_X86

src/coreclr/jit/codegencommon.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,6 +2213,11 @@ void CodeGen::genEmitMachineCode()
22132213
//
22142214
void CodeGen::genEmitUnwindDebugGCandEH()
22152215
{
2216+
#ifdef TARGET_WASM
2217+
// TODO-WASM: Fix this phase causing an assertion failure even for methods with no GC locals or EH clauses
2218+
return;
2219+
#endif
2220+
22162221
/* Now that the code is issued, we can finalize and emit the unwind data */
22172222

22182223
compiler->unwindEmit(*codePtr, coldCodePtr);

src/coreclr/jit/codegenwasm.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
269269
// Do nothing; this node is a marker for debug info.
270270
break;
271271

272+
case GT_CNS_INT:
273+
case GT_CNS_LNG:
274+
case GT_CNS_DBL:
275+
genCodeForConstant(treeNode);
276+
break;
277+
272278
default:
273279
#ifdef DEBUG
274280
NYIRAW(GenTree::OpName(treeNode->OperGet()));
@@ -537,6 +543,60 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode)
537543
genProduceReg(treeNode);
538544
}
539545

546+
//------------------------------------------------------------------------
547+
// genCodeForConstant: Generate code for an integer or floating point constant
548+
//
549+
// Arguments:
550+
// treeNode - The constant.
551+
//
552+
void CodeGen::genCodeForConstant(GenTree* treeNode)
553+
{
554+
instruction ins;
555+
cnsval_ssize_t bits;
556+
var_types type = treeNode->TypeIs(TYP_REF, TYP_BYREF) ? TYP_I_IMPL : treeNode->TypeGet();
557+
static_assert(sizeof(cnsval_ssize_t) >= sizeof(double));
558+
559+
switch (type)
560+
{
561+
case TYP_INT:
562+
{
563+
ins = INS_i32_const;
564+
GenTreeIntConCommon* con = treeNode->AsIntConCommon();
565+
bits = con->IntegralValue();
566+
break;
567+
}
568+
case TYP_LONG:
569+
{
570+
ins = INS_i64_const;
571+
GenTreeIntConCommon* con = treeNode->AsIntConCommon();
572+
bits = con->IntegralValue();
573+
break;
574+
}
575+
case TYP_FLOAT:
576+
{
577+
ins = INS_f32_const;
578+
GenTreeDblCon* con = treeNode->AsDblCon();
579+
double value = con->DconValue();
580+
memcpy(&bits, &value, sizeof(double));
581+
break;
582+
}
583+
case TYP_DOUBLE:
584+
{
585+
ins = INS_f64_const;
586+
GenTreeDblCon* con = treeNode->AsDblCon();
587+
double value = con->DconValue();
588+
memcpy(&bits, &value, sizeof(double));
589+
break;
590+
}
591+
default:
592+
unreached();
593+
}
594+
595+
// The IF_ for the selected instruction, i.e. IF_F64, determines how these bits are emitted
596+
GetEmitter()->emitIns_I(ins, emitTypeSize(treeNode), bits);
597+
genProduceReg(treeNode);
598+
}
599+
540600
//------------------------------------------------------------------------
541601
// genCodeForShift: Generate code for a shift or rotate operator
542602
//

src/coreclr/jit/emitfmtswasm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ IF_DEF(OPCODE, IS_NONE, NONE) // <opcode>
3131
IF_DEF(BLOCK, IS_NONE, NONE) // <opcode> <0x40>
3232
IF_DEF(LABEL, IS_NONE, NONE) // <ULEB128 immediate>
3333
IF_DEF(ULEB128, IS_NONE, NONE) // <opcode> <ULEB128 immediate>
34+
IF_DEF(SLEB128, IS_NONE, NONE) // <opcode> <LEB128 immediate (signed)>
35+
IF_DEF(F32, IS_NONE, NONE) // <opcode> <f32 immediate (stored as 64-bit integer constant)>
36+
IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bit integer constant)>
3437
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
3538

3639
#undef IF_DEF

src/coreclr/jit/emitwasm.cpp

Lines changed: 153 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ void emitter::emitIns(instruction ins)
2929
//------------------------------------------------------------------------
3030
// emitIns_I: Emit an instruction with an immediate operand.
3131
//
32-
void emitter::emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm)
32+
void emitter::emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm)
3333
{
3434
instrDesc* id = emitNewInstrSC(attr, imm);
3535
insFormat fmt = emitInsFormat(ins);
@@ -59,7 +59,7 @@ void emitter::emitIns_R(instruction ins, emitAttr attr, regNumber reg)
5959
NYI_WASM("emitIns_R");
6060
}
6161

62-
void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm)
62+
void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm)
6363
{
6464
NYI_WASM("emitIns_R_I");
6565
}
@@ -124,7 +124,13 @@ size_t emitter::emitSizeOfInsDsc(instrDesc* id) const
124124
return sizeof(instrDesc);
125125
}
126126

127-
static unsigned SizeOfULEB128(uint64_t value)
127+
unsigned emitter::emitGetAlignHintLog2(const instrDesc* id)
128+
{
129+
// FIXME
130+
return 0;
131+
}
132+
133+
unsigned emitter::SizeOfULEB128(uint64_t value)
128134
{
129135
// bits_to_encode = (data != 0) ? 64 - CLZ(x) : 1 = 64 - CLZ(data | 1)
130136
// bytes = ceil(bits_to_encode / 7.0); = (6 + bits_to_encode) / 7
@@ -134,6 +140,13 @@ static unsigned SizeOfULEB128(uint64_t value)
134140
return (x * 37) >> 8;
135141
}
136142

143+
unsigned emitter::SizeOfSLEB128(int64_t value)
144+
{
145+
// The same as SizeOfULEB128 calculation but we have to account for the sign bit.
146+
unsigned x = 1 + 6 + 64 - (unsigned)BitOperations::LeadingZeroCount((uint64_t)(value ^ (value >> 63)) | 1UL);
147+
return (x * 37) >> 8;
148+
}
149+
137150
unsigned emitter::instrDesc::idCodeSize() const
138151
{
139152
#ifdef TARGET_WASM32
@@ -154,15 +167,28 @@ unsigned emitter::instrDesc::idCodeSize() const
154167
break;
155168
case IF_LABEL:
156169
assert(!idIsCnsReloc());
157-
size = SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
170+
size = SizeOfULEB128(emitGetInsSC(this));
158171
break;
159172
case IF_ULEB128:
160-
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
173+
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this));
174+
break;
175+
case IF_SLEB128:
176+
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfSLEB128(emitGetInsSC(this));
177+
break;
178+
case IF_F32:
179+
size += 4;
180+
break;
181+
case IF_F64:
182+
size += 8;
161183
break;
162184
case IF_MEMARG:
163-
size += 1; // The alignment hint byte.
164-
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast<target_size_t>(emitGetInsSC(this)));
185+
{
186+
uint64_t align = emitGetAlignHintLog2(this);
187+
assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories.
188+
size += SizeOfULEB128(align);
189+
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this));
165190
break;
191+
}
166192
default:
167193
unreached();
168194
}
@@ -174,6 +200,56 @@ void emitter::emitSetShortJump(instrDescJmp* id)
174200
NYI_WASM("emitSetShortJump");
175201
}
176202

203+
size_t emitter::emitOutputULEB128(uint8_t* destination, uint64_t value)
204+
{
205+
uint8_t* buffer = destination + writeableOffset;
206+
if (value >= 0x80)
207+
{
208+
int pos = 0;
209+
do
210+
{
211+
buffer[pos++] = (uint8_t)((value & 0x7F) | ((value >= 0x80) ? 0x80u : 0));
212+
value >>= 7;
213+
} while (value > 0);
214+
215+
return pos;
216+
}
217+
else
218+
{
219+
buffer[0] = (uint8_t)value;
220+
return 1;
221+
}
222+
}
223+
224+
size_t emitter::emitOutputSLEB128(uint8_t* destination, int64_t value)
225+
{
226+
uint8_t* buffer = destination + writeableOffset;
227+
bool cont = true;
228+
int pos = 0;
229+
while (cont)
230+
{
231+
uint8_t b = ((uint8_t)value & 0x7F);
232+
value >>= 7;
233+
bool isSignBitSet = (b & 0x40) != 0;
234+
if ((value == 0 && !isSignBitSet) || (value == -1 && isSignBitSet))
235+
{
236+
cont = false;
237+
}
238+
else
239+
{
240+
b |= 0x80;
241+
}
242+
buffer[pos++] = b;
243+
}
244+
return pos;
245+
}
246+
247+
size_t emitter::emitRawBytes(uint8_t* destination, const void* source, size_t count)
248+
{
249+
memcpy(destination + writeableOffset, source, count);
250+
return count;
251+
}
252+
177253
size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
178254
{
179255
BYTE* dst = *dp;
@@ -189,16 +265,61 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
189265
break;
190266
case IF_BLOCK:
191267
dst += emitOutputByte(dst, opcode);
192-
dst += emitOutputByte(dst, 0x40);
268+
dst += emitOutputByte(dst, 0x40 /* block type of void */);
193269
break;
194270
case IF_ULEB128:
271+
{
272+
dst += emitOutputByte(dst, opcode);
273+
cnsval_ssize_t constant = emitGetInsSC(id);
274+
dst += emitOutputULEB128(dst, (uint64_t)constant);
275+
break;
276+
}
277+
case IF_SLEB128:
278+
{
279+
dst += emitOutputByte(dst, opcode);
280+
cnsval_ssize_t constant = emitGetInsSC(id);
281+
dst += emitOutputSLEB128(dst, (int64_t)constant);
282+
break;
283+
}
284+
case IF_F32:
285+
{
286+
dst += emitOutputByte(dst, opcode);
287+
// Reinterpret the bits as a double constant and then truncate it to f32,
288+
// then finally copy the raw truncated f32 bits to the output.
289+
cnsval_ssize_t bits = emitGetInsSC(id);
290+
double value;
291+
float truncated;
292+
memcpy(&value, &bits, sizeof(double));
293+
truncated = FloatingPointUtils::convertToSingle(value);
294+
dst += emitRawBytes(dst, &truncated, sizeof(float));
295+
break;
296+
}
297+
case IF_F64:
298+
{
195299
dst += emitOutputByte(dst, opcode);
196-
// TODO-WASM: emit uleb128
300+
// The int64 bits are actually a double constant we can copy directly
301+
// to the output stream.
302+
cnsval_ssize_t bits = emitGetInsSC(id);
303+
dst += emitRawBytes(dst, &bits, sizeof(cnsval_ssize_t));
197304
break;
305+
}
198306
case IF_LABEL:
199-
// TODO-WASM: emit uleb128
307+
NYI_WASM("emitOutputInstr IF_LABEL");
308+
break;
309+
case IF_MEMARG:
310+
{
311+
dst += emitOutputByte(dst, opcode);
312+
uint64_t align = emitGetAlignHintLog2(id);
313+
uint64_t offset = emitGetInsSC(id);
314+
assert(align <= UINT32_MAX); // spec says memarg alignment is u32
315+
assert(align < 64); // spec says align > 2^6 produces a memidx for multiple memories.
316+
dst += emitOutputULEB128(dst, align);
317+
dst += emitOutputULEB128(dst, offset);
318+
break;
319+
}
200320
default:
201321
NYI_WASM("emitOutputInstr");
322+
break;
202323
}
203324

204325
#ifdef DEBUG
@@ -301,17 +422,34 @@ void emitter::emitDispIns(
301422
case IF_LABEL:
302423
case IF_ULEB128:
303424
{
304-
target_size_t imm = emitGetInsSC(id);
305-
printf(" %u", imm);
425+
cnsval_ssize_t imm = emitGetInsSC(id);
426+
printf(" %llu", (uint64_t)imm);
427+
}
428+
break;
429+
430+
case IF_SLEB128:
431+
{
432+
cnsval_ssize_t imm = emitGetInsSC(id);
433+
printf(" %lli", (int64_t)imm);
434+
}
435+
break;
436+
437+
case IF_F32:
438+
case IF_F64:
439+
{
440+
cnsval_ssize_t bits = emitGetInsSC(id);
441+
double value;
442+
memcpy(&value, &bits, sizeof(double));
443+
printf(" %f", value);
306444
}
307445
break;
308446

309447
case IF_MEMARG:
310448
{
311449
// TODO-WASM: decide what our strategy for alignment hints is and display these accordingly.
312-
unsigned log2align = 1;
313-
target_size_t offset = emitGetInsSC(id);
314-
printf(" %u %u", log2align, offset);
450+
unsigned log2align = emitGetAlignHintLog2(id) + 1;
451+
cnsval_ssize_t offset = emitGetInsSC(id);
452+
printf(" %u %llu", log2align, (uint64_t)offset);
315453
}
316454
break;
317455

src/coreclr/jit/emitwasm.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,21 @@ void emitDispInst(instruction ins);
1717

1818
public:
1919
void emitIns(instruction ins);
20-
void emitIns_I(instruction ins, emitAttr attr, target_ssize_t imm);
20+
void emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm);
2121
void emitIns_S(instruction ins, emitAttr attr, int varx, int offs);
2222
void emitIns_R(instruction ins, emitAttr attr, regNumber reg);
2323

24-
void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm);
24+
void emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, cnsval_ssize_t imm);
2525
void emitIns_Mov(instruction ins, emitAttr attr, regNumber dstReg, regNumber srcReg, bool canSkip);
2626
void emitIns_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2);
2727

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

30+
static unsigned SizeOfULEB128(uint64_t value);
31+
static unsigned SizeOfSLEB128(int64_t value);
32+
33+
static unsigned emitGetAlignHintLog2(const instrDesc* id);
34+
3035
/************************************************************************/
3136
/* Private members that deal with target-dependent instr. descriptors */
3237
/************************************************************************/
@@ -51,3 +56,7 @@ instrDesc* emitNewInstrCallInd(int argCnt,
5156
bool emitInsIsStore(instruction ins);
5257

5358
insFormat emitInsFormat(instruction ins);
59+
60+
size_t emitOutputULEB128(uint8_t* destination, uint64_t value);
61+
size_t emitOutputSLEB128(uint8_t* destination, int64_t value);
62+
size_t emitRawBytes(uint8_t* destination, const void* source, size_t count);

src/coreclr/jit/gentree.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5175,11 +5175,13 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
51755175

51765176
case GT_CNS_LNG:
51775177
case GT_CNS_INT:
5178-
// TODO-WASM: needs tuning based on the [S]LEB128 encoding size.
5179-
NYI_WASM("GT_CNS_LNG/GT_CNS_INT costing");
5180-
costEx = 0;
5181-
costSz = 0;
5178+
{
5179+
GenTreeIntConCommon* con = tree->AsIntConCommon();
5180+
int64_t imm = con->IntegralValue();
5181+
costEx = 1;
5182+
costSz = 1 + (int)emitter::SizeOfSLEB128(imm);
51825183
goto COMMON_CNS;
5184+
}
51835185
#else
51845186
case GT_CNS_STR:
51855187
case GT_CNS_LNG:

0 commit comments

Comments
 (0)