Skip to content
Merged
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 @@ -2,12 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.DependencyAnalysis.Wasm;

using Internal.JitInterface;
using Internal.Text;
using Internal.TypeSystem;

Expand Down Expand Up @@ -63,72 +60,30 @@ public override string ToString()
}
}

/// <summary>
/// Signature information for an extern function, used to type its import on Wasm.
/// </summary>
public readonly record struct ExternalTypeSignature(
MethodSignature Signature,
bool IsUnmanagedCallersOnly,
bool IsAsyncCall,
bool HasGenericContextArg)
{
/// <summary>
/// Signature for a native target, such as a direct P/Invoke or a native runtime helper.
/// </summary>
public static ExternalTypeSignature Unmanaged(MethodSignature signature)
=> new ExternalTypeSignature(signature, IsUnmanagedCallersOnly: true, IsAsyncCall: false, HasGenericContextArg: false);

/// <summary>
/// Signature for managed code that is referenced by name.
/// </summary>
// Keep aligned with IMethodCodeNodeWithTypeSignature
public static ExternalTypeSignature FromMethod(MethodDesc method)
=> new ExternalTypeSignature(
method.Signature,
method.IsUnmanagedCallersOnly,
method.IsAsyncCall(),
method.RequiresInstMethodDescArg() || method.RequiresInstMethodTableArg() || method.IsArrayAddressMethod());
}

/// <summary>
/// Represents a function symbol that is defined externally and statically linked to the output obj file.
/// </summary>
public class ExternFunctionSymbolNode : ExternSymbolNode, INodeWithTypeSignature
{
private readonly ExternalTypeSignature? _typeSignature;
private readonly MethodSignature _signature;
private readonly bool _isUnmanagedCallersOnly;
private readonly bool _isAsyncCall;
private readonly bool _hasGenericContextArg;

public ExternFunctionSymbolNode(Utf8String name, MethodDesc method, bool isIndirection = false)
: this(name, method.Signature, method.IsUnmanagedCallersOnly, method.IsAsyncCall(), method.RequiresInstArg() || method.IsArrayAddressMethod(), isIndirection)
{ }

public ExternFunctionSymbolNode(Utf8String name, ExternalTypeSignature? typeSignature, bool isIndirection = false)
public ExternFunctionSymbolNode(Utf8String name, MethodSignature signature, bool isUnmanagedCallersOnly, bool isAsyncCall, bool hasGenericContextArg, bool isIndirection = false)
: base(name, isIndirection)
{
_typeSignature = typeSignature;
}
=> (_signature, _isUnmanagedCallersOnly, _isAsyncCall, _hasGenericContextArg) = (signature, isUnmanagedCallersOnly, isAsyncCall, hasGenericContextArg);

public override int ClassCode => 1452455506;

/// <summary>
/// The signature of the function, or null if it has no standard-ABI signature.
/// </summary>
public ExternalTypeSignature? TypeSignature => _typeSignature;

MethodSignature INodeWithTypeSignature.Signature => _typeSignature.Value.Signature;
bool INodeWithTypeSignature.IsUnmanagedCallersOnly => _typeSignature.Value.IsUnmanagedCallersOnly;
bool INodeWithTypeSignature.IsAsyncCall => _typeSignature.Value.IsAsyncCall;
bool INodeWithTypeSignature.HasGenericContextArg => _typeSignature.Value.HasGenericContextArg;

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
// Indirection cells are data, not functions, so they are not imported as functions
if (!factory.Target.IsWasm || RepresentsIndirectionCell)
return null;

Debug.Assert(_typeSignature is not null, $"Extern function '{this}' has no known signature and cannot be imported on Wasm");
return [new DependencyListEntry(factory.WasmFunctionImport(this), "Wasm extern functions are imported")];
}
}

