This repository has been archived on 2025-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nub-lang-archive-2/src/compiler/NubLang/Generation/QBE/QBEGenerator.cs
2025-07-24 18:46:32 +02:00

568 lines
17 KiB
C#

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using NubLang.Tokenization;
using NubLang.TypeChecking;
using NubLang.TypeChecking.Node;
namespace NubLang.Generation.QBE;
public partial class QBEGenerator
{
private readonly TypedSyntaxTree _syntaxTree;
private readonly TypedDefinitionTable _definitionTable;
private readonly QBEWriter _writer;
private readonly List<CStringLiteral> _cStringLiterals = [];
private readonly List<StringLiteral> _stringLiterals = [];
private readonly Stack<string> _breakLabels = [];
private readonly Stack<string> _continueLabels = [];
private readonly Queue<(ArrowFuncNode Func, string Name)> _arrowFunctions = [];
private readonly Stack<Scope> _scopes = [];
private int _tmpIndex;
private int _labelIndex;
private int _arrowFuncIndex;
private int _cStringLiteralIndex;
private int _stringLiteralIndex;
private bool _codeIsReachable = true;
private Scope Scope => _scopes.Peek();
public QBEGenerator(TypedSyntaxTree syntaxTree, TypedDefinitionTable definitionTable)
{
_syntaxTree = syntaxTree;
_definitionTable = definitionTable;
_writer = new QBEWriter();
}
public string Emit()
{
_cStringLiterals.Clear();
_stringLiterals.Clear();
_breakLabels.Clear();
_continueLabels.Clear();
_arrowFunctions.Clear();
_scopes.Clear();
_tmpIndex = 0;
_labelIndex = 0;
_arrowFuncIndex = 0;
_cStringLiteralIndex = 0;
_stringLiteralIndex = 0;
_codeIsReachable = true;
foreach (var structDef in _definitionTable.GetStructs())
{
EmitStructDefinition(structDef);
_writer.NewLine();
}
foreach (var trait in _definitionTable.GetTraits())
{
EmitTraitVTable(trait);
_writer.NewLine();
}
foreach (var funcDef in _syntaxTree.Definitions.OfType<LocalFuncNode>())
{
EmitFuncDefinition(LocalFuncName(funcDef), funcDef.Signature.Parameters, funcDef.Signature.ReturnType, funcDef.Body);
_writer.NewLine();
}
while (_arrowFunctions.TryDequeue(out var arrowFunc))
{
EmitFuncDefinition(arrowFunc.Name, arrowFunc.Func.Parameters, arrowFunc.Func.ReturnType, arrowFunc.Func.Body);
_writer.NewLine();
}
foreach (var cStringLiteral in _cStringLiterals)
{
_writer.WriteLine($"data {cStringLiteral.Name} = {{ b \"{cStringLiteral.Value}\", b 0 }}");
}
foreach (var stringLiteral in _stringLiterals)
{
var bytes = Encoding.UTF8.GetBytes(stringLiteral.Value).Select(b => $"b {b}");
_writer.WriteLine($"data {stringLiteral.Name} = {{ l {stringLiteral.Value.Length}, {string.Join(", ", bytes)} }}");
}
return _writer.ToString();
}
private static string QBEAssign(TypeNode type)
{
if (type.IsSimpleType(out var simpleType, out _))
{
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";
}
private void EmitStore(TypeNode type, string value, string destination)
{
string store;
if (type.IsSimpleType(out var simpleType, out _))
{
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";
}
_writer.Indented($"{store} {value}, {destination}");
}
private Val EmitLoad(TypeNode type, string from)
{
string load;
if (type.IsSimpleType(out var simpleType, out _))
{
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";
}
var into = TmpName();
_writer.Indented($"{into} {QBEAssign(type)} {load} {from}");
return new Val(into, type, ValKind.Direct);
}
private void EmitMemcpy(string source, string destination, string length)
{
_writer.Indented($"call $nub_memcpy(l {source}, l {destination}, l {length})");
}
private string EmitArraySizeInBytes(ArrayTypeNode type, string array)
{
var size = TmpName();
_writer.Indented($"{size} =l loadl {array}");
_writer.Indented($"{size} =l mul {size}, {type.ElementType.Size(_definitionTable)}");
_writer.Indented($"{size} =l add {size}, 8");
return size;
}
private string EmitCStringSizeInBytes(string cstring)
{
var size = TmpName();
_writer.Indented($"{size} =l call $nub_cstring_length(l {cstring})");
_writer.Indented($"{size} =l add {size}, 1");
return size;
}
private string EmitStringSizeInBytes(string nubstring)
{
var size = TmpName();
_writer.Indented($"{size} =l loadl {nubstring}");
_writer.Indented($"{size} =l add {size}, 8");
return size;
}
private bool EmitTryMoveInto(ExpressionNode source, string destinationPointer)
{
switch (source)
{
case ArrayInitializerNode arrayInitializer:
{
EmitStore(source.Type, EmitUnwrap(EmitArrayInitializer(arrayInitializer)), destinationPointer);
return true;
}
case StructInitializerNode structInitializer:
{
EmitStructInitializer(structInitializer, destinationPointer);
return true;
}
case LiteralNode { Kind: LiteralKind.String } literal:
{
EmitStore(source.Type, EmitUnwrap(EmitLiteral(literal)), destinationPointer);
return true;
}
}
return false;
}
private void EmitCopyIntoOrInitialize(ExpressionNode source, string destinationPointer)
{
// If the source is a value which is not used yet such as an array/struct initializer or literal, we can skip copying
if (EmitTryMoveInto(source, destinationPointer))
{
return;
}
var value = EmitUnwrap(EmitExpression(source));
if (source.Type.IsSimpleType(out var simpleType, out var complexType))
{
EmitStore(simpleType, value, destinationPointer);
}
else
{
if (complexType is CustomTypeNode customType)
{
EmitMemcpy(value, destinationPointer, customType.Size(_definitionTable).ToString());
}
else
{
var size = complexType switch
{
ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
CStringTypeNode => EmitCStringSizeInBytes(value),
NubStringTypeNode => EmitStringSizeInBytes(value),
_ => throw new ArgumentOutOfRangeException(nameof(source.Type))
};
var buffer = TmpName();
_writer.Indented($"{buffer} =l alloc8 {size}");
EmitMemcpy(value, buffer, size);
EmitStore(complexType, buffer, destinationPointer);
}
}
}
private bool EmitTryCreateWithoutCopy(ExpressionNode source, [NotNullWhen(true)] out string? destination)
{
switch (source)
{
case ArrayInitializerNode:
case StructInitializerNode:
case LiteralNode { Kind: LiteralKind.String }:
{
destination = EmitUnwrap(EmitExpression(source));
return true;
}
}
destination = null;
return false;
}
private string EmitCreateCopyOrInitialize(ExpressionNode source)
{
// If the source is a value which is not used yet such as an array/struct initializer or literal, we can skip copying
if (EmitTryCreateWithoutCopy(source, out var uncopiedValue))
{
return uncopiedValue;
}
var value = EmitUnwrap(EmitExpression(source));
if (source.Type.IsSimpleType(out _, out var complexType))
{
// Simple types are passed in registers and are therefore always copied
return value;
}
var size = complexType switch
{
ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
CStringTypeNode => EmitCStringSizeInBytes(value),
NubStringTypeNode => EmitStringSizeInBytes(value),
CustomTypeNode customType => customType.Size(_definitionTable).ToString(),
_ => throw new ArgumentOutOfRangeException(nameof(source.Type))
};
var destination = TmpName();
_writer.Indented($"{destination} =l alloc8 {size}");
EmitMemcpy(value, destination, size);
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 CustomTypeNode customType)
{
return CustomTypeName(customType);
}
return "l";
}
private void EmitFuncDefinition(string name, IReadOnlyList<FuncParameterNode> parameters, TypeNode returnType, BlockNode body)
{
_labelIndex = 0;
_tmpIndex = 0;
var builder = new StringBuilder();
builder.Append("export function ");
if (returnType is not VoidTypeNode)
{
builder.Append(FuncQBETypeName(returnType) + ' ');
}
builder.Append(name);
var parameterStrings = parameters.Select(x => FuncQBETypeName(x.Type) + $" %{x.Name}");
builder.Append($"({string.Join(", ", parameterStrings)})");
_writer.StartFunction(builder.ToString());
var scope = new Scope();
foreach (var parameter in parameters)
{
scope.Declare(parameter.Name, new Val("%" + parameter.Name, parameter.Type, ValKind.Direct));
}
EmitBlock(body, scope);
// Implicit return for void functions if no explicit return has been set
if (returnType is VoidTypeNode && body.Statements is [.., not ReturnNode])
{
if (returnType is VoidTypeNode)
{
_writer.Indented("ret");
}
}
_writer.EndFunction();
}
private void EmitStructDefinition(StructNode structDef)
{
_writer.WriteLine($"type {CustomTypeName(structDef.Name)} = {{ ");
var types = new Dictionary<string, string>();
foreach (var field in structDef.Fields)
{
types.Add(field.Name, StructDefQBEType(field));
}
var longest = types.Values.Max(x => x.Length);
foreach (var (name, type) in types)
{
var padding = longest - type.Length;
_writer.Indented($"{type},{new string(' ', padding)} # {name}");
}
_writer.WriteLine("}");
return;
string StructDefQBEType(StructFieldNode field)
{
if (field.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 CustomTypeNode customType)
{
return CustomTypeName(customType);
}
return "l";
}
}
private void EmitTraitVTable(TraitNode traitDef)
{
_writer.WriteLine($"type {CustomTypeName(traitDef.Name)} = {{");
foreach (var func in traitDef.Functions)
{
_writer.Indented($"l, # func {func.Name}({string.Join(", ", func.Signature.Parameters.Select(x => $"{x.Name}: {x.Type}"))}): {func.Signature.ReturnType}");
}
_writer.WriteLine("}");
}
private void EmitBlock(BlockNode block, Scope? scope = null)
{
_scopes.Push(scope ?? Scope.SubScope());
foreach (var statement in block.Statements)
{
if (_codeIsReachable)
{
EmitStatement(statement);
}
}
_scopes.Pop();
_codeIsReachable = true;
}
private string EmitUnwrap(Val val)
{
return val.Kind switch
{
ValKind.Direct => val.Name,
ValKind.Pointer => EmitLoad(val.Type, val.Name).Name,
_ => throw new ArgumentOutOfRangeException()
};
}
private int OffsetOf(StructNode structDefinition, string member)
{
var offset = 0;
foreach (var field in structDefinition.Fields)
{
if (field.Name == member)
{
return offset;
}
var fieldAlignment = field.Type.Alignment(_definitionTable);
offset = TypeNode.AlignTo(offset, fieldAlignment);
offset += field.Type.Size(_definitionTable);
}
throw new UnreachableException($"Member '{member}' not found in struct");
}
#region Naming utilities
private string TmpName()
{
return $"%t{++_tmpIndex}";
}
private string LabelName()
{
return $"@l{++_labelIndex}";
}
private string CStringName()
{
return $"$cstring{++_cStringLiteralIndex}";
}
private string StringName()
{
return $"$string{++_stringLiteralIndex}";
}
private string LocalFuncName(LocalFuncNode funcDef)
{
return $"${funcDef.Name}";
}
private string ExternFuncName(ExternFuncNode funcDef)
{
return $"${funcDef.CallName}";
}
private string CustomTypeName(CustomTypeNode customType)
{
return CustomTypeName(customType.Name);
}
private string CustomTypeName(string name)
{
return $":{name}";
}
#endregion
}
public class StringLiteral(string value, string name)
{
public string Value { get; } = value;
public string Name { get; } = name;
}
public class CStringLiteral(string value, string name)
{
public string Value { get; } = value;
public string Name { get; } = name;
}
public record Val(string Name, TypeNode Type, ValKind Kind);
public class Scope(Scope? parent = null)
{
private readonly Dictionary<string, Val> _variables = [];
public Val Lookup(string name)
{
var variable = _variables.GetValueOrDefault(name);
if (variable != null)
{
return variable;
}
return parent?.Lookup(name) ?? throw new UnreachableException($"Variable '{name}' not found");
}
public void Declare(string name, Val value)
{
_variables.Add(name, value);
}
public Scope SubScope()
{
return new Scope(this);
}
}
public enum ValKind
{
Pointer,
Direct,
}