.NET 10 IL virtualization library. Mark methods with [Virtualize] — the build pipeline automatically translates their CIL to a custom VM bytecode and replaces the original method bodies before AOT compilation.
Works with NativeAOT. Zero warnings on dotnet publish /p:PublishAot=true.
C# source
│
▼ CoreCompile
IL (readable in ILSpy)
│
▼ VMBytecodeWeave (AfterTargets="CoreCompile", BeforeTargets="ILLink")
IL stubs → VMDispatcher.Execute(__vmBody, args[])
VM bytecode stored as byte[] in static fields (not human-readable)
│
▼ ILLink (trimmer) → ILCompiler (AOT)
native binary
The weaver runs as an MSBuild task using Mono.Cecil. It:
- Finds all methods tagged
[Virtualize] - Translates their CIL to a custom stack-based VM instruction set
- Injects AOT-safe call stubs (concrete static methods, no reflection)
- Replaces the original method bodies with
VMDispatcher.Execute()calls
Add the reference to your project:
<ItemGroup>
<PackageReference Include="VMBytecode.Weaver" Version="1.0.0" />
</ItemGroup>The .targets file inside the package wires the weave step automatically. Tag any method you want to protect:
using VMBytecode;
public class MyService
{
[Virtualize]
public int SecretAlgorithm(int input)
{
return input * 42 + 7;
}
[Virtualize]
public bool Validate(string key)
{
// your logic here
return key.Length > 8 && key[0] == 'K';
}
}Build and publish normally:
# Debug run
dotnet run
# AOT publish
dotnet publish -r linux-x64 -c ReleaseBefore:
public int Fibonacci(int n)
{
if (n <= 1) return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}After:
public int Fibonacci(int n)
{
return (int)VMDispatcher.Execute(__vmBody_Fibonacci_3, new object[] { this, n });
}The original logic lives only inside __vmBody_Fibonacci_3 as an opaque byte[].
src/
VMBytecode.Runtime/ Core VM — ships with your app (AOT-safe)
VirtualizeAttribute.cs [Virtualize] attribute
VMOpcode.cs Custom instruction set
VMMethodBody.cs Bytecode + resolved call/field/type tables
VMDispatcher.cs Stack-based interpreter
VMBytecode.Weaver/ MSBuild task — build-time only, not shipped
WeaverTask.cs MSBuild Task entry point
ILTranslator.cs CIL → VM bytecode (2-pass branch fixup)
build/
VMBytecode.Weaver.targets Auto-imported MSBuild hook
samples/
SampleApp/ Demo project
| Category | Operations |
|---|---|
| Constants | ldc.i4, ldc.i8, ldc.r4, ldc.r8, ldstr, ldnull |
| Args / Locals | ldarg, starg, ldloc, stloc |
| Fields | ldfld, stfld, ldsfld, stsfld |
| Arithmetic | add, sub, mul, div, rem, neg |
| Bitwise | and, or, xor, not, shl, shr, shr.un |
| Comparisons | ceq, clt, cgt, clt.un, cgt.un |
| Branches | br, brtrue, brfalse, beq, bne, blt, bgt, ble, bge (and .un variants) |
| Calls | call, callvirt, newobj, ret |
| Arrays | newarr, ldelem, stelem, ldlen |
| Conversions | conv.i1/i2/i4/i8/u1/u2/u4/u8/r4/r8 |
| Type ops | box, unbox.any, castclass, isinst |
| Misc | dup, pop, throw, switch, initobj |
| Concern | Status |
|---|---|
No Reflection.Emit / DynamicMethod |
✅ |
Call stubs are concrete static methods (ldftn) |
✅ trimmer-safe |
Type references use ldtoken + Type.GetTypeFromHandle |
✅ |
[UnconditionalSuppressMessage] injected into weaved methods |
✅ |
ILLink + ILCompiler see weaved IL, never original |
✅ |
Tested: dotnet publish -r linux-x64 /p:PublishAot=true — 0 AOT warnings.
| Property | Default | Description |
|---|---|---|
VMBytecodeWeaveEnabled |
true |
Set to false to skip weaving (e.g. during debugging) |
VMBytecodeVerbose |
false |
Log each virtualized method name during build |
<!-- Disable weaving for Debug config only -->
<VMBytecodeWeaveEnabled Condition="'$(Configuration)' == 'Debug'">false</VMBytecodeWeaveEnabled>- .NET 10 SDK
- Mono.Cecil 0.11.6 (weaver dependency, not shipped to end users)
git clone https://github.com/aizen218/VMBytecode
cd VMBytecode
dotnet build
dotnet run --project samples/SampleApp