The runtime builds for net20, netstandard2.0, and net9.0. The virtualizer and unit tests target .NET 9. The sample builds for 25 configured target frameworks. The matrix runner has 26 entries because Framework 3.0 uses the same net20 sample on the CLR 2.0 line.

Execution groupMatrix entriesResult and boundary
Framework 2.0–3.5net20, mapped net30, net35The original and protected sample output matched for all three entries in x86 and x64. Framework 3.0 uses the CLR 2.0 line and the net20 sample.
Framework 4.0–4.8.1net40 through net481Original and protected output matched on the installed Framework 4.8.1 in-place CLR in x86 and x64 processes; separate historical 4.x runtimes were not tested.
.NET Core 2.0–3.1netcoreapp2.0 through netcoreapp3.1Original and protected output matched for all five entries in x86 and x64. Numeric opcode comparisons and a Single-precision stack probe also passed.
.NET 5–10net5.0 through net10.0Original and protected output matched for all six entries in x86 and x64. Numeric opcode comparisons and a Single-precision stack probe also passed.

Builds and runs answer different questions

Each matrix run executes the original sample, protects a copy of that same assembly, then compares the protected output line by line with the original output. The sample also runs CLR-generated comparisons for every implemented numeric opcode across selected integer endpoints, checked and unchecked overflow, division/remainder, shift counts around and beyond operand widths, conversion boundaries, signed zero, subnormals, infinities, NaN, and a multi-instruction Single-precision stack case. A protected/original difference or numeric result/exception mismatch fails the run. These are targeted cases, not an exhaustive numeric proof. All 26 configured entries passed at both pointer widths, for 52 protected sample runs.

Framework 4.x uses the machine's .NET Framework 4.8.1 in-place CLR, so those runs verify target-specific sample builds under that runtime, not the behavior of each historical 4.x runtime installed separately. The Framework 3.0 entry reuses the Framework 2.0 sample because the old SDK has no separate 3.0 reference pack.

Verified scope

The Release solution builds 25 configured sample targets. Original-versus-protected output matched on all 26 matrix entries in x86 and x64 processes, from Framework 2.0 through 4.8.1, Core 2.0 through 3.1, and .NET 5 through 10. All 25 unit tests pass on the .NET 9 x64 host. Framework 4.x execution uses the installed 4.8.1 in-place CLR.

This matrix verifies behavior for the current sample and implemented numeric comparisons; it is not a performance benchmark. The dispatch, stack, and decode changes are described in Reducing repeated work in the interpreter, with timing results scoped to that post's workload.

Why the old target is separate

.NET Standard 2.0 does not cover Framework 2.0, so the runtime is also compiled against the older framework surface. The virtualizer bundles that net20 runtime beside a protected output when one is not already present. The protected methods use ordinary arrays and a call to the shared Execute(VirtualMachineProgram, object[]) entrypoint, keeping their rewritten stubs compatible with the old target.

For Framework 4.6.1 and later, .NET Standard 2.0 is the intended shared library path. Microsoft's documentation notes that .NET Framework 4.6.1 has known .NET Standard consumption issues and recommends 4.7.2 or later. See Microsoft's .NET Standard support matrix and the .NET Framework porting overview.

Keep the compatibility claim narrow

The matrix covers the implemented scalar and string subset, including branches, a loop, switch dispatch, supported locals, numeric edge comparisons, and the single-precision stack case. It does not establish full CLR compatibility. Calls, fields, arrays, byrefs, pointers, and exception regions remain unsupported; stack validation is limited to the translated subset. A method containing an unsupported opcode is left unchanged and reported, so an application may contain both virtualized and ordinary methods. See M3 - Control flow for the current branch and local coverage.

← Back to all notes