Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e93e57d
Revert wasm method stub method in crossgen to allow invoking the actu…
adamperlin Dec 9, 2025
4b991da
WIP add args as locals to the beginning of method epilog in Wasm codegen
adamperlin Dec 10, 2025
5933e67
Multiple fixes for emitting local declarations for incoming args
adamperlin Dec 11, 2025
7b8e339
jit-format
adamperlin Dec 11, 2025
78b7c70
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/wasm…
adamperlin Dec 11, 2025
9fbfb86
Generate locals only for non-args
adamperlin Dec 11, 2025
7d12af0
Update src/coreclr/jit/emit.h
adamperlin Dec 11, 2025
4b91b3a
Update src/coreclr/jit/emitfmtswasm.h
adamperlin Dec 11, 2025
183851f
Update src/coreclr/jit/codegencommon.cpp
adamperlin Dec 11, 2025
c9d3046
Update src/coreclr/jit/emitwasm.cpp
adamperlin Dec 11, 2025
0482ee3
Remove incorrect noway_asserts
adamperlin Dec 11, 2025
dfb0627
Remove empty ifdef block
adamperlin Dec 11, 2025
b237c17
Apply some copilot review suggestions
adamperlin Dec 11, 2025
19c9eaf
Address some review feedback
adamperlin Dec 12, 2025
76a15f2
Finish addressing review feedback
adamperlin Dec 12, 2025
d32f695
Fix instGen placement
adamperlin Dec 12, 2025
f3e2f82
Add block->IsLast() guard for emitting end instruction in Wasm genFnE…
adamperlin Dec 13, 2025
0820a24
Emit correct local count for current state of Wasm codegen
adamperlin Dec 13, 2025
78945a3
Address some more review feedback
adamperlin Dec 16, 2025
9a3c21a
Merge branch 'main' of github.com:dotnet/runtime into wasm-arg-initia…
adamperlin Dec 16, 2025
ddb4801
Address additional review feedback
adamperlin Dec 17, 2025
704a4a7
Merge branch 'main' of github.com:dotnet/runtime into wasm-arg-initia…
adamperlin Dec 17, 2025
7bb7cfc
Apply suggestion from @SingleAccretion
adamperlin Dec 17, 2025
06207d5
Address additional review feedback; properly guard access to wasm-spe…
adamperlin Dec 17, 2025
c52da98
Address additional review feedback
adamperlin Dec 17, 2025
9373181
Fix condition for `end` generation in src/coreclr/jit/codegenwasm.cpp
adamperlin Dec 17, 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 @@ -13,6 +13,7 @@
#include "compiler.h" // temporary??
#include "regset.h"
#include "jitgcinfo.h"
#include "wasmtypesdef.h"

class CodeGen final : public CodeGenInterface
{
Expand Down Expand Up @@ -589,6 +590,9 @@ class CodeGen final : public CodeGenInterface
void genReserveProlog(BasicBlock* block); // currently unused
void genReserveEpilog(BasicBlock* block);
void genFnProlog();
#if defined(TARGET_WASM)
void genWasmLocals();
#endif
void genFnEpilog(BasicBlock* block);

void genReserveFuncletProlog(BasicBlock* block);
Expand Down
24 changes: 23 additions & 1 deletion src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5650,13 +5650,35 @@ void CodeGen::genFnProlog()
#endif // defined(DEBUG) && defined(TARGET_XARCH)

#else // defined(TARGET_WASM)
// TODO-WASM: prolog zeroing, shadow stack maintenance
// TODO-WASM: proper local count, local declarations, and shadow stack maintenance
assert(compiler->info.compLocalsCount >= compiler->info.compArgsCount);
GetEmitter()->emitIns_I(INS_local_cnt, EA_8BYTE, compiler->info.compLocalsCount - compiler->info.compArgsCount);
genWasmLocals();
GetEmitter()->emitMarkPrologEnd();
#endif // !defined(TARGET_WASM)

GetEmitter()->emitEndProlog();
}

#if defined(TARGET_WASM)

