diff --git a/src/compiler/CLI/Program.cs b/src/compiler/CLI/Program.cs index 8a8e0db..d9fba1b 100644 --- a/src/compiler/CLI/Program.cs +++ b/src/compiler/CLI/Program.cs @@ -120,9 +120,9 @@ foreach (var file in options.Files) { var outFileName = $"{HexString.CreateUnique(8)}_{Path.GetFileNameWithoutExtension(file)}"; - var ssa = QBEGenerator.Emit(boundSyntaxTrees[file], boundDefinitionTable, file); - // File.WriteAllText(Path.Join(INT_DEBUG_DIR, $"{outFileName}.ssa"), ssa); - File.WriteAllText(Path.Join(INT_DEBUG_DIR, $"{Path.GetFileNameWithoutExtension(file)}.ssa"), ssa); + var generator = new QBEGenerator(boundSyntaxTrees[file], boundDefinitionTable, file); + var ssa = generator.Emit(); + File.WriteAllText(Path.Join(INT_DEBUG_DIR, $"{outFileName}.ssa"), ssa); var asm = await QBE.Invoke(ssa); if (asm == null) diff --git a/src/compiler/NubLang/Generation/QBE/QBEGenerator.Expression.cs b/src/compiler/NubLang/Generation/QBE/QBEGenerator.Expression.cs new file mode 100644 index 0000000..5435be8 --- /dev/null +++ b/src/compiler/NubLang/Generation/QBE/QBEGenerator.Expression.cs @@ -0,0 +1,467 @@ +using System.Diagnostics; +using System.Globalization; +using NubLang.Syntax.Binding.Node; +using NubLang.Syntax.Tokenization; + +namespace NubLang.Generation.QBE; + +public partial class QBEGenerator +{ + private Val EmitExpression(BoundExpression expression) + { + _writer.WriteDebugLocation(expression); + + return expression switch + { + BoundArrayInitializer arrayInitializer => EmitArrayInitializer(arrayInitializer), + BoundStructInitializer structInitializer => EmitStructInitializer(structInitializer), + BoundAddressOf addressOf => EmitAddressOf(addressOf), + BoundDereference dereference => EmitDereference(dereference), + BoundAnonymousFunc anonymousFunc => EmitAnonymousFunc(anonymousFunc), + BoundBinaryExpression binaryExpression => EmitBinaryExpression(binaryExpression), + BoundFuncCall funcCallExpression => EmitFuncCall(funcCallExpression), + BoundExternFuncIdent externFuncIdent => EmitExternFuncIdent(externFuncIdent), + BoundLocalFuncIdent localFuncIdent => EmitLocalFuncIdent(localFuncIdent), + BoundVariableIdent variableIdent => EmitVariableIdent(variableIdent), + BoundLiteral literal => EmitLiteral(literal), + BoundUnaryExpression unaryExpression => EmitUnaryExpression(unaryExpression), + BoundStructFieldAccess structFieldAccess => EmitStructFieldAccess(structFieldAccess), + BoundTraitFuncAccess traitFuncAccess => EmitTraitFuncAccess(traitFuncAccess), + BoundTraitImplFuncAccess traitImplFuncAccess => EmitTraitImplFuncAccess(traitImplFuncAccess), + BoundArrayIndexAccess arrayIndex => EmitArrayIndexAccess(arrayIndex), + _ => throw new ArgumentOutOfRangeException(nameof(expression)) + }; + } + + private Val EmitAnonymousFunc(BoundAnonymousFunc anonymousFunc) + { + var name = $"$anon_func{++_anonymousFuncIndex}"; + _anonymousFunctions.Enqueue((anonymousFunc, name)); + return new Val(name, anonymousFunc.Type, ValKind.Direct); + } + + private Val EmitArrayIndexAccess(BoundArrayIndexAccess arrayIndexAccess) + { + var array = EmitUnwrap(EmitExpression(arrayIndexAccess.Target)); + var index = EmitUnwrap(EmitExpression(arrayIndexAccess.Index)); + + EmitArrayBoundsCheck(array, index); + + var elementType = ((NubArrayType)arrayIndexAccess.Target.Type).ElementType; + + var pointer = TmpName(); + _writer.Indented($"{pointer} =l mul {index}, {elementType.Size(_definitionTable)}"); + _writer.Indented($"{pointer} =l add {pointer}, 8"); + _writer.Indented($"{pointer} =l add {array}, {pointer}"); + return new Val(pointer, arrayIndexAccess.Type, ValKind.Pointer); + } + + private void EmitArrayBoundsCheck(string array, string index) + { + var count = TmpName(); + _writer.Indented($"{count} =l loadl {array}"); + + var isNegative = TmpName(); + _writer.Indented($"{isNegative} =w csltl {index}, 0"); + + var isOob = TmpName(); + _writer.Indented($"{isOob} =w csgel {index}, {count}"); + + var anyOob = TmpName(); + _writer.Indented($"{anyOob} =w or {isNegative}, {isOob}"); + + var oobLabel = LabelName(); + var notOobLabel = LabelName(); + _writer.Indented($"jnz {anyOob}, {oobLabel}, {notOobLabel}"); + + _writer.Indented(oobLabel); + _writer.Indented($"call $nub_panic_array_oob()"); + + _writer.Indented(notOobLabel); + } + + private Val EmitArrayInitializer(BoundArrayInitializer arrayInitializer) + { + var capacity = EmitUnwrap(EmitExpression(arrayInitializer.Capacity)); + var elementSize = arrayInitializer.ElementType.Size(_definitionTable); + + var capacityInBytes = TmpName(); + _writer.Indented($"{capacityInBytes} =l mul {capacity}, {elementSize}"); + var totalSize = TmpName(); + _writer.Indented($"{totalSize} =l add {capacityInBytes}, 8"); + + var arrayPointer = TmpName(); + _writer.Indented($"{arrayPointer} =l alloc8 {totalSize}"); + _writer.Indented($"storel {capacity}, {arrayPointer}"); + + var dataPointer = TmpName(); + _writer.Indented($"{dataPointer} =l add {arrayPointer}, 8"); + _writer.Indented($"call $nub_memset(l {dataPointer}, w 0, l {capacityInBytes})"); + + return new Val(arrayPointer, arrayInitializer.Type, ValKind.Direct); + } + + private Val EmitDereference(BoundDereference dereference) + { + return EmitLoad(dereference.Type, EmitUnwrap(EmitExpression(dereference.Expression))); + } + + private Val EmitAddressOf(BoundAddressOf addressOf) + { + var value = EmitExpression(addressOf.Expression); + if (value.Kind != ValKind.Pointer) + { + throw new UnreachableException("Tried to take address of non-pointer type. This should have been causht in the type checker"); + } + + return new Val(value.Name, addressOf.Type, ValKind.Direct); + } + + private Val EmitBinaryExpression(BoundBinaryExpression binaryExpression) + { + var left = EmitUnwrap(EmitExpression(binaryExpression.Left)); + var right = EmitUnwrap(EmitExpression(binaryExpression.Right)); + + var outputName = TmpName(); + + var instruction = EmitBinaryInstructionFor(binaryExpression.Operator, binaryExpression.Left.Type, left, right); + + _writer.Indented($"{outputName} {QBEAssign(binaryExpression.Left.Type)} {instruction} {left}, {right}"); + return new Val(outputName, binaryExpression.Type, ValKind.Direct); + } + + private string EmitBinaryInstructionFor(BoundBinaryOperator op, NubType type, string left, string right) + { + if (op is + BoundBinaryOperator.Equal or + BoundBinaryOperator.NotEqual or + BoundBinaryOperator.GreaterThan or + BoundBinaryOperator.GreaterThanOrEqual or + BoundBinaryOperator.LessThan or + BoundBinaryOperator.LessThanOrEqual) + { + char suffix; + + if (!type.IsSimpleType(out var simpleType, out _)) + { + throw new NotSupportedException("Binary operations is only supported for simple types."); + } + + switch (simpleType.StorageSize) + { + case StorageSize.I8: + _writer.Indented($"{left} =w extsb {left}"); + _writer.Indented($"{right} =w extsb {right}"); + suffix = 'w'; + break; + case StorageSize.U8: + _writer.Indented($"{left} =w extub {left}"); + _writer.Indented($"{right} =w extub {right}"); + suffix = 'w'; + break; + case StorageSize.I16: + _writer.Indented($"{left} =w extsh {left}"); + _writer.Indented($"{right} =w extsh {right}"); + suffix = 'w'; + break; + case StorageSize.U16: + _writer.Indented($"{left} =w extuh {left}"); + _writer.Indented($"{right} =w extuh {right}"); + suffix = 'w'; + break; + case StorageSize.I32 or StorageSize.U32: + suffix = 'w'; + break; + case StorageSize.I64 or StorageSize.U64: + suffix = 'l'; + break; + default: + throw new NotSupportedException($"Unsupported type '{simpleType}' for binary operator '{op}'"); + } + + if (op is BoundBinaryOperator.Equal) + { + return "ceq" + suffix; + } + + if (op is BoundBinaryOperator.NotEqual) + { + return "cne" + suffix; + } + + string sign; + + if (simpleType is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.I64 }) + { + sign = "s"; + } + else if (simpleType is NubPrimitiveType { Kind: PrimitiveTypeKind.U8 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.U64 }) + { + sign = "u"; + } + else + { + throw new NotSupportedException($"Unsupported type '{type}' for binary operator '{op}'"); + } + + return op switch + { + BoundBinaryOperator.GreaterThan => 'c' + sign + "gt" + suffix, + BoundBinaryOperator.GreaterThanOrEqual => 'c' + sign + "ge" + suffix, + BoundBinaryOperator.LessThan => 'c' + sign + "lt" + suffix, + BoundBinaryOperator.LessThanOrEqual => 'c' + sign + "le" + suffix, + _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) + }; + } + + return op switch + { + BoundBinaryOperator.Plus => "add", + BoundBinaryOperator.Minus => "sub", + BoundBinaryOperator.Multiply => "mul", + BoundBinaryOperator.Divide => "div", + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }; + } + + private Val EmitExternFuncIdent(BoundExternFuncIdent externFuncIdent) + { + var func = _definitionTable.LookupExternFunc(externFuncIdent.Namespace, externFuncIdent.Name); + return new Val(ExternFuncName(func), externFuncIdent.Type, ValKind.Direct); + } + + private Val EmitLocalFuncIdent(BoundLocalFuncIdent localFuncIdent) + { + var func = _definitionTable.LookupExternFunc(localFuncIdent.Namespace, localFuncIdent.Name); + return new Val(ExternFuncName(func), localFuncIdent.Type, ValKind.Direct); + } + + private Val EmitVariableIdent(BoundVariableIdent variableIdent) + { + return _variables.Single(v => v.Name == variableIdent.Name).Val; + } + + private Val EmitLiteral(BoundLiteral literal) + { + switch (literal.Kind) + { + case LiteralKind.Integer: + { + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }) + { + var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture); + var bits = BitConverter.SingleToInt32Bits(value); + return new Val(bits.ToString(), literal.Type, ValKind.Direct); + } + + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }) + { + var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture); + var bits = BitConverter.DoubleToInt64Bits(value); + return new Val(bits.ToString(), literal.Type, ValKind.Direct); + } + + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 }) + { + return new Val(literal.Literal, literal.Type, ValKind.Direct); + } + + break; + } + case LiteralKind.Float: + { + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 }) + { + return new Val(literal.Literal.Split(".").First(), literal.Type, ValKind.Direct); + } + + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }) + { + var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture); + var bits = BitConverter.SingleToInt32Bits(value); + return new Val(bits.ToString(), literal.Type, ValKind.Direct); + } + + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }) + { + var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture); + var bits = BitConverter.DoubleToInt64Bits(value); + return new Val(bits.ToString(), literal.Type, ValKind.Direct); + } + + break; + } + case LiteralKind.String: + { + if (literal.Type is NubStringType) + { + var stringLiteral = new StringLiteral(literal.Literal, StringName()); + _stringLiterals.Add(stringLiteral); + return new Val(stringLiteral.Name, literal.Type, ValKind.Direct); + } + + if (literal.Type is NubCStringType) + { + var cStringLiteral = new CStringLiteral(literal.Literal, CStringName()); + _cStringLiterals.Add(cStringLiteral); + return new Val(cStringLiteral.Name, literal.Type, ValKind.Direct); + } + + break; + } + case LiteralKind.Bool: + { + if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }) + { + return new Val(bool.Parse(literal.Literal) ? "1" : "0", literal.Type, ValKind.Direct); + } + + break; + } + } + + throw new NotSupportedException($"Cannot create literal of kind '{literal.Kind}' for type {literal.Type}"); + } + + private Val EmitStructInitializer(BoundStructInitializer structInitializer, string? destination = null) + { + var @struct = _definitionTable.LookupStruct(structInitializer.StructType.Namespace, structInitializer.StructType.Name); + + if (destination == null) + { + destination = TmpName(); + var size = structInitializer.StructType.Size(_definitionTable); + _writer.Indented($"{destination} =l alloc8 {size}"); + } + + foreach (var field in @struct.Fields) + { + if (!structInitializer.Initializers.TryGetValue(field.Name, out var valueExpression)) + { + valueExpression = field.Value.Value; + } + + Debug.Assert(valueExpression != null); + + var offset = TmpName(); + _writer.Indented($"{offset} =l add {destination}, {OffsetOf(@struct, field.Name)}"); + EmitCopyIntoOrInitialize(valueExpression, offset); + } + + return new Val(destination, structInitializer.StructType, ValKind.Direct); + } + + private Val EmitUnaryExpression(BoundUnaryExpression unaryExpression) + { + var operand = EmitUnwrap(EmitExpression(unaryExpression.Operand)); + var outputName = TmpName(); + + switch (unaryExpression.Operator) + { + case BoundUnaryOperator.Negate: + { + switch (unaryExpression.Operand.Type) + { + case NubPrimitiveType { Kind: PrimitiveTypeKind.I64 }: + _writer.Indented($"{outputName} =l neg {operand}"); + return new Val(outputName, unaryExpression.Type, ValKind.Direct); + case NubPrimitiveType { Kind: PrimitiveTypeKind.I32 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I8 }: + _writer.Indented($"{outputName} =w neg {operand}"); + return new Val(outputName, unaryExpression.Type, ValKind.Direct); + case NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }: + _writer.Indented($"{outputName} =d neg {operand}"); + return new Val(outputName, unaryExpression.Type, ValKind.Direct); + case NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }: + _writer.Indented($"{outputName} =s neg {operand}"); + return new Val(outputName, unaryExpression.Type, ValKind.Direct); + } + + break; + } + case BoundUnaryOperator.Invert: + { + switch (unaryExpression.Operand.Type) + { + case NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }: + _writer.Indented($"{outputName} =w xor {operand}, 1"); + return new Val(outputName, unaryExpression.Type, ValKind.Direct); + } + + break; + } + default: + { + throw new ArgumentOutOfRangeException(); + } + } + + throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported"); + } + + private Val EmitStructFieldAccess(BoundStructFieldAccess structFieldAccess) + { + var target = EmitUnwrap(EmitExpression(structFieldAccess.Target)); + + var structDef = _definitionTable.LookupStruct(structFieldAccess.StructType.Namespace, structFieldAccess.StructType.Name); + var offset = OffsetOf(structDef, structFieldAccess.Field); + + var output = TmpName(); + _writer.Indented($"{output} =l add {target}, {offset}"); + + // If the accessed member is an inline struct, it will not be a pointer + if (structFieldAccess.Type is NubCustomType customType && customType.Kind(_definitionTable) == CustomTypeKind.Struct) + { + return new Val(output, structFieldAccess.Type, ValKind.Direct); + } + + return new Val(output, structFieldAccess.Type, ValKind.Pointer); + } + + private Val EmitTraitFuncAccess(BoundTraitFuncAccess traitFuncAccess) + { + throw new NotImplementedException(); + } + + private Val EmitTraitImplFuncAccess(BoundTraitImplFuncAccess traitImplFuncAccess) + { + var target = EmitExpression(traitImplFuncAccess.Target); + + var funcImpl = _definitionTable.LookupTraitFuncImpl(traitImplFuncAccess.Target.Type, traitImplFuncAccess.FuncName); + + var name = ImplFuncName(); + _implFunctions.TryAdd(funcImpl, name); + return new Val(name, traitImplFuncAccess.Type, ValKind.Direct, new MethodCallContext(target)); + } + + private Val EmitFuncCall(BoundFuncCall funcCall) + { + var expression = EmitExpression(funcCall.Expression); + var funcPointer = EmitUnwrap(expression); + + var parameterStrings = new List(); + + if (expression.FuncCallContext != null) + { + var thisArg = EmitUnwrap(expression.FuncCallContext.ThisArg); + parameterStrings.Add($"{FuncQBETypeName(expression.FuncCallContext.ThisArg.Type)} {thisArg}"); + } + + foreach (var parameter in funcCall.Parameters) + { + var copy = EmitCreateCopyOrInitialize(parameter); + parameterStrings.Add($"{FuncQBETypeName(parameter.Type)} {copy}"); + } + + if (funcCall.Type is NubVoidType) + { + _writer.Indented($"call {funcPointer}({string.Join(", ", parameterStrings)})"); + return new Val(string.Empty, funcCall.Type, ValKind.Direct); + } + else + { + var outputName = TmpName(); + _writer.Indented($"{outputName} {QBEAssign(funcCall.Type)} call {funcPointer}({string.Join(", ", parameterStrings)})"); + return new Val(outputName, funcCall.Type, ValKind.Direct); + } + } +} \ No newline at end of file diff --git a/src/compiler/NubLang/Generation/QBE/QBEGenerator.Statement.cs b/src/compiler/NubLang/Generation/QBE/QBEGenerator.Statement.cs new file mode 100644 index 0000000..dcb7ddf --- /dev/null +++ b/src/compiler/NubLang/Generation/QBE/QBEGenerator.Statement.cs @@ -0,0 +1,158 @@ +using System.Diagnostics; +using NubLang.Syntax.Binding.Node; + +namespace NubLang.Generation.QBE; + +public partial class QBEGenerator +{ + private void EmitStatement(BoundStatement statement) + { + _writer.WriteDebugLocation(statement); + + switch (statement) + { + case BoundAssignment assignment: + EmitAssignment(assignment); + break; + case BoundBreak: + EmitBreak(); + break; + case BoundContinue: + EmitContinue(); + break; + case BoundIf ifStatement: + EmitIf(ifStatement); + break; + case BoundReturn @return: + EmitReturn(@return); + break; + case BoundStatementExpression statementExpression: + EmitExpression(statementExpression.Expression); + break; + case BoundVariableDeclaration variableDeclaration: + EmitVariableDeclaration(variableDeclaration); + break; + case BoundWhile whileStatement: + EmitWhile(whileStatement); + break; + default: + throw new ArgumentOutOfRangeException(nameof(statement)); + } + } + + private void EmitAssignment(BoundAssignment assignment) + { + var destination = EmitExpression(assignment.Target); + Debug.Assert(destination.Kind == ValKind.Pointer); + EmitCopyIntoOrInitialize(assignment.Value, destination.Name); + } + + private void EmitBlock(BoundBlock block, List? variables = null) + { + _variableScopes.Push(_variables.Count); + if (variables != null) + { + foreach (var variable in variables) + { + _variables.Push(variable); + } + } + + foreach (var statement in block.Statements.Where(_ => _codeIsReachable)) + { + EmitStatement(statement); + } + + var count = _variableScopes.Pop(); + while (_variableScopes.Count > count) + { + _variableScopes.Pop(); + } + + _codeIsReachable = true; + } + + private void EmitBreak() + { + _writer.Indented($"jmp {_breakLabels.Peek()}"); + _codeIsReachable = false; + } + + private void EmitContinue() + { + _writer.Indented($"jmp {_continueLabels.Peek()}"); + _codeIsReachable = false; + } + + private void EmitIf(BoundIf ifStatement) + { + var trueLabel = LabelName(); + var falseLabel = LabelName(); + var endLabel = LabelName(); + + var result = EmitUnwrap(EmitExpression(ifStatement.Condition)); + _writer.Indented($"jnz {result}, {trueLabel}, {falseLabel}"); + _writer.WriteLine(trueLabel); + EmitBlock(ifStatement.Body); + _writer.Indented($"jmp {endLabel}"); + _writer.WriteLine(falseLabel); + if (ifStatement.Else.HasValue) + { + ifStatement.Else.Value.Match + ( + elseIfNode => EmitIf(elseIfNode), + elseNode => EmitBlock(elseNode) + ); + } + + _writer.WriteLine(endLabel); + } + + private void EmitReturn(BoundReturn @return) + { + if (@return.Value.HasValue) + { + var result = EmitUnwrap(EmitExpression(@return.Value.Value)); + _writer.Indented($"ret {result}"); + } + else + { + _writer.Indented("ret"); + } + } + + private void EmitVariableDeclaration(BoundVariableDeclaration variableDeclaration) + { + var name = $"%{variableDeclaration.Name}"; + _writer.Indented($"{name} =l alloc8 8"); + + if (variableDeclaration.Assignment.HasValue) + { + var value = EmitCreateCopyOrInitialize(variableDeclaration.Assignment.Value); + EmitStore(variableDeclaration.Assignment.Value.Type, value, name); + } + + _variables.Push(new Variable(variableDeclaration.Name, new Val(name, variableDeclaration.Type, ValKind.Pointer))); + } + + private void EmitWhile(BoundWhile whileStatement) + { + var conditionLabel = LabelName(); + var iterationLabel = LabelName(); + var endLabel = LabelName(); + + _breakLabels.Push(endLabel); + _continueLabels.Push(conditionLabel); + + _writer.Indented($"jmp {conditionLabel}"); + _writer.WriteLine(iterationLabel); + EmitBlock(whileStatement.Body); + _writer.WriteLine(conditionLabel); + var result = EmitUnwrap(EmitExpression(whileStatement.Condition)); + _writer.Indented($"jnz {result}, {iterationLabel}, {endLabel}"); + _writer.WriteLine(endLabel); + + _continueLabels.Pop(); + _breakLabels.Pop(); + } +} \ No newline at end of file diff --git a/src/compiler/NubLang/Generation/QBE/QBEGenerator.cs b/src/compiler/NubLang/Generation/QBE/QBEGenerator.cs index 1a9744e..bb4483b 100644 --- a/src/compiler/NubLang/Generation/QBE/QBEGenerator.cs +++ b/src/compiler/NubLang/Generation/QBE/QBEGenerator.cs @@ -1,41 +1,42 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Text; using NubLang.Syntax.Binding.Node; using NubLang.Syntax.Tokenization; namespace NubLang.Generation.QBE; -public static class QBEGenerator +public partial class QBEGenerator { - private static BoundSyntaxTree _syntaxTree = null!; - private static BoundDefinitionTable _definitionTable = null!; + private readonly BoundSyntaxTree _syntaxTree; + private readonly BoundDefinitionTable _definitionTable; + private readonly QBEWriter _writer; - private static QBEWriter _writer = null!; - private static List _cStringLiterals = []; - private static List _stringLiterals = []; - private static Stack _breakLabels = []; - private static Stack _continueLabels = []; - private static Queue<(BoundAnonymousFunc Func, string Name)> _anonymousFunctions = []; - private static Dictionary _implFunctions = []; - private static Stack _variables = []; - private static Stack _variableScopes = []; - private static int _tmpIndex; - private static int _labelIndex; - private static int _anonymousFuncIndex; - private static int _cStringLiteralIndex; - private static int _stringLiteralIndex; - private static int _implFuncNameIndex; - private static bool _codeIsReachable = true; + private List _cStringLiterals = []; + private List _stringLiterals = []; + private Stack _breakLabels = []; + private Stack _continueLabels = []; + private Queue<(BoundAnonymousFunc Func, string Name)> _anonymousFunctions = []; + private Dictionary _implFunctions = []; + private Stack _variables = []; + private Stack _variableScopes = []; + private int _tmpIndex; + private int _labelIndex; + private int _anonymousFuncIndex; + private int _cStringLiteralIndex; + private int _stringLiteralIndex; + private int _implFuncNameIndex; + private bool _codeIsReachable = true; - public static string Emit(BoundSyntaxTree syntaxTree, BoundDefinitionTable definitionTable, string file) + public QBEGenerator(BoundSyntaxTree syntaxTree, BoundDefinitionTable definitionTable, string file) { _syntaxTree = syntaxTree; _definitionTable = definitionTable; - _writer = new QBEWriter(file); + } + public string Emit() + { _cStringLiterals = []; _stringLiterals = []; _breakLabels = []; @@ -96,52 +97,31 @@ public static class QBEGenerator return _writer.ToString(); } - private static string TmpName() + private string QBEAssign(NubType type) { - return $"%t{++_tmpIndex}"; + return type switch + { + NubComplexType => "=l", + NubSimpleType simpleType => simpleType switch + { + NubFuncType or NubFuncType => "=l", + NubPrimitiveType primitiveType => primitiveType.Kind switch + { + PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 => "=l", + PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 => "=w", + PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 => "=w", + PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.Bool => "=w", + PrimitiveTypeKind.F64 => "=d", + PrimitiveTypeKind.F32 => "=s", + _ => throw new ArgumentOutOfRangeException() + }, + _ => throw new NotSupportedException($"'{type}' type cannot be used in variables") + }, + _ => throw new UnreachableException() + }; } - private static string LabelName() - { - return $"@l{++_labelIndex}"; - } - - private static string CStringName() - { - return $"$cstring{++_cStringLiteralIndex}"; - } - - private static string StringName() - { - return $"$string{++_stringLiteralIndex}"; - } - - private static string LocalFuncName(BoundLocalFunc funcDef) - { - return funcDef.Exported ? $"${funcDef.Name}" : $"${funcDef.Namespace}_{funcDef.Name}"; - } - - private static string ExternFuncName(BoundExternFunc funcDef) - { - return $"${funcDef.CallName}"; - } - - private static string ImplFuncName() - { - return $"$impl{++_implFuncNameIndex}"; - } - - private static string CustomTypeName(NubCustomType customType) - { - return CustomTypeName(customType.Namespace, customType.Name); - } - - private static string CustomTypeName(string @namespace, string name) - { - return $":{@namespace}_{name}"; - } - - private static void EmitStore(NubType type, string value, string destination) + private void EmitStore(NubType type, string value, string destination) { string store; @@ -166,7 +146,7 @@ public static class QBEGenerator _writer.Indented($"{store} {value}, {destination}"); } - private static Val EmitLoad(NubType type, string from) + private Val EmitLoad(NubType type, string from) { string load; @@ -197,12 +177,12 @@ public static class QBEGenerator return new Val(into, type, ValKind.Direct); } - private static void EmitMemcpy(string source, string destination, string length) + private void EmitMemcpy(string source, string destination, string length) { _writer.Indented($"call $nub_memcpy(l {source}, l {destination}, l {length})"); } - private static string EmitArraySizeInBytes(NubArrayType type, string array) + private string EmitArraySizeInBytes(NubArrayType type, string array) { var size = TmpName(); _writer.Indented($"{size} =l loadl {array}"); @@ -211,7 +191,7 @@ public static class QBEGenerator return size; } - private static string EmitCStringSizeInBytes(string cstring) + private string EmitCStringSizeInBytes(string cstring) { var size = TmpName(); _writer.Indented($"{size} =l call $nub_cstring_length(l {cstring})"); @@ -219,7 +199,7 @@ public static class QBEGenerator return size; } - private static string EmitStringSizeInBytes(string nubstring) + private string EmitStringSizeInBytes(string nubstring) { var size = TmpName(); _writer.Indented($"{size} =l loadl {nubstring}"); @@ -227,7 +207,7 @@ public static class QBEGenerator return size; } - private static bool EmitTryMoveInto(BoundExpression source, string destinationPointer) + private bool EmitTryMoveInto(BoundExpression source, string destinationPointer) { switch (source) { @@ -254,7 +234,7 @@ public static class QBEGenerator return false; } - private static void EmitCopyIntoOrInitialize(BoundExpression source, string destinationPointer) + private void EmitCopyIntoOrInitialize(BoundExpression 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)) @@ -292,7 +272,7 @@ public static class QBEGenerator } } - private static bool EmitTryCreateWithoutCopy(BoundExpression source, [NotNullWhen(true)] out string? destination) + private bool EmitTryCreateWithoutCopy(BoundExpression source, [NotNullWhen(true)] out string? destination) { switch (source) { @@ -309,7 +289,7 @@ public static class QBEGenerator return false; } - private static string EmitCreateCopyOrInitialize(BoundExpression source) + private string EmitCreateCopyOrInitialize(BoundExpression 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)) @@ -340,32 +320,8 @@ public static class QBEGenerator return destination; } - private static string QBEAssign(NubType type) - { - return type switch - { - NubComplexType => "=l", - NubSimpleType simpleType => simpleType switch - { - NubFuncType or NubFuncType => "=l", - NubPrimitiveType primitiveType => primitiveType.Kind switch - { - PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 => "=l", - PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 => "=w", - PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 => "=w", - PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.Bool => "=w", - PrimitiveTypeKind.F64 => "=d", - PrimitiveTypeKind.F32 => "=s", - _ => throw new ArgumentOutOfRangeException() - }, - _ => throw new NotSupportedException($"'{type}' type cannot be used in variables") - }, - _ => throw new UnreachableException() - }; - } - // Utility to create QBE type names for function parameters and return types - private static string FuncQBETypeName(NubType type) + private string FuncQBETypeName(NubType type) { if (type.IsSimpleType(out var simpleType, out var complexType)) { @@ -391,7 +347,7 @@ public static class QBEGenerator return "l"; } - private static void EmitFuncDefinition(BoundNode debugNode, string name, List parameters, NubType returnType, BoundBlock body, bool exported) + private void EmitFuncDefinition(BoundNode debugNode, string name, List parameters, NubType returnType, BoundBlock body, bool exported) { _variables.Clear(); _variableScopes.Clear(); @@ -435,7 +391,7 @@ public static class QBEGenerator _writer.EndFunction(); } - private static void EmitStructDefinition(BoundStruct structDef) + private void EmitStructDefinition(BoundStruct structDef) { _writer.WriteLine($"type {CustomTypeName(structDef.Namespace, structDef.Name)} = {{ "); @@ -481,7 +437,7 @@ public static class QBEGenerator } } - private static void EmitTraitVTable(BoundTrait traitDef) + private void EmitTraitVTable(BoundTrait traitDef) { _writer.WriteLine($"type {CustomTypeName(traitDef.Namespace, traitDef.Name)} = {{"); @@ -493,615 +449,7 @@ public static class QBEGenerator _writer.WriteLine("}"); } - private static void EmitStatement(BoundStatement statement) - { - _writer.WriteDebugLocation(statement); - - switch (statement) - { - case BoundAssignment assignment: - EmitAssignment(assignment); - break; - case BoundBreak: - EmitBreak(); - break; - case BoundContinue: - EmitContinue(); - break; - case BoundIf ifStatement: - EmitIf(ifStatement); - break; - case BoundReturn @return: - EmitReturn(@return); - break; - case BoundStatementExpression statementExpression: - EmitExpression(statementExpression.Expression); - break; - case BoundVariableDeclaration variableDeclaration: - EmitVariableDeclaration(variableDeclaration); - break; - case BoundWhile whileStatement: - EmitWhile(whileStatement); - break; - default: - throw new ArgumentOutOfRangeException(nameof(statement)); - } - } - - private static void EmitAssignment(BoundAssignment assignment) - { - var destination = EmitExpression(assignment.Target); - Debug.Assert(destination.Kind == ValKind.Pointer); - EmitCopyIntoOrInitialize(assignment.Value, destination.Name); - } - - private static void EmitBlock(BoundBlock block, List? variables = null) - { - _variableScopes.Push(_variables.Count); - if (variables != null) - { - foreach (var variable in variables) - { - _variables.Push(variable); - } - } - - foreach (var statement in block.Statements.Where(_ => _codeIsReachable)) - { - EmitStatement(statement); - } - - var count = _variableScopes.Pop(); - while (_variableScopes.Count > count) - { - _variableScopes.Pop(); - } - - _codeIsReachable = true; - } - - private static void EmitBreak() - { - _writer.Indented($"jmp {_breakLabels.Peek()}"); - _codeIsReachable = false; - } - - private static void EmitContinue() - { - _writer.Indented($"jmp {_continueLabels.Peek()}"); - _codeIsReachable = false; - } - - private static void EmitIf(BoundIf ifStatement) - { - var trueLabel = LabelName(); - var falseLabel = LabelName(); - var endLabel = LabelName(); - - var result = EmitUnwrap(EmitExpression(ifStatement.Condition)); - _writer.Indented($"jnz {result}, {trueLabel}, {falseLabel}"); - _writer.WriteLine(trueLabel); - EmitBlock(ifStatement.Body); - _writer.Indented($"jmp {endLabel}"); - _writer.WriteLine(falseLabel); - if (ifStatement.Else.HasValue) - { - ifStatement.Else.Value.Match - ( - elseIfNode => EmitIf(elseIfNode), - elseNode => EmitBlock(elseNode) - ); - } - - _writer.WriteLine(endLabel); - } - - private static void EmitReturn(BoundReturn @return) - { - if (@return.Value.HasValue) - { - var result = EmitUnwrap(EmitExpression(@return.Value.Value)); - _writer.Indented($"ret {result}"); - } - else - { - _writer.Indented("ret"); - } - } - - private static void EmitVariableDeclaration(BoundVariableDeclaration variableDeclaration) - { - var name = $"%{variableDeclaration.Name}"; - _writer.Indented($"{name} =l alloc8 8"); - - if (variableDeclaration.Assignment.HasValue) - { - var value = EmitCreateCopyOrInitialize(variableDeclaration.Assignment.Value); - EmitStore(variableDeclaration.Assignment.Value.Type, value, name); - } - - _variables.Push(new Variable(variableDeclaration.Name, new Val(name, variableDeclaration.Type, ValKind.Pointer))); - } - - private static void EmitWhile(BoundWhile whileStatement) - { - var conditionLabel = LabelName(); - var iterationLabel = LabelName(); - var endLabel = LabelName(); - - _breakLabels.Push(endLabel); - _continueLabels.Push(conditionLabel); - - _writer.Indented($"jmp {conditionLabel}"); - _writer.WriteLine(iterationLabel); - EmitBlock(whileStatement.Body); - _writer.WriteLine(conditionLabel); - var result = EmitUnwrap(EmitExpression(whileStatement.Condition)); - _writer.Indented($"jnz {result}, {iterationLabel}, {endLabel}"); - _writer.WriteLine(endLabel); - - _continueLabels.Pop(); - _breakLabels.Pop(); - } - - private static Val EmitExpression(BoundExpression expression) - { - _writer.WriteDebugLocation(expression); - return expression switch - { - BoundArrayInitializer arrayInitializer => EmitArrayInitializer(arrayInitializer), - BoundStructInitializer structInitializer => EmitStructInitializer(structInitializer), - BoundAddressOf addressOf => EmitAddressOf(addressOf), - BoundDereference dereference => EmitDereference(dereference), - BoundAnonymousFunc anonymousFunc => EmitAnonymousFunc(anonymousFunc), - BoundBinaryExpression binaryExpression => EmitBinaryExpression(binaryExpression), - BoundFuncCall funcCallExpression => EmitFuncCall(funcCallExpression), - BoundExternFuncIdent externFuncIdent => EmitExternFuncIdent(externFuncIdent), - BoundLocalFuncIdent localFuncIdent => EmitLocalFuncIdent(localFuncIdent), - BoundVariableIdent variableIdent => EmitVariableIdent(variableIdent), - BoundLiteral literal => EmitLiteral(literal), - BoundUnaryExpression unaryExpression => EmitUnaryExpression(unaryExpression), - BoundStructFieldAccess structFieldAccess => EmitStructFieldAccess(structFieldAccess), - BoundTraitFuncAccess traitFuncAccess => EmitTraitFuncAccess(traitFuncAccess), - BoundTraitImplFuncAccess traitImplFuncAccess => EmitTraitImplFuncAccess(traitImplFuncAccess), - BoundArrayIndexAccess arrayIndex => EmitArrayIndexAccess(arrayIndex), - _ => throw new ArgumentOutOfRangeException(nameof(expression)) - }; - } - - private static Val EmitAnonymousFunc(BoundAnonymousFunc anonymousFunc) - { - var name = $"$anon_func{++_anonymousFuncIndex}"; - _anonymousFunctions.Enqueue((anonymousFunc, name)); - return new Val(name, anonymousFunc.Type, ValKind.Direct); - } - - private static Val EmitArrayIndexAccess(BoundArrayIndexAccess arrayIndexAccess) - { - var array = EmitUnwrap(EmitExpression(arrayIndexAccess.Target)); - var index = EmitUnwrap(EmitExpression(arrayIndexAccess.Index)); - - EmitArrayBoundsCheck(array, index); - - var elementType = ((NubArrayType)arrayIndexAccess.Target.Type).ElementType; - - var pointer = TmpName(); - _writer.Indented($"{pointer} =l mul {index}, {elementType.Size(_definitionTable)}"); - _writer.Indented($"{pointer} =l add {pointer}, 8"); - _writer.Indented($"{pointer} =l add {array}, {pointer}"); - return new Val(pointer, arrayIndexAccess.Type, ValKind.Pointer); - } - - private static void EmitArrayBoundsCheck(string array, string index) - { - var count = TmpName(); - _writer.Indented($"{count} =l loadl {array}"); - - var isNegative = TmpName(); - _writer.Indented($"{isNegative} =w csltl {index}, 0"); - - var isOob = TmpName(); - _writer.Indented($"{isOob} =w csgel {index}, {count}"); - - var anyOob = TmpName(); - _writer.Indented($"{anyOob} =w or {isNegative}, {isOob}"); - - var oobLabel = LabelName(); - var notOobLabel = LabelName(); - _writer.Indented($"jnz {anyOob}, {oobLabel}, {notOobLabel}"); - - _writer.Indented(oobLabel); - _writer.Indented($"call $nub_panic_array_oob()"); - - _writer.Indented(notOobLabel); - } - - private static Val EmitArrayInitializer(BoundArrayInitializer arrayInitializer) - { - var capacity = EmitUnwrap(EmitExpression(arrayInitializer.Capacity)); - var elementSize = arrayInitializer.ElementType.Size(_definitionTable); - - var capacityInBytes = TmpName(); - _writer.Indented($"{capacityInBytes} =l mul {capacity}, {elementSize}"); - var totalSize = TmpName(); - _writer.Indented($"{totalSize} =l add {capacityInBytes}, 8"); - - var arrayPointer = TmpName(); - _writer.Indented($"{arrayPointer} =l alloc8 {totalSize}"); - _writer.Indented($"storel {capacity}, {arrayPointer}"); - - var dataPointer = TmpName(); - _writer.Indented($"{dataPointer} =l add {arrayPointer}, 8"); - _writer.Indented($"call $nub_memset(l {dataPointer}, w 0, l {capacityInBytes})"); - - return new Val(arrayPointer, arrayInitializer.Type, ValKind.Direct); - } - - private static Val EmitDereference(BoundDereference dereference) - { - return EmitLoad(dereference.Type, EmitUnwrap(EmitExpression(dereference.Expression))); - } - - private static Val EmitAddressOf(BoundAddressOf addressOf) - { - var value = EmitExpression(addressOf.Expression); - if (value.Kind != ValKind.Pointer) - { - throw new UnreachableException("Tried to take address of non-pointer type. This should have been causht in the type checker"); - } - - return new Val(value.Name, addressOf.Type, ValKind.Direct); - } - - private static Val EmitBinaryExpression(BoundBinaryExpression binaryExpression) - { - var left = EmitUnwrap(EmitExpression(binaryExpression.Left)); - var right = EmitUnwrap(EmitExpression(binaryExpression.Right)); - - var outputName = TmpName(); - - var instruction = EmitBinaryInstructionFor(binaryExpression.Operator, binaryExpression.Left.Type, left, right); - - _writer.Indented($"{outputName} {QBEAssign(binaryExpression.Left.Type)} {instruction} {left}, {right}"); - return new Val(outputName, binaryExpression.Type, ValKind.Direct); - } - - private static string EmitBinaryInstructionFor(BoundBinaryOperator op, NubType type, string left, string right) - { - if (op is - BoundBinaryOperator.Equal or - BoundBinaryOperator.NotEqual or - BoundBinaryOperator.GreaterThan or - BoundBinaryOperator.GreaterThanOrEqual or - BoundBinaryOperator.LessThan or - BoundBinaryOperator.LessThanOrEqual) - { - char suffix; - - if (!type.IsSimpleType(out var simpleType, out _)) - { - throw new NotSupportedException("Binary operations is only supported for simple types."); - } - - switch (simpleType.StorageSize) - { - case StorageSize.I8: - _writer.Indented($"{left} =w extsb {left}"); - _writer.Indented($"{right} =w extsb {right}"); - suffix = 'w'; - break; - case StorageSize.U8: - _writer.Indented($"{left} =w extub {left}"); - _writer.Indented($"{right} =w extub {right}"); - suffix = 'w'; - break; - case StorageSize.I16: - _writer.Indented($"{left} =w extsh {left}"); - _writer.Indented($"{right} =w extsh {right}"); - suffix = 'w'; - break; - case StorageSize.U16: - _writer.Indented($"{left} =w extuh {left}"); - _writer.Indented($"{right} =w extuh {right}"); - suffix = 'w'; - break; - case StorageSize.I32 or StorageSize.U32: - suffix = 'w'; - break; - case StorageSize.I64 or StorageSize.U64: - suffix = 'l'; - break; - default: - throw new NotSupportedException($"Unsupported type '{simpleType}' for binary operator '{op}'"); - } - - if (op is BoundBinaryOperator.Equal) - { - return "ceq" + suffix; - } - - if (op is BoundBinaryOperator.NotEqual) - { - return "cne" + suffix; - } - - string sign; - - if (simpleType is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.I64 }) - { - sign = "s"; - } - else if (simpleType is NubPrimitiveType { Kind: PrimitiveTypeKind.U8 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.U64 }) - { - sign = "u"; - } - else - { - throw new NotSupportedException($"Unsupported type '{type}' for binary operator '{op}'"); - } - - return op switch - { - BoundBinaryOperator.GreaterThan => 'c' + sign + "gt" + suffix, - BoundBinaryOperator.GreaterThanOrEqual => 'c' + sign + "ge" + suffix, - BoundBinaryOperator.LessThan => 'c' + sign + "lt" + suffix, - BoundBinaryOperator.LessThanOrEqual => 'c' + sign + "le" + suffix, - _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) - }; - } - - return op switch - { - BoundBinaryOperator.Plus => "add", - BoundBinaryOperator.Minus => "sub", - BoundBinaryOperator.Multiply => "mul", - BoundBinaryOperator.Divide => "div", - _ => throw new ArgumentOutOfRangeException(nameof(op)) - }; - } - - private static Val EmitExternFuncIdent(BoundExternFuncIdent externFuncIdent) - { - var func = _definitionTable.LookupExternFunc(externFuncIdent.Namespace, externFuncIdent.Name); - return new Val(ExternFuncName(func), externFuncIdent.Type, ValKind.Direct); - } - - private static Val EmitLocalFuncIdent(BoundLocalFuncIdent localFuncIdent) - { - var func = _definitionTable.LookupExternFunc(localFuncIdent.Namespace, localFuncIdent.Name); - return new Val(ExternFuncName(func), localFuncIdent.Type, ValKind.Direct); - } - - private static Val EmitVariableIdent(BoundVariableIdent variableIdent) - { - return _variables.Single(v => v.Name == variableIdent.Name).Val; - } - - private static Val EmitLiteral(BoundLiteral literal) - { - switch (literal.Kind) - { - case LiteralKind.Integer: - { - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }) - { - var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture); - var bits = BitConverter.SingleToInt32Bits(value); - return new Val(bits.ToString(), literal.Type, ValKind.Direct); - } - - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }) - { - var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture); - var bits = BitConverter.DoubleToInt64Bits(value); - return new Val(bits.ToString(), literal.Type, ValKind.Direct); - } - - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 }) - { - return new Val(literal.Literal, literal.Type, ValKind.Direct); - } - - break; - } - case LiteralKind.Float: - { - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 }) - { - return new Val(literal.Literal.Split(".").First(), literal.Type, ValKind.Direct); - } - - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }) - { - var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture); - var bits = BitConverter.SingleToInt32Bits(value); - return new Val(bits.ToString(), literal.Type, ValKind.Direct); - } - - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }) - { - var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture); - var bits = BitConverter.DoubleToInt64Bits(value); - return new Val(bits.ToString(), literal.Type, ValKind.Direct); - } - - break; - } - case LiteralKind.String: - { - if (literal.Type is NubStringType) - { - var stringLiteral = new StringLiteral(literal.Literal, StringName()); - _stringLiterals.Add(stringLiteral); - return new Val(stringLiteral.Name, literal.Type, ValKind.Direct); - } - - if (literal.Type is NubCStringType) - { - var cStringLiteral = new CStringLiteral(literal.Literal, CStringName()); - _cStringLiterals.Add(cStringLiteral); - return new Val(cStringLiteral.Name, literal.Type, ValKind.Direct); - } - - break; - } - case LiteralKind.Bool: - { - if (literal.Type is NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }) - { - return new Val(bool.Parse(literal.Literal) ? "1" : "0", literal.Type, ValKind.Direct); - } - - break; - } - } - - throw new NotSupportedException($"Cannot create literal of kind '{literal.Kind}' for type {literal.Type}"); - } - - private static Val EmitStructInitializer(BoundStructInitializer structInitializer, string? destination = null) - { - var @struct = _definitionTable.LookupStruct(structInitializer.StructType.Namespace, structInitializer.StructType.Name); - - if (destination == null) - { - destination = TmpName(); - var size = structInitializer.StructType.Size(_definitionTable); - _writer.Indented($"{destination} =l alloc8 {size}"); - } - - foreach (var field in @struct.Fields) - { - if (!structInitializer.Initializers.TryGetValue(field.Name, out var valueExpression)) - { - valueExpression = field.Value.Value; - } - - Debug.Assert(valueExpression != null); - - var offset = TmpName(); - _writer.Indented($"{offset} =l add {destination}, {OffsetOf(@struct, field.Name)}"); - EmitCopyIntoOrInitialize(valueExpression, offset); - } - - return new Val(destination, structInitializer.StructType, ValKind.Direct); - } - - private static Val EmitUnaryExpression(BoundUnaryExpression unaryExpression) - { - var operand = EmitUnwrap(EmitExpression(unaryExpression.Operand)); - var outputName = TmpName(); - - switch (unaryExpression.Operator) - { - case BoundUnaryOperator.Negate: - { - switch (unaryExpression.Operand.Type) - { - case NubPrimitiveType { Kind: PrimitiveTypeKind.I64 }: - _writer.Indented($"{outputName} =l neg {operand}"); - return new Val(outputName, unaryExpression.Type, ValKind.Direct); - case NubPrimitiveType { Kind: PrimitiveTypeKind.I32 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I8 }: - _writer.Indented($"{outputName} =w neg {operand}"); - return new Val(outputName, unaryExpression.Type, ValKind.Direct); - case NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }: - _writer.Indented($"{outputName} =d neg {operand}"); - return new Val(outputName, unaryExpression.Type, ValKind.Direct); - case NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }: - _writer.Indented($"{outputName} =s neg {operand}"); - return new Val(outputName, unaryExpression.Type, ValKind.Direct); - } - - break; - } - case BoundUnaryOperator.Invert: - { - switch (unaryExpression.Operand.Type) - { - case NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }: - _writer.Indented($"{outputName} =w xor {operand}, 1"); - return new Val(outputName, unaryExpression.Type, ValKind.Direct); - } - - break; - } - default: - { - throw new ArgumentOutOfRangeException(); - } - } - - throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported"); - } - - private static Val EmitStructFieldAccess(BoundStructFieldAccess structFieldAccess) - { - var target = EmitUnwrap(EmitExpression(structFieldAccess.Target)); - - var structDef = _definitionTable.LookupStruct(structFieldAccess.StructType.Namespace, structFieldAccess.StructType.Name); - var offset = OffsetOf(structDef, structFieldAccess.Field); - - var output = TmpName(); - _writer.Indented($"{output} =l add {target}, {offset}"); - - // If the accessed member is an inline struct, it will not be a pointer - if (structFieldAccess.Type is NubCustomType customType && customType.Kind(_definitionTable) == CustomTypeKind.Struct) - { - return new Val(output, structFieldAccess.Type, ValKind.Direct); - } - - return new Val(output, structFieldAccess.Type, ValKind.Pointer); - } - - private static Val EmitTraitFuncAccess(BoundTraitFuncAccess traitFuncAccess) - { - throw new NotImplementedException(); - } - - private static Val EmitTraitImplFuncAccess(BoundTraitImplFuncAccess traitImplFuncAccess) - { - var target = EmitExpression(traitImplFuncAccess.Target); - - var funcImpl = _definitionTable.LookupTraitFuncImpl(traitImplFuncAccess.Target.Type, traitImplFuncAccess.FuncName); - - var name = ImplFuncName(); - _implFunctions.TryAdd(funcImpl, name); - return new Val(name, traitImplFuncAccess.Type, ValKind.Direct, new MethodCallContext(target)); - } - - private static Val EmitFuncCall(BoundFuncCall funcCall) - { - var expression = EmitExpression(funcCall.Expression); - var funcPointer = EmitUnwrap(expression); - - var parameterStrings = new List(); - - if (expression.FuncCallContext != null) - { - var thisArg = EmitUnwrap(expression.FuncCallContext.ThisArg); - parameterStrings.Add($"{FuncQBETypeName(expression.FuncCallContext.ThisArg.Type)} {thisArg}"); - } - - foreach (var parameter in funcCall.Parameters) - { - var copy = EmitCreateCopyOrInitialize(parameter); - parameterStrings.Add($"{FuncQBETypeName(parameter.Type)} {copy}"); - } - - if (funcCall.Type is NubVoidType) - { - _writer.Indented($"call {funcPointer}({string.Join(", ", parameterStrings)})"); - return new Val(string.Empty, funcCall.Type, ValKind.Direct); - } - else - { - var outputName = TmpName(); - _writer.Indented($"{outputName} {QBEAssign(funcCall.Type)} call {funcPointer}({string.Join(", ", parameterStrings)})"); - return new Val(outputName, funcCall.Type, ValKind.Direct); - } - } - - private static string EmitUnwrap(Val val) + private string EmitUnwrap(Val val) { return val.Kind switch { @@ -1111,7 +459,7 @@ public static class QBEGenerator }; } - private static int OffsetOf(BoundStruct structDefinition, string member) + private int OffsetOf(BoundStruct structDefinition, string member) { var offset = 0; @@ -1130,6 +478,55 @@ public static class QBEGenerator 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(BoundLocalFunc funcDef) + { + return funcDef.Exported ? $"${funcDef.Name}" : $"${funcDef.Namespace}_{funcDef.Name}"; + } + + private string ExternFuncName(BoundExternFunc funcDef) + { + return $"${funcDef.CallName}"; + } + + private string ImplFuncName() + { + return $"$impl{++_implFuncNameIndex}"; + } + + private string CustomTypeName(NubCustomType customType) + { + return CustomTypeName(customType.Namespace, customType.Name); + } + + private string CustomTypeName(string @namespace, string name) + { + return $":{@namespace}_{name}"; + } + + #endregion } internal class StringLiteral(string value, string name)