Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
39 changes: 2 additions & 37 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,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 @@ -2585,11 +2573,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");
}

GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex());
GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(tree), tree->GetLclNum(), tree->GetLclOffs());
WasmProduceReg(tree);
Expand Down Expand Up @@ -2747,15 +2730,8 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree)
}
else // A normal store, not a WriteBarrier store
{
var_types type = tree->TypeGet();
if (type == TYP_SIMD16)
{
// Storing a SIMD16 value emits v128.store, but the data operand is not
// materialized as a v128 (it comes through as an i32), producing an invalid
// module. Bail until SIMD16 store is properly supported.
NYI_WASM_SIMD("SIMD16 store indirect");
}
instruction ins = ins_Store(type);
var_types type = tree->TypeGet();
instruction ins = ins_Store(type);

// TODO-WASM: Memory barriers

Expand Down Expand Up @@ -2854,13 +2830,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 @@ -3769,10 +3738,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;
Comment thread
tannergooding marked this conversation as resolved.

/// <summary>
/// Caches a SIMD v128 type discovered during lowering. Only the first one is retained.
/// </summary>
public void CacheV128Type(TypeDesc type)
{
_cachedV128Type ??= type;
}
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
tannergooding marked this conversation as resolved.

/// <summary>
/// Gets the first empty struct type encountered during lowering, or null if none has been seen.
Expand Down
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 @@ -3900,6 +3900,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
42 changes: 40 additions & 2 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ public static MethodSignature GetStringCtorActualSignature(MethodSignature signa

public static TypeDesc LowerToAbiType(TypeDesc type)
{
// Vector128<T> is the only SIMD type passed by value as a wasm v128. The other SIMD
// types (Vector2/3/4, Vector64<T>, Vector<T>, ...) are not yet handled by the wasm
Comment thread
tannergooding marked this conversation as resolved.
Outdated
// calling convention and fall through to the generic struct lowering below.
if (IsWasmV128Type(type))
{
return type;
}
Comment thread
tannergooding marked this conversation as resolved.
Outdated

if (!(type.IsValueType && !type.IsPrimitive))
{
return type;
Expand Down Expand Up @@ -87,10 +95,30 @@ public static TypeDesc LowerToAbiType(TypeDesc type)
}
}

/// <summary>
/// Determines whether a type is <see cref="System.Runtime.Intrinsics.Vector128{T}"/>, the
/// only SIMD vector type currently passed and returned by value as a wasm <c>v128</c>. The
/// JIT recognizes <c>Vector128&lt;T&gt;</c> as <c>TYP_SIMD16</c> on wasm; the other SIMD
/// types (Vector2/3/4, Vector64/256/512&lt;T&gt;, Vector&lt;T&gt;, ...) are not yet handled
/// by the wasm calling convention and continue to use the generic struct ABI.
/// </summary>
public static bool IsWasmV128Type(TypeDesc type)
Comment thread
tannergooding marked this conversation as resolved.
Outdated
{
return type is MetadataType metadataType &&
metadataType.IsIntrinsic &&
metadataType.Namespace == "System.Runtime.Intrinsics"u8 &&
metadataType.Name == "Vector128`1"u8;
}
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
tannergooding marked this conversation as resolved.

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 +193,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 +376,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 @@ -424,6 +458,10 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag
else
{
WasmValueType paramWasmType = LowerType(paramType);
if (paramWasmType == WasmValueType.V128)
{
((CompilerTypeSystemContext)paramType.Context).CacheV128Type(paramType);
}
Comment thread
tannergooding marked this conversation as resolved.
sigBuilder.Append(WasmValueTypeToSigChar(paramWasmType));
result.Add(paramWasmType);
}
Comment thread
tannergooding marked this conversation as resolved.
Outdated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,48 @@ 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);
foreach (string name in new[] { "Echo", "ThroughLocal", "Store", "CallEcho" })
{
Assert.True(
methods.Exists(method => method.SignatureString.Contains($".{name}(", StringComparison.Ordinal)),
$"Expected compiled method '{name}' in the wasm SIMD module.");
}
}
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
tannergooding marked this conversation as resolved.
}

[Fact]
public void RuntimeFunctionsSectionSizeExcludesSentinel()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

namespace Webcil;

// Exercises the wasm v128 calling convention (SIMD passed/returned/stored by value)
// without relying on any SIMD arithmetic intrinsics, so only the ABI/materialization
// paths are covered.
public static class WasmSimdModule
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector128<int> Echo(Vector128<int> value)
{
return value;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector128<int> ThroughLocal(Vector128<int> value)
{
Vector128<int> local = value;
return local;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static void Store(Vector128<int> value, ref Vector128<int> destination)
{
destination = value;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector128<int> CallEcho(Vector128<int> value)
{
return Echo(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ protected override void EmitCode(NodeFactory factory, ref Wasm.WasmEmitter instr
case WasmValueType.F64:
expressions.Add(F64.Load((ulong)interpOffsets[i]));
break;
case WasmValueType.V128:
expressions.Add(V128.Load((ulong)interpOffsets[i]));
break;
default:
throw new Exception("Unexpected wasm type for interpreter-to-R2R arg");
}
Expand Down
Loading