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
19 changes: 14 additions & 5 deletions docs/design/coreclr/botr/readytorun-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,9 @@ The string format is:
| `V` | returns `v128` (a `Vector128<T>`, or a 16-byte `Vector<T>`) |
| `S<N>` | struct return via hidden buffer, `N` is the struct size in bytes |

Struct returns always use `S<N>` regardless of alignment. The aligned form is valid only
for parameters because their placement in the transition block depends on it.

**This pointer** (if the method has a `this` parameter):

| Encoding | Meaning |
Expand All @@ -1103,7 +1106,8 @@ it knows a hidden retbuf pointer argument is present in the Wasm parameter list.
| `f` | `f32` parameter |
| `d` | `f64` parameter |
| `V` | `v128` parameter (a `Vector128<T>`, or a 16-byte `Vector<T>`, passed by value) |
| `S<N>` | struct parameter passed by reference, `<N>` is the struct size in bytes |
| `S<N>` | struct parameter passed by reference, `<N>` is the struct size in bytes and its alignment is at most 8 |
| `A<N>` | struct parameter passed by reference, `<N>` is the struct size in bytes and its alignment exceeds 8 |
| `e` | empty struct parameter — elided from Wasm args but present in the string |
| `<slot><E>` | multi-slot parameter passed by value, see below |

Expand All @@ -1124,8 +1128,8 @@ matching the treatment of a struct wrapping a `v128`.
The digit is required. A repeated slot character without one — `ll`, `VV` — is not an
aggregate: it is two independent scalar parameters, which is how every implementation
reads it. So `ll2VV4` is `i64`, `Int128`, `Vector128<T>`, `Vector512<T>`. The grammar stays
unambiguous because no other token places a digit after a slot character; `S<N>` consumes
its own digits.
unambiguous because no other token places a digit after a slot character; struct tokens
consume their own size.

These types are still *returned* through a hidden buffer, encoded as `S<N>` like any other
aggregate. Limitation: a single digit carries both the slot count and the elevation factor,
Expand All @@ -1136,6 +1140,10 @@ elevation 1. No such type exists in the Wasm ABI today.
WasmAppBuilder does not emit or consume multi-slot tokens: they do not appear in
`InternalCall` or `PInvoke` signatures.

A struct argument is placed at its own alignment clamped to `[8, 16]` in the transition
block. Therefore `A<N>` covers every struct whose declared alignment exceeds 8: alignments
of 16 or higher all require the same 16-byte transition-block placement.

**Suffix**:

| Encoding | Meaning |
Expand All @@ -1162,14 +1170,15 @@ prefix to distinguish thunk categories:
| `void F(int x)` (instance) | `vTip` |
| `static MyStruct F()` where `MyStruct` is 16 bytes | `S16p` |
| `static void F(MyStruct s)` where `MyStruct` is 8 bytes | `vS8p` |
| `static void F(long tag, MyStruct s, int t)` where `MyStruct` is 32 bytes and at least 16-byte aligned | `vlA32ip` |
| `static int F(float x, double y)` | `ifdp` |
| `static long F(long tag, Int128 v, int t)` | `lll2ip` |
| `static int F(long tag, Vector512<int> v, int t)` | `ilV4ip` |
| `[UnmanagedCallersOnly] static int F(int x)` | `ii` |

**Slot sizing for structs**: When computing interpreter stack layout, struct parameters
(`S<N>`) consume `max(N / 8, 1)` interpreter stack slots, while all other parameter types
consume exactly 1 slot.
(`S<N>` and `A<N>`) consume `max((N + 7) / 8, 1)` interpreter stack slots, while all
other parameter types consume exactly 1 slot.

# References

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* a0017f91-c35d-4a2b-94a4-03d89f63e12b */
0xa0017f91,
0xc35d,
0x4a2b,
{0x94, 0xa4, 0x03, 0xd8, 0x9f, 0x63, 0xe1, 0x2b}
constexpr GUID JITEEVersionIdentifier = { /* 0d18a7df-af1f-4481-a72f-aa6cf8aa0a65 */
0x0d18a7df,
0xaf1f,
0x4481,
{0xa7, 0x2f, 0xaa, 0x6c, 0xf8, 0xaa, 0x0a, 0x65}
};

