diff --git a/.devcontainer/wasm/Dockerfile b/.devcontainer/wasm/Dockerfile
index ffb5b4f494b1ed..17ceadc668b0bf 100644
--- a/.devcontainer/wasm/Dockerfile
+++ b/.devcontainer/wasm/Dockerfile
@@ -27,9 +27,12 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
zlib1g-dev \
ninja-build
-# Install V8 Engine
SHELL ["/bin/bash", "-c"]
+# Install LTS npm and node
+RUN source /usr/local/share/nvm/nvm.sh && nvm install --lts
+
+# Install V8 Engine
RUN curl -sSL "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/linux/chromium-v8/v8-linux64-rel-8.5.183.zip" -o ./v8.zip \
&& unzip ./v8.zip -d /usr/local/v8 \
&& echo $'#!/usr/bin/env bash\n\
diff --git a/eng/Subsets.props b/eng/Subsets.props
index 093120feb24111..2906f49cd76d73 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -357,9 +357,6 @@
-
-
-
diff --git a/eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml b/eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
index 504f921e9de1f5..bd147de7e77264 100644
--- a/eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
+++ b/eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml
@@ -10,7 +10,7 @@ parameters:
steps:
- ${{ if eq(parameters.osGroup, 'windows') }}:
# TODO: Remove and consolidate this when we move to arcade via init-tools.cmd.
- - powershell: $(Build.SourcesDirectory)\eng\common\build.ps1 -ci ${{ parameters.restoreParams }}
+ - powershell: $(Build.SourcesDirectory)\eng\common\build.ps1 -ci -warnaserror 0 ${{ parameters.restoreParams }}
displayName: Restore blob feed tasks (Windows)
condition: and(succeeded(), ${{ and(ne(parameters.condition, false), ne(parameters.restoreParams, '')) }})
@@ -30,7 +30,7 @@ steps:
# Arcade uses this SDK instead of trying to restore one.
DotNetCoreSdkDir: /usr/local/dotnet
- - script: $(Build.SourcesDirectory)/eng/common/msbuild.sh --ci ${{ parameters.sendParams }}
+ - script: $(Build.SourcesDirectory)/eng/common/msbuild.sh --ci --warnaserror false ${{ parameters.sendParams }}
displayName: ${{ parameters.displayName }} (Unix)
condition: and(succeeded(), ${{ and(ne(parameters.condition, false), ne(parameters.sendParams, '')) }})
env: ${{ parameters.environment }}
diff --git a/src/coreclr/crossgen-corelib.proj b/src/coreclr/crossgen-corelib.proj
index 7be878d4e19abf..0ee3d5185c1934 100644
--- a/src/coreclr/crossgen-corelib.proj
+++ b/src/coreclr/crossgen-corelib.proj
@@ -1,72 +1,85 @@
-
-
- $(TargetOS).$(TargetArchitecture).$(Configuration)
- $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts'))
- $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'log'))
- $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'bin', 'coreclr', $(OSPlatformConfig)))
- $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'obj', 'coreclr', $(OSPlatformConfig)))
- $([MSBuild]::NormalizePath('$(RepoRoot)', 'dotnet.sh'))
- $([MSBuild]::NormalizePath('$(RepoRoot)', 'dotnet.cmd'))
-
+
+
+
+
+
+
+
+
+ $(TargetOS).$(TargetArchitecture).$(Configuration)
+ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts'))
+ $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'log'))
+ $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'bin', 'coreclr', $(OSPlatformConfig)))
+ $([MSBuild]::NormalizeDirectory('$(RootBinDir)', 'obj', 'coreclr', $(OSPlatformConfig)))
+ $([MSBuild]::NormalizePath('$(RepoRoot)', 'dotnet.sh'))
+ $([MSBuild]::NormalizePath('$(RepoRoot)', 'dotnet.cmd'))
+
+
+
+ true
+ false
+ false
+
+ false
+ true
+
+ false
+ true
+
+
+
+
+
+
+
+ System.Private.CoreLib
+ $([MSBuild]::NormalizePath('$(BinDir)', '$(CoreLibAssemblyName).dll'))
+
+
+ $([MSBuild]::NormalizePath('$(BinDir)', 'PDB', '$(CoreLibAssemblyName).ni.pdb'))
+ $([MSBuild]::NormalizePath('$(BinDir)', '$(CoreLibAssemblyName).ni.r2rmap'))
+ $([MSBuild]::NormalizePath('$(BinDir)', 'StandardOptimizationData.mibc'))
+
+
+
-
- $(BuildArchitecture)
-
- true
- false
- false
-
- false
- true
-
- false
- true
+ $(DotNetCli) @(DotNetPgo) merge
+ $(DotNetPgoCmd) -o:$(MergedMibcPath)
+ $(DotNetPgoCmd) @(OptimizationMibcFiles->'-i:%(Identity)', ' ')
+ $(DotNetPgoCmd) --inherit-timestamp
+ $(DotNetPgoCmd) --compressed false
-
-
-
-
-
-
-
-
-
-
- System.Private.CoreLib
- $([MSBuild]::NormalizePath('$(BinDir)', 'IL', '$(CoreLibAssemblyName).dll'))
- $([MSBuild]::NormalizePath('$(BinDir)', '$(CoreLibAssemblyName).dll'))
-
-
- $([MSBuild]::NormalizePath('$(BinDir)', 'PDB', '$(CoreLibAssemblyName).ni.pdb'))
- $([MSBuild]::NormalizePath('$(BinDir)', '$(CoreLibAssemblyName).ni.r2rmap'))
- $([MSBuild]::NormalizePath('$(BinDir)', 'StandardOptimizationData.mibc'))
-
+
+
+ DependsOnTargets="ResolveProjectReferences;MergeMibcFiles" />
+
+
+
+
+
+
- $(DotNetCli) $([MSBuild]::NormalizePath('$(BinDir)', 'dotnet-pgo', 'dotnet-pgo.dll')) merge
- $(DotNetPgoCmd) -o:$(MergedMibcPath)
- $(DotNetPgoCmd) @(OptimizationMibcFiles->'-i:%(Identity)', ' ')
- $(DotNetPgoCmd) --inherit-timestamp
- $(DotNetPgoCmd) --compressed false
+ %(Crossgen2.RootDir)%(Crossgen2.Directory)
-
-
-
+
+
+
@@ -77,7 +90,7 @@
Text="Generating native image of System.Private.CoreLib for $(OSPlatformConfig). Logging to $(CrossGenCoreLibLog)" />
- $(DotNetCli) $([MSBuild]::NormalizePath('$(BinDir)', '$(CrossDir)', 'crossgen2', 'crossgen2.dll'))
+ $(DotNetCli) @(Crossgen2)
$(CrossGenDllCmd) -o:$(CoreLibOutputPath)
$(CrossGenDllCmd) -r:$([MSBuild]::NormalizePath('$(BinDir)', 'IL', '*.dll'))
$(CrossGenDllCmd) --targetarch:$(TargetArchitecture)
@@ -85,7 +98,7 @@
$(CrossGenDllCmd) -m:$(MergedMibcPath) --embed-pgo-data
$(CrossGenDllCmd) -O
$(CrossGenDllCmd) --verify-type-and-field-layout
- $(CrossGenDllCmd) $(CoreLibInputPath)
+ $(CrossGenDllCmd) @(CoreLib)
@@ -111,7 +124,7 @@
-
+
diff --git a/src/coreclr/gc/env/gcenv.structs.h b/src/coreclr/gc/env/gcenv.structs.h
index 0019ae6c988672..9f287ec7bf8c26 100644
--- a/src/coreclr/gc/env/gcenv.structs.h
+++ b/src/coreclr/gc/env/gcenv.structs.h
@@ -17,20 +17,6 @@ typedef void * HANDLE;
#ifdef TARGET_UNIX
-typedef char TCHAR;
-#define _T(s) s
-
-#else
-
-#ifndef _INC_WINDOWS
-typedef wchar_t TCHAR;
-#define _T(s) L##s
-#endif
-
-#endif
-
-#ifdef TARGET_UNIX
-
class EEThreadId
{
pthread_t m_id;
diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h
index aec68a7bff2a73..5d89ef8d01832c 100644
--- a/src/coreclr/jit/codegen.h
+++ b/src/coreclr/jit/codegen.h
@@ -901,7 +901,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void genCodeForCompare(GenTreeOp* tree);
#ifdef TARGET_ARM64
void genCodeForCCMP(GenTreeCCMP* ccmp);
- void genCodeForCinc(GenTreeOp* cinc);
#endif
void genCodeForSelect(GenTreeOp* select);
void genIntrinsic(GenTreeIntrinsic* treeNode);
@@ -1250,7 +1249,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#if defined(TARGET_ARM64)
void genCodeForJumpCompare(GenTreeOpCC* tree);
void genCodeForBfiz(GenTreeOp* tree);
- void genCodeForCond(GenTreeOp* tree);
#endif // TARGET_ARM64
#if defined(FEATURE_EH_FUNCLETS)
diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp
index 256b766679545d..8e0faf1f05e381 100644
--- a/src/coreclr/jit/codegenarm64.cpp
+++ b/src/coreclr/jit/codegenarm64.cpp
@@ -4683,32 +4683,49 @@ void CodeGen::genCodeForCCMP(GenTreeCCMP* ccmp)
}
//------------------------------------------------------------------------
-// genCodeForSelect: Produce code for a GT_SELECT node.
+// genCodeForSelect: Produce code for a GT_SELECT/GT_SELECT_INV/GT_SELECT_NEG node.
//
// Arguments:
// tree - the node
//
void CodeGen::genCodeForSelect(GenTreeOp* tree)
{
- assert(tree->OperIs(GT_SELECT, GT_SELECTCC));
- GenTree* opcond = nullptr;
- if (tree->OperIs(GT_SELECT))
+ assert(tree->OperIs(GT_SELECT, GT_SELECTCC, GT_SELECT_INC, GT_SELECT_INCCC, GT_SELECT_INV, GT_SELECT_INVCC,
+ GT_SELECT_NEG, GT_SELECT_NEGCC));
+ GenTree* opcond = nullptr;
+ instruction ins = INS_csel;
+ GenTree* op1 = tree->gtOp1;
+ GenTree* op2 = tree->gtOp2;
+
+ if (tree->OperIs(GT_SELECT_INV, GT_SELECT_INVCC))
+ {
+ ins = (op2 == nullptr) ? INS_cinv : INS_csinv;
+ }
+ else if (tree->OperIs(GT_SELECT_NEG, GT_SELECT_NEGCC))
+ {
+ ins = (op2 == nullptr) ? INS_cneg : INS_csneg;
+ }
+ else if (tree->OperIs(GT_SELECT_INC, GT_SELECT_INCCC))
+ {
+ ins = (op2 == nullptr) ? INS_cinc : INS_csinc;
+ }
+
+ if (tree->OperIs(GT_SELECT, GT_SELECT_INV, GT_SELECT_NEG))
{
opcond = tree->AsConditional()->gtCond;
genConsumeRegs(opcond);
}
- emitter* emit = GetEmitter();
-
- GenTree* op1 = tree->gtOp1;
- GenTree* op2 = tree->gtOp2;
- var_types op1Type = genActualType(op1);
- var_types op2Type = genActualType(op2);
- emitAttr attr = emitActualTypeSize(tree);
+ if (op2 != nullptr)
+ {
+ var_types op1Type = genActualType(op1);
+ var_types op2Type = genActualType(op2);
+ assert(genTypeSize(op1Type) == genTypeSize(op2Type));
+ }
assert(!op1->isUsedFromMemory());
- assert(genTypeSize(op1Type) == genTypeSize(op2Type));
+ emitter* emit = GetEmitter();
GenCondition cond;
if (opcond != nullptr)
@@ -4719,92 +4736,62 @@ void CodeGen::genCodeForSelect(GenTreeOp* tree)
}
else
{
- assert(tree->OperIs(GT_SELECTCC));
+ assert(tree->OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC));
cond = tree->AsOpCC()->gtCondition;
}
assert(!op1->isContained() || op1->IsIntegralConst(0));
- assert(!op2->isContained() || op2->IsIntegralConst(0));
+ assert(op2 == nullptr || !op2->isContained() || op2->IsIntegralConst(0));
regNumber targetReg = tree->GetRegNum();
regNumber srcReg1 = op1->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op1);
- regNumber srcReg2 = op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2);
const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond);
+ emitAttr attr = emitActualTypeSize(tree);
+ regNumber srcReg2;
- emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1));
-
- // Some conditions require an additional condition check.
- if (prevDesc.oper == GT_OR)
- {
- emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2));
- }
- else if (prevDesc.oper == GT_AND)
- {
- emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2));
- }
-
- regSet.verifyRegUsed(targetReg);
- genProduceReg(tree);
-}
-
-//------------------------------------------------------------------------
-// genCodeForCinc: Produce code for a GT_CINC/GT_CINCCC node.
-//
-// Arguments:
-// tree - the node
-//
-void CodeGen::genCodeForCinc(GenTreeOp* cinc)
-{
- assert(cinc->OperIs(GT_CINC, GT_CINCCC));
-
- GenTree* opcond = nullptr;
- GenTree* op = cinc->gtOp1;
- if (cinc->OperIs(GT_CINC))
+ if (op2 == nullptr)
{
- opcond = cinc->gtOp1;
- op = cinc->gtOp2;
- genConsumeRegs(opcond);
- }
-
- emitter* emit = GetEmitter();
- var_types opType = genActualType(op->TypeGet());
- emitAttr attr = emitActualTypeSize(cinc->TypeGet());
-
- assert(!op->isUsedFromMemory());
- genConsumeRegs(op);
-
- GenCondition cond;
-
- if (cinc->OperIs(GT_CINC))
- {
- assert(!opcond->isContained());
- // Condition has been generated into a register - move it into flags.
- emit->emitIns_R_I(INS_cmp, emitActualTypeSize(opcond), opcond->GetRegNum(), 0);
- cond = GenCondition::NE;
+ srcReg2 = srcReg1;
+ emit->emitIns_R_R_COND(ins, attr, targetReg, srcReg1, JumpKindToInsCond(prevDesc.jumpKind1));
}
else
{
- assert(cinc->OperIs(GT_CINCCC));
- cond = cinc->AsOpCC()->gtCondition;
+ srcReg2 = (op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2));
+ emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1));
}
- const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond);
- regNumber targetReg = cinc->GetRegNum();
- regNumber srcReg;
- if (op->isContained())
+ // Some floating point comparision conditions require an additional condition check.
+ // These checks are emitted as a subsequent check using GT_AND or GT_OR nodes.
+ // e.g., using GT_OR => `dest = (cond1 || cond2) ? src1 : src2`
+ // GT_AND => `dest = (cond1 && cond2) ? src1 : src2`
+ // The GT_OR case results in emitting the following sequence of two csel instructions.
+ // csel dest, src1, src2, cond1 # emitted previously
+ // csel dest, src1, dest, cond2
+ //
+ if (prevDesc.oper == GT_AND)
{
- assert(op->IsIntegralConst(0));
- srcReg = REG_ZR;
+ // To ensure correctness with invert and negate variants of conditional select, the second instruction needs to
+ // be csinv or csneg respectively.
+ // dest = (cond1 && cond2) ? src1 : ~src2
+ // csinv dest, src1, src2, cond1
+ // csinv dest, dest, src2, cond2
+ //
+ // However, the other variants - increment and select, the second instruction needs to be csel.
+ // dest = (cond1 && cond2) ? src1 : src2++
+ // csinc dest, src1, src2, cond1
+ // csel dest, dest, src1 cond2
+ ins = ((ins == INS_csinv) || (ins == INS_csneg)) ? ins : INS_csel;
+ emit->emitIns_R_R_R_COND(ins, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2));
}
- else
+ else if (prevDesc.oper == GT_OR)
{
- srcReg = op->GetRegNum();
+ // Similarly, the second instruction needs to be csinc while emitting conditional increment.
+ ins = (ins == INS_csinc) ? ins : INS_csel;
+ emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2));
}
- assert(prevDesc.oper != GT_OR && prevDesc.oper != GT_AND);
- emit->emitIns_R_R_COND(INS_cinc, attr, targetReg, srcReg, JumpKindToInsCond(prevDesc.jumpKind1));
regSet.verifyRegUsed(targetReg);
- genProduceReg(cinc);
+ genProduceReg(tree);
}
//------------------------------------------------------------------------
@@ -10365,53 +10352,6 @@ void CodeGen::genCodeForBfiz(GenTreeOp* tree)
genProduceReg(tree);
}
-//------------------------------------------------------------------------
-// genCodeForCond: Generates the code sequence for a GenTree node that
-// represents a conditional instruction.
-//
-// Arguments:
-// tree - conditional op
-//
-void CodeGen::genCodeForCond(GenTreeOp* tree)
-{
- assert(tree->OperIs(GT_CSNEG_MI, GT_CNEG_LT));
- assert(!(tree->gtFlags & GTF_SET_FLAGS));
- genConsumeOperands(tree);
-
- switch (tree->OperGet())
- {
- case GT_CSNEG_MI:
- {
- instruction ins = INS_csneg;
- insCond cond = INS_COND_MI;
-
- regNumber dstReg = tree->GetRegNum();
- regNumber op1Reg = tree->gtGetOp1()->GetRegNum();
- regNumber op2Reg = tree->gtGetOp2()->GetRegNum();
-
- GetEmitter()->emitIns_R_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, op2Reg, cond);
- break;
- }
-
- case GT_CNEG_LT:
- {
- instruction ins = INS_cneg;
- insCond cond = INS_COND_LT;
-
- regNumber dstReg = tree->GetRegNum();
- regNumber op1Reg = tree->gtGetOp1()->GetRegNum();
-
- GetEmitter()->emitIns_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, cond);
- break;
- }
-
- default:
- unreached();
- }
-
- genProduceReg(tree);
-}
-
//------------------------------------------------------------------------
// JumpKindToInsCond: Convert a Jump Kind to a condition.
//
diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp
index 80a62454618972..ce5b8d547503df 100644
--- a/src/coreclr/jit/codegenarmarch.cpp
+++ b/src/coreclr/jit/codegenarmarch.cpp
@@ -315,11 +315,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
case GT_BFIZ:
genCodeForBfiz(treeNode->AsOp());
break;
-
- case GT_CSNEG_MI:
- case GT_CNEG_LT:
- genCodeForCond(treeNode->AsOp());
- break;
#endif // TARGET_ARM64
case GT_JMP:
@@ -355,15 +350,16 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
break;
#ifdef TARGET_ARM64
+ case GT_SELECT_NEG:
+ case GT_SELECT_INV:
+ case GT_SELECT_INC:
case GT_SELECT:
genCodeForSelect(treeNode->AsConditional());
break;
- case GT_CINC:
- case GT_CINCCC:
- genCodeForCinc(treeNode->AsOp());
- break;
-
+ case GT_SELECT_NEGCC:
+ case GT_SELECT_INVCC:
+ case GT_SELECT_INCCC:
case GT_SELECTCC:
genCodeForSelect(treeNode->AsOp());
break;
diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp
index 43a52fd8e8296b..b22bc7c519b0c9 100644
--- a/src/coreclr/jit/compiler.hpp
+++ b/src/coreclr/jit/compiler.hpp
@@ -4290,11 +4290,7 @@ void GenTree::VisitOperands(TVisitor visitor)
}
FALLTHROUGH;
-// Standard unary operators
-#ifdef TARGET_ARM64
- case GT_CNEG_LT:
- case GT_CINCCC:
-#endif // TARGET_ARM64
+ // Standard unary operators
case GT_STORE_LCL_VAR:
case GT_STORE_LCL_FLD:
case GT_NOT:
diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp
index 8503a609e0c8bc..307909f257f339 100644
--- a/src/coreclr/jit/gentree.cpp
+++ b/src/coreclr/jit/gentree.cpp
@@ -6361,11 +6361,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse)
case GT_IL_OFFSET:
return false;
-// Standard unary operators
-#ifdef TARGET_ARM64
- case GT_CNEG_LT:
- case GT_CINCCC:
-#endif // TARGET_ARM64
+ // Standard unary operators
case GT_STORE_LCL_VAR:
case GT_STORE_LCL_FLD:
case GT_NOT:
@@ -6554,7 +6550,11 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse)
}
return false;
}
-
+#ifdef TARGET_ARM64
+ case GT_SELECT_NEG:
+ case GT_SELECT_INV:
+ case GT_SELECT_INC:
+#endif
case GT_SELECT:
{
GenTreeConditional* const conditional = this->AsConditional();
@@ -9713,11 +9713,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node)
m_state = -1;
return;
-// Standard unary operators
-#ifdef TARGET_ARM64
- case GT_CNEG_LT:
- case GT_CINCCC:
-#endif // TARGET_ARM64
+ // Standard unary operators
case GT_STORE_LCL_VAR:
case GT_STORE_LCL_FLD:
case GT_NOT:
@@ -9829,7 +9825,11 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node)
m_advance = &GenTreeUseEdgeIterator::AdvanceCall;
AdvanceCall();
return;
-
+#ifdef TARGET_ARM64
+ case GT_SELECT_NEG:
+ case GT_SELECT_INV:
+ case GT_SELECT_INC:
+#endif
case GT_SELECT:
m_edge = &m_node->AsConditional()->gtCond;
assert(*m_edge != nullptr);
@@ -9955,8 +9955,15 @@ void GenTreeUseEdgeIterator::AdvanceConditional()
switch (m_state)
{
case 0:
- m_edge = &conditional->gtOp1;
- m_state = 1;
+ m_edge = &conditional->gtOp1;
+ if (conditional->gtOp2 == nullptr)
+ {
+ m_advance = &GenTreeUseEdgeIterator::Terminate;
+ }
+ else
+ {
+ m_state = 1;
+ }
break;
case 1:
m_edge = &conditional->gtOp2;
@@ -12229,7 +12236,7 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" cond=%s", tree->AsOpCC()->gtCondition.Name());
}
#ifdef TARGET_ARM64
- else if (tree->OperIs(GT_CINCCC))
+ else if (tree->OperIs(GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC))
{
printf(" cond=%s", tree->AsOpCC()->gtCondition.Name());
}
diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h
index 9e5e13a1f02fc4..1a60f5fad0da14 100644
--- a/src/coreclr/jit/gentree.h
+++ b/src/coreclr/jit/gentree.h
@@ -1679,7 +1679,7 @@ struct GenTree
}
#endif
#if defined(TARGET_ARM64)
- if (OperIs(GT_CCMP, GT_CINCCC))
+ if (OperIs(GT_CCMP, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC))
{
return true;
}
@@ -1733,6 +1733,10 @@ struct GenTree
#if defined(TARGET_ARM)
case GT_PUTARG_REG:
#endif // defined(TARGET_ARM)
+#if defined(TARGET_ARM64)
+ case GT_SELECT_NEGCC:
+ case GT_SELECT_INCCC:
+#endif // defined(TARGET_ARM64)
return true;
default:
@@ -8637,7 +8641,11 @@ struct GenTreeOpCC : public GenTreeOp
GenTreeOpCC(genTreeOps oper, var_types type, GenCondition condition, GenTree* op1 = nullptr, GenTree* op2 = nullptr)
: GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ FALSE)), gtCondition(condition)
{
+#ifdef TARGET_ARM64
+ assert(OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC));
+#else
assert(OperIs(GT_SELECTCC));
+#endif
}
#if DEBUGGABLE_GENTREE
diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h
index 972fd9cd194186..33925ae801a87f 100644
--- a/src/coreclr/jit/gtlist.h
+++ b/src/coreclr/jit/gtlist.h
@@ -215,8 +215,6 @@ GTNODE(AND_NOT , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
#ifdef TARGET_ARM64
GTNODE(BFIZ , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero.
-GTNODE(CSNEG_MI , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Conditional select, negate, minus result
-GTNODE(CNEG_LT , GenTreeOp ,0,GTK_UNOP|DBK_NOTHIR) // Conditional, negate, signed less than result
#endif
//-----------------------------------------------------------------------------
@@ -246,11 +244,21 @@ GTNODE(SELECTCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
// operands and sets the condition flags according to the result. Otherwise
// sets the condition flags to the specified immediate value.
GTNODE(CCMP , GenTreeCCMP ,0,GTK_BINOP|GTK_NOVALUE|DBK_NOTHIR)
-// Maps to arm64 cinc instruction. It returns the operand incremented by one when the condition is true.
-// Otherwise returns the unchanged operand. Optimises for patterns such as, result = condition ? op1 + 1 : op1
-GTNODE(CINC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
-// Variant of CINC that reuses flags computed by a previous node with the specified condition.
-GTNODE(CINCCC , GenTreeOpCC ,0,GTK_UNOP|DBK_NOTHIR)
+// Maps to arm64 csinc/cinc instruction. Computes result = condition ? op1 : op2 + 1.
+// If op2 is null, computes result = condition ? op1 + 1 : op1.
+GTNODE(SELECT_INC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
+// Variant of SELECT_INC that reuses flags computed by a previous node with the specified condition.
+GTNODE(SELECT_INCCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
+// Maps to arm64 csinv/cinv instruction. Computes result = condition ? op1 : ~op2.
+// If op2 is null, computes result = condition ? ~op1 : op1.
+GTNODE(SELECT_INV , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
+// Variant of SELECT_INV that reuses flags computed by a previous node with the specified condition.
+GTNODE(SELECT_INVCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
+// Maps to arm64 csneg/cneg instruction.. Computes result = condition ? op1 : -op2.
+// If op2 is null, computes result = condition ? -op1 : op1.
+GTNODE(SELECT_NEG , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
+// Variant of SELECT_NEG that reuses flags computed by a previous node with the specified condition.
+GTNODE(SELECT_NEGCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
#endif
//-----------------------------------------------------------------------------
diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h
index b4d68694d9daee..317e0b3f071b77 100644
--- a/src/coreclr/jit/gtstructs.h
+++ b/src/coreclr/jit/gtstructs.h
@@ -94,7 +94,11 @@ GTSTRUCT_1(PhiArg , GT_PHI_ARG)
GTSTRUCT_1(Phi , GT_PHI)
GTSTRUCT_1(StoreInd , GT_STOREIND)
GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_STORE_DYN_BLK)
+#ifdef TARGET_ARM64
+GTSTRUCT_N(Conditional , GT_SELECT, GT_SELECT_INC, GT_SELECT_INV, GT_SELECT_NEG)
+#else
GTSTRUCT_N(Conditional , GT_SELECT)
+#endif //TARGET_ARM64
#if FEATURE_ARG_SPLIT
GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT)
GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT)
@@ -111,7 +115,7 @@ GTSTRUCT_1(ArrAddr , GT_ARR_ADDR)
GTSTRUCT_2(CC , GT_JCC, GT_SETCC)
#ifdef TARGET_ARM64
GTSTRUCT_1(CCMP , GT_CCMP)
-GTSTRUCT_4(OpCC , GT_SELECTCC, GT_CINCCC, GT_JCMP, GT_JTEST)
+GTSTRUCT_N(OpCC , GT_SELECTCC, GT_SELECT_INCCC, GT_JCMP, GT_JTEST, GT_SELECT_INVCC, GT_SELECT_NEGCC)
#else
GTSTRUCT_3(OpCC , GT_SELECTCC, GT_JCMP, GT_JTEST)
#endif
diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp
index 0af9216dc46618..536b4382d1450a 100644
--- a/src/coreclr/jit/lower.cpp
+++ b/src/coreclr/jit/lower.cpp
@@ -3851,11 +3851,16 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select)
}
#ifdef TARGET_ARM64
- if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI())
+ if (trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG))
+ {
+ TryLowerCselToCinvOrCneg(select, cond);
+ }
+ else if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI())
{
TryLowerCselToCinc(select, cond);
}
#endif
+
return newSelect != nullptr ? newSelect->gtNext : select->gtNext;
}
diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h
index 63f59208427fa5..aabb28592803be 100644
--- a/src/coreclr/jit/lower.h
+++ b/src/coreclr/jit/lower.h
@@ -89,6 +89,7 @@ class Lowering final : public Phase
void ContainCheckConditionalCompare(GenTreeCCMP* ccmp);
void ContainCheckNeg(GenTreeOp* neg);
void TryLowerCselToCinc(GenTreeOp* select, GenTree* cond);
+ void TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond);
#endif
void ContainCheckSelect(GenTreeOp* select);
void ContainCheckBitCast(GenTree* node);
diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp
index 83f7be1b91a016..64826c428b77fa 100644
--- a/src/coreclr/jit/lowerarmarch.cpp
+++ b/src/coreclr/jit/lowerarmarch.cpp
@@ -874,8 +874,11 @@ void Lowering::LowerModPow2(GenTree* node)
BlockRange().InsertAfter(cnsZero, cmp);
LowerNode(cmp);
- mod->ChangeOper(GT_CNEG_LT);
- mod->gtOp1 = trueExpr;
+ mod->ChangeOper(GT_SELECT_NEGCC);
+ GenTreeOpCC* node = mod->AsOpCC();
+ node->gtOp1 = trueExpr;
+ node->gtOp2 = nullptr;
+ node->gtCondition = GenCondition::SLT;
}
else
{
@@ -900,9 +903,11 @@ void Lowering::LowerModPow2(GenTree* node)
BlockRange().InsertAfter(cns2, falseExpr);
LowerNode(falseExpr);
- mod->ChangeOper(GT_CSNEG_MI);
- mod->gtOp1 = trueExpr;
- mod->gtOp2 = falseExpr;
+ mod->SetOper(GT_SELECT_NEGCC);
+ GenTreeOpCC* node = mod->AsOpCC();
+ node->gtOp1 = trueExpr;
+ node->gtOp2 = falseExpr;
+ node->gtCondition = GenCondition::S;
}
ContainCheckNode(mod);
@@ -2552,8 +2557,93 @@ void Lowering::ContainCheckNeg(GenTreeOp* neg)
}
//----------------------------------------------------------------------------------------------
-// Try converting SELECT/SELECTCC to CINC/CINCCC. Conversion is possible only if
-// both the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1.
+// TryLowerCselToCinvOrCneg: Try converting SELECT/SELECTCC to SELECT_INV/SELECT_INVCC. Conversion is possible only if
+// one of the operands of the select node is inverted.
+//
+// Arguments:
+// select - The select node that is now SELECT or SELECTCC
+// cond - The condition node that SELECT or SELECTCC uses
+//
+void Lowering::TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond)
+{
+ assert(select->OperIs(GT_SELECT, GT_SELECTCC));
+
+ bool shouldReverseCondition;
+ GenTree* invertedOrNegatedVal;
+ GenTree* nonInvertedOrNegatedVal;
+ GenTree* nodeToRemove;
+
+ GenTree* trueVal = select->gtOp1;
+ GenTree* falseVal = select->gtOp2;
+ const bool isCneg = trueVal->OperIs(GT_NEG) || falseVal->OperIs(GT_NEG);
+
+ assert(trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG));
+
+ if (trueVal->OperIs(GT_NOT) || trueVal->OperIs(GT_NEG))
+ {
+ shouldReverseCondition = true;
+ invertedOrNegatedVal = trueVal->gtGetOp1();
+ nonInvertedOrNegatedVal = falseVal;
+ nodeToRemove = trueVal;
+ }
+ else
+ {
+ shouldReverseCondition = false;
+ invertedOrNegatedVal = falseVal->gtGetOp1();
+ nonInvertedOrNegatedVal = trueVal;
+ nodeToRemove = falseVal;
+ }
+
+ if (shouldReverseCondition && !cond->OperIsCompare() && select->OperIs(GT_SELECT))
+ {
+ // Non-compare nodes add additional GT_NOT node after reversing.
+ // This would remove gains from this optimisation so don't proceed.
+ return;
+ }
+
+ if (!(IsInvariantInRange(invertedOrNegatedVal, select) && IsInvariantInRange(nonInvertedOrNegatedVal, select)))
+ {
+ return;
+ }
+
+ // As the select node would handle the negation/inversion, the op is not required.
+ // If a value is contained in the negate/invert op, it cannot be contained anymore.
+ BlockRange().Remove(nodeToRemove);
+ invertedOrNegatedVal->ClearContained();
+ select->gtOp1 = nonInvertedOrNegatedVal;
+ select->gtOp2 = invertedOrNegatedVal;
+
+ if (select->OperIs(GT_SELECT))
+ {
+ if (shouldReverseCondition)
+ {
+ GenTree* revCond = comp->gtReverseCond(cond);
+ assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node.
+ }
+ select->SetOper(isCneg ? GT_SELECT_NEG : GT_SELECT_INV);
+ JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEG" : "SELECT_INV");
+ DISPTREERANGE(BlockRange(), select);
+ JITDUMP("\n");
+ }
+ else
+ {
+ GenTreeOpCC* selectcc = select->AsOpCC();
+ GenCondition selectCond = selectcc->gtCondition;
+ if (shouldReverseCondition)
+ {
+ // Reverse the condition so that op2 will be selected
+ selectcc->gtCondition = GenCondition::Reverse(selectCond);
+ }
+ selectcc->SetOper(isCneg ? GT_SELECT_NEGCC : GT_SELECT_INVCC);
+ JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEGCC" : "SELECT_INVCC");
+ DISPTREERANGE(BlockRange(), selectcc);
+ JITDUMP("\n");
+ }
+}
+
+//----------------------------------------------------------------------------------------------
+// TryLowerCselToCinc: Try converting SELECT/SELECTCC to SELECT_INC/SELECT_INCCC. Conversion is possible only if both
+// the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1.
//
// Arguments:
// select - The select node that is now SELECT or SELECTCC
@@ -2568,11 +2658,10 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
size_t op1Val = (size_t)trueVal->AsIntCon()->IconValue();
size_t op2Val = (size_t)falseVal->AsIntCon()->IconValue();
- if (op1Val + 1 == op2Val || op2Val + 1 == op1Val)
+ if ((op1Val + 1 == op2Val) || (op2Val + 1 == op1Val))
{
- const bool shouldReverseCondition = op1Val + 1 == op2Val;
+ const bool shouldReverseCondition = (op1Val + 1 == op2Val);
- // Create a cinc node, insert it and update the use.
if (select->OperIs(GT_SELECT))
{
if (shouldReverseCondition)
@@ -2584,18 +2673,21 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
// This would remove gains from this optimisation so don't proceed.
return;
}
- select->gtOp2 = select->gtOp1;
GenTree* revCond = comp->gtReverseCond(cond);
assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node.
}
- select->gtOp1 = cond->AsOp();
- select->SetOper(GT_CINC);
+ BlockRange().Remove(select->gtOp2, true);
+ select->gtOp2 = nullptr;
+ select->SetOper(GT_SELECT_INC);
+ JITDUMP("Converted to: GT_SELECT_INC\n");
DISPTREERANGE(BlockRange(), select);
+ JITDUMP("\n");
}
else
{
GenTreeOpCC* selectcc = select->AsOpCC();
GenCondition selectCond = selectcc->gtCondition;
+
if (shouldReverseCondition)
{
// Reverse the condition so that op2 will be selected
@@ -2605,9 +2697,13 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
{
std::swap(selectcc->gtOp1, selectcc->gtOp2);
}
- BlockRange().Remove(selectcc->gtOp2);
- selectcc->SetOper(GT_CINCCC);
+
+ BlockRange().Remove(selectcc->gtOp2, true);
+ selectcc->gtOp2 = nullptr;
+ selectcc->SetOper(GT_SELECT_INCCC);
+ JITDUMP("Converted to: GT_SELECT_INCCC\n");
DISPTREERANGE(BlockRange(), selectcc);
+ JITDUMP("\n");
}
}
}
diff --git a/src/coreclr/jit/promotion.cpp b/src/coreclr/jit/promotion.cpp
index 9452210abcd049..1bf1487d83ef10 100644
--- a/src/coreclr/jit/promotion.cpp
+++ b/src/coreclr/jit/promotion.cpp
@@ -62,6 +62,7 @@ struct Access
weight_t CountWtd = 0;
weight_t CountAssignmentSourceWtd = 0;
weight_t CountAssignmentDestinationWtd = 0;
+ weight_t CountAssignedFromCallWtd = 0;
weight_t CountCallArgsWtd = 0;
weight_t CountReturnsWtd = 0;
weight_t CountPassedAsRetbufWtd = 0;
@@ -101,7 +102,8 @@ enum class AccessKindFlags : uint32_t
IsAssignmentSource = 2,
IsAssignmentDestination = 4,
IsCallRetBuf = 8,
- IsReturned = 16,
+ IsAssignedFromCall = 16,
+ IsReturned = 32,
};
inline constexpr AccessKindFlags operator~(AccessKindFlags a)
@@ -263,6 +265,11 @@ class LocalUses
{
access->CountAssignmentDestination++;
access->CountAssignmentDestinationWtd += weight;
+
+ if ((flags & AccessKindFlags::IsAssignedFromCall) != AccessKindFlags::None)
+ {
+ access->CountAssignedFromCallWtd += weight;
+ }
}
if ((flags & AccessKindFlags::IsCallArg) != AccessKindFlags::None)
@@ -356,11 +363,11 @@ class LocalUses
//
bool EvaluateReplacement(Compiler* comp, unsigned lclNum, const Access& access)
{
- weight_t countOverlappedCallsWtd = 0;
- weight_t countOverlappedReturnsWtd = 0;
- weight_t countOverlappedRetbufsWtd = 0;
- weight_t countOverlappedAssignmentDestinationWtd = 0;
- weight_t countOverlappedAssignmentSourceWtd = 0;
+ weight_t countOverlappedCallArgWtd = 0;
+ weight_t countOverlappedReturnsWtd = 0;
+ weight_t countOverlappedRetbufsWtd = 0;
+ weight_t countOverlappedAssignedFromCallWtd = 0;
+ weight_t countOverlappedDecomposableAssignmentWtd = 0;
bool overlap = false;
for (const Access& otherAccess : m_accesses)
@@ -378,52 +385,78 @@ class LocalUses
return false;
}
- countOverlappedCallsWtd += otherAccess.CountCallArgsWtd;
+ countOverlappedCallArgWtd += otherAccess.CountCallArgsWtd;
countOverlappedReturnsWtd += otherAccess.CountReturnsWtd;
countOverlappedRetbufsWtd += otherAccess.CountPassedAsRetbufWtd;
- countOverlappedAssignmentDestinationWtd += otherAccess.CountAssignmentDestinationWtd;
- countOverlappedAssignmentSourceWtd += otherAccess.CountAssignmentSourceWtd;
+ countOverlappedAssignedFromCallWtd += otherAccess.CountAssignedFromCallWtd;
+ countOverlappedDecomposableAssignmentWtd +=
+ (otherAccess.CountAssignmentDestinationWtd + otherAccess.CountAssignmentSourceWtd -
+ otherAccess.CountAssignedFromCallWtd);
}
- // TODO-CQ: Tune the following heuristics. Currently they are based on
- // x64 code size although using BB weights when available. This mixing
- // does not make sense.
weight_t costWithout = 0;
- // A normal access without promotion looks like:
- // mov reg, [reg+offs]
- // It may also be contained. Overall we are going to cost each use of
- // an unpromoted local at 6.5 bytes.
- // TODO-CQ: We can make much better guesses on what will and won't be contained.
- costWithout += access.CountWtd * 6.5;
+ // We cost any normal access (which is a struct load or store) without promotion at 3 cycles.
+ costWithout += access.CountWtd * 3;
weight_t costWith = 0;
- // For any use we expect to just use the register directly. We will cost this at 3.5 bytes.
- costWith += access.CountWtd * 3.5;
+ // For promoted accesses we expect these to turn into reg-reg movs (and in many cases be fully contained in the
+ // parent).
+ // We cost these at 0.5 cycles.
+ costWith += access.CountWtd * 0.5;
+
+ // Now look at the overlapping struct uses that promotion will make more expensive.
weight_t countReadBacksWtd = 0;
LclVarDsc* lcl = comp->lvaGetDesc(lclNum);
- // For parameters or OSR locals we need an initial read back
+ // For parameters or OSR locals we always need one read back.
if (lcl->lvIsParam || lcl->lvIsOSRLocal)
{
countReadBacksWtd += comp->fgFirstBB->getBBWeight(comp);
}
+ // If used as a retbuf we need a readback after.
countReadBacksWtd += countOverlappedRetbufsWtd;
- countReadBacksWtd += countOverlappedAssignmentDestinationWtd;
- // A read back puts the value from stack back to (hopefully) register. We cost it at 5 bytes.
- costWith += countReadBacksWtd * 5;
+ // The same if the struct was assigned from a call, since we don't
+ // currently have any "forwarding" optimization for this case.
+ countReadBacksWtd += countOverlappedAssignedFromCallWtd;
+
+ // A readback turns into a stack load that we costed at 3 above.
+ costWith += countReadBacksWtd * 3;
// Write backs with TYP_REFs when the base local is an implicit byref
- // involves checked write barriers, so they are very expensive.
+ // involves checked write barriers, so they are very expensive. We cost that at 10 cycles.
// TODO-CQ: This should be adjusted once we type implicit byrefs as TYP_I_IMPL.
- weight_t writeBackCost = comp->lvaIsImplicitByRefLocal(lclNum) && (access.AccessType == TYP_REF) ? 15 : 5;
- weight_t countWriteBacksWtd =
- countOverlappedCallsWtd + countOverlappedReturnsWtd + countOverlappedAssignmentSourceWtd;
+ // Otherwise we cost it like a store to stack at 3 cycles.
+ weight_t writeBackCost = comp->lvaIsImplicitByRefLocal(lclNum) && (access.AccessType == TYP_REF) ? 10 : 3;
+
+ // We write back before an overlapping struct use passed as an arg.
+ // TODO-CQ: A store-forwarding optimization in lowering could get rid
+ // of these copies; however, it requires lowering to be able to prove
+ // that not writing the fields into the struct local is ok.
+ weight_t countWriteBacksWtd = countOverlappedCallArgWtd;
costWith += countWriteBacksWtd * writeBackCost;
+ // Overlapping assignments are decomposable so we don't cost them as
+ // being more expensive than their unpromoted counterparts (i.e. we
+ // don't consider them at all). However, we should do something more
+ // clever here, since:
+ // * We may still end up writing the full remainder as part of the
+ // decomposed assignment, in which case all the field writes are just
+ // added code size/perf cost.
+ // * Even if we don't, decomposing a single struct write into many
+ // field writes is not necessarily profitable (e.g. 16 byte field
+ // stores vs 1 XMM load/store).
+ //
+ // TODO-CQ: This ends up being a combinatorial optimization problem. We
+ // need to take a more "whole-struct" view here and look at sets of
+ // fields we are promoting together, evaluating all of them at once in
+ // comparison with the covering struct uses. This will also allow us to
+ // give a bonus to promoting remainders that may not have scalar uses
+ // but will allow fully decomposing assignments away.
+
JITDUMP(" Evaluating access %s @ %03u\n", varTypeName(access.AccessType), access.Offset);
JITDUMP(" Single write-back cost: " FMT_WT "\n", writeBackCost);
JITDUMP(" Write backs: " FMT_WT "\n", countWriteBacksWtd);
@@ -611,6 +644,11 @@ class LocalsUseVisitor : public GenTreeVisitor
if (lcl->OperIsLocalStore())
{
flags |= AccessKindFlags::IsAssignmentDestination;
+
+ if (lcl->AsLclVarCommon()->Data()->gtEffectiveVal()->IsCall())
+ {
+ flags |= AccessKindFlags::IsAssignedFromCall;
+ }
}
if (user == nullptr)
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
index 7cbcf4b6833dc5..2e124627d0a182 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
@@ -48,7 +48,7 @@ The .NET Foundation licenses this file to you under the MIT license.
@executable_path
libeventpipe-disabled
- libeventpipe-enabled
+ libeventpipe-enabled
diff --git a/src/coreclr/nativeaot/Directory.Build.props b/src/coreclr/nativeaot/Directory.Build.props
index 7fdf7430ac799d..76c5cb9eecbb72 100644
--- a/src/coreclr/nativeaot/Directory.Build.props
+++ b/src/coreclr/nativeaot/Directory.Build.props
@@ -66,7 +66,7 @@
false
- true
+ true
FEATURE_PERFTRACING;$(DefineConstants)
diff --git a/src/coreclr/nativeaot/Runtime/PalRedhawk.h b/src/coreclr/nativeaot/Runtime/PalRedhawk.h
index 81d08260015d95..a718812f23ddce 100644
--- a/src/coreclr/nativeaot/Runtime/PalRedhawk.h
+++ b/src/coreclr/nativeaot/Runtime/PalRedhawk.h
@@ -17,8 +17,9 @@
#include
#include
-#include "gcenv.structs.h"
+#include "gcenv.structs.h" // CRITICAL_SECTION
#include "IntrinsicConstants.h"
+#include "PalRedhawkCommon.h"
#ifndef PAL_REDHAWK_INCLUDED
#define PAL_REDHAWK_INCLUDED
@@ -50,7 +51,6 @@
#endif // !_MSC_VER
#ifndef _INC_WINDOWS
-//#ifndef DACCESS_COMPILE
// There are some fairly primitive type definitions below but don't pull them into the rest of Redhawk unless
// we have to (in which case these definitions will move to CommonTypes.h).
@@ -65,14 +65,13 @@ typedef void * LPOVERLAPPED;
#ifdef TARGET_UNIX
#define __stdcall
+typedef char TCHAR;
+#define _T(s) s
+#else
+typedef wchar_t TCHAR;
+#define _T(s) L##s
#endif
-#ifndef __GCENV_BASE_INCLUDED__
-#define CALLBACK __stdcall
-#define WINAPI __stdcall
-#define WINBASEAPI __declspec(dllimport)
-#endif //!__GCENV_BASE_INCLUDED__
-
#ifdef TARGET_UNIX
#define DIRECTORY_SEPARATOR_CHAR '/'
#else // TARGET_UNIX
@@ -101,9 +100,6 @@ typedef struct _GUID {
#define DECLARE_HANDLE(_name) typedef HANDLE _name
-// defined in gcrhenv.cpp
-bool __SwitchToThread(uint32_t dwSleepMSec, uint32_t dwSwitchCount);
-
struct FILETIME
{
uint32_t dwLowDateTime;
@@ -502,7 +498,6 @@ typedef enum _EXCEPTION_DISPOSITION {
#define NULL_AREA_SIZE (64*1024)
#endif
-//#endif // !DACCESS_COMPILE
#endif // !_INC_WINDOWS
@@ -510,10 +505,12 @@ typedef enum _EXCEPTION_DISPOSITION {
#ifndef DACCESS_COMPILE
#ifndef _INC_WINDOWS
-#ifndef __GCENV_BASE_INCLUDED__
+#ifndef TRUE
#define TRUE 1
+#endif
+#ifndef FALSE
#define FALSE 0
-#endif // !__GCENV_BASE_INCLUDED__
+#endif
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
@@ -750,6 +747,9 @@ REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalH
#ifdef FEATURE_ETW
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalEventEnabled(REGHANDLE regHandle, _In_ const EVENT_DESCRIPTOR* eventDescriptor);
+REDHAWK_PALIMPORT uint32_t REDHAWK_PALAPI PalEventRegister(const GUID * arg1, void * arg2, void * arg3, REGHANDLE * arg4);
+REDHAWK_PALIMPORT uint32_t REDHAWK_PALAPI PalEventUnregister(REGHANDLE arg1);
+REDHAWK_PALIMPORT uint32_t REDHAWK_PALAPI PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t arg3, EVENT_DATA_DESCRIPTOR * arg4);
#endif
REDHAWK_PALIMPORT _Ret_maybenull_ void* REDHAWK_PALAPI PalSetWerDataBuffer(_In_ void* pNewBuffer);
@@ -769,13 +769,15 @@ REDHAWK_PALIMPORT uint32_t REDHAWK_PALAPI PalCompatibleWaitAny(UInt32_BOOL alert
REDHAWK_PALIMPORT void REDHAWK_PALAPI PalAttachThread(void* thread);
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalDetachThread(void* thread);
-REDHAWK_PALIMPORT uint64_t PalGetCurrentThreadIdForLogging();
+REDHAWK_PALIMPORT uint64_t PalGetCurrentOSThreadId();
REDHAWK_PALIMPORT uint64_t PalQueryPerformanceCounter();
REDHAWK_PALIMPORT uint64_t PalQueryPerformanceFrequency();
REDHAWK_PALIMPORT void PalPrintFatalError(const char* message);
+REDHAWK_PALIMPORT char* PalCopyTCharAsChar(const TCHAR* toCopy);
+
#ifdef TARGET_UNIX
REDHAWK_PALIMPORT int32_t __cdecl _stricmp(const char *string1, const char *string2);
#endif // TARGET_UNIX
diff --git a/src/coreclr/nativeaot/Runtime/PalRedhawkFunctions.h b/src/coreclr/nativeaot/Runtime/PalRedhawkFunctions.h
index b03fc004c295ea..4c176593b9ff05 100644
--- a/src/coreclr/nativeaot/Runtime/PalRedhawkFunctions.h
+++ b/src/coreclr/nativeaot/Runtime/PalRedhawkFunctions.h
@@ -37,24 +37,6 @@ inline void PalEnterCriticalSection(CRITICAL_SECTION * arg1)
EnterCriticalSection(arg1);
}
-extern "C" uint32_t __stdcall EventRegister(const GUID *, void *, void *, REGHANDLE *);
-inline uint32_t PalEventRegister(const GUID * arg1, void * arg2, void * arg3, REGHANDLE * arg4)
-{
- return EventRegister(arg1, arg2, arg3, arg4);
-}
-
-extern "C" uint32_t __stdcall EventUnregister(REGHANDLE);
-inline uint32_t PalEventUnregister(REGHANDLE arg1)
-{
- return EventUnregister(arg1);
-}
-
-extern "C" uint32_t __stdcall EventWrite(REGHANDLE, const EVENT_DESCRIPTOR *, uint32_t, EVENT_DATA_DESCRIPTOR *);
-inline uint32_t PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t arg3, EVENT_DATA_DESCRIPTOR * arg4)
-{
- return EventWrite(arg1, arg2, arg3, arg4);
-}
-
extern "C" void __stdcall FlushProcessWriteBuffers();
inline void PalFlushProcessWriteBuffers()
{
diff --git a/src/coreclr/nativeaot/Runtime/RhConfig.cpp b/src/coreclr/nativeaot/Runtime/RhConfig.cpp
index a321b37cff259a..928778b09f4650 100644
--- a/src/coreclr/nativeaot/Runtime/RhConfig.cpp
+++ b/src/coreclr/nativeaot/Runtime/RhConfig.cpp
@@ -11,60 +11,120 @@
#include
-bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool decimal)
+#define DOTNET_PREFIX _T("DOTNET_")
+#define DOTNET_PREFIX_LEN STRING_LENGTH(DOTNET_PREFIX)
+
+namespace
+{
+ void GetEnvironmentConfigName(const char* name, TCHAR* buffer, uint32_t bufferSize)
+ {
+ assert(DOTNET_PREFIX_LEN + strlen(name) < bufferSize);
+ memcpy(buffer, DOTNET_PREFIX, (DOTNET_PREFIX_LEN) * sizeof(TCHAR));
+ #ifdef TARGET_WINDOWS
+ size_t nameLen = strlen(name);
+ for (size_t i = 0; i < nameLen; i++)
+ {
+ buffer[DOTNET_PREFIX_LEN + i] = name[i];
+ }
+ buffer[DOTNET_PREFIX_LEN + nameLen] = '\0';
+ #else
+ strcpy(buffer + DOTNET_PREFIX_LEN, name);
+ #endif
+ }
+}
+
+bool RhConfig::Environment::TryGetBooleanValue(const char* name, bool* value)
+{
+ uint64_t intValue;
+ if (!TryGetIntegerValue(name, &intValue))
+ return false;
+
+ *value = intValue != 0;
+ return true;
+}
+
+bool RhConfig::Environment::TryGetIntegerValue(const char* name, uint64_t* value, bool decimal)
{
+ TCHAR variableName[64];
+ GetEnvironmentConfigName(name, variableName, ARRAY_SIZE(variableName));
+
TCHAR buffer[CONFIG_VAL_MAXLEN + 1]; // hex digits plus a nul terminator.
const uint32_t cchBuffer = ARRAY_SIZE(buffer);
+ uint32_t cchResult = PalGetEnvironmentVariable(variableName, buffer, cchBuffer);
+ if (cchResult == 0 || cchResult >= cchBuffer)
+ return false;
- uint32_t cchResult = 0;
- TCHAR variableName[64] = _T("DOTNET_");
- assert(ARRAY_SIZE("DOTNET_") - 1 + strlen(name) < ARRAY_SIZE(variableName));
-#ifdef TARGET_WINDOWS
- for (size_t i = 0; i < strlen(name); i++)
+ // Environment variable was set. Convert it to an integer.
+ uint64_t uiResult = 0;
+ for (uint32_t i = 0; i < cchResult; i++)
{
- variableName[ARRAY_SIZE("DOTNET_") - 1 + i] = name[i];
- }
-#else
- strcat(variableName, name);
-#endif
+ TCHAR ch = buffer[i];
- cchResult = PalGetEnvironmentVariable(variableName, buffer, cchBuffer);
- if (cchResult != 0 && cchResult < cchBuffer)
- {
- // Environment variable was set. Convert it to an integer.
- uint64_t uiResult = 0;
- for (uint32_t i = 0; i < cchResult; i++)
+ if (decimal)
{
- TCHAR ch = buffer[i];
+ uiResult *= 10;
- if (decimal)
- {
- uiResult *= 10;
-
- if ((ch >= '0') && (ch <= '9'))
- uiResult += ch - '0';
- else
- return false; // parse error
- }
+ if ((ch >= '0') && (ch <= '9'))
+ uiResult += ch - '0';
else
- {
- uiResult *= 16;
-
- if ((ch >= '0') && (ch <= '9'))
- uiResult += ch - '0';
- else if ((ch >= 'a') && (ch <= 'f'))
- uiResult += (ch - 'a') + 10;
- else if ((ch >= 'A') && (ch <= 'F'))
- uiResult += (ch - 'A') + 10;
- else
- return false; // parse error
- }
+ return false; // parse error
+ }
+ else
+ {
+ uiResult *= 16;
+
+ if ((ch >= '0') && (ch <= '9'))
+ uiResult += ch - '0';
+ else if ((ch >= 'a') && (ch <= 'f'))
+ uiResult += (ch - 'a') + 10;
+ else if ((ch >= 'A') && (ch <= 'F'))
+ uiResult += (ch - 'A') + 10;
+ else
+ return false; // parse error
}
+ }
+
+ *value = uiResult;
+ return true;
+}
+
+bool RhConfig::Environment::TryGetStringValue(const char* name, char** value)
+{
+ TCHAR variableName[64];
+ GetEnvironmentConfigName(name, variableName, ARRAY_SIZE(variableName));
+
+ TCHAR buffer[260];
+ uint32_t bufferLen = ARRAY_SIZE(buffer);
+ uint32_t actualLen = PalGetEnvironmentVariable(variableName, buffer, bufferLen);
+ if (actualLen == 0)
+ return false;
- *pValue = uiResult;
+ if (actualLen < bufferLen)
+ {
+ *value = PalCopyTCharAsChar(buffer);
return true;
}
+ // Expand the buffer to get the value
+ bufferLen = actualLen + 1;
+ NewArrayHolder newBuffer {new (nothrow) TCHAR[bufferLen]};
+ actualLen = PalGetEnvironmentVariable(variableName, newBuffer, bufferLen);
+ if (actualLen >= bufferLen)
+ return false;
+
+#ifdef TARGET_WINDOWS
+ *value = PalCopyTCharAsChar(newBuffer);
+#else
+ *value = newBuffer.Extract();
+#endif
+ return true;
+}
+
+bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool decimal)
+{
+ if (Environment::TryGetIntegerValue(name, pValue, decimal))
+ return true;
+
// Check the embedded configuration
const char *embeddedValue = nullptr;
if (GetEmbeddedVariable(name, &embeddedValue))
diff --git a/src/coreclr/nativeaot/Runtime/RhConfig.h b/src/coreclr/nativeaot/Runtime/RhConfig.h
index 86e8c38a52bcfd..214a79c5c93b24 100644
--- a/src/coreclr/nativeaot/Runtime/RhConfig.h
+++ b/src/coreclr/nativeaot/Runtime/RhConfig.h
@@ -14,13 +14,14 @@
#ifndef DACCESS_COMPILE
+#include
+
class RhConfig
{
#define CONFIG_INI_NOT_AVAIL (void*)0x1 //signal for ini file failed to load
#define CONFIG_KEY_MAXLEN 50 //arbitrary max length of config keys increase if needed
#define CONFIG_VAL_MAXLEN 16 //64 bit uint in hex
-
private:
struct ConfigPair
{
@@ -36,6 +37,15 @@ class RhConfig
void* volatile g_embeddedSettings = NULL;
public:
+ class Environment
+ {
+ public: // static
+ static bool TryGetBooleanValue(const char* name, bool* value);
+ static bool TryGetIntegerValue(const char* name, uint64_t* value, bool decimal = false);
+
+ // Get environment variable configuration as a string. On success, the caller owns the returned string value.
+ static bool TryGetStringValue(const char* name, char** value);
+ };
bool ReadConfigValue(_In_z_ const char* wszName, uint64_t* pValue, bool decimal = false);
diff --git a/src/coreclr/nativeaot/Runtime/SpinLock.h b/src/coreclr/nativeaot/Runtime/SpinLock.h
index a343a1881f057c..56200d8b9398a6 100644
--- a/src/coreclr/nativeaot/Runtime/SpinLock.h
+++ b/src/coreclr/nativeaot/Runtime/SpinLock.h
@@ -3,6 +3,9 @@
#ifndef __SPINLOCK_H__
#define __SPINLOCK_H__
+// defined in gcrhenv.cpp
+bool __SwitchToThread(uint32_t dwSleepMSec, uint32_t dwSwitchCount);
+
// #SwitchToThreadSpinning
//
// If you call __SwitchToThread in a loop waiting for a condition to be met,
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/eventpipe/CMakeLists.txt
index 287c50d0676550..d0b918e3aabc30 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/CMakeLists.txt
@@ -18,13 +18,25 @@ set (SHARED_EVENTPIPE_SOURCE_PATH "${CLR_SRC_NATIVE_DIR}/eventpipe")
include (${SHARED_EVENTPIPE_SOURCE_PATH}/eventpipe.cmake)
include (${SHARED_CONTAINERS_SOURCE_PATH}/containers.cmake)
-list(APPEND SHARED_DIAGNOSTIC_SERVER_SOURCES
- ds-ipc-pal-namedpipe.c
-)
+if(CLR_CMAKE_HOST_WIN32)
+ list(APPEND SHARED_DIAGNOSTIC_SERVER_SOURCES
+ ds-ipc-pal-namedpipe.c
+ )
-list(APPEND SHARED_DIAGNOSTIC_SERVER_HEADERS
- ds-ipc-pal-namedpipe.h
-)
+ list(APPEND SHARED_DIAGNOSTIC_SERVER_HEADERS
+ ds-ipc-pal-namedpipe.h
+ )
+endif(CLR_CMAKE_HOST_WIN32)
+
+if(CLR_CMAKE_HOST_UNIX)
+ list(APPEND SHARED_DIAGNOSTIC_SERVER_SOURCES
+ ds-ipc-pal-socket.c
+ )
+
+ list(APPEND SHARED_DIAGNOSTIC_SERVER_HEADERS
+ ds-ipc-pal-socket.h
+ )
+endif(CLR_CMAKE_HOST_UNIX)
list(APPEND EVENTPIPE_SOURCES
${SHARED_EVENTPIPE_SOURCES}
@@ -63,6 +75,7 @@ endif()
list(APPEND AOT_EVENTPIPE_SHIM_SOURCES
${AOT_EVENTPIPE_SHIM_DIR}/ep-rt-aot.cpp
+ ${AOT_EVENTPIPE_SHIM_DIR}/ds-rt-aot.cpp
)
list(APPEND AOT_EVENTPIPE_SHIM_HEADERS
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.cpp b/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.cpp
new file mode 100644
index 00000000000000..9bb8d088a8d99b
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.cpp
@@ -0,0 +1,164 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include
+
+#ifdef ENABLE_PERFTRACING
+#include
+#include
+#include
+#include
+
+#include "ds-rt-aot.h"
+
+bool aot_ipc_get_process_id_disambiguation_key(uint32_t process_id, uint64_t *key);
+
+bool
+aot_ipc_get_process_id_disambiguation_key(
+ uint32_t process_id,
+ uint64_t *key)
+{
+ if (!key) {
+ EP_ASSERT (!"key argument cannot be null!");
+ return false;
+ }
+
+ *key = 0;
+
+// Mono implementation, restricted just to Unix
+#ifdef TARGET_UNIX
+
+ // Here we read /proc//stat file to get the start time for the process.
+ // We return this value (which is expressed in jiffies since boot time).
+
+ // Making something like: /proc/123/stat
+ char stat_file_name [64];
+ snprintf (stat_file_name, sizeof (stat_file_name), "/proc/%d/stat", process_id);
+
+ FILE *stat_file = fopen (stat_file_name, "r");
+ if (!stat_file) {
+ EP_ASSERT (!"Failed to get start time of a process, fopen failed.");
+ return false;
+ }
+
+ bool result = false;
+ unsigned long long start_time = 0;
+ char *scan_start_position;
+ int result_sscanf;
+
+ char *line = NULL;
+ size_t line_len = 0;
+ if (getline (&line, &line_len, stat_file) == -1)
+ {
+ EP_ASSERT (!"Failed to get start time of a process, getline failed.");
+ ep_raise_error ();
+ }
+
+
+ // According to `man proc`, the second field in the stat file is the filename of the executable,
+ // in parentheses. Tokenizing the stat file using spaces as separators breaks when that name
+ // has spaces in it, so we start using sscanf_s after skipping everything up to and including the
+ // last closing paren and the space after it.
+ scan_start_position = strrchr (line, ')');
+ if (!scan_start_position || scan_start_position [1] == '\0') {
+ EP_ASSERT (!"Failed to parse stat file contents with strrchr.");
+ ep_raise_error ();
+ }
+
+ scan_start_position += 2;
+
+ // All the format specifiers for the fields in the stat file are provided by 'man proc'.
+ result_sscanf = sscanf (scan_start_position,
+ "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d %*d %llu \n",
+ &start_time);
+
+ if (result_sscanf != 1) {
+ EP_ASSERT (!"Failed to parse stat file contents with sscanf.");
+ ep_raise_error ();
+ }
+
+ free (line);
+ fclose (stat_file);
+ result = true;
+
+ep_on_exit:
+ *key = (uint64_t)start_time;
+ return result;
+
+ep_on_error:
+ free (line);
+ fclose (stat_file);
+ result = false;
+ ep_exit_error_handler ();
+
+#else
+ // If we don't have /proc, we just return false.
+ DS_LOG_WARNING_0 ("ipc_get_process_id_disambiguation_key was called but is not implemented on this platform!");
+ return false;
+#endif
+}
+
+bool
+ds_rt_aot_transport_get_default_name (
+ ep_char8_t *name,
+ int32_t name_len,
+ const ep_char8_t *prefix,
+ int32_t id,
+ const ep_char8_t *group_id,
+ const ep_char8_t *suffix)
+{
+ STATIC_CONTRACT_NOTHROW;
+
+#ifdef TARGET_UNIX
+
+ EP_ASSERT (name != NULL);
+
+ bool result = false;
+ int32_t format_result = 0;
+ uint64_t disambiguation_key = 0;
+ ep_char8_t *format_buffer = NULL;
+
+ *name = '\0';
+
+ format_buffer = (ep_char8_t *)malloc (name_len + 1);
+ ep_raise_error_if_nok (format_buffer != NULL);
+
+ *format_buffer = '\0';
+
+ // If ipc_get_process_id_disambiguation_key failed for some reason, it should set the value
+ // to 0. We expect that anyone else making the pipe name will also fail and thus will
+ // also try to use 0 as the value.
+ if (!aot_ipc_get_process_id_disambiguation_key (id, &disambiguation_key))
+ EP_ASSERT (disambiguation_key == 0);
+
+ // Get a temp file location
+ format_result = ep_rt_temp_path_get (format_buffer, name_len);
+ if (format_result == 0) {
+ DS_LOG_ERROR_0 ("ep_rt_temp_path_get failed");
+ ep_raise_error ();
+ }
+
+ EP_ASSERT (format_result <= name_len);
+
+ format_result = snprintf(name, name_len, "%s%s-%d-%llu-%s", format_buffer, prefix, id, (unsigned long long)disambiguation_key, suffix);
+ if (format_result <= 0 || format_result > name_len) {
+ DS_LOG_ERROR_0 ("name buffer too small");
+ ep_raise_error ();
+ }
+
+ result = true;
+
+ep_on_exit:
+ free (format_buffer);
+ return result;
+
+ep_on_error:
+ EP_ASSERT (!result);
+ name [0] = '\0';
+ ep_exit_error_handler ();
+
+#else
+ return true;
+#endif
+}
+#endif /* ENABLE_PERFTRACING */
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.h b/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.h
index df255f7186e193..aaea67334eb0f4 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.h
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.h
@@ -191,11 +191,10 @@ ds_rt_transport_get_default_name (
const ep_char8_t *group_id,
const ep_char8_t *suffix)
{
- STATIC_CONTRACT_NOTHROW;
-
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: PAL_GetTransportName is defined in coreclr\pal\inc\pal.h
- return true;
+
+ extern bool ds_rt_aot_transport_get_default_name (ep_char8_t *name, int32_t name_len, const ep_char8_t *prefix, int32_t id, const ep_char8_t *group_id, const ep_char8_t *suffix);
+
+ return ds_rt_aot_transport_get_default_name(name, name_len, prefix, id, group_id, suffix);
}
/*
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp
index fea284db7f5df4..c629f2b86c5eb6 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp
@@ -9,6 +9,14 @@
#include
#include
+#ifdef TARGET_WINDOWS
+#include
+#else
+#include
+#include
+#include
+#endif
+
// The regdisplay.h, StackFrameIterator.h, and thread.h includes are present only to access the Thread
// class and can be removed if it turns out that the required ep_rt_thread_handle_t can be
// implemented in some manner that doesn't rely on the Thread class.
@@ -20,13 +28,19 @@
#include "holder.h"
#include "SpinLock.h"
+#ifdef TARGET_UNIX
+// Per module (1 for NativeAOT), key that will be used to implement TLS in Unix
+pthread_key_t eventpipe_tls_key;
+__thread EventPipeThreadHolder* eventpipe_tls_instance;
+#else
+thread_local EventPipeAotThreadHolderTLS EventPipeAotThreadHolderTLS::g_threadHolderTLS;
+#endif
+
// Uses _rt_aot_lock_internal_t that has CrstStatic as a field
// This is initialized at the beginning and EventPipe library requires the lock handle to be maintained by the runtime
ep_rt_lock_handle_t _ep_rt_aot_config_lock_handle;
CrstStatic _ep_rt_aot_config_lock;
-thread_local EventPipeAotThreadHolderTLS EventPipeAotThreadHolderTLS::g_threadHolderTLS;
-
ep_char8_t *volatile _ep_rt_aot_diagnostics_cmd_line;
#ifndef TARGET_UNIX
@@ -297,10 +311,7 @@ ep_rt_aot_current_thread_get_id (void)
STATIC_CONTRACT_NOTHROW;
#ifdef TARGET_UNIX
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: AOT doesn't have PAL_GetCurrentOSThreadId, as CoreCLR does.
- // PalDebugBreak();
- return static_cast(0);
+ return static_cast(PalGetCurrentOSThreadId());
#else
return static_cast(::GetCurrentThreadId ());
#endif
@@ -330,6 +341,73 @@ ep_rt_aot_system_timestamp_get (void)
return static_cast(((static_cast(value.dwHighDateTime)) << 32) | static_cast(value.dwLowDateTime));
}
+ep_rt_file_handle_t
+ep_rt_aot_file_open_write (const ep_char8_t *path)
+{
+ if (!path)
+ return INVALID_HANDLE_VALUE;
+
+#ifdef TARGET_WINDOWS
+ ep_char16_t *path_utf16 = ep_rt_utf8_to_utf16le_string (path, -1);
+ if (!path_utf16)
+ return INVALID_HANDLE_VALUE;
+
+ HANDLE res = ::CreateFileW (reinterpret_cast(path_utf16), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ ep_rt_utf16_string_free (path_utf16);
+ return static_cast(res);
+#else
+ mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ int fd = creat (path, perms);
+ if (fd == -1)
+ return INVALID_HANDLE_VALUE;
+
+ return (ep_rt_file_handle_t)(ptrdiff_t)fd;
+#endif
+}
+
+bool
+ep_rt_aot_file_close (ep_rt_file_handle_t file_handle)
+{
+#ifdef TARGET_WINDOWS
+ return ::CloseHandle (file_handle) != FALSE;
+#else
+ int fd = (int)(ptrdiff_t)file_handle;
+ close (fd);
+ return true;
+#endif
+}
+
+bool
+ep_rt_aot_file_write (
+ ep_rt_file_handle_t file_handle,
+ const uint8_t *buffer,
+ uint32_t bytes_to_write,
+ uint32_t *bytes_written)
+{
+#ifdef TARGET_WINDOWS
+ return ::WriteFile (file_handle, buffer, bytes_to_write, reinterpret_cast(bytes_written), NULL) != FALSE;
+#else
+ int fd = (int)(ptrdiff_t)file_handle;
+ int ret;
+ do {
+ ret = write (fd, buffer, bytes_to_write);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1) {
+ if (bytes_written != NULL) {
+ *bytes_written = 0;
+ }
+
+ return false;
+ }
+
+ if (bytes_written != NULL)
+ *bytes_written = ret;
+
+ return true;
+#endif
+}
+
uint8_t *
ep_rt_aot_valloc0 (size_t buffer_size)
{
@@ -378,7 +456,15 @@ ep_rt_aot_utf16_string_len (const ep_char16_t *str)
STATIC_CONTRACT_NOTHROW;
EP_ASSERT (str != NULL);
- return wcslen (reinterpret_cast(str));
+ #ifdef TARGET_UNIX
+ const uint16_t *a = (const uint16_t *)str;
+ size_t length = 0;
+ while (a [length])
+ ++length;
+ return length;
+ #else
+ return wcslen (reinterpret_cast(str));
+ #endif
}
uint32_t
@@ -509,6 +595,17 @@ ep_rt_aot_volatile_store_ptr_without_barrier (
VolatileStoreWithoutBarrier ((void **)ptr, value);
}
+void unix_tls_callback_fn(void *value)
+{
+ if (value) {
+ // we need to do the unallocation here
+ EventPipeThreadHolder *thread_holder_old = static_cast(value);
+ // @TODO - inline
+ thread_holder_free_func (thread_holder_old);
+ value = NULL;
+ }
+}
+
void ep_rt_aot_init (void)
{
extern ep_rt_lock_handle_t _ep_rt_aot_config_lock_handle;
@@ -516,6 +613,11 @@ void ep_rt_aot_init (void)
_ep_rt_aot_config_lock_handle.lock = &_ep_rt_aot_config_lock;
_ep_rt_aot_config_lock_handle.lock->InitNoThrow (CrstType::CrstEventPipeConfig);
+
+ // Initialize the pthread key used for TLS in Unix
+ #ifdef TARGET_UNIX
+ pthread_key_create(&eventpipe_tls_key, unix_tls_callback_fn);
+ #endif
}
bool ep_rt_aot_lock_acquire (ep_rt_lock_handle_t *lock)
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
index 786a67921b37cd..d17566514a379f 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
@@ -6,6 +6,10 @@
#define __EVENTPIPE_RT_AOT_H__
#include // For isspace
+#ifdef TARGET_UNIX
+#include
+#include
+#endif
#include
#ifdef ENABLE_PERFTRACING
@@ -15,6 +19,7 @@
#include
#include "rhassert.h"
+#include
#ifdef TARGET_UNIX
#define sprintf_s snprintf
@@ -42,6 +47,11 @@
#undef EP_ALIGN_UP
#define EP_ALIGN_UP(val,align) _rt_aot_align_up(val,align)
+#ifdef TARGET_UNIX
+extern pthread_key_t eventpipe_tls_key;
+extern __thread EventPipeThreadHolder* eventpipe_tls_instance;
+#endif
+
// shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
// TODO: The NativeAOT ALIGN_UP is defined in a tangled manner that generates linker errors if
// it is used here; instead, define a version tailored to the existing usage in the shared
@@ -62,7 +72,7 @@ ep_rt_lock_handle_t *
ep_rt_aot_config_lock_get (void)
{
extern ep_rt_lock_handle_t _ep_rt_aot_config_lock_handle;
- return &_ep_rt_aot_config_lock_handle;
+ return &_ep_rt_aot_config_lock_handle;
}
static
@@ -406,11 +416,11 @@ inline
bool
ep_rt_config_value_get_enable (void)
{
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EnableEventPipe) != 0
- // If EventPipe environment variables are specified, parse them and start a session.
- // TODO: Not start a session for now
+ // See https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables
+ bool value;
+ if (RhConfig::Environment::TryGetBooleanValue("EnableEventPipe", &value))
+ return value;
+
return false;
}
@@ -420,12 +430,13 @@ ep_char8_t *
ep_rt_config_value_get_config (void)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EventPipeConfig)
- // PalDebugBreak();
+
+ // See https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables
+ char* value;
+ if (RhConfig::Environment::TryGetStringValue("EventPipeConfig", &value))
+ return (ep_char8_t*)value;
+
return nullptr;
-// return ep_rt_utf16_to_utf8_string (reinterpret_cast(value.GetValue ()), -1);
}
static
@@ -434,10 +445,12 @@ ep_char8_t *
ep_rt_config_value_get_output_path (void)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EventPipeOutputPath)
- //PalDebugBreak();
+
+ // See https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables
+ char* value;
+ if (RhConfig::Environment::TryGetStringValue("EventPipeOutputPath", &value))
+ return (ep_char8_t*)value;
+
return nullptr;
}
@@ -447,10 +460,15 @@ uint32_t
ep_rt_config_value_get_circular_mb (void)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EventPipeCircularMB)
- //PalDebugBreak();
+
+ // See https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables
+ uint64_t value;
+ if (RhConfig::Environment::TryGetIntegerValue("EventPipeCircularMB", &value))
+ {
+ EP_ASSERT(value <= UINT32_MAX);
+ return static_cast(value);
+ }
+
return 0;
}
@@ -460,10 +478,11 @@ bool
ep_rt_config_value_get_output_streaming (void)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EventPipeOutputStreaming)
- //PalDebugBreak();
+
+ bool value;
+ if (RhConfig::Environment::TryGetBooleanValue("EventPipeOutputStreaming", &value))
+ return value;
+
return false;
}
@@ -473,10 +492,11 @@ bool
ep_rt_config_value_get_enable_stackwalk (void)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: EventPipe Configuration values - RhConfig?
- // (CLRConfig::INTERNAL_EventPipeEnableStackwalk)
- //PalDebugBreak();
+
+ bool value;
+ if (RhConfig::Environment::TryGetBooleanValue("EventPipeEnableStackwalk", &value))
+ return value;
+
return true;
}
@@ -667,28 +687,28 @@ ep_rt_create_activity_id (
uint8_t data1[] = {0x67,0xac,0x33,0xf1,0x8d,0xed,0x41,0x01,0xb4,0x26,0xc9,0xb7,0x94,0x35,0xf7,0x8a};
memcpy (activity_id, data1, EP_ACTIVITY_ID_SIZE);
- const uint16_t version_mask = 0xF000;
- const uint16_t random_guid_version = 0x4000;
- const uint8_t clock_seq_hi_and_reserved_mask = 0xC0;
- const uint8_t clock_seq_hi_and_reserved_value = 0x80;
+ const uint16_t version_mask = 0xF000;
+ const uint16_t random_guid_version = 0x4000;
+ const uint8_t clock_seq_hi_and_reserved_mask = 0xC0;
+ const uint8_t clock_seq_hi_and_reserved_value = 0x80;
- // Modify bits indicating the type of the GUID
- uint8_t *activity_id_c = activity_id + sizeof (uint32_t) + sizeof (uint16_t);
- uint8_t *activity_id_d = activity_id + sizeof (uint32_t) + sizeof (uint16_t) + sizeof (uint16_t);
+ // Modify bits indicating the type of the GUID
+ uint8_t *activity_id_c = activity_id + sizeof (uint32_t) + sizeof (uint16_t);
+ uint8_t *activity_id_d = activity_id + sizeof (uint32_t) + sizeof (uint16_t) + sizeof (uint16_t);
- uint16_t c;
- memcpy (&c, activity_id_c, sizeof (c));
+ uint16_t c;
+ memcpy (&c, activity_id_c, sizeof (c));
- uint8_t d;
- memcpy (&d, activity_id_d, sizeof (d));
+ uint8_t d;
+ memcpy (&d, activity_id_d, sizeof (d));
- // time_hi_and_version
- c = ((c & ~version_mask) | random_guid_version);
- // clock_seq_hi_and_reserved
- d = ((d & ~clock_seq_hi_and_reserved_mask) | clock_seq_hi_and_reserved_value);
+ // time_hi_and_version
+ c = ((c & ~version_mask) | random_guid_version);
+ // clock_seq_hi_and_reserved
+ d = ((d & ~clock_seq_hi_and_reserved_mask) | clock_seq_hi_and_reserved_value);
- memcpy (activity_id_c, &c, sizeof (c));
- memcpy (activity_id_d, &d, sizeof (d));
+ memcpy (activity_id_c, &c, sizeof (c));
+ memcpy (activity_id_d, &d, sizeof (d));
}
static
@@ -898,19 +918,54 @@ ep_rt_system_time_get (EventPipeSystemTime *system_time)
EP_ASSERT(system_time != NULL);
ep_system_time_set (
- system_time,
- value.wYear,
- value.wMonth,
- value.wDayOfWeek,
- value.wDay,
- value.wHour,
- value.wMinute,
- value.wSecond,
- value.wMilliseconds);
-#else
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Get System time
- // PalDebugBreak();
+ system_time,
+ value.wYear,
+ value.wMonth,
+ value.wDayOfWeek,
+ value.wDay,
+ value.wHour,
+ value.wMinute,
+ value.wSecond,
+ value.wMilliseconds);
+#elif TARGET_UNIX
+ time_t tt;
+ struct tm *ut_ptr;
+ struct timeval time_val;
+ int timeofday_retval;
+
+ EP_ASSERT (system_time != NULL);
+
+ tt = time (NULL);
+
+ timeofday_retval = gettimeofday (&time_val, NULL);
+
+ ut_ptr = gmtime (&tt);
+
+ uint16_t milliseconds = 0;
+ if (timeofday_retval != -1) {
+ int old_seconds;
+ int new_seconds;
+
+ milliseconds = (uint16_t)(time_val.tv_usec / 1000);
+
+ old_seconds = ut_ptr->tm_sec;
+ new_seconds = time_val.tv_sec % 60;
+
+ /* just in case we reached the next second in the interval between time () and gettimeofday () */
+ if (old_seconds != new_seconds)
+ milliseconds = 999;
+ }
+
+ ep_system_time_set (
+ system_time,
+ (uint16_t)(1900 + ut_ptr->tm_year),
+ (uint16_t)ut_ptr->tm_mon + 1,
+ (uint16_t)ut_ptr->tm_wday,
+ (uint16_t)ut_ptr->tm_mday,
+ (uint16_t)ut_ptr->tm_hour,
+ (uint16_t)ut_ptr->tm_min,
+ (uint16_t)ut_ptr->tm_sec,
+ milliseconds);
#endif
}
@@ -952,14 +1007,8 @@ ep_rt_file_open_write (const ep_char8_t *path)
{
STATIC_CONTRACT_NOTHROW;
- ep_char16_t *path_utf16 = ep_rt_utf8_to_utf16le_string (path, -1);
- ep_return_null_if_nok (path_utf16 != NULL);
-
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Find out the way to open a file in native
- // PalDebugBreak();
-
- return 0;
+ extern ep_rt_file_handle_t ep_rt_aot_file_open_write (const ep_char8_t *);
+ return ep_rt_aot_file_open_write (path);
}
static
@@ -969,10 +1018,8 @@ ep_rt_file_close (ep_rt_file_handle_t file_handle)
{
STATIC_CONTRACT_NOTHROW;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Find out the way to close a file in native
- // PalDebugBreak();
- return true;
+ extern bool ep_rt_aot_file_close (ep_rt_file_handle_t);
+ return ep_rt_aot_file_close (file_handle);
}
static
@@ -987,11 +1034,8 @@ ep_rt_file_write (
STATIC_CONTRACT_NOTHROW;
EP_ASSERT (buffer != NULL);
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Find out the way to write to a file in native
- // PalDebugBreak();
-
- return false;
+ extern bool ep_rt_aot_file_write (ep_rt_file_handle_t, const uint8_t*, uint32_t, uint32_t*);
+ return ep_rt_aot_file_write (file_handle, buffer, bytes_to_write, bytes_written);
}
static
@@ -1030,9 +1074,37 @@ ep_rt_temp_path_get (
uint32_t buffer_len)
{
STATIC_CONTRACT_NOTHROW;
-// EP_UNREACHABLE ("Can not reach here");
+#ifdef TARGET_UNIX
+
+ EP_ASSERT (buffer != NULL);
+ EP_ASSERT (buffer_len > 0);
+
+ const ep_char8_t *path = getenv ("TMPDIR");
+ if (path == NULL){
+ path = getenv ("TMP");
+ if (path == NULL){
+ path = getenv ("TEMP");
+ if (path == NULL)
+ path = "/tmp/";
+ }
+ }
+
+ int32_t result = snprintf (buffer, buffer_len, path[strlen(path) - 1] == '/' ? "%s" : "%s/", path);
+ if (result <= 0 || (uint32_t)result >= buffer_len)
+ ep_raise_error ();
+
+
+ep_on_exit:
+ return result;
+
+ep_on_error:
+ result = 0;
+ ep_exit_error_handler ();
+
+#else
return 0;
+#endif
}
static
@@ -1313,8 +1385,9 @@ ep_rt_utf8_to_utf16le_string (
if (!str)
return NULL;
- // shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Implementation would just use strlen and malloc to make a new buffer, and would then copy the string chars one by one
+ // Shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
+ // Implementation would just use strlen and malloc to make a new buffer, and would then copy the string chars one by one.
+ // Assumes that only ASCII is used for ep_char8_t
size_t len_utf8 = strlen(str);
if (len_utf8 == 0)
return NULL;
@@ -1325,6 +1398,7 @@ ep_rt_utf8_to_utf16le_string (
for (size_t i = 0; i < len_utf8; i++)
{
+ EP_ASSERT(isascii(str[i]));
str_utf16[i] = str[i];
}
@@ -1383,12 +1457,10 @@ ep_rt_utf16_to_utf8_string (
return NULL;
// shipping criteria: no EVENTPIPE-NATIVEAOT-TODO left in the codebase
- // TODO: Temp implementation that is the reverse of ep_rt_utf8_to_utf16le_string
+ // Simple implementation to create a utf8 string from a utf16 one
size_t len_utf16 = len;
if(len_utf16 == (size_t)-1)
- {
len_utf16 = ep_rt_utf16_string_len (str);
- }
ep_char8_t *str_utf8 = reinterpret_cast(malloc ((len_utf16 + 1) * sizeof (ep_char8_t)));
if (!str_utf8)
@@ -1531,6 +1603,44 @@ ep_rt_thread_setup (void)
// EP_ASSERT (thread_handle != NULL);
}
+#ifdef TARGET_UNIX
+static
+inline
+EventPipeThreadHolder *
+pthread_getThreadHolder (void)
+{
+ void *value = eventpipe_tls_instance;
+ if (value) {
+ EventPipeThreadHolder *thread_holder = static_cast(value);
+ return thread_holder;
+ }
+ return NULL;
+}
+
+static
+inline
+EventPipeThreadHolder *
+pthread_createThreadHolder (void)
+{
+ void *value = eventpipe_tls_instance;
+ if (value) {
+ // we need to do the unallocation here
+ EventPipeThreadHolder *thread_holder_old = static_cast(value);
+ thread_holder_free_func(thread_holder_old);
+ eventpipe_tls_instance = NULL;
+
+ value = NULL;
+ }
+ EventPipeThreadHolder *instance = thread_holder_alloc_func();
+ if (instance){
+ // We need to know when the thread is no longer in use to clean up EventPipeThreadHolder instance and will use pthread destructor function to get notification when that happens.
+ pthread_setspecific(eventpipe_tls_key, instance);
+ eventpipe_tls_instance = instance;
+ }
+ return instance;
+}
+#endif
+
static
inline
EventPipeThread *
@@ -1538,7 +1648,11 @@ ep_rt_thread_get (void)
{
STATIC_CONTRACT_NOTHROW;
+#ifdef TARGET_UNIX
+ EventPipeThreadHolder *thread_holder = pthread_getThreadHolder ();
+#else
EventPipeThreadHolder *thread_holder = EventPipeAotThreadHolderTLS::getThreadHolder ();
+#endif
return thread_holder ? ep_thread_holder_get_thread (thread_holder) : NULL;
}
@@ -1549,9 +1663,15 @@ ep_rt_thread_get_or_create (void)
{
STATIC_CONTRACT_NOTHROW;
- EventPipeThreadHolder *thread_holder = EventPipeAotThreadHolderTLS::getThreadHolder ();
+#ifdef TARGET_UNIX
+ EventPipeThreadHolder *thread_holder = pthread_getThreadHolder ();
+ if (!thread_holder)
+ thread_holder = pthread_createThreadHolder ();
+#else
+ EventPipeThreadHolder *thread_holder = EventPipeAotThreadHolderTLS::getThreadHolder ();
if (!thread_holder)
thread_holder = EventPipeAotThreadHolderTLS::createThreadHolder ();
+#endif
return ep_thread_holder_get_thread (thread_holder);
}
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-types-aot.h b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-types-aot.h
index df06d264c8d7b4..22c777f692812e 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-types-aot.h
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-types-aot.h
@@ -58,7 +58,7 @@ typedef class MethodDesc ep_rt_method_desc_t;
*/
#undef ep_rt_file_handle_t
-typedef class CFileStream * ep_rt_file_handle_t;
+typedef void * ep_rt_file_handle_t;
#undef ep_rt_wait_event_handle_t
typedef struct _rt_aot_event_internal_t ep_rt_wait_event_handle_t;
diff --git a/src/coreclr/nativeaot/Runtime/startup.cpp b/src/coreclr/nativeaot/Runtime/startup.cpp
index ed44c9e948a485..a178a11d102fae 100644
--- a/src/coreclr/nativeaot/Runtime/startup.cpp
+++ b/src/coreclr/nativeaot/Runtime/startup.cpp
@@ -474,22 +474,24 @@ static void UninitDLL()
// the process is terminated via `exit()` or a signal. Thus there is no such distinction
// between threads.
Thread* g_threadPerformingShutdown = NULL;
+#endif
static void __cdecl OnProcessExit()
{
+#ifdef _WIN32
// The process is exiting and the current thread is performing the shutdown.
// When this thread exits some threads may be already rudely terminated.
// It would not be a good idea for this thread to wait on any locks
// or run managed code at shutdown, so we will not try detaching it.
Thread* currentThread = ThreadStore::RawGetCurrentThread();
g_threadPerformingShutdown = currentThread;
+#endif
#ifdef FEATURE_PERFTRACING
EventPipeAdapter_Shutdown();
DiagnosticServerAdapter_Shutdown();
#endif
}
-#endif
void RuntimeThreadShutdown(void* thread)
{
@@ -526,7 +528,7 @@ extern "C" bool RhInitialize()
if (!PalInit())
return false;
-#ifdef _WIN32
+#if defined(_WIN32) || defined(FEATURE_PERFTRACING)
atexit(&OnProcessExit);
#endif
diff --git a/src/coreclr/nativeaot/Runtime/stressLog.cpp b/src/coreclr/nativeaot/Runtime/stressLog.cpp
index 3c8dd0f80a0891..ed2c02a5ee58ee 100644
--- a/src/coreclr/nativeaot/Runtime/stressLog.cpp
+++ b/src/coreclr/nativeaot/Runtime/stressLog.cpp
@@ -334,7 +334,7 @@ void ThreadStressLog::LogMsg ( uint32_t facility, int cArgs, const char* format,
msg->args[i] = data;
}
- ASSERT(IsValid() && threadId == PalGetCurrentThreadIdForLogging());
+ ASSERT(IsValid() && threadId == PalGetCurrentOSThreadId());
}
@@ -342,7 +342,7 @@ void ThreadStressLog::Activate (Thread * pThread)
{
_ASSERTE(pThread != NULL);
//there is no need to zero buffers because we could handle garbage contents
- threadId = PalGetCurrentThreadIdForLogging();
+ threadId = PalGetCurrentOSThreadId();
isDead = FALSE;
curWriteChunk = chunkListTail;
curPtr = (StressMsg *)curWriteChunk->EndPtr ();
diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp
index 4d8c2c29d1a780..9eb428c3d0c402 100644
--- a/src/coreclr/nativeaot/Runtime/thread.cpp
+++ b/src/coreclr/nativeaot/Runtime/thread.cpp
@@ -1307,7 +1307,7 @@ COOP_PINVOKE_HELPER(uint8_t*, RhCurrentNativeThreadId, ())
// This function is used to get the OS thread identifier for the current thread.
COOP_PINVOKE_HELPER(uint64_t, RhCurrentOSThreadId, ())
{
- return PalGetCurrentThreadIdForLogging();
+ return PalGetCurrentOSThreadId();
}
// Standard calling convention variant and actual implementation for RhpReversePInvokeAttachOrTrapThread
diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
index 407adf5838626c..a856be48f4ab8c 100644
--- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
+++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
@@ -738,6 +738,13 @@ REDHAWK_PALEXPORT void PalPrintFatalError(const char* message)
(void)!write(STDERR_FILENO, message, strlen(message));
}
+REDHAWK_PALEXPORT char* PalCopyTCharAsChar(const TCHAR* toCopy)
+{
+ NewArrayHolder copy {new (nothrow) char[strlen(toCopy) + 1]};
+ strcpy(copy, toCopy);
+ return copy.Extract();
+}
+
static int W32toUnixAccessControl(uint32_t flProtect)
{
int prot = 0;
@@ -1256,7 +1263,7 @@ extern "C" uint64_t PalQueryPerformanceFrequency()
return GCToOSInterface::QueryPerformanceFrequency();
}
-extern "C" uint64_t PalGetCurrentThreadIdForLogging()
+extern "C" uint64_t PalGetCurrentOSThreadId()
{
#if defined(__linux__)
return (uint64_t)syscall(SYS_gettid);
diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
index 568d9c1cdb000c..c7b1f3e313fa39 100644
--- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
+++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
@@ -24,11 +24,6 @@
#define PalRaiseFailFastException RaiseFailFastException
-uint32_t PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t arg3, EVENT_DATA_DESCRIPTOR * arg4)
-{
- return EventWrite(arg1, arg2, arg3, arg4);
-}
-
#include "gcenv.h"
#include "gcenv.ee.h"
#include "gcconfig.h"
@@ -227,7 +222,7 @@ extern "C" uint64_t PalQueryPerformanceFrequency()
return GCToOSInterface::QueryPerformanceFrequency();
}
-extern "C" uint64_t PalGetCurrentThreadIdForLogging()
+extern "C" uint64_t PalGetCurrentOSThreadId()
{
return GetCurrentThreadId();
}
@@ -634,6 +629,21 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalEventEnabled(REGHANDLE regHandle, _In_
return !!EventEnabled(regHandle, eventDescriptor);
}
+REDHAWK_PALEXPORT uint32_t REDHAWK_PALAPI PalEventRegister(const GUID * arg1, void * arg2, void * arg3, REGHANDLE * arg4)
+{
+ return EventRegister(arg1, reinterpret_cast(arg2), arg3, arg4);
+}
+
+REDHAWK_PALEXPORT uint32_t REDHAWK_PALAPI PalEventUnregister(REGHANDLE arg1)
+{
+ return EventUnregister(arg1);
+}
+
+REDHAWK_PALEXPORT uint32_t REDHAWK_PALAPI PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t arg3, EVENT_DATA_DESCRIPTOR * arg4)
+{
+ return EventWrite(arg1, arg2, arg3, arg4);
+}
+
REDHAWK_PALEXPORT void REDHAWK_PALAPI PalTerminateCurrentProcess(uint32_t arg2)
{
TerminateProcess(GetCurrentProcess(), arg2);
@@ -719,6 +729,18 @@ REDHAWK_PALEXPORT void PalPrintFatalError(const char* message)
WriteFile(GetStdHandle(STD_ERROR_HANDLE), message, (DWORD)strlen(message), &dwBytesWritten, NULL);
}
+REDHAWK_PALEXPORT char* PalCopyTCharAsChar(const TCHAR* toCopy)
+{
+ int len = ::WideCharToMultiByte(CP_UTF8, 0, toCopy, -1, nullptr, 0, nullptr, nullptr);
+ if (len == 0)
+ return nullptr;
+
+ char* converted = new (nothrow) char[len];
+ int written = ::WideCharToMultiByte(CP_UTF8, 0, toCopy, -1, converted, len, nullptr, nullptr);
+ assert(len == written);
+ return converted;
+}
+
REDHAWK_PALEXPORT _Ret_maybenull_ _Post_writable_byte_size_(size) void* REDHAWK_PALAPI PalVirtualAlloc(_In_opt_ void* pAddress, uintptr_t size, uint32_t allocationType, uint32_t protect)
{
return VirtualAlloc(pAddress, size, allocationType, protect);
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
index 1f1c6f8678ce88..3936253be7ace5 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -907,7 +907,20 @@ public void TotalProcessorTime_PerformLoop_TotalProcessorTimeValid()
double cpuUsage = cpuTimeDiff / (timeDiff * Environment.ProcessorCount);
- Assert.InRange(cpuUsage, 0, 1);
+ try
+ {
+ Assert.InRange(cpuUsage, 0, 1); // InRange is an inclusive test
+ }
+ catch (InRangeException)
+ {
+ string msg = $"Assertion failed. {cpuUsage} is not in range [0,1]. " +
+ $"proc time before:{processorTimeBeforeSpin.TotalMilliseconds} " +
+ $"proc time after:{processorTimeAfterSpin.TotalMilliseconds} " +
+ $"timeDiff:{timeDiff} " +
+ $"cpuTimeDiff:{cpuTimeDiff} " +
+ $"Environment.ProcessorCount:{Environment.ProcessorCount}";
+ throw new XunitException(msg);
+ }
}
[Fact]
diff --git a/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs b/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs
index bbf5b000423c25..5dfa892193d972 100644
--- a/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs
+++ b/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs
@@ -30,6 +30,8 @@ public BinaryData(string data) { }
public static System.BinaryData FromString(string data) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
+ public bool IsEmpty { get { throw null; } }
+ public int Length { get { throw null; } }
public static implicit operator System.ReadOnlyMemory (System.BinaryData? data) { throw null; }
public static implicit operator System.ReadOnlySpan (System.BinaryData? data) { throw null; }
public byte[] ToArray() { throw null; }
diff --git a/src/libraries/System.Memory.Data/src/System/BinaryData.cs b/src/libraries/System.Memory.Data/src/System/BinaryData.cs
index 82615b3a61e03e..0832a5bd7bcfba 100644
--- a/src/libraries/System.Memory.Data/src/System/BinaryData.cs
+++ b/src/libraries/System.Memory.Data/src/System/BinaryData.cs
@@ -32,6 +32,18 @@ public class BinaryData
///
public static BinaryData Empty { get; } = new BinaryData(ReadOnlyMemory.Empty);
+ ///
+ /// Gets the number of bytes of this data.
+ ///
+ /// The number of bytes of this data.
+ public int Length => _bytes.Length;
+
+ ///
+ /// Gets a value that indicates whether this data is empty.
+ ///
+ /// if the data is empty (that is, its is 0); otherwise, .
+ public bool IsEmpty => _bytes.IsEmpty;
+
///
/// Creates a instance by wrapping the
/// provided byte array.
diff --git a/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs b/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs
index aa2d18c8a3ca93..d3f1fe711bb9b1 100644
--- a/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs
+++ b/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
@@ -614,6 +614,93 @@ public void ToStringReturnEmptyStringWhenBinaryDataEmpty()
Assert.Equal(string.Empty, BinaryData.Empty.ToString());
}
+ [Theory]
+ [InlineData(0)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void LengthReturnsNumberOfBytesForBinaryDataFromReadOnlyMemory(int count)
+ {
+ var data = BinaryData.FromBytes(new ReadOnlyMemory(new byte[count]));
+ Assert.Equal(count, data.Length);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void LengthReturnsNumberOfBytesForBinaryDataFromArray(int count)
+ {
+ var data = BinaryData.FromBytes(new byte[count]);
+ Assert.Equal(count, data.Length);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void LengthReturnsNumberOfBytesForBinaryDataFromString(int count)
+ {
+ var data = BinaryData.FromString(new string('*', count));
+ Assert.Equal(count, data.Length);
+ }
+
+ [Fact]
+ public void BinaryDataEmptyIsEmpty()
+ {
+ Assert.True(BinaryData.Empty.IsEmpty);
+ }
+
+ [Fact]
+ public void BinaryDataFromEmptyReadOnlyMemoryIsEmpty()
+ {
+ var data = BinaryData.FromBytes(ReadOnlyMemory.Empty);
+ Assert.True(data.IsEmpty);
+ }
+
+ [Fact]
+ public void BinaryDataFromEmptyArrayIsEmpty()
+ {
+ var data = BinaryData.FromBytes(Array.Empty());
+ Assert.True(data.IsEmpty);
+ }
+
+ [Fact]
+ public void BinaryDataFromEmptyStringIsEmpty()
+ {
+ var data = BinaryData.FromString("");
+ Assert.True(data.IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void NonEmptyBinaryDataFromReadOnlyMemoryIsNotEmpty(int count)
+ {
+ var data = BinaryData.FromBytes(new ReadOnlyMemory(new byte[count]));
+ Assert.False(data.IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void NonEmptyBinaryDataFromArrayIsNotEmpty(int count)
+ {
+ var data = BinaryData.FromBytes(new byte[count]);
+ Assert.False(data.IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void NonEmptyBinaryDataFromStringIsNotEmpty(int count)
+ {
+ var data = BinaryData.FromString(new string('*', count));
+ Assert.False(data.IsEmpty);
+ }
+
[Fact]
public void IsBinaryDataMemberPropertySerialized()
{
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
index 2752225c032653..6c8a00bd00eebc 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs
@@ -11,22 +11,26 @@
using System.Runtime.CompilerServices;
using QueueType = System.Threading.Channels.Channel;
-namespace System.Runtime.InteropServices.JavaScript {
+namespace System.Runtime.InteropServices.JavaScript
+{
///
/// Provides a thread-safe default SynchronizationContext for the browser that will automatically
/// route callbacks to the main browser thread where they can interact with the DOM and other
/// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc.
/// Callbacks are processed during event loop turns via the runtime's background job system.
///
- internal sealed unsafe class JSSynchronizationContext : SynchronizationContext {
+ internal sealed class JSSynchronizationContext : SynchronizationContext
+ {
public readonly Thread MainThread;
- internal readonly struct WorkItem {
+ internal readonly struct WorkItem
+ {
public readonly SendOrPostCallback Callback;
public readonly object? Data;
public readonly ManualResetEventSlim? Signal;
- public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim? signal) {
+ public WorkItem(SendOrPostCallback callback, object? data, ManualResetEventSlim? signal)
+ {
Callback = callback;
Data = data;
Signal = signal;
@@ -35,11 +39,11 @@ public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim
private static JSSynchronizationContext? MainThreadSynchronizationContext;
private readonly QueueType Queue;
- private readonly Action _DataIsAvailable;
+ private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
- private JSSynchronizationContext (Thread mainThread)
- : this (
- mainThread,
+ private JSSynchronizationContext()
+ : this(
+ Thread.CurrentThread,
Channel.CreateUnbounded(
new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true }
)
@@ -47,19 +51,23 @@ private JSSynchronizationContext (Thread mainThread)
{
}
- private JSSynchronizationContext (Thread mainThread, QueueType queue) {
+ private JSSynchronizationContext(Thread mainThread, QueueType queue)
+ {
MainThread = mainThread;
Queue = queue;
_DataIsAvailable = DataIsAvailable;
}
- public override SynchronizationContext CreateCopy () {
+ public override SynchronizationContext CreateCopy()
+ {
return new JSSynchronizationContext(MainThread, Queue);
}
- private void AwaitNewData () {
+ private void AwaitNewData()
+ {
var vt = Queue.Reader.WaitToReadAsync();
- if (vt.IsCompleted) {
+ if (vt.IsCompleted)
+ {
DataIsAvailable();
return;
}
@@ -72,28 +80,33 @@ private void AwaitNewData () {
awaiter.UnsafeOnCompleted(_DataIsAvailable);
}
- private void DataIsAvailable () {
+ private unsafe void DataIsAvailable()
+ {
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
- ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler);
+ MainThreadScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler);
}
- public override void Post (SendOrPostCallback d, object? state) {
+ public override void Post(SendOrPostCallback d, object? state)
+ {
var workItem = new WorkItem(d, state, null);
if (!Queue.Writer.TryWrite(workItem))
throw new Exception("Internal error");
}
// This path can only run when threading is enabled
- #pragma warning disable CA1416
+#pragma warning disable CA1416
- public override void Send (SendOrPostCallback d, object? state) {
- if (Thread.CurrentThread == MainThread) {
+ public override void Send(SendOrPostCallback d, object? state)
+ {
+ if (Thread.CurrentThread == MainThread)
+ {
d(state);
return;
}
- using (var signal = new ManualResetEventSlim(false)) {
+ using (var signal = new ManualResetEventSlim(false))
+ {
var workItem = new WorkItem(d, state, signal);
if (!Queue.Writer.TryWrite(workItem))
throw new Exception("Internal error");
@@ -102,40 +115,50 @@ public override void Send (SendOrPostCallback d, object? state) {
}
}
- internal static void Install () {
- MainThreadSynchronizationContext ??= new JSSynchronizationContext(Thread.CurrentThread);
-
+ internal static void Install()
+ {
+ MainThreadSynchronizationContext ??= new JSSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext);
MainThreadSynchronizationContext.AwaitNewData();
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe void ScheduleBackgroundJob(void* callback);
+ internal static extern unsafe void MainThreadScheduleBackgroundJob(void* callback);
#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
- private static unsafe void BackgroundJobHandler () {
+#pragma warning restore CS3016
+ // this callback will arrive on the bound thread, called from mono_background_exec
+ private static void BackgroundJobHandler()
+ {
MainThreadSynchronizationContext!.Pump();
}
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
- private static unsafe void RequestPumpCallback () {
- ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler);
- }
-
- private void Pump () {
- try {
- while (Queue.Reader.TryRead(out var item)) {
- try {
+ private void Pump()
+ {
+ try
+ {
+ while (Queue.Reader.TryRead(out var item))
+ {
+ try
+ {
item.Callback(item.Data);
// While we would ideally have a catch block here and do something to dispatch/forward unhandled
// exceptions, the standard threadpool (and thus standard synchronizationcontext) have zero
// error handling, so for consistency with them we do nothing. Don't throw in SyncContext callbacks.
- } finally {
+ }
+ finally
+ {
item.Signal?.Set();
}
}
- } finally {
+ }
+ catch (Exception e)
+ {
+ Environment.FailFast("JSSynchronizationContext.BackgroundJobHandler failed", e);
+ }
+ finally
+ {
// If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
AwaitNewData();
}
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs
index 94a8fb6285aa13..54cfe877ec82e4 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs
@@ -16,15 +16,15 @@ internal sealed record ComInterfaceContext(ComInterfaceInfo Info, ComInterfaceCo
///
/// Takes a list of ComInterfaceInfo, and creates a list of ComInterfaceContext.
///
- public static ImmutableArray<(ComInterfaceContext? Context, Diagnostic? Diagnostic)> GetContexts(ImmutableArray data, CancellationToken _)
+ public static ImmutableArray> GetContexts(ImmutableArray data, CancellationToken _)
{
Dictionary nameToInterfaceInfoMap = new();
- var accumulator = ImmutableArray.CreateBuilder<(ComInterfaceContext? Context, Diagnostic? Diagnostic)>(data.Length);
+ var accumulator = ImmutableArray.CreateBuilder>(data.Length);
foreach (var iface in data)
{
nameToInterfaceInfoMap.Add(iface.ThisInterfaceKey, iface);
}
- Dictionary nameToContextCache = new();
+ Dictionary> nameToContextCache = new();
foreach (var iface in data)
{
@@ -32,7 +32,7 @@ internal sealed record ComInterfaceContext(ComInterfaceInfo Info, ComInterfaceCo
}
return accumulator.MoveToImmutable();
- (ComInterfaceContext? Context, Diagnostic? Diagnostic) AddContext(ComInterfaceInfo iface)
+ DiagnosticOr AddContext(ComInterfaceInfo iface)
{
if (nameToContextCache.TryGetValue(iface.ThisInterfaceKey, out var cachedValue))
{
@@ -41,33 +41,33 @@ internal sealed record ComInterfaceContext(ComInterfaceInfo Info, ComInterfaceCo
if (iface.BaseInterfaceKey is null)
{
- var baselessCtx = new ComInterfaceContext(iface, null);
- nameToContextCache[iface.ThisInterfaceKey] = (baselessCtx, null);
- return (baselessCtx, null);
+ var baselessCtx = DiagnosticOr.From(new ComInterfaceContext(iface, null));
+ nameToContextCache[iface.ThisInterfaceKey] = baselessCtx;
+ return baselessCtx;
}
+ DiagnosticOr baseReturnedValue;
if (
- // Cached base info has a diagnostic - failure
- (nameToContextCache.TryGetValue(iface.BaseInterfaceKey, out var basePair) && basePair.Diagnostic is not null)
+ // Cached base info is a diagnostic - failure
+ (nameToContextCache.TryGetValue(iface.BaseInterfaceKey, out var baseCachedValue) && baseCachedValue.IsDiagnostic)
// Cannot find base ComInterfaceInfo - failure (failed ComInterfaceInfo creation)
|| !nameToInterfaceInfoMap.TryGetValue(iface.BaseInterfaceKey, out var baseInfo)
- // Newly calculated base context pair has a diagnostic - failure
- || (AddContext(baseInfo) is { } baseReturnPair && baseReturnPair.Diagnostic is not null))
+ // Newly calculated base context pair is a diagnostic - failure
+ || (baseReturnedValue = AddContext(baseInfo)).IsDiagnostic)
{
// The base has failed generation at some point, so this interface cannot be generated
- (ComInterfaceContext, Diagnostic?) diagnosticPair = (null,
+ var diagnostic = DiagnosticOr.From(
Diagnostic.Create(
GeneratorDiagnostics.BaseInterfaceIsNotGenerated,
iface.DiagnosticLocation.AsLocation(), iface.ThisInterfaceKey, iface.BaseInterfaceKey));
- nameToContextCache[iface.ThisInterfaceKey] = diagnosticPair;
- return diagnosticPair;
+ nameToContextCache[iface.ThisInterfaceKey] = diagnostic;
+ return diagnostic;
}
- var baseContext = basePair.Context ?? baseReturnPair.Context;
- Debug.Assert(baseContext != null);
- var ctx = new ComInterfaceContext(iface, baseContext);
- (ComInterfaceContext, Diagnostic?) contextPair = (ctx, null);
- nameToContextCache[iface.ThisInterfaceKey] = contextPair;
- return contextPair;
+ DiagnosticOr baseContext = baseCachedValue ?? baseReturnedValue;
+ Debug.Assert(baseContext.IsValue);
+ var ctx = DiagnosticOr.From(new ComInterfaceContext(iface, baseContext.Value));
+ nameToContextCache[iface.ThisInterfaceKey] = ctx;
+ return ctx;
}
}
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
index 5497794cf62fed..cdfdbfab472072 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs
@@ -44,43 +44,27 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Where(
static modelData => modelData is not null);
- var interfaceSymbolAndDiagnostics = attributedInterfaces.Select(static (data, ct) =>
+ var interfaceSymbolOrDiagnostics = attributedInterfaces.Select(static (data, ct) =>
{
- var (info, diagnostic) = ComInterfaceInfo.From(data.Symbol, data.Syntax);
- return (InterfaceInfo: info, Diagnostic: diagnostic, Symbol: data.Symbol);
+ return ComInterfaceInfo.From(data.Symbol, data.Syntax, ct);
});
- context.RegisterDiagnostics(interfaceSymbolAndDiagnostics.Select((data, ct) => data.Diagnostic));
+ var interfaceSymbolsWithoutDiagnostics = context.FilterAndReportDiagnostics(interfaceSymbolOrDiagnostics);
- var interfaceSymbolsWithoutDiagnostics = interfaceSymbolAndDiagnostics
- .Where(data => data.Diagnostic is null)
- .Select((data, ct) => (data.InterfaceInfo, data.Symbol));
-
- var interfaceContextsAndDiagnostics = interfaceSymbolsWithoutDiagnostics
+ var interfaceContextsOrDiagnostics = interfaceSymbolsWithoutDiagnostics
.Select((data, ct) => data.InterfaceInfo!)
.Collect()
.SelectMany(ComInterfaceContext.GetContexts);
- context.RegisterDiagnostics(interfaceContextsAndDiagnostics.Select((data, ct) => data.Diagnostic));
- var interfaceContexts = interfaceContextsAndDiagnostics
- .Where(data => data.Context is not null)
- .Select((data, ct) => data.Context!);
+
// Filter down interface symbols to remove those with diagnostics from GetContexts
- interfaceSymbolsWithoutDiagnostics = interfaceSymbolsWithoutDiagnostics
- .Zip(interfaceContextsAndDiagnostics)
- .Where(data => data.Right.Diagnostic is null)
- .Select((data, ct) => data.Left);
-
- var comMethodsAndSymbolsAndDiagnostics = interfaceSymbolsWithoutDiagnostics.Select(ComMethodInfo.GetMethodsFromInterface);
- context.RegisterDiagnostics(comMethodsAndSymbolsAndDiagnostics.SelectMany(static (methodList, ct) => methodList.Select(m => m.Diagnostic)));
- var methodInfoAndSymbolGroupedByInterface = comMethodsAndSymbolsAndDiagnostics
- .Select(static (methods, ct) =>
- methods
- .Where(pair => pair.Diagnostic is null)
- .Select(pair => (pair.Symbol, pair.ComMethod))
- .ToSequenceEqualImmutableArray());
+ (var interfaceContexts, interfaceSymbolsWithoutDiagnostics) = context.FilterAndReportDiagnostics(interfaceContextsOrDiagnostics, interfaceSymbolsWithoutDiagnostics);
+
+ var comMethodsAndSymbolsOrDiagnostics = interfaceSymbolsWithoutDiagnostics.Select(ComMethodInfo.GetMethodsFromInterface);
+ var methodInfoAndSymbolGroupedByInterface = context
+ .FilterAndReportDiagnostics<(ComMethodInfo MethodInfo, IMethodSymbol Symbol)> (comMethodsAndSymbolsOrDiagnostics);
var methodInfosGroupedByInterface = methodInfoAndSymbolGroupedByInterface
.Select(static (methods, ct) =>
- methods.Select(pair => pair.ComMethod).ToSequenceEqualImmutableArray());
+ methods.Select(pair => pair.MethodInfo).ToSequenceEqualImmutableArray());
// Create list of methods (inherited and declared) and their owning interface
var comMethodContextBuilders = interfaceContexts
.Zip(methodInfosGroupedByInterface)
@@ -98,7 +82,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var methodInfoToSymbolMap = methodInfoAndSymbolGroupedByInterface
.SelectMany((data, ct) => data)
.Collect()
- .Select((data, ct) => data.ToDictionary(static x => x.ComMethod, static x => x.Symbol));
+ .Select((data, ct) => data.ToDictionary(static x => x.MethodInfo, static x => x.Symbol));
var comMethodContexts = comMethodContextBuilders
.Combine(methodInfoToSymbolMap)
.Combine(context.CreateStubEnvironmentProvider())
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs
index 533eb8c54bb8eb..31db875d638d48 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs
@@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -24,7 +25,7 @@ internal sealed record ComInterfaceInfo(
Guid InterfaceId,
LocationInfo DiagnosticLocation)
{
- public static (ComInterfaceInfo? Info, Diagnostic? Diagnostic) From(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax)
+ public static DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)> From(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax, CancellationToken _)
{
// Verify the method has no generic types or defined implementation
// and is not marked static or sealed
@@ -34,10 +35,11 @@ public static (ComInterfaceInfo? Info, Diagnostic? Diagnostic) From(INamedTypeSy
// and is not marked static or sealed
if (syntax.TypeParameterList is not null)
{
- return (null, Diagnostic.Create(
- GeneratorDiagnostics.InvalidAttributedInterfaceGenericNotSupported,
- syntax.Identifier.GetLocation(),
- symbol.Name));
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(
+ Diagnostic.Create(
+ GeneratorDiagnostics.InvalidAttributedInterfaceGenericNotSupported,
+ syntax.Identifier.GetLocation(),
+ symbol.Name));
}
}
@@ -46,25 +48,26 @@ public static (ComInterfaceInfo? Info, Diagnostic? Diagnostic) From(INamedTypeSy
{
if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
- return (null, Diagnostic.Create(
- GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers,
- syntax.Identifier.GetLocation(),
- symbol.Name,
- typeDecl.Identifier));
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(
+ Diagnostic.Create(
+ GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers,
+ syntax.Identifier.GetLocation(),
+ symbol.Name,
+ typeDecl.Identifier));
}
}
if (!TryGetGuid(symbol, syntax, out Guid? guid, out Diagnostic? guidDiagnostic))
- return (null, guidDiagnostic);
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(guidDiagnostic);
if (!TryGetBaseComInterface(symbol, syntax, out INamedTypeSymbol? baseSymbol, out Diagnostic? baseDiagnostic))
- return (null, baseDiagnostic);
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(baseDiagnostic);
if (!StringMarshallingIsValid(symbol, syntax, baseSymbol, out Diagnostic? stringMarshallingDiagnostic))
- return (null, stringMarshallingDiagnostic);
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(stringMarshallingDiagnostic);
- return (
- new ComInterfaceInfo(
+ return DiagnosticOr<(ComInterfaceInfo InterfaceInfo, INamedTypeSymbol Symbol)>.From(
+ (new ComInterfaceInfo(
ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol),
symbol.ToDisplayString(),
baseSymbol?.ToDisplayString(),
@@ -73,7 +76,7 @@ public static (ComInterfaceInfo? Info, Diagnostic? Diagnostic) From(INamedTypeSy
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
guid ?? Guid.Empty,
LocationInfo.From(symbol)),
- null);
+ symbol));
}
private static bool StringMarshallingIsValid(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax, INamedTypeSymbol? baseSymbol, [NotNullWhen(false)] out Diagnostic? stringMarshallingDiagnostic)
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs
index a2595736973333..4d220d29538549 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs
@@ -21,9 +21,9 @@ internal sealed record ComMethodInfo(
///
/// Returns a list of tuples of ComMethodInfo, IMethodSymbol, and Diagnostic. If ComMethodInfo is null, Diagnostic will not be null, and vice versa.
///
- public static SequenceEqualImmutableArray<(ComMethodInfo? ComMethod, IMethodSymbol Symbol, Diagnostic? Diagnostic)> GetMethodsFromInterface((ComInterfaceInfo ifaceContext, INamedTypeSymbol ifaceSymbol) data, CancellationToken ct)
+ public static SequenceEqualImmutableArray> GetMethodsFromInterface((ComInterfaceInfo ifaceContext, INamedTypeSymbol ifaceSymbol) data, CancellationToken ct)
{
- var methods = ImmutableArray.CreateBuilder<(ComMethodInfo, IMethodSymbol, Diagnostic?)>();
+ var methods = ImmutableArray.CreateBuilder>();
foreach (var member in data.ifaceSymbol.GetMembers())
{
if (IsComMethodCandidate(member))
@@ -59,7 +59,7 @@ private static bool IsComMethodCandidate(ISymbol member)
return member.Kind == SymbolKind.Method && !member.IsStatic;
}
- private static (ComMethodInfo?, IMethodSymbol, Diagnostic?) CalculateMethodInfo(ComInterfaceInfo ifaceContext, IMethodSymbol method, CancellationToken ct)
+ private static DiagnosticOr<(ComMethodInfo, IMethodSymbol)> CalculateMethodInfo(ComInterfaceInfo ifaceContext, IMethodSymbol method, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
Debug.Assert(IsComMethodCandidate(method));
@@ -82,7 +82,7 @@ private static (ComMethodInfo?, IMethodSymbol, Diagnostic?) CalculateMethodInfo(
if (methodLocationInAttributedInterfaceDeclaration is null)
{
- return (null, method, Diagnostic.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, method.Locations.FirstOrDefault(), method.ToDisplayString()));
+ return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(Diagnostic.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, method.Locations.FirstOrDefault(), method.ToDisplayString()));
}
@@ -100,16 +100,16 @@ private static (ComMethodInfo?, IMethodSymbol, Diagnostic?) CalculateMethodInfo(
}
if (comMethodDeclaringSyntax is null)
{
- return (null, method, Diagnostic.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString()));
+ return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(Diagnostic.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString()));
}
var diag = GetDiagnosticIfInvalidMethodForGeneration(comMethodDeclaringSyntax, method);
if (diag is not null)
{
- return (null, method, diag);
+ return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(diag);
}
var comMethodInfo = new ComMethodInfo(comMethodDeclaringSyntax, method.Name);
- return (comMethodInfo, method, null);
+ return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From((comMethodInfo, method));
}
}
}
diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf
index 1c0d66c7781aa4..d21ecbbeb15b2a 100644
--- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf
+++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf
@@ -39,37 +39,37 @@
Source-generated COM will ignore any configuration that is not supported.
- Zdrojem generovaná volání P/Invokes budou ignorovat všechny nepodporované konfigurace.
+ Model COM vygenerovaný zdrojem bude ignorovat všechny nepodporované konfigurace.
The '{0}' configuration is not supported by source-generated COM. If the specified configuration is required, use `ComImport` instead.
- Konfiguraci {0} nepodporují zdrojem generovaná volání P/Invokes. Pokud je určená konfigurace povinná, použijte místo ní normální DllImport.
+ Konfigurace „{0}“ není podporována modelem COM generovaným zdrojem. Pokud je zadaná konfigurace povinná, použijte místo toho „ComImport“.
The specified marshalling configuration is not supported by source-generated COM. {0}.
- Určenou konfiguraci zařazování nepodporují zdrojem generovaná volání P/Invokes. {0}.
+ Zadaná konfigurace zařazování není podporována modelem COM generovaným zdrojem. {0}.
The specified '{0}' configuration for parameter '{1}' is not supported by source-generated COM. If the specified configuration is required, use `ComImport` instead.
- Určená konfigurace {0} pro parametr {1} není podporována voláním P/Invokes. Pokud je určená konfigurace povinná, použijte místo ní normální DllImport.
+ Zadaná konfigurace „{0}“ pro parametr „{1}“ není podporována modelem COM generovaným zdrojem. Pokud je zadaná konfigurace povinná, použijte místo toho ComImport.
The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated COM. If the specified configuration is required, use `ComImport` instead.
- Určenou konfiguraci {0} návratové hodnoty metody {1} nepodporují zdrojem generovaná volání P/Invokes. Pokud je určená konfigurace povinná, použijte místo ní normální Dllimport.
+ Zadaná konfigurace „{0}“ pro návratovou hodnotu metody „{1}“ není podporována modelem COM generovaným zdrojem. Pokud je zadaná konfigurace povinná, použijte místo toho ComImport.
The specified value '{0}' for '{1}' is not supported by source-generated COM. If the specified configuration is required, use `ComImport` instead.
- Určená hodnota {0} pro {1} se nepodporuje u zdrojem generovaných volání P/Invokes. Pokud je určená konfigurace povinná, použijte místo ní normální Dllimport.
+ Zadaná hodnota „{0}“ pro „{1}“ není podporována modelem COM generovaným zdrojem. Pokud je zadaná konfigurace povinná, použijte místo toho ComImport.
Specified configuration is not supported by source-generated COM.
- Určenou konfiguraci nepodporují zdrojem generovaná volání P/Invokes.
+ Zadaná konfigurace není podporována modelem COM generovaným zdrojem.
@@ -89,7 +89,7 @@
Method '{0}' is contained in a type '{1}' that is not marked 'partial'. COM source generation will ignore method '{0}'.
- Metoda {0} je obsažena v typu {1}, který není označen jako „partial“. Generování zdrojů volání P/Invoke bude metodu {0} ignorovat.
+ Metoda „{0}“ je obsažena v typu „{1}“, který není označen jako „částečně provedený kód“. Generování zdroje modelu COM bude metodu „{0}“ ignorovat.
@@ -99,12 +99,12 @@
Methods on interfaces marked with 'GeneratedComInterfaceAttribute' should be non-generic. COM source generation will ignore methods that are generic.
- Metody označené atributem LibraryImportAttribute by měly být typu „static“, „partial“ a non-generic. Generování zdrojů volání P/Invoke bude ignorovat metody typu non-„static“, non-„partial“ a generic.
+ Metody na rozhraních označených atributem „GeneratedComInterfaceAttribute“ by neměly být obecné povahy. Generování zdroje modelu COM bude obecné metody ignorovat.
Method '{0}' should be non-generic when on interfaces marked with the 'GeneratedComInterfaceAttribute'. COM source generation will ignore method '{0}'.
- Metoda {0} by měla mít vlastnost „static“, „partial“ a non-generic, když je označena atributem LibraryImportAttribute. Generování zdroje voláním P/Invoke bude metodu {0} ignorovat.
+ Metoda „{0}“ by neměla být v rozhraních označených atributem GeneratedComInterfaceAttribute obecné povahy. Generování zdroje modelu COM bude metodu „{0}“ ignorovat.
@@ -134,12 +134,12 @@
Interfaces attributed with 'GeneratedComInterfaceAttribute' must be partial, non-generic, and must specify a GUID with 'System.Runtime.InteropServices.GuidAttribute'.
- Interfaces attributed with 'GeneratedComInterfaceAttribute' must be partial, non-generic, and must specify a GUID with 'System.Runtime.InteropServices.GuidAttribute'.
+ Rozhraní s atributem GeneratedComInterfaceAttribute musí být částečná, neobecná a musí určovat identifikátor GUID s atributem System.Runtime.InteropServices.GuidAttribute.
Interface '{0}' is attributed with 'GeneratedComInterfaceAttribute' but is generic.
- Interface '{0}' is attributed with 'GeneratedComInterfaceAttribute' but is generic.
+ Rozhraní „{0}“ má atribut GeneratedComInterfaceAttribute, ale je obecné.
@@ -174,7 +174,7 @@
The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1}
- The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1}
+ Konfigurace StringMarshalling a StringMarshallingCustomType u metody {0} je neplatná. {1}
{1} is a message containing additional details about what is not valid
@@ -214,12 +214,12 @@
For types that are not supported by source-generated COM, the resulting function pointer will rely on the underlying runtime to marshal the specified type.
- U typů, které nejsou podporovány zdrojem generovanými voláními P/Invoke, bude výsledné volání P/Invoke záviset na podkladovém modulu runtime, aby určený typ zařadil.
+ U typů, které nejsou podporovány modelem COM generovaným zdrojem, bude výsledný ukazatel funkce při zařazování zadaného typu spoléhat na základní modul runtime.
The type '{0}' is not supported by source-generated COM. The generated source will not handle marshalling of parameter '{1}'.
- Typ {0} nepodporují zdrojem generovaná volání P/Invokes. Vygenerovaný zdroj nebude zpracovávat zařazování parametru {1}.
+ Model COM vygenerovaný zdrojem nepodporuje typ „{0}“. Vygenerovaný zdroj nezpracuje zařazování parametru „{1}“.
@@ -230,7 +230,7 @@
The type '{0}' is not supported by source-generated COM. The generated source will not handle marshalling of the return value of method '{1}'.
- Typ {0} nepodporují zdrojem generovaná volání P/Invokes. Vygenerovaný zdroj nebude zpracovávat zařazování návratové hodnoty metody {1}.
+ Model COM vygenerovaný zdrojem nepodporuje typ „{0}“. Vygenerovaný zdroj nebude zpracovávat zařazování návratové hodnoty metody „{1}“.
@@ -241,7 +241,7 @@
Specified type is not supported by source-generated COM
- Určený typ nepodporují zdrojem generovaná volání P/Invokes.
+ Zadaný typ není podporován modelem COM generovaným zdrojem.