This commit is contained in:
nub31
2025-09-16 18:22:20 +02:00
parent 826f8ffad3
commit 9c1cdf19e6
7 changed files with 349 additions and 422 deletions

View File

@@ -8,6 +8,8 @@ namespace NubLang.Generation.QBE;
public class QBEGenerator
{
private const int PTR_SIZE = 8;
private readonly QBEWriter _writer;
private readonly List<DefinitionNode> _definitions;
private readonly HashSet<StructTypeNode> _structTypes;
@@ -20,7 +22,6 @@ public class QBEGenerator
private int _labelIndex;
private int _cStringLiteralIndex;
private int _stringLiteralIndex;
private bool _codeIsReachable = true;
public QBEGenerator(List<DefinitionNode> definitions, HashSet<StructTypeNode> 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}";

View File

@@ -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);

View File

@@ -2,6 +2,4 @@
public abstract record Node;
public record BlockNode(List<StatementNode> Statements) : Node;
public record TypedSyntaxTree(List<DefinitionNode> Definitions);
public record BlockNode(List<StatementNode> Statements) : Node;

View File

@@ -1,6 +1,6 @@
namespace NubLang.TypeChecking.Node;
public record StatementNode : Node;
public abstract record StatementNode : Node;
public record StatementExpressionNode(ExpressionNode Expression) : StatementNode;

View File

@@ -1,31 +1,13 @@
using System.Diagnostics.CodeAnalysis;
namespace NubLang.TypeChecking.Node;
namespace NubLang.TypeChecking.Node;
public abstract class TypeNode : IEquatable<TypeNode>
{
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<TypeNode>
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<TypeNode> 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<TypeNode> parameters, TypeNode returnType) : TypeNode
{
public override bool IsValueType => true;
public override bool IsScalar => true;
public List<TypeNode> 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<TypeNode> parameters, TypeNode returnType) : Simp
}
}
public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode
public class StructTypeNode(string module, string name, List<StructTypeField> fields, List<StructTypeFunc> 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<StructTypeField> Fields { get; set; } = fields;
public List<StructTypeFunc> 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<StructTypeField> fields, List<StructTypeFunc> functions) : ComplexTypeNode
public class CStringTypeNode : TypeNode
{
public string Module { get; } = module;
public string Name { get; } = name;
public List<StructTypeField> Fields { get; set; } = fields;
public List<StructTypeFunc> 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
}

View File

@@ -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<ExpressionNode>.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();