Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NativeAOT-LLVM] Port (most of) compiler-generated helpers to WASM #2645

Merged
merged 2 commits into from
Aug 6, 2024
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 @@ -67,14 +67,12 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
arm64Emitter.Builder.RequireInitialAlignment(alignment);
arm64Emitter.Builder.AddSymbol(this);
return arm64Emitter.Builder.ToObjectData();

#if !READYTORUN
case TargetArchitecture.Wasm32:
case TargetArchitecture.Wasm64:
Wasm.WasmEmitter wasmEmitter = new Wasm.WasmEmitter(factory, relocsOnly);

EmitCode(factory, ref wasmEmitter, relocsOnly);

wasmEmitter.Builder.AddSymbol(this);
return wasmEmitter.Builder.ToObjectData();
#endif
case TargetArchitecture.LoongArch64:
Expand All @@ -96,13 +94,15 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
}
}

#if !READYTORUN
public abstract Wasm.WasmFunctionType GetWasmFunctionType(NodeFactory factory);
protected abstract void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instructionEncoder, bool relocsOnly);
#endif

protected abstract void EmitCode(NodeFactory factory, ref X64.X64Emitter instructionEncoder, bool relocsOnly);
protected abstract void EmitCode(NodeFactory factory, ref X86.X86Emitter instructionEncoder, bool relocsOnly);
protected abstract void EmitCode(NodeFactory factory, ref ARM.ARMEmitter instructionEncoder, bool relocsOnly);
protected abstract void EmitCode(NodeFactory factory, ref ARM64.ARM64Emitter instructionEncoder, bool relocsOnly);
#if !READYTORUN
protected abstract void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instructionEncoder, bool relocsOnly);
#endif
protected abstract void EmitCode(NodeFactory factory, ref LoongArch64.LoongArch64Emitter instructionEncoder, bool relocsOnly);
protected abstract void EmitCode(NodeFactory factory, ref RiscV64.RiscV64Emitter instructionEncoder, bool relocsOnly);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ internal void EmitBytes(ArrayBuilder<byte> bytes)
_data.Append(bytes);
}

internal void EmitBytes(ReadOnlySpan<byte> bytes)
{
bytes.CopyTo(_data.AppendSpan(bytes.Length));
}

public void EmitZeroPointer()
{
_data.ZeroExtend(_target.PointerSize);
Expand Down Expand Up @@ -267,6 +272,23 @@ public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0)
// And add space for the reloc
switch (relocType)
{
case RelocType.R_WASM_MEMORY_ADDR_SLEB:
case RelocType.R_WASM_TABLE_INDEX_SLEB:
case RelocType.R_WASM_FUNCTION_INDEX_LEB:
int lebSize = 5; // All 32 bit LEBs must be padded out to 5 bytes.
goto WriteLeb128;
case RelocType.R_WASM_MEMORY_ADDR_SLEB64:
case RelocType.R_WASM_TABLE_INDEX_SLEB64:
lebSize = 10; // All 64 bit LEBs must be padded out to 10 (!) bytes.
WriteLeb128:
unsafe
{
fixed (byte* location = _data.AppendSpan(lebSize))
{
Relocation.WriteValue(relocType, location, delta);
}
}
break;
case RelocType.R_WASM_FUNCTION_OFFSET_I32:
case RelocType.IMAGE_REL_BASED_REL32:
case RelocType.IMAGE_REL_BASED_RELPTR32:
Expand Down
78 changes: 78 additions & 0 deletions src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public enum RelocType
// WASM relocations.
//
R_WASM_FUNCTION_OFFSET_I32, // Offset of a function relative to the Code section.
R_WASM_FUNCTION_INDEX_LEB, // 32 bit function index, used by the call instruction.
R_WASM_MEMORY_ADDR_SLEB, // 32 bit signed LEB for data references in code (i32.const).
R_WASM_TABLE_INDEX_SLEB, // 32 bit signed LEB for function pointer references in code (i32.const).
R_WASM_MEMORY_ADDR_SLEB64, // 64 bit signed LEB for data references in code (i64.const).
R_WASM_TABLE_INDEX_SLEB64, // 64 bit signed LEB for function pointer references in code (i64.const).
}

public struct Relocation
Expand All @@ -79,6 +84,56 @@ public struct Relocation
public readonly int Offset;
public readonly ISymbolNode Target;

