first pass of sync generator

This commit is contained in:
Adam Hathcock
2026-07-28 11:26:51 +01:00
parent 0037141ac4
commit ea44ec098b
17 changed files with 38 additions and 147 deletions

View File

@@ -154,6 +154,35 @@ SharpCompress supports multiple archive and compression formats:
- `OpenEntryStreamAsync` - Open entry stream asynchronously
- Always provide `CancellationToken` parameter in async methods
### Generating sync methods from async ones
Most of the library still keeps a hand-written sync twin beside each async method, usually split as
`Foo.cs` + `Foo.Async.cs`. Where the two bodies are the same logic, the sync one is generated
instead, using [Zomp.SyncMethodGenerator](https://github.com/zompinc/sync-method-generator) (already
referenced for every project via `Directory.Packages.props`).
- Put `[Zomp.SyncMethodGenerator.CreateSyncVersion]` on the **async** method — that body is then the
single source of truth — and delete the hand-written sync twin. The generator drops the `Async`
suffix, `CancellationToken` and `IProgress<T>` (unless `PreserveCancellationToken` /
`PreserveProgress`), maps `Task`/`ValueTask` to `void`/`T` and `Memory<T>` to `Span<T>`, and
rewrites `FooAsync(...)` calls to `Foo(...)`. Modifiers, including `override`/`virtual`, are kept.
- **Attribute individual methods, never the whole type.** A type-level attribute also generates the
members that must not exist (see below), so you would need more `[SkipSyncVersion]` than
`[CreateSyncVersion]`.
- Attribute a method **only when the generated signature already exists by hand.** Generating a
member that did not exist before is a behaviour change, not a deduplication — in particular, a
generated `Read(Span<byte>)` replaces `Stream`'s default rent-and-copy shim.
- Deliberately **not** generated: `Dispose`/`DisposeAsync` (on a `Stream` the generated `Dispose()`
cannot override the non-virtual `Stream.Dispose()`), `Flush`/`FlushAsync`, `CopyTo`/`CopyToAsync`,
`ReadByte`/`WriteByte` (generated without `override`, so they would hide `Stream`'s), and the
`Memory`/`ReadOnlyMemory` overloads that have no sync twin.
- Keep one XML doc comment, on the async method, phrased tense-neutrally ("Extract entry to the
specified stream.") — it is emitted onto both copies.
- Use `#if SYNC_ONLY` / `#if !SYNC_ONLY` only for a localised I/O idiom that genuinely differs; if it
would cover more than a small part of the method, keep two hand-written methods instead.
- To see what was generated, build with `-p:EmitCompilerGeneratedFiles=true` and look in
`obj/<config>/<tfm>/generated/Zomp.SyncMethodGenerator/`. A sync stack frame may therefore name a
method that has no file in the repo — debug it via the async source.
### Archive APIs vs Reader/Writer APIs
- **Archive API**: Use for random access with seekable streams (e.g., `ZipArchive`, `TarArchive`)
- **Reader API**: Use for forward-only reading on non-seekable streams (e.g., `ZipReader`, `TarReader`)

View File

@@ -7,6 +7,7 @@ namespace SharpCompress.Compressors.Filters;
internal abstract partial class Filter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
@@ -200,6 +201,7 @@ internal abstract partial class Filter
}
#endif
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task WriteAsync(
byte[] buffer,
int offset,

View File

@@ -50,99 +50,9 @@ internal abstract partial class Filter : Stream
set => throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
var size = 0;
if (_transformed > 0)
{
var copySize = _transformed;
if (copySize > count)
{
copySize = count;
}
Buffer.BlockCopy(_tail, 0, buffer, offset, copySize);
_transformed -= copySize;
_read -= copySize;
offset += copySize;
count -= copySize;
size += copySize;
Buffer.BlockCopy(_tail, copySize, _tail, 0, _read);
}
if (count == 0)
{
return size;
}
var inSize = _read;
if (inSize > count)
{
inSize = count;
}
Buffer.BlockCopy(_tail, 0, buffer, offset, inSize);
_read -= inSize;
Buffer.BlockCopy(_tail, inSize, _tail, 0, _read);
while (!_endReached && inSize < count)
{
var baseRead = _baseStream.Read(buffer, offset + inSize, count - inSize);
inSize += baseRead;
if (baseRead == 0)
{
_endReached = true;
}
}
while (!_endReached && _read < _tail.Length)
{
var baseRead = _baseStream.Read(_tail, _read, _tail.Length - _read);
_read += baseRead;
if (baseRead == 0)
{
_endReached = true;
}
}
if (inSize > _tail.Length)
{
_transformed = Transform(buffer, offset, inSize);
offset += _transformed;
count -= _transformed;
size += _transformed;
inSize -= _transformed;
_transformed = 0;
}
if (count == 0)
{
return size;
}
Buffer.BlockCopy(buffer, offset, _window, 0, inSize);
Buffer.BlockCopy(_tail, 0, _window, inSize, _read);
if (inSize + _read > _tail.Length)
{
_transformed = Transform(_window, 0, inSize + _read);
}
else
{
_transformed = inSize + _read;
}
Buffer.BlockCopy(_window, 0, buffer, offset, inSize);
Buffer.BlockCopy(_window, inSize, _tail, 0, _read);
size += inSize;
_transformed -= inSize;
return size;
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
{
Transform(buffer, offset, count);
_baseStream.Write(buffer, offset, count);
}
protected abstract int Transform(byte[] buffer, int offset, int count);
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class ArmFilter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -45,13 +44,5 @@ public partial class ArmFilter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.ARMConverter(buffer, _ip);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class ArmThumbFilter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -45,13 +44,5 @@ public partial class ArmThumbFilter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.ARMTConverter(buffer, _ip);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class IA64Filter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -45,13 +44,5 @@ public partial class IA64Filter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.IA64Converter(buffer, _ip);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}

View File

@@ -7,6 +7,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class Lzma2Filter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -52,8 +52,5 @@ public partial class Lzma2Filter : BlockFilter
public override void SetBaseStream(Stream stream) =>
BaseStream = LzmaStream.Create(new[] { _dictionarySize }, stream);
public override int Read(byte[] buffer, int offset, int count) =>
BaseStream.Read(buffer, offset, count);
public override int ReadByte() => BaseStream.ReadByte();
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class PowerPCFilter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -45,13 +44,5 @@ public partial class PowerPCFilter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.PowerPCConverter(buffer, _ip);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class SparcFilter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -45,13 +44,5 @@ public partial class SparcFilter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.SPARCConverter(buffer, _ip);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}

View File

@@ -8,6 +8,7 @@ namespace SharpCompress.Compressors.Xz.Filters;
public partial class X86Filter
{
[Zomp.SyncMethodGenerator.CreateSyncVersion]
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -6,7 +6,6 @@
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Filters;
namespace SharpCompress.Compressors.Xz.Filters;
@@ -47,13 +46,5 @@ public partial class X86Filter : BlockFilter
public override void ValidateFilter() { }
public override int Read(byte[] buffer, int offset, int count)
{
var bytesRead = BaseStream.Read(buffer, offset, count);
BranchExecFilter.X86Converter(buffer, _ip, ref _state);
_ip += (uint)bytesRead;
return bytesRead;
}
public override void SetBaseStream(Stream stream) => BaseStream = stream;
}