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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
11 changes: 10 additions & 1 deletion src/coreclr/tools/Common/Compiler/VectorFieldLayoutAlgorithm.cs
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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