Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement swift lowering algorithm in Mono's type system #99439

Merged
merged 11 commits into from
Mar 28, 2024
268 changes: 268 additions & 0 deletions src/mono/mono/metadata/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -6580,3 +6580,271 @@ mono_wrapper_caches_free (MonoWrapperCaches *cache)
free_hash (cache->thunk_invoke_cache);
free_hash (cache->unsafe_accessor_cache);
}

#ifdef MONO_ARCH_HAVE_SWIFTCALL
typedef enum {
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
SWIFT_EMPTY,
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
SWIFT_OPAQUE,
SWIFT_INT64,
SWIFT_FLOAT,
SWIFT_DOUBLE,
} SwiftPhysicalLoweringKind;

static int get_swift_lowering_alignment (SwiftPhysicalLoweringKind kind) {
switch (kind) {
case SWIFT_INT64:
case SWIFT_DOUBLE:
return 8;
case SWIFT_FLOAT:
return 4;
default:
return 1;
}
}

static void set_lowering_range(GArray* lowered_bytes, guint32 offset, guint32 size, SwiftPhysicalLoweringKind kind) {
bool force_opaque = false;

if (offset != ALIGN_TO(offset, get_swift_lowering_alignment(kind))) {
// If the start of the range is not aligned, we need to force the entire range to be opaque.
force_opaque = true;
}

// Check if any of the range is non-empty.
// If so, we need to force this range to be opaque
// and extend the range mark the existing tag's range as opaque.

for (guint32 i = 0; i < size; ++i) {
SwiftPhysicalLoweringKind current = g_array_index(lowered_bytes, SwiftPhysicalLoweringKind, offset + i);
if (current != SWIFT_EMPTY && current != kind) {
force_opaque = true;
offset = ALIGN_DOWN_TO(offset, get_swift_lowering_alignment(current));
size = ALIGN_TO(size + offset, get_swift_lowering_alignment(current)) - offset;
break;
}
}

if (force_opaque) {
kind = SWIFT_OPAQUE;
}

for (guint32 i = 0; i < size; ++i) {
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
g_array_index(lowered_bytes, SwiftPhysicalLoweringKind, offset + i) = kind;
}
}

static void record_struct_field_physical_lowering (GArray* lowered_bytes, MonoType* type, guint32 offset);

static void record_inlinearray_struct_physical_lowering (GArray* lowered_bytes, MonoClass* klass, guint32 offset) {
// Get the first field and record its physical lowering N times
MonoClassField* field = mono_class_get_fields_internal (klass, NULL);
MonoType* fieldType = field->type;
for (int i = 0; i < m_class_inlinearray_value(klass); ++i) {
record_struct_field_physical_lowering(lowered_bytes, fieldType, offset + m_field_get_offset(field) + i * mono_type_size(fieldType, NULL));
}
}

static void record_struct_physical_lowering (GArray* lowered_bytes, MonoClass* klass, guint32 offset)
kotlarmilos marked this conversation as resolved.
Show resolved Hide resolved
{
if (m_class_is_inlinearray(klass)) {
record_inlinearray_struct_physical_lowering(lowered_bytes, klass, offset);
return;
}

// Iterate through each field of klass
gpointer iter = NULL;
MonoClassField* field;
while ((field = mono_class_get_fields_internal (klass, &iter))) {
if (field->type->attrs & FIELD_ATTRIBUTE_STATIC)
continue;
if (mono_field_is_deleted (field))
continue;

MonoType* fieldType = field->type;
if (fieldType->type == MONO_TYPE_VALUETYPE) {
record_struct_physical_lowering(lowered_bytes, mono_class_from_mono_type_internal(fieldType), offset + m_field_get_offset(field));
} else {
record_struct_field_physical_lowering(lowered_bytes, fieldType, offset + m_field_get_offset(field));
}
}
}

static void record_struct_field_physical_lowering (GArray* lowered_bytes, MonoType* type, guint32 offset) {
if (type->type == MONO_TYPE_VALUETYPE) {
record_struct_physical_lowering(lowered_bytes, mono_class_from_mono_type_internal(type), offset);
} else {
SwiftPhysicalLoweringKind kind = SWIFT_OPAQUE;
if (type->type == MONO_TYPE_I8 || type->type == MONO_TYPE_U8 || type->type == MONO_TYPE_PTR || type->type == MONO_TYPE_FNPTR) {
kind = SWIFT_INT64;
} else if (type->type == MONO_TYPE_R4) {
kind = SWIFT_FLOAT;
} else if (type->type == MONO_TYPE_R8) {
kind = SWIFT_DOUBLE;
}

set_lowering_range(lowered_bytes, offset, mono_type_size(type, NULL), kind);
}
}

