Skip to content

Commit a9d4046

Browse files
Merge pull request #3160 from SixLabors/js/fix-3151
Add AOT compiler seeds for pixel formats and ICO, CUR, QOI codecs
2 parents 776cb8b + 395c8bd commit a9d4046

7 files changed

Lines changed: 196 additions & 0 deletions

File tree

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,15 @@
8484
# treat as binary
8585
###############################################################################
8686
*.basis binary
87+
*.a binary
8788
*.dll binary
89+
*.dylib binary
8890
*.exe binary
8991
*.pdf binary
9092
*.ppt binary
9193
*.pptx binary
9294
*.pvr binary
95+
*.so binary
9396
*.snk binary
9497
*.xls binary
9598
*.xlsx binary

.github/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# GitHub Copilot Instructions
2+
3+
Read and follow [AGENTS.md](../AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Prefer existing local patterns and repository configuration whenever generated code or suggestions are accepted.

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Six Labors AI Coding Guidelines
2+
3+
These instructions apply to the entire repository. More-specific `AGENTS.md` files may add to or override them for their directory tree.
4+
5+
## Working Practices
6+
7+
- Inspect the relevant implementation, tests, benchmarks, project files, and nearby code before proposing or making changes. Do not infer current behavior when the source is available.
8+
- Make the smallest complete change that solves the requested problem. Avoid unrelated cleanup, speculative abstractions, and formatting churn.
9+
- Match established architecture, naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative.
10+
- Preserve public API and observable behavior unless the task explicitly requires a change. Public API documentation must describe observable behavior, not implementation details.
11+
- Do not use reflection against built assemblies, ad hoc assembly loading, or temporary probe projects unless explicitly requested.
12+
- Build .NET projects in Release configuration unless explicitly instructed otherwise.
13+
14+
## Performance
15+
16+
- Treat throughput, latency, memory use, and binary size as design constraints, especially in pixel-processing, drawing, parsing, encoding, and other hot paths.
17+
- Avoid unnecessary allocations, copies, boxing, closures, interface dispatch, repeated enumeration, and extra passes over data.
18+
- Reuse the repository's existing memory ownership, pooling, span, vectorization, and parallelization patterns. Do not introduce a new mechanism when an established one fits.
19+
- Keep hot loops simple and bounds-check-friendly. Hoist invariant work, preserve locality, and use the narrowest suitable data types without sacrificing correctness.
20+
- Do not trade correctness or maintainability for assumed speed. Support non-obvious optimizations with measurements or clear evidence, and add or update benchmarks when performance is the purpose of the change.
21+
- Consider all supported target frameworks and runtime capabilities. Do not regress fallback paths while optimizing newer runtimes.
22+
23+
## C# Conventions
24+
25+
- Follow the existing code around the change; local patterns take precedence over generic preferences.
26+
- Do not use `record` or `record struct` types.
27+
- Prefer established invariants over redundant guards. Validate at real external boundaries and do not add defensive checks for internally controlled states.
28+
- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or meaningful complexity reduction.
29+
- Add vertical whitespace after multi-line statements and declarations and between distinct logical stages. Never add trailing whitespace.
30+
- Document every method, constructor, and property, regardless of whether it is public, internal, protected, or private. Keep public API documentation limited to observable behavior; use private and internal documentation to capture the contract and intent needed to maintain the code.
31+
- Add inline comments throughout complex code. Explain algorithms, formulas, invariants, ownership, compatibility behavior, and performance tradeoffs at the operations and decisions they govern. Explain why the code is shaped that way rather than narrating the syntax.
32+
- Document SIMD code especially thoroughly. Explain the vector layout, lane meaning, widening or narrowing, masks, shuffles, constants, alignment or remainder handling, supported instruction paths, scalar equivalence, and the reason each non-obvious operation is correct.
33+
- Write algorithm and SIMD comments for a maintainer who is unfamiliar with the implementation. The reader should not need to reconstruct intent from external documentation, issue history, or benchmark results.
34+
35+
## Verification
36+
37+
- Add or update focused tests when behavior changes, following the test framework and conventions already used by the project.
38+
- Never hack, weaken, skip, conditionally bypass, or otherwise manipulate a test to make it pass. Fix the production defect or the genuine test defect while preserving the test's intended coverage and sensitivity.
39+
- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts to resolve a test failure. Treat a mismatch as evidence to investigate and correct the implementation.
40+
- Run the narrowest relevant formatting, test, and Release build commands, then expand verification in proportion to the risk and scope of the change.
41+
- Report what changed, the verification performed, and any remaining risks or unverified assumptions.

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Claude Code Instructions
2+
3+
Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `CLAUDE.md` found below the files being changed.

GEMINI.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Gemini CLI Instructions
2+
3+
Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `GEMINI.md` found below the files being changed.

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
using System.Runtime.CompilerServices;
88
using SixLabors.ImageSharp.Formats;
99
using SixLabors.ImageSharp.Formats.Bmp;
10+
using SixLabors.ImageSharp.Formats.Cur;
1011
using SixLabors.ImageSharp.Formats.Exr;
1112
using SixLabors.ImageSharp.Formats.Gif;
13+
using SixLabors.ImageSharp.Formats.Ico;
1214
using SixLabors.ImageSharp.Formats.Jpeg;
1315
using SixLabors.ImageSharp.Formats.Jpeg.Components;
1416
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@@ -128,6 +130,67 @@ private static void SeedPixelFormats()
128130
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
129131
}
130132

