Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ public sealed override IEnumerable<DependencyListEntry> GetStaticDependencies(No
}
}

if (factory.Target.IsWasm && this is IMethodCodeNodeWithTypeSignature wasmMethodCodeNode)
// A method that is not emitted needs no wasm function type. Declined compilations
// publish empty code and are skipped at emission, but marking the type node anyway
// would leave an unreferenced signature, which for an over-limit one is enough on its
// own to make the module unloadable.
if (factory.Target.IsWasm && this is IMethodCodeNodeWithTypeSignature wasmMethodCodeNode
&& !ShouldSkipEmittingObjectNode(factory))
{
dependencies ??= new DependencyList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@

namespace ILCompiler.DependencyAnalysis.Wasm
{
/// <summary>
/// Widely adopted WebAssembly implementation limits, enforced by the engines and tools we
/// target. A module violating one is rejected at instantiation, which for a ReadyToRun image
/// means the runtime silently interprets the whole assembly.
/// See https://webassembly.github.io/spec/js-api/#limits.
/// </summary>
public static class WasmLimits
{
/// <summary>Maximum number of parameters a function type may declare.</summary>
public const int MaxFunctionParams = 1000;

/// <summary>
/// Maximum number of results a function type may declare. ReadyToRun signatures have at
/// most one result today, so this cannot currently be approached.
/// </summary>
public const int MaxFunctionResults = 1000;

/// <summary>Returns true if <paramref name="funcType"/> cannot be emitted into a loadable module.</summary>
public static bool ExceedsLimits(in WasmFuncType funcType) =>
funcType.Params.Types.Length > MaxFunctionParams ||
funcType.Returns.Types.Length > MaxFunctionResults;
}
Comment thread
lewing marked this conversation as resolved.

// For now, we only encode Wasm numeric value types.
// These are encoded as a single byte. However,
// not all value types can be encoded this way.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
definedSymbols: new ISymbolDefinitionNode[] { this });
}

// Compilation declines methods needing an over-limit type, so reaching this point means
// a producer was missed. Fail the build rather than emit an unloadable module.
if (WasmLimits.ExceedsLimits(_type))
{
throw new InvalidOperationException(
$"Cannot emit wasm function type '{_type}': it declares {_type.Params.Types.Length} parameters " +
$"and {_type.Returns.Types.Length} results, exceeding the wasm implementation limit of " +
$"{WasmLimits.MaxFunctionParams} parameters / {WasmLimits.MaxFunctionResults} results. " +
$"A module containing it cannot be instantiated by any engine.");
}

byte[] data = new byte[_type.EncodeSize()];
_type.Encode(data);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

extern alias crossgen2;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using crossgen2::ILCompiler.DependencyAnalysis.Wasm;
using ILCompiler.ReadyToRun.Tests.TestCasesRunner;
using ILCompiler.Reflection.ReadyToRun;
using Internal.ReadyToRunConstants;
Expand Down Expand Up @@ -121,6 +124,53 @@ static void Validate(ReadyToRunReader reader)
}
}

[ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))]
public void WasmWideSignatureModule()
{
var wasmWideSignatureModule = new CompiledAssembly
{
AssemblyName = nameof(WasmWideSignatureModule),
SourceResourceNames = ["Webcil/WasmWideSignatureModule.cs"],
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(WasmWideSignatureModule),
[
new(nameof(WasmWideSignatureModule), [new CrossgenAssembly(wasmWideSignatureModule)])
{
OutputFileExtension = ".wasm",
Validate = Validate,
},
]));

static void Validate(ReadyToRunReader reader)
{
var webcilReader = Assert.IsType<WebcilImageReader>(reader.CompositeReader);
Assert.True(webcilReader.IsWasmWrapped);

// Must hold for the type section as a whole: an over-limit type left behind by a method
// that was not emitted is as fatal as one in use.
WasmR2RAssert.GetMaxWasmFunctionTypeArity(webcilReader, out int maxParams, out int maxResults);
Assert.InRange(maxParams, 0, WasmLimits.MaxFunctionParams);
Assert.InRange(maxResults, 0, WasmLimits.MaxFunctionResults);

List<ReadyToRunMethod> methods = R2RAssert.GetAllMethods(reader);

// The over-limit method and the one whose call site needs the same type are both left
// to the interpreter.
Assert.DoesNotContain(methods, method =>
method.SignatureString.Contains("TooManyParameters", StringComparison.Ordinal));
Assert.DoesNotContain(methods, method =>
method.SignatureString.Contains("CallsTooManyParameters", StringComparison.Ordinal));

// Declining costs only those methods, not the assembly's R2R coverage.
Assert.Contains(methods, method =>
method.SignatureString.Contains("AddIntegers", StringComparison.Ordinal));
Assert.Contains(methods, method =>
method.SignatureString.Contains("MultiplyIntegers", StringComparison.Ordinal));
}
}

[ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))]
public void WasmSimdModule()
{
Expand Down
Loading
Loading