using System.Diagnostics; using System.Text; using NubLang.Ast; using NubLang.Modules; using NubLang.Types; namespace NubLang.Generation; public class LlvmGenerator { private string _module = string.Empty; private int _tmpIndex; private int _labelIndex; private List<(string Name, int Size, string Text)> _stringLiterals = []; private Stack<(string breakLabel, string continueLabel)> _loopStack = []; public string Emit(List topLevelNodes, ModuleRepository repository) { _stringLiterals = []; _loopStack = []; var writer = new IndentedTextWriter(); _module = topLevelNodes.OfType().First().NameToken.Value; writer.WriteLine($$""" ; Module {{_module}} target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" %nub.slice = type { i64, ptr } %nub.string = type { i64, ptr } declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1) """); var declaredExternFunctions = new HashSet(); writer.WriteLine("; == Function declarations =="); foreach (var module in repository.GetAll()) { writer.WriteLine($"; ==== {module.Name} ===="); foreach (var prototype in module.FunctionPrototypes) { // note(nub31): If we are in the current module and the function has a body, we skip it to prevent duplicate definition if (module.Name == _module && topLevelNodes.OfType().First(x => x.NameToken.Value == prototype.NameToken.Value).Body != null) { continue; } if (prototype.ExternSymbolToken != null && !declaredExternFunctions.Add(prototype.ExternSymbolToken.Value)) { continue; } writer.WriteLine($"declare {CreateFunctionPrototype(prototype, module.Name)}"); } } writer.WriteLine(); writer.WriteLine("; == Struct declarations =="); foreach (var module in repository.GetAll()) { writer.WriteLine($"; ==== {module.Name} ===="); foreach (var structType in module.StructTypes) { var fieldTypes = structType.Fields.Select(x => MapType(x.Type)); writer.WriteLine($"%{StructName(structType.Module, structType.Name)} = type {{ {string.Join(", ", fieldTypes)} }}"); } } writer.WriteLine(); foreach (var structNode in topLevelNodes.OfType()) { _tmpIndex = 0; _labelIndex = 0; writer.WriteLine("; == Struct constructors =="); writer.WriteLine($"define void @{StructName(structNode)}.new(ptr %self) {{"); using (writer.Indent()) { foreach (var field in structNode.Fields) { if (field.Value != null) { var index = structNode.StructType.GetFieldIndex(field.NameToken.Value); var fieldTmp = NewTmp($"struct.field.{field.NameToken.Value}"); writer.WriteLine($"{fieldTmp} = getelementptr %{StructName(structNode)}, ptr %self, i32 0, i32 {index}"); EmitExpressionInto(writer, field.Value, fieldTmp); } } writer.WriteLine("ret void"); } writer.WriteLine("}"); writer.WriteLine(); } writer.WriteLine("; == Function definitions =="); foreach (var funcNode in topLevelNodes.OfType()) { if (funcNode.Body == null) continue; _tmpIndex = 0; _labelIndex = 0; writer.WriteLine($"define {CreateFunctionPrototype(funcNode.Prototype, _module)} {{"); using (writer.Indent()) { EmitBlock(writer, funcNode.Body); // note(nub31): Implicit return for void functions if (funcNode.Prototype.ReturnType is NubVoidType) { writer.WriteLine("ret void"); } } writer.WriteLine("}"); writer.WriteLine(); } writer.WriteLine("; == String literals =="); foreach (var stringLiteral in _stringLiterals) { writer.WriteLine($"{stringLiteral.Name} = private unnamed_addr constant [{stringLiteral.Size} x i8] c\"{stringLiteral.Text}\\00\", align 1"); } return writer.ToString(); } private string CreateFunctionPrototype(FuncPrototypeNode prototypeNode, string module) { var parameterStrings = new List(); foreach (var parameter in prototypeNode.Parameters) { var llvmType = MapType(parameter.Type); var name = parameter.NameToken.Value; if (parameter.Type is NubStructType) { var alignment = parameter.Type.GetAlignment(); parameterStrings.Add($"{llvmType}* byval({llvmType}) align {alignment} %{name}"); } else { parameterStrings.Add($"{llvmType} %{name}"); } } var funcName = FuncName(module, prototypeNode.NameToken.Value, prototypeNode.ExternSymbolToken?.Value); var returnType = MapType(prototypeNode.ReturnType); if (prototypeNode.ReturnType is NubStructType) { var alignment = prototypeNode.ReturnType.GetAlignment(); var parameters = ""; if (parameterStrings.Count != 0) { parameters = ", " + string.Join(", ", parameterStrings); } return $"ccc void @{funcName}({returnType}* sret({returnType}) align {alignment}{parameters})"; } else { return $"ccc {returnType} @{funcName}({string.Join(", ", parameterStrings)})"; } } private void EmitStatement(IndentedTextWriter writer, StatementNode statementNode) { switch (statementNode) { case AssignmentNode assignmentNode: EmitAssignment(writer, assignmentNode); break; case BlockNode blockNode: EmitBlock(writer, blockNode); break; case BreakNode: EmitBreak(writer); break; case ContinueNode: EmitContinue(writer); break; case DeferNode deferNode: EmitDefer(writer, deferNode); break; case ForConstArrayNode forConstArrayNode: EmitForConstArray(writer, forConstArrayNode); break; case ForSliceNode forSliceNode: EmitForSlice(writer, forSliceNode); break; case IfNode ifNode: EmitIf(writer, ifNode); break; case ReturnNode returnNode: EmitReturn(writer, returnNode); break; case StatementFuncCallNode statementFuncCallNode: EmitStatementFuncCall(writer, statementFuncCallNode); break; case VariableDeclarationNode variableDeclarationNode: EmitVariableDeclaration(writer, variableDeclarationNode); break; case WhileNode whileNode: EmitWhile(writer, whileNode); break; default: throw new ArgumentOutOfRangeException(nameof(statementNode)); } } private void EmitAssignment(IndentedTextWriter writer, AssignmentNode assignmentNode) { var target = EmitExpression(writer, assignmentNode.Target); var value = Unwrap(writer, EmitExpression(writer, assignmentNode.Value)); writer.WriteLine($"store {MapType(assignmentNode.Value.Type)} {value}, ptr {target.Ident}"); } private void EmitBlock(IndentedTextWriter writer, BlockNode blockNode) { foreach (var statementNode in blockNode.Statements) { EmitStatement(writer, statementNode); } } private void EmitBreak(IndentedTextWriter writer) { var (breakLabel, _) = _loopStack.Peek(); writer.WriteLine($"br label %{breakLabel}"); } private void EmitContinue(IndentedTextWriter writer) { var (_, continueLabel) = _loopStack.Peek(); writer.WriteLine($"br label %{continueLabel}"); } private void EmitDefer(IndentedTextWriter writer, DeferNode deferNode) { throw new NotImplementedException(); } private void EmitForConstArray(IndentedTextWriter writer, ForConstArrayNode forConstArrayNode) { throw new NotImplementedException(); } private void EmitForSlice(IndentedTextWriter writer, ForSliceNode forSliceNode) { throw new NotImplementedException(); } private void EmitIf(IndentedTextWriter writer, IfNode ifNode) { var endLabel = NewLabel("if.end"); EmitIf(writer, ifNode, endLabel); writer.WriteLine($"{endLabel}:"); } private void EmitIf(IndentedTextWriter writer, IfNode ifNode, string endLabel) { var condition = Unwrap(writer, EmitExpression(writer, ifNode.Condition)); var thenLabel = NewLabel("if.then"); var elseLabel = ifNode.Else.HasValue ? NewLabel("if.else") : endLabel; writer.WriteLine($"br i1 {condition}, label %{thenLabel}, label %{elseLabel}"); writer.WriteLine($"{thenLabel}:"); using (writer.Indent()) { EmitBlock(writer, ifNode.Body); writer.WriteLine($"br label %{endLabel}"); } if (!ifNode.Else.HasValue) return; writer.WriteLine($"{elseLabel}:"); using (writer.Indent()) { ifNode.Else.Value.Match( nestedElseIf => EmitIf(writer, nestedElseIf, endLabel), finalElse => { EmitBlock(writer, finalElse); writer.WriteLine($"br label %{endLabel}"); } ); } } private void EmitReturn(IndentedTextWriter writer, ReturnNode returnNode) { if (returnNode.Value != null) { var returnValue = Unwrap(writer, EmitExpression(writer, returnNode.Value)); writer.WriteLine($"ret {MapType(returnNode.Value.Type)} {returnValue}"); } else { writer.WriteLine("ret void"); } } private void EmitStatementFuncCall(IndentedTextWriter writer, StatementFuncCallNode statementFuncCallNode) { EmitFuncCall(writer, statementFuncCallNode.FuncCall); } private void EmitVariableDeclaration(IndentedTextWriter writer, VariableDeclarationNode variableDeclarationNode) { writer.WriteLine($"%{variableDeclarationNode.NameToken.Value} = alloca {MapType(variableDeclarationNode.Type)}"); if (variableDeclarationNode.Assignment != null) { EmitExpressionInto(writer, variableDeclarationNode.Assignment, $"%{variableDeclarationNode.NameToken.Value}"); } } private void EmitWhile(IndentedTextWriter writer, WhileNode whileNode) { var conditionLabel = NewLabel("while.condition"); var bodyLabel = NewLabel("while.body"); var endLabel = NewLabel("while.end"); _loopStack.Push((endLabel, conditionLabel)); writer.WriteLine($"br label %{conditionLabel}"); writer.WriteLine($"{conditionLabel}:"); using (writer.Indent()) { var condition = Unwrap(writer, EmitExpression(writer, whileNode.Condition)); writer.WriteLine($"br i1 {condition}, label %{bodyLabel}, label %{endLabel}"); } writer.WriteLine($"{bodyLabel}:"); using (writer.Indent()) { EmitBlock(writer, whileNode.Body); writer.WriteLine($"br label %{conditionLabel}"); } _loopStack.Pop(); writer.WriteLine($"{endLabel}:"); } private Tmp EmitExpression(IndentedTextWriter writer, ExpressionNode expressionNode) { return expressionNode switch { AddressOfNode addressOfNode => EmitAddressOf(writer, addressOfNode), DereferenceNode dereferenceNode => EmitDereference(writer, dereferenceNode), UnaryExpressionNode unaryExpressionNode => EmitUnaryExpression(writer, unaryExpressionNode), BinaryExpressionNode binaryExpressionNode => EmitBinaryExpression(writer, binaryExpressionNode), ConstArrayInitializerNode constArrayInitializerNode => EmitConstArrayInitializer(writer, constArrayInitializerNode), StructInitializerNode structInitializerNode => EmitStructInitializer(writer, structInitializerNode), ConstArrayIndexAccessNode constArrayIndexAccessNode => EmitConstArrayIndexAccess(writer, constArrayIndexAccessNode), ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(writer, arrayIndexAccessNode), SliceIndexAccessNode sliceIndexAccessNode => EmitSliceIndexAccess(writer, sliceIndexAccessNode), StructFieldAccessNode structFieldAccessNode => EmitStructFieldAccess(writer, structFieldAccessNode), CStringLiteralNode cStringLiteralNode => EmitCStringLiteral(writer, cStringLiteralNode), StringLiteralNode stringLiteralNode => EmitStringLiteral(writer, stringLiteralNode), BoolLiteralNode boolLiteralNode => EmitBoolLiteral(boolLiteralNode), Float32LiteralNode float32LiteralNode => EmitFloat32Literal(float32LiteralNode), Float64LiteralNode float64LiteralNode => EmitFloat64Literal(float64LiteralNode), U8LiteralNode u8LiteralNode => EmitU8Literal(u8LiteralNode), U16LiteralNode u16LiteralNode => EmitU16Literal(u16LiteralNode), U32LiteralNode u32LiteralNode => EmitU32Literal(u32LiteralNode), U64LiteralNode u64LiteralNode => EmitU64Literal(u64LiteralNode), I8LiteralNode i8LiteralNode => EmitI8Literal(i8LiteralNode), I16LiteralNode i16LiteralNode => EmitI16Literal(i16LiteralNode), I32LiteralNode i32LiteralNode => EmitI32Literal(i32LiteralNode), I64LiteralNode i64LiteralNode => EmitI64Literal(i64LiteralNode), LocalFuncIdentifierNode localFuncIdentifierNode => EmitLocalFuncIdentifier(writer, localFuncIdentifierNode), ModuleFuncIdentifierNode moduleFuncIdentifierNode => EmitModuleFuncIdentifier(writer, moduleFuncIdentifierNode), VariableIdentifierNode variableIdentifierNode => EmitVariableIdentifier(writer, variableIdentifierNode), FuncCallNode funcCallNode => EmitFuncCall(writer, funcCallNode), SizeNode sizeNode => EmitSize(sizeNode), CastNode castNode => EmitCast(writer, castNode), _ => throw new ArgumentOutOfRangeException(nameof(expressionNode)) }; } private void EmitExpressionInto(IndentedTextWriter writer, ExpressionNode expressionNode, string destination) { switch (expressionNode) { case StructInitializerNode structInitializerNode: { EmitStructInitializer(writer, structInitializerNode, destination); return; } case ConstArrayInitializerNode constArrayInitializerNode: { EmitConstArrayInitializer(writer, constArrayInitializerNode, destination); return; } } var value = Unwrap(writer, EmitExpression(writer, expressionNode)); if (expressionNode.Type.IsAggregate()) { // note(nub31): Fall back to slow method creating a copy writer.WriteLine("; Slow rvalue copy instead of direct memory write"); writer.WriteLine($"call void @llvm.memcpy.p0.p0.i64(ptr {destination}, ptr {value}, i64 {expressionNode.Type.GetSize()}, i1 false)"); } else { writer.WriteLine($"store {MapType(expressionNode.Type)} {value}, ptr {destination}"); } } private Tmp EmitAddressOf(IndentedTextWriter writer, AddressOfNode addressOfNode) { var target = EmitExpression(writer, addressOfNode.Target); return new Tmp(target.Ident, addressOfNode.Type, false); } private Tmp EmitDereference(IndentedTextWriter writer, DereferenceNode dereferenceNode) { var target = EmitExpression(writer, dereferenceNode.Target); return new Tmp(target.Ident, dereferenceNode.Type, true); } private Tmp EmitUnaryExpression(IndentedTextWriter writer, UnaryExpressionNode unaryExpressionNode) { var operand = Unwrap(writer, EmitExpression(writer, unaryExpressionNode.Operand)); var result = NewTmp("unary"); switch (unaryExpressionNode.Operator) { case UnaryOperator.Negate: { switch (unaryExpressionNode.Operand.Type) { case NubIntType intType: writer.WriteLine($"{result} = sub {MapType(intType)} 0, {operand}"); break; case NubFloatType floatType: writer.WriteLine($"{result} = fneg {MapType(floatType)} {operand}"); break; default: throw new UnreachableException(); } break; } case UnaryOperator.Invert: { writer.WriteLine($"{result} = xor i1 {operand}, true"); break; } default: { throw new ArgumentOutOfRangeException(); } } return new Tmp(result, unaryExpressionNode.Type, false); } private Tmp EmitBinaryExpression(IndentedTextWriter writer, BinaryExpressionNode binaryExpressionNode) { var left = Unwrap(writer, EmitExpression(writer, binaryExpressionNode.Left)); var right = Unwrap(writer, EmitExpression(writer, binaryExpressionNode.Right)); var result = NewTmp("binop"); var leftType = binaryExpressionNode.Left.Type; var op = binaryExpressionNode.Operator; switch (op) { case BinaryOperator.Equal: case BinaryOperator.NotEqual: case BinaryOperator.GreaterThan: case BinaryOperator.GreaterThanOrEqual: case BinaryOperator.LessThan: case BinaryOperator.LessThanOrEqual: { var cmpOp = leftType switch { NubIntType intType => GenerateIntComparison(op, intType.Signed), NubFloatType => GenerateFloatComparison(op), NubBoolType => GenerateBoolComparison(op), NubPointerType => GeneratePointerComparison(op), _ => throw new InvalidOperationException($"Unexpected type for comparison: {leftType}") }; writer.WriteLine($"{result} = {cmpOp} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.LogicalAnd: { writer.WriteLine($"{result} = and i1 {left}, {right}"); break; } case BinaryOperator.LogicalOr: { writer.WriteLine($"{result} = or i1 {left}, {right}"); break; } case BinaryOperator.Plus: { var instruction = leftType switch { NubIntType => "add", NubFloatType => "fadd", _ => throw new InvalidOperationException($"Unexpected type for plus: {leftType}") }; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.Minus: { var instruction = leftType switch { NubIntType => "sub", NubFloatType => "fsub", _ => throw new InvalidOperationException($"Unexpected type for minus: {leftType}") }; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.Multiply: { var instruction = leftType switch { NubIntType => "mul", NubFloatType => "fmul", _ => throw new InvalidOperationException($"Unexpected type for multiply: {leftType}") }; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.Divide: { var instruction = leftType switch { NubIntType intType => intType.Signed ? "sdiv" : "udiv", NubFloatType => "fdiv", _ => throw new InvalidOperationException($"Unexpected type for divide: {leftType}") }; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.Modulo: { var instruction = leftType switch { NubIntType intType => intType.Signed ? "srem" : "urem", NubFloatType => "frem", _ => throw new InvalidOperationException($"Unexpected type for modulo: {leftType}") }; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.LeftShift: { writer.WriteLine($"{result} = shl {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.RightShift: { var intType = (NubIntType)leftType; var instruction = intType.Signed ? "ashr" : "lshr"; writer.WriteLine($"{result} = {instruction} {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.BitwiseAnd: { writer.WriteLine($"{result} = and {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.BitwiseXor: { writer.WriteLine($"{result} = xor {MapType(leftType)} {left}, {right}"); break; } case BinaryOperator.BitwiseOr: { writer.WriteLine($"{result} = or {MapType(leftType)} {left}, {right}"); break; } default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } return new Tmp(result, binaryExpressionNode.Type, false); } private string GenerateIntComparison(BinaryOperator op, bool signed) { return op switch { BinaryOperator.Equal => "icmp eq", BinaryOperator.NotEqual => "icmp ne", BinaryOperator.GreaterThan => signed ? "icmp sgt" : "icmp ugt", BinaryOperator.GreaterThanOrEqual => signed ? "icmp sge" : "icmp uge", BinaryOperator.LessThan => signed ? "icmp slt" : "icmp ult", BinaryOperator.LessThanOrEqual => signed ? "icmp sle" : "icmp ule", _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) }; } private string GenerateFloatComparison(BinaryOperator op) { return op switch { BinaryOperator.Equal => "fcmp oeq", BinaryOperator.NotEqual => "fcmp one", BinaryOperator.GreaterThan => "fcmp ogt", BinaryOperator.GreaterThanOrEqual => "fcmp oge", BinaryOperator.LessThan => "fcmp olt", BinaryOperator.LessThanOrEqual => "fcmp ole", _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) }; } private static string GenerateBoolComparison(BinaryOperator op) { return op switch { BinaryOperator.Equal => "icmp eq", BinaryOperator.NotEqual => "icmp ne", _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) }; } private static string GeneratePointerComparison(BinaryOperator op) { return op switch { BinaryOperator.Equal => "icmp eq", BinaryOperator.NotEqual => "icmp ne", BinaryOperator.GreaterThan => "icmp ugt", BinaryOperator.GreaterThanOrEqual => "icmp uge", BinaryOperator.LessThan => "icmp ult", BinaryOperator.LessThanOrEqual => "icmp ule", _ => throw new ArgumentOutOfRangeException(nameof(op), op, null) }; } #region Initializers private Tmp EmitConstArrayInitializer(IndentedTextWriter writer, ConstArrayInitializerNode constArrayInitializerNode, string? destination = null) { var arrayType = (NubConstArrayType)constArrayInitializerNode.Type; if (destination == null) { destination = NewTmp("array"); writer.WriteLine($"{destination} = alloca {MapType(arrayType)}"); } for (var i = 0; i < constArrayInitializerNode.Values.Count; i++) { var value = constArrayInitializerNode.Values[i]; var indexTmp = NewTmp("array.element"); writer.WriteLine($"{indexTmp} = getelementptr {MapType(arrayType)}, ptr {destination}, i32 0, i32 {i}"); EmitExpressionInto(writer, value, indexTmp); } return new Tmp(destination, constArrayInitializerNode.Type, false); } private Tmp EmitStructInitializer(IndentedTextWriter writer, StructInitializerNode structInitializerNode, string? destination = null) { if (destination == null) { destination = NewTmp("struct"); writer.WriteLine($"{destination} = alloca {MapType(structInitializerNode.Type)}"); } var structType = (NubStructType)structInitializerNode.Type; writer.WriteLine($"call void @{StructName(structType.Module, structType.Name)}.new(ptr {destination})"); foreach (var (name, value) in structInitializerNode.Initializers) { var index = structType.GetFieldIndex(name.Value); var fieldTmp = NewTmp($"struct.field.{name}"); writer.WriteLine($"{fieldTmp} = getelementptr %{StructName(structType.Module, structType.Name)}, ptr {destination}, i32 0, i32 {index}"); EmitExpressionInto(writer, value, fieldTmp); } return new Tmp(destination, structInitializerNode.Type, false); } #endregion #region Array indexing private Tmp EmitConstArrayIndexAccess(IndentedTextWriter writer, ConstArrayIndexAccessNode constArrayIndexAccessNode) { var arrayPtr = Unwrap(writer, EmitExpression(writer, constArrayIndexAccessNode.Target)); var index = Unwrap(writer, EmitExpression(writer, constArrayIndexAccessNode.Index)); var elementType = ((NubConstArrayType)constArrayIndexAccessNode.Target.Type).ElementType; var ptrTmp = NewTmp("array.element"); writer.WriteLine($"{ptrTmp} = getelementptr {MapType(elementType)}, ptr {arrayPtr}, {MapType(constArrayIndexAccessNode.Index.Type)} {index}"); return new Tmp(ptrTmp, constArrayIndexAccessNode.Type, true); } private Tmp EmitArrayIndexAccess(IndentedTextWriter writer, ArrayIndexAccessNode arrayIndexAccessNode) { var arrayPtr = Unwrap(writer, EmitExpression(writer, arrayIndexAccessNode.Target)); var index = Unwrap(writer, EmitExpression(writer, arrayIndexAccessNode.Index)); var elementType = ((NubArrayType)arrayIndexAccessNode.Target.Type).ElementType; var ptrTmp = NewTmp("array.element"); writer.WriteLine($"{ptrTmp} = getelementptr {MapType(elementType)}, ptr {arrayPtr}, {MapType(arrayIndexAccessNode.Index.Type)} {index}"); return new Tmp(ptrTmp, arrayIndexAccessNode.Type, true); } private Tmp EmitSliceIndexAccess(IndentedTextWriter writer, SliceIndexAccessNode sliceIndexAccessNode) { throw new NotImplementedException(); } #endregion private Tmp EmitStructFieldAccess(IndentedTextWriter writer, StructFieldAccessNode structFieldAccessNode) { var target = Unwrap(writer, EmitExpression(writer, structFieldAccessNode.Target)); var structType = (NubStructType)structFieldAccessNode.Target.Type; var index = structType.GetFieldIndex(structFieldAccessNode.FieldToken.Value); var ptrTmp = NewTmp($"struct.field.{structFieldAccessNode.FieldToken.Value}"); writer.WriteLine($"{ptrTmp} = getelementptr %{StructName(structType.Module, structType.Name)}, ptr {target}, i32 0, i32 {index}"); return new Tmp(ptrTmp, structFieldAccessNode.Type, true); } #region Literals private Tmp EmitCStringLiteral(IndentedTextWriter writer, CStringLiteralNode cStringLiteralNode) { var escaped = new StringBuilder(); foreach (var c in cStringLiteralNode.Value) { switch (c) { case '\0': escaped.Append("\\00"); break; case '\n': escaped.Append("\\0A"); break; case '\r': escaped.Append("\\0D"); break; case '\t': escaped.Append("\\09"); break; case '\\': escaped.Append("\\\\"); break; case '"': escaped.Append("\\22"); break; default: { if (c < 32 || c > 126) escaped.Append($"\\{(int)c:X2}"); else escaped.Append(c); break; } } } var stringWithNull = cStringLiteralNode.Value + "\0"; var length = stringWithNull.Length; var globalName = $"@.str.{_stringLiterals.Count}"; _stringLiterals.Add((globalName, length, escaped.ToString())); var gepTmp = NewTmp("str.ptr"); writer.WriteLine($"{gepTmp} = getelementptr [{length} x i8], ptr {globalName}, i32 0, i32 0"); return new Tmp(gepTmp, cStringLiteralNode.Type, false); } private static Tmp EmitStringLiteral(IndentedTextWriter writer, StringLiteralNode stringLiteralNode) { throw new NotImplementedException(); } private static Tmp EmitBoolLiteral(BoolLiteralNode boolLiteralNode) { return new Tmp(boolLiteralNode.Value ? "1" : "0", boolLiteralNode.Type, false); } private static Tmp EmitFloat32Literal(Float32LiteralNode float32LiteralNode) { var literal = ((double)float32LiteralNode.Value).ToString("R", System.Globalization.CultureInfo.InvariantCulture); if (!literal.Contains('.')) { literal += ".0"; } return new Tmp(literal, float32LiteralNode.Type, false); } private static Tmp EmitFloat64Literal(Float64LiteralNode float64LiteralNode) { var literal = float64LiteralNode.Value.ToString("R", System.Globalization.CultureInfo.InvariantCulture); if (!literal.Contains('.')) { literal += ".0"; } return new Tmp(literal, float64LiteralNode.Type, false); } private static Tmp EmitU8Literal(U8LiteralNode u8LiteralNode) { return new Tmp(u8LiteralNode.Value.ToString(), u8LiteralNode.Type, false); } private static Tmp EmitU16Literal(U16LiteralNode u16LiteralNode) { return new Tmp(u16LiteralNode.Value.ToString(), u16LiteralNode.Type, false); } private static Tmp EmitU32Literal(U32LiteralNode u32LiteralNode) { return new Tmp(u32LiteralNode.Value.ToString(), u32LiteralNode.Type, false); } private static Tmp EmitU64Literal(U64LiteralNode u64LiteralNode) { return new Tmp(u64LiteralNode.Value.ToString(), u64LiteralNode.Type, false); } private static Tmp EmitI8Literal(I8LiteralNode i8LiteralNode) { return new Tmp(i8LiteralNode.Value.ToString(), i8LiteralNode.Type, false); } private static Tmp EmitI16Literal(I16LiteralNode i16LiteralNode) { return new Tmp(i16LiteralNode.Value.ToString(), i16LiteralNode.Type, false); } private static Tmp EmitI32Literal(I32LiteralNode i32LiteralNode) { return new Tmp(i32LiteralNode.Value.ToString(), i32LiteralNode.Type, false); } private static Tmp EmitI64Literal(I64LiteralNode i64LiteralNode) { return new Tmp(i64LiteralNode.Value.ToString(), i64LiteralNode.Type, false); } #endregion #region Identifiers private Tmp EmitLocalFuncIdentifier(IndentedTextWriter writer, LocalFuncIdentifierNode localFuncIdentifierNode) { var name = FuncName(_module, localFuncIdentifierNode.NameToken.Value, localFuncIdentifierNode.ExternSymbolToken?.Value); return new Tmp($"@{name}", localFuncIdentifierNode.Type, false); } private Tmp EmitModuleFuncIdentifier(IndentedTextWriter writer, ModuleFuncIdentifierNode moduleFuncIdentifierNode) { var name = FuncName(moduleFuncIdentifierNode.ModuleToken.Value, moduleFuncIdentifierNode.NameToken.Value, moduleFuncIdentifierNode.ExternSymbolToken?.Value); return new Tmp($"@{name}", moduleFuncIdentifierNode.Type, false); } private Tmp EmitVariableIdentifier(IndentedTextWriter writer, VariableIdentifierNode variableIdentifierNode) { return new Tmp($"%{variableIdentifierNode.NameToken.Value}", variableIdentifierNode.Type, true); } #endregion private Tmp EmitFuncCall(IndentedTextWriter writer, FuncCallNode funcCallNode) { var result = NewTmp(); var parameterStrings = new List(); foreach (var parameter in funcCallNode.Parameters) { var value = Unwrap(writer, EmitExpression(writer, parameter)); parameterStrings.Add($"{MapType(parameter.Type)} {value}"); } var functionPtr = Unwrap(writer, EmitExpression(writer, funcCallNode.Expression)); if (funcCallNode.Type is NubVoidType) { writer.WriteLine($"call ccc {MapType(funcCallNode.Type)} {functionPtr}({string.Join(", ", parameterStrings)})"); } else { writer.WriteLine($"{result} = call ccc {MapType(funcCallNode.Type)} {functionPtr}({string.Join(", ", parameterStrings)})"); } return new Tmp(result, funcCallNode.Type, false); } private static Tmp EmitSize(SizeNode sizeNode) { return new Tmp(sizeNode.TargetType.GetSize().ToString(), sizeNode.Type, false); } private Tmp EmitCast(IndentedTextWriter writer, CastNode castNode) { var source = Unwrap(writer, EmitExpression(writer, castNode.Value)); var result = NewTmp("cast"); switch (castNode.ConversionType) { case CastNode.Conversion.IntToInt: { var sourceInt = (NubIntType)castNode.Value.Type; var targetInt = (NubIntType)castNode.Type; var op = sourceInt.Width < targetInt.Width ? sourceInt.Signed ? "sext" : "zext" : sourceInt.Width > targetInt.Width ? "trunc" : "bitcast"; writer.WriteLine($"{result} = {op} {MapType(sourceInt)} {source} to {MapType(targetInt)}"); break; } case CastNode.Conversion.FloatToFloat: { var sourceFloat = (NubFloatType)castNode.Value.Type; var targetFloat = (NubFloatType)castNode.Type; var op = sourceFloat.Width < targetFloat.Width ? "fpext" : "fptrunc"; writer.WriteLine($"{result} = {op} {MapType(sourceFloat)} {source} to {MapType(targetFloat)}"); break; } case CastNode.Conversion.IntToFloat: { var sourceInt = (NubIntType)castNode.Value.Type; var targetFloat = (NubFloatType)castNode.Type; var op = sourceInt.Signed ? "sitofp" : "uitofp"; writer.WriteLine($"{result} = {op} {MapType(sourceInt)} {source} to {MapType(targetFloat)}"); break; } case CastNode.Conversion.FloatToInt: { var sourceFloat = (NubFloatType)castNode.Value.Type; var targetInt = (NubIntType)castNode.Type; var op = targetInt.Signed ? "fptosi" : "fptoui"; writer.WriteLine($"{result} = {op} {MapType(sourceFloat)} {source} to {MapType(targetInt)}"); break; } case CastNode.Conversion.PointerToPointer: case CastNode.Conversion.PointerToUInt64: case CastNode.Conversion.UInt64ToPointer: { writer.WriteLine($"{result} = inttoptr {MapType(castNode.Value.Type)} {source} to {MapType(castNode.Type)}"); break; } case CastNode.Conversion.ConstArrayToArray: { var sourceConstArrayType = (NubConstArrayType)castNode.Value.Type; var targetArrayType = (NubArrayType)castNode.Type; writer.WriteLine($"{result} = getelementptr {MapType(sourceConstArrayType)}, {MapType(targetArrayType)} {source}, i32 0, i32 0"); break; } case CastNode.Conversion.ConstArrayToSlice: { throw new NotImplementedException(); } default: { throw new UnreachableException(); } } return new Tmp(result, castNode.Type, false); } private string StructName(StructNode structNode) { return StructName(_module, structNode.NameToken.Value); } private string StructName(string module, string name) { return $"struct.{module}.{name}"; } private string FuncName(string module, string name, string? externSymbol) { if (externSymbol != null) { return externSymbol; } return $"{module}.{name}"; } private string MapType(NubType type) { return type switch { NubArrayType arrayType => $"{MapType(arrayType.ElementType)}*", NubBoolType => "i1", NubConstArrayType constArrayType => $"[{constArrayType.Size} x {MapType(constArrayType.ElementType)}]", NubFloatType floatType => floatType.Width == 32 ? "float" : "double", NubFuncType funcType => MapFuncType(funcType), NubIntType intType => $"i{intType.Width}", NubPointerType pointerType => $"{MapType(pointerType.BaseType)}*", NubSliceType sliceType => throw new NotImplementedException(), NubStringType stringType => throw new NotImplementedException(), NubStructType structType => $"%{StructName(structType.Module, structType.Name)}", NubVoidType => "void", _ => throw new ArgumentOutOfRangeException(nameof(type)) }; } private string MapFuncType(NubFuncType funcType) { var paramTypes = string.Join(", ", funcType.Parameters.Select(MapType)); var returnType = MapType(funcType.ReturnType); return $"{returnType} ({paramTypes})*"; } private record Tmp(string Ident, NubType Type, bool LValue); private string Unwrap(IndentedTextWriter writer, Tmp tmp) { if (tmp.LValue && !tmp.Type.IsAggregate()) { var newTmp = NewTmp("deref"); writer.WriteLine($"{newTmp} = load {MapType(tmp.Type)}, ptr {tmp.Ident}"); return newTmp; } return tmp.Ident; } private string NewTmp(string name = "t") { return $"%{name}.{++_tmpIndex}"; } private string NewLabel(string name = "l") { return $"{name}.{++_labelIndex}"; } }