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

View File

@@ -8,6 +8,8 @@ namespace NubLang.Generation.QBE;
public class QBEGenerator public class QBEGenerator
{ {
private const int PTR_SIZE = 8;
private readonly QBEWriter _writer; private readonly QBEWriter _writer;
private readonly List<DefinitionNode> _definitions; private readonly List<DefinitionNode> _definitions;
private readonly HashSet<StructTypeNode> _structTypes; private readonly HashSet<StructTypeNode> _structTypes;
@@ -20,7 +22,6 @@ public class QBEGenerator
private int _labelIndex; private int _labelIndex;
private int _cStringLiteralIndex; private int _cStringLiteralIndex;
private int _stringLiteralIndex; private int _stringLiteralIndex;
private bool _codeIsReachable = true;
public QBEGenerator(List<DefinitionNode> definitions, HashSet<StructTypeNode> structTypes) public QBEGenerator(List<DefinitionNode> definitions, HashSet<StructTypeNode> structTypes)
{ {
@@ -39,7 +40,6 @@ public class QBEGenerator
_labelIndex = 0; _labelIndex = 0;
_cStringLiteralIndex = 0; _cStringLiteralIndex = 0;
_stringLiteralIndex = 0; _stringLiteralIndex = 0;
_codeIsReachable = true;
_writer.Comment("========== Builtin functions =========="); _writer.Comment("========== Builtin functions ==========");
@@ -142,77 +142,125 @@ public class QBEGenerator
private static string QBEAssign(TypeNode type) private static string QBEAssign(TypeNode type)
{ {
if (type.IsSimpleType(out var simpleType, out _)) return type switch
{ {
return simpleType.StorageSize switch IntTypeNode { Width: <= 32 } => "=w",
{ IntTypeNode { Width: 64 } => "=l",
StorageSize.I8 or StorageSize.U8 or StorageSize.I16 or StorageSize.U16 or StorageSize.I32 or StorageSize.U32 => "=w", FloatTypeNode { Width: 32 } => "=s",
StorageSize.I64 or StorageSize.U64 => "=l", FloatTypeNode { Width: 64 } => "=d",
StorageSize.F32 => "=s", BoolTypeNode => "=w",
StorageSize.F64 => "=d", PointerTypeNode => "=l",
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) 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()}")
}; };
} }
return "=l";
}
private void EmitStore(TypeNode type, string value, string destination) private void EmitStore(TypeNode type, string value, string destination)
{ {
string store; var store = type switch
if (type.IsSimpleType(out var simpleType, out _))
{ {
store = simpleType.StorageSize switch BoolTypeNode => "storeb",
{ IntTypeNode { Width: 8 } => "storeb",
StorageSize.I8 or StorageSize.U8 => "storeb", IntTypeNode { Width: 16 } => "storeh",
StorageSize.I16 or StorageSize.U16 => "storeh", IntTypeNode { Width: 32 } => "storew",
StorageSize.I32 or StorageSize.U32 => "storew", IntTypeNode { Width: 64 } => "storel",
StorageSize.I64 or StorageSize.U64 => "storel", FloatTypeNode { Width: 32 } => "stores",
StorageSize.F32 => "stores", FloatTypeNode { Width: 64 } => "stored",
StorageSize.F64 => "stored", PointerTypeNode => "storel",
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) 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()}")
}; };
}
else
{
store = "storel";
}
_writer.Indented($"{store} {value}, {destination}"); _writer.Indented($"{store} {value}, {destination}");
} }
private string EmitLoad(TypeNode type, string from) private string EmitLoad(TypeNode type, string from)
{ {
string load; string load = type switch
if (type.IsSimpleType(out var simpleType, out _))
{ {
load = simpleType.StorageSize switch BoolTypeNode => "loadub",
{ IntTypeNode { Signed: true, Width: 8 } => "loadsb",
StorageSize.I64 or StorageSize.U64 => "loadl", IntTypeNode { Signed: true, Width: 16 } => "loadsh",
StorageSize.I32 or StorageSize.U32 => "loadw", IntTypeNode { Signed: true, Width: 32 } => "loadw",
StorageSize.I16 => "loadsh", IntTypeNode { Signed: true, Width: 64 } => "loadl",
StorageSize.I8 => "loadsb", IntTypeNode { Signed: false, Width: 8 } => "loadsb",
StorageSize.U16 => "loaduh", IntTypeNode { Signed: false, Width: 16 } => "loadsh",
StorageSize.U8 => "loadub", IntTypeNode { Signed: false, Width: 32 } => "loadw",
StorageSize.F64 => "loadd", IntTypeNode { Signed: false, Width: 64 } => "loadl",
StorageSize.F32 => "loads", FloatTypeNode { Width: 32 } => "loads",
_ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize)) 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()}")
}; };
}
else
{
load = "loadl";
}
var into = TmpName(); var into = TmpName();
_writer.Indented($"{into} {QBEAssign(type)} {load} {from}"); _writer.Indented($"{into} {QBEAssign(type)} {load} {from}");
return into; 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) private void EmitMemset(string destination, int value, string length)
{ {
_writer.Indented($"call $.memset(l {destination}, l {value}, l {length})"); _writer.Indented($"call $.memset(l {destination}, l {value}, l {length})");
@@ -250,107 +298,98 @@ public class QBEGenerator
private void EmitCopyInto(ExpressionNode source, string destination) private void EmitCopyInto(ExpressionNode source, string destination)
{ {
// Simple types are passed in registers and can therefore just be stored if (source.Type.IsScalar)
if (source.Type.IsSimpleType(out var simpleType, out var complexType))
{ {
var value = EmitExpression(source); EmitStore(source.Type, EmitExpression(source), destination);
EmitStore(simpleType, value, destination);
return; return;
} }
// Structs has known sizes at compile time if (source.Type.IsValueType)
if (complexType is StructTypeNode)
{ {
var value = EmitExpression(source); 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 switch (source.Type)
{
case ArrayTypeNode arrayType:
{ {
var value = EmitExpression(source); var value = EmitExpression(source);
var size = complexType switch var size = EmitArraySizeInBytes(arrayType, value);
{
ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
CStringTypeNode => EmitCStringSizeInBytes(value),
StringTypeNode => EmitStringSizeInBytes(value),
_ => throw new ArgumentOutOfRangeException(nameof(source.Type))
};
var buffer = TmpName(); var buffer = TmpName();
_writer.Indented($"{buffer} =l alloc8 {size}"); _writer.Indented($"{buffer} =l alloc8 {size}");
EmitMemcpy(value, buffer, size); EmitMemcpy(value, buffer, size);
EmitStore(complexType, buffer, destination); 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) private string EmitCopy(ExpressionNode source)
{ {
// Allowlist for types which are safe to not copy if (source is RValueExpressionNode || source.Type.IsScalar)
if (source is ArrayInitializerNode or StructInitializerNode or LiteralNode)
{ {
return EmitExpression(source); 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 value = EmitExpression(source);
var destination = TmpName(); var destination = TmpName();
// Structs has known sizes at compile time if (source.Type.IsValueType)
if (complexType is StructTypeNode)
{ {
var size = SizeOf(complexType); var size = SizeOf(source.Type);
_writer.Indented($"{destination} =l alloc8 {size}"); _writer.Indented($"{destination} =l alloc8 {size}");
_writer.Indented($"blit {value}, {destination}, {size}"); _writer.Indented($"blit {value}, {destination}, {size}");
}
// 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);
}
return destination; return destination;
} }
// Utility to create QBE type names for function parameters and return types switch (source.Type)
private string FuncQBETypeName(TypeNode type)
{ {
if (type.IsSimpleType(out var simpleType, out var complexType)) case ArrayTypeNode arrayType:
{ var arraySize = EmitArraySizeInBytes(arrayType, value);
return simpleType.StorageSize switch _writer.Indented($"{destination} =l alloc8 {arraySize}");
{ EmitMemcpy(value, destination, arraySize);
StorageSize.I64 or StorageSize.U64 => "l", break;
StorageSize.I32 or StorageSize.U32 => "w", case CStringTypeNode:
StorageSize.I16 => "sh", var cstrSize = EmitCStringSizeInBytes(value);
StorageSize.I8 => "sb", _writer.Indented($"{destination} =l alloc8 {cstrSize}");
StorageSize.U16 => "uh", EmitMemcpy(value, destination, cstrSize);
StorageSize.U8 => "ub", break;
StorageSize.F64 => "d", case StringTypeNode:
StorageSize.F32 => "s", var strSize = EmitStringSizeInBytes(value);
_ => throw new ArgumentOutOfRangeException() _writer.Indented($"{destination} =l alloc8 {strSize}");
}; EmitMemcpy(value, destination, strSize);
break;
default:
throw new InvalidOperationException($"Cannot copy type {source.Type}");
} }
if (complexType is StructTypeNode structType) return destination;
{
return StructTypeName(structType.Module, structType.Name);
}
return "l";
} }
private void EmitStructType(StructTypeNode structType) private void EmitStructType(StructTypeNode structType)
@@ -365,31 +404,6 @@ public class QBEGenerator
} }
_writer.WriteLine(" }"); _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) private void EmitStructDefinition(StructNode structDef)
@@ -477,12 +491,6 @@ public class QBEGenerator
EmitBlock(funcDef.Body); 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.WriteLine("}");
_writer.NewLine(); _writer.NewLine();
} }
@@ -490,28 +498,23 @@ public class QBEGenerator
private void EmitBlock(BlockNode block) private void EmitBlock(BlockNode block)
{ {
foreach (var statement in block.Statements) foreach (var statement in block.Statements)
{
if (_codeIsReachable)
{ {
EmitStatement(statement); EmitStatement(statement);
} }
} }
_codeIsReachable = true;
}
private void EmitStatement(StatementNode statement) private void EmitStatement(StatementNode statement)
{ {
switch (statement) switch (statement)
{ {
case AssignmentNode assignment: case AssignmentNode assignment:
EmitAssignment(assignment); EmitCopyInto(assignment.Value, EmitAddressOf(assignment.Target));
break; break;
case BreakNode: case BreakNode:
EmitBreak(); _writer.Indented($"jmp {_breakLabels.Peek()}");
break; break;
case ContinueNode: case ContinueNode:
EmitContinue(); _writer.Indented($"jmp {_continueLabels.Peek()}");
break; break;
case IfNode ifStatement: case IfNode ifStatement:
EmitIf(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) private void EmitIf(IfNode ifStatement)
{ {
var trueLabel = LabelName(); var trueLabel = LabelName();
@@ -666,60 +652,47 @@ public class QBEGenerator
_writer.WriteLine(endLabel); _writer.WriteLine(endLabel);
} }
private string EmitExpression(ExpressionNode expression) private string EmitExpression(ExpressionNode expr)
{ {
return expression switch return expr switch
{ {
ArrayInitializerNode arrayInitializer => EmitArrayInitializer(arrayInitializer), RValueExpressionNode rValue => EmitRValue(rValue),
StructInitializerNode structInitializer => EmitStructInitializer(structInitializer), LValueExpressionNode lValue => EmitLValue(lValue),
AddressOfNode addressOf => EmitAddressOf(addressOf), _ => throw new ArgumentOutOfRangeException(nameof(expr))
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))
}; };
} }
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); var address = EmitAddressOf(lValue);
if (variableIdent.Type is StructTypeNode)
if (lValue.Type is { IsValueType: true, IsScalar: false })
{ {
return address; return address;
} }
return EmitLoad(variableIdent.Type, address); return EmitLoad(lValue.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);
} }
private string EmitArrayInitializer(ArrayInitializerNode arrayInitializer) private string EmitArrayInitializer(ArrayInitializerNode arrayInitializer)
@@ -743,30 +716,14 @@ public class QBEGenerator
return arrayPointer; return arrayPointer;
} }
private string EmitDereference(DereferenceNode dereference) private string EmitAddressOf(LValueExpressionNode lval)
{ {
var address = EmitExpression(dereference.Expression); return lval switch
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
{ {
ArrayIndexAccessNode arrayIndexAccess => EmitAddressOfArrayIndexAccess(arrayIndexAccess), ArrayIndexAccessNode arrayIndexAccess => EmitAddressOfArrayIndexAccess(arrayIndexAccess),
StructFieldAccessNode structFieldAccess => EmitAddressOfStructFieldAccess(structFieldAccess), StructFieldAccessNode structFieldAccess => EmitAddressOfStructFieldAccess(structFieldAccess),
VariableIdentifierNode variableIdent => EmitAddressOfVariableIdent(variableIdent), VariableIdentifierNode variableIdent => $"%{variableIdent.Name}",
_ => throw new ArgumentOutOfRangeException(nameof(addressOf)) _ => throw new ArgumentOutOfRangeException(nameof(lval))
}; };
} }
@@ -794,11 +751,6 @@ public class QBEGenerator
return address; return address;
} }
private string EmitAddressOfVariableIdent(VariableIdentifierNode variableIdent)
{
return "%" + variableIdent.Name;
}
private string EmitBinaryExpression(BinaryExpressionNode binaryExpression) private string EmitBinaryExpression(BinaryExpressionNode binaryExpression)
{ {
var left = EmitExpression(binaryExpression.Left); 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"); 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) private string EmitStructFuncCall(StructFuncCallNode structFuncCall)
{ {
var func = StructFuncName(structFuncCall.StructType.Module, structFuncCall.StructType.Name, structFuncCall.Name); var func = StructFuncName(structFuncCall.StructType.Module, structFuncCall.StructType.Name, structFuncCall.Name);
@@ -1236,19 +1177,16 @@ public class QBEGenerator
{ {
return type switch return type switch
{ {
SimpleTypeNode simple => simple.StorageSize switch IntTypeNode intType => intType.Width / 8,
{ FloatTypeNode fType => fType.Width / 8,
StorageSize.Void => 0, BoolTypeNode => 1,
StorageSize.I8 or StorageSize.U8 => 1, PointerTypeNode => PTR_SIZE,
StorageSize.I16 or StorageSize.U16 => 2, FuncTypeNode => PTR_SIZE,
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 => CalculateStructSize(structType), 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()}") _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
}; };
} }
@@ -1272,23 +1210,21 @@ public class QBEGenerator
{ {
return type switch return type switch
{ {
SimpleTypeNode simple => simple.StorageSize switch IntTypeNode intType => intType.Width / 8,
{ FloatTypeNode fType => fType.Width / 8,
StorageSize.Void => 1, BoolTypeNode => 1,
StorageSize.I8 or StorageSize.U8 => 1, PointerTypeNode => PTR_SIZE,
StorageSize.I16 or StorageSize.U16 => 2, FuncTypeNode => PTR_SIZE,
StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4, StructTypeNode st => CalculateStructAlignment(st),
StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8, CStringTypeNode => PTR_SIZE,
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}") StringTypeNode => PTR_SIZE,
}, ArrayTypeNode => PTR_SIZE,
CStringTypeNode => 8, VoidTypeNode => throw new InvalidOperationException("Void has no alignment"),
StringTypeNode => 8,
ArrayTypeNode => 8,
StructTypeNode structType => CalculateStructAlignment(structType),
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}") _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
}; };
} }
private static int CalculateStructAlignment(StructTypeNode structType) private static int CalculateStructAlignment(StructTypeNode structType)
{ {
var maxAlignment = 1; var maxAlignment = 1;
@@ -1307,26 +1243,27 @@ public class QBEGenerator
return (offset + alignment - 1) & ~(alignment - 1); 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; 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) if (field.Name == member)
{ {
return offset; return offset;
} }
var fieldAlignment = AlignmentOf(field.Type);
offset = AlignTo(offset, fieldAlignment);
offset += SizeOf(field.Type); offset += SizeOf(field.Type);
} }
throw new UnreachableException($"Member '{member}' not found in struct"); throw new UnreachableException($"Member '{member}' not found in struct");
} }
private string TmpName() private string TmpName()
{ {
return $"%.t{++_tmpIndex}"; return $"%.t{++_tmpIndex}";

View File

@@ -32,7 +32,7 @@ public enum BinaryOperator
public abstract record ExpressionNode(TypeNode Type) : Node; 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); public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);

