Skip to content

Commit a280613

Browse files
authored
Struct Layouts (#898)
* Restructure Struct to allow layouting * Add failing layout tests * Apply Layouting attributes * Accept StructLayout attribute on empty structs
1 parent dac966d commit a280613

File tree

8 files changed

+164
-38
lines changed

8 files changed

+164
-38
lines changed

src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,39 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol)
6969
throw new InvalidOperationException("Field Identifier was not visited correctly");
7070
ClearState();
7171

72-
var memberList = new List<MemberDeclarationSyntax>(structSymbol.Members.Length);
73-
foreach (var member in structSymbol.Members)
72+
var memberList = new List<MemberDeclarationSyntax>(structSymbol.Layout.Entries.Length);
73+
foreach (var entry in structSymbol.Layout.Entries)
7474
{
75-
VisitMember(member);
75+
VisitMember(entry.Member);
7676
if (_syntax is not MemberDeclarationSyntax memberDeclarationSyntax)
7777
throw new InvalidOperationException("Member was not visited correctly");
7878
ClearState();
7979
memberDeclarationSyntax = memberDeclarationSyntax.WithLeadingTrivia(LineFeed, _indentation);
80+
memberDeclarationSyntax = memberDeclarationSyntax.WithAttributeLists
81+
(
82+
SingletonList
83+
(
84+
AttributeList
85+
(
86+
SingletonSeparatedList
87+
(
88+
Attribute
89+
(IdentifierName("FieldOffset"))
90+
.WithArgumentList
91+
(
92+
AttributeArgumentList
93+
(
94+
SingletonSeparatedList
95+
(
96+
AttributeArgument
97+
(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(entry.ByteOffset)))
98+
)
99+
)
100+
)
101+
)
102+
)
103+
)
104+
).WithLeadingTrivia(LineFeed, _indentation);
80105
memberList.Add(memberDeclarationSyntax);
81106
}
82107

@@ -85,8 +110,34 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol)
85110
var modifiers = TokenList(Token(SyntaxTriviaList.Empty, SyntaxKind.PublicKeyword, TriviaList(Space)));
86111
_syntax = StructDeclaration
87112
(
88-
List<AttributeListSyntax>(), modifiers, identifierToken, null, null,
89-
List<TypeParameterConstraintClauseSyntax>(), members
113+
SingletonList
114+
(
115+
AttributeList
116+
(
117+
SingletonSeparatedList
118+
(
119+
Attribute(IdentifierName("StructLayout"))
120+
.WithArgumentList
121+
(
122+
AttributeArgumentList
123+
(
124+
SingletonSeparatedList
125+
(
126+
AttributeArgument
127+
(
128+
MemberAccessExpression
129+
(
130+
SyntaxKind.SimpleMemberAccessExpression,
131+
IdentifierName("LayoutKind"), IdentifierName("Explicit")
132+
)
133+
)
134+
)
135+
)
136+
)
137+
)
138+
)
139+
.WithTrailingTrivia(LineFeed)
140+
), modifiers, identifierToken, null, null, List<TypeParameterConstraintClauseSyntax>(), members
90141
)
91142
.WithKeyword(Token(SyntaxTriviaList.Empty, SyntaxKind.StructKeyword, TriviaList(Space)))
92143
.WithOpenBraceToken(Token(TriviaList(LineFeed), SyntaxKind.OpenBraceToken, SyntaxTriviaList.Empty))

src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,31 @@ namespace Silk.NET.SilkTouch.Symbols;
99
/// A <see cref="TypeSymbol"/> representing a <c>struct</c>.
1010
/// </summary>
1111
/// <param name="Identifier">The Identifier of this struct</param>
12-
/// <param name="Members">The Members of this struct</param>
12+
/// <param name="Layout">The layout of this struct</param>
1313
/// <remarks>
1414
/// In this context, a Struct means a type that represents the layout of a continuous block of memory.
1515
/// </remarks>
1616
// /// Each meaningful place in this memory called a field (see <see cref="FieldSymbol"/>) is accessible via this type.
1717
// /// Fields are allowed to overlap.
1818
// /// Additionally it may contain one or multiple <see cref="MethodSymbol"/> that are called with an instance of this type as their first argument.
19-
public sealed record StructSymbol(IdentifierSymbol Identifier, ImmutableArray<MemberSymbol> Members) : TypeSymbol(Identifier);
19+
public sealed record StructSymbol(IdentifierSymbol Identifier, StructLayout Layout) : TypeSymbol(Identifier);
20+
21+
/// <summary>
22+
/// A <see cref="StructSymbol"/> representing the layout of a <see cref="StructSymbol"/>
23+
/// </summary>
24+
/// <param name="Entries">The entries of this layout</param>
25+
public sealed record StructLayout(ImmutableArray<LayoutEntry> Entries)
26+
{
27+
/// <summary>
28+
/// An empty layout with no members
29+
/// </summary>
30+
public static readonly StructLayout Empty = new StructLayout(ImmutableArray<LayoutEntry>.Empty);
31+
}
32+
33+
/// <summary>
34+
/// Represents an entry in a <see cref="StructLayout"/>
35+
/// </summary>
36+
/// <param name="Member">The member symbol associated with this layout entry</param>
37+
/// <param name="ByteOffset">The offset of this entry in bytes</param>
38+
/// <seealso cref="StructLayout"/>
39+
public sealed record LayoutEntry(MemberSymbol Member, int ByteOffset);

