Skip to content

Commit

Permalink
Make things smaller and add support for .NET 8
Browse files Browse the repository at this point in the history
* Merge `.managedcode` into `.text`.
* Remove `string[]` from `Main` that brings in a bunch of garbage
* Add support for .NET 8 (but keep the projects at .NET 7). Upgrading to .NET 8 will be a size improvement for Ocean, but a regression for the rest.
* Remove a couple things from the .props file that don't do anything.
* Update README.md with new sizes
  • Loading branch information
MichalStrehovsky committed Sep 12, 2023
1 parent 4ad35b2 commit 030febd
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 12 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ SmolSharp is a repository that demonstrates the ability to use NativeAOT to buil
<OptimizationPreference>Size</OptimizationPreference>
<PublishTrimmed>true</PublishTrimmed>
```
With the `SmolSharp.props` file being imported, the compiler produces a binary that is only `2069` bytes in size - a 0.07% of the original file-size.
With the `SmolSharp.props` file being imported, the compiler produces a binary that is only `2021` bytes in size - a 0.07% of the original file-size.

## Project overview
| Project Name | Binary size | Description |
| ---------------- | ----------- | ------------------------------------------------------------------ |
| HelloWorld | 2069 B | A console program that outputs "Hello World".
| Mandelbrot | 3197 B | A windowed program that renders a fractal (the Mandelbrot set).
| Ocean | 7629 B | A windowed OpenGL program that renders a ray-marched stylized ocean.
| HelloWorld | 2021 B | A console program that outputs "Hello World".
| Mandelbrot | 2879 B | A windowed program that renders a fractal (the Mandelbrot set).
| Ocean | 7316 B | A windowed OpenGL program that renders a ray-marched stylized ocean.

https://github.com/ascpixi/smolsharp/assets/44982772/c70e1e20-7cef-473d-bbf5-67079bec2487
###### Screen capture of the Ocean demo

## How does it work
All of the functionality of SmolSharp is contained in the `SmolSharp.props` file. The following techniques are employed in order to achieve minimal binary sizes:
1. **Custom standard library** - SmolSharp uses the BFlat zerolib standard library, serving as the primary size-saving technique. However, this results in the lack of any kind of GC and removes all built-in BCL classes and functionality, requiring the use of raw P/Invokes to interface with Windows' APIs.
1. **Custom standard library** - SmolSharp uses the [bflat zerolib](https://github.com/bflattened/bflat/tree/master/src/zerolib) standard library, serving as the primary size-saving technique. However, this results in the lack of any kind of GC and removes all built-in BCL classes and functionality, requiring the use of raw P/Invokes to interface with Windows' APIs.
2. **Raw P/Invokes** - all external `[DllImport]` declarations are specified in the `<DirectPInvoke>` list in the MSBuild `.props` file, removing the need for a dynamic loader. To prevent redundant `RhpReversePInvoke` calls, every `[DllImport]` is marked with the `[SuppressGCTransition]` attribute.
3. **ILC configuration** - several MSBuild properties instruct the IL compiler (ILC) to optimize and generate code with binary size as its top priority. All Win32 resources (usually embedded in the `.rsrc` section) are omitted by setting the internal property `_Win32ResFile` to an empty string, in a target that executes before the `LinkNative` target.
3. **ILC configuration** - several MSBuild properties instruct the IL compiler (ILC) to optimize and generate code with binary size as its top priority. All Win32 resources (usually embedded in the `.rsrc` section) are omitted by setting the internal property `_Win32ResFile` to an empty string, in a target that executes before the `LinkNative` target (or for .NET 8+, by setting an undocumented property).
4. **Native object file manipulation** - the alignment of all sections in the native object file is set to their minimum accepted value using `objcopy`. Additionally, since no exception handling is used, the SEH exception data directory (the `.pdata` section) is removed.
5. **Linker flags** - several MSVC linker flags are specified, significantly reducing the size of the final binary image:
- `/align:16` - sets section alignment to 16 bytes, which, based on testing, is the minimum accepted value
Expand All @@ -35,6 +35,7 @@ All of the functionality of SmolSharp is contained in the `SmolSharp.props` file
- `/nodefaultlib` - excludes CRT libraries from the binary
- `/fixed` - instructs the operating system to load the binary at a static address, disabling relocations and making the linker skip emitting the `.reloc` section
- `/merge:.modules=.rdata` - merges the `.modules` and `.rdata` sections due to their identical attributes.
- `/merge:.managedcode=.text` - merges the `.managedcode` and `.text` sections due to their identical attributes.
6. **Finishing touches** - all trailing null bytes are stripped from the binary.
Please note that this is not bound to MSBuild - this can also be achieved with calling `ilc` calling the linker manually, as demonstrated by the [`MichalStrehovsky/zerosharp`](https://github.com/MichalStrehovsky/zerosharp) repository.

Expand Down
2 changes: 1 addition & 1 deletion src/SmolSharp.Mandelbrot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace SmolSharp.Raytracer
{
internal static class Program
{
static void Main(string[] args) { }
static void Main() { }

[UnmanagedCallersOnly(EntryPoint = "smolsharp_main")]
public static unsafe int UnmanagedMain(nint hInstance, nint hPrevInstance, char* pCmdLine, int nCmdShow)
Expand Down
2 changes: 1 addition & 1 deletion src/SmolSharp.Ocean/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal class Program
static ushort windowWidth, windowHeight;
static bool windowDimensionsChanged;

static void Main(string[] args) { }
static void Main() { }

[UnmanagedCallersOnly(EntryPoint = "smolsharp_main")]
public static unsafe int UnmanagedMain(nint hInstance, nint hPrevInstance, char* pCmdLine, int nCmdShow)
Expand Down
18 changes: 14 additions & 4 deletions src/SmolSharp.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
<IlcSystemModule>$(MSBuildProjectName)</IlcSystemModule>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EntryPointSymbol>smolsharp_main</EntryPointSymbol>
<PublishTrimmed>true</PublishTrimmed>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
<DebugType>none</DebugType>
</PropertyGroup>

Expand All @@ -41,16 +39,19 @@
<LinkerArg Include="/nodefaultlib"></LinkerArg>
<LinkerArg Include="/fixed"></LinkerArg> <!-- disabling relocations makes the linker skip emitting the .reloc section, which saves us about ~100 bytes -->
<LinkerArg Include="/merge:.modules=.rdata"></LinkerArg>
<LinkerArg Include="/merge:.managedcode=.text"></LinkerArg>
<LinkerArg Include="user32.lib"></LinkerArg>
<LinkerArg Include="shell32.lib"></LinkerArg>
<LinkerArg Include="gdi32.lib"></LinkerArg>
</ItemGroup>

<PropertyGroup>
<IlcDisableReflection>true</IlcDisableReflection>
<DynamicCodeSupport>false</DynamicCodeSupport>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<StackTraceSupport>false</StackTraceSupport> <!-- .NET 8+ -->
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData> <!-- < .NET 8 -->
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<IlcDehydrate>false</IlcDehydrate>
<IlcGenerateWin32Resources>false</IlcGenerateWin32Resources> <!-- .NET 8 -->
<Optimize>true</Optimize>
<OptimizationPreference>Size</OptimizationPreference>
</PropertyGroup>
Expand Down Expand Up @@ -91,6 +92,15 @@
<TrimNullBytes TargetPath="$(NativeBinary)"/>
</Target>

<Target Name="RemoveIlcSwitches" BeforeTargets="IlcCompile" DependsOnTargets="WriteIlcRspFileForCompilation">
<ItemGroup>
<IlcArg Remove="--runtimeknob:RUNTIME_IDENTIFIER=win-x64" />
<IlcArg Remove="--resilient" />
</ItemGroup>
<WriteLinesToFile File="%(ManagedBinary.IlcRspFile)" Lines="@(IlcArg)" Overwrite="true" WriteOnlyWhenDifferent="true" />
</Target>

<!-- < .NET 8 -->
<Target Name="RemoveWin32Resources" BeforeTargets="LinkNative" Outputs="$(_Win32ResFile)">
<PropertyGroup>
<_Win32ResFile></_Win32ResFile>
Expand Down

0 comments on commit 030febd

Please sign in to comment.