diff --git a/src/Pure.DI.Core/Core/Code/BlockCodeBuilder.cs b/src/Pure.DI.Core/Core/Code/BlockCodeBuilder.cs index ea4cb6425..9afa5bdf0 100644 --- a/src/Pure.DI.Core/Core/Code/BlockCodeBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/BlockCodeBuilder.cs @@ -132,7 +132,31 @@ variable.Node.Lifetime is Lifetime.Singleton or Lifetime.Scoped finally { info.HasCode = true; - ctx.Code.AppendLines(info.Code.Lines); + // ctx.Code.AppendLines(info.Code.Lines); + if (block.Parent is not null + && info is { PerBlockRefCount: > 2, Code.Lines.Count: > 3 }) + { + var localFunctionsCode = ctx.LocalFunctionsCode; + var localMethodName = $"{Names.LocalMethodPrefix}{variable.VariableName}{Names.EnsureExistsMethodNamePostfix}"; + if (variable.Node.Binding.SemanticModel.Compilation.GetLanguageVersion() >= LanguageVersion.CSharp9) + { + localFunctionsCode.AppendLine($"[{Names.MethodImplAttribute}(({Names.MethodImplOptions})0x100)]"); + } + + localFunctionsCode.AppendLine($"void {localMethodName}()"); + localFunctionsCode.AppendLine("{"); + using (localFunctionsCode.Indent()) + { + localFunctionsCode.AppendLines(info.Code.Lines); + } + + localFunctionsCode.AppendLine("}"); + ctx.Code.AppendLine($"{localMethodName}();"); + } + else + { + ctx.Code.AppendLines(info.Code.Lines); + } } } diff --git a/src/Pure.DI.Core/Core/Code/BuildContext.cs b/src/Pure.DI.Core/Core/Code/BuildContext.cs index 8203c8d6f..24e66bd70 100644 --- a/src/Pure.DI.Core/Core/Code/BuildContext.cs +++ b/src/Pure.DI.Core/Core/Code/BuildContext.cs @@ -7,6 +7,7 @@ internal record BuildContext( DependencyGraph DependencyGraph, Variable Variable, LinesBuilder Code, + LinesBuilder LocalFunctionsCode, object? ContextTag, bool? LockIsRequired, ImmutableArray Accumulators); \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs index 2ec175ab8..d9baade08 100644 --- a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs @@ -31,6 +31,7 @@ public CompositionCode Build(DependencyGraph graph) graph, rootBlock.Current, new LinesBuilder(), + new LinesBuilder(), root.Injection.Tag != MdTag.ContextTag ? root.Injection.Tag : default, default, root.Node.Accumulators.ToImmutableArray()); @@ -46,6 +47,7 @@ public CompositionCode Build(DependencyGraph graph) blockBuilder.Build(ctx, rootBlock); ctx.Code.AppendLine($"return {buildTools.OnInjected(ctx, rootBlock.Current)};"); + ctx.Code.AppendLines(ctx.LocalFunctionsCode.Lines); var args = GetRootArgs(map.Values).ToImmutableArray(); var processedRoot = root with diff --git a/src/Pure.DI.Core/Core/Code/VariableInfo.cs b/src/Pure.DI.Core/Core/Code/VariableInfo.cs index f31c86105..beb889b25 100644 --- a/src/Pure.DI.Core/Core/Code/VariableInfo.cs +++ b/src/Pure.DI.Core/Core/Code/VariableInfo.cs @@ -2,6 +2,7 @@ internal class VariableInfo { + private readonly HashSet _perBlockRefCounts = []; public readonly HashSet Owners = []; public bool IsCreated; public bool HasCode; @@ -9,13 +10,17 @@ internal class VariableInfo public int RefCount { get; private set; } = 1; - public void AddRef() + public int PerBlockRefCount => _perBlockRefCounts.Count; + + public void AddRef(Block parentBlock) { RefCount++; + _perBlockRefCounts.Add(parentBlock.Id); } public void Reset() { + _perBlockRefCounts.Clear(); Owners.Clear(); RefCount = 1; IsCreated = false; diff --git a/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs b/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs index c216dbaed..fdc248e13 100644 --- a/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/VariablesBuilder.cs @@ -23,13 +23,17 @@ public Block Build( var counter = 0; while (blocks.TryPop(out var currentBlock)) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + var stack = new Stack(currentBlock.Statements); while (stack.TryPop(out var currentStatement)) { - cancellationToken.ThrowIfCancellationRequested(); if (counter++ > Const.MaxIterationsCount) { - throw new CompileErrorException("Cyclic dependency has been found.", rootNode.Binding.Source.GetLocation(), LogId.ErrorCyclicDependency); + throw new CompileErrorException($"The composition is too large. Stopped on the #{counter} instance.", rootNode.Binding.Source.GetLocation(), LogId.ErrorInvalidMetadata); } switch (currentStatement) @@ -219,7 +223,7 @@ private Variable GetVariable( if (map.TryGetValue(node.Binding, out var variable)) { - variable.Info.AddRef(); + variable.Info.AddRef(parentBlock); return variable with { Parent = parentBlock, diff --git a/src/Pure.DI.Core/Core/CodeBuilder.cs b/src/Pure.DI.Core/Core/CodeBuilder.cs index ffb27ec20..2caaf6b8e 100644 --- a/src/Pure.DI.Core/Core/CodeBuilder.cs +++ b/src/Pure.DI.Core/Core/CodeBuilder.cs @@ -55,14 +55,10 @@ public Unit Build(MdSetup setup) composition = classBuilder.Build(composition); cancellationToken.ThrowIfCancellationRequested(); - var code = new StringBuilder(composition.Code.Sum(i => i.Length + 2)); - foreach (var line in composition.Code) - { - code.AppendLine(line); - } - + var size = composition.Code.SaveToArray(Encoding.UTF8, out var buffer); + cancellationToken.ThrowIfCancellationRequested(); - sources.AddSource($"{setup.Name.FullName}.g.cs", SourceText.From(code.ToString(), Encoding.UTF8)); + sources.AddSource($"{setup.Name.FullName}.g.cs", SourceText.From(buffer, size, Encoding.UTF8, SourceHashAlgorithm.Sha1, false, true)); return Unit.Shared; } } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Const.cs b/src/Pure.DI.Core/Core/Const.cs index 022c58964..9d8ed547c 100644 --- a/src/Pure.DI.Core/Core/Const.cs +++ b/src/Pure.DI.Core/Core/Const.cs @@ -2,5 +2,5 @@ public static class Const { - public const int MaxIterationsCount = 0xffff; + public const int MaxIterationsCount = 0x8FFFF; } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs index 6735295f6..ecfeba0cc 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs @@ -75,7 +75,7 @@ public IEnumerable TryBuild( cancellationToken.ThrowIfCancellationRequested(); if (counter++ > Const.MaxIterationsCount) { - throw new CompileErrorException("Cyclic dependency has been found.", setup.Source.GetLocation(), LogId.ErrorCyclicDependency); + throw new CompileErrorException($"The composition is too large. Stopped on the #{counter} dependency.", setup.Source.GetLocation(), LogId.ErrorInvalidMetadata); } var targetNode = node.Node; diff --git a/src/Pure.DI.Core/Core/LinesBuilder.cs b/src/Pure.DI.Core/Core/LinesBuilder.cs index 131da16a3..29b19325d 100644 --- a/src/Pure.DI.Core/Core/LinesBuilder.cs +++ b/src/Pure.DI.Core/Core/LinesBuilder.cs @@ -2,6 +2,8 @@ // ReSharper disable ReturnTypeCanBeEnumerable.Global namespace Pure.DI.Core; +using System.Buffers; + internal sealed class LinesBuilder: IEnumerable { private static readonly string[] Indents = new string[64]; @@ -87,6 +89,31 @@ public IEnumerator GetEnumerator() return _lines.Select(i => $"{GetIndent(i.Indent)}{i.Text}").GetEnumerator(); } + public int SaveToArray(Encoding encoding, out byte[] buffer) + { + var charCount = 0; + var newLine = Environment.NewLine; + foreach (var line in _lines) + { + charCount += GetIndent(line.Indent).Length; + charCount += line.Text.Length; + charCount += newLine.Length; + } + + var size = encoding.GetMaxByteCount(charCount); + buffer = ArrayPool.Shared.Rent(size); + var position = 0; + foreach (var line in _lines) + { + var indent = GetIndent(line.Indent); + position += encoding.GetBytes(indent, 0, indent.Length, buffer, position); + position += encoding.GetBytes(line.Text, 0, line.Text.Length, buffer, position); + position += encoding.GetBytes(newLine, 0, newLine.Length, buffer, position); + } + + return position; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private void FlushLines() diff --git a/src/Pure.DI.Core/Core/Names.cs b/src/Pure.DI.Core/Core/Names.cs index 452e975c3..71bd50881 100644 --- a/src/Pure.DI.Core/Core/Names.cs +++ b/src/Pure.DI.Core/Core/Names.cs @@ -46,6 +46,7 @@ internal static class Names // Local methods public const string LocalMethodPrefix = "Local"; + public const string EnsureExistsMethodNamePostfix = "EnsureExists"; // Fields public static readonly string BucketsFieldName = $"_buckets{Salt}";