View File

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

View File

@@ -1,6 +1,6 @@
namespace NubLang.TypeChecking.Node; namespace NubLang.TypeChecking.Node;
public record StatementNode : Node; public abstract record StatementNode : Node;
public record StatementExpressionNode(ExpressionNode Expression) : StatementNode; 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 abstract class TypeNode : IEquatable<TypeNode>
{ {
public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType) public abstract bool IsValueType { get; }
{ public abstract bool IsScalar { get; }
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 override bool Equals(object? obj) => obj is TypeNode other && Equals(other); public override bool Equals(object? obj) => obj is TypeNode other && Equals(other);
public abstract bool Equals(TypeNode? other); public abstract bool Equals(TypeNode? other);
public abstract override int GetHashCode(); public abstract override int GetHashCode();
public abstract override string ToString(); 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 static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right);
} }
public enum StorageSize public class VoidTypeNode : TypeNode
{ {
Void, public override bool IsValueType => false;
I8, public override bool IsScalar => false;
I16,
I32, public override string ToString() => "void";
I64, public override bool Equals(TypeNode? other) => other is VoidTypeNode;
U8, public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
U16,
U32,
U64,
F32,
F64
} }
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 bool Signed { get; } = signed;
public int Width { get; } = width; 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 string ToString() => $"{(Signed ? "i" : "u")}{Width}";
public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed; 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 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 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 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 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 string ToString() => "bool";
public override bool Equals(TypeNode? other) => other is BoolTypeNode; public override bool Equals(TypeNode? other) => other is BoolTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(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 List<TypeNode> Parameters { get; } = parameters;
public TypeNode ReturnType { get; } = returnType; public TypeNode ReturnType { get; } = returnType;
public override StorageSize StorageSize => StorageSize.U64;
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}"; 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); 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 string ToString() => Name;
public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType); public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType); public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
}
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 class StructTypeField(string name, TypeNode type, bool hasDefaultValue) 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 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 override bool IsValueType => false;
public string Name { get; } = name; public override bool IsScalar => false;
public List<StructTypeField> Fields { get; set; } = fields;
public List<StructTypeFunc> Functions { get; set; } = functions;
public override string ToString() => Name; public override string ToString() => "cstring";
public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module; public override bool Equals(TypeNode? other) => other is CStringTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name); 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 TypeNode ElementType { get; } = elementType;
public override string ToString() => "[]" + ElementType; public override string ToString() => "[]" + ElementType;
public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType); public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType);
public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), 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)); scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
} }
var signature = CheckFuncSignature(node.Signature);
BlockNode? body = null; BlockNode? body = null;
if (node.Body != null) if (node.Body != null)
{ {
_funcReturnTypes.Push(ResolveType(node.Signature.ReturnType)); _funcReturnTypes.Push(signature.ReturnType);
body = CheckBlock(node.Body, scope); 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(); _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) private StatementNode CheckStatement(StatementSyntax node)
@@ -715,9 +725,29 @@ public sealed class TypeChecker
_scopes.Push(scope ?? Scope.SubScope()); _scopes.Push(scope ?? Scope.SubScope());
var reachable = true;
var warnedUnreachable = false;
foreach (var statement in node.Statements) 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(); _scopes.Pop();

View File

@@ -2,6 +2,11 @@ module "main"
extern "puts" func puts(text: cstring) extern "puts" func puts(text: cstring)
struct Human
{
name: cstring
}
extern "main" func main(args: []cstring): i64 extern "main" func main(args: []cstring): i64
{ {
let x = [2]cstring let x = [2]cstring
@@ -14,5 +19,16 @@ extern "main" func main(args: []cstring): i64
puts(u) puts(u)
} }
let me: Human = {
name = "test"
}
puts(me.name)
return 0 return 0
} }
func test()
{
}