static bool InlineAllInBlock(ILFunction function, Block block, ILTransformContext context, ref int?ctorCallStart) { bool modified = false; var instructions = block.Instructions; for (int i = 0; i < instructions.Count;) { if (instructions[i] is StLoc inst) { InliningOptions options = InliningOptions.None; if (IsCatchWhenBlock(block) || IsInConstructorInitializer(function, inst, ref ctorCallStart)) { options = InliningOptions.Aggressive; } if (InlineOneIfPossible(block, i, options, context)) { modified = true; i = Math.Max(0, i - 1); // Go back one step continue; } } i++; } return(modified); }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction, if possible. /// </summary> public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context) { context.CancellationToken.ThrowIfCancellationRequested(); StLoc stloc = block.Instructions[pos] as StLoc; if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal) { return(false); } ILVariable v = stloc.Variable; // ensure the variable is accessed only a single time if (v.StoreCount != 1) { return(false); } if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1) { return(false); } // TODO: inlining of small integer types might be semantically incorrect, // but we can't avoid it this easily without breaking lots of tests. //if (v.Type.IsSmallIntegerType()) // return false; // stloc might perform implicit truncation return(InlineOne(stloc, options, context)); }
public void TracingDisabled_DoesNotSubmitsTraces(InliningOptions inlining, InstrumentationOptions instrumentation, bool enableSocketsHandler) { ConfigureInstrumentation(inlining, instrumentation, enableSocketsHandler); const string expectedOperationName = "http.request"; int agentPort = TcpPortProvider.GetOpenPort(); int httpPort = TcpPortProvider.GetOpenPort(); using (var agent = new MockTracerAgent(agentPort)) using (ProcessResult processResult = RunSampleAndWaitForExit(agent.Port, arguments: $"TracingDisabled Port={httpPort}")) { Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); var spans = agent.WaitForSpans(1, 2000, operationName: expectedOperationName); Assert.Equal(0, spans.Count); var traceId = StringUtil.GetHeader(processResult.StandardOutput, HttpHeaderNames.TraceId); var parentSpanId = StringUtil.GetHeader(processResult.StandardOutput, HttpHeaderNames.ParentId); var tracingEnabled = StringUtil.GetHeader(processResult.StandardOutput, HttpHeaderNames.TracingEnabled); Assert.Null(traceId); Assert.Null(parentSpanId); Assert.Equal("false", tracingEnabled); } }
internal static InliningOptions OptionsForBlock(Block block) { InliningOptions options = InliningOptions.None; if (IsCatchWhenBlock(block)) { options |= InliningOptions.Aggressive; } return(options); }
public void HttpClient_SubmitsTraces(InliningOptions inlining, InstrumentationOptions instrumentation, bool enableSocketsHandler) { ConfigureInstrumentation(inlining, instrumentation, enableSocketsHandler); var expectedAsyncCount = CalculateExpectedAsyncSpans(instrumentation, inlining.EnableCallTarget); var expectedSyncCount = CalculateExpectedSyncSpans(instrumentation); var expectedSpanCount = expectedAsyncCount + expectedSyncCount; const string expectedOperationName = "http.request"; const string expectedServiceName = "Samples.HttpMessageHandler-http-client"; int agentPort = TcpPortProvider.GetOpenPort(); int httpPort = TcpPortProvider.GetOpenPort(); Output.WriteLine($"Assigning port {agentPort} for the agentPort."); Output.WriteLine($"Assigning port {httpPort} for the httpPort."); using (var agent = new MockTracerAgent(agentPort)) using (ProcessResult processResult = RunSampleAndWaitForExit(agent.Port, arguments: $"Port={httpPort}")) { Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); var spans = agent.WaitForSpans(expectedSpanCount, operationName: expectedOperationName); Assert.Equal(expectedSpanCount, spans.Count); foreach (var span in spans) { Assert.Equal(expectedOperationName, span.Name); Assert.Equal(expectedServiceName, span.Service); Assert.Equal(SpanTypes.Http, span.Type); Assert.Equal("HttpMessageHandler", span.Tags[Tags.InstrumentationName]); Assert.False(span.Tags?.ContainsKey(Tags.Version), "External service span should not have service version tag."); if (span.Tags[Tags.HttpStatusCode] == "502") { Assert.Equal(1, span.Error); } } var firstSpan = spans.First(); var traceId = StringUtil.GetHeader(processResult.StandardOutput, HttpHeaderNames.TraceId); var parentSpanId = StringUtil.GetHeader(processResult.StandardOutput, HttpHeaderNames.ParentId); Assert.Equal(firstSpan.TraceId.ToString(CultureInfo.InvariantCulture), traceId); Assert.Equal(firstSpan.SpanId.ToString(CultureInfo.InvariantCulture), parentSpanId); } }
private void ConfigureInstrumentation(InliningOptions inlining, InstrumentationOptions instrumentation, bool enableSocketsHandler) { SetCallTargetSettings(inlining.EnableCallTarget, inlining.EnableInlining); // Should HttpClient try to use HttpSocketsHandler SetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER", enableSocketsHandler ? "1" : "0"); // Enable specific integrations, or use defaults if (instrumentation.InstrumentSocketHandler.HasValue) { SetEnvironmentVariable("DD_HttpSocketsHandler_ENABLED", instrumentation.InstrumentSocketHandler.Value ? "true" : "false"); } if (instrumentation.InstrumentWinHttpHandler.HasValue) { SetEnvironmentVariable("DD_WinHttpHandler_ENABLED", instrumentation.InstrumentWinHttpHandler.Value ? "true" : "false"); } }
internal static InliningOptions OptionsForBlock(Block block, int pos) { InliningOptions options = InliningOptions.None; if (IsCatchWhenBlock(block)) { options |= InliningOptions.Aggressive; } else { var function = block.Ancestors.OfType <ILFunction>().FirstOrDefault(); var inst = block.Instructions[pos]; if (IsInConstructorInitializer(function, inst)) { options |= InliningOptions.Aggressive; } } return(options); }
/// <summary> /// Inlines instructions before pos into block.Instructions[pos]. /// </summary> /// <returns>The number of instructions that were inlined.</returns> public static int InlineInto(Block block, int pos, InliningOptions options, ILTransformContext context) { if (pos >= block.Instructions.Count) { return(0); } int count = 0; while (--pos >= 0) { if (InlineOneIfPossible(block, pos, options, context)) { count++; } else { break; } } return(count); }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> public static bool InlineOne(StLoc stloc, InliningOptions options, ILTransformContext context) { ILVariable v = stloc.Variable; Block block = (Block)stloc.Parent; int pos = stloc.ChildIndex; if (DoInline(v, stloc.Value, block.Instructions.ElementAtOrDefault(pos + 1), options, context)) { // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc instruction: Debug.Assert(block.Instructions[pos] == stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.LoadCount == 0 && v.AddressCount == 0) { // The variable is never loaded if (SemanticHelper.IsPure(stloc.Value.Flags)) { // Remove completely if the instruction has no effects // (except for reading locals) context.Step("Remove dead store without side effects", stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.Kind == VariableKind.StackSlot) { context.Step("Remove dead store, but keep expression", stloc); // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc, but keep the inner expression stloc.ReplaceWith(stloc.Value); return(true); } } return(false); }
public static bool InlineAllInBlock(ILFunction function, Block block, ILTransformContext context) { bool modified = false; var instructions = block.Instructions; for (int i = instructions.Count - 1; i >= 0; i--) { if (instructions[i] is StLoc inst) { InliningOptions options = InliningOptions.None; if (context.Settings.AggressiveInlining || IsCatchWhenBlock(block) || IsInConstructorInitializer(function, inst)) { options = InliningOptions.Aggressive; } if (InlineOneIfPossible(block, i, options, context)) { modified = true; continue; } } } return(modified); }
internal static InliningOptions OptionsForBlock(Block block, int pos, ILTransformContext context) { InliningOptions options = InliningOptions.None; if (context.Settings.AggressiveInlining || IsCatchWhenBlock(block)) { options |= InliningOptions.Aggressive; } else { var function = block.Ancestors.OfType <ILFunction>().FirstOrDefault(); var inst = block.Instructions[pos]; if (IsInConstructorInitializer(function, inst)) { options |= InliningOptions.Aggressive; } } if (!context.Settings.UseRefLocalsForAccurateOrderOfEvaluation) { options |= InliningOptions.AllowChangingOrderOfEvaluationForExceptions; } return(options); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) { if ((options & InliningOptions.AllowChangingOrderOfEvaluationForExceptions) != 0) { // Intentionally change code semantics so that we can avoid a ref local if (expressionBeingMoved is LdFlda ldflda) { ldflda.DelayExceptions = true; } else if (expressionBeingMoved is LdElema ldelema) { ldelema.DelayExceptions = true; } } else { // special case: the StObj.TargetSlot does not accept some kinds of expressions return(FindResult.Stop); } } return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ControlFlow when block.Parent is BlockContainer: case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (options.HasFlag(InliningOptions.FindDeconstruction) && expr is DeconstructInstruction di) { return(FindResult.Deconstruction(di)); } foreach (var child in expr.Children) { if (!expr.CanInlineIntoSlot(child.ChildIndex, expressionBeingMoved)) { return(FindResult.Stop); } // Recursively try to find the load instruction FindResult r = FindLoadInNext(child, v, expressionBeingMoved, options); if (r.Type != FindResultType.Continue) { if (r.Type == FindResultType.Stop && (options & InliningOptions.IntroduceNamedArguments) != 0 && expr is CallInstruction call) { return(NamedArgumentTransform.CanIntroduceNamedArgument(call, child, v, expressionBeingMoved)); } return(r); } } if (IsSafeForInlineOver(expr, expressionBeingMoved)) { return(FindResult.Continue); // continue searching } else { return(FindResult.Stop); // abort, inlining not possible } }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, InliningOptions options, ILTransformContext context) { var r = FindLoadInNext(next, v, inlinedExpression, options); if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument) { var loadInst = r.LoadInst; if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedValueTypeTemporary((LdLoca)loadInst, v, inlinedExpression)) { return(false); } } else { Debug.Assert(loadInst.OpCode == OpCode.LdLoc); bool aggressive = (options & InliningOptions.Aggressive) != 0; if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, r, inlinedExpression, v)) { return(false); } } if (r.Type == FindResultType.NamedArgument) { NamedArgumentTransform.IntroduceNamedArgument(r.CallArgument, context); // Now that the argument is evaluated early, we can inline as usual } context.Step($"Inline variable '{v.Name}'", inlinedExpression); // Assign the ranges of the ldloc instruction: inlinedExpression.AddILRange(loadInst); if (loadInst.OpCode == OpCode.LdLoca) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' // to preserve the semantics of the compiler-generated temporary Debug.Assert(((LdLoca)loadInst).Variable == v); loadInst.ReplaceWith(new AddressOf(inlinedExpression, v.Type)); } else { loadInst.ReplaceWith(inlinedExpression); } return(true); } return(false); }
internal static bool DoInline(ILVariable v, StLoc originalStore, LdLoc loadInst, InliningOptions options, ILTransformContext context) { if ((options & InliningOptions.Aggressive) == 0 && originalStore.ILStackWasEmpty) { return(false); } context.Step($"Introduce named argument '{v.Name}'", originalStore); var call = (CallInstruction)loadInst.Parent; if (!(call.Parent is Block namedArgBlock) || namedArgBlock.Kind != BlockKind.CallWithNamedArgs) { // create namedArgBlock: namedArgBlock = new Block(BlockKind.CallWithNamedArgs); call.ReplaceWith(namedArgBlock); namedArgBlock.FinalInstruction = call; if (call.IsInstanceCall) { IType thisVarType = call.Method.DeclaringType; if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref) { thisVarType = new ByReferenceType(thisVarType); } var function = call.Ancestors.OfType <ILFunction>().First(); var thisArgVar = function.RegisterVariable(VariableKind.NamedArgument, thisVarType, "this_arg"); namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0])); call.Arguments[0] = new LdLoc(thisArgVar); } } v.Kind = VariableKind.NamedArgument; namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, originalStore); return(true); }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, InliningOptions options, ILTransformContext context) { var r = FindLoadInNext(next, v, inlinedExpression, out var loadInst); if (r == FindResult.Found) { if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedValueTypeTemporary(next, (LdLoca)loadInst, v, inlinedExpression)) { return(false); } } else { Debug.Assert(loadInst.OpCode == OpCode.LdLoc); bool aggressive = (options & InliningOptions.Aggressive) != 0; if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, loadInst, inlinedExpression, v)) { return(false); } } context.Step($"Inline variable '{v.Name}'", inlinedExpression); // Assign the ranges of the ldloc instruction: inlinedExpression.AddILRange(loadInst.ILRange); if (loadInst.OpCode == OpCode.LdLoca) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' // to preserve the semantics of the compiler-generated temporary loadInst.ReplaceWith(new AddressOf(inlinedExpression)); } else { loadInst.ReplaceWith(inlinedExpression); } return(true); } else if (r == FindResult.NamedArgument && (options & InliningOptions.IntroduceNamedArguments) != 0) { return(NamedArgumentTransform.DoInline(v, (StLoc)inlinedExpression.Parent, (LdLoc)loadInst, options, context)); } return(false); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) { // Possibly a switch-container, allow inlining into the switch instruction: return(NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the blockcontainer. }
/// <summary> /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values /// </summary> /// <param name="loadInst">The load instruction (a descendant within 'next')</param> /// <param name="v">The variable being inlined.</param> static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression, InliningOptions options) { Debug.Assert(loadInst.Variable == v); // Inlining a value type variable is allowed only if the resulting code will maintain the semantics // that the method is operating on a copy. // Thus, we have to ensure we're operating on an r-value. // Additionally, we cannot inline in cases where the C# compiler prohibits the direct use // of the rvalue (e.g. M(ref (MyStruct)obj); is invalid). if (IsUsedAsThisPointerInCall(loadInst, out var method)) { if (options.HasFlag(InliningOptions.Aggressive)) { // Inlining might be required in ctor initializers (see #2714). // expressionBuilder.VisitAddressOf will handle creating the copy for us. return(true); } switch (ClassifyExpression(inlinedExpression)) { case ExpressionClassification.RValue: // For struct method calls on rvalues, the C# compiler always generates temporaries. return(true); case ExpressionClassification.MutableLValue: // For struct method calls on mutable lvalues, the C# compiler never generates temporaries. return(false); case ExpressionClassification.ReadonlyLValue: // For struct method calls on readonly lvalues, the C# compiler // only generates a temporary if it isn't a "readonly struct" return(MethodRequiresCopyForReadonlyLValue(method)); default: throw new InvalidOperationException("invalid expression classification"); } } else if (IsUsedAsThisPointerInFieldRead(loadInst)) { // mcs generated temporaries for field reads on rvalues (#1555) return(ClassifyExpression(inlinedExpression) == ExpressionClassification.RValue); } else { return(false); } }