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..367211f43 100644 --- a/src/Pure.DI.Core/Core/CodeBuilder.cs +++ b/src/Pure.DI.Core/Core/CodeBuilder.cs @@ -55,14 +55,8 @@ 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); - } - - cancellationToken.ThrowIfCancellationRequested(); - sources.AddSource($"{setup.Name.FullName}.g.cs", SourceText.From(code.ToString(), Encoding.UTF8)); + using var rent = composition.Code.SaveToArray(Encoding.UTF8, out var buffer, out var size); + 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/Formatting.cs b/src/Pure.DI.Core/Core/Formatting.cs index 96ebf9f5f..de1f3a07b 100644 --- a/src/Pure.DI.Core/Core/Formatting.cs +++ b/src/Pure.DI.Core/Core/Formatting.cs @@ -3,8 +3,8 @@ namespace Pure.DI.Core; internal static class Formatting { - public const int IndentSize = 2; - private const int IndentsCount = 5; + public const int IndentSize = 1; + private const int IndentsCount = 256; private static readonly string[] Indents; static Formatting() @@ -26,5 +26,6 @@ public static string IndentPrefix(Indent indent) => _ => IndentInternal(indent.Value) }; - private static string IndentInternal(int count = 1) => new(' ', count * IndentSize); + private static string IndentInternal(int count = 1) => + string.Intern(new string('\t', count * IndentSize)); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Indent.cs b/src/Pure.DI.Core/Core/Indent.cs index 1ace4c8ba..51c1ac752 100644 --- a/src/Pure.DI.Core/Core/Indent.cs +++ b/src/Pure.DI.Core/Core/Indent.cs @@ -1,18 +1,10 @@ namespace Pure.DI.Core; -internal sealed class Indent +internal readonly struct Indent(int value) { - private readonly string _str; - - public Indent(int value) - { - Value = value; - _str = Formatting.IndentPrefix(this); - } - - public int Value { get; set; } + public int Value { get; } = value; public static implicit operator Indent(int value) => new(value); - public override string ToString() => _str; + public override string ToString() => Formatting.IndentPrefix(this); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/LinesBuilder.cs b/src/Pure.DI.Core/Core/LinesBuilder.cs index 131da16a3..203d9baa0 100644 --- a/src/Pure.DI.Core/Core/LinesBuilder.cs +++ b/src/Pure.DI.Core/Core/LinesBuilder.cs @@ -2,20 +2,13 @@ // ReSharper disable ReturnTypeCanBeEnumerable.Global namespace Pure.DI.Core; +using System.Buffers; + internal sealed class LinesBuilder: IEnumerable { - private static readonly string[] Indents = new string[64]; private readonly StringBuilder _sb = new(); private readonly List _lines = []; - private readonly Indent _indent; - - static LinesBuilder() - { - for (var i = 0; i < Indents.Length; i++) - { - Indents[i] = new Indent(i).ToString(); - } - } + private Indent _indent; public LinesBuilder(Indent indent) => _indent = new Indent(indent.Value - 1); @@ -72,13 +65,13 @@ public IDisposable Indent(int value = 1) public void IncIndent(int value = 1) { FlushLines(); - _indent.Value += value; + _indent = new Indent(_indent.Value + 1); } public void DecIndent(int value = 1) { FlushLines(); - _indent.Value -= value; + _indent = new Indent(_indent.Value - 1); } public IEnumerator GetEnumerator() @@ -87,28 +80,43 @@ public IEnumerator GetEnumerator() return _lines.Select(i => $"{GetIndent(i.Indent)}{i.Text}").GetEnumerator(); } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private void FlushLines() + public IDisposable SaveToArray(Encoding encoding, out byte[] buffer, out int size) { - if (_sb.Length > 0) + var charCount = 0; + var newLine = Environment.NewLine; + foreach (var line in _lines) { - AppendLine(); + charCount += GetIndent(line.Indent).Length; + charCount += line.Text.Length; + charCount += newLine.Length; } - } - private static string GetIndent(int indent) - { - if (indent < 1) + size = encoding.GetMaxByteCount(charCount); + var rent = ArrayPool.Shared.Rent(size); + buffer = rent; + var position = 0; + foreach (var line in _lines) { - return string.Empty; + 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); } + + size = position; + return Disposables.Create(() => ArrayPool.Shared.Return(rent)); + } - if (indent < Indents.Length) + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private void FlushLines() + { + if (_sb.Length > 0) { - return Indents[indent]; + AppendLine(); } - - return new Indent(indent).ToString(); } + + private static string GetIndent(int indent) => + new Indent(indent).ToString(); } \ No newline at end of file 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}";