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

Struct Layouts #898

Merged
merged 4 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
{
Perksey marked this conversation as resolved.
Show resolved Hide resolved
/// <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);
HurricanKai marked this conversation as resolved.
Show resolved Hide resolved
Perksey marked this conversation as resolved.
Show resolved Hide resolved
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