diff --git a/src/coreclr/inc/targetosarch.h b/src/coreclr/inc/targetosarch.h index 7b67cccc75f7f..1251104c10798 100644 --- a/src/coreclr/inc/targetosarch.h +++ b/src/coreclr/inc/targetosarch.h @@ -42,6 +42,11 @@ class TargetOS class TargetArchitecture { public: +#ifdef TARGET_64BIT + static const bool Is64Bit = true; +#else + static const bool Is64Bit = false; +#endif #ifdef TARGET_ARM static const bool IsX86 = false; static const bool IsX64 = false; diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 20a0bd0ab963f..c1336be0d75f3 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -9647,9 +9647,9 @@ void cTreeFlags(Compiler* comp, GenTree* tree) { chars += printf("[VAR_ITERATOR]"); } - if (tree->gtFlags & GTF_VAR_CLONED) + if (tree->gtFlags & GTF_VAR_MOREUSES) { - chars += printf("[VAR_CLONED]"); + chars += printf("[VAR_MOREUSES]"); } if (!comp->lvaGetDesc(tree->AsLclVarCommon())->lvPromoted) { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index c228810d4ee18..ce373cef85028 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4087,7 +4087,7 @@ class Compiler BasicBlock* block = nullptr); GenTree* impStoreStructPtr(GenTree* destAddr, GenTree* value, unsigned curLevel); - GenTree* impGetStructAddr(GenTree* structVal, unsigned curLevel, bool willDeref); + GenTree* impGetNodeAddr(GenTree* val, unsigned curLevel, GenTreeFlags* pDerefFlags); var_types impNormStructType(CORINFO_CLASS_HANDLE structHnd, CorInfoType* simdBaseJitType = nullptr); diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index fb55a115d8ca2..71a0f9985a068 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -1632,7 +1632,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo) GenTree* argSingleUseNode = argInfo.argBashTmpNode; - if ((argSingleUseNode != nullptr) && !(argSingleUseNode->gtFlags & GTF_VAR_CLONED) && argIsSingleDef) + if ((argSingleUseNode != nullptr) && !(argSingleUseNode->gtFlags & GTF_VAR_MOREUSES) && argIsSingleDef) { // Change the temp in-place to the actual argument. // We currently do not support this for struct arguments, so it must not be a GT_BLK. diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 455e30df7198d..7b654da6eb39f 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -8781,7 +8781,7 @@ GenTree* Compiler::gtClone(GenTree* tree, bool complexOK) FINISH_CLONING_LCL_NODE: // Remember that the local node has been cloned. Below the flag will be set on 'copy' too. - tree->gtFlags |= GTF_VAR_CLONED; + tree->gtFlags |= GTF_VAR_MOREUSES; copy->AsLclVarCommon()->SetSsaNum(tree->AsLclVarCommon()->GetSsaNum()); assert(!copy->AsLclVarCommon()->HasSsaName() || ((copy->gtFlags & GTF_VAR_DEF) == 0)); break; @@ -8952,7 +8952,7 @@ GenTree* Compiler::gtCloneExpr( else { // Remember that the local node has been cloned. The flag will be set on 'copy' as well. - tree->gtFlags |= GTF_VAR_CLONED; + tree->gtFlags |= GTF_VAR_MOREUSES; copy = gtNewLclvNode(tree->AsLclVar()->GetLclNum(), tree->gtType DEBUGARG(tree->AsLclVar()->gtLclILoffs)); copy->AsLclVarCommon()->SetSsaNum(tree->AsLclVarCommon()->GetSsaNum()); @@ -8967,7 +8967,7 @@ GenTree* Compiler::gtCloneExpr( else { // Remember that the local node has been cloned. The flag will be set on 'copy' as well. - tree->gtFlags |= GTF_VAR_CLONED; + tree->gtFlags |= GTF_VAR_MOREUSES; copy = new (this, GT_LCL_FLD) GenTreeLclFld(GT_LCL_FLD, tree->TypeGet(), tree->AsLclFld()->GetLclNum(), tree->AsLclFld()->GetLclOffs(), tree->AsLclFld()->GetLayout()); @@ -9032,14 +9032,14 @@ GenTree* Compiler::gtCloneExpr( { case GT_STORE_LCL_VAR: // Remember that the local node has been cloned. The flag will be set on 'copy' as well. - tree->gtFlags |= GTF_VAR_CLONED; + tree->gtFlags |= GTF_VAR_MOREUSES; copy = new (this, GT_STORE_LCL_VAR) GenTreeLclVar(tree->TypeGet(), tree->AsLclVar()->GetLclNum(), tree->AsLclVar()->Data()); break; case GT_STORE_LCL_FLD: // Remember that the local node has been cloned. The flag will be set on 'copy' as well. - tree->gtFlags |= GTF_VAR_CLONED; + tree->gtFlags |= GTF_VAR_MOREUSES; copy = new (this, GT_STORE_LCL_FLD) GenTreeLclFld(tree->TypeGet(), tree->AsLclFld()->GetLclNum(), tree->AsLclFld()->GetLclOffs(), tree->AsLclFld()->Data(), tree->AsLclFld()->GetLayout()); @@ -16074,7 +16074,9 @@ GenTree* Compiler::gtNewRefCOMfield(GenTree* objPtr, // helper needs pointer to struct, not struct itself if (pFieldInfo->helper == CORINFO_HELP_SETFIELDSTRUCT) { - assg = impGetStructAddr(assg, CHECK_SPILL_ALL, true); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + assg = impGetNodeAddr(assg, CHECK_SPILL_ALL, &indirFlags); } else if (lclTyp == TYP_DOUBLE && assg->TypeGet() == TYP_FLOAT) { @@ -16149,8 +16151,10 @@ GenTree* Compiler::gtNewRefCOMfield(GenTree* objPtr, { if (!varTypeIsStruct(lclTyp)) { - result = impGetStructAddr(result, CHECK_SPILL_ALL, true); - result = gtNewIndir(lclTyp, result); + // get the result as primitive type + GenTreeFlags indirFlags = GTF_EMPTY; + result = impGetNodeAddr(result, CHECK_SPILL_ALL, &indirFlags); + result = gtNewIndir(lclTyp, result, indirFlags); } } else if (varTypeIsSmall(lclTyp)) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 4e2a9563a471b..27688c3d41790 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -453,7 +453,7 @@ enum GenTreeFlags : unsigned int GTF_LIVENESS_MASK = GTF_VAR_DEF | GTF_VAR_USEASG | GTF_VAR_DEATH_MASK, GTF_VAR_ITERATOR = 0x01000000, // GT_LCL_VAR -- this is a iterator reference in the loop condition - GTF_VAR_CLONED = 0x00800000, // GT_LCL_VAR -- this node has been cloned or is a clone + GTF_VAR_MOREUSES = 0x00800000, // GT_LCL_VAR -- this node has additonal uses, for example due to cloning GTF_VAR_CONTEXT = 0x00400000, // GT_LCL_VAR -- this node is part of a runtime lookup GTF_VAR_EXPLICIT_INIT = 0x00200000, // GT_LCL_VAR -- this node is an "explicit init" store. Valid until rationalization. @@ -491,8 +491,8 @@ enum GenTreeFlags : unsigned int GTF_IND_NONNULL = 0x00400000, // GT_IND -- the indirection never returns null (zero) GTF_IND_INITCLASS = 0x00200000, // OperIsIndir() -- the indirection requires preceding static cctor - GTF_IND_FLAGS = GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_UNALIGNED | GTF_IND_INVARIANT | - GTF_IND_NONNULL | GTF_IND_TGT_NOT_HEAP | GTF_IND_TGT_HEAP | GTF_IND_INITCLASS, + GTF_IND_COPYABLE_FLAGS = GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_UNALIGNED | GTF_IND_INITCLASS, + GTF_IND_FLAGS = GTF_IND_COPYABLE_FLAGS | GTF_IND_NONNULL | GTF_IND_TGT_NOT_HEAP | GTF_IND_TGT_HEAP | GTF_IND_INVARIANT, GTF_ADDRMODE_NO_CSE = 0x80000000, // GT_ADD/GT_MUL/GT_LSH -- Do not CSE this node only, forms complex // addressing mode diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 3126025c0d922..6e648ac189130 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -807,8 +807,10 @@ GenTree* Compiler::impStoreStruct(GenTree* store, WellKnownArg wellKnownArgType = srcCall->ShouldHaveRetBufArg() ? WellKnownArg::RetBuffer : WellKnownArg::None; - GenTree* destAddr = impGetStructAddr(store, CHECK_SPILL_ALL, /* willDeref */ true); - NewCallArg newArg = NewCallArg::Primitive(destAddr).WellKnown(wellKnownArgType); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + GenTree* destAddr = impGetNodeAddr(store, CHECK_SPILL_ALL, &indirFlags); + NewCallArg newArg = NewCallArg::Primitive(destAddr).WellKnown(wellKnownArgType); #if !defined(TARGET_ARM) // Unmanaged instance methods on Windows or Unix X86 need the retbuf arg after the first (this) parameter @@ -909,7 +911,9 @@ GenTree* Compiler::impStoreStruct(GenTree* store, if (call->ShouldHaveRetBufArg()) { // insert the return value buffer into the argument list as first byref parameter after 'this' - GenTree* destAddr = impGetStructAddr(store, CHECK_SPILL_ALL, /* willDeref */ true); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + GenTree* destAddr = impGetNodeAddr(store, CHECK_SPILL_ALL, &indirFlags); call->gtArgs.InsertAfterThisOrFirst(this, NewCallArg::Primitive(destAddr).WellKnown(WellKnownArg::RetBuffer)); @@ -926,8 +930,9 @@ GenTree* Compiler::impStoreStruct(GenTree* store, { // Since we are assigning the result of a GT_MKREFANY, "destAddr" must point to a refany. // TODO-CQ: we can do this without address-exposing the local on the LHS. - GenTree* destAddr = impGetStructAddr(store, CHECK_SPILL_ALL, /* willDeref */ true); - GenTree* destAddrClone; + GenTreeFlags indirFlags = GTF_EMPTY; + GenTree* destAddr = impGetNodeAddr(store, CHECK_SPILL_ALL, &indirFlags); + GenTree* destAddrClone; destAddr = impCloneExpr(destAddr, &destAddrClone, curLevel, pAfterStmt DEBUGARG("MKREFANY assignment")); assert(OFFSETOF__CORINFO_TypedReference__dataPtr == 0); @@ -935,7 +940,7 @@ GenTree* Compiler::impStoreStruct(GenTree* store, // Append the store of the pointer value. // TODO-Bug: the pointer value can be a byref. Use its actual type here instead of TYP_I_IMPL. - GenTree* ptrFieldStore = gtNewStoreIndNode(TYP_I_IMPL, destAddr, src->AsOp()->gtOp1); + GenTree* ptrFieldStore = gtNewStoreIndNode(TYP_I_IMPL, destAddr, src->AsOp()->gtOp1, indirFlags); if (pAfterStmt) { Statement* newStmt = gtNewStmt(ptrFieldStore, usedDI); @@ -1020,51 +1025,59 @@ GenTree* Compiler::impStoreStructPtr(GenTree* destAddr, GenTree* value, unsigned } //------------------------------------------------------------------------ -// impGetStructAddr: Get the address of a struct value / location. +// impGetNodeAddr: Get the address of a value. // // Arguments: -// structVal - The value in question -// curLevel - Stack level for spilling -// willDeref - Whether the caller will dereference the address +// val - The value in question +// curLevel - Stack level for spilling +// pDerefFlags - Flags to be used on dereference, nullptr when +// the address won't be dereferenced. Returned flags +// are included in the GTF_IND_COPYABLE_FLAGS mask. // // Return Value: -// In case "structVal" can represent locations (is an indirection/local), +// In case "val" represents a location (is an indirection/local), // will return its address. Otherwise, address of a temporary assigned -// the value of "structVal" will be returned. +// the value of "val" will be returned. // -GenTree* Compiler::impGetStructAddr(GenTree* structVal, unsigned curLevel, bool willDeref) +GenTree* Compiler::impGetNodeAddr(GenTree* val, unsigned curLevel, GenTreeFlags* pDerefFlags) { - assert(varTypeIsStruct(structVal)); - switch (structVal->OperGet()) + if (pDerefFlags != nullptr) + { + *pDerefFlags = GTF_EMPTY; + } + switch (val->OperGet()) { case GT_BLK: case GT_IND: case GT_STOREIND: case GT_STORE_BLK: - if (willDeref) + if (pDerefFlags != nullptr) { - return structVal->AsIndir()->Addr(); + *pDerefFlags = val->gtFlags & GTF_IND_COPYABLE_FLAGS; + return val->AsIndir()->Addr(); } break; case GT_LCL_VAR: case GT_STORE_LCL_VAR: - return gtNewLclVarAddrNode(structVal->AsLclVar()->GetLclNum(), TYP_BYREF); + val->gtFlags |= GTF_VAR_MOREUSES; + return gtNewLclVarAddrNode(val->AsLclVar()->GetLclNum(), TYP_BYREF); case GT_LCL_FLD: case GT_STORE_LCL_FLD: - return gtNewLclAddrNode(structVal->AsLclFld()->GetLclNum(), structVal->AsLclFld()->GetLclOffs(), TYP_BYREF); + val->gtFlags |= GTF_VAR_MOREUSES; + return gtNewLclAddrNode(val->AsLclFld()->GetLclNum(), val->AsLclFld()->GetLclOffs(), TYP_BYREF); case GT_COMMA: - impAppendTree(structVal->AsOp()->gtGetOp1(), curLevel, impCurStmtDI); - return impGetStructAddr(structVal->AsOp()->gtGetOp2(), curLevel, willDeref); + impAppendTree(val->AsOp()->gtGetOp1(), curLevel, impCurStmtDI); + return impGetNodeAddr(val->AsOp()->gtGetOp2(), curLevel, pDerefFlags); default: break; } unsigned lclNum = lvaGrabTemp(true DEBUGARG("location for address-of(RValue)")); - impStoreTemp(lclNum, structVal, curLevel); + impStoreTemp(lclNum, val, curLevel); // The 'return value' is now address of the temp itself. return gtNewLclVarAddrNode(lclNum, TYP_BYREF); @@ -3000,7 +3013,9 @@ int Compiler::impBoxPatternMatch(CORINFO_RESOLVED_TOKEN* pResolvedToken, GenTree* objToBox = impPopStack().val; // Spill struct to get its address (to access hasValue field) - objToBox = impGetStructAddr(objToBox, CHECK_SPILL_ALL, true); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + objToBox = impGetNodeAddr(objToBox, CHECK_SPILL_ALL, &indirFlags); static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0); impPushOnStack(gtNewIndir(TYP_BOOL, objToBox), typeInfo(TYP_INT)); @@ -3348,7 +3363,9 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken) return; } - op1 = gtNewHelperCallNode(boxHelper, TYP_REF, op2, impGetStructAddr(exprToBox, CHECK_SPILL_ALL, true)); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + op1 = gtNewHelperCallNode(boxHelper, TYP_REF, op2, impGetNodeAddr(exprToBox, CHECK_SPILL_ALL, &indirFlags)); } /* Push the result back on the stack, */ @@ -7968,7 +7985,7 @@ void Compiler::impImportBlockCode(BasicBlock* block) } else { - op1 = impGetStructAddr(op1, CHECK_SPILL_ALL, false); + op1 = impGetNodeAddr(op1, CHECK_SPILL_ALL, nullptr); } JITDUMP("\n ... optimized to ...\n"); @@ -8908,7 +8925,9 @@ void Compiler::impImportBlockCode(BasicBlock* block) BADCODE3("Unexpected opcode (has to be LDFLD)", ": %02X", (int)opcode); } - obj = impGetStructAddr(obj, CHECK_SPILL_ALL, true); + // TODO-Bug?: verify if flags matter here + GenTreeFlags indirFlags = GTF_EMPTY; + obj = impGetNodeAddr(obj, CHECK_SPILL_ALL, &indirFlags); } op1 = gtNewFieldAddrNode(resolvedToken.hField, obj, fieldInfo.offset); @@ -9661,12 +9680,13 @@ void Compiler::impImportBlockCode(BasicBlock* block) else { // Get the address of the refany - op1 = impGetStructAddr(op1, CHECK_SPILL_ALL, /* willDeref */ true); + GenTreeFlags indirFlags = GTF_EMPTY; + op1 = impGetNodeAddr(op1, CHECK_SPILL_ALL, &indirFlags); // Fetch the type from the correct slot op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL)); - op1 = gtNewIndir(TYP_BYREF, op1); + op1 = gtNewIndir(TYP_BYREF, op1, indirFlags); } // Convert native TypeHandle to RuntimeTypeHandle. diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index e2aa53c10a61a..ceea076e5b030 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -4079,16 +4079,15 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, assert(sig->sigInst.methInstCount == 2); CORINFO_CLASS_HANDLE fromTypeHnd = sig->sigInst.methInst[0]; - CORINFO_CLASS_HANDLE toTypeHnd = sig->sigInst.methInst[1]; + ClassLayout* fromLayout = nullptr; + var_types fromType = TypeHandleToVarType(fromTypeHnd, &fromLayout); - if (fromTypeHnd == toTypeHnd) - { - // Handle the easy case of matching type handles, such as `int` to `int` - return impPopStack().val; - } + CORINFO_CLASS_HANDLE toTypeHnd = sig->sigInst.methInst[1]; + ClassLayout* toLayout = nullptr; + var_types toType = TypeHandleToVarType(toTypeHnd, &toLayout); - unsigned fromSize = info.compCompHnd->getClassSize(fromTypeHnd); - unsigned toSize = info.compCompHnd->getClassSize(toTypeHnd); + unsigned fromSize = fromLayout != nullptr ? fromLayout->GetSize() : genTypeSize(fromType); + unsigned toSize = toLayout != nullptr ? toLayout->GetSize() : genTypeSize(toType); // Runtime requires all types to be at least 1-byte assert((fromSize != 0) && (toSize != 0)); @@ -4099,57 +4098,41 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, return nullptr; } - CorInfoType fromJitType = info.compCompHnd->asCorInfoType(fromTypeHnd); - var_types fromType = JITtype2varType(fromJitType); + assert((fromType != TYP_REF) && (toType != TYP_REF)); - CorInfoType toJitType = info.compCompHnd->asCorInfoType(toTypeHnd); - var_types toType = JITtype2varType(toJitType); + GenTree* op1 = impPopStack().val; - bool involvesStructType = false; + op1 = impImplicitR4orR8Cast(op1, fromType); + op1 = impImplicitIorI4Cast(op1, fromType); - if (fromType == TYP_STRUCT) + var_types valType = op1->gtType; + GenTree* effectiveVal = op1->gtEffectiveVal(); + if (effectiveVal->OperIs(GT_LCL_VAR)) { - involvesStructType = true; + valType = lvaGetDesc(effectiveVal->AsLclVar()->GetLclNum())->TypeGet(); + } - if (toType == TYP_STRUCT) + // Handle matching handles, compatible struct layouts or integrals where we can simply return op1 + if (varTypeIsSmall(toType)) + { + if (genActualTypeIsInt(valType)) { - ClassLayout* fromLayout = typGetObjLayout(fromTypeHnd); - ClassLayout* toLayout = typGetObjLayout(toTypeHnd); - - if (ClassLayout::AreCompatible(fromLayout, toLayout)) + if (fgCastNeeded(op1, toType)) { - // Handle compatible struct layouts where we can simply return op1 - return impPopStack().val; + op1 = gtNewCastNode(TYP_INT, op1, false, toType); } + return op1; } } - else if (toType == TYP_STRUCT) + else if (((toType != TYP_STRUCT) && (genActualType(valType) == toType)) || + ClassLayout::AreCompatible(fromLayout, toLayout)) { - involvesStructType = true; - } - - if (involvesStructType) - { - // TODO-CQ: Handle this by getting the address of `op1` and then dereferencing - // that as TTo, much as `Unsafe.As(ref op1)` would work. - return nullptr; + return op1; } - if (varTypeIsFloating(fromType)) + // Handle bitcasting between floating and same sized integral, such as `float` to `int` + if (varTypeIsFloating(fromType) && varTypeIsIntegral(toType)) { - // Handle bitcasting from floating to same sized integral, such as `float` to `int` - assert(varTypeIsIntegral(toType)); - -#if !TARGET_64BIT - if ((fromType == TYP_DOUBLE) && !impStackTop().val->IsCnsFltOrDbl()) - { - // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work - return nullptr; - } -#endif // !TARGET_64BIT - - GenTree* op1 = impPopStack().val; - if (op1->IsCnsFltOrDbl()) { if (fromType == TYP_DOUBLE) @@ -4164,30 +4147,15 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, return gtNewIconNode(static_cast(BitOperations::SingleToUInt32Bits(f32Cns))); } } - else + // TODO-CQ: We should support this on 32-bit via decomposition + else if (TargetArchitecture::Is64Bit || (fromType == TYP_FLOAT)) { toType = varTypeToSigned(toType); - op1 = impImplicitR4orR8Cast(op1, fromType); return gtNewBitCastNode(toType, op1); } - break; } - - if (varTypeIsFloating(toType)) + else if (varTypeIsIntegral(fromType) && varTypeIsFloating(toType)) { - // Handle bitcasting from integral to same sized floating, such as `int` to `float` - assert(varTypeIsIntegral(fromType)); - -#if !TARGET_64BIT - if ((toType == TYP_DOUBLE) && !impStackTop().val->IsIntegralConst()) - { - // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work - return nullptr; - } -#endif // !TARGET_64BIT - - GenTree* op1 = impPopStack().val; - if (op1->IsIntegralConst()) { if (toType == TYP_DOUBLE) @@ -4204,14 +4172,33 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, return gtNewDconNode(FloatingPointUtils::convertToDouble(f32Cns), TYP_FLOAT); } } - else + // TODO-CQ: We should support this on 32-bit via decomposition + else if (TargetArchitecture::Is64Bit || (toType == TYP_FLOAT)) { return gtNewBitCastNode(toType, op1); } } - // Handle bitcasting for same sized integrals, such as `int` to `uint` - return impPopStack().val; + GenTree* addr; + GenTreeFlags indirFlags = GTF_EMPTY; + if (varTypeIsIntegral(valType) && (genTypeSize(valType) < fromSize)) + { + unsigned lclNum = lvaGrabTemp(true DEBUGARG("bitcast small type extension")); + impStoreTemp(lclNum, op1, CHECK_SPILL_ALL); + addr = gtNewLclVarAddrNode(lclNum, TYP_I_IMPL); + } + else + { + addr = impGetNodeAddr(op1, CHECK_SPILL_ALL, &indirFlags); + } + + if (info.compCompHnd->getClassAlignmentRequirement(fromTypeHnd) < + info.compCompHnd->getClassAlignmentRequirement(toTypeHnd)) + { + indirFlags |= GTF_IND_UNALIGNED; + } + + return gtNewLoadValueNode(toType, toLayout, addr, indirFlags); } case NI_SRCS_UNSAFE_ByteOffset: @@ -4244,7 +4231,14 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, // stobj !!T // ret - return nullptr; + CORINFO_CLASS_HANDLE typeHnd = sig->sigInst.methInst[0]; + ClassLayout* layout = nullptr; + var_types type = TypeHandleToVarType(typeHnd, &layout); + + GenTree* source = impPopStack().val; + GenTree* dest = impPopStack().val; + + return gtNewStoreValueNode(type, layout, dest, gtNewLoadValueNode(type, layout, source)); } case NI_SRCS_UNSAFE_CopyBlock: @@ -4363,26 +4357,21 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, } case NI_SRCS_UNSAFE_Read: - { - assert(sig->sigInst.methInstCount == 1); - - // ldarg.0 - // ldobj !!T - // ret - - return nullptr; - } - case NI_SRCS_UNSAFE_ReadUnaligned: { assert(sig->sigInst.methInstCount == 1); // ldarg.0 - // unaligned. 0x1 + // if NI_SRCS_UNSAFE_ReadUnaligned: unaligned. 0x1 // ldobj !!T // ret - return nullptr; + CORINFO_CLASS_HANDLE typeHnd = sig->sigInst.methInst[0]; + ClassLayout* layout = nullptr; + var_types type = TypeHandleToVarType(typeHnd, &layout); + GenTreeFlags flags = intrinsic == NI_SRCS_UNSAFE_ReadUnaligned ? GTF_IND_UNALIGNED : GTF_EMPTY; + + return gtNewLoadValueNode(type, layout, impPopStack().val, flags); } case NI_SRCS_UNSAFE_SizeOf: @@ -4473,28 +4462,30 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, } case NI_SRCS_UNSAFE_Write: + case NI_SRCS_UNSAFE_WriteUnaligned: { assert(sig->sigInst.methInstCount == 1); // ldarg.0 // ldarg.1 + // if NI_SRCS_UNSAFE_WriteUnaligned: unaligned. 0x01 // stobj !!T // ret - return nullptr; - } - - case NI_SRCS_UNSAFE_WriteUnaligned: - { - assert(sig->sigInst.methInstCount == 1); + CORINFO_CLASS_HANDLE typeHnd = sig->sigInst.methInst[0]; + ClassLayout* layout = nullptr; + var_types type = TypeHandleToVarType(typeHnd, &layout); + GenTreeFlags flags = intrinsic == NI_SRCS_UNSAFE_WriteUnaligned ? GTF_IND_UNALIGNED : GTF_EMPTY; - // ldarg.0 - // ldarg.1 - // unaligned. 0x01 - // stobj !!T - // ret + GenTree* value = impPopStack().val; + GenTree* addr = impPopStack().val; - return nullptr; + GenTree* store = gtNewStoreValueNode(type, layout, addr, value, flags); + if (varTypeIsStruct(store)) + { + store = impStoreStruct(store, CHECK_SPILL_ALL); + } + return store; } default: diff --git a/src/coreclr/jit/promotiondecomposition.cpp b/src/coreclr/jit/promotiondecomposition.cpp index 951e1698a77a7..78b92a53eb3a8 100644 --- a/src/coreclr/jit/promotiondecomposition.cpp +++ b/src/coreclr/jit/promotiondecomposition.cpp @@ -501,15 +501,13 @@ class DecompositionPlan if (m_store->OperIs(GT_STORE_BLK)) { - addr = m_store->AsIndir()->Addr(); - indirFlags = - m_store->gtFlags & (GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_UNALIGNED | GTF_IND_INITCLASS); + addr = m_store->AsIndir()->Addr(); + indirFlags = m_store->gtFlags & GTF_IND_COPYABLE_FLAGS; } else if (m_src->OperIs(GT_BLK)) { - addr = m_src->AsIndir()->Addr(); - indirFlags = - m_src->gtFlags & (GTF_IND_VOLATILE | GTF_IND_NONFAULTING | GTF_IND_UNALIGNED | GTF_IND_INITCLASS); + addr = m_src->AsIndir()->Addr(); + indirFlags = m_src->gtFlags & GTF_IND_COPYABLE_FLAGS; } int numAddrUses = 0; diff --git a/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs b/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs index 8d596482766a4..b07ef0c72d13e 100644 --- a/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs +++ b/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs @@ -157,6 +157,30 @@ public static unsafe void CopyToVoidPtr() Assert.Equal(10, value); } + [Fact] + public static unsafe void CopyToRefGenericStruct() + { + Int32Generic destination = default; + Int32Generic value = new() { Int32 = 5, Value = "a" }; + + Unsafe.Copy(ref destination, Unsafe.AsPointer(ref value)); + + Assert.Equal(5, destination.Int32); + Assert.Equal("a", destination.Value); + } + + [Fact] + public static unsafe void CopyToVoidPtrGenericStruct() + { + Int32Generic destination = default; + Int32Generic value = new() { Int32 = 5, Value = "a" }; + + Unsafe.Copy(Unsafe.AsPointer(ref destination), ref value); + + Assert.Equal(5, destination.Int32); + Assert.Equal("a", destination.Value); + } + [Fact] public static unsafe void SizeOf() { @@ -740,6 +764,20 @@ public static unsafe void ReadUnaligned_ByRef_Struct() Assert.Equal(3.42, actual.Double); } + [Fact] + public static unsafe void ReadUnaligned_ByRef_StructManaged() + { + Int32Generic s = new() { Int32 = 5, Value = "a" }; + + Int32Generic actual = Read>(ref Unsafe.As, byte>(ref s)); + + Assert.Equal(5, actual.Int32); + Assert.Equal("a", actual.Value); + + [MethodImpl(MethodImplOptions.NoInlining)] + static T Read(ref byte b) => Unsafe.ReadUnaligned(ref b); + } + [Fact] public static unsafe void ReadUnaligned_Ptr_Int32() { @@ -814,6 +852,20 @@ public static unsafe void WriteUnaligned_ByRef_Struct() Assert.Equal(3.42, actual.Double); } + [Fact] + public static unsafe void WriteUnaligned_ByRef_StructManaged() + { + Int32Generic actual = default; + + Write(ref Unsafe.As, byte>(ref actual), new Int32Generic() { Int32 = 5, Value = "a" }); + + Assert.Equal(5, actual.Int32); + Assert.Equal("a", actual.Value); + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Write(ref byte b, T value) => Unsafe.WriteUnaligned(ref b, value); + } + [Fact] public static unsafe void WriteUnaligned_Ptr_Int32() { @@ -1133,9 +1185,101 @@ public static unsafe void BitCast() Assert.Throws(() => Unsafe.BitCast(5)); Assert.Throws(() => Unsafe.BitCast(empty1)); + + Assert.Equal(uint.MaxValue, (long)Unsafe.BitCast(-1)); + Assert.Equal(uint.MaxValue, (ulong)Unsafe.BitCast(-1)); + + byte b = 255; + sbyte sb = -1; + + Assert.Equal(255L, (long)Unsafe.BitCast(sb)); + Assert.Equal(-1L, (long)Unsafe.BitCast(b)); + + Assert.Equal(255L, (long)Unsafe.BitCast(b)); + Assert.Equal(ushort.MaxValue, (long)Unsafe.BitCast(sb)); + Assert.Equal(255L, (long)Unsafe.BitCast(b)); + + Assert.Equal(255L, (long)Unsafe.BitCast(b)); + Assert.Equal(uint.MaxValue, (long)Unsafe.BitCast(sb)); + Assert.Equal(255L, (long)Unsafe.BitCast(b)); + + Assert.Equal(255UL, Unsafe.BitCast(b)); + Assert.Equal(ulong.MaxValue, Unsafe.BitCast(sb)); + Assert.Equal(255L, Unsafe.BitCast(b)); + + S2 s2 = BitConverter.IsLittleEndian ? new S2(255, 0) : new S2(0, 255); + S4 s4 = BitConverter.IsLittleEndian ? new S4(255, 0, 0, 0) : new S4(0, 0, 0, 255); + S8 s8 = BitConverter.IsLittleEndian ? new S8(255, 0, 0, 0, 0, 0, 0, 0) : new S8(0, 0, 0, 0, 0, 0, 0, 255); + + Assert.Equal(s2, Unsafe.BitCast(b)); + Assert.Equal(s2, Unsafe.BitCast(b)); + Assert.Equal(new S2(255, 255), Unsafe.BitCast(sb)); + + Assert.Equal(s4, Unsafe.BitCast(b)); + Assert.Equal(s4, Unsafe.BitCast(b)); + Assert.Equal(new S4(255, 255, 255, 255), Unsafe.BitCast(sb)); + + Assert.Equal(s8, Unsafe.BitCast(b)); + Assert.Equal(s8, Unsafe.BitCast(b)); + Assert.Equal(new S8(255, 255, 255, 255, 255, 255, 255, 255), Unsafe.BitCast(sb)); + + Assert.Equal((ushort)255, Unsafe.BitCast(s2)); + Assert.Equal((short)255, Unsafe.BitCast(s2)); + Assert.Equal(255U, Unsafe.BitCast(s4)); + Assert.Equal(255, Unsafe.BitCast(s4)); + Assert.Equal(255UL, Unsafe.BitCast(s8)); + Assert.Equal(255L, Unsafe.BitCast(s8)); + + byte* misalignedPtr = (byte*)NativeMemory.AlignedAlloc(9, 64) + 1; + new Span(misalignedPtr, 8).Clear(); + + *misalignedPtr = 255; + + Assert.Equal(s2, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(s2, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(new S2(255, 255), Unsafe.BitCast(*(sbyte*)misalignedPtr)); + + Assert.Equal(s4, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(s4, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(new S4(255, 255, 255, 255), Unsafe.BitCast(*(sbyte*)misalignedPtr)); + + Assert.Equal(s8, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(s8, Unsafe.BitCast(*misalignedPtr)); + Assert.Equal(new S8(255, 255, 255, 255, 255, 255, 255, 255), Unsafe.BitCast(*(sbyte*)misalignedPtr)); + + *(S2*)misalignedPtr = s2; + Assert.Equal((ushort)255, Unsafe.BitCast(*(S2*)misalignedPtr)); + Assert.Equal((short)255, Unsafe.BitCast(*(S2*)misalignedPtr)); + *(S4*)misalignedPtr = s4; + Assert.Equal(255U, Unsafe.BitCast(*(S4*)misalignedPtr)); + Assert.Equal(255, Unsafe.BitCast(*(S4*)misalignedPtr)); + *(S8*)misalignedPtr = s8; + Assert.Equal(255UL, Unsafe.BitCast(*(S8*)misalignedPtr)); + Assert.Equal(255L, Unsafe.BitCast(*(S8*)misalignedPtr)); + + Half h = Unsafe.ReadUnaligned(ref Unsafe.As(ref s2)); + float s = Unsafe.ReadUnaligned(ref Unsafe.As(ref s4)); + double d = Unsafe.ReadUnaligned(ref Unsafe.As(ref s8)); + + Assert.Equal(h, Unsafe.BitCast(s2)); + Assert.Equal(s, Unsafe.BitCast(s4)); + Assert.Equal(d, Unsafe.BitCast(s8)); + + *(S2*)misalignedPtr = s2; + Assert.Equal(h, Unsafe.BitCast(*(S2*)misalignedPtr)); + *(S4*)misalignedPtr = s4; + Assert.Equal(s, Unsafe.BitCast(*(S4*)misalignedPtr)); + *(S8*)misalignedPtr = s8; + Assert.Equal(d, Unsafe.BitCast(*(S8*)misalignedPtr)); + + NativeMemory.AlignedFree(misalignedPtr - 1); } } + [StructLayout(LayoutKind.Sequential)] public record struct S2(byte a, byte b); + [StructLayout(LayoutKind.Sequential)] public record struct S4(byte a, byte b, byte c, byte d); + [StructLayout(LayoutKind.Sequential)] public record struct S8(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h); + [StructLayout(LayoutKind.Explicit)] public struct Byte4 { @@ -1211,6 +1355,12 @@ public struct StringInt32 public int Int32; } + public struct Int32Generic + { + public int Int32; + public T Value; + } + public struct Single4 { public float X; diff --git a/src/tests/JIT/Intrinsics/BitCast.il b/src/tests/JIT/Intrinsics/BitCast.il new file mode 100644 index 0000000000000..0cba680ad3eff --- /dev/null +++ b/src/tests/JIT/Intrinsics/BitCast.il @@ -0,0 +1,228 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// This test checks implicit floating point and small type casts in IL + +.assembly extern System.Runtime {} +.assembly extern System.Console {} +.assembly Bitcast {} + +.class public abstract auto ansi sealed beforefieldinit Program + extends [System.Runtime]System.Object +{ + .field private static int32 exitCode + .method public hidebysig static int32 Main() cil managed + { + .entrypoint + // Code size 306 (0x132) + .maxstack 3 + .locals init (uint64 V_0, + int8* V_1, + int16* V_2, + int32* V_3) + IL_0000: ldc.i8 0x5555555555555555 + IL_0009: stloc.0 + IL_000a: ldloca.s V_0 + IL_000c: conv.u + IL_000d: ldc.i4.3 + IL_000e: add + IL_000f: stloc.1 + IL_0010: ldloc.1 + IL_0011: ldc.i4.m1 + IL_0012: stind.i1 + IL_0013: ldc.i4 0xff + IL_0018: conv.i8 + IL_0019: ldloc.1 + IL_001a: call !!0* Program::NoInline(!!0*) + IL_001f: ldind.i1 + IL_0020: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_0025: conv.u8 + IL_0026: ldc.i4.s 18 + IL_0028: call void Program::Verify(int64, + int64, + int32) + IL_002d: ldc.i4.m1 + IL_002e: conv.i8 + IL_002f: ldloc.1 + IL_0030: call !!0* Program::NoInline(!!0*) + IL_0035: ldind.u1 + IL_0036: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_003b: conv.i8 + IL_003c: ldc.i4.s 19 + IL_003e: call void Program::Verify(int64, + int64, + int32) + IL_0043: ldloca.s V_0 + IL_0045: conv.u + IL_0046: ldc.i4.2 + IL_0047: add + IL_0048: stloc.2 + IL_0049: ldloc.2 + IL_004a: ldc.i4.m1 + IL_004b: stind.i2 + IL_004c: ldc.i4 0xffff + IL_0051: conv.i8 + IL_0052: ldloc.2 + IL_0053: call !!0* Program::NoInline(!!0*) + IL_0058: ldind.i2 + IL_0059: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_005e: conv.u8 + IL_005f: ldc.i4.s 23 + IL_0061: call void Program::Verify(int64, + int64, + int32) + IL_0066: ldc.i4.m1 + IL_0067: conv.i8 + IL_0068: ldloc.2 + IL_0069: call !!0* Program::NoInline(!!0*) + IL_006e: ldind.u2 + IL_006f: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_0074: conv.i8 + IL_0075: ldc.i4.s 24 + IL_0077: call void Program::Verify(int64, + int64, + int32) + IL_007c: ldloc.2 + IL_007d: stloc.3 + IL_007e: ldloc.3 + IL_007f: ldc.i4.m1 + IL_0080: unaligned. 2 + IL_0083: stind.i4 + IL_0084: ldc.i4.m1 + IL_0085: conv.u8 + IL_0086: ldloc.3 + IL_0087: call !!0* Program::NoInline(!!0*) + IL_008c: unaligned. 2 + IL_008f: ldind.i4 + IL_0090: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_0095: conv.u8 + IL_0096: ldc.i4.s 28 + IL_0098: call void Program::Verify(int64, + int64, + int32) + IL_009d: ldc.i4.m1 + IL_009e: conv.i8 + IL_009f: ldloc.3 + IL_00a0: call !!0* Program::NoInline(!!0*) + IL_00a5: unaligned. 2 + IL_00a8: ldind.u4 + IL_00a9: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_00ae: conv.i8 + IL_00af: ldc.i4.s 29 + IL_00b1: call void Program::Verify(int64, + int64, + int32) + IL_00b6: ldc.i4 0x3f800000 + IL_00bb: conv.i8 + IL_00bc: ldc.r8 1. + IL_00c5: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_00ca: conv.i8 + IL_00cb: ldc.i4.s 31 + IL_00cd: call void Program::Verify(int64, + int64, + int32) + IL_00d2: ldc.i8 0x3ff0000000000000 + IL_00db: ldc.r4 1. + IL_00e0: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_00e5: ldc.i4.s 32 + IL_00e7: call void Program::Verify(int64, + int64, + int32) + IL_00ec: ldc.i4 0x3f800000 + IL_00f1: conv.i8 + IL_00f2: ldc.r8 1. + IL_00fb: call !!0 Program::NoInline(!!0) + IL_0100: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_0105: conv.i8 + IL_0106: ldc.i4.s 33 + IL_0108: call void Program::Verify(int64, + int64, + int32) + IL_010d: ldc.i8 0x3ff0000000000000 + IL_0116: ldc.r4 1. + IL_011b: call !!0 Program::NoInline(!!0) + IL_0120: call !!1 [System.Runtime]System.Runtime.CompilerServices.Unsafe::BitCast(!!0) + IL_0125: ldc.i4.s 34 + IL_0127: call void Program::Verify(int64, + int64, + int32) + IL_012c: ldsfld int32 Program::exitCode + IL_0131: ret + } // end of method Program::Main + + .method private hidebysig static void Verify(int64 expected, + int64 actual, + [opt] int32 line) cil managed noinlining + { + .param [3] = int32(0x00000000) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CallerLineNumberAttribute::.ctor() = ( 01 00 00 00 ) + // Code size 100 (0x64) + .maxstack 3 + .locals init (valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: bne.un.s IL_0011 + + IL_0004: ldsfld int32 Program::exitCode + IL_0009: ldc.i4.1 + IL_000a: sub + IL_000b: stsfld int32 Program::exitCode + IL_0010: ret + + IL_0011: ldloca.s V_0 + IL_0013: ldc.i4.s 32 + IL_0015: ldc.i4.3 + IL_0016: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, + int32) + IL_001b: ldloca.s V_0 + IL_001d: ldstr "Failed at line " + IL_0022: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string) + IL_0027: ldloca.s V_0 + IL_0029: ldarg.2 + IL_002a: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_002f: ldloca.s V_0 + IL_0031: ldstr ", expected " + IL_0036: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string) + IL_003b: ldloca.s V_0 + IL_003d: ldarg.0 + IL_003e: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_0043: ldloca.s V_0 + IL_0045: ldstr ", got " + IL_004a: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string) + IL_004f: ldloca.s V_0 + IL_0051: ldarg.1 + IL_0052: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted(!!0) + IL_0057: ldloca.s V_0 + IL_0059: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear() + IL_005e: call void [System.Console]System.Console::WriteLine(string) + IL_0063: ret + } // end of method Program::Verify + + .method private hidebysig static !!T NoInline(!!T 'value') cil managed noinlining + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ret + } // end of method Program::NoInline + + .method private hidebysig static !!T* NoInline(!!T* 'value') cil managed noinlining + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ret + } // end of method Program::NoInline + + .method private hidebysig specialname rtspecialname static + void .cctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldc.i4.s 110 + IL_0002: stsfld int32 Program::exitCode + IL_0007: ret + } // end of method Program::.cctor + +} // end of class Program diff --git a/src/tests/JIT/Intrinsics/BitCast.ilproj b/src/tests/JIT/Intrinsics/BitCast.ilproj new file mode 100644 index 0000000000000..8b9c8251b5ca0 --- /dev/null +++ b/src/tests/JIT/Intrinsics/BitCast.ilproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + +