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
- 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)
- 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
- 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
- Always pass exact MSBuild TFMs (net8.0, net9.0, net10.0, net11.0) — never shortened forms like 'net8'.
- Prefer NativeAotRuntime constants (e.g. NativeAotRuntime.NativeAot80) over hand-typed moniker strings.
- Upgrade BDN alongside .NET SDK upgrades so new TFMs are recognized by ConfigParser.TryParse.
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
- {0} provided as ilcPackages does NOT exist
- RuntimeMoniker not supported
- Runtime moniker {runtimeMoniker} is not supported
- Runtime moniker {runtimeMoniker} is not supported
- Runtime {runtimeId} is not supported
AI-assisted analysis of dotnet/BenchmarkDotNet@b515068b61 (2026-08-13).
Data as JSON: /api/errors/650fc1e20d9046d7.
Report an issue: GitHub.