diff --git a/src/TypeSystem/src/Common/FieldLayout.cs b/src/TypeSystem/src/Common/FieldLayout.cs
index a2653d2e0c6..36c33926531 100644
--- a/src/TypeSystem/src/Common/FieldLayout.cs
+++ b/src/TypeSystem/src/Common/FieldLayout.cs
@@ -546,10 +546,13 @@ internal void ComputeInstanceFieldLayout()
_instanceFieldAlignment = computedLayout.FieldAlignment;
_instanceByteCount = computedLayout.ByteCount;
- foreach (var fieldAndOffset in computedLayout.Offsets)
+ if (computedLayout.Offsets != null)
{
- Debug.Assert(fieldAndOffset.Field.OwningType == this);
- fieldAndOffset.Field.InitializeOffset(fieldAndOffset.Offset);
+ foreach (var fieldAndOffset in computedLayout.Offsets)
+ {
+ Debug.Assert(fieldAndOffset.Field.OwningType == this);
+ fieldAndOffset.Field.InitializeOffset(fieldAndOffset.Offset);
+ }
}
_fieldLayoutFlags.AddFlags(FieldLayoutFlags.HasInstanceFieldLayout);
diff --git a/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj b/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj
new file mode 100644
index 00000000000..2bda7169d8d
--- /dev/null
+++ b/src/TypeSystem/tests/CoreTestAssembly/CoreTestAssembly.csproj
@@ -0,0 +1,38 @@
+
+
+
+
+ Debug
+ AnyCPU
+ Library
+ CoreTestAssembly
+
+ false
+ true
+
+
+ Portable
+ .NETPortable
+ v4.5
+ Profile7
+ .NET Portable Subset
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs b/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs
new file mode 100644
index 00000000000..62e1a9a4acc
--- /dev/null
+++ b/src/TypeSystem/tests/CoreTestAssembly/InstanceFieldLayout.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+#pragma warning disable 169
+
+namespace ContainsPointers
+{
+ struct NoPointers
+ {
+ int int1;
+ byte byte1;
+ char char1;
+ }
+
+ struct StillNoPointers
+ {
+ NoPointers noPointers1;
+ bool bool1;
+ }
+
+ class ClassNoPointers
+ {
+ char char1;
+ }
+
+ struct HasPointers
+ {
+ string string1;
+ }
+
+ struct FieldHasPointers
+ {
+ HasPointers hasPointers1;
+ }
+
+ class ClassHasPointers
+ {
+ ClassHasPointers classHasPointers1;
+ }
+
+ class BaseClassHasPointers : ClassHasPointers
+ {
+ }
+
+ public class ClassHasIntArray
+ {
+ int[] intArrayField;
+ }
+
+ public class ClassHasArrayOfClassType
+ {
+ ClassNoPointers[] classTypeArray;
+ }
+}
+
+namespace Explicit
+{
+ [StructLayout(LayoutKind.Explicit)]
+ class Class1
+ {
+ static int Stat;
+ [FieldOffset(4)]
+ bool Bar;
+ [FieldOffset(10)]
+ char Baz;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ class Class2 : Class1
+ {
+ [FieldOffset(0)]
+ int Lol;
+ [FieldOffset(20)]
+ byte Omg;
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 40)]
+ class ExplicitSize : Class1
+ {
+ [FieldOffset(0)]
+ int Lol;
+ [FieldOffset(20)]
+ byte Omg;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public class ExplicitEmptyClass
+ {
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct ExplicitEmptyStruct
+ {
+ }
+}
+
+namespace Sequential
+{
+ class Class1
+ {
+ int MyInt;
+ bool MyBool;
+ char MyChar;
+ string MyString;
+ byte[] MyByteArray;
+ Class1 MyClass1SelfRef;
+ }
+
+ class Class2 : Class1
+ {
+ int MyInt2;
+ }
+
+ struct Struct0
+ {
+ bool b1;
+ bool b2;
+ bool b3;
+ int i1;
+ string s1;
+ }
+
+ struct Struct1
+ {
+ Struct0 MyStruct0;
+ bool MyBool;
+ }
+}
+
diff --git a/src/TypeSystem/tests/CoreTestAssembly/Platform.cs b/src/TypeSystem/tests/CoreTestAssembly/Platform.cs
new file mode 100644
index 00000000000..e22eb6ed725
--- /dev/null
+++ b/src/TypeSystem/tests/CoreTestAssembly/Platform.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#pragma warning disable 649
+
+namespace System
+{
+ // Dummy core types to allow us compiling this assembly as a core library so that the type
+ // system tests don't have a dependency on a real core library.
+
+ // We might need to bring in some extra things (Interface lists? Virtual methods on Object?),
+ // but let's postpone that until actually needed.
+
+ public class Object
+ {
+ internal IntPtr m_pEEType;
+ }
+
+ public struct Void { }
+ public struct Boolean { }
+ public struct Char { }
+ public struct SByte { }
+ public struct Byte { }
+ public struct Int16 { }
+ public struct UInt16 { }
+ public struct Int32 { }
+ public struct UInt32 { }
+ public struct Int64 { }
+ public struct UInt64 { }
+ public struct IntPtr { }
+ public struct UIntPtr { }
+ public struct Single { }
+ public struct Double { }
+ public abstract class ValueType { }
+ public abstract class Enum : ValueType { }
+ public struct Nullable where T : struct { }
+
+ public sealed class String { }
+ public abstract class Array { }
+ public abstract class Delegate { }
+ public abstract class MulticastDelegate : Delegate { }
+
+ public struct RuntimeTypeHandle { }
+ public struct RuntimeMethodHandle { }
+ public struct RuntimeFieldHandle { }
+
+ public class Attribute { }
+}
+
+namespace System.Runtime.InteropServices
+{
+ public enum LayoutKind
+ {
+ Sequential = 0, // 0x00000008,
+ Explicit = 2, // 0x00000010,
+ Auto = 3, // 0x00000000,
+ }
+
+ public sealed class StructLayoutAttribute : Attribute
+ {
+ internal LayoutKind _val;
+
+ public StructLayoutAttribute(LayoutKind layoutKind)
+ {
+ _val = layoutKind;
+ }
+
+ public LayoutKind Value { get { return _val; } }
+ public int Pack;
+ public int Size;
+ }
+
+ public sealed class FieldOffsetAttribute : Attribute
+ {
+ private int _val;
+ public FieldOffsetAttribute(int offset)
+ {
+ _val = offset;
+ }
+ public int Value { get { return _val; } }
+ }
+}
+
diff --git a/src/TypeSystem/tests/InstanceFieldLayoutTests.cs b/src/TypeSystem/tests/InstanceFieldLayoutTests.cs
new file mode 100644
index 00000000000..b3485b6f59c
--- /dev/null
+++ b/src/TypeSystem/tests/InstanceFieldLayoutTests.cs
@@ -0,0 +1,303 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Internal.TypeSystem.Ecma;
+using Internal.TypeSystem;
+
+using Xunit;
+
+namespace TypeSystemTests
+{
+ public class InstanceFieldLayoutTests
+ {
+ TestTypeSystemContext _context;
+ EcmaModule _testModule;
+
+ public InstanceFieldLayoutTests()
+ {
+ _context = new TestTypeSystemContext(TargetArchitecture.X64);
+ var systemModule = _context.CreateModuleForSimpleName("CoreTestAssembly");
+ _context.SetSystemModule(systemModule);
+
+ _testModule = systemModule;
+ }
+
+ [Fact]
+ public void TestExplicitLayout()
+ {
+ MetadataType t = _testModule.GetType("Explicit", "Class1");
+
+ // With 64bit, there should be 8 bytes for the System.Object EE data pointer +
+ // 10 bytes up until the offset of the char field + the char size of 2 + we
+ // round up the whole instance size to the next pointer size (+4) = 24
+ Assert.Equal(24, t.InstanceByteCount);
+
+ foreach (var field in t.GetFields())
+ {
+ if (field.IsStatic)
+ continue;
+
+ if (field.Name == "Bar")
+ {
+ // Bar has explicit offset 4 and is in a class (with S.O size overhead of )
+ // Therefore it should have offset 4 + 8 = 12
+ Assert.Equal(12, field.Offset);
+ }
+ else if (field.Name == "Baz")
+ {
+ // Baz has explicit offset 10. 10 + 8 = 18
+ Assert.Equal(18, field.Offset);
+ }
+ else
+ {
+ Assert.True(false);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestExplicitLayoutThatIsEmpty()
+ {
+ var explicitEmptyClassType = _testModule.GetType("Explicit", "ExplicitEmptyClass");
+
+ // ExplicitEmpty class has 8 from System.Object overhead = 8
+ Assert.Equal(8, explicitEmptyClassType.InstanceByteCount);
+
+ var explicitEmptyStructType = _testModule.GetType("Explicit", "ExplicitEmptyStruct");
+
+ // ExplicitEmpty class has 0 bytes in it... so instance field size gets pushed up to 1.
+ Assert.Equal(1, explicitEmptyStructType.InstanceFieldSize);
+ }
+
+ [Fact]
+ public void TestExplicitTypeLayoutWithSize()
+ {
+ var explicitSizeType = _testModule.GetType("Explicit", "ExplicitSize");
+ Assert.Equal(48, explicitSizeType.InstanceByteCount);
+ }
+
+ [Fact]
+ public void TestExplicitTypeLayoutWithInheritance()
+ {
+ MetadataType class2Type = _testModule.GetType("Explicit", "Class2");
+
+ // Class1 has size 24 which Class2 inherits from. Class2 adds a byte at offset 20, so + 21
+ // = 45, rounding up to the next pointer size = 48
+ Assert.Equal(48, class2Type.InstanceByteCount);
+
+ foreach (var f in class2Type.GetFields())
+ {
+ if (f.IsStatic)
+ continue;
+
+ if (f.Name == "Lol")
+ {
+ // First field after base class, with offset 0 so it should lie on the byte count of
+ // the base class = 24
+ Assert.Equal(24, f.Offset);
+ }
+ else if (f.Name == "Omg")
+ {
+ // Offset 20 from base class byte count = 44
+ Assert.Equal(44, f.Offset);
+ }
+ else
+ {
+ Assert.True(false);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestSequentialTypeLayout()
+ {
+ MetadataType class1Type = _testModule.GetType("Sequential", "Class1");
+
+ // Byte count
+ // Base Class 8
+ // MyInt 4
+ // MyBool 1 + 1 padding
+ // MyChar 2
+ // MyString 8
+ // MyByteArray 8
+ // MyClass1SelfRef 8
+ // -------------------
+ // 40
+ Assert.Equal(0x28, class1Type.InstanceByteCount);
+
+ foreach (var f in class1Type.GetFields())
+ {
+ if (f.IsStatic)
+ continue;
+
+ switch (f.Name)
+ {
+ case "MyInt":
+ Assert.Equal(0x8, f.Offset);
+ break;
+ case "MyBool":
+ Assert.Equal(0xC, f.Offset);
+ break;
+ case "MyChar":
+ Assert.Equal(0xE, f.Offset);
+ break;
+ case "MyString":
+ Assert.Equal(0x10, f.Offset);
+ break;
+ case "MyByteArray":
+ Assert.Equal(0x18, f.Offset);
+ break;
+ case "MyClass1SelfRef":
+ Assert.Equal(0x20, f.Offset);
+ break;
+ default:
+ Assert.True(false);
+ break;
+ }
+ }
+ }
+
+ [Fact]
+ public void TestSequentialTypeLayoutInheritance()
+ {
+ MetadataType class2Type = _testModule.GetType("Sequential", "Class2");
+
+ // Byte count
+ // Base Class 40
+ // MyInt2 4 + 4 byte padding to make class size % pointer size == 0
+ // -------------------
+ // 44
+ Assert.Equal(0x30, class2Type.InstanceByteCount);
+
+ foreach (var f in class2Type.GetFields())
+ {
+ if (f.IsStatic)
+ continue;
+
+ switch (f.Name)
+ {
+ case "MyInt2":
+ Assert.Equal(0x28, f.Offset);
+ break;
+ default:
+ Assert.True(false);
+ break;
+ }
+ }
+ }
+
+ [Fact]
+ public void TestSequentialTypeLayoutStruct()
+ {
+ MetadataType struct0Type = _testModule.GetType("Sequential", "Struct0");
+
+ // Byte count
+ // bool b1 1
+ // bool b2 1
+ // bool b3 1 + 1 padding for int alignment
+ // int i1 4
+ // string s1 8
+ // -------------------
+ // 16 (0x10)
+ Assert.Equal(0x10, struct0Type.InstanceByteCount);
+
+ foreach (var f in struct0Type.GetFields())
+ {
+ if (f.IsStatic)
+ continue;
+
+ switch (f.Name)
+ {
+ case "b1":
+ Assert.Equal(0x0, f.Offset);
+ break;
+ case "b2":
+ Assert.Equal(0x1, f.Offset);
+ break;
+ case "b3":
+ Assert.Equal(0x2, f.Offset);
+ break;
+ case "i1":
+ Assert.Equal(0x4, f.Offset);
+ break;
+ case "s1":
+ Assert.Equal(0x8, f.Offset);
+ break;
+ default:
+ Assert.True(false);
+ break;
+ }
+ }
+ }
+
+ [Fact]
+ // Test that when a struct is used as a field, we use its instance byte size as the size (ie, treat it
+ // as a value type) and not a pointer size.
+ public void TestSequentialTypeLayoutStructEmbedded()
+ {
+ MetadataType struct1Type = _testModule.GetType("Sequential", "Struct1");
+
+ // Byte count
+ // struct MyStruct0 16
+ // bool MyBool 1
+ // -----------------------
+ // 24 (0x18)
+ Assert.Equal(0x18, struct1Type.InstanceByteCount);
+
+ foreach (var f in struct1Type.GetFields())
+ {
+ if (f.IsStatic)
+ continue;
+
+ switch (f.Name)
+ {
+ case "MyStruct0":
+ Assert.Equal(0x0, f.Offset);
+ break;
+ case "MyBool":
+ Assert.Equal(0x10, f.Offset);
+ break;
+ default:
+ Assert.True(false);
+ break;
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTypeContainsPointers()
+ {
+ MetadataType type = _testModule.GetType("ContainsPointers", "NoPointers");
+ Assert.False(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "StillNoPointers");
+ Assert.False(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "ClassNoPointers");
+ Assert.False(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "HasPointers");
+ Assert.True(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "FieldHasPointers");
+ Assert.True(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "ClassHasPointers");
+ Assert.True(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "BaseClassHasPointers");
+ Assert.True(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "ClassHasIntArray");
+ Assert.True(type.ContainsPointers);
+
+ type = _testModule.GetType("ContainsPointers", "ClassHasArrayOfClassType");
+ Assert.True(type.ContainsPointers);
+ }
+ }
+}
diff --git a/src/TypeSystem/tests/TestTypeSystemContext.cs b/src/TypeSystem/tests/TestTypeSystemContext.cs
new file mode 100644
index 00000000000..f81396f1a13
--- /dev/null
+++ b/src/TypeSystem/tests/TestTypeSystemContext.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Debug = System.Diagnostics.Debug;
+
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+using System.Reflection.PortableExecutable;
+using System.IO;
+
+namespace TypeSystemTests
+{
+ class TestTypeSystemContext : TypeSystemContext
+ {
+ static readonly string[] s_wellKnownTypeNames = new string[] {
+ "Void",
+ "Boolean",
+ "Char",
+ "SByte",
+ "Byte",
+ "Int16",
+ "UInt16",
+ "Int32",
+ "UInt32",
+ "Int64",
+ "UInt64",
+ "IntPtr",
+ "UIntPtr",
+ "Single",
+ "Double",
+
+ "ValueType",
+ "Enum",
+ "Nullable`1",
+
+ "Object",
+ "String",
+ "Array",
+ "MulticastDelegate",
+
+ "RuntimeTypeHandle",
+ "RuntimeMethodHandle",
+ "RuntimeFieldHandle",
+ };
+
+ MetadataType[] _wellKnownTypes = new MetadataType[s_wellKnownTypeNames.Length];
+
+ EcmaModule _systemModule;
+
+ Dictionary _modules = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public TestTypeSystemContext(TargetArchitecture arch)
+ : base(new TargetDetails(arch))
+ {
+ }
+
+ public override MetadataType GetWellKnownType(WellKnownType wellKnownType)
+ {
+ return _wellKnownTypes[(int)wellKnownType - 1];
+ }
+
+ public void SetSystemModule(EcmaModule systemModule)
+ {
+ _systemModule = systemModule;
+
+ // Sanity check the name table
+ Debug.Assert(s_wellKnownTypeNames[(int)WellKnownType.MulticastDelegate - 1] == "MulticastDelegate");
+
+ // Initialize all well known types - it will save us from checking the name for each loaded type
+ for (int typeIndex = 0; typeIndex < _wellKnownTypes.Length; typeIndex++)
+ {
+ MetadataType type = _systemModule.GetType("System", s_wellKnownTypeNames[typeIndex]);
+ type.SetWellKnownType((WellKnownType)(typeIndex + 1));
+ _wellKnownTypes[typeIndex] = type;
+ }
+ }
+
+ public EcmaModule GetModuleForSimpleName(string simpleName)
+ {
+ EcmaModule existingModule;
+ if (_modules.TryGetValue(simpleName, out existingModule))
+ return existingModule;
+
+ return CreateModuleForSimpleName(simpleName);
+ }
+
+ public EcmaModule CreateModuleForSimpleName(string simpleName)
+ {
+ EcmaModule module = new EcmaModule(this, new PEReader(File.OpenRead(simpleName + ".dll")));
+ _modules.Add(simpleName, module);
+ return module;
+ }
+
+ public override object ResolveAssembly(System.Reflection.AssemblyName name)
+ {
+ return GetModuleForSimpleName(name.Name);
+ }
+ }
+}
diff --git a/src/TypeSystem/tests/TypeSystem.Tests.csproj b/src/TypeSystem/tests/TypeSystem.Tests.csproj
new file mode 100644
index 00000000000..c4e06dc0eed
--- /dev/null
+++ b/src/TypeSystem/tests/TypeSystem.Tests.csproj
@@ -0,0 +1,45 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8874CEEC-5594-511B-A44C-5CA9EC1CEB11}
+ Library
+ TypeSystem.Tests
+ TypeSystem.Tests
+
+
+
+
+
+
+
+
+
+ {dd5b6baa-d41a-4a6e-9e7d-83060f394b10}
+ TypeSystem
+
+
+ {dd5b6baa-d41a-4a6e-9e7d-83060f394b10}
+ TypeSystem
+
+
+
+
+ false
+ Content
+ PreserveNewest
+ Build;DebugSymbolsProjectOutputGroup
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TypeSystem/tests/project.json b/src/TypeSystem/tests/project.json
new file mode 100644
index 00000000000..d0c8706163e
--- /dev/null
+++ b/src/TypeSystem/tests/project.json
@@ -0,0 +1,22 @@
+{
+ "dependencies": {
+ "System.Collections": "4.0.10",
+ "System.Collections.Concurrent": "4.0.10",
+ "System.Console": "4.0.0-beta-*",
+ "System.Diagnostics.Debug": "4.0.10",
+ "System.Diagnostics.Tracing": "4.0.20",
+ "System.Linq": "4.0.0",
+ "System.IO.FileSystem": "4.0.0",
+ "System.Reflection": "4.0.10",
+ "System.Runtime": "4.0.20",
+ "System.Runtime.Extensions": "4.0.10",
+ "System.Threading": "4.0.10",
+ "System.Threading.Tasks": "4.0.10",
+ "xunit": "2.1.0-beta3-*",
+ "xunit.netcore.extensions": "1.0.0-prerelease-*",
+ "System.Reflection.Metadata": "1.0.22"
+ },
+ "frameworks": {
+ "dotnet": {}
+ }
+}
\ No newline at end of file