Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ property offset は型の先頭からの相対位置で、`0` は null を表し
- `[ZeroSerializer]` と `[StructLayout(LayoutKind.Sequential, Pack = 1)]` を再帰的に満たす Blittable Struct。
- `string`。
- 一次元配列。要素は Blittable primitive、enum、または Blittable Struct に限定されます。
- `[ZeroSerializer]` を付けたネスト型。非 Blittable Struct はネストした View として公開されます。
- `[ZeroSerializer]` を付けた構造体型プロパティー。非 Blittable Struct View として公開されます。

Blittable Struct は全ケースで offset table を持たない raw payload として扱います。

- 単体とネストプロパティーは `MemoryMarshal.Write/Read`。
- 単体と構造体型プロパティーは `MemoryMarshal.Write/Read`。
- 配列は `MemoryMarshal.AsBytes/Cast`。
- nullable は property offset `0` で null を表し、非 null の場合だけ raw payload。

`[ZeroSerializer]` が付いていても Blittable Struct はネスト View 化しません。親が非 Blittable 型なら親の property offset table は存在しますが、Blittable Struct payload 内部には table を生成しません。
`[ZeroSerializer]` が付いていても Blittable Struct View 化しません。親が非 Blittable 型なら親の property offset table は存在しますが、Blittable Struct payload 内部には table を生成しません。

全プロパティー型が Blittable 対応済みで、自身の `StructLayout(LayoutKind.Sequential, Pack = 1)` だけが不足する `[ZeroSerializer]` struct には、型名 identifier へ `ZEROS006` warning を出します。

ネストした class/struct にも `[ZeroSerializer]` が必要です。未修飾の型は `ZEROS003` error になります。
構造体型プロパティーの class/struct にも `[ZeroSerializer]` が必要です。未修飾の型は `ZEROS003` error になります。

## null と可変長データ

Expand Down Expand Up @@ -108,7 +108,7 @@ null は property offset `0` で表し、length header と payload を格納し

## View と長さ

生成 View は `ReadOnlyMemory<byte>` を保持する通常の `readonly struct` です。コンストラクターはデータ先頭が揃った `ReadOnlyMemory<byte>` だけを受け取り、offset は受け取りません。呼び出し側が必要な位置で `Slice` し、ネストした非 Blittable 型も property offset で `Slice` した Memory から View を生成します。固定長・可変長ともにコンストラクターでは長さを検証せず、プロパティーアクセス時の Memory・Span・index・`BinaryPrimitives`・`MemoryMarshal` の標準例外に範囲検証を任せます。
生成 View は `ReadOnlyMemory<byte>` を保持する通常の `readonly struct` です。コンストラクターはデータ先頭が揃った `ReadOnlyMemory<byte>` だけを受け取り、offset は受け取りません。呼び出し側が必要な位置で `Slice` し、構造体型プロパティーの非 Blittable 型も property offset で `Slice` した Memory から View を生成します。固定長・可変長ともにコンストラクターでは長さを検証せず、プロパティーアクセス時の Memory・Span・index・`BinaryPrimitives`・`MemoryMarshal` の標準例外に範囲検証を任せます。

View は `ReadOnlySpan<byte>` と `ReadOnlyMemory<byte>` へ暗黙変換でき、どちらもコピーや再シリアライズを行いません。`RequiredByteLength >= 0` の固定長 View は先頭から `RequiredByteLength` までを返します。負値になる可変長 View は総バイト長を wire format から復元できないため、コンストラクターへ渡した借用領域全体を返します。可変長データのハッシュや検証では、未使用のバッファ末尾を含めないよう `writtenBytes` で Memory を切り詰めてから View を作成します。