//------------------------------------------------------------------------
// genWasmLocals: generate wasm locals for all locals
//
// TODO-WASM: re-evaluate this, we may not want a 1:1 mapping of locals to wasm locals
// TODO-WASM: pre-declare all "register" locals
void CodeGen::genWasmLocals()
{
for (unsigned i = 0; i < compiler->info.compLocalsCount - compiler->info.compArgsCount; i++)
{
LclVarDsc* varDsc = compiler->lvaGetDesc(i);
assert(varDsc->lvIsParam);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The loop index logic is incorrect. The loop variable 'i' ranges from 0 to (compLocalsCount - compArgsCount - 1), but lvaGetDesc(i) is being called with 'i'. This will always retrieve the first (compLocalsCount - compArgsCount) local variable descriptors, which are typically parameters. To iterate over non-parameter locals, the loop should use lvaGetDesc(i + compArgsCount) or start i at compArgsCount and iterate while i < compLocalsCount.

Suggested change
for (unsigned i = 0; i < compiler->info.compLocalsCount - compiler->info.compArgsCount; i++)
{
LclVarDsc* varDsc = compiler->lvaGetDesc(i);
assert(varDsc->lvIsParam);
for (unsigned i = compiler->info.compArgsCount; i < compiler->info.compLocalsCount; i++)
{
LclVarDsc* varDsc = compiler->lvaGetDesc(i);
assert(!varDsc->lvIsParam);

Copilot uses AI. Check for mistakes.
emitter::instWasmValueType type = GetEmitter()->genWasmTypeFromVarType(varDsc->TypeGet());
GetEmitter()->emitIns_I_Ty(INS_local_decl, 1, type);
}
}
#endif

#if !defined(TARGET_WASM)

//----------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ void CodeGen::genFnEpilog(BasicBlock* block)
// TODO-WASM-CQ: do not emit "return" in case this is the last block

instGen(INS_return);
instGen(INS_end);
Copy link
Member

Choose a reason for hiding this comment

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

Is this end required at the end of methods? We can have multiple epilogs (though we might want to reconsider)... and there's no guarantee even with one epilog that the epilog will be at the end of the instruction stream.

Copy link
Contributor Author

@adamperlin adamperlin Dec 11, 2025

Choose a reason for hiding this comment

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

Yeah it is required. If the epilog isn't guaranteed to be the end, where would be a safe place to ensure that the end comes at the actual end of the instruction stream for a method?

Copy link
Member

Choose a reason for hiding this comment

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

Probably at the end of genCodeForBBlist or similar.

Copy link
Contributor

@SingleAccretion SingleAccretion Dec 11, 2025

Choose a reason for hiding this comment

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

genFnEpilog is a BasicBlock* parameter. I am pretty sure you can just check block->IsLast() and switch whether you return return or end based on that. That would address the TODO above as well.

Copy link
Member

Choose a reason for hiding this comment

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

It might not be the actual last block when we have funclets.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point. This case can be detected by checking if the next block is a funclet entry.

Copy link
Contributor Author

@adamperlin adamperlin Dec 13, 2025

Choose a reason for hiding this comment

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

Adding this at the end of genCodeForBBList proved a little bit tricky because of instruction groups. It does look like we track the last used instruction group and could grab that and append after we've finished all method code? Will the last used group be guaranteed to be at the end of the method though? Also, what is the best way to detect if the next block is a funclet? For now I've added an IsLast check to genFnEpilog with a comment that we need to revisit if we're doing funclets.

}

void CodeGen::genCaptureFuncletPrologEpilogInfo()
Expand Down
50 changes: 49 additions & 1 deletion src/coreclr/jit/emit.h
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ class emitter
BasicBlock* idTargetBlock; // Target block for branches
};

#if defined(TARGET_WASM)
#include "wasmtypesdef.h"
#endif

#ifdef TARGET_ARM
unsigned insEncodeSetFlags(insFlags sf);

Expand Down Expand Up @@ -892,6 +896,9 @@ class emitter
unsigned _idLclFPBase : 1; // access a local on stack - SP based offset
insOpts _idInsOpt : 3; // options for Load/Store instructions
#endif
#ifdef TARGET_WASM
unsigned _idLclDecl : 1; // is this a local declaration?
#endif

////////////////////////////////////////////////////////////////////////
// Space taken up to here:
Expand Down Expand Up @@ -919,7 +926,7 @@ class emitter
#elif defined(TARGET_AMD64)
#define ID_EXTRA_BITFIELD_BITS (20)
#elif defined(TARGET_WASM)
#define ID_EXTRA_BITFIELD_BITS (-4)
#define ID_EXTRA_BITFIELD_BITS (-3)
#else
#error Unsupported or unset target architecture
#endif
Expand Down Expand Up @@ -1296,6 +1303,18 @@ class emitter
assert(reg == _idReg1);
}

#ifdef TARGET_WASM
bool idIsLclVarDecl() const
{
return (_idLclDecl != 0);
}

