diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs
index cf7df717..cb089ed4 100644
--- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs
+++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs
@@ -1639,18 +1639,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
if (_moduleManager.TryGetExport(nid, out ExportedFunction export))
{
- if (IsKernelLibrary(export.LibraryName))
+ var preferLleForLibc = IsLibcLibrary(export.LibraryName) && PreferLleForLibcExport(export.Name);
+ if (!ShouldResolveRegisteredExportViaLle(export, preferLleForLibc))
{
- if (_logAllImports)
+ if (_logAllImports && IsKernelLibrary(export.LibraryName))
{
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} ({export.LibraryName}:{export.Name}) -> HLE (kernel library)");
}
return false;
}
- if (!IsLibcLibrary(export.LibraryName) || !PreferLleForLibcExport(export.Name))
- {
- return false;
- }
if (TryResolveRuntimeSymbolAddress(nid, out var value2) && IsDirectImportTargetUsable(value2))
{
targetAddress = value2;
@@ -1704,6 +1701,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return false;
}
+ internal static bool ShouldResolveRegisteredExportViaLle(
+ ExportedFunction export,
+ bool preferLleForLibc)
+ {
+ ArgumentNullException.ThrowIfNull(export);
+ return !IsKernelLibrary(export.LibraryName) && (export.PreferLle || preferLleForLibc);
+ }
+
private static bool IsHlePreferredNid(string nid)
{
return string.Equals(nid, "QrZZdJ8XsX0", StringComparison.Ordinal) ||
diff --git a/src/SharpEmu.HLE/ExportedFunction.cs b/src/SharpEmu.HLE/ExportedFunction.cs
index 4e9eec3e..d11e7bf1 100644
--- a/src/SharpEmu.HLE/ExportedFunction.cs
+++ b/src/SharpEmu.HLE/ExportedFunction.cs
@@ -5,7 +5,13 @@ namespace SharpEmu.HLE;
public sealed class ExportedFunction
{
- public ExportedFunction(string libraryName, string nid, string name, Generation target, SysAbiFunction function)
+ public ExportedFunction(
+ string libraryName,
+ string nid,
+ string name,
+ Generation target,
+ SysAbiFunction function,
+ bool preferLle = false)
{
ArgumentException.ThrowIfNullOrWhiteSpace(libraryName);
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
@@ -17,6 +23,7 @@ public sealed class ExportedFunction
Name = name;
Target = target;
Function = function;
+ PreferLle = preferLle;
}
public string LibraryName { get; }
@@ -28,4 +35,10 @@ public sealed class ExportedFunction
public Generation Target { get; }
public SysAbiFunction Function { get; }
+
+ ///
+ /// A loaded guest export is authoritative for this registration. The HLE function
+ /// remains available as an explicit fallback when no usable guest target exists.
+ ///
+ public bool PreferLle { get; }
}
diff --git a/src/SharpEmu.HLE/SysAbiExportAttribute.cs b/src/SharpEmu.HLE/SysAbiExportAttribute.cs
index 6a412205..72c3f15c 100644
--- a/src/SharpEmu.HLE/SysAbiExportAttribute.cs
+++ b/src/SharpEmu.HLE/SysAbiExportAttribute.cs
@@ -3,7 +3,7 @@
namespace SharpEmu.HLE;
-[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public sealed class SysAbiExportAttribute : Attribute
{
public string LibraryName { get; set; } = "libKernel";
@@ -13,4 +13,11 @@ public sealed class SysAbiExportAttribute : Attribute
public string ExportName { get; set; } = string.Empty;
public Generation Target { get; set; } = Generation.None;
+
+ ///
+ /// Prefer a matching export from a loaded guest module and use this handler only
+ /// as the explicit fallback when that LLE provider is unavailable. Individual
+ /// handlers define whether that fallback is fail-closed or compatibility behavior.
+ ///
+ public bool PreferLle { get; set; }
}
diff --git a/src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs b/src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs
index c2ef1f10..4fefc434 100644
--- a/src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs
+++ b/src/SharpEmu.SourceGenerators/SysAbiExportAnalyzer.cs
@@ -102,17 +102,16 @@ public sealed class SysAbiExportAnalyzer : DiagnosticAnalyzer
ConcurrentDictionary exportsByNid)
{
var method = (IMethodSymbol)context.Symbol;
- AttributeData? exportAttribute = null;
+ var exportAttributes = ImmutableArray.CreateBuilder();
foreach (var attribute in method.GetAttributes())
{
if (SysAbiExportShape.IsSysAbiExportAttribute(attribute.AttributeClass))
{
- exportAttribute = attribute;
- break;
+ exportAttributes.Add(attribute);
}
}
- if (exportAttribute is null)
+ if (exportAttributes.Count == 0)
{
return;
}
@@ -135,6 +134,28 @@ public sealed class SysAbiExportAnalyzer : DiagnosticAnalyzer
SysAbiDiagnostics.HandlerNotAccessible, location, methodDisplay));
}
+ foreach (var exportAttribute in exportAttributes)
+ {
+ AnalyzeExportAttribute(
+ context,
+ catalogNames,
+ exportsByNid,
+ method,
+ exportAttribute,
+ location,
+ methodDisplay);
+ }
+ }
+
+ private static void AnalyzeExportAttribute(
+ SymbolAnalysisContext context,
+ HashSet? catalogNames,
+ ConcurrentDictionary exportsByNid,
+ IMethodSymbol method,
+ AttributeData exportAttribute,
+ Location location,
+ string methodDisplay)
+ {
var arguments = SysAbiExportShape.ReadArguments(exportAttribute);
var hasNid = !string.IsNullOrWhiteSpace(arguments.Nid);
var hasName = !string.IsNullOrWhiteSpace(arguments.ExportName);
@@ -188,9 +209,9 @@ public sealed class SysAbiExportAnalyzer : DiagnosticAnalyzer
}
}
- var existing = exportsByNid.GetOrAdd(effectiveNid, method);
- if (!SymbolEqualityComparer.Default.Equals(existing, method))
+ if (!exportsByNid.TryAdd(effectiveNid, method))
{
+ var existing = exportsByNid[effectiveNid];
context.ReportDiagnostic(Diagnostic.Create(
SysAbiDiagnostics.DuplicateNid,
location,
diff --git a/src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs b/src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs
index 6e8b8e9c..ac2f9793 100644
--- a/src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs
+++ b/src/SharpEmu.SourceGenerators/SysAbiExportGenerator.cs
@@ -27,7 +27,16 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
private sealed class ExportModel : IEquatable
{
- public ExportModel(string containingType, string methodName, SysAbiExportShape.HandlerShape shape, string typedParameterKinds, string libraryName, string nid, string exportName, int target)
+ public ExportModel(
+ string containingType,
+ string methodName,
+ SysAbiExportShape.HandlerShape shape,
+ string typedParameterKinds,
+ string libraryName,
+ string nid,
+ string exportName,
+ int target,
+ bool preferLle)
{
ContainingType = containingType;
MethodName = methodName;
@@ -37,6 +46,7 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
Nid = nid;
ExportName = exportName;
Target = target;
+ PreferLle = preferLle;
}
public string ContainingType { get; }
@@ -51,6 +61,7 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
public string Nid { get; }
public string ExportName { get; }
public int Target { get; }
+ public bool PreferLle { get; }
public bool Equals(ExportModel? other) =>
other is not null &&
@@ -61,7 +72,8 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
LibraryName == other.LibraryName &&
Nid == other.Nid &&
ExportName == other.ExportName &&
- Target == other.Target;
+ Target == other.Target &&
+ PreferLle == other.PreferLle;
public override bool Equals(object? obj) => Equals(obj as ExportModel);
@@ -73,6 +85,7 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
hash = (hash * 31) + ContainingType.GetHashCode();
hash = (hash * 31) + MethodName.GetHashCode();
hash = (hash * 31) + Nid.GetHashCode();
+ hash = (hash * 31) + PreferLle.GetHashCode();
return hash;
}
}
@@ -80,80 +93,90 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- var exports = context.SyntaxProvider
+ var exportGroups = context.SyntaxProvider
.ForAttributeWithMetadataName(
AttributeMetadataName,
static (node, _) => node is MethodDeclarationSyntax,
- static (attributeContext, _) => CreateModel(attributeContext))
- .Where(static model => model is not null)
+ static (attributeContext, _) => CreateModels(attributeContext))
+ .Where(static models => !models.IsDefaultOrEmpty)
.Collect();
var assemblyName = context.CompilationProvider
.Select(static (compilation, _) => compilation.AssemblyName ?? "Assembly");
context.RegisterSourceOutput(
- exports.Combine(assemblyName),
+ exportGroups.Combine(assemblyName),
static (productionContext, source) => Emit(productionContext, source.Left!, source.Right));
}
- private static ExportModel? CreateModel(GeneratorAttributeSyntaxContext context)
+ private static ImmutableArray CreateModels(GeneratorAttributeSyntaxContext context)
{
if (context.TargetSymbol is not IMethodSymbol method ||
!SysAbiExportShape.IsAccessibleFromGeneratedCode(method))
{
- return null;
+ return ImmutableArray.Empty;
}
var shape = SysAbiExportShape.Classify(method, out var typedParameterKinds);
if (shape == SysAbiExportShape.HandlerShape.Invalid)
{
- return null;
+ return ImmutableArray.Empty;
}
- var attribute = context.Attributes[0];
- var arguments = SysAbiExportShape.ReadArguments(attribute);
- var nid = arguments.Nid;
- var exportName = arguments.ExportName;
-
- // Mirror ModuleManager.ResolveExportInfo: a missing NID resolves from the export
- // name (algorithmically — equivalent to the runtime catalog lookup, which was
- // built with the same computation); a missing name falls back to the method name.
- if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName))
+ var models = ImmutableArray.CreateBuilder(context.Attributes.Length);
+ foreach (var attribute in context.Attributes)
{
- nid = Ps5Nid.Compute(exportName);
+ var arguments = SysAbiExportShape.ReadArguments(attribute);
+ var nid = arguments.Nid;
+ var exportName = arguments.ExportName;
+
+ // Mirror ModuleManager.ResolveExportInfo: a missing NID resolves from the
+ // export name. A missing name falls back to the method name.
+ if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName))
+ {
+ nid = Ps5Nid.Compute(exportName);
+ }
+
+ if (string.IsNullOrWhiteSpace(nid))
+ {
+ continue;
+ }
+
+ if (string.IsNullOrWhiteSpace(exportName))
+ {
+ exportName = method.Name;
+ }
+
+ var libraryName = string.IsNullOrWhiteSpace(arguments.LibraryName) ? "libKernel" : arguments.LibraryName;
+ models.Add(new ExportModel(
+ method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
+ method.Name,
+ shape,
+ typedParameterKinds,
+ libraryName,
+ nid!,
+ exportName!,
+ arguments.Target,
+ arguments.PreferLle));
}
- if (string.IsNullOrWhiteSpace(nid))
- {
- return null;
- }
-
- if (string.IsNullOrWhiteSpace(exportName))
- {
- exportName = method.Name;
- }
-
- var libraryName = string.IsNullOrWhiteSpace(arguments.LibraryName) ? "libKernel" : arguments.LibraryName;
- return new ExportModel(
- method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
- method.Name,
- shape,
- typedParameterKinds,
- libraryName,
- nid!,
- exportName!,
- arguments.Target);
+ return models.ToImmutable();
}
private static void Emit(
SourceProductionContext context,
- ImmutableArray exports,
+ ImmutableArray> exportGroups,
string assemblyName)
{
// No exports, no registry: an assembly that merely references the analyzer
// (e.g. SharpEmu.HLE itself) must not mint a colliding
// SharpEmu.Generated.SysAbiExportRegistry type.
- if (exports.IsDefaultOrEmpty)
+ var exportCount = 0;
+ foreach (var group in exportGroups)
+ {
+ exportCount += group.Length;
+ }
+ if (exportCount == 0)
{
return;
}
@@ -175,24 +198,23 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
builder.AppendLine(" public static global::System.Collections.Generic.IReadOnlyList CreateExports(");
builder.AppendLine(" global::SharpEmu.HLE.Generation registrationGeneration)");
builder.AppendLine(" {");
- builder.AppendLine($" var exports = new global::System.Collections.Generic.List({exports.Length});");
+ builder.AppendLine($" var exports = new global::System.Collections.Generic.List({exportCount});");
- foreach (var export in exports)
+ foreach (var group in exportGroups)
{
- if (export is null)
+ foreach (var export in group)
{
- continue;
+ var function = export.Shape switch
+ {
+ SysAbiExportShape.HandlerShape.ContextOnly => $"{export.ContainingType}.{export.MethodName}",
+ SysAbiExportShape.HandlerShape.Parameterless => $"static _ => {export.ContainingType}.{export.MethodName}()",
+ _ => TypedThunk(export),
+ };
+ builder.AppendLine(
+ $" Add(exports, registrationGeneration, {Literal(export.LibraryName)}, {Literal(export.Nid)}, " +
+ $"{Literal(export.ExportName)}, (global::SharpEmu.HLE.Generation){export.Target}, " +
+ $"{(export.PreferLle ? "true" : "false")}, {function});");
}
-
- var function = export.Shape switch
- {
- SysAbiExportShape.HandlerShape.ContextOnly => $"{export.ContainingType}.{export.MethodName}",
- SysAbiExportShape.HandlerShape.Parameterless => $"static _ => {export.ContainingType}.{export.MethodName}()",
- _ => TypedThunk(export),
- };
- builder.AppendLine(
- $" Add(exports, registrationGeneration, {Literal(export.LibraryName)}, {Literal(export.Nid)}, " +
- $"{Literal(export.ExportName)}, (global::SharpEmu.HLE.Generation){export.Target}, {function});");
}
builder.AppendLine(" return exports;");
@@ -205,6 +227,7 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
builder.AppendLine(" string nid,");
builder.AppendLine(" string exportName,");
builder.AppendLine(" global::SharpEmu.HLE.Generation attributeTarget,");
+ builder.AppendLine(" bool preferLle,");
builder.AppendLine(" global::SharpEmu.HLE.SysAbiFunction function)");
builder.AppendLine(" {");
builder.AppendLine(" var target = attributeTarget == global::SharpEmu.HLE.Generation.None ? registrationGeneration : attributeTarget;");
@@ -213,7 +236,7 @@ public sealed class SysAbiExportGenerator : IIncrementalGenerator
builder.AppendLine(" return;");
builder.AppendLine(" }");
builder.AppendLine();
- builder.AppendLine(" exports.Add(new global::SharpEmu.HLE.ExportedFunction(libraryName, nid, exportName, target, function));");
+ builder.AppendLine(" exports.Add(new global::SharpEmu.HLE.ExportedFunction(libraryName, nid, exportName, target, function, preferLle));");
builder.AppendLine(" }");
builder.AppendLine("}");
diff --git a/src/SharpEmu.SourceGenerators/SysAbiExportShape.cs b/src/SharpEmu.SourceGenerators/SysAbiExportShape.cs
index 00d0dbc5..865a590d 100644
--- a/src/SharpEmu.SourceGenerators/SysAbiExportShape.cs
+++ b/src/SharpEmu.SourceGenerators/SysAbiExportShape.cs
@@ -18,18 +18,20 @@ public static class SysAbiExportShape
public readonly struct Arguments
{
- public Arguments(string libraryName, string nid, string exportName, int target)
+ public Arguments(string libraryName, string nid, string exportName, int target, bool preferLle)
{
LibraryName = libraryName;
Nid = nid;
ExportName = exportName;
Target = target;
+ PreferLle = preferLle;
}
public string LibraryName { get; }
public string Nid { get; }
public string ExportName { get; }
public int Target { get; }
+ public bool PreferLle { get; }
}
///
@@ -205,6 +207,7 @@ public static class SysAbiExportShape
var nid = string.Empty;
var exportName = string.Empty;
var target = 0;
+ var preferLle = false;
foreach (var argument in attribute.NamedArguments)
{
switch (argument.Key)
@@ -221,9 +224,12 @@ public static class SysAbiExportShape
case "Target":
target = argument.Value.Value is int value ? value : 0;
break;
+ case "PreferLle":
+ preferLle = argument.Value.Value is bool boolValue && boolValue;
+ break;
}
}
- return new Arguments(libraryName, nid, exportName, target);
+ return new Arguments(libraryName, nid, exportName, target, preferLle);
}
}
diff --git a/tests/SharpEmu.Libs.Tests/Cpu/DirectExecutionBackendLlePreferenceTests.cs b/tests/SharpEmu.Libs.Tests/Cpu/DirectExecutionBackendLlePreferenceTests.cs
new file mode 100644
index 00000000..fd168531
--- /dev/null
+++ b/tests/SharpEmu.Libs.Tests/Cpu/DirectExecutionBackendLlePreferenceTests.cs
@@ -0,0 +1,60 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using SharpEmu.Core.Cpu.Native;
+using SharpEmu.HLE;
+using Xunit;
+
+namespace SharpEmu.Libs.Tests.Cpu;
+
+public sealed class DirectExecutionBackendLlePreferenceTests
+{
+ [Fact]
+ public void ExplicitLlePreference_AllowsNonKernelRegisteredExport()
+ {
+ var export = Export("libSceNpCppWebApi", preferLle: true);
+
+ Assert.True(DirectExecutionBackend.ShouldResolveRegisteredExportViaLle(
+ export,
+ preferLleForLibc: false));
+ }
+
+ [Fact]
+ public void ExplicitLlePreference_CannotOverrideKernelHleBoundary()
+ {
+ var export = Export("libKernel", preferLle: true);
+
+ Assert.False(DirectExecutionBackend.ShouldResolveRegisteredExportViaLle(
+ export,
+ preferLleForLibc: true));
+ }
+
+ [Fact]
+ public void RegisteredExportWithoutLlePreference_RemainsHle()
+ {
+ var export = Export("libSceNpCppWebApi", preferLle: false);
+
+ Assert.False(DirectExecutionBackend.ShouldResolveRegisteredExportViaLle(
+ export,
+ preferLleForLibc: false));
+ }
+
+ [Fact]
+ public void ExistingLibcPolicy_CanStillSelectRegisteredFirmwareExport()
+ {
+ var export = Export("libSceLibcInternal", preferLle: false);
+
+ Assert.True(DirectExecutionBackend.ShouldResolveRegisteredExportViaLle(
+ export,
+ preferLleForLibc: true));
+ }
+
+ private static ExportedFunction Export(string libraryName, bool preferLle) =>
+ new(
+ libraryName,
+ "Zxa0VhQVTsk",
+ "sceKernelWaitSema",
+ Generation.Gen5,
+ static _ => 0,
+ preferLle);
+}
diff --git a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportAnalyzerTests.cs b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportAnalyzerTests.cs
index 68dca82c..d35a91bd 100644
--- a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportAnalyzerTests.cs
+++ b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportAnalyzerTests.cs
@@ -53,6 +53,40 @@ public sealed class SysAbiExportAnalyzerTests
AssertSingle(diagnostics, "SHEM001");
}
+ [Fact]
+ public void DuplicateNidOnTheSameMultiAttributeHandlerIsReported()
+ {
+ var diagnostics = Analyze("""
+ using SharpEmu.HLE;
+
+ public static class Exports
+ {
+ [SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
+ [SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
+ public static int Shared(CpuContext ctx) => 0;
+ }
+ """);
+
+ AssertSingle(diagnostics, "SHEM001");
+ }
+
+ [Fact]
+ public void EveryAttributeOnAMultiAttributeHandlerIsAnalyzed()
+ {
+ var diagnostics = Analyze("""
+ using SharpEmu.HLE;
+
+ public static class Exports
+ {
+ [SysAbiExport(Nid = "Zxa0VhQVTsk", ExportName = "sceKernelWaitSema")]
+ [SysAbiExport(Nid = "not_a_nid")]
+ public static int Shared(CpuContext ctx) => 0;
+ }
+ """);
+
+ AssertSingle(diagnostics, "SHEM002");
+ }
+
[Fact]
public void MalformedNidIsReported()
{
diff --git a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs
index 9e8f16be..a0752000 100644
--- a/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs
+++ b/tests/SharpEmu.SourceGenerators.Tests/SysAbiExportGeneratorTests.cs
@@ -36,6 +36,11 @@ public sealed class SysAbiExportGeneratorTests
// Guest string marshalling: the thunk reads the pointer before the handler.
[SysAbiExport(Nid = "1G3lF1Gg1k8", ExportName = "sceKernelOpen")]
public static int KernelOpen(CpuContext ctx, [GuestCString(4096)] string path, int flags) => 0;
+
+ // A single fail-closed handler may back a catalog of LLE-preferred exports.
+ [SysAbiExport(Nid = "5fbPUzoA2fM", ExportName = "sceLleFirst", Target = Generation.Gen5, LibraryName = "libSceLle", PreferLle = true)]
+ [SysAbiExport(Nid = "L9NfM+f4f1Y", ExportName = "sceLleSecond", Target = Generation.Gen5, LibraryName = "libSceLle", PreferLle = true)]
+ public static int LleFallback(CpuContext ctx) => -1;
}
""";
@@ -123,6 +128,22 @@ public sealed class SysAbiExportGeneratorTests
Assert.Contains("(target & registrationGeneration) == 0", generated, StringComparison.Ordinal);
}
+ [Fact]
+ public void MultipleLlePreferredAttributesShareOneFailClosedHandler()
+ {
+ var (_, generated) = RoslynTestHost.RunGenerator(RoslynTestHost.Compile(HandlerSource));
+
+ Assert.Contains("\"5fbPUzoA2fM\"", generated, StringComparison.Ordinal);
+ Assert.Contains("\"L9NfM+f4f1Y\"", generated, StringComparison.Ordinal);
+ Assert.Equal(
+ 2,
+ generated.Split("global::TestExports.SampleExports.LleFallback", StringSplitOptions.None).Length - 1);
+ Assert.Contains(
+ ", true, global::TestExports.SampleExports.LleFallback",
+ generated,
+ StringComparison.Ordinal);
+ }
+
[Fact]
public void AssemblyWithoutExportsEmitsNoRegistry()
{