Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
10 changes: 9 additions & 1 deletion src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2189,7 +2189,15 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
}
else
{
InvokeCalliStub(LOCAL_VAR(calliFunctionPointerVar, PCODE), cookie, stack + callArgsOffset, stack + returnOffset);
PCODE calliFunctionPointer = LOCAL_VAR(calliFunctionPointerVar, PCODE);
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
if (!PortableEntryPoint::HasNativeEntryPoint(calliFunctionPointer))
{
targetMethod = PortableEntryPoint::GetMethodDesc(calliFunctionPointer);
goto CALL_INTERP_METHOD;
}
#endif // FEATURE_PORTABLE_ENTRYPOINTS
InvokeCalliStub(calliFunctionPointer, cookie, stack + callArgsOffset, stack + returnOffset);
}

break;
Expand Down
152 changes: 122 additions & 30 deletions src/coreclr/vm/wasm/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,8 @@ void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, in
namespace
{
// Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE.
#define ARG(i) *((int32_t*)(pArgs + (i * INTERP_STACK_SLOT_SIZE)))
#define ARG_IND(i) ((int32_t)((int32_t*)(pArgs + (i * INTERP_STACK_SLOT_SIZE))))
#define ARG(i) (*(int32_t*)ARG_IND(i))

void CallFunc_Void_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
Expand All @@ -437,6 +438,12 @@ namespace
(*fptr)(ARG(0), ARG(1), ARG(2));
}

void CallFunc_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
(*fptr)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5));
}

void CallFunc_Void_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
int32_t (*fptr)(void) = (int32_t (*)(void))pcode;
Expand All @@ -461,6 +468,20 @@ namespace
*(int32_t*)pRet = (*fptr)(ARG(0), ARG(1), ARG(2));
}

// Special thunks for signatures with indirect arguments.

void CallFunc_I32IND_I32_RetVoid(PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
void (*fptr)(int32_t, int32_t) = (void (*)(int32_t, int32_t))pcode;
(*fptr)(ARG_IND(0), ARG(1));
}

void CallFunc_I32IND_I32_RetI32(PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
int32_t (*fptr)(int32_t, int32_t) = (int32_t (*)(int32_t, int32_t))pcode;
*(int32_t*)pRet = (*fptr)(ARG_IND(0), ARG(1));
}

#undef ARG

void* const RetVoidThunks[] =
Expand All @@ -469,6 +490,9 @@ namespace
(void*)&CallFunc_I32_RetVoid,
(void*)&CallFunc_I32_I32_RetVoid,
(void*)&CallFunc_I32_I32_I32_RetVoid,
NULL,
NULL,
(void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid,
};

void* const RetI32Thunks[] =
Expand All @@ -479,34 +503,86 @@ namespace
(void*)&CallFunc_I32_I32_I32_RetI32,
};

bool ConvertibleToI32(CorElementType argType)
enum class ConvertType
{
NotConvertible,
ToI32,
ToI32Indirect
};

ConvertType ConvertibleTo(CorElementType argType, MetaSig& sig, bool isReturn)
{
// See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md
switch (argType)
// See https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md
switch (argType)
{
case ELEMENT_TYPE_BOOLEAN:
case ELEMENT_TYPE_CHAR:
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_STRING:
case ELEMENT_TYPE_PTR:
case ELEMENT_TYPE_BYREF:
case ELEMENT_TYPE_CLASS:
case ELEMENT_TYPE_ARRAY:
case ELEMENT_TYPE_I:
case ELEMENT_TYPE_U:
case ELEMENT_TYPE_FNPTR:
case ELEMENT_TYPE_SZARRAY:
return ConvertType::ToI32;
case ELEMENT_TYPE_TYPEDBYREF:
// Typed references are passed indirectly in WASM since they are larger than pointer size.
return ConvertType::ToI32Indirect;
case ELEMENT_TYPE_VALUETYPE:
{
case ELEMENT_TYPE_BOOLEAN:
case ELEMENT_TYPE_CHAR:
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_STRING:
case ELEMENT_TYPE_PTR:
case ELEMENT_TYPE_BYREF:
case ELEMENT_TYPE_VALUETYPE:
case ELEMENT_TYPE_CLASS:
case ELEMENT_TYPE_ARRAY:
case ELEMENT_TYPE_TYPEDBYREF:
case ELEMENT_TYPE_I:
case ELEMENT_TYPE_U:
case ELEMENT_TYPE_FNPTR:
case ELEMENT_TYPE_SZARRAY:
return true;
default:
return false;
// In WASM, values types that are larger than pointer size or have multiple fields are passed indirectly.
// WASMTODO: Single fields may not always be passed as i32. Floats and doubles are passed as f32 and f64 respectively.
TypeHandle vt = isReturn
? sig.GetRetTypeHandleThrowing()
: sig.GetLastTypeHandleThrowing();

if (!vt.IsTypeDesc()
&& vt.AsMethodTable()->GetNumInstanceFields() >= 2)
{
return ConvertType::ToI32Indirect;
}

return vt.GetSize() <= sizeof(uint32_t)
? ConvertType::ToI32
: ConvertType::ToI32Indirect;
}
default:
return ConvertType::NotConvertible;
}
}

void* ComputeCalliSigThunkSpecial(bool isVoidReturn, uint32_t numArgs, ConvertType* args)
{
STANDARD_VM_CONTRACT;

if (isVoidReturn)
{
if (numArgs == 2 &&
args[0] == ConvertType::ToI32Indirect &&
args[1] == ConvertType::ToI32)
{
return (void*)&CallFunc_I32IND_I32_RetVoid;
}
}
else
{
if (numArgs == 2 &&
args[0] == ConvertType::ToI32Indirect &&
args[1] == ConvertType::ToI32)
{
return (void*)&CallFunc_I32IND_I32_RetI32;
}
}

return NULL;
}

// This is a simple signature computation routine for signatures currently supported in the wasm environment.
Expand All @@ -530,21 +606,37 @@ namespace
return NULL;
}

// Check return value
// Check return value. We only support void or i32 return types for now.
bool returnsVoid = sig.IsReturnTypeVoid();
if (!returnsVoid && !ConvertibleToI32(sig.GetReturnType()))
if (!returnsVoid && ConvertibleTo(sig.GetReturnType(), sig, true /* isReturn */) != ConvertType::ToI32)
return NULL;

ConvertType args[16];
_ASSERTE(sig.NumFixedArgs() < ARRAY_SIZE(args));

uint32_t i = 0;
// Ensure all arguments are wasm i32 compatible types.
for (CorElementType argType = sig.NextArg();
argType != ELEMENT_TYPE_END;
argType = sig.NextArg())
{
if (!ConvertibleToI32(argType))
// If we have no conversion, immediately return.
ConvertType type = ConvertibleTo(argType, sig, false /* isReturn */);
if (type == ConvertType::NotConvertible)
return NULL;

args[i++] = type;
}

uint32_t numArgs = sig.NumFixedArgs();

// Check for homogeneous i32 argument types.
for (uint32_t j = 0; j < numArgs; j++)
{
if (args[j] != ConvertType::ToI32)
return ComputeCalliSigThunkSpecial(returnsVoid, numArgs, args);
}

UINT numArgs = sig.NumFixedArgs();
void* const * thunks;
if (returnsVoid)
{
Expand Down