diff --git a/Cesium.CodeGen.Tests/CodeGenTypeTests.cs b/Cesium.CodeGen.Tests/CodeGenTypeTests.cs index 39f53255..97e1587e 100644 --- a/Cesium.CodeGen.Tests/CodeGenTypeTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenTypeTests.cs @@ -82,4 +82,12 @@ public Task ArrayDeclaration() => DoTest(@"int main() int x[1]; return 0; }"); + + [Fact] + public Task StructFunctionMemberDeclaration() => DoTest(@"typedef struct { void bar(int unused); } foo; +int main(void) {}"); + + [Fact] + public Task BasicTypeDef() => DoTest(@"typedef int foo; +int main(void) { foo x; }"); } diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 64064e4e..388daf5f 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -22,8 +22,7 @@ internal void GenerateType(IGeneratedType type, string name) _types.Add(name, typeReference); } - // TODO[#72]: Unnecessary. - internal void AddType(TypeReference type, string name) => _types.Add(name, type); + internal void AddPlainType(IType type, string name) => _types.Add(name, type.Resolve(this)); internal TypeReference? GetTypeReference(IGeneratedType type) => _generatedTypes.GetValueOrDefault(type); internal TypeReference? GetTypeReference(string typeName) => _types.GetValueOrDefault(typeName); diff --git a/Cesium.CodeGen/Ir/BlockItems/DeclarationBlockItem.cs b/Cesium.CodeGen/Ir/BlockItems/DeclarationBlockItem.cs index 94b41918..1595d87f 100644 --- a/Cesium.CodeGen/Ir/BlockItems/DeclarationBlockItem.cs +++ b/Cesium.CodeGen/Ir/BlockItems/DeclarationBlockItem.cs @@ -63,17 +63,13 @@ private static void EmitScopedIdentifier(IDeclarationScope scope, ScopedIdentifi foreach (var (declaration, initializer) in declarations) { var method = scope.Method; - var (type, identifier, parametersInfo, cliImportMemberName) = declaration; + var (type, identifier, cliImportMemberName) = declaration; // TODO[#91]: A place to register whether {type} is const or not. if (identifier == null) throw new NotSupportedException("An anonymous local declaration isn't supported."); - if (parametersInfo != null) - throw new NotImplementedException( - $"A local declaration of function {identifier} isn't supported, yet."); - if (cliImportMemberName != null) throw new NotSupportedException( $"Local declaration with a CLI import member name {cliImportMemberName} isn't supported."); diff --git a/Cesium.CodeGen/Ir/Declarations/LocalDeclarationInfo.cs b/Cesium.CodeGen/Ir/Declarations/LocalDeclarationInfo.cs index 80fc149b..3da686e8 100644 --- a/Cesium.CodeGen/Ir/Declarations/LocalDeclarationInfo.cs +++ b/Cesium.CodeGen/Ir/Declarations/LocalDeclarationInfo.cs @@ -12,14 +12,13 @@ namespace Cesium.CodeGen.Ir.Declarations; internal record LocalDeclarationInfo( IType Type, string? Identifier, - ParametersInfo? Parameters, string? CliImportMemberName) { public static LocalDeclarationInfo Of(IReadOnlyList specifiers, Declarator? declarator) { var (type, cliImportMemberName) = ProcessSpecifiers(specifiers); if (declarator == null) - return new LocalDeclarationInfo(type, null, null, null); + return new LocalDeclarationInfo(type, null, null); var (pointer, directDeclarator) = declarator; if (pointer != null) @@ -32,7 +31,6 @@ public static LocalDeclarationInfo Of(IReadOnlyList speci } string? identifier = null; - ParametersInfo? parameters = null; var currentDirectDeclarator = directDeclarator; while (currentDirectDeclarator != null) @@ -46,6 +44,11 @@ public static LocalDeclarationInfo Of(IReadOnlyList speci throw new NotImplementedException( "Non-empty identifier list inside of a direct declarator is not supported, yet:" + $" {string.Join(", ", identifiers)}"); + + // An absent identifier list is `()` in a declaration like `int main()`. It means that there's an + // empty parameter list, actually. + type = ProcessFunctionParameters(type, null); + break; } @@ -57,11 +60,8 @@ public static LocalDeclarationInfo Of(IReadOnlyList speci break; case ParameterListDirectDeclarator parametersD: - if (parameters != null) - throw new NotSupportedException( - $"Second parameters list declarator for an entity already having one: {parametersD}."); - - parameters = ParametersInfo.Of(parametersD.Parameters); + var (_ /* base */, parameters) = parametersD; + type = ProcessFunctionParameters(type, parameters); break; case ArrayDirectDeclarator array: @@ -112,7 +112,7 @@ public static LocalDeclarationInfo Of(IReadOnlyList speci currentDirectDeclarator = currentDirectDeclarator.Base; } - return new LocalDeclarationInfo(type, identifier, parameters, cliImportMemberName); + return new LocalDeclarationInfo(type, identifier, cliImportMemberName); } private static (IType, string? cliImportMemberName) ProcessSpecifiers( @@ -281,4 +281,7 @@ private static IType ProcessSimpleTypeSpecifiers( $"Simple type specifiers are not supported: {string.Join(" ", typeNames)}"), }); } + + private static IType ProcessFunctionParameters(IType returnType, ParameterTypeList? parameters) => + new FunctionType(ParametersInfo.Of(parameters), returnType); } diff --git a/Cesium.CodeGen/Ir/ParametersInfo.cs b/Cesium.CodeGen/Ir/ParametersInfo.cs index 9607478f..1175366e 100644 --- a/Cesium.CodeGen/Ir/ParametersInfo.cs +++ b/Cesium.CodeGen/Ir/ParametersInfo.cs @@ -6,8 +6,9 @@ namespace Cesium.CodeGen.Ir; internal record ParametersInfo(IList Parameters, bool IsVoid, bool IsVarArg) { - public static ParametersInfo Of(ParameterTypeList parameters) + public static ParametersInfo? Of(ParameterTypeList? parameters) { + if (parameters == null) return null; var (parameterList, hasEllipsis) = parameters; bool isVoid; @@ -46,10 +47,7 @@ public static ParameterInfo Of(ParameterDeclaration declaration) throw new NotImplementedException( $"Parameter with abstract declarator is not supported, yet: {declaration}."); - var (type, identifier, parameters, cliImportMemberName) = LocalDeclarationInfo.Of(specifiers, declarator); - - if (parameters != null) - throw new NotImplementedException($"Parameters with parameters are not supported, yet: {parameters}."); + var (type, identifier, cliImportMemberName) = LocalDeclarationInfo.Of(specifiers, declarator); if (cliImportMemberName != null) throw new NotSupportedException("CLI import specifier isn't supported for a parameter."); diff --git a/Cesium.CodeGen/Ir/TopLevel/FunctionDefinition.cs b/Cesium.CodeGen/Ir/TopLevel/FunctionDefinition.cs index 9ceb4894..8086f2db 100644 --- a/Cesium.CodeGen/Ir/TopLevel/FunctionDefinition.cs +++ b/Cesium.CodeGen/Ir/TopLevel/FunctionDefinition.cs @@ -16,19 +16,18 @@ internal class FunctionDefinition : ITopLevelNode { private const string MainFunctionName = "main"; - private readonly IType _returnType; + private readonly FunctionType _functionType; private readonly string _name; - private readonly ParametersInfo? _parameters; private readonly CompoundStatement _statement; private bool IsMain => _name == MainFunctionName; - public FunctionDefinition(Ast.FunctionDefinition function) { var (specifiers, declarator, declarations, astStatement) = function; - (_returnType, var name, _parameters, var cliImportMemberName) = - LocalDeclarationInfo.Of(specifiers, declarator); + var (type, name, cliImportMemberName) = LocalDeclarationInfo.Of(specifiers, declarator); + _functionType = type as FunctionType + ?? throw new NotSupportedException($"Function of not a function type: {type}."); _name = name ?? throw new NotSupportedException($"Function without name: {function}."); if (declarations?.IsEmpty == false) @@ -42,21 +41,22 @@ public FunctionDefinition(Ast.FunctionDefinition function) public void EmitTo(TranslationUnitContext context) { - var returnType = _returnType.Resolve(context); - if (IsMain && returnType != context.TypeSystem.Int32) + var (parameters, returnType) = _functionType; + var resolvedReturnType = returnType.Resolve(context); + if (IsMain && resolvedReturnType != context.TypeSystem.Int32) throw new NotSupportedException( $"Invalid return type for the {_name} function: " + - $"int expected, got {_returnType}."); + $"int expected, got {returnType}."); - if (IsMain && _parameters?.IsVarArg == true) + if (IsMain && parameters?.IsVarArg == true) throw new NotSupportedException($"Variable arguments for the {_name} function aren't supported."); var declaration = context.Functions.GetValueOrDefault(_name); - declaration?.VerifySignatureEquality(_name, _parameters, _returnType); + declaration?.VerifySignatureEquality(_name, parameters, returnType); var method = declaration switch { - null => context.ModuleType.DefineMethod(context, _name, returnType, _parameters), + null => context.ModuleType.DefineMethod(context, _name, resolvedReturnType, parameters), { MethodReference: MethodDefinition md } => md, _ => throw new NotSupportedException($"Function {_name} already defined as immutable.") }; @@ -65,7 +65,7 @@ public void EmitTo(TranslationUnitContext context) throw new NotSupportedException($"Double definition of function {_name}."); if (declaration == null) - context.Functions.Add(_name, new FunctionInfo(_parameters, _returnType, method, IsDefined: true)); + context.Functions.Add(_name, new FunctionInfo(parameters, returnType, method, IsDefined: true)); else context.Functions[_name] = declaration with { IsDefined = true }; @@ -91,10 +91,10 @@ public void EmitTo(TranslationUnitContext context) /// Whether the synthetic entry point should be generated. private bool ValidateMainParameters() { - if (_parameters == null) + if (_functionType.Parameters == null) return false; // TODO[#87]: Decide whether this is normal or not. - var (parameterList, isVoid, isVarArg) = _parameters; + var (parameterList, isVoid, isVarArg) = _functionType.Parameters; if (isVoid) return false; // supported, no synthetic entry point required if (isVarArg) @@ -264,7 +264,7 @@ private void EmitCode(TranslationUnitContext context, FunctionScope scope) instructions.Add(Instruction.Create(OpCodes.Ldc_I4_0)); instructions.Add(Instruction.Create(OpCodes.Ret)); } - else if (_returnType.Resolve(context) == context.TypeSystem.Void) + else if (_functionType.ReturnType.Resolve(context) == context.TypeSystem.Void) { var instructions = scope.Method.Body.Instructions; instructions.Add(Instruction.Create(OpCodes.Ret)); diff --git a/Cesium.CodeGen/Ir/TopLevel/TopLevelDeclaration.cs b/Cesium.CodeGen/Ir/TopLevel/TopLevelDeclaration.cs index c11a87dc..8288ee88 100644 --- a/Cesium.CodeGen/Ir/TopLevel/TopLevelDeclaration.cs +++ b/Cesium.CodeGen/Ir/TopLevel/TopLevelDeclaration.cs @@ -3,7 +3,6 @@ using Cesium.CodeGen.Extensions; using Cesium.CodeGen.Ir.Declarations; using Cesium.CodeGen.Ir.Types; -using Mono.Cecil; namespace Cesium.CodeGen.Ir.TopLevel; @@ -39,7 +38,7 @@ private static void EmitScopedIdentifier( foreach (var (declaration, initializer) in items) { - var (type, identifier, parametersInfo, cliImportMemberName) = declaration; + var (type, identifier, cliImportMemberName) = declaration; if (identifier == null) throw new NotSupportedException($"Unnamed global symbol of type {type} is not supported."); @@ -49,17 +48,20 @@ private static void EmitScopedIdentifier( throw new NotSupportedException( $"Initializer expression for a CLI import isn't supported: {initializer}."); - EmitCliImportDeclaration(context, identifier, parametersInfo, type, cliImportMemberName); + if (type is not FunctionType cliFunction) + throw new NotSupportedException($"CLI initializer should be a function for identifier {identifier}."); + + EmitCliImportDeclaration(context, identifier, cliFunction, cliImportMemberName); continue; } - if (parametersInfo != null) + if (type is FunctionType functionType) { if (initializer != null) throw new NotSupportedException( $"Initializer expression for a function declaration isn't supported: {initializer}."); - EmitFunctionDeclaration(context, identifier, parametersInfo, type); + EmitFunctionDeclaration(context, identifier, functionType); continue; } @@ -78,23 +80,23 @@ private static void EmitScopedIdentifier( private static void EmitCliImportDeclaration( TranslationUnitContext context, string name, - ParametersInfo? parametersInfo, - IType returnType, + FunctionType functionType, string memberName) { var method = context.MethodLookup(memberName); if (method == null) throw new NotSupportedException($"Cannot find CLI-imported member {memberName}."); - // TODO[#93]: Verify method signature: {parametersIInfo, type}. + var (parametersInfo, returnType) = functionType; + // TODO[#93]: Verify method signature: {parametersInfo, type}. context.Functions.Add(name, new FunctionInfo(parametersInfo, returnType, method, IsDefined: true)); } private static void EmitFunctionDeclaration( TranslationUnitContext context, string identifier, - ParametersInfo parametersInfo, - IType returnType) + FunctionType functionType) { + var (parametersInfo, returnType) = functionType; var existingFunction = context.Functions.GetValueOrDefault(identifier); if (existingFunction != null) { @@ -118,34 +120,17 @@ private static void EmitTypeDef(TranslationUnitContext context, TypeDefDeclarati declaration.Deconstruct(out var types); foreach (var typeDef in types) { - var (type, identifier, parametersInfo, cliImportMemberName) = typeDef; + var (type, identifier, cliImportMemberName) = typeDef; if (identifier == null) throw new NotSupportedException($"Anonymous typedef not supported: {type}."); - if (parametersInfo != null) - GenerateFunctionType(context, identifier, parametersInfo, type); - if (cliImportMemberName != null) throw new NotSupportedException($"typedef for CLI import not supported: {cliImportMemberName}."); if (type is IGeneratedType t) context.GenerateType(t, identifier); else - throw new NotSupportedException($"Not supported type generation for type {type}."); + context.AddPlainType(type, identifier); } } - - private static void GenerateFunctionType( - TranslationUnitContext context, - string identifier, - ParametersInfo parametersInfo, - IType returnType) - { - var type = new FunctionPointerType(); - - throw new NotImplementedException( - $"Function type not supported, yet: {identifier}: {parametersInfo} → {returnType}"); - - context.AddType(type, identifier); - } } diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs new file mode 100644 index 00000000..b9bc3b37 --- /dev/null +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -0,0 +1,12 @@ +using Cesium.CodeGen.Contexts; +using Mono.Cecil; + +namespace Cesium.CodeGen.Ir.Types; + +internal record FunctionType(ParametersInfo? Parameters, IType ReturnType) : IType +{ + public TypeReference Resolve(TranslationUnitContext context) => + throw new NotImplementedException(); + + public int SizeInBytes => throw new NotImplementedException("Could not calculate size yet."); +} diff --git a/Cesium.CodeGen/Ir/Types/IGeneratedType.cs b/Cesium.CodeGen/Ir/Types/IGeneratedType.cs deleted file mode 100644 index 45f2d312..00000000 --- a/Cesium.CodeGen/Ir/Types/IGeneratedType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Cesium.CodeGen.Contexts; -using Mono.Cecil; - -namespace Cesium.CodeGen.Ir.Types; - -internal interface IGeneratedType -{ - TypeDefinition Emit(string name, TranslationUnitContext context); -} diff --git a/Cesium.CodeGen/Ir/Types/IType.cs b/Cesium.CodeGen/Ir/Types/IType.cs index 6b6fa0d1..dea470a6 100644 --- a/Cesium.CodeGen/Ir/Types/IType.cs +++ b/Cesium.CodeGen/Ir/Types/IType.cs @@ -3,8 +3,19 @@ namespace Cesium.CodeGen.Ir.Types; +/// An interface representing a C type. +/// +/// Can be of two flavors: an or a plain type that doesn't require any byte code to be +/// generated (a basic type, a pointer or a function pointer. +/// internal interface IType { TypeReference Resolve(TranslationUnitContext context); int SizeInBytes { get; } } + +/// A generated type, i.e. a type that has some bytecode to be generated once. +internal interface IGeneratedType : IType +{ + TypeDefinition Emit(string name, TranslationUnitContext context); +} diff --git a/Cesium.CodeGen/Ir/Types/StructType.cs b/Cesium.CodeGen/Ir/Types/StructType.cs index 9a6e661e..08a8e01b 100644 --- a/Cesium.CodeGen/Ir/Types/StructType.cs +++ b/Cesium.CodeGen/Ir/Types/StructType.cs @@ -23,15 +23,11 @@ public TypeDefinition Emit(string name, TranslationUnitContext context) foreach (var member in _members) { - var (type, identifier, parametersInfo, cliImportMemberName) = member; + var (type, identifier, cliImportMemberName) = member; if (identifier == null) throw new NotImplementedException( $"Anonymous struct members for {name} aren't supported, yet: {type}."); - if (parametersInfo != null) - throw new NotImplementedException( - $"Functional struct members for {name} aren't supported, yet: {identifier}."); - if (cliImportMemberName != null) throw new NotSupportedException( $"CLI imports inside struct members aren't supported: {cliImportMemberName}.");