#endif // JIT_EE_VERSIONING_GUID_H
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public partial class CompilerTypeSystemContext
{
private readonly object _structCacheLock = new object();
private readonly Dictionary<int, TypeDesc> _structsBySize = new Dictionary<int, TypeDesc>();
private readonly Dictionary<int, TypeDesc> _alignedStructsBySize = new Dictionary<int, TypeDesc>();
private readonly Dictionary<int, TypeDesc> _returnStructsBySize = new Dictionary<int, TypeDesc>();
private volatile TypeDesc _cachedEmptyStruct;
private volatile TypeDesc _wasmV128Type;
Expand Down Expand Up @@ -90,18 +91,27 @@ public void CacheEmptyStruct(TypeDesc type)
}

/// <summary>
/// Caches a struct type by its element size, so RaiseSignature can retrieve a real
/// type of that size. Only the first struct encountered for a given size is retained.
/// Caches a struct type by the layout represented in its signature encoding, so
/// RaiseSignature can retrieve a real type with the same argument layout. Structs whose
/// effective argument alignment exceeds 8 use a separate cache because their
/// transition-block slots are 16-byte aligned.
/// </summary>
public void CacheStructBySize(TypeDesc type)
public void CacheStruct(TypeDesc type, bool requiresAlignedSlot)
{
int size = type.GetElementSize().AsInt;
if (size <= 0)
return;

lock (_structCacheLock)
{
_structsBySize.TryAdd(size, type);
if (requiresAlignedSlot)
{
_alignedStructsBySize.TryAdd(size, type);
}
else
{
_structsBySize.TryAdd(size, type);
}
}
}

Expand Down Expand Up @@ -150,5 +160,16 @@ public TypeDesc GetCachedStructOfSize(int size)

return null;
}

public TypeDesc GetCachedAlignedStructOfSize(int size)
{
lock (_structCacheLock)
{
if (_alignedStructsBySize.TryGetValue(size, out TypeDesc result))
return result;
}

return null;
}
}
}
4 changes: 2 additions & 2 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2449,12 +2449,12 @@ public static int GetClassAlignmentRequirementStatic(DefType type)

if (type is MetadataType metadataType && !metadataType.IsAutoLayout)
{
if (metadataType.IsSequentialLayout || MarshalUtils.IsBlittableType(metadataType))
if (metadataType.IsSequentialLayout ||
MarshalUtils.IsBlittableType(metadataType))
{
alignment = metadataType.InstanceFieldAlignment.AsInt;
}
}

