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
31 changes: 1 addition & 30 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,6 @@ void CodeGen::genMarkLabelsForCodegen()
//
void CodeGen::genBeginFnProlog()
{
// SIMD (Vector2/3/4, Vector128) parameters are lowered to i32 in the wasm signature, so any
// vector operation performed on them produces an invalid module (e.g. a v128/f64 op with
// an i32 operand). Bail such methods to the interpreter until SIMD parameters are
// properly supported in the wasm calling convention.
for (unsigned lclNum = 0; lclNum < m_compiler->info.compArgsCount; lclNum++)
{
if (varTypeIsSIMD(m_compiler->lvaGetDesc(lclNum)->TypeGet()))
{
NYI_WASM_SIMD("SIMD parameter");
}
}

GetEmitter()->emitIns(INS_code_size);

Comment thread
tannergooding marked this conversation as resolved.
FuncInfoDsc* const func = m_compiler->funGetFunc(ROOT_FUNC_IDX);
Expand Down Expand Up @@ -2592,11 +2580,6 @@ void CodeGen::genCodeForLclFld(GenTreeLclFld* tree)
LclVarDsc* varDsc = m_compiler->lvaGetDesc(tree);
var_types type = tree->TypeGet();

if (type == TYP_SIMD16)
{
NYI_WASM_SIMD("SIMD16 local field load");
}

if (type == TYP_SIMD12)
{
genLoadLclTypeSimd12(tree);
Expand Down Expand Up @@ -2862,8 +2845,7 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree)
}
else // A normal store, not a WriteBarrier store
{
var_types type = tree->TypeGet();
instruction ins = ins_Store(type);
var_types type = tree->TypeGet();

// TODO-WASM: Memory barriers

Expand Down Expand Up @@ -2974,13 +2956,6 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
assert(seg.IsPassedInRegister());
WasmValueType wvt = WasmRegToType(seg.GetRegister());
assert(wvt < WasmValueType::Count);
if (wvt == WasmValueType::V128)
{
// Passing a 16-byte SIMD value by value through a call is not yet correctly
// implemented: the argument is materialized as an i32 (by-ref) while the call
// signature requires v128, producing an invalid module. Bail for now.
NYI_WASM_SIMD("SIMD16 call argument");
}
typeStack.Push((CorInfoWasmType)emitter::GetWasmValueTypeCode(wvt));
}
}
Expand Down Expand Up @@ -3905,10 +3880,6 @@ void CodeGen::genLoadLocalIntoReg(regNumber targetReg, unsigned lclNum)
{
LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum);
var_types type = varDsc->GetRegisterType();
if (type == TYP_SIMD16)
{
NYI_WASM_SIMD("SIMD16 local load");
}

GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex());
GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(type), lclNum, 0);
Expand Down
3 changes: 1 addition & 2 deletions src/coreclr/jit/targetwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ var_types WasmClassifier::ToJitType(CorInfoWasmType wasmType)
case CORINFO_WASM_TYPE_F64:
return TYP_DOUBLE;
case CORINFO_WASM_TYPE_V128:
// TODO-WASM: Simd support
unreached();
return TYP_SIMD16;
default:
unreached();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ public partial class CompilerTypeSystemContext
private readonly object _structCacheLock = new object();
private readonly Dictionary<int, TypeDesc> _structsBySize = new Dictionary<int, TypeDesc>();
private volatile TypeDesc _cachedEmptyStruct;
private volatile TypeDesc _cachedV128Type;

/// <summary>
/// Gets the first SIMD v128 type encountered during lowering, or null if none has been seen.
/// Used by RaiseSignature to produce a roundtrippable type for the 'V' encoding.
/// </summary>
public TypeDesc CachedV128Type => _cachedV128Type;

/// <summary>
/// Caches a SIMD v128 type discovered during lowering. Only the first one is retained.
/// </summary>
public void CacheV128Type(TypeDesc type)
{
_cachedV128Type ??= type;
}

/// <summary>
/// Gets the first empty struct type encountered during lowering, or null if none has been seen.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public override bool ComputeIsUnsafeValueType(DefType type)
public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristics(DefType type)
{
if (type.Context.Target.Architecture == TargetArchitecture.ARM64 &&
type.Instantiation[0].IsPrimitiveNumeric)
IsSupportedVectorBaseType(type.Instantiation[0]))
{
return type.InstanceFieldSize.AsInt switch
{
Expand All @@ -170,5 +170,14 @@ public static bool IsVectorType(DefType type)
type.Name == "Vector256`1"u8 ||
type.Name == "Vector512`1"u8);
}

