Skip to content
Draft
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
6 changes: 4 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1555,7 +1555,6 @@ bool CodeGen::genCreateAddrMode(GenTree* addr,
return true;
}

#ifndef TARGET_WASM
//------------------------------------------------------------------------
// genEmitCallWithCurrentGC:
// Emit a call with GC information captured from current GC information.
Expand All @@ -1570,7 +1569,6 @@ void CodeGen::genEmitCallWithCurrentGC(EmitCallParams& params)
params.byrefRegs = gcInfo.gcRegByrefSetCur;
GetEmitter()->emitIns_Call(params);
}
#endif // !TARGET_WASM

/*****************************************************************************
*
Expand Down Expand Up @@ -5735,6 +5733,8 @@ CORINFO_FIELD_HANDLE CodeGen::genEmitAsyncResumeInfo(unsigned stateNum)
return compiler->eeFindJitDataOffs(baseOffs + stateNum * sizeof(CORINFO_AsyncResumeInfo));
}

#endif // TARGET_WASM

//------------------------------------------------------------------------
// getCallTarget - Get the node that evaluates to the call target
//
Expand Down Expand Up @@ -5812,6 +5812,8 @@ regNumber CodeGen::getCallIndirectionCellReg(GenTreeCall* call)
return result;
}

#if !defined(TARGET_WASM)

//------------------------------------------------------------------------
// genDefinePendingLabel - If necessary, define the pending call label after a
// call instruction was emitted.
Expand Down
272 changes: 272 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,18 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genCodeForStoreInd(treeNode->AsStoreInd());
break;

case GT_NULLCHECK:
genCodeForNullCheck(treeNode->AsIndir());
break;

case GT_CALL:
genCall(treeNode->AsCall());
break;

case GT_PUTARG_REG:
genPutArgReg(treeNode->AsOp());
break;

default:
#ifdef DEBUG
NYIRAW(GenTree::OpName(treeNode->OperGet()));
Expand Down Expand Up @@ -948,6 +960,266 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree)
genUpdateLife(tree);
}

//---------------------------------------------------------------------
// genPutArgReg - generate code for a T_PUTARG_REG node
//
// Arguments
// tree - the GT_PUTARG_REG node
//
// Return value:
// None
//
void CodeGen::genPutArgReg(GenTreeOp* tree)
{
assert(tree->OperIs(GT_PUTARG_REG));

var_types targetType = tree->TypeGet();
regNumber targetReg = tree->GetRegNum();

assert(targetType != TYP_STRUCT);

GenTree* op1 = tree->gtOp1;
genConsumeReg(op1);

GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), WasmRegToIndex(op1->GetRegNum()));
GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(tree), WasmRegToIndex(targetReg));

genProduceReg(tree);
}

//------------------------------------------------------------------------
// genCall: Produce code for a GT_CALL node
//
void CodeGen::genCall(GenTreeCall* call)
{
// Insert a null check on "this" pointer if asked.
if (call->NeedsNullCheck())
{
// TODO-WASM: Ensure that when the this-arg is pushed onto the stack earlier, we perform the null check there.
// Once that's done this NYI can go away.
NYI_WASM("this ptr null check in genCall");
}

// TODO-WASM: Handle "fast tail calls" here if we decide to implement them.

genCallInstruction(call);

// TODO-WASM: Disabled in WASM currently. Not sure if we need it
// genDefinePendingCallLabel(call);

var_types returnType = call->TypeGet();
if (returnType != TYP_VOID)
{
if (call->HasMultiRegRetVal())
{
NYI_WASM("multi-reg return values");
}

genProduceReg(call);
}
}

//------------------------------------------------------------------------
// genCallInstruction - Generate instructions necessary to transfer control to the call.
//
// Arguments:
// call - the GT_CALL node
//
void CodeGen::genCallInstruction(GenTreeCall* call)
{
// Determine return value size(s).
const ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc();
EmitCallParams params;

// unused values are of no interest to GC.
if (!call->IsUnusedValue())
{
if (call->HasMultiRegRetVal())
{
NYI_WASM("multi-reg return values");
}
else
{
assert(!call->TypeIs(TYP_STRUCT));

if (call->TypeIs(TYP_REF))
{
params.retSize = EA_GCREF;
}
else if (call->TypeIs(TYP_BYREF))
{
params.retSize = EA_BYREF;
}
}
}

params.isJump = call->IsFastTailCall();
params.hasAsyncRet = call->IsAsync();

// We need to propagate the debug information to the call instruction, so we can emit
// an IL to native mapping record for the call, to support managed return value debugging.
// We don't want tail call helper calls that were converted from normal calls to get a record,
// so we skip this hash table lookup logic in that case.
if (compiler->opts.compDbgInfo && compiler->genCallSite2DebugInfoMap != nullptr && !call->IsTailCall())
{
DebugInfo di;
(void)compiler->genCallSite2DebugInfoMap->Lookup(call, &di);
params.debugInfo = di;
}

#ifdef DEBUG
// Pass the call signature information down into the emitter so the emitter can associate
// native call sites with the signatures they were generated from.
if (!call->IsHelperCall())
{
params.sigInfo = call->callSig;
}
#endif // DEBUG
GenTree* target = getCallTarget(call, &params.methHnd);

if (target != nullptr)
{
// A call target can not be a contained indirection
assert(!target->isContainedIndir());

// Codegen should have already evaluated our target node (last) and pushed it onto the stack,
// ready for call_indirect. Consume it.
genConsumeReg(target);

if (target->isContainedIntOrIImmed())
{
NYI_WASM("Contained int or iimmed call target");
}
else
{
params.callType = EC_INDIR_R;
Copy link
Member

Choose a reason for hiding this comment

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

Do we want custom EmitCallTypes for Wasm?

Note for calls we are still thinking of the parameters as being passed in regs (perhaps specially encoded to distinguish them from wasm locals, which we are also thinking of as regs).

Copy link
Member Author

Choose a reason for hiding this comment

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

I think custom EmitCallTypes is a good idea.

I'm not sure it makes sense to have imaginary regs for parameters; we should be able to emit

i32.const 5
i32.const <ftnptr>
call_indirect someFuncThatAcceptsInt

or the equivalent for foo(5) without having to shove 5 into a temp reg and then load it. Or do we need to associate all values, even constants, with registers for things to work? I would have assumed you could just have an icon node in the args list for a call node and it would work.

// TODO-WASM: Figure out whether we need this. I think the target should always be on stack, not in a local.
// params.ireg = target->GetRegNum();
}

// TODO-WASM: Assert disabled due to above
// assert(genIsValidIntReg(params.ireg));

genEmitCallWithCurrentGC(params);
}
else
{
// If we have no target and this is a call with indirection cell then
// we do an optimization where we load the call address directly from
// the indirection cell instead of duplicating the tree. In BuildCall
// we ensure that get an extra register for the purpose. Note that for
// CFG the call might have changed to
// CORINFO_HELP_DISPATCH_INDIRECT_CALL in which case we still have the
// indirection cell but we should not try to optimize.
regNumber callThroughIndirReg = REG_NA;
if (!call->IsHelperCall(compiler, CORINFO_HELP_DISPATCH_INDIRECT_CALL))
{
callThroughIndirReg = getCallIndirectionCellReg(call);
}

if (callThroughIndirReg != REG_NA)
{
assert(call->IsR2ROrVirtualStubRelativeIndir());
NYI_WASM("load call target from indirection cell in register");

// We have now generated code loading the target address from the indirection cell onto the stack.
params.callType = EC_INDIR_R;
// params.ireg = targetAddrReg;
genEmitCallWithCurrentGC(params);
}
else
{
// Generate a direct call to a non-virtual user defined or helper method
assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC));

if (call->gtEntryPoint.addr != NULL)
Copy link
Member

Choose a reason for hiding this comment

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

Per the calling convention, for user and helper calls we'll also be invoking them indirectly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. I'm not clear on whether that means there won't be an addr filled in

{
NYI_WASM("Call with statically known address");
assert(call->gtEntryPoint.accessType == IAT_VALUE);
params.addr = call->gtEntryPoint.addr;
}
else
{
if (call->IsHelperCall())
{
CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(params.methHnd);
noway_assert(helperNum != CORINFO_HELP_UNDEF);

CORINFO_CONST_LOOKUP helperLookup = compiler->compGetHelperFtn(helperNum);
params.addr = helperLookup.addr;
assert(helperLookup.accessType == IAT_VALUE);
}
else
{
// Direct call to a non-virtual user function.
params.addr = call->gtDirectCallAddress;
}
}

params.callType = EC_FUNC_TOKEN;
// params.ireg = params.isJump ? rsGetRsvdReg() : REG_RA;

genEmitCallWithCurrentGC(params);
}
}
}

//---------------------------------------------------------------------
// genCodeForNullCheck - generate code for a GT_NULLCHECK node
//
// Arguments
// tree - the GT_NULLCHECK node
//
// Return value:
// None
//
void CodeGen::genCodeForNullCheck(GenTreeIndir* tree)
{
assert(tree->OperIs(GT_NULLCHECK));
GenTree* op1 = tree->gtOp1;

genConsumeRegs(op1);

genEmitHelperCall(CORINFO_HELP_THROWNULLREF, 0, EA_UNKNOWN, REG_NA);
}

/*****************************************************************************
* Emit a call to a helper function.
*/
void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, regNumber callTargetReg /*= REG_NA */)
{
EmitCallParams params;

CORINFO_CONST_LOOKUP helperFunction = compiler->compGetHelperFtn((CorInfoHelpFunc)helper);
params.ireg = callTargetReg;

if (helperFunction.accessType == IAT_VALUE)
{
params.callType = EC_FUNC_TOKEN;
params.addr = helperFunction.addr;
}
else
{
// TODO-WASM: Just put helperFunction.addr in params.addr and do the indirect load
// further down in the emitter for all IAT_PVALUE calls instead of doing it here
params.addr = nullptr;
assert(helperFunction.accessType == IAT_PVALUE);
void* pAddr = helperFunction.addr;

// Indirect load from pAddr
GetEmitter()->emitIns_I(INS_i32_const, emitActualTypeSize(TYP_I_IMPL), (cnsval_ssize_t)pAddr);
GetEmitter()->emitIns_I(INS_i32_load, emitActualTypeSize(TYP_I_IMPL), 0);

params.callType = EC_INDIR_R;
}

params.methHnd = compiler->eeFindHelper(helper);
params.argSize = argSize;
params.retSize = retSize;

genEmitCallWithCurrentGC(params);
}

//------------------------------------------------------------------------
// genCodeForCompare: Produce code for a GT_EQ/GT_NE/GT_LT/GT_LE/GT_GE/GT_GT node.
//
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ IF_DEF(F32, IS_NONE, NONE) // <opcode> <f32 immediate (stored as 64-bit
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_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(ULEB128_X2, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
Loading
Loading