Skip to content

Commit

Permalink
Struct Layouts (#898)
Browse files Browse the repository at this point in the history
* Restructure Struct to allow layouting

* Add failing layout tests

* Apply Layouting attributes

* Accept StructLayout attribute on empty structs
  • Loading branch information
HurricanKai committed Apr 20, 2022
1 parent dac966d commit a280613
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 38 deletions.
61 changes: 56 additions & 5 deletions src/generators/Silk.NET.SilkTouch.Emitter/CSharpEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,39 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol)
throw new InvalidOperationException("Field Identifier was not visited correctly");
ClearState();

var memberList = new List<MemberDeclarationSyntax>(structSymbol.Members.Length);
foreach (var member in structSymbol.Members)
var memberList = new List<MemberDeclarationSyntax>(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);
}

Expand All @@ -85,8 +110,34 @@ protected override StructSymbol VisitStruct(StructSymbol structSymbol)
var modifiers = TokenList(Token(SyntaxTriviaList.Empty, SyntaxKind.PublicKeyword, TriviaList(Space)));
_syntax = StructDeclaration
(
List<AttributeListSyntax>(), modifiers, identifierToken, null, null,
List<TypeParameterConstraintClauseSyntax>(), 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<TypeParameterConstraintClauseSyntax>(), members
)
.WithKeyword(Token(SyntaxTriviaList.Empty, SyntaxKind.StructKeyword, TriviaList(Space)))
.WithOpenBraceToken(Token(TriviaList(LineFeed), SyntaxKind.OpenBraceToken, SyntaxTriviaList.Empty))
Expand Down
24 changes: 22 additions & 2 deletions src/generators/Silk.NET.SilkTouch.Symbols/StructSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,31 @@ namespace Silk.NET.SilkTouch.Symbols;
/// A <see cref="TypeSymbol"/> representing a <c>struct</c>.
/// </summary>
/// <param name="Identifier">The Identifier of this struct</param>
/// <param name="Members">The Members of this struct</param>
/// <param name="Layout">The layout of this struct</param>
/// <remarks>
/// In this context, a Struct means a type that represents the layout of a continuous block of memory.
/// </remarks>
// /// Each meaningful place in this memory called a field (see <see cref="FieldSymbol"/>) is accessible via this type.
// /// Fields are allowed to overlap.
// /// Additionally it may contain one or multiple <see cref="MethodSymbol"/> that are called with an instance of this type as their first argument.
public sealed record StructSymbol(IdentifierSymbol Identifier, ImmutableArray<MemberSymbol> Members) : TypeSymbol(Identifier);
public sealed record StructSymbol(IdentifierSymbol Identifier, StructLayout Layout) : TypeSymbol(Identifier);

/// <summary>
/// A <see cref="StructSymbol"/> representing the layout of a <see cref="StructSymbol"/>
/// </summary>
/// <param name="Entries">The entries of this layout</param>
public sealed record StructLayout(ImmutableArray<LayoutEntry> Entries)
{
/// <summary>
/// An empty layout with no members
/// </summary>
public static readonly StructLayout Empty = new StructLayout(ImmutableArray<LayoutEntry>.Empty);
}

/// <summary>
/// Represents an entry in a <see cref="StructLayout"/>
/// </summary>
/// <param name="Member">The member symbol associated with this layout entry</param>
/// <param name="ByteOffset">The offset of this entry in bytes</param>
/// <seealso cref="StructLayout"/>
public sealed record LayoutEntry(MemberSymbol Member, int ByteOffset);
11 changes: 10 additions & 1 deletion src/generators/Silk.NET.SilkTouch.Symbols/SymbolVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,16 @@ protected virtual TypeSymbol VisitType(TypeSymbol typeSymbol)
/// </remarks>
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()
)
);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class EmitterFieldIntegrationTests : EmitterTest
[Fact]
public void StructHasStructKeyword()
{
var syntax = Transform(new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MemberSymbol>.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()
);
}
Expand Down
13 changes: 7 additions & 6 deletions tests/Silk.NET.SilkTouch.Emitter.Tests/EmitterStructTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@ public sealed class EmitterStructTests : EmitterTest
[Fact]
public void StructIsStructSyntax()
{
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.Empty));
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty));
Assert.IsType<StructDeclarationSyntax>(syntax);
}

[Fact]
public void StructHasStructKeyword()
{
var syntax = Transform(new StructSymbol(new IdentifierSymbol("Test"), ImmutableArray<MemberSymbol>.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<MemberSymbol>.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<MemberSymbol>.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);
}
Expand All @@ -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<MemberSymbol>.Empty)).ToFullString());
}", Transform(new StructSymbol(new IdentifierSymbol("Test"), StructLayout.Empty)).ToFullString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class FieldTests
[Fact]
public void FieldIsVisitedAsField()
{
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -28,7 +28,7 @@ public void FieldIsVisitedAsField()
[Fact]
public void FieldIsVisitedAsMember()
{
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -43,7 +43,7 @@ public void FieldIsVisitedAsMember()
[Fact]
public void FieldTypeIsVisited()
{
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -58,7 +58,7 @@ public void FieldTypeIsVisited()
[Fact]
public void FieldIdentifierIsVisited()
{
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol(""));
var symbol = new FieldSymbol(new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty), new IdentifierSymbol(""));
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class StructTests
[Fact]
public void StructSymbolIsVisitedAsType()
{
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -28,7 +28,7 @@ public void StructSymbolIsVisitedAsType()
[Fact]
public void StructSymbolIsVisitedAsStruct()
{
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -43,7 +43,7 @@ public void StructSymbolIsVisitedAsStruct()
[Fact]
public void StructIdentifierIsVisitedAsIdentifier()
{
var symbol = new StructSymbol(new IdentifierSymbol(""), ImmutableArray<MemberSymbol>.Empty);
var symbol = new StructSymbol(new IdentifierSymbol(""), StructLayout.Empty);
var visitor = new Mock<SymbolVisitor>
{
CallBase = true
Expand All @@ -58,11 +58,11 @@ public void StructIdentifierIsVisitedAsIdentifier()
[Fact]
public void StructMemberIsVisited()
{
MemberSymbol member = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.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<SymbolVisitor>
{
CallBase = true
Expand All @@ -77,13 +77,13 @@ public void StructMemberIsVisited()
[Fact]
public void StructMembersAreVisited()
{
MemberSymbol member1 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.Empty), new IdentifierSymbol("Test1"));
MemberSymbol member2 = new FieldSymbol(new StructSymbol(new IdentifierSymbol("int"), ImmutableArray<MemberSymbol>.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<SymbolVisitor>
{
CallBase = true
Expand Down

0 comments on commit a280613

Please sign in to comment.