/// <summary>
/// Determines whether <paramref name="elementType"/> is supported as the base (element)
/// type of an intrinsic vector, mirroring the set the JIT recognizes as a SIMD base type.
/// </summary>
public static bool IsSupportedVectorBaseType(TypeDesc elementType)
{
return elementType.IsPrimitiveNumeric;
}
}
}
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3907,6 +3907,8 @@ private CorInfoWasmType getWasmLowering(CORINFO_CLASS_STRUCT_* structHnd)
return CorInfoWasmType.CORINFO_WASM_TYPE_F32;
case WasmValueType.F64:
return CorInfoWasmType.CORINFO_WASM_TYPE_F64;
case WasmValueType.V128:
return CorInfoWasmType.CORINFO_WASM_TYPE_V128;
default:
ThrowHelper.ThrowInvalidProgramException();
return CorInfoWasmType.CORINFO_WASM_TYPE_I32; // unreachable
Expand Down
70 changes: 67 additions & 3 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public static MethodSignature GetStringCtorActualSignature(MethodSignature signa

public static TypeDesc LowerToAbiType(TypeDesc type)
{
// Vector128<T> and a 128-bit Vector<T> are wasm v128 ABI primitives passed by value.
if (IsWasmV128Type(type))
{
return type;
}

if (!(type.IsValueType && !type.IsPrimitive))
{
return type;
Expand Down Expand Up @@ -65,6 +71,10 @@ public static TypeDesc LowerToAbiType(TypeDesc type)

if (numIntroducedFields != 1)
{
// Multi-field aggregates (including a homogeneous 2x v128) use the generic by-ref
// struct ABI; the wasm C ABI has no HFA/HVA concept. Only emscripten's opt-in
// experimental multivalue ABI expands these into per-field registers, which we
// don't target.
return null;
}

Expand All @@ -78,6 +88,13 @@ public static TypeDesc LowerToAbiType(TypeDesc type)

type = firstFieldElementType;

// A single-field wrapper struct around a v128 lowers to the v128 primitive, matching
// emscripten, which passes a struct wrapping a v128 as a v128.
if (IsWasmV128Type(type))
{
return type;
}

if (type.IsValueType && !type.IsPrimitive)
{
continue;
Expand All @@ -87,10 +104,47 @@ public static TypeDesc LowerToAbiType(TypeDesc type)
}
}

/// <summary>
/// Determines whether a type is passed and returned by value as a wasm <c>v128</c>, matching
/// the SIMD types the JIT recognizes as <c>TYP_SIMD16</c> on wasm. This is
/// <see cref="System.Runtime.Intrinsics.Vector128{T}"/> and a 128-bit
/// <see cref="System.Numerics.Vector{T}"/>, in both cases only when <c>T</c> is a supported
/// primitive numeric base type. Other SIMD types (Vector2/3/4, Vector64/256/512&lt;T&gt;, ...)
/// and non-primitive instantiations (e.g. the shared <c>__Canon</c> form) are not ABI
/// primitives and continue to use the generic struct ABI.
/// </summary>
private static bool IsWasmV128Type(TypeDesc type)
{
if (!type.IsIntrinsic ||
type.Instantiation.Length != 1 ||
!VectorFieldLayoutAlgorithm.IsSupportedVectorBaseType(type.Instantiation[0]))
{
return false;
}

// Vector128<T> is always a 16-byte v128.
if (Internal.TypeSystem.Interop.InteropTypes.IsSystemRuntimeIntrinsicsVector128T(type.Context, type))
{
return true;
}

// Vector<T> is target-sized, so it is only a v128 when the target's maximum SIMD width is
// 128-bit (i.e. it is exactly 16 bytes). This matches the JIT recognizing it as TYP_SIMD16
// via getVectorTByteLength() and keeps the ABI correct should wasm later gain wider vectors.
return type is DefType vectorOfT &&
VectorOfTFieldLayoutAlgorithm.IsVectorOfTType(vectorOfT) &&
type.GetElementSize().AsInt == 16;
}

public static WasmValueType LowerType(TypeDesc type)
{
WasmValueType pointerType = (type.Context.Target.PointerSize == 4) ? WasmValueType.I32 : WasmValueType.I64;

if (IsWasmV128Type(type))
{
return WasmValueType.V128;
}

TypeDesc abiType = LowerToAbiType(type);

if (abiType == null)
Expand Down Expand Up @@ -165,7 +219,8 @@ public static WasmValueType LowerType(TypeDesc type)
'l' => context.GetWellKnownType(WellKnownType.Int64),
'f' => context.GetWellKnownType(WellKnownType.Single),
'd' => context.GetWellKnownType(WellKnownType.Double),
'V' => throw new NotSupportedException("SIMD types are not supported in this version of the compiler"),
'V' => ((CompilerTypeSystemContext)context).CachedV128Type
?? throw new InvalidOperationException("Encountered 'V' in signature but no v128 type was cached during lowering"),
_ => throw new InvalidOperationException($"Unknown signature char: {c}")
};

Expand Down Expand Up @@ -347,7 +402,12 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag
}
else
{
sigBuilder.Append(WasmValueTypeToSigChar(LowerType(loweredReturnType)));
WasmValueType returnWasmType = LowerType(loweredReturnType);
if (returnWasmType == WasmValueType.V128)
{
((CompilerTypeSystemContext)returnType.Context).CacheV128Type(loweredReturnType);
}
sigBuilder.Append(WasmValueTypeToSigChar(returnWasmType));
}

// Reserve space for potential implicit this, stack pointer parameter, portable entrypoint parameter,
Expand Down Expand Up @@ -423,7 +483,11 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag
}
else
{
WasmValueType paramWasmType = LowerType(paramType);
WasmValueType paramWasmType = LowerType(loweredParamType);
if (paramWasmType == WasmValueType.V128)
{
((CompilerTypeSystemContext)paramType.Context).CacheV128Type(loweredParamType);
}
sigBuilder.Append(WasmValueTypeToSigChar(paramWasmType));
result.Add(paramWasmType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public override bool ComputeContainsByRefs(DefType type)
public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristics(DefType type)
{
if (type.Context.Target.Architecture == TargetArchitecture.ARM64 &&
type.Instantiation[0].IsPrimitiveNumeric)
VectorFieldLayoutAlgorithm.IsSupportedVectorBaseType(type.Instantiation[0]))
Comment thread
tannergooding marked this conversation as resolved.
{
return type.InstanceFieldSize.AsInt switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using ILCompiler.ReadyToRun.Tests.TestCasesRunner;
using ILCompiler.Reflection.ReadyToRun;
Expand Down Expand Up @@ -120,6 +121,87 @@ static void Validate(ReadyToRunReader reader)
}
}

[Fact]
public void WasmSimdModule()
{
var wasmSimdModule = new CompiledAssembly
{
AssemblyName = nameof(WasmSimdModule),
SourceResourceNames = ["Webcil/WasmSimdModule.cs"],
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(WasmSimdModule),
[
new(nameof(WasmSimdModule), [new CrossgenAssembly(wasmSimdModule)])
{
OutputFileExtension = ".wasm",
AdditionalArgs =
{
"--targetarch",
"wasm",
"--targetos",
"browser",
},
Validate = Validate,
},
]));

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

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

// Each method's compiled body must actually use the wasm v128 (0x7B) valtype for its
// Vector128<int> parameter, and for its return when it returns one. A regression that
// reverts to the by-ref i32 ABI would produce no v128 in the signature at all.
const byte WasmV128 = 0x7B;

// (method name, expects v128 return). All take a v128-classified value by value (a
// Vector128<int>, a 128-bit Vector<int>, or a single-field struct wrapping one); Store
// returns void (its 'ref Vector128<int>' destination is an i32 pointer).
foreach ((string name, bool expectsV128Return) in
new[]
{
("Echo", true), ("ThroughLocal", true), ("Store", false), ("CallEcho", true),
("EchoVectorT", true), ("CallEchoVectorT", true),
("EchoWrapped", true), ("CallEchoWrapped", true),
("EchoWrappedVectorT", true), ("CallEchoWrappedVectorT", true),
})
{
ReadyToRunMethod method = Assert.Single(
methods, m => m.SignatureString.Contains($".{name}(", StringComparison.Ordinal));

WebcilImageReader.WasmFunctionInfo body = ResolveWasmBody(reader, webcilReader, method);

Assert.True(
body.ParamTypes.Count(b => b == WasmV128) == 1,
$"'{name}' should have exactly one wasm v128 parameter; params were {Format(body.ParamTypes)}.");
Assert.True(
body.ResultTypes.Contains(WasmV128) == expectsV128Return,
$"'{name}' v128 return expectation was {expectsV128Return}; results were {Format(body.ResultTypes)}.");
}

static string Format(IReadOnlyList<byte> valTypes) =>
$"[{string.Join(",", valTypes.Select(b => $"0x{b:X2}"))}]";
}

static WebcilImageReader.WasmFunctionInfo ResolveWasmBody(
ReadyToRunReader reader, WebcilImageReader webcilReader, ReadyToRunMethod method)
{
uint tableIndex = checked(reader.WasmMinFunctionTableIndex + (uint)method.EntryPointRuntimeFunctionId);
int functionIndex = webcilReader.GetFunctionIndexFromTableIndex(tableIndex);
Assert.True(functionIndex >= 0, $"Could not resolve wasm table index {tableIndex} to a function body.");

WebcilImageReader.WasmFunctionInfo? body = webcilReader.GetWasmFunctionBody(functionIndex);
Assert.True(body is not null, $"Wasm function body {functionIndex} was not found.");
return body.Value;
}
}

[Fact]
public void RuntimeFunctionsSectionSizeExcludesSentinel()
{
Expand Down
Loading
Loading