diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 88779da..f06fff6 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -85,7 +85,25 @@
"Bash(git -C H:/Applications/Aycode/Source/AyCode.Core log -p --all -S \"MaxDepth\" -- AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs)",
"Bash(git -C H:/Applications/Aycode/Source/AyCode.Core show ac6e66f^:AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs)",
"Bash(ls -la \"H:\\\\Applications\\\\Aycode\\\\Source\\\\\" 2>&1 && echo \"---\" && ls -la \"H:\\\\Applications\\\\Aycode\\\\\" 2>&1)",
- "Bash(dotnet publish *)"
+ "Bash(dotnet publish *)",
+ "Bash(head -n 2998 AcBinarySourceGenerator.cs)",
+ "Bash(echo \"\")",
+ "Bash(mv AcBinarySourceGenerator.cs.tmp AcBinarySourceGenerator.cs)",
+ "Read(//h/h/Applications/Aycode/Source/AyCode.Core/AyCode.Core.Serializers.SourceGenerator/**)",
+ "Bash(head -n 2644 AcBinarySourceGenerator.cs)",
+ "Bash(cat)",
+ "Bash(mv tmp.cs AcBinarySourceGenerator.cs)",
+ "Bash(awk 'NR < 407 || NR > 533 { print }' AcBinarySourceGenerator.cs)",
+ "Bash(awk 'NR == 406 { print; print \"\"; print \" // DetectAndReportCycles + DetectAndReportPolymorphicMisuse + ShortTypeName moved to\"; print \" // AcBinarySourceGenerator.Diagnostics.cs.\"; next } { print }' tmp.cs)",
+ "Bash(rm tmp.cs)",
+ "Bash(cat /tmp/getclassinfo-body.txt)",
+ "Bash(echo \"}\")",
+ "Bash(awk 'NR < 44 || NR > 387 { print }' AcBinarySourceGenerator.cs)",
+ "Bash(awk 'NR == 43 { print; print \"\"; print \" // GetClassInfo extraction pass moved to AcBinarySourceGenerator.GetClassInfo.cs.\"; next } { print }' tmp.cs)",
+ "Bash(awk 'NR < 69 || NR > 1257 { print }' AcBinarySourceGenerator.cs)",
+ "Bash(awk 'NR == 68 { print; print \"\"; print \" // Writer-side emit pass \\(GenWriter + GenScanProperties + EmitProp + EmitScan* + EmitDirect*Write +\"; print \" // EmitSkip + EmitVal + EmitMarkerless + helpers\\) moved to AcBinarySourceGenerator.GenWriter.cs.\"; next } { print }' tmp.cs)",
+ "Bash(awk 'NR < 73 || NR > 930 { print }' AcBinarySourceGenerator.cs)",
+ "Bash(awk 'NR == 72 { print; print \" // Reader-side emit pass \\(GenReader + EmitReadProp + EmitRead* helpers\\) moved to\"; print \" // AcBinarySourceGenerator.GenReader.cs.\"; next } { print }' tmp.cs)"
]
}
}
diff --git a/.refactor-snapshot/.gitignore b/.refactor-snapshot/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/.refactor-snapshot/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.Diagnostics.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.Diagnostics.cs
new file mode 100644
index 0000000..c61b0f2
--- /dev/null
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.Diagnostics.cs
@@ -0,0 +1,182 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace AyCode.Core.Serializers.SourceGenerator;
+
+///
+/// Build-time diagnostics for the AcBinary source generator.
+///
+/// Registered diagnostics:
+///
+/// - ACBIN001 — : detects circular type references
+/// among [AcBinarySerializable] types and warns the developer to consider ref-handling mode.
+/// - ACBIN002 — : ACCORE-BIN-I-T7K3
+/// compile-time guard. Fires when a type opts out of EnablePolymorphDetectFeature AND still
+/// declares an object property — the SGen-emitted writer would silently corrupt the wire.
+///
+///
+public partial class AcBinarySourceGenerator
+{
+ private static readonly DiagnosticDescriptor CircularReferenceWarning = new(
+ id: "ACBIN001",
+ title: "Circular reference detected",
+ messageFormat: "Type '{0}' participates in a circular reference chain: {1}. Consider using ReferenceHandling.OnlyId or .All to avoid exponential serialization size.",
+ category: "AcBinarySerializer",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ ///
+ /// ACCORE-BIN-I-T7K3 compile-time guard: a property declared as System.Object requires
+ /// polymorphic-prefix emit (ObjectWithTypeName) so the deserializer can resolve the
+ /// concrete runtime type. When the type opts out of the feature via
+ /// [AcBinarySerializable(enablePolymorphDetectFeature: false)], the prefix is suppressed
+ /// and the wire silently corrupts on round-trip (FixObj slot byte against typeof(object)
+ /// at read-time → 0-byte object wrapper → reader position drifts → downstream
+ /// DECIMAL_DRIFT / IndexOutOfRangeException).
+ ///
+ /// Surface the misconfiguration at build time so the silent corruption is structurally
+ /// impossible. Three escape hatches for the developer:
+ /// 1. Enable the polymorph-detect feature on the type
+ /// ([AcBinarySerializable(...enablePolymorphDetectFeature: true)] — default true).
+ /// 2. Change the property type to a concrete type (no polymorphism needed).
+ /// 3. Mark the property with [AcBinaryIgnore] — ignored properties are filtered out
+ /// at property enumeration, so this diagnostic does not fire for them.
+ ///
+ private static readonly DiagnosticDescriptor PolymorphicPropertyWithFeatureDisabledError = new(
+ id: "ACBIN002",
+ title: "Polymorphic property requires EnablePolymorphDetectFeature",
+ messageFormat: "Type '{0}' contains property '{1}' declared as System.Object, but EnablePolymorphDetectFeature is disabled on the type. " +
+ "The generated writer would silently corrupt the wire on round-trip. " +
+ "To fix: (1) enable EnablePolymorphDetectFeature on [AcBinarySerializable], (2) change '{1}' to a concrete type, or (3) exclude it with [AcBinaryIgnore].",
+ category: "AcBinarySerializer",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ ///
+ /// ACCORE-BIN-I-T7K3 guard: emits
+ /// (ACBIN002) for every System.Object-declared property on any
+ /// [AcBinarySerializable] type whose EnablePolymorphDetectFeature is false.
+ /// Per-class gating: types with the feature enabled (default) skip the check entirely; only
+ /// opt-out types are scanned for misuse.
+ ///
+ private static void DetectAndReportPolymorphicMisuse(List classes, SourceProductionContext spc)
+ {
+ foreach (var ci in classes)
+ {
+ if (ci.EnablePolymorphDetect) continue; // Feature enabled → polymorphic prefix is emitted, no misuse possible.
+
+ foreach (var p in ci.Properties)
+ {
+ if (p.IsObjectDeclaredType)
+ {
+ spc.ReportDiagnostic(Diagnostic.Create(
+ PolymorphicPropertyWithFeatureDisabledError, Location.None,
+ ci.ClassName, p.Name));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Detects circular reference chains among [AcBinarySerializable] types at compile time
+ /// and reports ACBIN001 warnings. Uses DFS with 3-color marking to find back-edges.
+ ///
+ private static void DetectAndReportCycles(List classes, SourceProductionContext spc)
+ {
+ // Build lookup: WriterClassName → FullTypeName
+ var writerToFull = new Dictionary(classes.Count);
+ foreach (var ci in classes)
+ {
+ var writerName = string.IsNullOrEmpty(ci.Namespace)
+ ? $"{ci.ClassName}_GeneratedWriter"
+ : $"{ci.Namespace}.{ci.ClassName}_GeneratedWriter";
+ writerToFull[writerName] = ci.FullTypeName;
+ }
+
+ // Build adjacency list: FullTypeName → set of referenced FullTypeNames
+ var adjacency = new Dictionary>(classes.Count);
+ foreach (var ci in classes)
+ {
+ var edges = new HashSet();
+ foreach (var p in ci.Properties)
+ {
+ if (p.TypeKind == PropertyTypeKind.Complex && p.HasGeneratedWriter && p.WriterClassName != null)
+ {
+ if (writerToFull.TryGetValue(p.WriterClassName, out var target))
+ edges.Add(target);
+ }
+ if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter && p.ElementWriterClassName != null)
+ {
+ if (writerToFull.TryGetValue(p.ElementWriterClassName, out var target))
+ edges.Add(target);
+ }
+ if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter && p.DictValueWriterClassName != null)
+ {
+ if (writerToFull.TryGetValue(p.DictValueWriterClassName, out var target))
+ edges.Add(target);
+ }
+ }
+ adjacency[ci.FullTypeName] = edges;
+ }
+
+ // DFS with 3-color marking: White=0, Gray=1, Black=2
+ var color = new Dictionary(classes.Count);
+ foreach (var ci in classes)
+ color[ci.FullTypeName] = 0;
+
+ var stack = new List();
+ var reported = new HashSet();
+
+ void Dfs(string node)
+ {
+ color[node] = 1; // Gray
+ stack.Add(node);
+
+ if (adjacency.TryGetValue(node, out var neighbors))
+ {
+ foreach (var next in neighbors)
+ {
+ if (!color.TryGetValue(next, out var c)) continue;
+ if (c == 1) // Gray → back-edge = cycle
+ {
+ var cycleStart = stack.IndexOf(next);
+ var parts = new List();
+ for (var i = cycleStart; i < stack.Count; i++)
+ parts.Add(ShortTypeName(stack[i]));
+ parts.Add(ShortTypeName(next)); // close the cycle
+
+ var cycleDesc = string.Join(" → ", parts);
+ for (var i = cycleStart; i < stack.Count; i++)
+ {
+ if (reported.Add(stack[i]))
+ {
+ spc.ReportDiagnostic(Diagnostic.Create(
+ CircularReferenceWarning, Location.None,
+ ShortTypeName(stack[i]), cycleDesc));
+ }
+ }
+ }
+ else if (c == 0) // White → unvisited
+ {
+ Dfs(next);
+ }
+ }
+ }
+
+ stack.RemoveAt(stack.Count - 1);
+ color[node] = 2; // Black
+ }
+
+ foreach (var ci in classes)
+ {
+ if (color[ci.FullTypeName] == 0)
+ Dfs(ci.FullTypeName);
+ }
+ }
+
+ private static string ShortTypeName(string fullTypeName)
+ {
+ var dot = fullTypeName.LastIndexOf('.');
+ return dot >= 0 ? fullTypeName.Substring(dot + 1) : fullTypeName;
+ }
+}
diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenInit.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenInit.cs
new file mode 100644
index 0000000..a4a76d0
--- /dev/null
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenInit.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace AyCode.Core.Serializers.SourceGenerator;
+
+///
+/// Module-init emit pass: generates the static class with a [ModuleInitializer] method that
+/// auto-registers every generated writer / reader instance into the runtime registries
+/// (AcBinarySerializer.RegisterGeneratedWriter / AcBinaryDeserializer.RegisterGeneratedReader).
+/// Emitted once per compilation as AcBinaryGeneratedWriters_Init.g.cs.
+///
+public partial class AcBinarySourceGenerator
+{
+ private static string GenInit(List classes)
+ {
+ var sb = new StringBuilder(512);
+ sb.AppendLine("// ");
+ sb.AppendLine("using System.Runtime.CompilerServices;");
+ sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
+ sb.AppendLine();
+ sb.AppendLine("namespace AyCode.Core.Serializers.Generated;");
+ sb.AppendLine();
+ sb.AppendLine("internal static class AcBinaryGeneratedWritersInit");
+ sb.AppendLine("{");
+ sb.AppendLine(" [ModuleInitializer]");
+ sb.AppendLine(" internal static void Register()");
+ sb.AppendLine(" {");
+ foreach (var ci in classes)
+ {
+ var writerRef = string.IsNullOrEmpty(ci.Namespace)
+ ? $"{ci.ClassName}_GeneratedWriter"
+ : $"{ci.Namespace}.{ci.ClassName}_GeneratedWriter";
+ var readerRef = string.IsNullOrEmpty(ci.Namespace)
+ ? $"{ci.ClassName}_GeneratedReader"
+ : $"{ci.Namespace}.{ci.ClassName}_GeneratedReader";
+ sb.AppendLine($" AcBinarySerializer.RegisterGeneratedWriter(typeof({ci.FullTypeName}), {writerRef}.Instance);");
+ sb.AppendLine($" AcBinaryDeserializer.RegisterGeneratedReader(typeof({ci.FullTypeName}), {readerRef}.Instance);");
+ }
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+}
diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenReader.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenReader.cs
new file mode 100644
index 0000000..cf9df98
--- /dev/null
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.GenReader.cs
@@ -0,0 +1,884 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace AyCode.Core.Serializers.SourceGenerator;
+
+///
+/// Reader-side emit pass: generates the IGeneratedBinaryReader implementation for each
+/// [AcBinarySerializable] type. Emits ReadProperties (inline property reads with marker
+/// dispatch) and ReadObject (entry point with cache-index registration).
+///
+/// Sub-passes:
+///
+/// - EmitReadProp — per-property read emit (markerless + markered variants).
+/// - EmitReadString — H2Q6 string-tier marker dispatch (FixStrAscii + tier-tables +
+/// intern cases gated by EnableInternStringFeature).
+/// - EmitReadComplex — Object / ObjectRef* / FixObj-slot dispatch for IId-typed children.
+/// - EmitReadCollection / EmitReadCollectionInline / EmitReadCollectionElement /
+/// EmitReadNonComplexCollectionElement — collection-shape inline reading.
+/// - EmitReadDictionary / EmitReadDictElement — dict-shape inline reading.
+/// - EmitReadMarkeredValue / EmitReadMarkeredValueForKind — primitive value-with-marker reads.
+/// - EmitReadMarkerless — markerless primitive reads (FastMode + per-property markerless types).
+///
+///
+public partial class AcBinarySourceGenerator
+{
+ #region Reader Code Generation
+
+ ///
+ /// Generates the IGeneratedBinaryReader implementation for a type.
+ /// Phase 1: handles markerless path (no UseMetadata). UseMetadata/ChainMode → runtime fallback.
+ /// Eliminates: GetWrapper dictionary lookup, CreateInstance delegate, property setter delegates,
+ /// AccessorType switch dispatch, ReadValue dispatch table.
+ ///
+ private static string GenReader(SerializableClassInfo ci)
+ {
+ var sb = new StringBuilder(4096);
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine("using System.Runtime.CompilerServices;");
+ sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
+ sb.AppendLine();
+ if (!string.IsNullOrEmpty(ci.Namespace))
+ sb.AppendLine($"namespace {ci.Namespace};");
+ sb.AppendLine();
+ sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedReader : IGeneratedBinaryReader");
+ sb.AppendLine("{");
+ sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedReader Instance = new();");
+ sb.AppendLine();
+
+ // ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
+ // No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
+ // already prevents pathological depth in well-formed payloads.
+ sb.AppendLine(" public void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context)");
+ sb.AppendLine(" where TInput : struct, IBinaryInputBase");
+ sb.AppendLine(" {");
+ sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
+
+ // Emit property reads — markerless for primitive types, markered for the rest
+ foreach (var p in ci.Properties)
+ {
+ sb.AppendLine();
+ EmitReadProp(sb, p, " ", ci.EnableMetadata, ci.EnableInternString);
+ }
+
+ sb.AppendLine(" }");
+ sb.AppendLine();
+
+ // ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
+ sb.AppendLine(" public object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int cacheIndex)");
+ sb.AppendLine(" where TInput : struct, IBinaryInputBase");
+ sb.AppendLine(" {");
+ sb.AppendLine($" var obj = new {ci.FullTypeName}();");
+ sb.AppendLine(" if (cacheIndex >= 0)");
+ sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
+ sb.AppendLine(" ReadProperties(obj, context);");
+ sb.AppendLine(" return obj;");
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+
+ ///
+ /// Emits inline read code for a single property.
+ /// Markerless types: read raw value directly (no type code in stream).
+ /// Markered types: read type code byte, then dispatch.
+ /// Mirrors the serializer's EmitProp symmetry.
+ ///
+ private static void EmitReadProp(StringBuilder sb, PropInfo p, string i, bool enableMetadata, bool enableInternString)
+ {
+ var a = $"obj.{p.Name}";
+
+ // Markerless types: read raw value directly — mirrors EmitMarkerless in writer
+ if (IsMarkerless(p.TypeKind))
+ {
+ if (p.TypeKind == PropertyTypeKind.Enum)
+ sb.AppendLine($"{i}{{ var ev = context.ReadVarInt(); {a} = Unsafe.As(ref ev); }}");
+ else
+ EmitReadMarkerless(sb, p.TypeKind, a, i);
+ return;
+ }
+
+ // String FastWire markerless fast-path: int32 sentinel header (-1 = null, 0 = empty, N > 0 = content).
+ // Wire-symmetric with `WriteStringGenerated` (SGen) and `WriteStringUtf16Markerless` (Runtime).
+ // Skips the typeCode-read entirely in FastWire mode; falls through to markered dispatch in Compact.
+ if (p.TypeKind == PropertyTypeKind.String)
+ {
+ sb.AppendLine($"{i}if (context.FastWire)");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} {a} = context.ReadStringUtf16Markerless()!;");
+ sb.AppendLine($"{i}}}");
+ sb.AppendLine($"{i}else");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} var tc_{p.Name} = context.ReadByte();");
+ sb.AppendLine($"{i} if (tc_{p.Name} != BinaryTypeCode.PropertySkip)");
+ sb.AppendLine($"{i} {{");
+ EmitReadString(sb, a, $"tc_{p.Name}", i + " ", enableInternString);
+ sb.AppendLine($"{i} }}");
+ sb.AppendLine($"{i}}}");
+ return;
+ }
+
+ // Markered types: read type code, then dispatch
+ var tc = $"tc_{p.Name}";
+ sb.AppendLine($"{i}var {tc} = context.ReadByte();");
+
+ // PropertySkip → leave default
+ sb.AppendLine($"{i}if ({tc} != BinaryTypeCode.PropertySkip)");
+ sb.AppendLine($"{i}{{");
+
+ // Nullable value types
+ if (IsNullableVTKind(p.TypeKind))
+ {
+ sb.AppendLine($"{i} if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
+ sb.AppendLine($"{i} else");
+ sb.AppendLine($"{i} {{");
+ EmitReadMarkeredValue(sb, Underlying(p.TypeKind), a, tc, i + " ", p, nullable: true);
+ sb.AppendLine($"{i} }}");
+ }
+ else
+ {
+ switch (p.TypeKind)
+ {
+ case PropertyTypeKind.String:
+ EmitReadString(sb, a, tc, i + " ", enableInternString);
+ break;
+
+ case PropertyTypeKind.Complex:
+ EmitReadComplex(sb, p, a, tc, i + " ");
+ break;
+
+ case PropertyTypeKind.Collection:
+ EmitReadCollection(sb, p, a, tc, i + " ", enableInternString);
+ break;
+
+ case PropertyTypeKind.Dictionary:
+ EmitReadDictionary(sb, p, a, tc, i + " ", enableInternString);
+ break;
+
+ default:
+ // Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
+ sb.AppendLine($"{i} context._position--;");
+ if (p.IsNullable)
+ sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
+ else
+ sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
+ break;
+ }
+ }
+
+ sb.AppendLine($"{i}}}");
+ }
+
+ ///
+ /// Emits raw value read — no type code in stream. Mirrors EmitMarkerless exactly.
+ ///
+ private static void EmitReadMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
+ {
+ switch (k)
+ {
+ case PropertyTypeKind.Int32: sb.AppendLine($"{i}{a} = context.ReadVarInt();"); break;
+ case PropertyTypeKind.Int64: sb.AppendLine($"{i}{a} = context.ReadVarLong();"); break;
+ case PropertyTypeKind.Double: sb.AppendLine($"{i}{a} = context.ReadDoubleUnsafe();"); break;
+ case PropertyTypeKind.Single: sb.AppendLine($"{i}{a} = context.ReadSingleUnsafe();"); break;
+ case PropertyTypeKind.Decimal: sb.AppendLine($"{i}{a} = context.ReadDecimalUnsafe();"); break;
+ case PropertyTypeKind.DateTime: sb.AppendLine($"{i}{a} = context.ReadDateTimeUnsafe();"); break;
+ case PropertyTypeKind.Guid: sb.AppendLine($"{i}{a} = context.ReadGuidUnsafe();"); break;
+ case PropertyTypeKind.Byte: sb.AppendLine($"{i}{a} = context.ReadByte();"); break;
+ case PropertyTypeKind.Int16: sb.AppendLine($"{i}{a} = context.ReadInt16Unsafe();"); break;
+ case PropertyTypeKind.UInt16: sb.AppendLine($"{i}{a} = context.ReadUInt16Unsafe();"); break;
+ case PropertyTypeKind.UInt32: sb.AppendLine($"{i}{a} = context.ReadVarUInt();"); break;
+ case PropertyTypeKind.UInt64: sb.AppendLine($"{i}{a} = context.ReadVarULong();"); break;
+ case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}{a} = new System.TimeSpan(context.ReadRaw());"); break;
+ case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}{a} = context.ReadDateTimeOffsetUnsafe();"); break;
+ case PropertyTypeKind.Boolean: sb.AppendLine($"{i}{a} = context.ReadByte() != 0;"); break;
+ }
+ }
+
+ ///
+ /// Emits inline string read from type code. Handles all H2Q6 (v3 wire format) string markers:
+ /// FixStrAscii (ASCII short, 135-166), StringAscii (ASCII long, 167),
+ /// StringSmall/Medium/Big (non-ASCII tiers, 91/94/103),
+ /// StringInternFirstSmall/Medium (interning tiers, 104/105),
+ /// StringInterned (cache ref, 92), StringEmpty (93), Null.
+ ///
+ /// FixStrAscii is checked first as the hot path for short ASCII property names; non-ASCII
+ /// tier markers carry both charLen and utf8Len in fixed-width headers (1-pass decode).
+ ///
+ private static void EmitReadString(StringBuilder sb, string a, string tc, string i, bool enableInternString)
+ {
+ // FixStrAscii is the hot path — most short strings (property names) are ASCII.
+ sb.AppendLine($"{i}if (BinaryTypeCode.IsFixStrAscii({tc}))");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} var falen = BinaryTypeCode.DecodeFixStrAsciiLength({tc});");
+ sb.AppendLine($"{i} {a} = falen == 0 ? string.Empty : context.ReadAsciiBytesAsString(falen);");
+ sb.AppendLine($"{i}}}");
+ // Switch gives O(1) dispatch via JIT jump table for the remaining markers.
+ sb.AppendLine($"{i}else switch ({tc})");
+ sb.AppendLine($"{i}{{");
+ // Interning case (2nd+ occurrence ref) — only emit when EnableInternStringFeature is enabled
+ // on this type. When disabled, the writer never emits StringInterned markers for this type's
+ // properties, so the reader doesn't need to handle them. ACCORE-BIN-T-K9M3 Phase C.
+ if (enableInternString)
+ {
+ sb.AppendLine($"{i} case BinaryTypeCode.StringInterned:");
+ sb.AppendLine($"{i} {a} = context.GetInternedString((int)context.ReadVarUInt());");
+ sb.AppendLine($"{i} break;");
+ }
+ // H2Q6 string-tier markers + StringAscii. Wire-decode body is shared with the runtime path
+ // (TypeReaderTable + cross-type populate) — see context.ReadStringSmall/Medium/Big, ReadPlainStringAscii.
+ // These markers are feature-independent: writer emits them on any string property regardless of
+ // intern setting (intern is opt-in per-property via [AcStringIntern] + InternBit).
+ sb.AppendLine($"{i} case BinaryTypeCode.StringSmall:");
+ sb.AppendLine($"{i} {a} = context.ReadStringSmall();");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i} case BinaryTypeCode.StringMedium:");
+ sb.AppendLine($"{i} {a} = context.ReadStringMedium();");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i} case BinaryTypeCode.StringBig:");
+ sb.AppendLine($"{i} {a} = context.ReadStringBig();");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i} case BinaryTypeCode.StringAscii:");
+ sb.AppendLine($"{i} {a} = context.ReadPlainStringAscii();");
+ sb.AppendLine($"{i} break;");
+ // Interning first-occurrence cases — see comment above.
+ if (enableInternString)
+ {
+ sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstSmall:");
+ sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringSmall();");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstMedium:");
+ sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringMedium();");
+ sb.AppendLine($"{i} break;");
+ }
+ sb.AppendLine($"{i} case BinaryTypeCode.Null:");
+ sb.AppendLine($"{i} {a} = null;");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i} case BinaryTypeCode.StringEmpty:");
+ sb.AppendLine($"{i} {a} = string.Empty;");
+ sb.AppendLine($"{i} break;");
+ sb.AppendLine($"{i}}}");
+ }
+
+ ///
+ /// Emits inline read for a Complex property.
+ /// SGen reader only runs in non-metadata mode → ObjectWithMetadata never appears.
+ /// Compile-time ChildNeedsRefScan eliminates ObjectRefFirst/ObjectRef when provably unused.
+ /// Non-nullable + no ref → ZERO branches (tc consumed but ignored).
+ /// No SGen → runtime fallback via ReadValueGenerated.
+ ///
+ private static void EmitReadComplex(StringBuilder sb, PropInfo p, string a, string tc, string i)
+ {
+ if (!p.HasGeneratedWriter)
+ {
+ // No SGen reader — runtime fallback (rewind + ReadValueGenerated)
+ if (p.IsNullable)
+ {
+ sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
+ sb.AppendLine($"{i}else");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} context._position--;");
+ sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
+ sb.AppendLine($"{i}}}");
+ }
+ else
+ {
+ sb.AppendLine($"{i}context._position--;");
+ sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
+ }
+ return;
+ }
+
+ var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
+ var cast = $"({p.TypeNameForTypeof})";
+
+ if (!p.ChildNeedsRefScan)
+ {
+ // Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
+ // Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
+ // FixObj slot bytes (0..SlotCount-1) are also valid markers here — populate slot cache
+ // to keep _nextRuntimeSlot in sync with the serializer's _nextTypeSlot counter.
+ if (p.IsNullable)
+ {
+ sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
+ sb.AppendLine($"{i}else");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
+ sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
+ sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
+ sb.AppendLine($"{i} {a} = rc_{p.Name};");
+ sb.AppendLine($"{i}}}");
+ }
+ else
+ {
+ // ZERO branches — tc is always Object or FixObj
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
+ sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
+ sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
+ sb.AppendLine($"{i} {a} = rc_{p.Name};");
+ sb.AppendLine($"{i}}}");
+ }
+ }
+ else
+ {
+ // Ref tracking possible — switch on tc (Object / ObjectRefFirst / [Null] / ObjectRef /