dotnet/BenchmarkDotNet · error · NotSupportedException

Invalid TFM: '{0}'

Error message

Invalid TFM: '{0}'

What it means

Thrown by the NativeAOT generator when it cannot parse the configured TargetFrameworkMoniker (TFM) string into a RuntimeMoniker enum value. The generator needs the moniker to decide which hardware instruction sets to enumerate for the native compilation. ConfigParser.TryParse strips any '-suffix', then tries Enum.TryParse with dots removed (e.g. 'net80') and with dots replaced by underscores (e.g. 'net_8_0'); if neither matches a RuntimeMoniker value, NotSupportedException is thrown.

Source

Thrown at src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs:267

                """;

            string directoryName = Path.GetDirectoryName(artifactsPaths.ProjectFilePath)!;
            if (directoryName == null)
                throw new InvalidOperationException($"Can't get directory of projectFilePath ('{artifactsPaths.ProjectFilePath}')");

            return new(File.WriteAllTextAsync(Path.Combine(directoryName, GeneratedRdXmlFileName), content, cancellationToken));
        }

        private string GetCurrentInstructionSet(Platform platform)
            => string.Join(",", GetCurrentProcessInstructionSets(platform));

        // based on https://github.com/dotnet/runtime/tree/v10.0.0-rc.1.25451.107/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt
        private IEnumerable<string> GetCurrentProcessInstructionSets(Platform platform)
        {
            if (!ConfigParser.TryParse(TargetFrameworkMoniker, out RuntimeMoniker runtimeMoniker))
            {
                throw new NotSupportedException($"Invalid TFM: '{TargetFrameworkMoniker}'");
            }

            // TFM is the MSBuild moniker, not the BDN moniker, so it resolves to Net10_0 instead of NativeAot10_0.
            // TODO: Get the correct moniker from the NativeAotRuntime (#2609)
            runtimeMoniker += RuntimeMoniker.NativeAot70 - RuntimeMoniker.Net70;

            if (platform == RuntimeInformation.GetCurrentPlatform() // "native" does not support cross-compilation (so does BDN for now)
                && runtimeMoniker >= RuntimeMoniker.NativeAot80)
            {
                yield return "native"; // added in .NET 8 https://github.com/dotnet/runtime/pull/87865
                yield break;
            }

            switch (platform)
            {
                case Platform.X86:
                case Platform.X64:
                    if (HardwareIntrinsics.IsX86BaseSupported) yield return "base";

View on GitHub (pinned to b515068b61)

Solutions

  1. Use an exact, supported TFM string matching a RuntimeMoniker enum value: "net8.0", "net9.0", "net10.0", "net11.0" (check the RuntimeMoniker enum for your BDN version)
  2. Avoid passing a raw TFM via the builder; instead use a recognized RuntimeMoniker through NativeAotRuntime (e.g. NativeAotRuntime.NativeAot80) so the correct MsBuildMoniker is supplied
  3. Upgrade BenchmarkDotNet to a release that adds support for the TFM you are targeting

Example fix

// before
var toolchain = NativeAotToolchain.CreateBuilder()
    .UseNuGet()
    .TargetFrameworkMoniker("net8")  // wrong: no matching RuntimeMoniker
    .ToToolchain();

// after (exact TFM)
var toolchain = NativeAotToolchain.CreateBuilder()
    .UseNuGet()
    .TargetFrameworkMoniker("net8.0")
    .ToToolchain();

// after (preferred: use a known runtime)
var toolchain = NativeAotToolchain.CreateBuilder()
    .UseNuGet()
    .TargetFrameworkMoniker(RuntimeMoniker.NativeAot80.MsBuildMoniker())
    .ToToolchain();
Defensive patterns

Strategy: validation

Validate before calling

// Validate a TFM string resolves to a RuntimeMoniker before configuring the NativeAOT toolchain.
using BenchmarkDotNet.ConsoleArguments;
using BenchmarkDotNet.Environments;

static string EnsureValidTfm(string tfm)
{
    if (!ConfigParser.TryParse(tfm, out RuntimeMoniker _))
        throw new ArgumentException(
            $"TFM '{tfm}' does not map to any RuntimeMoniker. Use e.g. 'net8.0', 'net9.0', 'net10.0'.",
            nameof(tfm));
    return tfm;
}

var tfm = EnsureValidTfm(myTfm);
var toolchain = NativeAotToolchain.CreateBuilder()
    .UseNuGet()
    .TargetFrameworkMoniker(tfm)
    .ToToolchain();

Try / catch

// Catch NotSupportedException around benchmark execution if the TFM is user-supplied.
try { BenchmarkRunner.Run<MyBench>(); }
catch (NotSupportedException ex) when (ex.Message.Contains("Invalid TFM"))
{
    throw new ArgumentException($"Configured TargetFrameworkMoniker is not recognized by BDN: {ex.Message}", ex);
}

Prevention

When it happens

Trigger: Called from GetCurrentProcessInstructionSets(Platform) → GetCurrentInstructionSet, which runs during NativeAOT csproj generation (Generate or its project-building path). The TFM string originates from the NativeAotToolchain / builder's .TargetFrameworkMoniker(...) call. Any string that does not map to a RuntimeMoniker enum member (after stripping after '-') triggers it: typos like 'net8', unsupported previews like 'net12.0', or arbitrary custom strings.

Common situations: Passing .TargetFrameworkMoniker("net8") instead of "net8.0". Using a future TFM that the installed BDN version does not yet know. Constructing a NativeAotToolchain with a hand-typed moniker that doesn't exactly match an enum entry. Upgrading the .NET SDK to a preview TFM without upgrading BDN.

Related errors


AI-assisted analysis of dotnet/BenchmarkDotNet@b515068b61 (2026-08-13). Data as JSON: /api/errors/650fc1e20d9046d7. Report an issue: GitHub.