|
| 1 | +# Trimming and NativeAOT |
| 2 | + |
| 3 | +The kernel is published with `IsAotCompatible`, which is a claim to consumers: a trimmer takes it |
| 4 | +as licence to remove code *inside* `AngouriMath.dll`, and the NativeAOT compiler keeps no |
| 5 | +reflection metadata for a member nothing referenced. The claim is only as good as what checks it, |
| 6 | +and what checks it is `Sources/Tests/AotSmokeTest`. |
| 7 | + |
| 8 | +Run it the way CI does: |
| 9 | + |
| 10 | +``` |
| 11 | +dotnet publish Sources/Tests/AotSmokeTest/AotSmokeTest.csproj -c Release -r linux-x64 -o artifacts/trimmed |
| 12 | +./artifacts/trimmed/AotSmokeTest |
| 13 | +
|
| 14 | +dotnet publish Sources/Tests/AotSmokeTest/AotSmokeTest.csproj -c Release -r linux-x64 -p:AngouriMathPublishAot=true -o artifacts/aot |
| 15 | +./artifacts/aot/AotSmokeTest |
| 16 | +``` |
| 17 | + |
| 18 | +`-r` is required for both — trimming and ILC are per-runtime-identifier. On Linux the NativeAOT |
| 19 | +half also needs `clang` and `zlib1g-dev`; on Windows the MSVC build tools; on macOS the Xcode |
| 20 | +command line tools. |
| 21 | + |
| 22 | +## Two things it checks, and they are not the same |
| 23 | + |
| 24 | +**Publishing.** The project sets `TrimmerSingleWarn=false` so a warning names a line rather than an |
| 25 | +assembly, and `ILLinkTreatWarningsAsErrors` / `IlcTreatWarningsAsErrors` so any IL2xxx or IL3xxx |
| 26 | +from the kernel fails the publish. `TreatWarningsAsErrors` alone does not cover either of those: |
| 27 | +Roslyn, the trimmer and the NativeAOT compiler each have their own switch. |
| 28 | + |
| 29 | +**Running.** The binary is then executed, and its exit code is the gate. This half is not |
| 30 | +redundant. Everything that goes wrong here goes wrong *silently at publish time*: a member removed |
| 31 | +because only a string named it produces no warning at all, and the failure is a `null` from |
| 32 | +`Type.GetMethod` or a "no coercion operator is defined between types" from `Expression.Convert` |
| 33 | +some number of calls later. Publishing proves nothing about that; running does. |
| 34 | + |
| 35 | +The smoke test covers parse, `Simplify`, `Solve`, `Differentiate`, `Integrate`, `Limit` and |
| 36 | +`Compile`, asserts values rather than printed forms, and returns non-zero on any mismatch. |
| 37 | + |
| 38 | +## What breaks a trimmed build |
| 39 | + |
| 40 | +Anything that names a member at run time. |
| 41 | + |
| 42 | +- `Type.GetMethod("Name")`, `Activator.CreateInstance(type)`, assembly scanning. Use a table |
| 43 | + generated beside what it dispatches to; `MathAllMethods.Definitions` is the worked example, and |
| 44 | + its entries are expression trees precisely because the compiler emits an `ldtoken` for the call |
| 45 | + inside one, which is how C# names a method without a string. |
| 46 | +- `Type.MakeGenericType` over a value type, which NativeAOT cannot promise (IL3050). Where the set |
| 47 | + of types is closed, write the closed set down: `CompilationProtocol.nullableForms`. |
| 48 | +- Letting `System.Linq.Expressions` find an operator or a conversion for you. |
| 49 | + `Expression.Add(l, r)` reflects over the operand type looking for `op_Addition`, and |
| 50 | + `Expression.Convert(e, t)` reflects over the target type looking for `op_Implicit`. For `double` |
| 51 | + and `long` those are IL instructions and there is nothing to find, but for |
| 52 | + `System.Numerics.Complex` and `BigInteger` they are methods, and a NativeAOT build had no |
| 53 | + metadata for them: `x + 1` compiled over a complex argument threw "the binary operator Add is not |
| 54 | + defined for the types 'System.Numerics.Complex' and 'System.Numerics.Complex'". Pass the method |
| 55 | + in — `CompilationProtocol.operators` and `CompilationProtocol.conversionOperators`. |
| 56 | + |
| 57 | +`Expression.Compile()` itself is fine: under NativeAOT it falls back to the interpreter rather than |
| 58 | +emitting IL, so a compiled expression is slower there than under the JIT but it is not broken. |
| 59 | +See [#363](https://github.com/asc-community/AngouriMath/issues/363). |
| 60 | + |
| 61 | +## When you add a node, a function or a protocol |
| 62 | + |
| 63 | +Add the case to the smoke test as well. It is a few lines, it is the only place the trimmed and |
| 64 | +NativeAOT behaviour of the new path is measured, and the publish will not tell you it is missing. |
0 commit comments