public class AddressTakenExternFunctionSymbolNode(Utf8String name) : ExternFunctionSymbolNode(name, typeSignature: null)
{
public override int ClassCode => -45645737;
MethodSignature INodeWithTypeSignature.Signature => _signature;
bool INodeWithTypeSignature.IsUnmanagedCallersOnly => _isUnmanagedCallersOnly;
bool INodeWithTypeSignature.IsAsyncCall => _isAsyncCall;
bool INodeWithTypeSignature.HasGenericContextArg => _hasGenericContextArg;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

namespace ILCompiler.DependencyAnalysis
{
public interface INodeWithTypeSignature : ISymbolDefinitionNode
public interface INodeWithTypeSignature : ISymbolNode
{
MethodSignature Signature { get; }
bool IsUnmanagedCallersOnly { get; }
bool IsAsyncCall { get; }
bool HasGenericContextArg { get; }
}

public interface IMethodCodeNodeWithTypeSignature : IMethodNode, INodeWithTypeSignature
public interface IMethodCodeNodeWithTypeSignature : IMethodNode, INodeWithTypeSignature, ISymbolDefinitionNode
{
// Keep methods aligned with WasmLowering.GetSignature(MethodDesc)
MethodSignature INodeWithTypeSignature.Signature => Method.Signature;
Expand Down

This file was deleted.

27 changes: 15 additions & 12 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ private protected Utf8String GetMangledName(ISymbolNode symbolNode)
return symbolName;
}

private protected virtual void PrepareImportsForUndefinedSymbols()
{
}

private protected virtual void EmitSectionsAndLayout()
{
}
Expand All @@ -305,7 +309,7 @@ private protected virtual void EmitSectionsAndLayout()

partial void EmitDebugInfo(IReadOnlyCollection<DependencyNode> nodes, Logger logger);

private SortedSet<Utf8String> GetUndefinedSymbols()
private protected SortedSet<Utf8String> GetUndefinedSymbols()
{
SortedSet<Utf8String> undefinedSymbolSet = new SortedSet<Utf8String>();
foreach (var relocationList in _sectionIndexToRelocations)
Expand Down Expand Up @@ -358,6 +362,11 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
List<ChecksumsToCalculate> checksumRelocations = [];
foreach (DependencyNode depNode in nodes)
{
if (_nodeFactory.Target.IsWasm && depNode is INodeWithTypeSignature methodDeclaration)
{
RecordMethodDeclaration(methodDeclaration);
}

// TODO-WASM: emit symbol ranges properly when code and data are separated
// Right now we still need to determine placements for some traditionally text-placed nodes,
// such as DebugDirectoryEntryNode and AssemblyStubNode
Expand Down Expand Up @@ -416,19 +425,11 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
RecordMethodSignature(signature);
}

if (node is WasmFunctionImportNode import)
{
RecordFunctionImport(import);
}

if (node is INodeWithTypeSignature codeNode && _nodeFactory.Target.IsWasm)
{
Debug.Assert(codeNode.Signature != null, $"Wasm code node {codeNode.GetType()} has null signature");

// Record only information we can get from the MethodDesc here. The actual
// body will be emitted by the call to EmitData() at the end
// of this loop iteration.
RecordMethodDeclaration(codeNode);
RecordMethodDefinition(codeNode);
}

foreach (ISymbolDefinitionNode n in nodeContents.DefinedSymbols)
Expand Down Expand Up @@ -590,6 +591,7 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
}
blocksToRelocate.Clear();

PrepareImportsForUndefinedSymbols();
EmitSectionsAndLayout();

if (_options.HasFlag(ObjectWritingOptions.GenerateDebugInfo))
Expand Down Expand Up @@ -627,13 +629,14 @@ private protected virtual void RecordMethodDeclaration(INodeWithTypeSignature no
Debug.Assert(LayoutMode == CodeDataLayout.Separate);
}

private protected virtual void RecordMethodSignature(WasmTypeNode signature)
private protected virtual void RecordMethodDefinition(INodeWithTypeSignature node)
{
Debug.Assert(LayoutMode == CodeDataLayout.Separate);
}

private protected virtual void RecordFunctionImport(WasmFunctionImportNode import)
private protected virtual void RecordMethodSignature(WasmTypeNode signature)
{
Debug.Assert(LayoutMode == CodeDataLayout.Separate);
}

private protected virtual void RecordWellKnownSymbol(Utf8String currentSymbolName, SortableDependencyNode.ObjectNodeOrder classCode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,6 @@ public WasmImportSection(Stream stream, Utf8String name, int sectionIndex)
{
}

/// <summary>
/// Counts an import entry that was written directly to the section by an object node.
/// </summary>
public void CompleteExternallyWrittenEntry() => CompleteEntry();

protected override void WriteEntryCore(SectionWriter writer, WasmImport entry)
{
writer.EmitSymbolDefinition(new Utf8String(entry.Name));
Expand Down
15 changes: 15 additions & 0 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ internal enum WasmExportKind : byte
Global = 0x03,
}

internal sealed class WasmFunctionImportType : WasmImportType
{
private readonly int _typeIndex;

public WasmFunctionImportType(int typeIndex) : base(WasmExternalKind.Function)
{
_typeIndex = typeIndex;
}

public override int Encode(Span<byte> buffer) => DwarfHelper.WriteULEB128(buffer, (ulong)_typeIndex);
public override int EncodeSize() => (int)DwarfHelper.SizeOfULEB128((ulong)_typeIndex);
public override int EncodeRelocationCount() => 0;
public override int EncodeRelocations(Span<Relocation> buffer) => 0;
}

public class WasmGlobalImportType : WasmImportType
{
private readonly WasmValueType _valueType;
Expand Down
39 changes: 29 additions & 10 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Linq;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.Wasm;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.ObjectWriter.WasmInstructions;
using Internal.JitInterface;
using Internal.Text;
Expand Down Expand Up @@ -63,6 +62,7 @@ internal abstract partial class WasmObjectWriter : ObjectWriter
/// logical WebAssembly indices and must not be used to resolve index relocations.
/// </summary>
private protected Dictionary<Utf8String, SymbolDefinition> _definedSymbols;
private readonly Dictionary<Utf8String, INodeWithTypeSignature> _externalFunctions = new();
private int[] _sectionEmitOrder;

/// <summary>
Expand All @@ -88,6 +88,25 @@ protected WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, Ou
{
}

private protected override void PrepareImportsForUndefinedSymbols()
{
// Imports must be registered before layout consumes function indices. Only actual unresolved
// references require imports; a marked extern can also resolve to a definition in this object.
foreach (Utf8String name in GetUndefinedSymbols())
{
if (_externalFunctions.TryGetValue(name, out INodeWithTypeSignature function))
{
if (function.Signature is null)
{
throw new InvalidOperationException($"Extern function '{name}' has no known signature and cannot be imported on Wasm");
}

int typeIndex = RegisterSignature(WasmLowering.GetSignature(function).FuncType);
WriteImport(new WasmImport("env", name.ToString(), new WasmFunctionImportType(typeIndex)));
}
}
}

private protected static void EmitWasmHeader(Stream outputFileStream)
{
outputFileStream.Write("\0asm"u8);
Expand Down Expand Up @@ -142,6 +161,14 @@ private protected override void RecordMethodSignature(WasmTypeNode signature)
}

private protected override void RecordMethodDeclaration(INodeWithTypeSignature node)
{
if (node is not ISymbolDefinitionNode && !node.RepresentsIndirectionCell)
{
_externalFunctions.TryAdd(GetMangledName(node), node);
}
}

private protected override void RecordMethodDefinition(INodeWithTypeSignature node)
{
WriteSignatureIndexForFunction(node);
Utf8String methodName = new(node.GetMangledName(_nodeFactory.NameMangler));
Expand All @@ -159,14 +186,6 @@ private protected override void RecordMethodDeclaration(INodeWithTypeSignature n
}
}

private protected override void RecordFunctionImport(WasmFunctionImportNode import)
{
// Assign the import's index in the function index space. The import entry itself is emitted
// by the node's own data.
_wasmSymbolManager.AddImport(new Utf8String(import.GetMangledName(_nodeFactory.NameMangler)), WasmIndexSpace.Function);
GetOrCreateSection<WasmImportSection>(WasmObjectNodeSection.ImportSection, out _).CompleteExternallyWrittenEntry();
}

private void RecordFunclets(INodeWithFunclets nodeWithFunclets)
{
FuncletKind[] funcletKinds = nodeWithFunclets.GetFuncletKinds();
Expand Down Expand Up @@ -329,7 +348,7 @@ private protected void WriteGlobal(
private protected void RegisterFunctionSymbol(Utf8String name) =>
_wasmSymbolManager.AddDefinition(name, WasmIndexSpace.Function);

// This effectively recreates the logic of RecordMethodBody/RecordMethodDeclaration, but for manually inserted stubs that are not
// This effectively recreates the logic of RecordMethodDefinition, but for manually inserted stubs that are not
// represented by nodes in the dependency graph.
// TODO-Wasm: for maintability, we should try and push some of this into the dependency graph when we do more stub generation.
private protected void RegisterStubIndexAndSignature(WasmFuncType signature)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,6 @@ public enum ReadyToRunHelper
ResolveInterfaceMethodFast,
ResolveInterfaceMethod,

NyiLdVirtFtn,
TlsGetAddr,
}

Expand Down
Loading