if (type.Context.Target.SupportsAlign8 &&
alignment < 8 && type.RequiresAlign8())
{
Expand Down
26 changes: 18 additions & 8 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,14 @@ public static WasmValueType LowerType(TypeDesc type)

private static int ParseStructSize(string sig, ref int pos)
{
Debug.Assert(sig[pos] == 'S');
pos++; // skip 'S'
Debug.Assert(sig[pos] is 'S' or 'A');
pos++; // skip 'S'/'A'
int start = pos;
while (pos < sig.Length && char.IsDigit(sig[pos]))
{
pos++;
}

return int.Parse(sig.AsSpan(start, pos - start));
}

Expand Down Expand Up @@ -496,11 +497,16 @@ public static MethodSignature RaiseSignature(WasmSignature wasmSignature, TypeSy
parameters.Add(((CompilerTypeSystemContext)context).GetWasmElevatedType(c, elevation));
pos += 2;
}
else if (c == 'S')
else if (c is 'S' or 'A')
{
bool isAlignedStruct = c == 'A';
int structSize = ParseStructSize(sig, ref pos);
TypeDesc cachedStruct = ((CompilerTypeSystemContext)context).GetCachedStructOfSize(structSize);
Debug.Assert(cachedStruct is not null, $"No cached struct of size {structSize} for parameter in signature '{sig}'");
CompilerTypeSystemContext compilerContext = (CompilerTypeSystemContext)context;
TypeDesc cachedStruct = isAlignedStruct
? compilerContext.GetCachedAlignedStructOfSize(structSize)
: compilerContext.GetCachedStructOfSize(structSize);
Debug.Assert(cachedStruct is not null,
$"No cached {(isAlignedStruct ? "aligned " : "")}struct of size {structSize} for parameter in signature '{sig}'");
parameters.Add(cachedStruct);
}
else
Expand Down Expand Up @@ -622,7 +628,8 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag
returnContext.CacheReturnStructBySize(returnType);
if (!TryGetMultiSegmentLayout(returnType, out _, out _))
{
returnContext.CacheStructBySize(returnType);
int returnAlignment = CorInfoImpl.GetClassAlignmentRequirementStatic((DefType)returnType);
returnContext.CacheStruct(returnType, returnAlignment > 8);
}
}
}
Expand Down Expand Up @@ -719,9 +726,12 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag
}
else
{
sigBuilder.Append('S');
Debug.Assert(paramType is DefType);
int paramAlignment = CorInfoImpl.GetClassAlignmentRequirementStatic((DefType)paramType);
bool requiresAlignedSlot = paramAlignment > 8;
sigBuilder.Append(requiresAlignedSlot ? 'A' : 'S');
sigBuilder.Append(paramSize);
((CompilerTypeSystemContext)paramType.Context).CacheStructBySize(paramType);
((CompilerTypeSystemContext)paramType.Context).CacheStruct(paramType, requiresAlignedSlot);
result.Add(pointerType);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,43 @@ public void SameSizedOrdinaryStructsStayByReference(int fieldCount, string expec
Assert.Equal(expectedOffsets, GetArgumentOffsets(context, signature));
}

/// <summary>
/// Auto-layout structs use the runtime's effective aggregate alignment for argument placement,
/// which can be smaller than the alignment crossgen uses while laying out their fields.
/// </summary>
[Fact]
public void AutoLayoutStructUsesRuntimeAggregateAlignment()
{
ReadyToRunCompilerContext context = CreateWasmContext();
DefType alignedEight = MakeAlignedEightBlob(context, 32);
DefType int128 = InstantiateMultiSlotType(context, Int128Type);
DefType autoLayout = MakeValueTuple(context, int128, int128);

Assert.Equal(32, alignedEight.InstanceFieldSize.AsInt);
Assert.Equal(8, alignedEight.InstanceFieldAlignment.AsInt);
Assert.Equal(32, autoLayout.InstanceFieldSize.AsInt);
Assert.Equal(16, autoLayout.InstanceFieldAlignment.AsInt);
Assert.Equal(8, CorInfoImpl.GetClassAlignmentRequirementStatic(autoLayout));

MethodSignature autoLayoutSignature = MakeProbeSignature(context, autoLayout);
MethodSignature alignedEightSignature = MakeProbeSignature(context, alignedEight);

WasmSignature autoLayoutLowered =
WasmLowering.GetSignature(autoLayoutSignature, WasmLowering.LoweringFlags.None);
WasmSignature alignedEightLowered =
WasmLowering.GetSignature(alignedEightSignature, WasmLowering.LoweringFlags.None);

Assert.Equal("vlS32ip", autoLayoutLowered.SignatureString);
Assert.Equal("vlS32ip", alignedEightLowered.SignatureString);
Assert.Equal(new[] { 0, 8, 40 }, GetArgumentOffsets(context, autoLayoutSignature));
Assert.Equal(
GetArgumentOffsets(context, autoLayoutSignature),
GetArgumentOffsets(context, WasmLowering.RaiseSignature(autoLayoutLowered, context)));
Assert.Equal(
GetArgumentOffsets(context, alignedEightSignature),
GetArgumentOffsets(context, WasmLowering.RaiseSignature(alignedEightLowered, context)));
}

/// <summary>
/// Narrow vectors are not multi-slot. <see cref="System.Runtime.Intrinsics.Vector64{T}"/> is a
/// single <c>ulong</c> field, so it unwraps to a scalar <c>i64</c> rather than to any slot form.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public bool IsTrivialPointerSizedStruct()

public int GetFieldAlignment()
{
return ((DefType)_type).InstanceFieldAlignment.AsInt;
return CorInfoImpl.GetClassAlignmentRequirementStatic((DefType)_type);
}

/// <summary>
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1952,7 +1952,6 @@ unsigned CEEInfo::getClassAlignmentRequirementStatic(TypeHandle clsHnd)
result = pInfo->GetAlignmentRequirement();
}
}