SwiftPhysicalLowering
mono_marshal_get_swift_physical_lowering (MonoType *type, gboolean native_layout)
{
g_assert (!native_layout);
SwiftPhysicalLowering lowering = { 0 };

// Normalize pointer types to IntPtr.
// We don't need to care about specific pointer types at this ABI level.
if (type->type == MONO_TYPE_PTR || type->type == MONO_TYPE_FNPTR) {
type = m_class_get_byval_arg (mono_defaults.int_class);
}

if (type->type != MONO_TYPE_VALUETYPE && !mono_type_is_primitive(type)) {
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
lowering.byReference = FALSE;
return lowering;
}
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved

MonoClass *klass = mono_class_from_mono_type_internal (type);

GArray* lowered_bytes = g_array_sized_new(FALSE, TRUE, sizeof(SwiftPhysicalLoweringKind), m_class_get_instance_size(klass));

Copy link
Member

@lambdageek lambdageek Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to account for every byte of the struct, or just the first 4*PointerSize bytes?
Can't we abort this whole algorithm if we're ever certain we're out of room?

Wonder if we can stack alloc this array with a maximum size or else just bail out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now, 4*PointerSize is the max, but once we have SIMD support, the max goes up drastically (as we could have up to 4 256-byte vectors here).

I'm using a slightly different algorithm in NativeAOT that I could probably use here instead of matching the CoreCLR algorithm that doesn't allocate "struct size" bytes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok. I'm not sure we need a completely different algorithm (I didn't look at the NativeAOT one yet, so I don't know how much work it would entail), just rule out cases that are "obviously too big" - whatever that will mean even with SIMD. i'm particularly (perhaps mistakenly) concerned about InlineArray since it's trivial to make something absolutely massive.

// Loop through all fields and get the physical lowering for each field
record_struct_physical_lowering(lowered_bytes, klass, 0);

struct _SwiftInterval {
guint32 start;
guint32 size;
SwiftPhysicalLoweringKind kind;
};

GArray* intervals = g_array_new(FALSE, TRUE, sizeof(struct _SwiftInterval));

// Now we'll build the intervals from the lowered_bytes array
for (int i = 0; i < lowered_bytes->len; ++i) {
// Don't create an interval for empty bytes
if (g_array_index(lowered_bytes, SwiftPhysicalLoweringKind, i) == SWIFT_EMPTY) {
continue;
}

SwiftPhysicalLoweringKind current = g_array_index(lowered_bytes, SwiftPhysicalLoweringKind, i);

bool start_new_interval =
// We're at the start of the type
i == 0
// We're starting a new float (as we're aligned)
|| (i == ALIGN_TO(i, 4) && current == SWIFT_FLOAT)
// We're starting a new double or int64_t (as we're aligned)
|| (i == ALIGN_TO(i, 8) && (current == SWIFT_DOUBLE || current == SWIFT_INT64))
// We've changed interval types
|| current != g_array_index(lowered_bytes, SwiftPhysicalLoweringKind, i - 1);

if (start_new_interval) {
struct _SwiftInterval interval = { i, 1, current };
g_array_append_val(intervals, interval);
} else {
// Extend the current interval
(g_array_index(intervals, struct _SwiftInterval, intervals->len - 1)).size++;
}
}

// We've now produced the intervals, so we can release the lowered bytes
g_array_free(lowered_bytes, TRUE);

// Merge opaque intervals that are in the same pointer-sized block
for (int i = 0; i < intervals->len - 1; ++i) {
struct _SwiftInterval current = g_array_index(intervals, struct _SwiftInterval, i);
struct _SwiftInterval next = g_array_index(intervals, struct _SwiftInterval, i + 1);

if (current.kind == SWIFT_OPAQUE && next.kind == SWIFT_OPAQUE && current.start / TARGET_SIZEOF_VOID_P == next.start / TARGET_SIZEOF_VOID_P) {
current.size = next.start + next.size - current.start;
g_array_remove_index(intervals, i + 1);
i--;
}
}

// Now that we have the intervals, we can calculate the lowering
MonoTypeEnum lowered_types[4];
guint32 offsets[4];
guint32 num_lowered_types = 0;

for (int i = 0; i < intervals->len; ++i, ++num_lowered_types) {
if (num_lowered_types == 4) {
// We can't handle more than 4 fields
lowering.byReference = TRUE;
g_array_free(intervals, TRUE);
return lowering;
}

struct _SwiftInterval interval = g_array_index(intervals, struct _SwiftInterval, i);

offsets[num_lowered_types] = interval.start;

switch (interval.kind) {
case SWIFT_INT64:
lowered_types[num_lowered_types] = MONO_TYPE_I8;
break;
case SWIFT_FLOAT:
lowered_types[num_lowered_types] = MONO_TYPE_R4;
break;
case SWIFT_DOUBLE:
lowered_types[num_lowered_types] = MONO_TYPE_R8;
break;
case SWIFT_OPAQUE:
{
// We need to split the opaque ranges into integer parameters.
// As part of this splitting, we must ensure that we don't introduce alignment padding.
// This lowering algorithm should produce a lowered type sequence that would have the same padding for
// a naturally-aligned struct with the lowered fields as the original type has.
// This algorithm intends to split the opaque range into the least number of lowered elements that covers the entire range.
// The lowered range is allowed to extend past the end of the opaque range (including past the end of the struct),
// but not into the next non-empty interval.
// However, due to the properties of the lowering (the only non-8 byte elements of the lowering are 4-byte floats),
// we'll never encounter a scneario where we need would need to account for a correctly-aligned
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
// opaque range of > 4 bytes that we must not pad to 8 bytes.


// As long as we need to fill more than 4 bytes and the sequence is currently 8-byte aligned, we'll split into 8-byte integers.
// If we have more than 2 bytes but less than 4 and the sequence is 4-byte aligned, we'll use a 4-byte integer to represent the rest of the parameters.
// If we have 2 bytes and the sequence is 2-byte aligned, we'll use a 2-byte integer to represent the rest of the parameters.
// If we have 1 byte, we'll use a 1-byte integer to represent the rest of the parameters.
guint32 opaque_interval_start = interval.start;
guint32 remaining_interval_size = interval.size;
for (;remaining_interval_size > 0; num_lowered_types++) {
if (num_lowered_types == 4) {
// We can't handle more than 4 fields
lowering.byReference = TRUE;
g_array_free(intervals, TRUE);
return lowering;
}

offsets[num_lowered_types] = opaque_interval_start;

if (remaining_interval_size > 8 && (opaque_interval_start % 8 == 0)) {
lowered_types[num_lowered_types] = MONO_TYPE_I8;
remaining_interval_size -= 8;
opaque_interval_start += 8;
} else if (remaining_interval_size > 4 && (opaque_interval_start % 4 == 0)) {
lowered_types[num_lowered_types] = MONO_TYPE_I4;
remaining_interval_size -= 4;
opaque_interval_start += 4;
} else if (remaining_interval_size > 2 && (opaque_interval_start % 2 == 0)) {
lowered_types[num_lowered_types] = MONO_TYPE_I2;
remaining_interval_size -= 2;
opaque_interval_start += 2;
} else {
lowered_types[num_lowered_types] = MONO_TYPE_U1;
remaining_interval_size -= 1;
opaque_interval_start += 1;
}
}
}
}
}

memcpy(lowering.loweredElements, lowered_types, num_lowered_types * sizeof(MonoTypeEnum));
memcpy(lowering.offsets, offsets, num_lowered_types * sizeof(guint32));
lowering.numLoweredElements = num_lowered_types;
lowering.byReference = FALSE;

return lowering;
}
#endif /* MONO_ARCH_HAVE_SWIFTCALL */
12 changes: 12 additions & 0 deletions src/mono/mono/metadata/marshal.h
Original file line number Diff line number Diff line change
Expand Up @@ -742,4 +742,16 @@ mono_marshal_get_mono_callbacks_for_ilgen (void);
GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_self)
GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_error)

#ifdef MONO_ARCH_HAVE_SWIFTCALL
typedef struct {
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
gboolean byReference;
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
int numLoweredElements;
MonoTypeEnum loweredElements[4];
uint32_t offsets[4];
} SwiftPhysicalLowering;

SwiftPhysicalLowering
mono_marshal_get_swift_physical_lowering (MonoType *type, gboolean native_layout);
#endif /* MONO_ARCH_HAVE_SWIFTCALL */

#endif /* __MONO_MARSHAL_H__ */