From 9c1cdf19e646c13f775cae97d8be401d7dad2a65 Mon Sep 17 00:00:00 2001 From: nub31 Date: Tue, 16 Sep 2025 18:22:20 +0200 Subject: [PATCH] ... --- .../NubLang/Generation/QBE/QBEGenerator.cs | 519 ++++++++---------- .../TypeChecking/Node/ExpressionNode.cs | 2 +- compiler/NubLang/TypeChecking/Node/Node.cs | 4 +- .../TypeChecking/Node/StatementNode.cs | 2 +- .../NubLang/TypeChecking/Node/TypeNode.cs | 192 +++---- compiler/NubLang/TypeChecking/TypeChecker.cs | 36 +- example/src/main.nub | 16 + 7 files changed, 349 insertions(+), 422 deletions(-) diff --git a/compiler/NubLang/Generation/QBE/QBEGenerator.cs b/compiler/NubLang/Generation/QBE/QBEGenerator.cs index d7c19ae..7d1cc0a 100644 --- a/compiler/NubLang/Generation/QBE/QBEGenerator.cs +++ b/compiler/NubLang/Generation/QBE/QBEGenerator.cs @@ -8,6 +8,8 @@ namespace NubLang.Generation.QBE; public class QBEGenerator { + private const int PTR_SIZE = 8; + private readonly QBEWriter _writer; private readonly List _definitions; private readonly HashSet _structTypes; @@ -20,7 +22,6 @@ public class QBEGenerator private int _labelIndex; private int _cStringLiteralIndex; private int _stringLiteralIndex; - private bool _codeIsReachable = true; public QBEGenerator(List definitions, HashSet structTypes) { @@ -39,7 +40,6 @@ public class QBEGenerator _labelIndex = 0; _cStringLiteralIndex = 0; _stringLiteralIndex = 0; - _codeIsReachable = true; _writer.Comment("========== Builtin functions =========="); @@ -142,77 +142,125 @@ public class QBEGenerator private static string QBEAssign(TypeNode type) { - if (type.IsSimpleType(out var simpleType, out _)) + return type switch { - return simpleType.StorageSize switch - { - StorageSize.I8 or StorageSize.U8 or StorageSize.I16 or StorageSize.U16 or StorageSize.I32 or StorageSize.U32 => "=w", - StorageSize.I64 or StorageSize.U64 => "=l", - StorageSize.F32 => "=s", - StorageSize.F64 => "=d", - _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) - }; - } - - return "=l"; + IntTypeNode { Width: <= 32 } => "=w", + IntTypeNode { Width: 64 } => "=l", + FloatTypeNode { Width: 32 } => "=s", + FloatTypeNode { Width: 64 } => "=d", + BoolTypeNode => "=w", + PointerTypeNode => "=l", + FuncTypeNode => "=l", + CStringTypeNode => "=l", + StringTypeNode => "=l", + ArrayTypeNode => "=l", + StructTypeNode => throw new InvalidOperationException("Structs are not loaded/stored directly"), + VoidTypeNode => throw new InvalidOperationException("Void has no assignment"), + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}") + }; } private void EmitStore(TypeNode type, string value, string destination) { - string store; - - if (type.IsSimpleType(out var simpleType, out _)) + var store = type switch { - store = simpleType.StorageSize switch - { - StorageSize.I8 or StorageSize.U8 => "storeb", - StorageSize.I16 or StorageSize.U16 => "storeh", - StorageSize.I32 or StorageSize.U32 => "storew", - StorageSize.I64 or StorageSize.U64 => "storel", - StorageSize.F32 => "stores", - StorageSize.F64 => "stored", - _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) - }; - } - else - { - store = "storel"; - } + BoolTypeNode => "storeb", + IntTypeNode { Width: 8 } => "storeb", + IntTypeNode { Width: 16 } => "storeh", + IntTypeNode { Width: 32 } => "storew", + IntTypeNode { Width: 64 } => "storel", + FloatTypeNode { Width: 32 } => "stores", + FloatTypeNode { Width: 64 } => "stored", + PointerTypeNode => "storel", + FuncTypeNode => "storel", + CStringTypeNode => "storel", + StringTypeNode => "storel", + ArrayTypeNode => "storel", + StructTypeNode => throw new InvalidOperationException("Struct stores must use blit/memcpy"), + VoidTypeNode => throw new InvalidOperationException("Cannot store void"), + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}") + }; _writer.Indented($"{store} {value}, {destination}"); } private string EmitLoad(TypeNode type, string from) { - string load; - - if (type.IsSimpleType(out var simpleType, out _)) + string load = type switch { - load = simpleType.StorageSize switch - { - StorageSize.I64 or StorageSize.U64 => "loadl", - StorageSize.I32 or StorageSize.U32 => "loadw", - StorageSize.I16 => "loadsh", - StorageSize.I8 => "loadsb", - StorageSize.U16 => "loaduh", - StorageSize.U8 => "loadub", - StorageSize.F64 => "loadd", - StorageSize.F32 => "loads", - _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) - }; - } - else - { - load = "loadl"; - } + BoolTypeNode => "loadub", + IntTypeNode { Signed: true, Width: 8 } => "loadsb", + IntTypeNode { Signed: true, Width: 16 } => "loadsh", + IntTypeNode { Signed: true, Width: 32 } => "loadw", + IntTypeNode { Signed: true, Width: 64 } => "loadl", + IntTypeNode { Signed: false, Width: 8 } => "loadsb", + IntTypeNode { Signed: false, Width: 16 } => "loadsh", + IntTypeNode { Signed: false, Width: 32 } => "loadw", + IntTypeNode { Signed: false, Width: 64 } => "loadl", + FloatTypeNode { Width: 32 } => "loads", + FloatTypeNode { Width: 64 } => "loadd", + PointerTypeNode => "loadl", + FuncTypeNode => "loadl", + CStringTypeNode => "loadl", + StringTypeNode => "loadl", + ArrayTypeNode => "loadl", + StructTypeNode => throw new InvalidOperationException("Struct loads must use blit/memcpy"), + VoidTypeNode => throw new InvalidOperationException("Cannot load void"), + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type {type.GetType()}") + }; var into = TmpName(); - _writer.Indented($"{into} {QBEAssign(type)} {load} {from}"); - return into; } + + private static string StructDefQBEType(TypeNode type) + { + return type switch + { + BoolTypeNode => "b", + IntTypeNode { Width: 8 } => "b", + IntTypeNode { Width: 16 } => "h", + IntTypeNode { Width: 32 } => "w", + IntTypeNode { Width: 64 } => "l", + FloatTypeNode { Width: 32 } => "s", + FloatTypeNode { Width: 64 } => "d", + PointerTypeNode => "l", + FuncTypeNode => "l", + CStringTypeNode => "l", + StringTypeNode => "l", + ArrayTypeNode => "l", + StructTypeNode st => StructTypeName(st.Module, st.Name), + VoidTypeNode => throw new InvalidOperationException("Void has no QBE type"), + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}") + }; + } + + private string FuncQBETypeName(TypeNode type) + { + return type switch + { + BoolTypeNode => "ub", + IntTypeNode { Signed: true, Width: 8 } => "sb", + IntTypeNode { Signed: true, Width: 16 } => "sh", + IntTypeNode { Signed: false, Width: 8 } => "ub", + IntTypeNode { Signed: false, Width: 16 } => "uh", + IntTypeNode { Width: 32 } => "w", + IntTypeNode { Width: 64 } => "l", + FloatTypeNode { Width: 32 } => "s", + FloatTypeNode { Width: 64 } => "d", + PointerTypeNode => "l", + FuncTypeNode => "l", + CStringTypeNode => "l", + StringTypeNode => "l", + ArrayTypeNode => "l", + StructTypeNode st => StructTypeName(st.Module, st.Name), + VoidTypeNode => throw new InvalidOperationException("Void has no QBE type"), + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}") + }; + } + private void EmitMemset(string destination, int value, string length) { _writer.Indented($"call $.memset(l {destination}, l {value}, l {length})"); @@ -250,109 +298,100 @@ public class QBEGenerator private void EmitCopyInto(ExpressionNode source, string destination) { - // Simple types are passed in registers and can therefore just be stored - if (source.Type.IsSimpleType(out var simpleType, out var complexType)) + if (source.Type.IsScalar) { - var value = EmitExpression(source); - EmitStore(simpleType, value, destination); + EmitStore(source.Type, EmitExpression(source), destination); return; } - // Structs has known sizes at compile time - if (complexType is StructTypeNode) + if (source.Type.IsValueType) { var value = EmitExpression(source); - _writer.Indented($"blit {value}, {destination}, {SizeOf(complexType)}"); + _writer.Indented($"blit {value}, {destination}, {SizeOf(source.Type)}"); + return; } - // The rest of the complex types has unknown sizes - else - { - var value = EmitExpression(source); - var size = complexType switch - { - ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value), - CStringTypeNode => EmitCStringSizeInBytes(value), - StringTypeNode => EmitStringSizeInBytes(value), - _ => throw new ArgumentOutOfRangeException(nameof(source.Type)) - }; - var buffer = TmpName(); - _writer.Indented($"{buffer} =l alloc8 {size}"); - EmitMemcpy(value, buffer, size); - EmitStore(complexType, buffer, destination); + switch (source.Type) + { + case ArrayTypeNode arrayType: + { + var value = EmitExpression(source); + var size = EmitArraySizeInBytes(arrayType, value); + var buffer = TmpName(); + _writer.Indented($"{buffer} =l alloc8 {size}"); + EmitMemcpy(value, buffer, size); + EmitStore(arrayType, buffer, destination); + return; + } + case CStringTypeNode: + { + var value = EmitExpression(source); + var size = EmitCStringSizeInBytes(value); + var buffer = TmpName(); + _writer.Indented($"{buffer} =l alloc8 {size}"); + EmitMemcpy(value, buffer, size); + EmitStore(source.Type, buffer, destination); + return; + } + case StringTypeNode: + { + var value = EmitExpression(source); + var size = EmitStringSizeInBytes(value); + var buffer = TmpName(); + _writer.Indented($"{buffer} =l alloc8 {size}"); + EmitMemcpy(value, buffer, size); + EmitStore(source.Type, buffer, destination); + return; + } + case VoidTypeNode: + throw new InvalidOperationException("Cannot copy void"); + default: + throw new ArgumentOutOfRangeException(nameof(source.Type), $"Unknown type {source.Type}"); } } private string EmitCopy(ExpressionNode source) { - // Allowlist for types which are safe to not copy - if (source is ArrayInitializerNode or StructInitializerNode or LiteralNode) + if (source is RValueExpressionNode || source.Type.IsScalar) { return EmitExpression(source); } - // Simple types are passed in registers and therefore always copied - if (source.Type.IsSimpleType(out _, out var complexType)) - { - return EmitExpression(source); - } - - // For the rest, we figure out the size of the type and shallow copy them var value = EmitExpression(source); var destination = TmpName(); - // Structs has known sizes at compile time - if (complexType is StructTypeNode) + if (source.Type.IsValueType) { - var size = SizeOf(complexType); + var size = SizeOf(source.Type); _writer.Indented($"{destination} =l alloc8 {size}"); _writer.Indented($"blit {value}, {destination}, {size}"); + return destination; } - // The rest of the complex types has unknown sizes - else - { - var size = complexType switch - { - ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value), - CStringTypeNode => EmitCStringSizeInBytes(value), - StringTypeNode => EmitStringSizeInBytes(value), - _ => throw new ArgumentOutOfRangeException(nameof(source.Type)) - }; - _writer.Indented($"{destination} =l alloc8 {size}"); - EmitMemcpy(value, destination, size); + switch (source.Type) + { + case ArrayTypeNode arrayType: + var arraySize = EmitArraySizeInBytes(arrayType, value); + _writer.Indented($"{destination} =l alloc8 {arraySize}"); + EmitMemcpy(value, destination, arraySize); + break; + case CStringTypeNode: + var cstrSize = EmitCStringSizeInBytes(value); + _writer.Indented($"{destination} =l alloc8 {cstrSize}"); + EmitMemcpy(value, destination, cstrSize); + break; + case StringTypeNode: + var strSize = EmitStringSizeInBytes(value); + _writer.Indented($"{destination} =l alloc8 {strSize}"); + EmitMemcpy(value, destination, strSize); + break; + default: + throw new InvalidOperationException($"Cannot copy type {source.Type}"); } return destination; } - // Utility to create QBE type names for function parameters and return types - private string FuncQBETypeName(TypeNode type) - { - if (type.IsSimpleType(out var simpleType, out var complexType)) - { - return simpleType.StorageSize switch - { - StorageSize.I64 or StorageSize.U64 => "l", - StorageSize.I32 or StorageSize.U32 => "w", - StorageSize.I16 => "sh", - StorageSize.I8 => "sb", - StorageSize.U16 => "uh", - StorageSize.U8 => "ub", - StorageSize.F64 => "d", - StorageSize.F32 => "s", - _ => throw new ArgumentOutOfRangeException() - }; - } - - if (complexType is StructTypeNode structType) - { - return StructTypeName(structType.Module, structType.Name); - } - - return "l"; - } - private void EmitStructType(StructTypeNode structType) { // todo(nub31): qbe expects structs to be declared in order. We must Check the dependencies of the struct to see if a type need to be declared before this one @@ -365,31 +404,6 @@ public class QBEGenerator } _writer.WriteLine(" }"); - return; - - string StructDefQBEType(TypeNode type) - { - if (type.IsSimpleType(out var simpleType, out var complexType)) - { - return simpleType.StorageSize switch - { - StorageSize.I64 or StorageSize.U64 => "l", - StorageSize.I32 or StorageSize.U32 => "w", - StorageSize.I16 or StorageSize.U16 => "h", - StorageSize.I8 or StorageSize.U8 => "b", - StorageSize.F64 => "d", - StorageSize.F32 => "s", - _ => throw new ArgumentOutOfRangeException() - }; - } - - if (complexType is StructTypeNode childStructType) - { - return StructTypeName(childStructType.Module, childStructType.Name); - } - - return "l"; - } } private void EmitStructDefinition(StructNode structDef) @@ -477,12 +491,6 @@ public class QBEGenerator EmitBlock(funcDef.Body); - // Implicit return for void functions if no explicit return has been set - if (funcDef.Signature.ReturnType is VoidTypeNode && funcDef.Body.Statements.LastOrDefault() is not ReturnNode) - { - _writer.Indented("ret"); - } - _writer.WriteLine("}"); _writer.NewLine(); } @@ -491,13 +499,8 @@ public class QBEGenerator { foreach (var statement in block.Statements) { - if (_codeIsReachable) - { - EmitStatement(statement); - } + EmitStatement(statement); } - - _codeIsReachable = true; } private void EmitStatement(StatementNode statement) @@ -505,13 +508,13 @@ public class QBEGenerator switch (statement) { case AssignmentNode assignment: - EmitAssignment(assignment); + EmitCopyInto(assignment.Value, EmitAddressOf(assignment.Target)); break; case BreakNode: - EmitBreak(); + _writer.Indented($"jmp {_breakLabels.Peek()}"); break; case ContinueNode: - EmitContinue(); + _writer.Indented($"jmp {_continueLabels.Peek()}"); break; case IfNode ifStatement: EmitIf(ifStatement); @@ -536,23 +539,6 @@ public class QBEGenerator } } - private void EmitAssignment(AssignmentNode assignment) - { - EmitCopyInto(assignment.Value, EmitAddressOfLValue(assignment.Target)); - } - - private void EmitBreak() - { - _writer.Indented($"jmp {_breakLabels.Peek()}"); - _codeIsReachable = false; - } - - private void EmitContinue() - { - _writer.Indented($"jmp {_continueLabels.Peek()}"); - _codeIsReachable = false; - } - private void EmitIf(IfNode ifStatement) { var trueLabel = LabelName(); @@ -666,60 +652,47 @@ public class QBEGenerator _writer.WriteLine(endLabel); } - private string EmitExpression(ExpressionNode expression) + private string EmitExpression(ExpressionNode expr) { - return expression switch + return expr switch { - ArrayInitializerNode arrayInitializer => EmitArrayInitializer(arrayInitializer), - StructInitializerNode structInitializer => EmitStructInitializer(structInitializer), - AddressOfNode addressOf => EmitAddressOf(addressOf), - DereferenceNode dereference => EmitDereference(dereference), - BinaryExpressionNode binary => EmitBinaryExpression(binary), - FuncCallNode funcCall => EmitFuncCall(funcCall), - ConvertIntNode convertInt => EmitConvertInt(convertInt), - ConvertFloatNode convertFloat => EmitConvertFloat(convertFloat), - VariableIdentifierNode identifier => EmitVariableIdentifier(identifier), - FuncIdentifierNode funcIdentifier => EmitFuncIdentifier(funcIdentifier), - FuncParameterIdentifierNode funcParameterIdentifier => EmitParameterFuncIdentifier(funcParameterIdentifier), - LiteralNode literal => EmitLiteral(literal), - UnaryExpressionNode unaryExpression => EmitUnaryExpression(unaryExpression), - StructFieldAccessNode structFieldAccess => EmitStructFieldAccess(structFieldAccess), - StructFuncCallNode structFuncCall => EmitStructFuncCall(structFuncCall), - ArrayIndexAccessNode arrayIndex => EmitArrayIndexAccess(arrayIndex), - _ => throw new ArgumentOutOfRangeException(nameof(expression)) + RValueExpressionNode rValue => EmitRValue(rValue), + LValueExpressionNode lValue => EmitLValue(lValue), + _ => throw new ArgumentOutOfRangeException(nameof(expr)) }; } - private string EmitFuncIdentifier(FuncIdentifierNode funcIdent) + private string EmitRValue(RValueExpressionNode rValue) { - return FuncName(funcIdent.Module, funcIdent.Name, funcIdent.ExternSymbol); + return rValue switch + { + AddressOfNode expr => EmitAddressOf(expr.LValue), + ArrayInitializerNode expr => EmitArrayInitializer(expr), + BinaryExpressionNode expr => EmitBinaryExpression(expr), + ConvertFloatNode expr => EmitConvertFloat(expr), + ConvertIntNode expr => EmitConvertInt(expr), + DereferenceNode expr => EmitLoad(expr.Type, EmitExpression(expr.Expression)), + FuncCallNode expr => EmitFuncCall(expr), + FuncIdentifierNode expr => FuncName(expr.Module, expr.Name, expr.ExternSymbol), + FuncParameterIdentifierNode expr => $"%{expr.Name}", + LiteralNode expr => EmitLiteral(expr), + StructFuncCallNode expr => EmitStructFuncCall(expr), + StructInitializerNode expr => EmitStructInitializer(expr), + UnaryExpressionNode expr => EmitUnaryExpression(expr), + _ => throw new ArgumentOutOfRangeException(nameof(rValue)) + }; } - private string EmitVariableIdentifier(VariableIdentifierNode variableIdent) + private string EmitLValue(LValueExpressionNode lValue) { - var address = EmitAddressOfVariableIdent(variableIdent); - if (variableIdent.Type is StructTypeNode) + var address = EmitAddressOf(lValue); + + if (lValue.Type is { IsValueType: true, IsScalar: false }) { return address; } - return EmitLoad(variableIdent.Type, address); - } - - private string EmitParameterFuncIdentifier(FuncParameterIdentifierNode funcParameterIdent) - { - return "%" + funcParameterIdent.Name; - } - - private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess) - { - var address = EmitAddressOfArrayIndexAccess(arrayIndexAccess); - if (arrayIndexAccess.Type is StructTypeNode) - { - return address; - } - - return EmitLoad(arrayIndexAccess.Type, address); + return EmitLoad(lValue.Type, address); } private string EmitArrayInitializer(ArrayInitializerNode arrayInitializer) @@ -743,30 +716,14 @@ public class QBEGenerator return arrayPointer; } - private string EmitDereference(DereferenceNode dereference) + private string EmitAddressOf(LValueExpressionNode lval) { - var address = EmitExpression(dereference.Expression); - if (dereference.Type is StructTypeNode) - { - return address; - } - - return EmitLoad(dereference.Type, address); - } - - private string EmitAddressOf(AddressOfNode addressOf) - { - return EmitAddressOfLValue(addressOf.LValue); - } - - private string EmitAddressOfLValue(LValueExpressionNode addressOf) - { - return addressOf switch + return lval switch { ArrayIndexAccessNode arrayIndexAccess => EmitAddressOfArrayIndexAccess(arrayIndexAccess), StructFieldAccessNode structFieldAccess => EmitAddressOfStructFieldAccess(structFieldAccess), - VariableIdentifierNode variableIdent => EmitAddressOfVariableIdent(variableIdent), - _ => throw new ArgumentOutOfRangeException(nameof(addressOf)) + VariableIdentifierNode variableIdent => $"%{variableIdent.Name}", + _ => throw new ArgumentOutOfRangeException(nameof(lval)) }; } @@ -794,11 +751,6 @@ public class QBEGenerator return address; } - private string EmitAddressOfVariableIdent(VariableIdentifierNode variableIdent) - { - return "%" + variableIdent.Name; - } - private string EmitBinaryExpression(BinaryExpressionNode binaryExpression) { var left = EmitExpression(binaryExpression.Left); @@ -1116,17 +1068,6 @@ public class QBEGenerator throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported"); } - private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccess) - { - var address = EmitAddressOfStructFieldAccess(structFieldAccess); - if (structFieldAccess.Type is StructTypeNode) - { - return address; - } - - return EmitLoad(structFieldAccess.Type, address); - } - private string EmitStructFuncCall(StructFuncCallNode structFuncCall) { var func = StructFuncName(structFuncCall.StructType.Module, structFuncCall.StructType.Name, structFuncCall.Name); @@ -1236,19 +1177,16 @@ public class QBEGenerator { return type switch { - SimpleTypeNode simple => simple.StorageSize switch - { - StorageSize.Void => 0, - StorageSize.I8 or StorageSize.U8 => 1, - StorageSize.I16 or StorageSize.U16 => 2, - StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4, - StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8, - _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}") - }, - CStringTypeNode => 8, - StringTypeNode => 8, - ArrayTypeNode => 8, + IntTypeNode intType => intType.Width / 8, + FloatTypeNode fType => fType.Width / 8, + BoolTypeNode => 1, + PointerTypeNode => PTR_SIZE, + FuncTypeNode => PTR_SIZE, StructTypeNode structType => CalculateStructSize(structType), + VoidTypeNode => throw new InvalidOperationException("Void type has no size"), + CStringTypeNode => PTR_SIZE, + StringTypeNode => PTR_SIZE, + ArrayTypeNode => PTR_SIZE, _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}") }; } @@ -1272,23 +1210,21 @@ public class QBEGenerator { return type switch { - SimpleTypeNode simple => simple.StorageSize switch - { - StorageSize.Void => 1, - StorageSize.I8 or StorageSize.U8 => 1, - StorageSize.I16 or StorageSize.U16 => 2, - StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4, - StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8, - _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}") - }, - CStringTypeNode => 8, - StringTypeNode => 8, - ArrayTypeNode => 8, - StructTypeNode structType => CalculateStructAlignment(structType), + IntTypeNode intType => intType.Width / 8, + FloatTypeNode fType => fType.Width / 8, + BoolTypeNode => 1, + PointerTypeNode => PTR_SIZE, + FuncTypeNode => PTR_SIZE, + StructTypeNode st => CalculateStructAlignment(st), + CStringTypeNode => PTR_SIZE, + StringTypeNode => PTR_SIZE, + ArrayTypeNode => PTR_SIZE, + VoidTypeNode => throw new InvalidOperationException("Void has no alignment"), _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}") }; } + private static int CalculateStructAlignment(StructTypeNode structType) { var maxAlignment = 1; @@ -1307,26 +1243,27 @@ public class QBEGenerator return (offset + alignment - 1) & ~(alignment - 1); } - private static int OffsetOf(StructTypeNode structDef, string member) + private static int OffsetOf(StructTypeNode type, string member) { var offset = 0; - foreach (var field in structDef.Fields) + foreach (var field in type.Fields) { + var fieldAlignment = AlignmentOf(field.Type); + offset = AlignTo(offset, fieldAlignment); + if (field.Name == member) { return offset; } - var fieldAlignment = AlignmentOf(field.Type); - - offset = AlignTo(offset, fieldAlignment); offset += SizeOf(field.Type); } throw new UnreachableException($"Member '{member}' not found in struct"); } + private string TmpName() { return $"%.t{++_tmpIndex}"; diff --git a/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs b/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs index 0f68754..8e28030 100644 --- a/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs +++ b/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs @@ -32,7 +32,7 @@ public enum BinaryOperator public abstract record ExpressionNode(TypeNode Type) : Node; -public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type); +public abstract record LValueExpressionNode(TypeNode Type) : ExpressionNode(Type); public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type); diff --git a/compiler/NubLang/TypeChecking/Node/Node.cs b/compiler/NubLang/TypeChecking/Node/Node.cs index b7e57f1..2ee1062 100644 --- a/compiler/NubLang/TypeChecking/Node/Node.cs +++ b/compiler/NubLang/TypeChecking/Node/Node.cs @@ -2,6 +2,4 @@ public abstract record Node; -public record BlockNode(List Statements) : Node; - -public record TypedSyntaxTree(List Definitions); \ No newline at end of file +public record BlockNode(List Statements) : Node; \ No newline at end of file diff --git a/compiler/NubLang/TypeChecking/Node/StatementNode.cs b/compiler/NubLang/TypeChecking/Node/StatementNode.cs index 934a7d1..0e41a20 100644 --- a/compiler/NubLang/TypeChecking/Node/StatementNode.cs +++ b/compiler/NubLang/TypeChecking/Node/StatementNode.cs @@ -1,6 +1,6 @@ namespace NubLang.TypeChecking.Node; -public record StatementNode : Node; +public abstract record StatementNode : Node; public record StatementExpressionNode(ExpressionNode Expression) : StatementNode; diff --git a/compiler/NubLang/TypeChecking/Node/TypeNode.cs b/compiler/NubLang/TypeChecking/Node/TypeNode.cs index 394faa4..47bfdf3 100644 --- a/compiler/NubLang/TypeChecking/Node/TypeNode.cs +++ b/compiler/NubLang/TypeChecking/Node/TypeNode.cs @@ -1,31 +1,13 @@ -using System.Diagnostics.CodeAnalysis; - -namespace NubLang.TypeChecking.Node; +namespace NubLang.TypeChecking.Node; public abstract class TypeNode : IEquatable { - public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType) - { - if (this is SimpleTypeNode st) - { - complexType = null; - simpleType = st; - return true; - } - - if (this is ComplexTypeNode ct) - { - complexType = ct; - simpleType = null; - return false; - } - - throw new ArgumentException($"Type {this} is not a simple type nor a complex type"); - } + public abstract bool IsValueType { get; } + public abstract bool IsScalar { get; } public override bool Equals(object? obj) => obj is TypeNode other && Equals(other); - public abstract bool Equals(TypeNode? other); + public abstract override int GetHashCode(); public abstract override string ToString(); @@ -33,90 +15,71 @@ public abstract class TypeNode : IEquatable public static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right); } -public enum StorageSize +public class VoidTypeNode : TypeNode { - Void, - I8, - I16, - I32, - I64, - U8, - U16, - U32, - U64, - F32, - F64 + public override bool IsValueType => false; + public override bool IsScalar => false; + + public override string ToString() => "void"; + public override bool Equals(TypeNode? other) => other is VoidTypeNode; + public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode)); } -public abstract class SimpleTypeNode : TypeNode +public sealed class IntTypeNode(bool signed, int width) : TypeNode { - public abstract StorageSize StorageSize { get; } -} + public override bool IsValueType => true; + public override bool IsScalar => true; -#region Simple types - -public class IntTypeNode(bool signed, int width) : SimpleTypeNode -{ public bool Signed { get; } = signed; public int Width { get; } = width; - public override StorageSize StorageSize => Signed switch - { - true => Width switch - { - 8 => StorageSize.I8, - 16 => StorageSize.I16, - 32 => StorageSize.I32, - 64 => StorageSize.I64, - _ => throw new ArgumentOutOfRangeException(nameof(Width)) - }, - false => Width switch - { - 8 => StorageSize.U8, - 16 => StorageSize.U16, - 32 => StorageSize.U32, - 64 => StorageSize.U64, - _ => throw new ArgumentOutOfRangeException(nameof(Width)) - } - }; - public override string ToString() => $"{(Signed ? "i" : "u")}{Width}"; public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed; public override int GetHashCode() => HashCode.Combine(typeof(IntTypeNode), Signed, Width); } -public class FloatTypeNode(int width) : SimpleTypeNode +public sealed class FloatTypeNode(int width) : TypeNode { + public override bool IsValueType => true; + public override bool IsScalar => true; + public int Width { get; } = width; - public override StorageSize StorageSize => Width switch - { - 32 => StorageSize.F32, - 64 => StorageSize.F64, - _ => throw new ArgumentOutOfRangeException(nameof(Width)) - }; - public override string ToString() => $"f{Width}"; - public override bool Equals(TypeNode? other) => other is FloatTypeNode @int && @int.Width == Width; + public override bool Equals(TypeNode? other) => other is FloatTypeNode @float && @float.Width == Width; public override int GetHashCode() => HashCode.Combine(typeof(FloatTypeNode), Width); } -public class BoolTypeNode : SimpleTypeNode +public class BoolTypeNode : TypeNode { - public override StorageSize StorageSize => StorageSize.U8; + public override bool IsValueType => true; + public override bool IsScalar => true; public override string ToString() => "bool"; public override bool Equals(TypeNode? other) => other is BoolTypeNode; public override int GetHashCode() => HashCode.Combine(typeof(BoolTypeNode)); } -public class FuncTypeNode(List parameters, TypeNode returnType) : SimpleTypeNode +public sealed class PointerTypeNode(TypeNode baseType) : TypeNode { + public override bool IsValueType => true; + public override bool IsScalar => true; + + public TypeNode BaseType { get; } = baseType; + + public override string ToString() => "^" + BaseType; + public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType); + public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType); +} + +public class FuncTypeNode(List parameters, TypeNode returnType) : TypeNode +{ + public override bool IsValueType => true; + public override bool IsScalar => true; + public List Parameters { get; } = parameters; public TypeNode ReturnType { get; } = returnType; - public override StorageSize StorageSize => StorageSize.U64; - public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}"; public override bool Equals(TypeNode? other) => other is FuncTypeNode func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters); @@ -134,44 +97,19 @@ public class FuncTypeNode(List parameters, TypeNode returnType) : Simp } } -public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode +public class StructTypeNode(string module, string name, List fields, List functions) : TypeNode { - public TypeNode BaseType { get; } = baseType; + public override bool IsValueType => true; + public override bool IsScalar => false; - public override StorageSize StorageSize => StorageSize.U64; + public string Module { get; } = module; + public string Name { get; } = name; + public List Fields { get; set; } = fields; + public List Functions { get; set; } = functions; - public override string ToString() => "^" + BaseType; - public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType); - public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType); -} - -public class VoidTypeNode : SimpleTypeNode -{ - public override StorageSize StorageSize => StorageSize.Void; - - public override string ToString() => "void"; - public override bool Equals(TypeNode? other) => other is VoidTypeNode; - public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode)); -} - -#endregion - -public abstract class ComplexTypeNode : TypeNode; - -#region Complex types - -public class CStringTypeNode : ComplexTypeNode -{ - public override string ToString() => "cstring"; - public override bool Equals(TypeNode? other) => other is CStringTypeNode; - public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode)); -} - -public class StringTypeNode : ComplexTypeNode -{ - public override string ToString() => "string"; - public override bool Equals(TypeNode? other) => other is StringTypeNode; - public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode)); + public override string ToString() => Name; + public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module; + public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name); } public class StructTypeField(string name, TypeNode type, bool hasDefaultValue) @@ -187,26 +125,34 @@ public class StructTypeFunc(string name, FuncTypeNode type) public FuncTypeNode Type { get; } = type; } -public class StructTypeNode(string module, string name, List fields, List functions) : ComplexTypeNode +public class CStringTypeNode : TypeNode { - public string Module { get; } = module; - public string Name { get; } = name; - public List Fields { get; set; } = fields; - public List Functions { get; set; } = functions; + public override bool IsValueType => false; + public override bool IsScalar => false; - public override string ToString() => Name; - public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module; - public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name); + public override string ToString() => "cstring"; + public override bool Equals(TypeNode? other) => other is CStringTypeNode; + public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode)); } -public class ArrayTypeNode(TypeNode elementType) : ComplexTypeNode +public class StringTypeNode : TypeNode { + public override bool IsValueType => false; + public override bool IsScalar => false; + + public override string ToString() => "string"; + public override bool Equals(TypeNode? other) => other is StringTypeNode; + public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode)); +} + +public class ArrayTypeNode(TypeNode elementType) : TypeNode +{ + public override bool IsValueType => false; + public override bool IsScalar => false; + public TypeNode ElementType { get; } = elementType; public override string ToString() => "[]" + ElementType; - public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType); public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), ElementType); -} - -#endregion \ No newline at end of file +} \ No newline at end of file diff --git a/compiler/NubLang/TypeChecking/TypeChecker.cs b/compiler/NubLang/TypeChecking/TypeChecker.cs index 1518ffa..e606ad2 100644 --- a/compiler/NubLang/TypeChecking/TypeChecker.cs +++ b/compiler/NubLang/TypeChecking/TypeChecker.cs @@ -117,15 +117,25 @@ public sealed class TypeChecker scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter)); } + var signature = CheckFuncSignature(node.Signature); + BlockNode? body = null; if (node.Body != null) { - _funcReturnTypes.Push(ResolveType(node.Signature.ReturnType)); + _funcReturnTypes.Push(signature.ReturnType); + body = CheckBlock(node.Body, scope); + + // Insert implicit return for void functions + if (signature.ReturnType is VoidTypeNode && body.Statements.LastOrDefault() is not ReturnNode) + { + body.Statements.Add(new ReturnNode(Optional.Empty())); + } + _funcReturnTypes.Pop(); } - return new FuncNode(_syntaxTree.Metadata.ModuleName, node.Name, node.ExternSymbol, CheckFuncSignature(node.Signature), body); + return new FuncNode(_syntaxTree.Metadata.ModuleName, node.Name, node.ExternSymbol, signature, body); } private StatementNode CheckStatement(StatementSyntax node) @@ -715,9 +725,29 @@ public sealed class TypeChecker _scopes.Push(scope ?? Scope.SubScope()); + var reachable = true; + var warnedUnreachable = false; + foreach (var statement in node.Statements) { - statements.Add(CheckStatement(statement)); + var checkedStatement = CheckStatement(statement); + + if (reachable) + { + statements.Add(checkedStatement); + if (checkedStatement is ReturnNode or BreakNode or ContinueNode) + { + reachable = false; + } + } + else + { + if (!warnedUnreachable) + { + Diagnostics.Add(Diagnostic.Warning("Statement is unreachable").At(statement).Build()); + warnedUnreachable = true; + } + } } _scopes.Pop(); diff --git a/example/src/main.nub b/example/src/main.nub index c091174..2ee728d 100644 --- a/example/src/main.nub +++ b/example/src/main.nub @@ -2,6 +2,11 @@ module "main" extern "puts" func puts(text: cstring) +struct Human +{ + name: cstring +} + extern "main" func main(args: []cstring): i64 { let x = [2]cstring @@ -14,5 +19,16 @@ extern "main" func main(args: []cstring): i64 puts(u) } + let me: Human = { + name = "test" + } + + puts(me.name) + return 0 } + + +func test() +{ +} \ No newline at end of file