}
//------------------------------------------------------------------------
-// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar
-//
-// Arguments:
-// treeNode - A TYP_LONG lclVar node.
-//
-// Return Value:
-// None.
-//
-// Assumptions:
-// 'treeNode' must be a TYP_LONG lclVar node for a lclVar that has NOT been promoted.
-// Its operand must be a GT_LONG node.
-//
-void CodeGen::genStoreLongLclVar(GenTree* treeNode)
-{
- emitter* emit = getEmitter();
-
- GenTreeLclVarCommon* lclNode = treeNode->AsLclVarCommon();
- unsigned lclNum = lclNode->gtLclNum;
- LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]);
- assert(varDsc->TypeGet() == TYP_LONG);
- assert(!varDsc->lvPromoted);
- GenTree* op1 = treeNode->gtOp.gtOp1;
- noway_assert(op1->OperGet() == GT_LONG || op1->OperGet() == GT_MUL_LONG);
- genConsumeRegs(op1);
-
- if (op1->OperGet() == GT_LONG)
- {
- // Definitions of register candidates will have been lowered to 2 int lclVars.
- assert(!treeNode->gtHasReg());
-
- GenTree* loVal = op1->gtGetOp1();
- GenTree* hiVal = op1->gtGetOp2();
-
- noway_assert((loVal->gtRegNum != REG_NA) && (hiVal->gtRegNum != REG_NA));
-
- emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
- emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
- }
- else if (op1->OperGet() == GT_MUL_LONG)
- {
- assert((op1->gtFlags & GTF_MUL_64RSLT) != 0);
-
- GenTreeMultiRegOp* mul = op1->AsMultiRegOp();
-
- // Stack store
- getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), mul->gtRegNum, lclNum, 0);
- getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), mul->gtOtherReg, lclNum,
- genTypeSize(TYP_INT));
- }
-}
-
-//------------------------------------------------------------------------
// genCodeForMulLong: Generates code for int*int->long multiplication
//
// Arguments:
}
// The per-case functions call genProduceReg()
}
+
+#if !defined(_TARGET_64BIT_)
+//------------------------------------------------------------------------
+// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar
+//
+// Arguments:
+// treeNode - A TYP_LONG lclVar node.
+//
+// Return Value:
+// None.
+//
+// Assumptions:
+// 'treeNode' must be a TYP_LONG lclVar node for a lclVar that has NOT been promoted.
+// Its operand must be a GT_LONG node.
+//
+void CodeGen::genStoreLongLclVar(GenTree* treeNode)
+{
+ emitter* emit = getEmitter();
+
+ GenTreeLclVarCommon* lclNode = treeNode->AsLclVarCommon();
+ unsigned lclNum = lclNode->gtLclNum;
+ LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]);
+ assert(varDsc->TypeGet() == TYP_LONG);
+ assert(!varDsc->lvPromoted);
+ GenTree* op1 = treeNode->gtOp.gtOp1;
+
+ // A GT_LONG is always contained, so it cannot have RELOAD or COPY inserted between it and its consumer,
+ // but a MUL_LONG may.
+ noway_assert(op1->OperIs(GT_LONG) || op1->gtSkipReloadOrCopy()->OperIs(GT_MUL_LONG));
+ genConsumeRegs(op1);
+
+ if (op1->OperGet() == GT_LONG)
+ {
+ GenTree* loVal = op1->gtGetOp1();
+ GenTree* hiVal = op1->gtGetOp2();
+
+ noway_assert((loVal->gtRegNum != REG_NA) && (hiVal->gtRegNum != REG_NA));
+
+ emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
+ emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
+ }
+ else
+ {
+ assert((op1->gtSkipReloadOrCopy()->gtFlags & GTF_MUL_64RSLT) != 0);
+ // This is either a multi-reg MUL_LONG, or a multi-reg reload or copy.
+ assert(op1->IsMultiRegNode() && (op1->GetMultiRegCount() == 2));
+
+ // Stack store
+ emit->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), op1->GetRegByIndex(0), lclNum, 0);
+ emit->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), op1->GetRegByIndex(1), lclNum,
+ genTypeSize(TYP_INT));
+ }
+}
+#endif // !defined(_TARGET_64BIT_)
genProduceReg(tree);
}
+//------------------------------------------------------------------------
+// genRegCopy: Produce code for a GT_COPY node.
+//
+// Arguments:
+// tree - the GT_COPY node
+//
+// Notes:
+// This will copy the register(s) produced by this nodes source, to
+// the register(s) allocated to this GT_COPY node.
+// It has some special handling for these casess:
+// - when the source and target registers are in different register files
+// (note that this is *not* a conversion).
+// - when the source is a lclVar whose home location is being moved to a new
+// register (rather than just being copied for temporary use).
+//
void CodeGen::genRegCopy(GenTree* treeNode)
{
assert(treeNode->OperGet() == GT_COPY);
GenTree* op1 = treeNode->gtOp.gtOp1;
- if (op1->IsMultiRegCall())
+ if (op1->IsMultiRegNode())
{
genConsumeReg(op1);
- GenTreeCopyOrReload* copyTree = treeNode->AsCopyOrReload();
- GenTreeCall* call = op1->AsCall();
- ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
- unsigned regCount = retTypeDesc->GetReturnRegCount();
+ GenTreeCopyOrReload* copyTree = treeNode->AsCopyOrReload();
+ unsigned regCount = treeNode->GetMultiRegCount();
for (unsigned i = 0; i < regCount; ++i)
{
- var_types type = retTypeDesc->GetReturnRegType(i);
- regNumber fromReg = call->GetRegNumByIdx(i);
+ var_types type = op1->GetRegTypeByIndex(i);
+ regNumber fromReg = op1->GetRegByIndex(i);
regNumber toReg = copyTree->GetRegNumByIdx(i);
- // A Multi-reg GT_COPY node will have valid reg only for those
- // positions that corresponding result reg of call node needs
- // to be copied.
+ // A Multi-reg GT_COPY node will have a valid reg only for those positions for which a corresponding
+ // result reg of the multi-reg node needs to be copied.
if (toReg != REG_NA)
{
assert(toReg != fromReg);
regSet.verifyRegistersUsed(killMask);
}
-#if !defined(_TARGET_64BIT_)
-//-----------------------------------------------------------------------------
-//
-// Code Generation for Long integers
-//
-//-----------------------------------------------------------------------------
-
-//------------------------------------------------------------------------
-// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar
-//
-// Arguments:
-// treeNode - A TYP_LONG lclVar node.
-//
-// Return Value:
-// None.
-//
-// Assumptions:
-// 'treeNode' must be a TYP_LONG lclVar node for a lclVar that has NOT been promoted.
-// Its operand must be a GT_LONG node.
-//
-void CodeGen::genStoreLongLclVar(GenTree* treeNode)
-{
- emitter* emit = getEmitter();
-
- GenTreeLclVarCommon* lclNode = treeNode->AsLclVarCommon();
- unsigned lclNum = lclNode->gtLclNum;
- LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]);
- assert(varDsc->TypeGet() == TYP_LONG);
- assert(!varDsc->lvPromoted);
- GenTree* op1 = treeNode->gtOp.gtOp1;
- noway_assert(op1->OperGet() == GT_LONG || op1->OperGet() == GT_MUL_LONG);
- genConsumeRegs(op1);
-
- if (op1->OperGet() == GT_LONG)
- {
- GenTree* loVal = op1->gtGetOp1();
- GenTree* hiVal = op1->gtGetOp2();
-
- noway_assert((loVal->gtRegNum != REG_NA) && (hiVal->gtRegNum != REG_NA));
-
- emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
- emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
- }
- else if (op1->OperGet() == GT_MUL_LONG)
- {
- assert((op1->gtFlags & GTF_MUL_64RSLT) != 0);
-
- // Stack store
- getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_LO, lclNum, 0);
- getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_HI, lclNum,
- genTypeSize(TYP_INT));
- }
-}
-#endif // !defined(_TARGET_64BIT_)
-
/*****************************************************************************
* Unit testing of the XArch emitter: generate a bunch of instructions into the prolog
* (it's as good a place as any), then use COMPlus_JitLateDisasm=* to see if the late
{
return (const_cast<GenTree*>(this))->AsPutArgSplit()->gtNumRegs;
}
- // A PUTARG_REG could be a MultiRegOp on ARM since we could move a double register to two int registers,
- // either for all double parameters w/SoftFP or for varargs).
- else
+#endif
+#if !defined(_TARGET_64BIT_)
+ else if (OperIsMultiRegOp())
{
+ // A MultiRegOp is a GT_MUL_LONG, GT_PUTARG_REG, or GT_BITCAST.
+ // For the latter two (ARM-only), they only have multiple registers if they produce a long value
+ // (GT_MUL_LONG always produces a long value).
+ CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef _TARGET_ARM_
- assert(OperIsMultiRegOp());
return (TypeGet() == TYP_LONG) ? 2 : 1;
#else
- unreached();
-#endif // _TARGET_ARM_
+ assert(OperIs(GT_MUL_LONG));
+ return 2;
+#endif
}
-#endif // FEATURE_ARG_SPLIT
+#endif
assert(!"Unexpected multi-reg node");
return 0;
}
bool OperIsMultiRegOp() const
{
+#if !defined(_TARGET_64BIT_)
+ if (OperIs(GT_MUL_LONG))
+ {
+ return true;
+ }
#if defined(_TARGET_ARM_)
- if ((gtOper == GT_MUL_LONG) || (gtOper == GT_PUTARG_REG) || (gtOper == GT_BITCAST))
+ if (OperIs(GT_PUTARG_REG, GT_BITCAST))
{
return true;
}
-#endif
-
+#endif // _TARGET_ARM_
+#endif // _TARGET_64BIT_
return false;
}
#endif
};
-#if defined(_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
struct GenTreeMultiRegOp : public GenTreeOp
{
regNumber gtOtherReg;
}
#endif
};
-#endif // defined(_TARGET_ARM_)
+#endif // !defined(_TARGET_64BIT_)
struct GenTreeFptrVal : public GenTree
{
#endif // FEATURE_ARG_SPLIT
// Represents GT_COPY or GT_RELOAD node
+//
+// As it turns out, these are only needed on targets that happen to have multi-reg returns.
+// However, they are actually needed on any target that has any multi-reg ops. It is just
+// coincidence that those are the same (and there isn't a FEATURE_MULTIREG_OPS).
+//
struct GenTreeCopyOrReload : public GenTreeUnOp
{
#if FEATURE_MULTIREG_RET
return true;
}
-#if defined(_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
if (OperIsMultiRegOp() || OperIsPutArgSplit() || (gtOper == GT_COPY))
{
return true;
return AsPutArgSplit()->gtNumRegs;
}
#endif
-#if defined(_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
if (OperIsMultiRegOp())
{
return AsMultiRegOp()->GetRegCount();
return AsPutArgSplit()->GetRegNumByIdx(regIndex);
}
#endif
-#if defined(_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
if (OperIsMultiRegOp())
{
return AsMultiRegOp()->GetRegNumByIdx(regIndex);
return AsPutArgSplit()->GetRegType(regIndex);
}
#endif
-#if defined(_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
if (OperIsMultiRegOp())
{
return AsMultiRegOp()->GetRegType(regIndex);
// with long results are morphed into helper calls. It is similar to GT_MULHI,
// the difference being that GT_MULHI drops the lo part of the result, whereas
// GT_MUL_LONG keeps both parts of the result.
-#if defined(_TARGET_X86_)
-GTNODE(MUL_LONG , GenTreeOp ,1,GTK_BINOP)
-#elif defined (_TARGET_ARM_)
+#if !defined(_TARGET_64BIT_)
GTNODE(MUL_LONG , GenTreeMultiRegOp ,1,GTK_BINOP)
#endif
GTSTRUCT_1(AllocObj , GT_ALLOCOBJ)
GTSTRUCT_1(RuntimeLookup, GT_RUNTIMELOOKUP)
GTSTRUCT_2(CC , GT_JCC, GT_SETCC)
-#if defined(_TARGET_ARM_)
+#if defined(_TARGET_X86_)
+GTSTRUCT_1(MultiRegOp , GT_MUL_LONG)
+#elif defined (_TARGET_ARM_)
GTSTRUCT_3(MultiRegOp , GT_MUL_LONG, GT_PUTARG_REG, GT_BITCAST)
#endif
/*****************************************************************************/
{
tree->gtRegNum = reg;
}
-#if FEATURE_ARG_SPLIT
-#ifdef _TARGET_ARM_
+#if !defined(_TARGET_64BIT_)
else if (tree->OperIsMultiRegOp())
{
assert(regIdx == 1);
GenTreeMultiRegOp* mul = tree->AsMultiRegOp();
mul->gtOtherReg = reg;
}
-#endif // _TARGET_ARM_
+#endif // _TARGET_64BIT_
+#if FEATURE_MULTIREG_RET
else if (tree->OperGet() == GT_COPY)
{
assert(regIdx == 1);
GenTreeCopyOrReload* copy = tree->AsCopyOrReload();
copy->gtOtherRegs[0] = (regNumberSmall)reg;
}
+#endif // FEATURE_MULTIREG_RET
+#if FEATURE_ARG_SPLIT
else if (tree->OperIsPutArgSplit())
{
GenTreePutArgSplit* putArg = tree->AsPutArgSplit();
#endif
}
- // If the parent is a reload/copy node, then tree must be a multi-reg call node
- // that has already had one of its registers spilled. This is because multi-reg
- // call node is the only node whose RefTypeDef positions get independently
- // spilled or reloaded. It is possible that one of its RefTypeDef position got
- // spilled and the next use of it requires it to be in a different register.
+ // If the parent is a reload/copy node, then tree must be a multi-reg node
+ // that has already had one of its registers spilled.
+ // It is possible that one of its RefTypeDef positions got spilled and the next
+ // use of it requires it to be in a different register.
//
// In this case set the i'th position reg of reload/copy node to the reg allocated
// for copy/reload refPosition. Essentially a copy/reload node will have a reg
if (parent->IsCopyOrReload())
{
noway_assert(parent->OperGet() == oper);
- noway_assert(tree->IsMultiRegCall());
- GenTreeCall* call = tree->AsCall();
+ noway_assert(tree->IsMultiRegNode());
GenTreeCopyOrReload* copyOrReload = parent->AsCopyOrReload();
noway_assert(copyOrReload->GetRegNumByIdx(multiRegIdx) == REG_NA);
copyOrReload->SetRegNumByIdx(refPosition->assignedReg(), multiRegIdx);
}
else
{
- _snprintf_s(operandString, operandStringLength, operandStringLength, "%s%s",
- getRegName(tree->gtRegNum, useFloatReg(tree->TypeGet())), lastUseChar);
+ regNumber reg = tree->gtRegNum;
+ int charCount = _snprintf_s(operandString, operandStringLength, operandStringLength, "%s%s",
+ getRegName(reg, genIsValidFloatReg(reg)), lastUseChar);
+ operandString += charCount;
+ operandStringLength -= charCount;
+
+ if (tree->IsMultiRegNode())
+ {
+ unsigned regCount = tree->GetMultiRegCount();
+ for (unsigned regIndex = 1; regIndex < regCount; regIndex++)
+ {
+ regNumber reg = tree->GetRegByIndex(regIndex);
+ charCount = _snprintf_s(operandString, operandStringLength, operandStringLength, ",%s%s",
+ getRegName(reg, genIsValidFloatReg(reg)), lastUseChar);
+ operandString += charCount;
+ operandStringLength -= charCount;
+ }
+ }
}
}
break;
if (dstCount != 0)
{
// This operand directly produces registers; print it.
- for (int i = 0; i < dstCount; i++)
+ if (!first)
{
- if (!first)
- {
- printf(",");
- }
-
- lsraGetOperandString(operand, mode, operandString, operandStringLength);
- printf("%s", operandString);
-
- first = false;
+ printf(",");
}
+ lsraGetOperandString(operand, mode, operandString, operandStringLength);
+ printf("%s", operandString);
+ first = false;
}
else if (operand->isContained())
{
{
if (op1->OperIs(GT_MUL_LONG))
{
-#ifdef _TARGET_X86_
- // This is actually a bug. A GT_MUL_LONG produces two registers, but is modeled as only producing
- // eax (and killing edx). This only works because it always occurs as var = GT_MUL_LONG (ensured by
- // DecomposeMul), and therefore edx won't be reused before the store.
- // TODO-X86-Cleanup: GT_MUL_LONG should be a multireg node on x86, just as on ARM.
- srcCount = 1;
- singleUseRef = BuildUse(op1);
-#else
srcCount = 2;
BuildUse(op1, allRegs(TYP_INT), 0);
BuildUse(op1, allRegs(TYP_INT), 1);
-#endif
}
else
{
srcCount = BuildModDiv(tree->AsOp());
break;
- case GT_MUL:
- case GT_MULHI:
#if defined(_TARGET_X86_)
case GT_MUL_LONG:
+ dstCount = 2;
+ __fallthrough;
#endif
+ case GT_MUL:
+ case GT_MULHI:
srcCount = BuildMul(tree->AsOp());
break;
} // end switch (tree->OperGet())
- // We need to be sure that we've set srcCount and dstCount appropriately
- assert((dstCount < 2) || (tree->IsMultiRegCall() && dstCount == MAX_RET_REG_COUNT));
+ // We need to be sure that we've set srcCount and dstCount appropriately.
+ // Not that for XARCH, the maximum number of registers defined is 2.
+ assert((dstCount < 2) || ((dstCount == 2) && tree->IsMultiRegNode()));
assert(isLocalDefUse == (tree->IsValue() && tree->IsUnusedValue()));
assert(!tree->IsUnusedValue() || (dstCount != 0));
assert(dstCount == tree->GetRegisterDstCount());
}
int srcCount = BuildBinaryUses(tree->AsOp());
+ int dstCount = 1;
regMaskTP dstCandidates = RBM_NONE;
bool isUnsignedMultiply = ((tree->gtFlags & GTF_UNSIGNED) != 0);
else if (tree->OperGet() == GT_MUL_LONG)
{
// have to use the encoding:RDX:RAX = RAX * rm
- dstCandidates = RBM_RAX;
+ dstCandidates = RBM_RAX | RBM_RDX;
+ dstCount = 2;
}
#endif
GenTree* containedMemOp = nullptr;
containedMemOp = op2;
}
regMaskTP killMask = getKillSetForMul(tree->AsOp());
- BuildDefsWithKills(tree, 1, dstCandidates, killMask);
+ BuildDefsWithKills(tree, dstCount, dstCandidates, killMask);
return srcCount;
}
MaxAllowedDurationSeconds=600
Categories=EXPECTED_PASS
HostStyle=0
+
+[GitHub_19397.cmd_11916]
+RelativePath=JIT\Regression\JitBlue\GitHub_19397\GitHub_19397\GitHub_19397.cmd
+WorkingDir=JIT\Regression\JitBlue\GitHub_19397\GitHub_19397
+Expected=0
+MaxAllowedDurationSeconds=600
+Categories=EXPECTED_PASS
+HostStyle=0
MaxAllowedDurationSeconds=600
Categories=EXPECTED_PASS
HostStyle=0
+
+[GitHub_19397.cmd_12235]
+RelativePath=JIT\Regression\JitBlue\GitHub_19397\GitHub_19397\GitHub_19397.cmd
+WorkingDir=JIT\Regression\JitBlue\GitHub_19397\GitHub_19397
+Expected=0
+MaxAllowedDurationSeconds=600
+Categories=EXPECTED_PASS
+HostStyle=0
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.Runtime.CompilerServices;
+
+// This is a regression test for a bug that was exposed with jitStressRegs=8 in
+// System.Text.ConsoleEncoding::GetMaxByteCount, due to the lack of handling
+// of a register-allocator-added COPY of a multi-reg multiply.
+
+namespace GitHub_19397
+{
+ public class Program
+ {
+ [MethodImplAttribute(MethodImplOptions.NoInlining)]
+ public static long getValue()
+ {
+ return(0x0101010101010101L);
+ }
+ static int Main()
+ {
+ long value = getValue();
+ Console.WriteLine($"Result is {value}");
+ if (value == 0x0101010101010101L)
+ {
+ Console.WriteLine("PASS");
+ return 100;
+ }
+ else
+ {
+ Console.WriteLine("FAIL");
+ return -1;
+ }
+ }
+
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{76E69AA0-8C5A-4F76-8561-B8089FFA8D79}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <PropertyGroup>
+ <DebugType></DebugType>
+ <Optimize>True</Optimize>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>