src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,16 @@ protected virtual TypeSymbol VisitType(TypeSymbol typeSymbol)
7575
/// </remarks>
7676
protected virtual StructSymbol VisitStruct(StructSymbol structSymbol)
7777
{
78-
return new StructSymbol(VisitIdentifier(structSymbol.Identifier), structSymbol.Members.Select(VisitMember).ToImmutableArray());
78+
return new StructSymbol
79+
(
80+
VisitIdentifier(structSymbol.Identifier),
81+
new StructLayout
82+
(
83+
structSymbol.Layout.Entries.Select
84+
(x => new LayoutEntry(VisitMember(x.Member), x.ByteOffset))
85+
.ToImmutableArray()
86+
)
87+
);
7988
}
8089

8190
/// <summary>

tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterFieldTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class EmitterFieldIntegrationTests : EmitterTest
1212
[Fact]
1313
public void StructHasStructKeyword()
1414
{
15-
var syntax = Transform(new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol("Test")));
15+
var syntax = Transform(new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"),StructLayout.Empty), new IdentifierSymbol("Test")));
1616

1717
var result = syntax.ToFullString();
1818
Assert.Equal("public int Test;", result);

tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructMemberFieldsTests.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,67 @@ public void StructWithSingleFieldIntegration()
1616
(
1717
new StructSymbol
1818
(
19-
new IdentifierSymbol("Test"), (new[]
19+
new IdentifierSymbol("Test"), new StructLayout((new[]
2020
{
21-
(MemberSymbol) new FieldSymbol
21+
new LayoutEntry(new FieldSymbol
2222
(
23-
new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty),
23+
new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty),
2424
new IdentifierSymbol("F1")
25-
)
26-
}).ToImmutableArray()
25+
), 0)
26+
}).ToImmutableArray())
2727
)
2828
);
2929

3030
Assert.Equal
3131
(
32-
@"public struct Test
32+
@"[StructLayout(LayoutKind.Explicit)]
33+
public struct Test
3334
{
35+
[FieldOffset(0)]
3436
public int F1;
37+
}", node.ToFullString()
38+
);
39+
}
40+
41+
[Fact]
42+
public void StructWithMultipleFieldsIntegration()
43+
{
44+
var node = Transform
45+
(
46+
new StructSymbol
47+
(
48+
new IdentifierSymbol("Test"), new StructLayout((new[]
49+
{
50+
new LayoutEntry(new FieldSymbol
51+
(
52+
new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty),
53+
new IdentifierSymbol("F1")
54+
), 0),
55+
new LayoutEntry(new FieldSymbol
56+
(
57+
new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty),
58+
new IdentifierSymbol("F2")
59+
), 20),
60+
new LayoutEntry(new FieldSymbol
61+
(
62+
new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty),
63+
new IdentifierSymbol("F3")
64+
), 12)
65+
}).ToImmutableArray())
66+
)
67+
);
68+
69+
Assert.Equal
70+
(
71+
@"[StructLayout(LayoutKind.Explicit)]
72+
public struct Test
73+
{
74+
[FieldOffset(0)]
75+
public int F1;
76+
[FieldOffset(20)]
77+
public int F2;
78+
[FieldOffset(12)]
79+
public int F3;
3580
}", node.ToFullString()
3681
);
3782
}

tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,28 @@ public sealed class EmitterStructTests : EmitterTest
1313
[Fact]
1414
public void StructIsStructSyntax()
1515
{
16-
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty));
16+
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty));
1717
Assert.IsType<StructDeclarationSyntax>(syntax);
1818
}
1919

2020
[Fact]
2121
public void StructHasStructKeyword()
2222
{
23-
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty)) as StructDeclarationSyntax;
23+
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax;
2424
Assert.Equal("struct", syntax!.Keyword.Text);
2525
}
2626