#if !READYTORUN
private static unsafe long GetSLeb128(byte* location)
{
long value = 0;
int shift = 0;
byte @byte;
do
{
@byte = *location;
value |= (long)(@byte & 0x7f) << shift;
shift += 7;
location++;
} while (@byte >= 128);

// Sign extend negative numbers if needed.
if (shift < 64 && (@byte & 0x40) != 0)
{
value |= -1L << shift;
}
return value;
}

private static unsafe void PutSLeb128(byte* location, long value, int size)
{
Span<byte> bytes = new(location, size);

int actualSize = ObjectWriter.DwarfHelper.WriteSLEB128(bytes, value);
if (actualSize < size)
{
byte padValue = value < 0 ? (byte)0x7f : (byte)0x00;
bytes[actualSize - 1] |= 0x80;
bytes.Slice(actualSize, size - actualSize - 1).Fill((byte)(padValue | 0x80));
bytes[size - 1] = padValue;
}
}

private static unsafe void PutULeb128(byte* location, uint value, int size)
{
Span<byte> bytes = new(location, size);

int actualSize = ObjectWriter.DwarfHelper.WriteULEB128(bytes, value);
if (actualSize < size)
{
bytes[actualSize - 1] |= 0x80;
bytes.Slice(actualSize, size - actualSize - 1).Fill(0x80);
bytes[size - 1] = 0x00;
}
}
#endif