#ifdef FEATURE_64BIT_ALIGNMENT
if (result < 8 && pMT->RequiresAlign8())
{
Expand Down
14 changes: 9 additions & 5 deletions src/coreclr/vm/wasm/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1049,14 +1049,15 @@ namespace
ToV128,
ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)
ToStruct, // S<N> — multi-field struct passed by pointer, structSize holds the size
ToStruct, // S<N>/A<N> — multi-field struct passed by pointer
ToEmpty, // e — empty struct, takes no wasm argument
};

struct ConvertResult
{
ConvertType type;
uint32_t structSize; // only meaningful when type == ToStruct
uint32_t structSize; // meaningful for struct and multi-slot types
bool requiresAlignedStructSlot; // only meaningful when type == ToStruct
};

// Lowers a TypeHandle to a ConvertResult, unwrapping single-field structs
Expand Down Expand Up @@ -1237,7 +1238,7 @@ namespace
// One field with padding — treat as multi-field struct
}

return { ConvertType::ToStruct, size };
return { ConvertType::ToStruct, size, CEEInfo::getClassAlignmentRequirementStatic(th) > INTERP_STACK_SLOT_SIZE };
}

ConvertResult ConvertibleTo(CorElementType argType, MetaSig& sig, bool isReturn)
Expand Down Expand Up @@ -1316,9 +1317,11 @@ namespace
case ConvertType::ToEmpty: c = 'e'; break;
case ConvertType::ToStruct:
{
// Encode as S<N> where N is the struct size in decimal
// A struct whose alignment exceeds 8 is placed at a 16-byte aligned transition-block
// slot. The interpreter stack does not support a larger placement alignment.
char sizeBuf[16];
int len = sprintf_s(sizeBuf, sizeof(sizeBuf), "S%u", cr.structSize);
int len = sprintf_s(sizeBuf, sizeof(sizeBuf), "%c%u",
cr.requiresAlignedStructSlot ? 'A' : 'S', cr.structSize);
for (int j = 0; j < len; j++)
{
if (pos + (uint32_t)j < maxSize)
Expand Down Expand Up @@ -1378,6 +1381,7 @@ namespace
{
cr.type = ConvertType::ToStruct;
}
cr.requiresAlignedStructSlot = false;

pos += AppendTypeCode(cr, keyBuffer, pos, maxSize);
}
Expand Down
9 changes: 7 additions & 2 deletions src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private static void Emit(StreamWriter w, IEnumerable<string> cookies)
{
var toks = SignatureMapper.ParseSignatureTokens(sig);
if (toks[0][0] == 'S' && toks[0].Length > 1)
structReturnSizes.Add(int.Parse(toks[0].Substring(1)));
structReturnSizes.Add(SignatureMapper.GetStructSize(toks[0]));
}

w.Write(
Expand Down Expand Up @@ -184,6 +184,11 @@ static List<string> ArgsWithSlotOffsets(List<string> args)
int slot = 0;
foreach (var token in args)
{
if (token[0] == 'A')
{
slot = (slot + 1) & ~1;
}

result.Add($"{SignatureMapper.TokenToArgType(token)}({slot})");
slot += SignatureMapper.TokenToSlotCount(token);
}
Expand All @@ -195,7 +200,7 @@ static List<string> ArgsWithSlotOffsets(List<string> args)
{
// For struct returns, use the typedef so emcc generates the correct sret ABI
if (returnToken[0] == 'S' && returnToken.Length > 1)
return (false, $"wasm_ret_{returnToken}");
return (false, $"wasm_ret_S{SignatureMapper.GetStructSize(returnToken)}");
return new(returnToken == "v", SignatureMapper.TokenToNativeType(returnToken));
}

Expand Down
Loading
Loading