Expand Down Expand Up @@ -146,8 +146,8 @@ ReadOnlyMemory<byte> directMemory = view.AsMemory();
2. **可変長データ型のプロパティー**
- `string`
- 一次元配列(例: `int[]` など)
3. **サイズ予測不可能なネスト型のプロパティー**
- `[ZeroSerializer]` が付与されたネスト型であって、その内部に上記の Nullable 型、参照型、可変長データ型、またはその他のサイズ予測不可能なプロパティーを含んでいるもの
3. **サイズ予測不可能な構造体型プロパティー**
- `[ZeroSerializer]` が付与された構造体型プロパティーであって、その内部に上記の Nullable 型、参照型、可変長データ型、またはその他のサイズ予測不可能なプロパティーを含んでいるもの
4. **再帰・循環参照構造**
- 自分自身や他の型を相互に参照し合うなどして、依存関係が再帰・循環している場合
5. **サイズ計算時の算術オーバーフロー**
Expand All @@ -157,6 +157,6 @@ Blittable Struct は property offset table を持たないため、`RequiredByte

`RequiredByteLength >= 0` は固定長であることだけを表し、Blittable 判定とは独立しています。class と非 Blittable struct は固定長でも property offset table から各プロパティーを読みます。struct 全体を `MemoryMarshal.Read` するのは Blittable Struct だけです。

`Serialize` は呼び出し側が渡した `Span<byte>` に直接書き込み、書き込んだ総バイト数(offset table を含む)を `int` で返します。ネスト型の書き込みは生成時にルート `Serialize` の本体へ展開され、実行時に別の `Serialize` を再帰呼び出ししません。`MemoryMarshal.Write` が読み取り専用の `in T` を受け取る .NET 8 以降では、参照型と `RequiredByteLength` の絶対値が `16` 以下の構造体は通常の `this`、`16` を超える構造体は `this in T source` で宣言します。書き込み可能な `ref T` を受け取る .NET 7 以前では、構造体を通常の `this` で受け取り、Blittable Struct のルート値を `ref source` で直接書き込みます。`in` は宣言側だけに付き、呼び出しは常に `source.Serialize(buffer)` です。固定長・可変長ともに独自の長さ検証を生成せず、範囲外を index・Span・`BinaryPrimitives`・`MemoryMarshal` の標準例外に任せます。
`Serialize` は呼び出し側が渡した `Span<byte>` に直接書き込み、書き込んだ総バイト数(offset table を含む)を `int` で返します。構造体型プロパティーの書き込みは生成時にルート `Serialize` の本体へ展開され、実行時に別の `Serialize` を再帰呼び出ししません。`MemoryMarshal.Write` が読み取り専用の `in T` を受け取る .NET 8 以降では、参照型と `RequiredByteLength` の絶対値が `16` 以下の構造体は通常の `this`、`16` を超える構造体は `this in T source` で宣言します。書き込み可能な `ref T` を受け取る .NET 7 以前では、構造体を通常の `this` で受け取り、Blittable Struct のルート値を `ref source` で直接書き込みます。`in` は宣言側だけに付き、呼び出しは常に `source.Serialize(buffer)` です。固定長・可変長ともに独自の長さ検証を生成せず、範囲外を index・Span・`BinaryPrimitives`・`MemoryMarshal` の標準例外に任せます。

string と Blittable payload は native memory image を利用するため、Serializer と View はリトルエンディアン環境だけを受け付けます。
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Transform original = view.Materialize();
- Primitives and enums
- Nullable values
- `string` stored as UTF-16 (UTF-8 can be stored as byte[] by hand)
- Nested `[ZeroSerializer]` types
- Struct-typed `[ZeroSerializer]` properties
- Blittable structs with `[StructLayout(LayoutKind.Sequential, Pack = 1)]`
- One-dimensional arrays of blittable values or structs

Expand Down Expand Up @@ -130,4 +130,4 @@ Blittable structs are stored directly as raw struct bytes without an offset tabl
- The wire format requires a little-endian runtime.
- View structs expose a compile-time constant `IsBlittable`, indicating whether the underlying serialized type is a blittable struct.
- You can use `.AsMemory()` extension method (returns `ReadOnlyMemory<byte>`) or `.Materialize()` extension method (for views of blittable structs to convert them back to the original struct).
- Nested classes and structs must be marked with `[ZeroSerializer]`; otherwise the generator reports an unsupported property diagnostic.
- Struct-typed properties must be marked with `[ZeroSerializer]`; otherwise the generator reports an unsupported property diagnostic.
44 changes: 22 additions & 22 deletions benchmark/Benchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ public void Setup()
Integers = integers,
Longs = longs,
PackedValues = packedValues,
Nested = new NestedPayload
StructTyped = new StructTypedPayload
{
Version = random.Next(),
Label = CreateRandomString(random, 128),
Summary = packedValues[0],
},
NestedStruct = new NestedStructPayload(
StructTypedStruct = new StructTypedStructPayload(
random.Next(),
CreateRandomInt64(random),
CreateRandomString(random, 128)),
Expand Down Expand Up @@ -126,14 +126,14 @@ public int DeserializeAllProperties()
ReadOnlySpan<int> integers = view.Integers;
ReadOnlySpan<long> longs = view.Longs;
ReadOnlySpan<PackedBenchmarkValue> packedValues = view.PackedValues;
NestedPayloadView? nested = view.Nested;
int nestedVersion = nested!.Value.Version;
ReadOnlySpan<char> nestedLabel = nested!.Value.Label;
PackedBenchmarkValueView nestedSummary = nested!.Value.Summary;
NestedStructPayloadView nestedStruct = view.NestedStruct;
int nestedStructCode = nestedStruct.Code;
long nestedStructAmount = nestedStruct.Amount;
ReadOnlySpan<char> nestedStructLabel = nestedStruct.Label;
StructTypedPayloadView? structTyped = view.StructTyped;
int structTypedVersion = structTyped!.Value.Version;
ReadOnlySpan<char> structTypedLabel = structTyped!.Value.Label;
PackedBenchmarkValueView structTypedSummary = structTyped!.Value.Summary;
StructTypedStructPayloadView structTypedStruct = view.StructTypedStruct;
int structTypedStructCode = structTypedStruct.Code;
long structTypedStructAmount = structTypedStruct.Amount;
ReadOnlySpan<char> structTypedStructLabel = structTypedStruct.Label;

// Consume getter results without traversing or validating collection contents.
unchecked
Expand All @@ -149,13 +149,13 @@ public int DeserializeAllProperties()
consumedValue = (consumedValue * 31) + integers.Length;
consumedValue = (consumedValue * 31) + longs.Length;
consumedValue = (consumedValue * 31) + packedValues.Length;
consumedValue = (consumedValue * 31) + nestedVersion;
consumedValue = (consumedValue * 31) + nestedLabel.Length;
consumedValue = (consumedValue * 31) + nestedSummary.Number;
consumedValue = (consumedValue * 31) + nestedSummary.Amount.GetHashCode();
consumedValue = (consumedValue * 31) + nestedStructCode;
consumedValue = (consumedValue * 31) + nestedStructAmount.GetHashCode();
consumedValue = (consumedValue * 31) + nestedStructLabel.Length;
consumedValue = (consumedValue * 31) + structTypedVersion;
consumedValue = (consumedValue * 31) + structTypedLabel.Length;
consumedValue = (consumedValue * 31) + structTypedSummary.Number;
consumedValue = (consumedValue * 31) + structTypedSummary.Amount.GetHashCode();
consumedValue = (consumedValue * 31) + structTypedStructCode;
consumedValue = (consumedValue * 31) + structTypedStructAmount.GetHashCode();
consumedValue = (consumedValue * 31) + structTypedStructLabel.Length;
return consumedValue;
}
}
Expand Down Expand Up @@ -288,13 +288,13 @@ public sealed class BenchmarkPayload

public PackedBenchmarkValue[] PackedValues { get; init; } = Array.Empty<PackedBenchmarkValue>();

public NestedPayload Nested { get; init; } = new();
public StructTypedPayload StructTyped { get; init; } = new();

public NestedStructPayload NestedStruct { get; init; }
public StructTypedStructPayload StructTypedStruct { get; init; }
}

[ZeroSerializerAttribute]
public sealed class NestedPayload
public sealed class StructTypedPayload
{
public int Version { get; init; }

Expand All @@ -304,9 +304,9 @@ public sealed class NestedPayload
}

[ZeroSerializerAttribute]
public readonly struct NestedStructPayload
public readonly struct StructTypedStructPayload
{
public NestedStructPayload(int code, long amount, string label)
public StructTypedStructPayload(int code, long amount, string label)
{
Code = code;
Amount = amount;
Expand Down
6 changes: 3 additions & 3 deletions src/PropertyGenerationModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ internal PropertyGenerationModel(
PropertySerializationKind kind,
int elementByteCount,
ITypeSymbol? arrayElementType,
INamedTypeSymbol? nestedSerializableType,
INamedTypeSymbol? structTypedSerializableType,
ITypeSymbol? nullableUnderlyingType = null,
bool isNullableType = false)
{
Symbol = symbol;
Kind = kind;
ElementByteCount = elementByteCount;
ArrayElementType = arrayElementType;
NestedSerializableType = nestedSerializableType;
StructTypedSerializableType = structTypedSerializableType;
NullableUnderlyingType = nullableUnderlyingType;
IsNullableType = isNullableType;
}
Expand All @@ -37,7 +37,7 @@ internal PropertyGenerationModel(

internal ITypeSymbol? ArrayElementType { get; }

internal INamedTypeSymbol? NestedSerializableType { get; }
internal INamedTypeSymbol? StructTypedSerializableType { get; }

internal ITypeSymbol? NullableUnderlyingType { get; }

Expand Down
2 changes: 1 addition & 1 deletion src/PropertySerializationKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal enum PropertySerializationKind
String,
BlittableStruct,
Array,
Nested,
StructTyped,
InvalidArray,
}
Loading
Loading