-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[Wasm RyuJIT] Initial scaffolding for calls and helper calls #123044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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())); | ||
|
|
@@ -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, ¶ms.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; | ||
| // 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) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| // | ||
|
|
||
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
or the equivalent for
foo(5)without having to shove5into 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.