133+
/// <summary>
134+
/// Seeds the modern .NET AOT compiler with bulk pixel operations for every built-in pixel format.
135+
/// </summary>
136+
/// <exception cref="InvalidOperationException">
137+
/// This method is used for AOT code generation only. Do not call it at runtime.
138+
/// </exception>
139+
[Preserve]
140+
public static void SeedPixelOperations()
141+
{
142+
try
143+
{
144+
// Keep this inventory finite and explicit. ImageSharp cannot precompile consumer-defined pixel types, while
145+
// these closed calls make every built-in specialization visible without retaining the much larger legacy seed graph.
146+
AotCompilePixelOperations<A8>();
147+
AotCompilePixelOperations<Argb32>();
148+
AotCompilePixelOperations<Argb32P>();
149+
AotCompilePixelOperations<Abgr32>();
150+
AotCompilePixelOperations<Abgr32P>();
151+
AotCompilePixelOperations<Bgr24>();
152+
AotCompilePixelOperations<Bgr565>();
153+
AotCompilePixelOperations<Bgra32>();
154+
AotCompilePixelOperations<Bgra32P>();
155+
AotCompilePixelOperations<Bgra4444>();
156+
AotCompilePixelOperations<Bgra5551>();
157+
AotCompilePixelOperations<Byte4>();
158+
AotCompilePixelOperations<L16>();
159+
AotCompilePixelOperations<L8>();
160+
AotCompilePixelOperations<La16>();
161+
AotCompilePixelOperations<La32>();
162+
AotCompilePixelOperations<HalfSingle>();
163+
AotCompilePixelOperations<HalfVector2>();
164+
AotCompilePixelOperations<HalfVector4>();
165+
AotCompilePixelOperations<HalfVector4P>();
166+
AotCompilePixelOperations<NormalizedByte2>();
167+
AotCompilePixelOperations<NormalizedByte4>();
168+
AotCompilePixelOperations<NormalizedByte4P>();
169+
AotCompilePixelOperations<NormalizedShort2>();
170+
AotCompilePixelOperations<NormalizedShort4>();
171+
AotCompilePixelOperations<Rg32>();
172+
AotCompilePixelOperations<Rgb24>();
173+
AotCompilePixelOperations<Rgb48>();
174+
AotCompilePixelOperations<Rgb96>();
175+
AotCompilePixelOperations<Rgba1010102>();
176+
AotCompilePixelOperations<Rgba128>();
177+
AotCompilePixelOperations<Rgba32>();
178+
AotCompilePixelOperations<Rgba32P>();
179+
AotCompilePixelOperations<Rgba64>();
180+
AotCompilePixelOperations<RgbaHalf>();
181+
AotCompilePixelOperations<RgbaHalfP>();
182+
AotCompilePixelOperations<RgbaVector>();
183+
AotCompilePixelOperations<Short2>();
184+
AotCompilePixelOperations<Short4>();
185+
}
186+
catch
187+
{
188+
// The calls only need to exist in IL; this method must never contribute a runtime execution path.
189+
}
190+
191+
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
192+
}
193+
131194
/// <summary>
132195
/// Seeds the compiler using the given pixel format.
133196
/// </summary>
@@ -138,6 +201,7 @@ private static void Seed<TPixel>()
138201
{
139202
// This is we actually call all the individual methods you need to seed.
140203
AotCompileImage<TPixel>();
204+
AotCompilePixelOperations<TPixel>();
141205
AotCompileImageProcessingContextFactory<TPixel>();
142206
AotCompileImageEncoderInternals<TPixel>();
143207
AotCompileImageDecoderInternals<TPixel>();
@@ -158,6 +222,68 @@ private static void Seed<TPixel>()
158222
// TODO: Do the discovery work to figure out what works and what doesn't.
159223
}
160224

