diff --git a/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs b/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs index 0e8b2146b1..419af868dd 100644 --- a/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs +++ b/src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs @@ -69,14 +69,39 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol) throw new InvalidOperationException("Field Identifier was not visited correctly"); ClearState(); - var memberList = new List(structSymbol.Members.Length); - foreach (var member in structSymbol.Members) + var memberList = new List(structSymbol.Layout.Entries.Length); + foreach (var entry in structSymbol.Layout.Entries) { - VisitMember(member); + VisitMember(entry.Member); if (_syntax is not MemberDeclarationSyntax memberDeclarationSyntax) throw new InvalidOperationException("Member was not visited correctly"); ClearState(); memberDeclarationSyntax = memberDeclarationSyntax.WithLeadingTrivia(LineFeed, _indentation); + memberDeclarationSyntax = memberDeclarationSyntax.WithAttributeLists + ( + SingletonList + ( + AttributeList + ( + SingletonSeparatedList + ( + Attribute + (IdentifierName("FieldOffset")) + .WithArgumentList + ( + AttributeArgumentList + ( + SingletonSeparatedList + ( + AttributeArgument + (LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(entry.ByteOffset))) + ) + ) + ) + ) + ) + ) + ).WithLeadingTrivia(LineFeed, _indentation); memberList.Add(memberDeclarationSyntax); } @@ -85,8 +110,34 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol) var modifiers = TokenList(Token(SyntaxTriviaList.Empty, SyntaxKind.PublicKeyword, TriviaList(Space))); _syntax = StructDeclaration ( - List(), modifiers, identifierToken, null, null, - List(), members + SingletonList + ( + AttributeList + ( + SingletonSeparatedList + ( + Attribute(IdentifierName("StructLayout")) + .WithArgumentList + ( + AttributeArgumentList + ( + SingletonSeparatedList + ( + AttributeArgument + ( + MemberAccessExpression + ( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("LayoutKind"), IdentifierName("Explicit") + ) + ) + ) + ) + ) + ) + ) + .WithTrailingTrivia(LineFeed) + ), modifiers, identifierToken, null, null, List(), members ) .WithKeyword(Token(SyntaxTriviaList.Empty, SyntaxKind.StructKeyword, TriviaList(Space))) .WithOpenBraceToken(Token(TriviaList(LineFeed), SyntaxKind.OpenBraceToken, SyntaxTriviaList.Empty)) diff --git a/src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs b/src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs index b80b822dd0..a9744d41d4 100644 --- a/src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs +++ b/src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs @@ -9,11 +9,31 @@ namespace Silk.NET.SilkTouch.Symbols; /// A representing a struct. /// /// The Identifier of this struct -/// The Members of this struct +/// The layout of this struct /// /// In this context, a Struct means a type that represents the layout of a continuous block of memory. /// // /// Each meaningful place in this memory called a field (see ) is accessible via this type. // /// Fields are allowed to overlap. // /// Additionally it may contain one or multiple that are called with an instance of this type as their first argument. -public sealed record StructSymbol(IdentifierSymbol Identifier, ImmutableArray Members) : TypeSymbol(Identifier); +public sealed record StructSymbol(IdentifierSymbol Identifier, StructLayout Layout) : TypeSymbol(Identifier); + +/// +/// A representing the layout of a +/// +/// The entries of this layout +public sealed record StructLayout(ImmutableArray Entries) +{ + /// + /// An empty layout with no members + /// + public static readonly StructLayout Empty = new StructLayout(ImmutableArray.Empty); +} + +/// +/// Represents an entry in a +/// +/// The member symbol associated with this layout entry +/// The offset of this entry in bytes +/// +public sealed record LayoutEntry(MemberSymbol Member, int ByteOffset); diff --git a/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs b/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs index bfb626052a..6c056783f1 100644 --- a/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs +++ b/src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs @@ -75,7 +75,16 @@ protected virtual TypeSymbol VisitType(TypeSymbol typeSymbol) /// protected virtual StructSymbol VisitStruct(StructSymbol structSymbol) { - return new StructSymbol(VisitIdentifier(structSymbol.Identifier), structSymbol.Members.Select(VisitMember).ToImmutableArray()); + return new StructSymbol + ( + VisitIdentifier(structSymbol.Identifier), + new StructLayout + ( + structSymbol.Layout.Entries.Select + (x => new LayoutEntry(VisitMember(x.Member), x.ByteOffset)) + .ToImmutableArray() + ) + ); } /// diff --git a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs index 9180bedeb0..caac6dd623 100644 --- a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs +++ b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs @@ -12,7 +12,7 @@ public sealed class EmitterFieldIntegrationTests : EmitterTest [Fact] public void StructHasStructKeyword() { - var syntax = Transform(new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray.Empty), new IdentifierSymbol("Test"))); + var syntax = Transform(new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"),StructLayout.Empty), new IdentifierSymbol("Test"))); var result = syntax.ToFullString(); Assert.Equal("public int Test;", result); diff --git a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructMemberFieldsTests.cs b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructMemberFieldsTests.cs index 7e54d4da8d..bad78774f6 100644 --- a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructMemberFieldsTests.cs +++ b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructMemberFieldsTests.cs @@ -16,22 +16,67 @@ public void StructWithSingleFieldIntegration() ( new StructSymbol ( - new IdentifierSymbol("Test"), (new[] + new IdentifierSymbol("Test"), new StructLayout((new[] { - (MemberSymbol) new FieldSymbol + new LayoutEntry(new FieldSymbol ( - new StructSymbol(new IdentifierSymbol("int"), ImmutableArray.Empty), + new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("F1") - ) - }).ToImmutableArray() + ), 0) + }).ToImmutableArray()) ) ); Assert.Equal ( - @"public struct Test + @"[StructLayout(LayoutKind.Explicit)] +public struct Test { + [FieldOffset(0)] public int F1; +}", node.ToFullString() + ); + } + + [Fact] + public void StructWithMultipleFieldsIntegration() + { + var node = Transform + ( + new StructSymbol + ( + new IdentifierSymbol("Test"), new StructLayout((new[] + { + new LayoutEntry(new FieldSymbol + ( + new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), + new IdentifierSymbol("F1") + ), 0), + new LayoutEntry(new FieldSymbol + ( + new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), + new IdentifierSymbol("F2") + ), 20), + new LayoutEntry(new FieldSymbol + ( + new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), + new IdentifierSymbol("F3") + ), 12) + }).ToImmutableArray()) + ) + ); + + Assert.Equal + ( + @"[StructLayout(LayoutKind.Explicit)] +public struct Test +{ + [FieldOffset(0)] + public int F1; + [FieldOffset(20)] + public int F2; + [FieldOffset(12)] + public int F3; }", node.ToFullString() ); } diff --git a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs index 2055e9f306..45c8daee3b 100644 --- a/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs +++ b/tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs @@ -13,28 +13,28 @@ public sealed class EmitterStructTests : EmitterTest [Fact] public void StructIsStructSyntax() { - var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray.Empty)); + var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)); Assert.IsType(syntax); } [Fact] public void StructHasStructKeyword() { - var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray.Empty)) as StructDeclarationSyntax; + var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax; Assert.Equal("struct", syntax!.Keyword.Text); } [Fact] public void StructHasCorrectIdentifier() { - var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray.Empty)) as StructDeclarationSyntax; + var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax; Assert.Equal("Test", syntax!.Identifier.Text); } [Fact] public void StructIsOnlyPublic() { - var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray.Empty)) as StructDeclarationSyntax; + var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax; var @public = Assert.Single(syntax!.Modifiers); Assert.Equal("public", @public.Text); } @@ -43,8 +43,9 @@ public void StructIsOnlyPublic() public void IntegrationEmptyStruct() { // Note that this test also covers trivia, which is not checked otherwise. - Assert.Equal(@"public struct Test + Assert.Equal(@"[StructLayout(LayoutKind.Explicit)] +public struct Test { -}", Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray.Empty)).ToFullString()); +}", Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)).ToFullString()); } } diff --git a/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs index a75bb987fa..ec5d98de64 100644 --- a/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs +++ b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs @@ -13,7 +13,7 @@ public class FieldTests [Fact] public void FieldIsVisitedAsField() { - var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty), new IdentifierSymbol("")); + var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol("")); var visitor = new Mock { CallBase = true @@ -28,7 +28,7 @@ public void FieldIsVisitedAsField() [Fact] public void FieldIsVisitedAsMember() { - var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty), new IdentifierSymbol("")); + var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol("")); var visitor = new Mock { CallBase = true @@ -43,7 +43,7 @@ public void FieldIsVisitedAsMember() [Fact] public void FieldTypeIsVisited() { - var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty), new IdentifierSymbol("")); + var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol("")); var visitor = new Mock { CallBase = true @@ -58,7 +58,7 @@ public void FieldTypeIsVisited() [Fact] public void FieldIdentifierIsVisited() { - var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty), new IdentifierSymbol("")); + var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol("")); var visitor = new Mock { CallBase = true diff --git a/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs index 8c7048bb6a..26f1b25c76 100644 --- a/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs +++ b/tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs @@ -13,7 +13,7 @@ public class StructTests [Fact] public void StructSymbolIsVisitedAsType() { - var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty); + var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty); var visitor = new Mock { CallBase = true @@ -28,7 +28,7 @@ public void StructSymbolIsVisitedAsType() [Fact] public void StructSymbolIsVisitedAsStruct() { - var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty); + var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty); var visitor = new Mock { CallBase = true @@ -43,7 +43,7 @@ public void StructSymbolIsVisitedAsStruct() [Fact] public void StructIdentifierIsVisitedAsIdentifier() { - var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray.Empty); + var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty); var visitor = new Mock { CallBase = true @@ -58,11 +58,11 @@ public void StructIdentifierIsVisitedAsIdentifier() [Fact] public void StructMemberIsVisited() { - MemberSymbol member = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray.Empty), new IdentifierSymbol("Test1")); - var symbol = new StructSymbol(new IdentifierSymbol("Test"), (new[] + MemberSymbol member = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test1")); + var symbol = new StructSymbol(new IdentifierSymbol("Test"), new StructLayout((new[] { - member - }).ToImmutableArray()); + new LayoutEntry(member, 0) + }).ToImmutableArray())); var visitor = new Mock { CallBase = true @@ -77,13 +77,13 @@ public void StructMemberIsVisited() [Fact] public void StructMembersAreVisited() { - MemberSymbol member1 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray.Empty), new IdentifierSymbol("Test1")); - MemberSymbol member2 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray.Empty), new IdentifierSymbol("Test2")); - var symbol = new StructSymbol(new IdentifierSymbol("Test"), (new[] + MemberSymbol member1 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test1")); + MemberSymbol member2 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test2")); + var symbol = new StructSymbol(new IdentifierSymbol("Test"), new StructLayout((new[] { - member1, - member2 - }).ToImmutableArray()); + new LayoutEntry(member1, 0), + new LayoutEntry(member2, 4) + }).ToImmutableArray())); var visitor = new Mock { CallBase = true