//*****************************************************************************
// Extract the 16-bit immediate from ARM Thumb2 Instruction (format T2_N)
//*****************************************************************************
Expand Down Expand Up @@ -504,6 +559,19 @@ public static unsafe void WriteValue(RelocType relocType, void* location, long v
{
switch (relocType)
{
#if !READYTORUN
case RelocType.R_WASM_FUNCTION_INDEX_LEB:
PutULeb128((byte*)location, checked((uint)value), 5);
break;
case RelocType.R_WASM_MEMORY_ADDR_SLEB:
case RelocType.R_WASM_TABLE_INDEX_SLEB:
PutSLeb128((byte*)location, value, 5);
break;
case RelocType.R_WASM_MEMORY_ADDR_SLEB64:
case RelocType.R_WASM_TABLE_INDEX_SLEB64:
PutSLeb128((byte*)location, value, 10);
break;
#endif
case RelocType.R_WASM_FUNCTION_OFFSET_I32:
case RelocType.IMAGE_REL_BASED_ABSOLUTE:
case RelocType.IMAGE_REL_BASED_ADDR32NB:
Expand Down Expand Up @@ -575,6 +643,16 @@ public static unsafe long ReadValue(RelocType relocType, void* location)
{
switch (relocType)
{
#if !READYTORUN
case RelocType.R_WASM_FUNCTION_INDEX_LEB:
case RelocType.R_WASM_TABLE_INDEX_SLEB:
case RelocType.R_WASM_TABLE_INDEX_SLEB64:
// These relocs do not support addends.
return 0;
case RelocType.R_WASM_MEMORY_ADDR_SLEB:
case RelocType.R_WASM_MEMORY_ADDR_SLEB64:
return GetSLeb128((byte*)location);
#endif
case RelocType.R_WASM_FUNCTION_OFFSET_I32:
case RelocType.IMAGE_REL_BASED_ABSOLUTE:
case RelocType.IMAGE_REL_BASED_ADDR32NB:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace ILCompiler.DependencyAnalysis
{
public abstract partial class NodeFactory
{
// TODO-LLVM-Upstream: make the EH model part of "TargetDetails" and delete this hack.
public virtual bool TargetsEmulatedEH() => throw new NotSupportedException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis.Wasm
{
public static class WasmAbi
{
public static WasmValueType GetNaturalIntType(TargetDetails target) =>
target.PointerSize == 4 ? WasmValueType.I32 : WasmValueType.I64;

public static WasmFunctionType GetWasmFunctionType(MethodDesc method) =>
GetWasmFunctionType(method.Signature, method.RequiresInstArg());

public static WasmFunctionType GetWasmFunctionType(MethodSignature signature, bool hasHiddenParam)
{
WasmValueType wasmPointerType = GetNaturalIntType(signature.Context.Target);

WasmValueType GetWasmArgTypeForArg(TypeDesc argSigType)
{
bool isPassedByRef = false;
TypeDesc argType = argSigType;
if (IsStruct(argSigType))
{
argType = GetPrimitiveTypeForTrivialWasmStruct(argSigType);
if (argType == null)
{
isPassedByRef = true;
}
}

return isPassedByRef ? wasmPointerType : GetWasmTypeForTypeDesc(argType);
}

WasmValueType wasmReturnType = GetWasmReturnType(signature.ReturnType, out bool isReturnByRef);

int maxWasmSigLength = signature.Length + 4;
Span<WasmValueType> signatureTypes =
maxWasmSigLength > 100 ? new WasmValueType[maxWasmSigLength] : stackalloc WasmValueType[maxWasmSigLength];

int index = 0;
signatureTypes[index++] = wasmPointerType;

if (!signature.IsStatic) // TODO-LLVM-Bug: doesn't handle explicit 'this'.
{
signatureTypes[index++] = wasmPointerType;
}

if (isReturnByRef)
{
signatureTypes[index++] = wasmPointerType;
}

if (hasHiddenParam)
{
signatureTypes[index++] = wasmPointerType;
}

foreach (TypeDesc type in signature)
{
signatureTypes[index++] = GetWasmArgTypeForArg(type);
}

return new WasmFunctionType(wasmReturnType, signatureTypes.Slice(0, index).ToArray());
}

public static WasmValueType GetWasmReturnType(MethodDesc method, out bool isPassedByRef) =>
GetWasmReturnType(method.Signature.ReturnType, out isPassedByRef);

public static TypeDesc GetPrimitiveTypeForTrivialWasmStruct(TypeDesc type)
{
int size = type.GetElementSize().AsInt;
if (size <= sizeof(double) && int.IsPow2(size))
{
while (true)
{
FieldDesc singleInstanceField = null;
foreach (FieldDesc field in type.GetFields())
{
if (!field.IsStatic)
{
if (singleInstanceField != null)
{
return null;
}

singleInstanceField = field;
}
}

if (singleInstanceField == null)
{
return null;
}

TypeDesc singleInstanceFieldType = singleInstanceField.FieldType;
if (!IsStruct(singleInstanceFieldType))
{
if (singleInstanceFieldType.GetElementSize().AsInt != size)
{
return null;
}

return singleInstanceFieldType;
}

type = singleInstanceFieldType;
}
}

return null;
}

private static WasmValueType GetWasmReturnType(TypeDesc sigReturnType, out bool isPassedByRef)
{
TypeDesc returnType = sigReturnType;
if (IsStruct(sigReturnType))
{
returnType = GetPrimitiveTypeForTrivialWasmStruct(sigReturnType);
}

isPassedByRef = returnType == null;
return isPassedByRef ? WasmValueType.Invalid : GetWasmTypeForTypeDesc(returnType);
}

private static WasmValueType GetWasmTypeForTypeDesc(TypeDesc type)
{
switch (type.Category)
{
case TypeFlags.Boolean:
case TypeFlags.SByte:
case TypeFlags.Byte:
case TypeFlags.Int16:
case TypeFlags.UInt16:
case TypeFlags.Char:
case TypeFlags.Int32:
case TypeFlags.UInt32:
return WasmValueType.I32;

case TypeFlags.IntPtr:
case TypeFlags.UIntPtr:
case TypeFlags.Array:
case TypeFlags.SzArray:
case TypeFlags.ByRef:
case TypeFlags.Class:
case TypeFlags.Interface:
case TypeFlags.Pointer:
case TypeFlags.FunctionPointer:
return GetNaturalIntType(type.Context.Target);

case TypeFlags.Int64:
case TypeFlags.UInt64:
return WasmValueType.I64;

case TypeFlags.Single:
return WasmValueType.F32;

case TypeFlags.Double:
return WasmValueType.F64;

case TypeFlags.Enum:
return GetWasmTypeForTypeDesc(type.UnderlyingType);

case TypeFlags.Void:
return WasmValueType.Invalid;

default:
throw new UnreachableException(type.Category.ToString());
}
}

private static bool IsStruct(TypeDesc type) => type.Category is TypeFlags.ValueType or TypeFlags.Nullable;
}
}
Loading