225+
/// <summary>
226+
/// Seeds the selected <see cref="PixelOperations{TPixel}"/> methods required by Mono WASM AOT.
227+
/// </summary>
228+
/// <typeparam name="TPixel">The pixel format.</typeparam>
229+
[Preserve]
230+
private static void AotCompilePixelOperations<TPixel>()
231+
where TPixel : unmanaged, IPixel<TPixel>
232+
{
233+
// These default arguments are never consumed. Direct calls are required so the IL contains the exact closed
234+
// MethodSpecs that Mono WASM AOT can otherwise miss when following static-abstract pixel dispatch indirectly.
235+
PixelOperations<TPixel> operations = PixelOperations<TPixel>.Instance;
236+
237+
_ = operations.GetPixelTypeInfo();
238+
_ = operations.GetPixelBlender(default(GraphicsOptions));
239+
_ = operations.GetPixelBlender(default, default);
240+
operations.FromVector4Destructive(default, default, default);
241+
operations.FromVector4Destructive(default, default, default, default);
242+
operations.ToVector4(default, default, default);
243+
operations.ToVector4(default, default, default, default);
244+
operations.PackFromRgbPlanes(default, default, default, default);
245+
operations.UnpackIntoRgbPlanes(default, default, default, default);
246+
247+
operations.FromArgb32Bytes(default, default, default, default);
248+
operations.ToArgb32Bytes(default, default, default, default);
249+
250+
operations.FromAbgr32Bytes(default, default, default, default);
251+
operations.ToAbgr32Bytes(default, default, default, default);
252+
253+
operations.FromBgr24Bytes(default, default, default, default);
254+
operations.ToBgr24Bytes(default, default, default, default);
255+
256+
operations.FromBgra32Bytes(default, default, default, default);
257+
operations.ToBgra32Bytes(default, default, default, default);
258+
259+
operations.FromL8Bytes(default, default, default, default);
260+
operations.ToL8Bytes(default, default, default, default);
261+
262+
operations.FromL16Bytes(default, default, default, default);
263+
operations.ToL16Bytes(default, default, default, default);
264+
265+
operations.FromLa16Bytes(default, default, default, default);
266+
operations.ToLa16Bytes(default, default, default, default);
267+
268+
operations.FromLa32Bytes(default, default, default, default);
269+
operations.ToLa32Bytes(default, default, default, default);
270+
271+
operations.FromRgb24Bytes(default, default, default, default);
272+
operations.ToRgb24Bytes(default, default, default, default);
273+
274+
operations.FromRgba32Bytes(default, default, default, default);
275+
operations.ToRgba32Bytes(default, default, default, default);
276+
277+
operations.FromRgb48Bytes(default, default, default, default);
278+
operations.ToRgb48Bytes(default, default, default, default);
279+
280+
operations.FromRgba64Bytes(default, default, default, default);
281+
operations.ToRgba64Bytes(default, default, default, default);
282+
283+
operations.FromBgra5551Bytes(default, default, default, default);
284+
operations.ToBgra5551Bytes(default, default, default, default);
285+
}
286+
161287
/// <summary>
162288
/// This method pre-seeds the <see cref="Image{TPixel}"/> for a given pixel format in the AoT compiler.
163289
/// </summary>
@@ -229,8 +355,10 @@ private static void AotCompileImageEncoderInternals<TPixel>()
229355
where TPixel : unmanaged, IPixel<TPixel>
230356
{
231357
default(BmpEncoderCore).Encode<TPixel>(default, default, default);
358+
default(CurEncoderCore).Encode<TPixel>(default, default, default);
232359
default(ExrEncoderCore).Encode<TPixel>(default, default, default);
233360
default(GifEncoderCore).Encode<TPixel>(default, default, default);
361+
default(IcoEncoderCore).Encode<TPixel>(default, default, default);
234362
default(JpegEncoderCore).Encode<TPixel>(default, default, default);
235363
default(PbmEncoderCore).Encode<TPixel>(default, default, default);
236364
default(PngEncoderCore).Encode<TPixel>(default, default, default);
@@ -249,8 +377,10 @@ private static void AotCompileImageDecoderInternals<TPixel>()
249377
where TPixel : unmanaged, IPixel<TPixel>
250378
{
251379
default(BmpDecoderCore).Decode<TPixel>(default, default, default);
380+
default(CurDecoderCore).Decode<TPixel>(default, default, default);
252381
default(ExrDecoderCore).Decode<TPixel>(default, default, default);
253382
default(GifDecoderCore).Decode<TPixel>(default, default, default);
383+
default(IcoDecoderCore).Decode<TPixel>(default, default, default);
254384
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
255385
default(PbmDecoderCore).Decode<TPixel>(default, default, default);
256386
default(PngDecoderCore).Decode<TPixel>(default, default, default);
@@ -270,11 +400,14 @@ private static void AotCompileImageEncoders<TPixel>()
270400
{
271401
AotCompileImageEncoder<TPixel, WebpEncoder>();
272402
AotCompileImageEncoder<TPixel, BmpEncoder>();
403+
AotCompileImageEncoder<TPixel, CurEncoder>();
273404
AotCompileImageEncoder<TPixel, ExrEncoder>();
274405
AotCompileImageEncoder<TPixel, GifEncoder>();
406+
AotCompileImageEncoder<TPixel, IcoEncoder>();
275407
AotCompileImageEncoder<TPixel, JpegEncoder>();
276408
AotCompileImageEncoder<TPixel, PbmEncoder>();
277409
AotCompileImageEncoder<TPixel, PngEncoder>();
410+
AotCompileImageEncoder<TPixel, QoiEncoder>();
278411
AotCompileImageEncoder<TPixel, TgaEncoder>();
279412
AotCompileImageEncoder<TPixel, TiffEncoder>();
280413
}
@@ -289,11 +422,14 @@ private static void AotCompileImageDecoders<TPixel>()
289422
{
290423
AotCompileImageDecoder<TPixel, WebpDecoder>();
291424
AotCompileImageDecoder<TPixel, BmpDecoder>();
425+
AotCompileImageDecoder<TPixel, CurDecoder>();
292426
AotCompileImageDecoder<TPixel, ExrDecoder>();
293427
AotCompileImageDecoder<TPixel, GifDecoder>();
428+
AotCompileImageDecoder<TPixel, IcoDecoder>();
294429
AotCompileImageDecoder<TPixel, JpegDecoder>();
295430
AotCompileImageDecoder<TPixel, PbmDecoder>();
296431
AotCompileImageDecoder<TPixel, PngDecoder>();
432+
AotCompileImageDecoder<TPixel, QoiDecoder>();
297433
AotCompileImageDecoder<TPixel, TgaDecoder>();
298434
AotCompileImageDecoder<TPixel, TiffDecoder>();
299435
}

src/ImageSharp/Configuration.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Collections.Concurrent;
5+
using System.Diagnostics.CodeAnalysis;
6+
using SixLabors.ImageSharp.Advanced;
57
using SixLabors.ImageSharp.Formats;
68
using SixLabors.ImageSharp.Formats.Bmp;
79
using SixLabors.ImageSharp.Formats.Cur;
@@ -38,6 +40,9 @@ public sealed class Configuration
3840
/// <summary>
3941
/// Initializes a new instance of the <see cref="Configuration" /> class.
4042
/// </summary>
43+
// Every image operation requires a Configuration. Attaching the dependency to its constructors keeps the compile-only
44+
// seed graph visible to modern .NET trimmers without executing that graph or adding module-initialization work.
45+
[DynamicDependency(nameof(AotCompilerTools.SeedPixelOperations), typeof(AotCompilerTools))]
4146
public Configuration()
4247
{
4348
}
@@ -46,6 +51,8 @@ public Configuration()
4651
/// Initializes a new instance of the <see cref="Configuration" /> class.
4752
/// </summary>
4853
/// <param name="configurationModules">A collection of configuration modules to register.</param>
54+
// The default Configuration is created through this overload, while callers may use either constructor.
55+
[DynamicDependency(nameof(AotCompilerTools.SeedPixelOperations), typeof(AotCompilerTools))]
4956
public Configuration(params IImageFormatConfigurationModule[] configurationModules)
5057
{
5158
if (configurationModules != null)

0 commit comments

Comments
 (0)