void idSetIsLclVarDecl(bool isDecl)
{
_idLclDecl = isDecl ? 1 : 0;
}
#endif

#ifdef TARGET_ARM64
GCtype idGCrefReg2() const
{
Expand Down Expand Up @@ -2335,6 +2354,25 @@ class emitter
};
#endif

#if defined(TARGET_WASM)
struct instrDescLclVarDecl : instrDesc
{
instrDescLclVarDecl() = delete;
cnsval_ssize_t lclCnt;
instWasmValueType lclType;

void idLclType(instWasmValueType type)
{
lclType = type;
}

void idLclCnt(cnsval_ssize_t cnt)
{
lclCnt = cnt;
}
};
#endif // TARGET_WASM

#ifdef TARGET_RISCV64
struct instrDescLoadImm : instrDescCns
{
Expand Down Expand Up @@ -3293,6 +3331,11 @@ class emitter
instrDesc* emitNewInstrCns(emitAttr attr, cnsval_ssize_t cns);
instrDesc* emitNewInstrDsp(emitAttr attr, target_ssize_t dsp);
instrDesc* emitNewInstrCnsDsp(emitAttr attr, target_ssize_t cns, int dsp);

#ifdef TARGET_WASM

#endif

#ifdef TARGET_ARM
instrDesc* emitNewInstrReloc(emitAttr attr, BYTE* addr);
#endif // TARGET_ARM
Expand Down Expand Up @@ -4108,6 +4151,11 @@ inline emitter::instrDesc* emitter::emitNewInstrSC(emitAttr attr, cnsval_ssize_t
}
}

#ifdef TARGET_WASM
#include "wasmtypesdef.h"

#endif

#ifdef TARGET_ARM

inline emitter::instrDesc* emitter::emitNewInstrReloc(emitAttr attr, BYTE* addr)
Expand Down
20 changes: 11 additions & 9 deletions src/coreclr/jit/emitfmtswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ enum ID_OPS
// (unused)
//////////////////////////////////////////////////////////////////////////////

IF_DEF(NONE, IS_NONE, NONE)
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>)
IF_DEF(NONE, IS_NONE, NONE)
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>)
IF_DEF(LOCAL_CNT, IS_NONE, NONE) // <ULEB128 immediate>
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
85 changes: 85 additions & 0 deletions src/coreclr/jit/emitwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,51 @@ bool emitter::emitInsIsStore(instruction ins)
return false;
}

emitter::instrDesc* emitter::emitNewInstrLclVarDecl(emitAttr attr, cnsval_ssize_t localCount, instWasmValueType type)
{
instrDescLclVarDecl* id = static_cast<instrDescLclVarDecl*>(emitAllocAnyInstr(sizeof(instrDescLclVarDecl), attr));
id->idSetIsLclVarDecl(true);
id->idLclCnt(localCount);
id->idLclType(type);

return id;
}

//-----------------------------------------------------------------------------------
// emitIns_I: Emit an instruction with an immediate operand and an encoded value type
//
void emitter::emitIns_I_Ty(instruction ins, cnsval_ssize_t imm, emitter::instWasmValueType valType)
{
instrDesc* id = this->emitNewInstrLclVarDecl(EA_8BYTE, imm, valType);
insFormat fmt = this->emitInsFormat(ins);

id->idIns(ins);
id->idInsFmt(fmt);

this->dispIns(id);
this->appendToCurIG(id);
}

emitter::instWasmValueType emitter::emitGetLclVarDeclType(instrDesc* id)
{
if (!id->idIsLclVarDecl())
{
noway_assert("not a LclVarDecl instrDesc");
}

return static_cast<instrDescLclVarDecl*>(id)->lclType;
}

cnsval_ssize_t emitter::emitGetLclVarDeclCount(instrDesc* id)
{
if (!id->idIsLclVarDecl())
{
noway_assert("not a LclVarDecl instrDesc");
}

return static_cast<instrDescLclVarDecl*>(id)->lclCnt;
}

emitter::insFormat emitter::emitInsFormat(instruction ins)
{
static_assert(IF_COUNT < 255);
Expand Down Expand Up @@ -155,6 +200,11 @@ size_t emitter::emitSizeOfInsDsc(instrDesc* id) const
return sizeof(instrDescCns);
}

if (id->idIsLclVarDecl())
{
return sizeof(instrDescLclVarDecl);
}

return sizeof(instrDesc);
}