2727
[Fact]
2828
public void StructHasCorrectIdentifier()
2929
{
30-
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty)) as StructDeclarationSyntax;
30+
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax;
3131
Assert.Equal("Test", syntax!.Identifier.Text);
3232
}
3333

3434
[Fact]
3535
public void StructIsOnlyPublic()
3636
{
37-
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty)) as StructDeclarationSyntax;
37+
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)) as StructDeclarationSyntax;
3838
var @public = Assert.Single(syntax!.Modifiers);
3939
Assert.Equal("public", @public.Text);
4040
}
@@ -43,8 +43,9 @@ public void StructIsOnlyPublic()
4343
public void IntegrationEmptyStruct()
4444
{
4545
// Note that this test also covers trivia, which is not checked otherwise.
46-
Assert.Equal(@"public struct Test
46+
Assert.Equal(@"[StructLayout(LayoutKind.Explicit)]
47+
public struct Test
4748
{
48-
}", Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty)).ToFullString());
49+
}", Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)).ToFullString());
4950
}
5051
}

tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/FieldTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class FieldTests
1313
[Fact]
1414
public void FieldIsVisitedAsField()
1515
{
16-
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
16+
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
1717
var visitor = new Mock<SymbolVisitor>
1818
{
1919
CallBase = true
@@ -28,7 +28,7 @@ public void FieldIsVisitedAsField()
2828
[Fact]
2929
public void FieldIsVisitedAsMember()
3030
{
31-
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
31+
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
3232
var visitor = new Mock<SymbolVisitor>
3333
{
3434
CallBase = true
@@ -43,7 +43,7 @@ public void FieldIsVisitedAsMember()
4343
[Fact]
4444
public void FieldTypeIsVisited()
4545
{
46-
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
46+
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
4747
var visitor = new Mock<SymbolVisitor>
4848
{
4949
CallBase = true
@@ -58,7 +58,7 @@ public void FieldTypeIsVisited()
5858
[Fact]
5959
public void FieldIdentifierIsVisited()
6060
{
61-
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
61+
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
6262
var visitor = new Mock<SymbolVisitor>
6363
{
6464
CallBase = true

tests/Silk.NET.SilkTouch.Symbols.Tests/SymbolVisitorTests/StructTests.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class StructTests
1313
[Fact]
1414
public void StructSymbolIsVisitedAsType()
1515
{
16-
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
16+
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
1717
var visitor = new Mock<SymbolVisitor>
1818
{
1919
CallBase = true
@@ -28,7 +28,7 @@ public void StructSymbolIsVisitedAsType()
2828
[Fact]
2929
public void StructSymbolIsVisitedAsStruct()
3030
{
31-
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
31+
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
3232
var visitor = new Mock<SymbolVisitor>
3333
{
3434
CallBase = true
@@ -43,7 +43,7 @@ public void StructSymbolIsVisitedAsStruct()
4343
[Fact]
4444
public void StructIdentifierIsVisitedAsIdentifier()
4545
{
46-
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
46+
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
4747
var visitor = new Mock<SymbolVisitor>
4848
{
4949
CallBase = true
@@ -58,11 +58,11 @@ public void StructIdentifierIsVisitedAsIdentifier()
5858
[Fact]
5959
public void StructMemberIsVisited()
6060
{
61-
MemberSymbol member = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol("Test1"));
62-
var symbol = new StructSymbol(new IdentifierSymbol("Test"), (new[]
61+
MemberSymbol member = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test1"));
62+
var symbol = new StructSymbol(new IdentifierSymbol("Test"), new StructLayout((new[]
6363
{
64-
member
65-
}).ToImmutableArray());
64+
new LayoutEntry(member, 0)
65+
}).ToImmutableArray()));
6666
var visitor = new Mock<SymbolVisitor>
6767
{
6868
CallBase = true
@@ -77,13 +77,13 @@ public void StructMemberIsVisited()
7777
[Fact]
7878
public void StructMembersAreVisited()
7979
{
80-
MemberSymbol member1 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol("Test1"));
81-
MemberSymbol member2 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol("Test2"));
82-
var symbol = new StructSymbol(new IdentifierSymbol("Test"), (new[]
80+
MemberSymbol member1 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test1"));
81+
MemberSymbol member2 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), StructLayout.Empty), new IdentifierSymbol("Test2"));
82+
var symbol = new StructSymbol(new IdentifierSymbol("Test"), new StructLayout((new[]
8383
{
84-
member1,
85-
member2
86-
}).ToImmutableArray());
84+
new LayoutEntry(member1, 0),
85+
new LayoutEntry(member2, 4)
86+
}).ToImmutableArray()));
8787
var visitor = new Mock<SymbolVisitor>
8888
{
8989
CallBase = true

0 commit comments

Comments
 (0)