Expand Down Expand Up @@ -200,9 +250,17 @@ unsigned emitter::instrDesc::idCodeSize() const
size += 1;
break;
case IF_LABEL:
case IF_LOCAL_CNT:
assert(!idIsCnsReloc());
size = SizeOfULEB128(emitGetInsSC(this));
break;
case IF_LOCAL_DECL:
{
assert(idIsLclVarDecl());
instrDescLclVarDecl* idl = static_cast<instrDescLclVarDecl*>(const_cast<instrDesc*>(this));
size = SizeOfULEB128(idl->lclCnt) + sizeof(emitter::instWasmValueType);
break;
}
case IF_ULEB128:
size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this));
break;
Expand Down Expand Up @@ -354,6 +412,24 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
dst += emitOutputULEB128(dst, offset);
break;
}
case IF_LOCAL_CNT:
{
cnsval_ssize_t constant = emitGetInsSC(id);
dst += emitOutputULEB128(dst, (uint64_t)constant);
break;
}
case IF_LOCAL_DECL:
{
assert(id->idIsLclVarDecl());
cnsval_ssize_t count = emitGetLclVarDeclCount(id);
instWasmValueType valType = emitGetLclVarDeclType(id);
dst += emitOutputULEB128(dst, (uint64_t)count);
// TODO-WASM: currently assuming all locals are numtypes which are single byte encoded.
// vec types are also single byte encoded. If we end up using reftypes, we'll need to handle the more
// complex encoding.
dst += emitOutputByte(dst, static_cast<uint8_t>(valType));
break;
}
default:
NYI_WASM("emitOutputInstr");
break;
Expand Down Expand Up @@ -472,13 +548,22 @@ void emitter::emitDispIns(

case IF_LABEL:
case IF_ULEB128:
case IF_LOCAL_CNT:
{
cnsval_ssize_t imm = emitGetInsSC(id);
printf(" %llu", (uint64_t)imm);
dispJumpTargetIfAny();
}
break;

case IF_LOCAL_DECL:
{
cnsval_ssize_t imm = emitGetLclVarDeclCount(id);
instWasmValueType valType = emitGetLclVarDeclType(id);
printf(" %llu %s", (uint64_t)imm, instWasmValueTypeToStr(valType));
}
break;

case IF_SLEB128:
{
cnsval_ssize_t imm = emitGetInsSC(id);
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/jit/emitwasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/* Debug-only routines to display instructions */
/************************************************************************/

#include "wasmtypesdef.h"

#if defined(DEBUG) || defined(LATE_DISASM)
void getInsSveExecutionCharacteristics(instrDesc* id, insExecutionCharacteristics& result);
#endif // defined(DEBUG) || defined(LATE_DISASM)
Expand All @@ -18,6 +20,7 @@ void emitDispInst(instruction ins);
public:
void emitIns(instruction ins);
void emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm);
void emitIns_I_Ty(instruction ins, cnsval_ssize_t imm, instWasmValueType valType);
void emitIns_J(instruction ins, emitAttr attr, cnsval_ssize_t imm, BasicBlock* tgtBlock);
void emitIns_S(instruction ins, emitAttr attr, int varx, int offs);
void emitIns_R(instruction ins, emitAttr attr, regNumber reg);
Expand All @@ -33,6 +36,10 @@ static unsigned SizeOfSLEB128(int64_t value);

static unsigned emitGetAlignHintLog2(const instrDesc* id);

instrDesc* emitNewInstrLclVarDecl(emitAttr attr, cnsval_ssize_t localCount, instWasmValueType type);
static instWasmValueType emitGetLclVarDeclType(instrDesc* id);
static cnsval_ssize_t emitGetLclVarDeclCount(instrDesc* id);

/************************************************************************/
/* Private members that deal with target-dependent instr. descriptors */
/************************************************************************/
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ INST(br, "br", 0, IF_ULEB128, 0x0C)
INST(br_if, "br_if", 0, IF_ULEB128, 0x0D)
INST(br_table, "br_table", 0, IF_ULEB128, 0x0E)
INST(return, "return", 0, IF_OPCODE, 0x0F)
INST(local_cnt, "local.cnt", 0, IF_LOCAL_CNT, 0x00)
INST(local_decl, "local", 0, IF_LOCAL_DECL, 0x00)

INST(local_get, "local.get", 0, IF_ULEB128, 0x20)
INST(i32_load, "i32.load", 0, IF_MEMARG, 0x28)
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/jit/registeropswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ enum class WasmValueType : unsigned
F32,
F64,
Count,

#ifdef TARGET_64BIT
I = I64,
#else
Expand Down
Loading
Loading