diff --git a/LLVMSharp.slnx b/LLVMSharp.slnx index 474ffa4a..0767f61d 100644 --- a/LLVMSharp.slnx +++ b/LLVMSharp.slnx @@ -47,6 +47,17 @@ + + + + + + + + + + + @@ -89,5 +100,6 @@ + diff --git a/samples/KaleidoscopeTutorial/Chapter3/Chapter3.csproj b/samples/KaleidoscopeTutorial/Chapter3/Chapter3.csproj new file mode 100644 index 00000000..84fb8de9 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter3/Chapter3.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter3/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter3/CodeGenVisitor.cs new file mode 100644 index 00000000..4266f400 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter3/CodeGenVisitor.cs @@ -0,0 +1,153 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter3; + +/// +/// Chapter 3 — "Code generation to LLVM IR". Lowers the core expression language (numbers, variables, +/// the built-in + - * < operators, calls) and function definitions to LLVM IR. Later chapters +/// inherit this class and override only the nodes they add. +/// +public class CodeGenVisitor : CodeGenVisitorBase +{ + protected override LLVMValueRef CodegenNumber(NumberExprAST node) + { + return LLVMValueRef.CreateConstReal(Module.Context.DoubleType, node.Value); + } + + protected override LLVMValueRef CodegenVariable(VariableExprAST node) + { + if (NamedValues.TryGetValue(node.Name, out LLVMValueRef value)) + { + return value; + } + + throw new InvalidOperationException($"Unknown variable name '{node.Name}'"); + } + + protected override LLVMValueRef CodegenBinary(BinaryExprAST node) + { + LLVMValueRef left = Codegen(node.Lhs); + LLVMValueRef right = Codegen(node.Rhs); + + switch (node.Op) + { + case '+': + { + return Builder.BuildFAdd(left, right, "addtmp"); + } + + case '-': + { + return Builder.BuildFSub(left, right, "subtmp"); + } + + case '*': + { + return Builder.BuildFMul(left, right, "multmp"); + } + + case '<': + { + LLVMValueRef comparison = Builder.BuildFCmp(LLVMRealPredicate.LLVMRealULT, left, right, "cmptmp"); + + // Convert the i1 result to a double (0.0 or 1.0), Kaleidoscope's only value type. + return Builder.BuildUIToFP(comparison, Module.Context.DoubleType, "booltmp"); + } + + default: + { + throw new InvalidOperationException($"invalid binary operator '{node.Op}'"); + } + } + } + + protected override LLVMValueRef CodegenCall(CallExprAST node) + { + LLVMValueRef callee = GetFunction(node.Callee); + if (callee.Handle == IntPtr.Zero) + { + throw new InvalidOperationException($"Unknown function '{node.Callee}' referenced"); + } + + if (callee.ParamsCount != node.Arguments.Count) + { + throw new InvalidOperationException("Incorrect # arguments passed"); + } + + var arguments = new LLVMValueRef[node.Arguments.Count]; + for (int i = 0; i < arguments.Length; i++) + { + arguments[i] = Codegen(node.Arguments[i]); + } + + // Recover the callee's function type from the value itself; BuildCall2 needs it explicitly. + LLVMTypeRef functionType = GetFunctionType(callee); + return Builder.BuildCall2(functionType, callee, arguments, "calltmp"); + } + + public override LLVMValueRef CodegenPrototype(PrototypeAST node) + { + LLVMTypeRef doubleType = Module.Context.DoubleType; + + var parameterTypes = new LLVMTypeRef[node.Arguments.Count]; + Array.Fill(parameterTypes, doubleType); + + LLVMTypeRef functionType = LLVMTypeRef.CreateFunction(doubleType, parameterTypes); + LLVMValueRef function = Module.AddFunction(node.Name, functionType); + + for (int i = 0; i < node.Arguments.Count; i++) + { + function.GetParam((uint)i).Name = node.Arguments[i]; + } + + return function; + } + + public override LLVMValueRef CodegenFunction(FunctionAST node) + { + // Remember the prototype (so recursive references resolve) then get-or-declare the function. + RegisterPrototype(node.Proto); + LLVMValueRef function = GetFunction(node.Proto.Name); + + if (function.BasicBlocksCount != 0) + { + throw new InvalidOperationException($"Function '{node.Proto.Name}' cannot be redefined"); + } + + LLVMBasicBlockRef entry = function.AppendBasicBlock("entry"); + Builder.PositionAtEnd(entry); + + CreateParameterBindings(function, node.Proto); + + try + { + LLVMValueRef body = Codegen(node.Body); + Builder.BuildRet(body); + function.VerifyFunction(LLVMVerifierFailureAction.LLVMPrintMessageAction); + return function; + } + catch + { + // Remove the half-built function so the REPL can keep going after an error. + function.DeleteFunction(); + throw; + } + } + + /// + /// Binds the function's parameters into before the body + /// is generated. Chapter 3 binds the SSA parameter values directly; chapter 7 overrides this to give + /// each parameter a stack slot so it can be reassigned. + /// + protected virtual void CreateParameterBindings(LLVMValueRef function, PrototypeAST proto) + { + NamedValues.Clear(); + for (int i = 0; i < proto.Arguments.Count; i++) + { + NamedValues[proto.Arguments[i]] = function.GetParam((uint)i); + } + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter3/DumpDriver.cs b/samples/KaleidoscopeTutorial/Chapter3/DumpDriver.cs new file mode 100644 index 00000000..b20594bb --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter3/DumpDriver.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter3; + +/// +/// Chapter 3's driver: it generates IR into one module and prints it. There is no execution yet — the +/// point of this chapter is just to see the generated LLVM IR. Chapter 4 keeps the same code generator +/// but adds a JIT so top-level expressions actually run. +/// +public sealed class DumpDriver : ReplDriver +{ + private readonly CodeGenVisitor _visitor; + + public DumpDriver(Lexer lexer, Parser parser, CodeGenVisitor visitor, LLVMModuleRef module) + : base(lexer, parser) + { + _visitor = visitor; + _visitor.SetModule(module); + } + + protected override void OnDefinition(FunctionAST function) + { + LLVMValueRef ir = _visitor.CodegenFunction(function); + Console.WriteLine("Read function definition:"); + Console.WriteLine(ir); + } + + protected override void OnExtern(PrototypeAST prototype) + { + LLVMValueRef ir = _visitor.CodegenExtern(prototype); + Console.WriteLine("Read extern:"); + Console.WriteLine(ir); + } + + protected override void OnTopLevelExpression(FunctionAST function) + { + LLVMValueRef ir = _visitor.CodegenFunction(function); + Console.WriteLine("Read top-level expression:"); + Console.WriteLine(ir); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope.sln b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope.sln deleted file mode 100644 index eae9808a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaleidoscope", "Kaleidoscope\Kaleidoscope.csproj", "{CD23CB2E-951B-4FAD-A4CD-4048731DBA31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KaleidoscopeLLVM", "KaleidoscopeLLVM\KaleidoscopeLLVM.csproj", "{C1147A13-4174-410A-A891-335D858703B7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|x64.ActiveCfg = Debug|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|x64.Build.0 = Debug|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.Build.0 = Release|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|x64.ActiveCfg = Release|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|x64.Build.0 = Release|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|x64.ActiveCfg = Debug|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Debug|x64.Build.0 = Debug|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.Build.0 = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|x64.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/BinaryExprAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/BinaryExprAST.cs deleted file mode 100644 index 1143f724..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/BinaryExprAST.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System; - - public sealed class BinaryExprAST : ExprAST - { - public BinaryExprAST(char op, ExprAST lhs, ExprAST rhs) - { - switch (op) - { - case '+': - this.NodeType = ExprType.AddExpr; - break; - case '-': - this.NodeType = ExprType.SubtractExpr; - break; - case '*': - this.NodeType = ExprType.MultiplyExpr; - break; - case '<': - this.NodeType = ExprType.LessThanExpr; - break; - default: - throw new ArgumentException("op " + op + " is not a valid operator"); - } - - this.Lhs = lhs; - this.Rhs = rhs; - } - - public ExprAST Lhs { get; private set; } - - public ExprAST Rhs { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitBinaryExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/CallExprAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/CallExprAST.cs deleted file mode 100644 index 1a722b27..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/CallExprAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class CallExprAST : ExprAST - { - public CallExprAST(string callee, List args) - { - this.Callee = callee; - this.Arguments = args; - this.NodeType = ExprType.CallExpr; - } - - public string Callee { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitCallExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprAST.cs deleted file mode 100644 index 6841cbec..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprAST.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprAST - { - public abstract ExprType NodeType { get; protected set; } - - protected internal virtual ExprAST VisitChildren(ExprVisitor visitor) - { - return visitor.Visit(this); - } - - protected internal virtual ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitExtension(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprType.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprType.cs deleted file mode 100644 index e890f346..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Kaleidoscope.AST -{ - public enum ExprType - { - AddExpr, - SubtractExpr, - MultiplyExpr, - LessThanExpr, - CallExpr, - VariableExpr, - PrototypeExpr, - FunctionExpr, - NumberExpr - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprVisitor.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprVisitor.cs deleted file mode 100644 index a51f80cc..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/ExprVisitor.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprVisitor - { - protected ExprVisitor() - { - } - - public virtual ExprAST Visit(ExprAST node) - { - if (node != null) - { - return node.Accept(this); - } - - return null; - } - - protected internal virtual ExprAST VisitExtension(ExprAST node) - { - return node.VisitChildren(this); - } - - protected internal virtual ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - return node; - } - - protected internal virtual ExprAST VisitCallExprAST(CallExprAST node) - { - foreach (var argument in node.Arguments) - { - this.Visit(argument); - } - - return node; - } - - protected internal virtual ExprAST VisitFunctionAST(FunctionAST node) - { - this.Visit(node.Proto); - this.Visit(node.Body); - - return node; - } - - protected internal virtual ExprAST VisitVariableExprAST(VariableExprAST node) - { - return node; - } - - protected internal virtual ExprAST VisitPrototypeAST(PrototypeAST node) - { - return node; - } - - protected internal virtual ExprAST VisitNumberExprAST(NumberExprAST node) - { - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/FunctionAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/FunctionAST.cs deleted file mode 100644 index a5e90405..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/FunctionAST.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class FunctionAST : ExprAST - { - public FunctionAST(PrototypeAST proto, ExprAST body) - { - this.Proto = proto; - this.Body = body; - this.NodeType = ExprType.FunctionExpr; - } - - public PrototypeAST Proto { get; private set; } - - public ExprAST Body { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitFunctionAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/NumberExprAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/NumberExprAST.cs deleted file mode 100644 index bd823cd3..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/NumberExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class NumberExprAST : ExprAST - { - public NumberExprAST(double value) - { - this.Value = value; - this.NodeType = ExprType.NumberExpr; - } - - public double Value { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitNumberExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/PrototypeAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/PrototypeAST.cs deleted file mode 100644 index 3d0f6b4a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/PrototypeAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class PrototypeAST : ExprAST - { - public PrototypeAST(string name, List args) - { - this.Name = name; - this.Arguments = args; - this.NodeType = ExprType.PrototypeExpr; - } - - public string Name { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitPrototypeAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/VariableExprAST.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/VariableExprAST.cs deleted file mode 100644 index 41aeb6c2..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/AST/VariableExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class VariableExprAST : ExprAST - { - public VariableExprAST(string name) - { - this.Name = name; - this.NodeType = ExprType.VariableExpr; - } - - public string Name { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitVariableExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/BaseParserListener.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/BaseParserListener.cs deleted file mode 100644 index 9eb96731..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/BaseParserListener.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using AST; - - internal sealed class BaseParserListener - { - private static readonly Type IParserListenerType = typeof(IParserListener); - - private readonly Stack descentStack = new Stack(); - - private readonly Stack ascentStack = new Stack(); - - private readonly IParserListener listener; - - public BaseParserListener(IParserListener listener) - { - this.listener = listener; - } - - public void EnterRule(string ruleName) - { - this.descentStack.Push(ruleName); - } - - public void ExitRule(ExprAST argument) - { - string ruleName = this.descentStack.Pop(); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Exit" + ruleName), this.listener, argument)); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Enter" + ruleName), this.listener, argument)); - } - - public void Listen() - { - if (this.listener != null) - { - while (this.ascentStack.Count != 0) - { - var context = this.ascentStack.Pop(); - context.MethodInfo.Invoke(context.Instance, new object[] { context.Argument }); - } - } - } - - private sealed class ASTContext - { - public ASTContext(MethodInfo methodInfo, object instance, ExprAST argument) - { - this.MethodInfo = methodInfo; - this.Instance = instance; - this.Argument = argument; - } - - public MethodInfo MethodInfo { get; private set; } - - public ExprAST Argument { get; set; } - - public object Instance { get; private set; } - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/ILexer.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/ILexer.cs deleted file mode 100644 index aca5d18f..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/ILexer.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Kaleidoscope -{ - public interface ILexer - { - int CurrentToken { get; } - - string GetLastIdentifier(); - - double GetLastNumber(); - - int GetTokPrecedence(); - - int GetNextToken(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParser.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParser.cs deleted file mode 100644 index adf84705..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Kaleidoscope -{ - public interface IParser - { - void HandleDefinition(); - - void HandleExtern(); - - void HandleTopLevelExpression(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParserListener.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParserListener.cs deleted file mode 100644 index fbc91f82..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/IParserListener.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Kaleidoscope -{ - using AST; - - public interface IParserListener - { - void EnterHandleDefinition(FunctionAST data); - - void ExitHandleDefinition(FunctionAST data); - - void EnterHandleExtern(PrototypeAST data); - - void ExitHandleExtern(PrototypeAST data); - - void EnterHandleTopLevelExpression(FunctionAST data); - - void ExitHandleTopLevelExpression(FunctionAST data); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Kaleidoscope.csproj b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Kaleidoscope.csproj deleted file mode 100644 index 09321765..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Kaleidoscope.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - net10.0 - - diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Lexer.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Lexer.cs deleted file mode 100644 index fc321d68..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Lexer.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - - public sealed class Lexer : ILexer - { - private const int EOF = -1; - - private readonly TextReader reader; - - private readonly StringBuilder identifierBuilder = new StringBuilder(); - - private readonly StringBuilder numberBuilder = new StringBuilder(); - - private readonly Dictionary binopPrecedence; - - private int c = ' '; - - private string identifier; - - private double numVal; - - public Lexer(TextReader reader, Dictionary binOpPrecedence) - { - this.reader = reader; - this.binopPrecedence = binOpPrecedence; - } - - public int CurrentToken { get; private set; } - - public string GetLastIdentifier() - { - return this.identifier; - } - - public double GetLastNumber() - { - return this.numVal; - } - - public int GetTokPrecedence() - { - // Make sure it's a declared binop. - int tokPrec; - if (this.binopPrecedence.TryGetValue((char)this.CurrentToken, out tokPrec)) - { - return tokPrec; - } - - return -1; - } - - public int GetNextToken() - { - // Skip any whitespace. - while (char.IsWhiteSpace((char)c)) - { - c = this.reader.Read(); - } - - if (char.IsLetter((char)c)) // identifier: [a-zA-Z][a-zA-Z0-9]* - { - this.identifierBuilder.Append((char)c); - while (char.IsLetterOrDigit((char)(c = this.reader.Read()))) - { - this.identifierBuilder.Append((char)c); - } - - this.identifier = this.identifierBuilder.ToString(); - this.identifierBuilder.Clear(); - - if (string.Equals(identifier, "def", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.DEF; - } - else if (string.Equals(identifier, "extern", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.EXTERN; - } - else - { - this.CurrentToken = (int)Token.IDENTIFIER; - } - - return this.CurrentToken; - } - - // Number: [0-9.]+ - if (char.IsDigit((char)c) || c == '.') - { - do - { - this.numberBuilder.Append((char)c); - c = this.reader.Read(); - } while (char.IsDigit((char)c) || c == '.'); - - this.numVal = double.Parse(this.numberBuilder.ToString()); - this.numberBuilder.Clear(); - this.CurrentToken = (int)Token.NUMBER; - - return this.CurrentToken; - } - - if (c == '#') - { - // Comment until end of line. - do - { - c = this.reader.Read(); - } while (c != EOF && c != '\n' && c != '\r'); - - if (c != EOF) - { - return this.GetNextToken(); - } - } - - // Check for end of file. Don't eat the EOF. - if (c == EOF) - { - this.CurrentToken = c; - return (int)Token.EOF; - } - - this.CurrentToken = c; - c = this.reader.Read(); - return this.c; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Parser.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Parser.cs deleted file mode 100644 index 15954149..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Parser.cs +++ /dev/null @@ -1,313 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using AST; - - public sealed class Parser : IParser - { - private readonly Lexer scanner; - - private readonly BaseParserListener baseListener; - - public Parser(Lexer scanner, IParserListener listener) - { - this.scanner = scanner; - this.baseListener = new BaseParserListener(listener); - } - - public void HandleDefinition() - { - this.baseListener.EnterRule("HandleDefinition"); - - var functionAST = this.ParseDefinition(); - - baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleExtern() - { - this.baseListener.EnterRule("HandleExtern"); - - var prototypeAST = this.ParseExtern(); - - this.baseListener.ExitRule(prototypeAST); - - if (prototypeAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleTopLevelExpression() - { - // Evaluate a top-level expression into an anonymous function. - this.baseListener.EnterRule("HandleTopLevelExpression"); - - var functionAST = this.ParseTopLevelExpr(); - - this.baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - // identifierexpr - // ::= identifier - // ::= identifier '(' expression* ')' - private ExprAST ParseIdentifierExpr() - { - string idName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); // eat identifier. - - if (this.scanner.CurrentToken != '(') // Simple variable ref. - { - return new VariableExprAST(idName); - } - - // Call. - this.scanner.GetNextToken(); // eat ( - List args = new List(); - - if (this.scanner.CurrentToken != ')') - { - while (true) - { - ExprAST arg = this.ParseExpression(); - if (arg == null) - { - return null; - } - - args.Add(arg); - - if (this.scanner.CurrentToken == ')') - { - break; - } - - if (this.scanner.CurrentToken != ',') - { - Console.WriteLine("Expected ')' or ',' in argument list"); - return null; - } - - this.scanner.GetNextToken(); - } - } - - // Eat the ')'. - this.scanner.GetNextToken(); - - return new CallExprAST(idName, args); - } - - // numberexpr ::= number - private ExprAST ParseNumberExpr() - { - ExprAST result = new NumberExprAST(this.scanner.GetLastNumber()); - this.scanner.GetNextToken(); - return result; - } - - // parenexpr ::= '(' expression ')' - private ExprAST ParseParenExpr() - { - this.scanner.GetNextToken(); // eat (. - ExprAST v = this.ParseExpression(); - if (v == null) - { - return null; - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("expected ')'"); - return null; - } - - this.scanner.GetNextToken(); // eat ). - - return v; - } - - // primary - // ::= identifierexpr - // ::= numberexpr - // ::= parenexpr - private ExprAST ParsePrimary() - { - switch (this.scanner.CurrentToken) - { - case (int)Token.IDENTIFIER: - return this.ParseIdentifierExpr(); - case (int)Token.NUMBER: - return this.ParseNumberExpr(); - case '(': - return this.ParseParenExpr(); - default: - Console.WriteLine("unknown token when expecting an expression"); - return null; - } - } - - // binoprhs - // ::= ('+' primary)* - private ExprAST ParseBinOpRHS(int exprPrec, ExprAST lhs) - { - // If this is a binop, find its precedence. - while (true) - { - int tokPrec = this.scanner.GetTokPrecedence(); - - // If this is a binop that binds at least as tightly as the current binop, - // consume it, otherwise we are done. - if (tokPrec < exprPrec) - { - return lhs; - } - - // Okay, we know this is a binop. - int binOp = this.scanner.CurrentToken; - this.scanner.GetNextToken(); // eat binop - - // Parse the primary expression after the binary operator. - ExprAST rhs = this.ParsePrimary(); - if (rhs == null) - { - return null; - } - - // If BinOp binds less tightly with RHS than the operator after RHS, let - // the pending operator take RHS as its LHS. - int nextPrec = this.scanner.GetTokPrecedence(); - if (tokPrec < nextPrec) - { - rhs = this.ParseBinOpRHS(tokPrec + 1, rhs); - if (rhs == null) - { - return null; - } - } - - // Merge LHS/RHS. - lhs = new BinaryExprAST((char)binOp, lhs, rhs); - } - } - - // expression - // ::= primary binoprhs - // - private ExprAST ParseExpression() - { - ExprAST lhs = this.ParsePrimary(); - if (lhs == null) - { - return null; - } - - return this.ParseBinOpRHS(0, lhs); - } - - // prototype - // ::= id '(' id* ')' - private PrototypeAST ParsePrototype() - { - if (this.scanner.CurrentToken != (int)Token.IDENTIFIER) - { - Console.WriteLine("Expected function name in prototype"); - return null; - } - - string fnName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); - - if (this.scanner.CurrentToken != '(') - { - Console.WriteLine("Expected '(' in prototype"); - return null; - } - - List argNames = new List(); - while (this.scanner.GetNextToken() == (int)Token.IDENTIFIER) - { - argNames.Add(this.scanner.GetLastIdentifier()); - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("Expected ')' in prototype"); - return null; - } - - this.scanner.GetNextToken(); // eat ')'. - - return new PrototypeAST(fnName, argNames); - } - - // definition ::= 'def' prototype expression - private FunctionAST ParseDefinition() - { - this.scanner.GetNextToken(); // eat def. - PrototypeAST proto = this.ParsePrototype(); - - if (proto == null) - { - return null; - } - - ExprAST body = this.ParseExpression(); - if (body == null) - { - return null; - } - - return new FunctionAST(proto, body); - } - - /// toplevelexpr ::= expression - private FunctionAST ParseTopLevelExpr() - { - ExprAST e = this.ParseExpression(); - if (e == null) - { - return null; - } - - // Make an anonymous proto. - PrototypeAST proto = new PrototypeAST(string.Empty, new List()); - return new FunctionAST(proto, e); - } - - /// external ::= 'extern' prototype - private PrototypeAST ParseExtern() - { - this.scanner.GetNextToken(); // eat extern. - return this.ParsePrototype(); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Token.cs b/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Token.cs deleted file mode 100644 index a4f84c23..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/Kaleidoscope/Token.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Kaleidoscope -{ - public enum Token - { - EOF = -1, - DEF = -2, - EXTERN = -3, - IDENTIFIER = -4, - NUMBER = -5, - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenParserListener.cs b/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenParserListener.cs deleted file mode 100644 index ed23a774..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenParserListener.cs +++ /dev/null @@ -1,47 +0,0 @@ -using LLVMSharp; - -namespace KaleidoscopeLLVM -{ - using Kaleidoscope; - using Kaleidoscope.AST; - - internal sealed class CodeGenParserListener : IParserListener - { - private readonly CodeGenVisitor visitor; - - public CodeGenParserListener(CodeGenVisitor visitor) - { - this.visitor = visitor; - } - - public void EnterHandleDefinition(FunctionAST data) - { - } - - public void ExitHandleDefinition(FunctionAST data) - { - this.visitor.Visit(data); - LLVM.DumpValue(this.visitor.ResultStack.Pop()); - } - - public void EnterHandleExtern(PrototypeAST data) - { - } - - public void ExitHandleExtern(PrototypeAST data) - { - this.visitor.Visit(data); - LLVM.DumpValue(this.visitor.ResultStack.Pop()); - } - - public void EnterHandleTopLevelExpression(FunctionAST data) - { - } - - public void ExitHandleTopLevelExpression(FunctionAST data) - { - this.visitor.Visit(data); - LLVM.DumpValue(this.visitor.ResultStack.Pop()); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenVisitor.cs deleted file mode 100644 index f5aa974a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/CodeGenVisitor.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using Kaleidoscope.AST; -using LLVMSharp; - -namespace KaleidoscopeLLVM -{ - internal sealed class CodeGenVisitor : ExprVisitor - { - private static readonly LLVMBool LLVMBoolFalse = new LLVMBool(0); - - private static readonly LLVMValueRef NullValue = new LLVMValueRef(IntPtr.Zero); - - private readonly LLVMModuleRef module; - - private readonly LLVMBuilderRef builder; - - private readonly Dictionary namedValues = new Dictionary(); - - private readonly Stack valueStack = new Stack(); - - public CodeGenVisitor(LLVMModuleRef module, LLVMBuilderRef builder) - { - this.module = module; - this.builder = builder; - } - - public Stack ResultStack { get { return valueStack; } } - - public void ClearResultStack() - { - this.valueStack.Clear(); - } - - protected override ExprAST VisitNumberExprAST(NumberExprAST node) - { - this.valueStack.Push(LLVM.ConstReal(LLVM.DoubleType(), node.Value)); - return node; - } - - protected override ExprAST VisitVariableExprAST(VariableExprAST node) - { - LLVMValueRef value; - - // Look this variable up in the function. - if (this.namedValues.TryGetValue(node.Name, out value)) - { - this.valueStack.Push(value); - } - else - { - throw new Exception("Unknown variable name"); - } - - return node; - } - - protected override ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - LLVMValueRef r = this.valueStack.Pop(); - LLVMValueRef l = this.valueStack.Pop(); - - LLVMValueRef n; - - switch (node.NodeType) - { - case ExprType.AddExpr: - n = LLVM.BuildFAdd(this.builder, l, r, "addtmp"); - break; - case ExprType.SubtractExpr: - n = LLVM.BuildFSub(this.builder, l, r, "subtmp"); - break; - case ExprType.MultiplyExpr: - n = LLVM.BuildFMul(this.builder, l, r, "multmp"); - break; - case ExprType.LessThanExpr: - // Convert bool 0/1 to double 0.0 or 1.0 - n = LLVM.BuildUIToFP(this.builder, LLVM.BuildFCmp(this.builder, LLVMRealPredicate.LLVMRealULT, l, r, "cmptmp"), LLVM.DoubleType(), "booltmp"); - break; - default: - throw new Exception("invalid binary operator"); - } - - this.valueStack.Push(n); - return node; - } - - protected override ExprAST VisitCallExprAST(CallExprAST node) - { - var calleeF = LLVM.GetNamedFunction(this.module, node.Callee); - if (calleeF.Pointer == IntPtr.Zero) - { - throw new Exception("Unknown function referenced"); - } - - if (LLVM.CountParams(calleeF) != node.Arguments.Count) - { - throw new Exception("Incorrect # arguments passed"); - } - - var argumentCount = (uint)node.Arguments.Count; - var argsV = new LLVMValueRef[Math.Max(argumentCount, 1)]; - for (int i = 0; i < argumentCount; ++i) - { - this.Visit(node.Arguments[i]); - argsV[i] = this.valueStack.Pop(); - } - - valueStack.Push(LLVM.BuildCall(this.builder, calleeF, argsV, "calltmp")); - - return node; - } - - protected override ExprAST VisitPrototypeAST(PrototypeAST node) - { - // Make the function type: double(double,double) etc. - var argumentCount = (uint)node.Arguments.Count; - var arguments = new LLVMTypeRef[Math.Max(argumentCount, 1)]; - - var function = LLVM.GetNamedFunction(this.module, node.Name); - - // If F conflicted, there was already something named 'Name'. If it has a - // body, don't allow redefinition or reextern. - if (function.Pointer != IntPtr.Zero) - { - // If F already has a body, reject this. - if (LLVM.CountBasicBlocks(function) != 0) - { - throw new Exception("redefinition of function."); - } - - // If F took a different number of args, reject. - if (LLVM.CountParams(function) != argumentCount) - { - throw new Exception("redefinition of function with different # args"); - } - } - else - { - for (int i = 0; i < argumentCount; ++i) - { - arguments[i] = LLVM.DoubleType(); - } - - function = LLVM.AddFunction(this.module, node.Name, LLVM.FunctionType(LLVM.DoubleType(), arguments, LLVMBoolFalse)); - LLVM.SetLinkage(function, LLVMLinkage.LLVMExternalLinkage); - } - - for (int i = 0; i < argumentCount; ++i) - { - string argumentName = node.Arguments[i]; - - LLVMValueRef param = LLVM.GetParam(function, (uint)i); - LLVM.SetValueName(param, argumentName); - - this.namedValues[argumentName] = param; - } - - this.valueStack.Push(function); - return node; - } - - protected override ExprAST VisitFunctionAST(FunctionAST node) - { - this.namedValues.Clear(); - - this.Visit(node.Proto); - - LLVMValueRef function = this.valueStack.Pop(); - - // Create a new basic block to start insertion into. - LLVM.PositionBuilderAtEnd(this.builder, LLVM.AppendBasicBlock(function, "entry")); - - try - { - this.Visit(node.Body); - } - catch (Exception) - { - LLVM.DeleteFunction(function); - throw; - } - - // Finish off the function. - LLVM.BuildRet(this.builder, this.valueStack.Pop()); - - // Validate the generated code, checking for consistency. - LLVM.VerifyFunction(function, LLVMVerifierFailureAction.LLVMPrintMessageAction); - - this.valueStack.Push(function); - - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj b/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj deleted file mode 100644 index 96ff9d4b..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net10.0 - - - - - - - - - - diff --git a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/Program.cs b/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/Program.cs deleted file mode 100644 index 91acc031..00000000 --- a/samples/KaleidoscopeTutorial/Chapter3/KaleidoscopeLLVM/Program.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace KaleidoscopeLLVM -{ - using System; - using System.Collections.Generic; - using Kaleidoscope; - using LLVMSharp; - - public sealed class Program - { - private static void Main(string[] args) - { - // Make the module, which holds all the code. - LLVMModuleRef module = LLVM.ModuleCreateWithName("my cool jit"); - LLVMBuilderRef builder = LLVM.CreateBuilder(); - var codeGenlistener = new CodeGenParserListener(new CodeGenVisitor(module, builder)); - - // Install standard binary operators. - // 1 is lowest precedence. - var binopPrecedence = new Dictionary - { - ['<'] = 10, - ['+'] = 20, - ['-'] = 20, - ['*'] = 40 - }; - // highest. - - var scanner = new Lexer(Console.In, binopPrecedence); - var parser = new Parser(scanner, codeGenlistener); - - // Prime the first token. - Console.Write("ready> "); - scanner.GetNextToken(); - - // Run the main "interpreter loop" now. - MainLoop(scanner, parser); - - // Print out all of the generated code. - LLVM.DumpModule(module); - } - - private static void MainLoop(ILexer lexer, IParser parser) - { - // top ::= definition | external | expression | ';' - while (true) - { - Console.Write("ready> "); - switch (lexer.CurrentToken) - { - case (int)Token.EOF: - return; - case ';': - lexer.GetNextToken(); - break; - case (int)Token.DEF: - parser.HandleDefinition(); - break; - case (int)Token.EXTERN: - parser.HandleExtern(); - break; - default: - parser.HandleTopLevelExpression(); - break; - } - } - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter3/Program.cs b/samples/KaleidoscopeTutorial/Chapter3/Program.cs new file mode 100644 index 00000000..566fdd67 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter3/Program.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter3; +using LLVMSharp.Interop; + +// The built-in operator precedences (higher binds tighter). The lexer and parser share this +// dictionary; from chapter 6 the parser adds user-defined operators to it at run time. +var binaryOpPrecedence = new Dictionary +{ + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Parser(lexer); +var visitor = new CodeGenVisitor(); + +LLVMContextRef context = LLVMContextRef.Create(); +LLVMModuleRef module = context.CreateModuleWithName("KaleidoscopeModule"); + +var driver = new DumpDriver(lexer, parser, visitor, module); +driver.Run(); + +Console.WriteLine(); +Console.WriteLine("=== Full module IR ==="); +Console.Write(module.PrintToString()); diff --git a/samples/KaleidoscopeTutorial/Chapter4/Chapter4.csproj b/samples/KaleidoscopeTutorial/Chapter4/Chapter4.csproj new file mode 100644 index 00000000..678424d9 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter4/Chapter4.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter4/JitReplDriver.cs b/samples/KaleidoscopeTutorial/Chapter4/JitReplDriver.cs new file mode 100644 index 00000000..29f19819 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter4/JitReplDriver.cs @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter4; + +/// +/// Chapter 4 — "Adding JIT and optimizer support". Reuses chapter 3's code generator unchanged, but +/// instead of just printing IR it optimizes each top-level item and feeds it to an ORC LLJIT so +/// top-level expressions actually run. Each item is compiled into its own module; the anonymous +/// expression is added under a resource tracker and removed after it runs, so the REPL can evaluate +/// any number of expressions in a row (upstream issue #1, which the old MCJIT sample could not do). +/// +/// This driver is deliberately independent of the code generator's chapter — later chapters reuse it +/// as-is and only pass in their own . +/// +public sealed unsafe class JitReplDriver : ReplDriver, IDisposable +{ + // Function-level cleanup/optimization pipeline, parsed by the new pass manager. mem2reg only does + // work once chapter 7 introduces allocas; it is harmless before then, so one pipeline serves all. + private const string PassPipeline = "function(mem2reg,instcombine,reassociate,gvn,simplifycfg)"; + + private readonly CodeGenVisitorBase _visitor; + private readonly KaleidoscopeJit _jit; + private readonly LLVMTargetMachineRef _targetMachine; + + public JitReplDriver(Lexer lexer, Parser parser, CodeGenVisitorBase visitor) + : base(lexer, parser) + { + _visitor = visitor; + _jit = new KaleidoscopeJit(); + _targetMachine = LlvmSupport.CreateHostTargetMachine(); + + // Make putchard/printd available to any 'extern' that references them. + HostFunctions.DefineAll(_jit); + } + + protected override void OnDefinition(FunctionAST function) + { + LLVMModuleRef module = CreateModule(); + _visitor.SetModule(module); + + LLVMValueRef ir = _visitor.CodegenFunction(function); + Console.WriteLine("Read function definition:"); + Console.Write(ir); + + Optimize(module); + _jit.AddModule(module); // keep the definition around so later expressions can call it. + } + + protected override void OnExtern(PrototypeAST prototype) + { + // A declaration needs no module of its own — remember it so it is emitted into whichever + // module first references it (see CodeGenVisitorBase.GetFunction). + _visitor.RegisterPrototype(prototype); + Console.WriteLine($"Read extern: {prototype.Name}"); + } + + protected override void OnTopLevelExpression(FunctionAST function) + { + LLVMModuleRef module = CreateModule(); + _visitor.SetModule(module); + _visitor.CodegenFunction(function); + Optimize(module); + + // Add under a resource tracker so we can drop this anonymous module once we've called it, + // freeing the __anon_expr name for the next top-level expression. + nint tracker = _jit.AddModuleRemovable(module); + try + { + ulong address = _jit.Lookup(Parser.AnonymousExpressionName); + var evaluate = (delegate* unmanaged[Cdecl])address; + Console.WriteLine($"Evaluated to {evaluate()}"); + } + finally + { + _jit.RemoveModule(tracker); + } + } + + private LLVMModuleRef CreateModule() + { + // Each module is built in its own context; the JIT takes ownership of that context when the + // module is added. Stamp it with the JIT's data layout/triple so codegen matches the target. + LLVMContextRef context = LLVMContextRef.Create(); + LLVMModuleRef module = context.CreateModuleWithName("KaleidoscopeModule"); + _jit.ConfigureModule(module); + return module; + } + + private void Optimize(LLVMModuleRef module) + { + using LLVMPassBuilderOptionsRef options = LLVMPassBuilderOptionsRef.Create(); + using var passes = new MarshaledString(PassPipeline); + LLVMOpaqueError* error = LLVM.RunPasses(module, passes, _targetMachine, options); + LlvmSupport.ThrowIfError(error, "Optimization failed"); + } + + public void Dispose() => _jit.Dispose(); +} diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope.sln b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope.sln deleted file mode 100644 index 1bb9c05b..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaleidoscope", "Kaleidoscope\Kaleidoscope.csproj", "{CD23CB2E-951B-4FAD-A4CD-4048731DBA31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KaleidoscopeLLVM", "KaleidoscopeLLVM\KaleidoscopeLLVM.csproj", "{C1147A13-4174-410A-A891-335D858703B7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.Build.0 = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.Build.0 = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|x64.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/BinaryExprAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/BinaryExprAST.cs deleted file mode 100644 index 1143f724..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/BinaryExprAST.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System; - - public sealed class BinaryExprAST : ExprAST - { - public BinaryExprAST(char op, ExprAST lhs, ExprAST rhs) - { - switch (op) - { - case '+': - this.NodeType = ExprType.AddExpr; - break; - case '-': - this.NodeType = ExprType.SubtractExpr; - break; - case '*': - this.NodeType = ExprType.MultiplyExpr; - break; - case '<': - this.NodeType = ExprType.LessThanExpr; - break; - default: - throw new ArgumentException("op " + op + " is not a valid operator"); - } - - this.Lhs = lhs; - this.Rhs = rhs; - } - - public ExprAST Lhs { get; private set; } - - public ExprAST Rhs { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitBinaryExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/CallExprAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/CallExprAST.cs deleted file mode 100644 index 1a722b27..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/CallExprAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class CallExprAST : ExprAST - { - public CallExprAST(string callee, List args) - { - this.Callee = callee; - this.Arguments = args; - this.NodeType = ExprType.CallExpr; - } - - public string Callee { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitCallExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprAST.cs deleted file mode 100644 index 6841cbec..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprAST.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprAST - { - public abstract ExprType NodeType { get; protected set; } - - protected internal virtual ExprAST VisitChildren(ExprVisitor visitor) - { - return visitor.Visit(this); - } - - protected internal virtual ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitExtension(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprType.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprType.cs deleted file mode 100644 index e890f346..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Kaleidoscope.AST -{ - public enum ExprType - { - AddExpr, - SubtractExpr, - MultiplyExpr, - LessThanExpr, - CallExpr, - VariableExpr, - PrototypeExpr, - FunctionExpr, - NumberExpr - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprVisitor.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprVisitor.cs deleted file mode 100644 index a357e1d4..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/ExprVisitor.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprVisitor - { - protected ExprVisitor() - { - } - - public virtual ExprAST Visit(ExprAST node) - { - return node?.Accept(this); - } - - protected internal virtual ExprAST VisitExtension(ExprAST node) - { - return node.VisitChildren(this); - } - - protected internal virtual ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - return node; - } - - protected internal virtual ExprAST VisitCallExprAST(CallExprAST node) - { - foreach (var argument in node.Arguments) - { - this.Visit(argument); - } - - return node; - } - - protected internal virtual ExprAST VisitFunctionAST(FunctionAST node) - { - this.Visit(node.Proto); - this.Visit(node.Body); - - return node; - } - - protected internal virtual ExprAST VisitVariableExprAST(VariableExprAST node) - { - return node; - } - - protected internal virtual ExprAST VisitPrototypeAST(PrototypeAST node) - { - return node; - } - - protected internal virtual ExprAST VisitNumberExprAST(NumberExprAST node) - { - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/FunctionAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/FunctionAST.cs deleted file mode 100644 index a5e90405..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/FunctionAST.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class FunctionAST : ExprAST - { - public FunctionAST(PrototypeAST proto, ExprAST body) - { - this.Proto = proto; - this.Body = body; - this.NodeType = ExprType.FunctionExpr; - } - - public PrototypeAST Proto { get; private set; } - - public ExprAST Body { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitFunctionAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/NumberExprAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/NumberExprAST.cs deleted file mode 100644 index bd823cd3..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/NumberExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class NumberExprAST : ExprAST - { - public NumberExprAST(double value) - { - this.Value = value; - this.NodeType = ExprType.NumberExpr; - } - - public double Value { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitNumberExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/PrototypeAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/PrototypeAST.cs deleted file mode 100644 index 3d0f6b4a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/PrototypeAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class PrototypeAST : ExprAST - { - public PrototypeAST(string name, List args) - { - this.Name = name; - this.Arguments = args; - this.NodeType = ExprType.PrototypeExpr; - } - - public string Name { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitPrototypeAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/VariableExprAST.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/VariableExprAST.cs deleted file mode 100644 index 41aeb6c2..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/AST/VariableExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class VariableExprAST : ExprAST - { - public VariableExprAST(string name) - { - this.Name = name; - this.NodeType = ExprType.VariableExpr; - } - - public string Name { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitVariableExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/BaseParserListener.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/BaseParserListener.cs deleted file mode 100644 index 6fb50254..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/BaseParserListener.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using AST; - - internal sealed class BaseParserListener - { - private static readonly Type IParserListenerType = typeof(IParserListener); - - private readonly Stack descentStack = new Stack(); - - private readonly Stack ascentStack = new Stack(); - - private readonly IParserListener listener; - - public BaseParserListener(IParserListener listener) - { - this.listener = listener; - } - - public void EnterRule(string ruleName) - { - this.descentStack.Push(ruleName); - } - - public void ExitRule(ExprAST argument) - { - string ruleName = this.descentStack.Pop(); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Exit" + ruleName), this.listener, - argument)); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Enter" + ruleName), this.listener, - argument)); - } - - public void Listen() - { - if (this.listener != null) - { - while (this.ascentStack.Count != 0) - { - var context = this.ascentStack.Pop(); - context.MethodInfo.Invoke(context.Instance, new object[] {context.Argument}); - } - } - } - - private sealed class ASTContext - { - public ASTContext(MethodInfo methodInfo, object instance, ExprAST argument) - { - this.MethodInfo = methodInfo; - this.Instance = instance; - this.Argument = argument; - } - - public MethodInfo MethodInfo { get; private set; } - - public ExprAST Argument { get; set; } - - public object Instance { get; private set; } - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/ILexer.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/ILexer.cs deleted file mode 100644 index aca5d18f..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/ILexer.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Kaleidoscope -{ - public interface ILexer - { - int CurrentToken { get; } - - string GetLastIdentifier(); - - double GetLastNumber(); - - int GetTokPrecedence(); - - int GetNextToken(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParser.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParser.cs deleted file mode 100644 index adf84705..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Kaleidoscope -{ - public interface IParser - { - void HandleDefinition(); - - void HandleExtern(); - - void HandleTopLevelExpression(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParserListener.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParserListener.cs deleted file mode 100644 index fbc91f82..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/IParserListener.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Kaleidoscope -{ - using AST; - - public interface IParserListener - { - void EnterHandleDefinition(FunctionAST data); - - void ExitHandleDefinition(FunctionAST data); - - void EnterHandleExtern(PrototypeAST data); - - void ExitHandleExtern(PrototypeAST data); - - void EnterHandleTopLevelExpression(FunctionAST data); - - void ExitHandleTopLevelExpression(FunctionAST data); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Kaleidoscope.csproj b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Kaleidoscope.csproj deleted file mode 100644 index 09321765..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Kaleidoscope.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - net10.0 - - diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Lexer.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Lexer.cs deleted file mode 100644 index f73d0612..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Lexer.cs +++ /dev/null @@ -1,133 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - - public sealed class Lexer : ILexer - { - private const int EOF = -1; - - private readonly TextReader reader; - - private readonly StringBuilder identifierBuilder = new StringBuilder(); - - private readonly StringBuilder numberBuilder = new StringBuilder(); - - private readonly Dictionary binopPrecedence; - - private int c = ' '; - - private string identifier; - - private double numVal; - - public Lexer(TextReader reader, Dictionary binOpPrecedence) - { - this.reader = reader; - this.binopPrecedence = binOpPrecedence; - } - - public int CurrentToken { get; private set; } - - public string GetLastIdentifier() - { - return this.identifier; - } - - public double GetLastNumber() - { - return this.numVal; - } - - public int GetTokPrecedence() - { - // Make sure it's a declared binop. - if (this.binopPrecedence.TryGetValue((char)this.CurrentToken, out var tokPrec)) - { - return tokPrec; - } - - return -1; - } - - public int GetNextToken() - { - // Skip any whitespace. - while (char.IsWhiteSpace((char)c)) - { - c = this.reader.Read(); - } - - if (char.IsLetter((char)c)) // identifier: [a-zA-Z][a-zA-Z0-9]* - { - this.identifierBuilder.Append((char)c); - while (char.IsLetterOrDigit((char)(c = this.reader.Read()))) - { - this.identifierBuilder.Append((char)c); - } - - this.identifier = this.identifierBuilder.ToString(); - this.identifierBuilder.Clear(); - - if (string.Equals(identifier, "def", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.DEF; - } - else if (string.Equals(identifier, "extern", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.EXTERN; - } - else - { - this.CurrentToken = (int)Token.IDENTIFIER; - } - - return this.CurrentToken; - } - - // Number: [0-9.]+ - if (char.IsDigit((char)c) || c == '.') - { - do - { - this.numberBuilder.Append((char)c); - c = this.reader.Read(); - } while (char.IsDigit((char)c) || c == '.'); - - this.numVal = double.Parse(this.numberBuilder.ToString()); - this.numberBuilder.Clear(); - this.CurrentToken = (int)Token.NUMBER; - - return this.CurrentToken; - } - - if (c == '#') - { - // Comment until end of line. - do - { - c = this.reader.Read(); - } while (c != EOF && c != '\n' && c != '\r'); - - if (c != EOF) - { - return this.GetNextToken(); - } - } - - // Check for end of file. Don't eat the EOF. - if (c == EOF) - { - this.CurrentToken = c; - return (int)Token.EOF; - } - - this.CurrentToken = c; - c = this.reader.Read(); - return this.c; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Parser.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Parser.cs deleted file mode 100644 index bd17834f..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Parser.cs +++ /dev/null @@ -1,313 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using AST; - - public sealed class Parser : IParser - { - private readonly Lexer scanner; - - private readonly BaseParserListener baseListener; - - public Parser(Lexer scanner, IParserListener listener) - { - this.scanner = scanner; - this.baseListener = new BaseParserListener(listener); - } - - public void HandleDefinition() - { - this.baseListener.EnterRule("HandleDefinition"); - - var functionAST = this.ParseDefinition(); - - baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleExtern() - { - this.baseListener.EnterRule("HandleExtern"); - - var prototypeAST = this.ParseExtern(); - - this.baseListener.ExitRule(prototypeAST); - - if (prototypeAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleTopLevelExpression() - { - // Evaluate a top-level expression into an anonymous function. - this.baseListener.EnterRule("HandleTopLevelExpression"); - - var functionAST = this.ParseTopLevelExpr(); - - this.baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - // identifierexpr - // ::= identifier - // ::= identifier '(' expression* ')' - private ExprAST ParseIdentifierExpr() - { - string idName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); // eat identifier. - - if (this.scanner.CurrentToken != '(') // Simple variable ref. - { - return new VariableExprAST(idName); - } - - // Call. - this.scanner.GetNextToken(); // eat ( - List args = new List(); - - if (this.scanner.CurrentToken != ')') - { - while (true) - { - ExprAST arg = this.ParseExpression(); - if (arg == null) - { - return null; - } - - args.Add(arg); - - if (this.scanner.CurrentToken == ')') - { - break; - } - - if (this.scanner.CurrentToken != ',') - { - Console.WriteLine("Expected ')' or ',' in argument list"); - return null; - } - - this.scanner.GetNextToken(); - } - } - - // Eat the ')'. - this.scanner.GetNextToken(); - - return new CallExprAST(idName, args); - } - - // numberexpr ::= number - private ExprAST ParseNumberExpr() - { - ExprAST result = new NumberExprAST(this.scanner.GetLastNumber()); - this.scanner.GetNextToken(); - return result; - } - - // parenexpr ::= '(' expression ')' - private ExprAST ParseParenExpr() - { - this.scanner.GetNextToken(); // eat (. - ExprAST v = this.ParseExpression(); - if (v == null) - { - return null; - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("expected ')'"); - return null; - } - - this.scanner.GetNextToken(); // eat ). - - return v; - } - - // primary - // ::= identifierexpr - // ::= numberexpr - // ::= parenexpr - private ExprAST ParsePrimary() - { - switch (this.scanner.CurrentToken) - { - case (int) Token.IDENTIFIER: - return this.ParseIdentifierExpr(); - case (int) Token.NUMBER: - return this.ParseNumberExpr(); - case '(': - return this.ParseParenExpr(); - default: - Console.WriteLine("unknown token when expecting an expression"); - return null; - } - } - - // binoprhs - // ::= ('+' primary)* - private ExprAST ParseBinOpRHS(int exprPrec, ExprAST lhs) - { - // If this is a binop, find its precedence. - while (true) - { - int tokPrec = this.scanner.GetTokPrecedence(); - - // If this is a binop that binds at least as tightly as the current binop, - // consume it, otherwise we are done. - if (tokPrec < exprPrec) - { - return lhs; - } - - // Okay, we know this is a binop. - int binOp = this.scanner.CurrentToken; - this.scanner.GetNextToken(); // eat binop - - // Parse the primary expression after the binary operator. - ExprAST rhs = this.ParsePrimary(); - if (rhs == null) - { - return null; - } - - // If BinOp binds less tightly with RHS than the operator after RHS, let - // the pending operator take RHS as its LHS. - int nextPrec = this.scanner.GetTokPrecedence(); - if (tokPrec < nextPrec) - { - rhs = this.ParseBinOpRHS(tokPrec + 1, rhs); - if (rhs == null) - { - return null; - } - } - - // Merge LHS/RHS. - lhs = new BinaryExprAST((char) binOp, lhs, rhs); - } - } - - // expression - // ::= primary binoprhs - // - private ExprAST ParseExpression() - { - ExprAST lhs = this.ParsePrimary(); - if (lhs == null) - { - return null; - } - - return this.ParseBinOpRHS(0, lhs); - } - - // prototype - // ::= id '(' id* ')' - private PrototypeAST ParsePrototype() - { - if (this.scanner.CurrentToken != (int) Token.IDENTIFIER) - { - Console.WriteLine("Expected function name in prototype"); - return null; - } - - string fnName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); - - if (this.scanner.CurrentToken != '(') - { - Console.WriteLine("Expected '(' in prototype"); - return null; - } - - List argNames = new List(); - while (this.scanner.GetNextToken() == (int) Token.IDENTIFIER) - { - argNames.Add(this.scanner.GetLastIdentifier()); - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("Expected ')' in prototype"); - return null; - } - - this.scanner.GetNextToken(); // eat ')'. - - return new PrototypeAST(fnName, argNames); - } - - // definition ::= 'def' prototype expression - private FunctionAST ParseDefinition() - { - this.scanner.GetNextToken(); // eat def. - PrototypeAST proto = this.ParsePrototype(); - - if (proto == null) - { - return null; - } - - ExprAST body = this.ParseExpression(); - if (body == null) - { - return null; - } - - return new FunctionAST(proto, body); - } - - /// toplevelexpr ::= expression - private FunctionAST ParseTopLevelExpr() - { - ExprAST e = this.ParseExpression(); - if (e == null) - { - return null; - } - - // Make an anonymous proto. - PrototypeAST proto = new PrototypeAST(string.Empty, new List()); - return new FunctionAST(proto, e); - } - - /// external ::= 'extern' prototype - private PrototypeAST ParseExtern() - { - this.scanner.GetNextToken(); // eat extern. - return this.ParsePrototype(); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Token.cs b/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Token.cs deleted file mode 100644 index a4f84c23..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/Kaleidoscope/Token.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Kaleidoscope -{ - public enum Token - { - EOF = -1, - DEF = -2, - EXTERN = -3, - IDENTIFIER = -4, - NUMBER = -5, - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenParserListener.cs b/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenParserListener.cs deleted file mode 100644 index d1cd661f..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenParserListener.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using LLVMSharp; - -namespace KaleidoscopeLLVM -{ - using Kaleidoscope; - using Kaleidoscope.AST; - - internal sealed class CodeGenParserListener : IParserListener - { - private readonly CodeGenVisitor visitor; - - private readonly LLVMExecutionEngineRef ee; - - private readonly LLVMPassManagerRef passManager; - - public CodeGenParserListener(LLVMExecutionEngineRef ee, LLVMPassManagerRef passManager, CodeGenVisitor visitor) - { - this.visitor = visitor; - this.ee = ee; - this.passManager = passManager; - } - - public void EnterHandleDefinition(FunctionAST data) - { - } - - public void ExitHandleDefinition(FunctionAST data) - { - this.visitor.Visit(data); - var function = this.visitor.ResultStack.Pop(); - // LLVM.DumpValue(function); - - LLVM.RunFunctionPassManager(this.passManager, function); - // LLVM.DumpValue(function); // Dump the function for exposition purposes. - } - - public void EnterHandleExtern(PrototypeAST data) - { - } - - public void ExitHandleExtern(PrototypeAST data) - { - this.visitor.Visit(data); - // LLVM.DumpValue(this.visitor.ResultStack.Pop()); - } - - public void EnterHandleTopLevelExpression(FunctionAST data) - { - } - - public void ExitHandleTopLevelExpression(FunctionAST data) - { - this.visitor.Visit(data); - var anonymousFunction = this.visitor.ResultStack.Pop(); -// LLVM.DumpValue(anonymousFunction); // Dump the function for exposition purposes. - var dFunc = (Program.D) Marshal.GetDelegateForFunctionPointer( - LLVM.GetPointerToGlobal(this.ee, anonymousFunction), typeof(Program.D)); - LLVM.RunFunctionPassManager(this.passManager, anonymousFunction); - -// LLVM.DumpValue(anonymousFunction); // Dump the function for exposition purposes. - Console.WriteLine("Evaluated to " + dFunc()); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenVisitor.cs deleted file mode 100644 index 275b1796..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/CodeGenVisitor.cs +++ /dev/null @@ -1,199 +0,0 @@ -namespace KaleidoscopeLLVM -{ - using System; - using System.Collections.Generic; - using Kaleidoscope.AST; - using LLVMSharp; - - internal sealed class CodeGenVisitor : ExprVisitor - { - private static readonly LLVMBool LLVMBoolFalse = new LLVMBool(0); - - private static readonly LLVMValueRef NullValue = new LLVMValueRef(IntPtr.Zero); - - private readonly LLVMModuleRef module; - - private readonly LLVMBuilderRef builder; - - private readonly Dictionary namedValues = new Dictionary(); - - private readonly Stack valueStack = new Stack(); - - public CodeGenVisitor(LLVMModuleRef module, LLVMBuilderRef builder) - { - this.module = module; - this.builder = builder; - } - - public Stack ResultStack => valueStack; - - public void ClearResultStack() - { - this.valueStack.Clear(); - } - - protected override ExprAST VisitNumberExprAST(NumberExprAST node) - { - this.valueStack.Push(LLVM.ConstReal(LLVM.DoubleType(), node.Value)); - return node; - } - - protected override ExprAST VisitVariableExprAST(VariableExprAST node) - { - // Look this variable up in the function. - if (this.namedValues.TryGetValue(node.Name, out var value)) - { - this.valueStack.Push(value); - } - else - { - throw new Exception($"Unknown variable name {node.Name}"); - } - - return node; - } - - protected override ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - LLVMValueRef r = this.valueStack.Pop(); - LLVMValueRef l = this.valueStack.Pop(); - - LLVMValueRef n; - - switch (node.NodeType) - { - case ExprType.AddExpr: - n = LLVM.BuildFAdd(this.builder, l, r, "addtmp"); - break; - case ExprType.SubtractExpr: - n = LLVM.BuildFSub(this.builder, l, r, "subtmp"); - break; - case ExprType.MultiplyExpr: - n = LLVM.BuildFMul(this.builder, l, r, "multmp"); - break; - case ExprType.LessThanExpr: - // Convert bool 0/1 to double 0.0 or 1.0 - n = LLVM.BuildUIToFP(this.builder, - LLVM.BuildFCmp(this.builder, LLVMRealPredicate.LLVMRealULT, l, r, "cmptmp"), LLVM.DoubleType(), - "booltmp"); - break; - default: - throw new Exception("invalid binary operator"); - } - - this.valueStack.Push(n); - return node; - } - - protected override ExprAST VisitCallExprAST(CallExprAST node) - { - var calleeF = LLVM.GetNamedFunction(this.module, node.Callee); - if (calleeF.Pointer == IntPtr.Zero) - { - throw new Exception($"Unknown function referenced {node.Callee}"); - } - - if (LLVM.CountParams(calleeF) != node.Arguments.Count) - { - throw new Exception("Incorrect # arguments passed"); - } - - var argumentCount = (uint) node.Arguments.Count; - var argsV = new LLVMValueRef[Math.Max(argumentCount, 1)]; - for (int i = 0; i < argumentCount; ++i) - { - this.Visit(node.Arguments[i]); - argsV[i] = this.valueStack.Pop(); - } - - this.valueStack.Push(LLVM.BuildCall(this.builder, calleeF, argsV, "calltmp")); - - return node; - } - - protected override ExprAST VisitPrototypeAST(PrototypeAST node) - { - // Make the function type: double(double,double) etc. - var argumentCount = (uint) node.Arguments.Count; - var arguments = new LLVMTypeRef[Math.Max(argumentCount, 0)]; - - var function = LLVM.GetNamedFunction(this.module, node.Name); - - // If F conflicted, there was already something named 'Name'. If it has a - // body, don't allow redefinition or reextern. - if (function.Pointer != IntPtr.Zero) - { - // If F already has a body, reject this. - if (LLVM.CountBasicBlocks(function) != 0) - { - throw new Exception("redefinition of function."); - } - - // If F took a different number of args, reject. - if (LLVM.CountParams(function) != argumentCount) - { - throw new Exception("redefinition of function with different # args"); - } - } - else - { - for (int i = 0; i < argumentCount; ++i) - { - arguments[i] = LLVM.DoubleType(); - } - - function = LLVM.AddFunction(this.module, node.Name, - LLVM.FunctionType(LLVM.DoubleType(), arguments, LLVMBoolFalse)); - LLVM.SetLinkage(function, LLVMLinkage.LLVMExternalLinkage); - } - - for (int i = 0; i < argumentCount; ++i) - { - var argumentName = node.Arguments[i]; - - LLVMValueRef param = LLVM.GetParam(function, (uint) i); - LLVM.SetValueName(param, argumentName); - - this.namedValues[argumentName] = param; - } - - this.valueStack.Push(function); - return node; - } - - protected override ExprAST VisitFunctionAST(FunctionAST node) - { - namedValues.Clear(); - - Visit(node.Proto); - - LLVMValueRef function = valueStack.Pop(); - - // Create a new basic block to start insertion into. - LLVM.PositionBuilderAtEnd(builder, LLVM.AppendBasicBlock(function, "entry")); - - try - { - Visit(node.Body); - } - catch (Exception) - { - LLVM.DeleteFunction(function); - throw; - } - - // Finish off the function. - LLVM.BuildRet(builder, valueStack.Pop()); - - // Validate the generated code, checking for consistency. - LLVM.VerifyFunction(function, LLVMVerifierFailureAction.LLVMPrintMessageAction); - - valueStack.Push(function); - - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj b/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj deleted file mode 100644 index 96ff9d4b..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net10.0 - - - - - - - - - - diff --git a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/Program.cs b/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/Program.cs deleted file mode 100644 index edfc659c..00000000 --- a/samples/KaleidoscopeTutorial/Chapter4/KaleidoscopeLLVM/Program.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace KaleidoscopeLLVM -{ - using System; - using System.Collections.Generic; - using Kaleidoscope; - using LLVMSharp; - - public sealed class Program - { - public delegate double D(); - - public static void Main(string[] args) - { - // Make the module, which holds all the code. - LLVMModuleRef module = LLVM.ModuleCreateWithName("my cool jit"); - LLVMBuilderRef builder = LLVM.CreateBuilder(); - LLVM.LinkInMCJIT(); - LLVM.InitializeX86TargetMC(); - LLVM.InitializeX86Target(); - LLVM.InitializeX86TargetInfo(); - LLVM.InitializeX86AsmParser(); - LLVM.InitializeX86AsmPrinter(); - - if (LLVM.CreateExecutionEngineForModule(out var engine, module, out var errorMessage).Value == 1) - { - Console.WriteLine(errorMessage); - // LLVM.DisposeMessage(errorMessage); - return; - } - - // Create a function pass manager for this engine - LLVMPassManagerRef passManager = LLVM.CreateFunctionPassManagerForModule(module); - - // Set up the optimizer pipeline. Start with registering info about how the - // target lays out data structures. - // LLVM.DisposeTargetData(LLVM.GetExecutionEngineTargetData(engine)); - - // Provide basic AliasAnalysis support for GVN. - LLVM.AddBasicAliasAnalysisPass(passManager); - - // Promote allocas to registers. - LLVM.AddPromoteMemoryToRegisterPass(passManager); - - // Do simple "peephole" optimizations and bit-twiddling optzns. - LLVM.AddInstructionCombiningPass(passManager); - - // Reassociate expressions. - LLVM.AddReassociatePass(passManager); - - // Eliminate Common SubExpressions. - LLVM.AddGVNPass(passManager); - - // Simplify the control flow graph (deleting unreachable blocks, etc). - LLVM.AddCFGSimplificationPass(passManager); - - LLVM.InitializeFunctionPassManager(passManager); - - var codeGenlistener = new CodeGenParserListener(engine, passManager, new CodeGenVisitor(module, builder)); - - // Install standard binary operators. - // 1 is lowest precedence. - var binopPrecedence = new Dictionary - { - ['<'] = 10, - ['+'] = 20, - ['-'] = 20, - ['*'] = 40 - }; - // highest. - - var scanner = new Lexer(Console.In, binopPrecedence); - var parser = new Parser(scanner, codeGenlistener); - - // Prime the first token. - Console.Write("ready> "); - scanner.GetNextToken(); - - // Run the main "interpreter loop" now. - MainLoop(scanner, parser); - - // Print out all of the generated code. - LLVM.DumpModule(module); - } - - private static void MainLoop(ILexer lexer, IParser parser) - { - // top ::= definition | external | expression | ';' - while (true) - { - Console.Write("ready> "); - switch (lexer.CurrentToken) - { - case (int) Token.EOF: - return; - case ';': - lexer.GetNextToken(); - break; - case (int) Token.DEF: - parser.HandleDefinition(); - break; - case (int) Token.EXTERN: - parser.HandleExtern(); - break; - default: - parser.HandleTopLevelExpression(); - break; - } - } - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter4/Program.cs b/samples/KaleidoscopeTutorial/Chapter4/Program.cs new file mode 100644 index 00000000..eb7b54e8 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter4/Program.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter4; + +// Chapter 4 keeps chapter 3's frontend and code generator verbatim and only swaps the dump driver for +// a JIT driver, so a top-level expression is compiled and executed instead of just printed. +var binaryOpPrecedence = new Dictionary +{ + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Parser(lexer); +var visitor = new Kaleidoscope.Chapter3.CodeGenVisitor(); + +using var driver = new JitReplDriver(lexer, parser, visitor); +driver.Run(); diff --git a/samples/KaleidoscopeTutorial/Chapter5/Chapter5.csproj b/samples/KaleidoscopeTutorial/Chapter5/Chapter5.csproj new file mode 100644 index 00000000..e5d166ab --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter5/Chapter5.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter5/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter5/CodeGenVisitor.cs new file mode 100644 index 00000000..ba4e95f5 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter5/CodeGenVisitor.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter5; + +/// +/// Chapter 5 code generation delta — lowers if/then/else and for/in to control flow. +/// The loop variable here is an SSA PHI node; chapter 7 revisits it with stack allocations so it can +/// be mutated. Everything else is inherited from chapter 3. +/// +public class CodeGenVisitor : Chapter3.CodeGenVisitor +{ + protected override LLVMValueRef CodegenIf(IfExprAST node) + { + LLVMValueRef condition = Codegen(node.Condition); + + // Convert the condition to a bool by comparing it to 0.0. + LLVMValueRef zero = LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + condition = Builder.BuildFCmp(LLVMRealPredicate.LLVMRealONE, condition, zero, "ifcond"); + + LLVMValueRef function = Builder.InsertBlock.Parent; + LLVMBasicBlockRef thenBlock = function.AppendBasicBlock("then"); + LLVMBasicBlockRef elseBlock = function.AppendBasicBlock("else"); + LLVMBasicBlockRef mergeBlock = function.AppendBasicBlock("ifcont"); + + Builder.BuildCondBr(condition, thenBlock, elseBlock); + + // Emit the 'then' value. + Builder.PositionAtEnd(thenBlock); + LLVMValueRef thenValue = Codegen(node.Then); + Builder.BuildBr(mergeBlock); + thenBlock = Builder.InsertBlock; // codegen of 'then' may have changed the current block. + + // Emit the 'else' value. + Builder.PositionAtEnd(elseBlock); + LLVMValueRef elseValue = Codegen(node.Else); + Builder.BuildBr(mergeBlock); + elseBlock = Builder.InsertBlock; // codegen of 'else' may have changed the current block. + + // Emit the merge block with a PHI selecting the value from whichever branch ran. + Builder.PositionAtEnd(mergeBlock); + LLVMValueRef phi = Builder.BuildPhi(Module.Context.DoubleType, "iftmp"); + phi.AddIncoming([thenValue, elseValue], [thenBlock, elseBlock], 2); + return phi; + } + + protected override LLVMValueRef CodegenFor(ForExprAST node) + { + // Emit the start value first, in the current (preheader) block. + LLVMValueRef startValue = Codegen(node.Start); + + LLVMValueRef function = Builder.InsertBlock.Parent; + LLVMBasicBlockRef preheaderBlock = Builder.InsertBlock; + LLVMBasicBlockRef loopBlock = function.AppendBasicBlock("loop"); + Builder.BuildBr(loopBlock); + + // Start the loop body; the loop variable is a PHI merging the start and stepped values. + Builder.PositionAtEnd(loopBlock); + LLVMValueRef variable = Builder.BuildPhi(Module.Context.DoubleType, node.VarName); + variable.AddIncoming([startValue], [preheaderBlock], 1); + + // Bring the loop variable into scope, shadowing any outer binding of the same name. + bool hadOldValue = NamedValues.TryGetValue(node.VarName, out LLVMValueRef oldValue); + NamedValues[node.VarName] = variable; + + // Emit the body; its value is discarded. + Codegen(node.Body); + + // Compute the next value of the loop variable. + LLVMValueRef stepValue = node.Step is not null + ? Codegen(node.Step) + : LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 1.0); + LLVMValueRef nextVariable = Builder.BuildFAdd(variable, stepValue, "nextvar"); + + // Evaluate the end condition (!= 0.0 continues the loop). + LLVMValueRef endCondition = Codegen(node.End); + LLVMValueRef zero = LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + endCondition = Builder.BuildFCmp(LLVMRealPredicate.LLVMRealONE, endCondition, zero, "loopcond"); + + LLVMBasicBlockRef loopEndBlock = Builder.InsertBlock; + LLVMBasicBlockRef afterBlock = function.AppendBasicBlock("afterloop"); + Builder.BuildCondBr(endCondition, loopBlock, afterBlock); + Builder.PositionAtEnd(afterBlock); + + // Wire the stepped value back into the PHI now that we know the loop's back-edge block. + variable.AddIncoming([nextVariable], [loopEndBlock], 1); + + // Restore the shadowed variable. + if (hadOldValue) + { + NamedValues[node.VarName] = oldValue; + } + else + { + NamedValues.Remove(node.VarName); + } + + // A 'for' expression always evaluates to 0.0. + return LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope.sln b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope.sln deleted file mode 100644 index eae9808a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaleidoscope", "Kaleidoscope\Kaleidoscope.csproj", "{CD23CB2E-951B-4FAD-A4CD-4048731DBA31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KaleidoscopeLLVM", "KaleidoscopeLLVM\KaleidoscopeLLVM.csproj", "{C1147A13-4174-410A-A891-335D858703B7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|x64.ActiveCfg = Debug|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Debug|x64.Build.0 = Debug|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|Any CPU.Build.0 = Release|Any CPU - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|x64.ActiveCfg = Release|x64 - {CD23CB2E-951B-4FAD-A4CD-4048731DBA31}.Release|x64.Build.0 = Release|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Debug|x64.ActiveCfg = Debug|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Debug|x64.Build.0 = Debug|x64 - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|Any CPU.Build.0 = Release|Any CPU - {C1147A13-4174-410A-A891-335D858703B7}.Release|x64.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/BinaryExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/BinaryExprAST.cs deleted file mode 100644 index 1143f724..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/BinaryExprAST.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System; - - public sealed class BinaryExprAST : ExprAST - { - public BinaryExprAST(char op, ExprAST lhs, ExprAST rhs) - { - switch (op) - { - case '+': - this.NodeType = ExprType.AddExpr; - break; - case '-': - this.NodeType = ExprType.SubtractExpr; - break; - case '*': - this.NodeType = ExprType.MultiplyExpr; - break; - case '<': - this.NodeType = ExprType.LessThanExpr; - break; - default: - throw new ArgumentException("op " + op + " is not a valid operator"); - } - - this.Lhs = lhs; - this.Rhs = rhs; - } - - public ExprAST Lhs { get; private set; } - - public ExprAST Rhs { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitBinaryExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/CallExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/CallExprAST.cs deleted file mode 100644 index 1a722b27..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/CallExprAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class CallExprAST : ExprAST - { - public CallExprAST(string callee, List args) - { - this.Callee = callee; - this.Arguments = args; - this.NodeType = ExprType.CallExpr; - } - - public string Callee { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitCallExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprAST.cs deleted file mode 100644 index 6841cbec..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprAST.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprAST - { - public abstract ExprType NodeType { get; protected set; } - - protected internal virtual ExprAST VisitChildren(ExprVisitor visitor) - { - return visitor.Visit(this); - } - - protected internal virtual ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitExtension(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprType.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprType.cs deleted file mode 100644 index 5e89158a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprType.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Kaleidoscope.AST -{ - public enum ExprType - { - AddExpr, - SubtractExpr, - MultiplyExpr, - LessThanExpr, - CallExpr, - VariableExpr, - PrototypeExpr, - FunctionExpr, - NumberExpr, - IfExpr, - ForExpr, - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprVisitor.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprVisitor.cs deleted file mode 100644 index 43b36369..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ExprVisitor.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace Kaleidoscope.AST -{ - public abstract class ExprVisitor - { - protected ExprVisitor() - { - } - - public virtual ExprAST Visit(ExprAST node) - { - if (node != null) - { - return node.Accept(this); - } - - return null; - } - - protected internal virtual ExprAST VisitExtension(ExprAST node) - { - return node.VisitChildren(this); - } - - protected internal virtual ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - return node; - } - - protected internal virtual ExprAST VisitCallExprAST(CallExprAST node) - { - foreach (var argument in node.Arguments) - { - this.Visit(argument); - } - - return node; - } - - protected internal virtual ExprAST VisitFunctionAST(FunctionAST node) - { - this.Visit(node.Proto); - this.Visit(node.Body); - - return node; - } - - protected internal virtual ExprAST VisitVariableExprAST(VariableExprAST node) - { - return node; - } - - protected internal virtual ExprAST VisitPrototypeAST(PrototypeAST node) - { - return node; - } - - protected internal virtual ExprAST VisitNumberExprAST(NumberExprAST node) - { - return node; - } - - protected internal virtual ExprAST VisitIfExprAST(IfExpAST node) - { - this.Visit(node.Condition); - this.Visit(node.Then); - this.Visit(node.Else); - - return node; - } - - protected internal virtual ExprAST VisitForExprAST(ForExprAST node) - { - this.Visit(node.Start); - this.Visit(node.End); - this.Visit(node.Step); - this.Visit(node.Body); - - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ForExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ForExprAST.cs deleted file mode 100644 index 1316c221..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/ForExprAST.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class ForExprAST : ExprAST - { - public ForExprAST(string varName, ExprAST start, ExprAST end, ExprAST step, ExprAST body) - { - this.VarName = varName; - this.Start = start; - this.End = end; - this.Step = step; - this.Body = body; - this.NodeType = ExprType.ForExpr; - } - - public string VarName { get; private set; } - - public ExprAST Start { get; private set; } - - public ExprAST End { get; private set; } - - public ExprAST Step { get; private set; } - - public ExprAST Body { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitForExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/FunctionAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/FunctionAST.cs deleted file mode 100644 index a5e90405..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/FunctionAST.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class FunctionAST : ExprAST - { - public FunctionAST(PrototypeAST proto, ExprAST body) - { - this.Proto = proto; - this.Body = body; - this.NodeType = ExprType.FunctionExpr; - } - - public PrototypeAST Proto { get; private set; } - - public ExprAST Body { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitFunctionAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/IfExpAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/IfExpAST.cs deleted file mode 100644 index 12b3acb0..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/IfExpAST.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class IfExpAST : ExprAST - { - public IfExpAST(ExprAST condition, ExprAST then, ExprAST @else) - { - this.Condition = condition; - this.Then = then; - this.Else = @else; - this.NodeType = ExprType.IfExpr; - } - - public ExprAST Condition { get; private set; } - - public ExprAST Then { get; private set; } - - public ExprAST Else { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitIfExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/NumberExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/NumberExprAST.cs deleted file mode 100644 index bd823cd3..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/NumberExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class NumberExprAST : ExprAST - { - public NumberExprAST(double value) - { - this.Value = value; - this.NodeType = ExprType.NumberExpr; - } - - public double Value { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitNumberExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/PrototypeAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/PrototypeAST.cs deleted file mode 100644 index 3d0f6b4a..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/PrototypeAST.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Kaleidoscope.AST -{ - using System.Collections.Generic; - - public sealed class PrototypeAST : ExprAST - { - public PrototypeAST(string name, List args) - { - this.Name = name; - this.Arguments = args; - this.NodeType = ExprType.PrototypeExpr; - } - - public string Name { get; private set; } - - public List Arguments { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitPrototypeAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/VariableExprAST.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/VariableExprAST.cs deleted file mode 100644 index 41aeb6c2..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/AST/VariableExprAST.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kaleidoscope.AST -{ - public sealed class VariableExprAST : ExprAST - { - public VariableExprAST(string name) - { - this.Name = name; - this.NodeType = ExprType.VariableExpr; - } - - public string Name { get; private set; } - - public override ExprType NodeType { get; protected set; } - - protected internal override ExprAST Accept(ExprVisitor visitor) - { - return visitor.VisitVariableExprAST(this); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/BaseParserListener.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/BaseParserListener.cs deleted file mode 100644 index 9eb96731..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/BaseParserListener.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using AST; - - internal sealed class BaseParserListener - { - private static readonly Type IParserListenerType = typeof(IParserListener); - - private readonly Stack descentStack = new Stack(); - - private readonly Stack ascentStack = new Stack(); - - private readonly IParserListener listener; - - public BaseParserListener(IParserListener listener) - { - this.listener = listener; - } - - public void EnterRule(string ruleName) - { - this.descentStack.Push(ruleName); - } - - public void ExitRule(ExprAST argument) - { - string ruleName = this.descentStack.Pop(); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Exit" + ruleName), this.listener, argument)); - this.ascentStack.Push(new ASTContext(IParserListenerType.GetMethod("Enter" + ruleName), this.listener, argument)); - } - - public void Listen() - { - if (this.listener != null) - { - while (this.ascentStack.Count != 0) - { - var context = this.ascentStack.Pop(); - context.MethodInfo.Invoke(context.Instance, new object[] { context.Argument }); - } - } - } - - private sealed class ASTContext - { - public ASTContext(MethodInfo methodInfo, object instance, ExprAST argument) - { - this.MethodInfo = methodInfo; - this.Instance = instance; - this.Argument = argument; - } - - public MethodInfo MethodInfo { get; private set; } - - public ExprAST Argument { get; set; } - - public object Instance { get; private set; } - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/ILexer.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/ILexer.cs deleted file mode 100644 index aca5d18f..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/ILexer.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Kaleidoscope -{ - public interface ILexer - { - int CurrentToken { get; } - - string GetLastIdentifier(); - - double GetLastNumber(); - - int GetTokPrecedence(); - - int GetNextToken(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParser.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParser.cs deleted file mode 100644 index adf84705..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Kaleidoscope -{ - public interface IParser - { - void HandleDefinition(); - - void HandleExtern(); - - void HandleTopLevelExpression(); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParserListener.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParserListener.cs deleted file mode 100644 index fbc91f82..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/IParserListener.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Kaleidoscope -{ - using AST; - - public interface IParserListener - { - void EnterHandleDefinition(FunctionAST data); - - void ExitHandleDefinition(FunctionAST data); - - void EnterHandleExtern(PrototypeAST data); - - void ExitHandleExtern(PrototypeAST data); - - void EnterHandleTopLevelExpression(FunctionAST data); - - void ExitHandleTopLevelExpression(FunctionAST data); - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Kaleidoscope.csproj b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Kaleidoscope.csproj deleted file mode 100644 index 09321765..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Kaleidoscope.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - - net10.0 - - diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Lexer.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Lexer.cs deleted file mode 100644 index 820d53de..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Lexer.cs +++ /dev/null @@ -1,154 +0,0 @@ -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - - public sealed class Lexer : ILexer - { - private const int EOF = -1; - - private readonly TextReader reader; - - private readonly StringBuilder identifierBuilder = new StringBuilder(); - - private readonly StringBuilder numberBuilder = new StringBuilder(); - - private readonly Dictionary binopPrecedence; - - private int c = ' '; - - private string identifier; - - private double numVal; - - public Lexer(TextReader reader, Dictionary binOpPrecedence) - { - this.reader = reader; - this.binopPrecedence = binOpPrecedence; - } - - public int CurrentToken { get; private set; } - - public string GetLastIdentifier() - { - return this.identifier; - } - - public double GetLastNumber() - { - return this.numVal; - } - - public int GetTokPrecedence() - { - // Make sure it's a declared binop. - int tokPrec; - if (this.binopPrecedence.TryGetValue((char)this.CurrentToken, out tokPrec)) - { - return tokPrec; - } - - return -1; - } - - public int GetNextToken() - { - // Skip any whitespace. - while (char.IsWhiteSpace((char)c)) - { - c = this.reader.Read(); - } - - if (char.IsLetter((char)c)) // identifier: [a-zA-Z][a-zA-Z0-9]* - { - this.identifierBuilder.Append((char)c); - while (char.IsLetterOrDigit((char)(c = this.reader.Read()))) - { - this.identifierBuilder.Append((char)c); - } - - this.identifier = this.identifierBuilder.ToString(); - this.identifierBuilder.Clear(); - - if (string.Equals(identifier, "def", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.DEF; - } - else if (string.Equals(identifier, "extern", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.EXTERN; - } - else if (string.Equals(identifier, "if", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.IF; - } - else if (string.Equals(identifier, "then", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.THEN; - } - else if (string.Equals(identifier, "else", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.ELSE; - } - else if (string.Equals(identifier, "for", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.FOR; - } - else if (string.Equals(identifier, "in", StringComparison.Ordinal)) - { - this.CurrentToken = (int)Token.IN; - } - else - { - this.CurrentToken = (int)Token.IDENTIFIER; - } - - return this.CurrentToken; - } - - // Number: [0-9.]+ - if (char.IsDigit((char)c) || c == '.') - { - do - { - this.numberBuilder.Append((char)c); - c = this.reader.Read(); - } while (char.IsDigit((char)c) || c == '.'); - - this.numVal = double.Parse(this.numberBuilder.ToString()); - this.numberBuilder.Clear(); - this.CurrentToken = (int)Token.NUMBER; - - return this.CurrentToken; - } - - if (c == '#') - { - // Comment until end of line. - do - { - c = this.reader.Read(); - } while (c != EOF && c != '\n' && c != '\r'); - - if (c != EOF) - { - return this.GetNextToken(); - } - } - - // Check for end of file. Don't eat the EOF. - if (c == EOF) - { - this.CurrentToken = c; - return (int)Token.EOF; - } - - this.CurrentToken = c; - c = this.reader.Read(); - return this.c; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Parser.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Parser.cs deleted file mode 100644 index f7900a62..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Parser.cs +++ /dev/null @@ -1,431 +0,0 @@ -using System.Diagnostics; - -namespace Kaleidoscope -{ - using System; - using System.Collections.Generic; - using AST; - - public sealed class Parser : IParser - { - private readonly Lexer scanner; - - private readonly BaseParserListener baseListener; - - public Parser(Lexer scanner, IParserListener listener) - { - this.scanner = scanner; - this.baseListener = new BaseParserListener(listener); - } - - public void HandleDefinition() - { - this.baseListener.EnterRule("HandleDefinition"); - - var functionAST = this.ParseDefinition(); - - baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleExtern() - { - this.baseListener.EnterRule("HandleExtern"); - - var prototypeAST = this.ParseExtern(); - - this.baseListener.ExitRule(prototypeAST); - - if (prototypeAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - public void HandleTopLevelExpression() - { - // Evaluate a top-level expression into an anonymous function. - this.baseListener.EnterRule("HandleTopLevelExpression"); - - var functionAST = this.ParseTopLevelExpr(); - - this.baseListener.ExitRule(functionAST); - - if (functionAST != null) - { - this.baseListener.Listen(); - } - else - { - // Skip token for error recovery. - this.scanner.GetNextToken(); - } - } - - // identifierexpr - // ::= identifier - // ::= identifier '(' expression* ')' - private ExprAST ParseIdentifierExpr() - { - string idName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); // eat identifier. - - if (this.scanner.CurrentToken != '(') // Simple variable ref. - { - return new VariableExprAST(idName); - } - - // Call. - this.scanner.GetNextToken(); // eat ( - List args = new List(); - - if (this.scanner.CurrentToken != ')') - { - while (true) - { - ExprAST arg = this.ParseExpression(); - if (arg == null) - { - return null; - } - - args.Add(arg); - - if (this.scanner.CurrentToken == ')') - { - break; - } - - if (this.scanner.CurrentToken != ',') - { - Console.WriteLine("Expected ')' or ',' in argument list"); - return null; - } - - this.scanner.GetNextToken(); - } - } - - // Eat the ')'. - this.scanner.GetNextToken(); - - return new CallExprAST(idName, args); - } - - // numberexpr ::= number - private ExprAST ParseNumberExpr() - { - ExprAST result = new NumberExprAST(this.scanner.GetLastNumber()); - this.scanner.GetNextToken(); - return result; - } - - // parenexpr ::= '(' expression ')' - private ExprAST ParseParenExpr() - { - this.scanner.GetNextToken(); // eat (. - ExprAST v = this.ParseExpression(); - if (v == null) - { - return null; - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("expected ')'"); - return null; - } - - this.scanner.GetNextToken(); // eat ). - - return v; - } - - // ifexpr ::= 'if' expression 'then' expression 'else' expression - public ExprAST ParseIfExpr() - { - this.scanner.GetNextToken(); // eat the if. - - // condition - ExprAST cond = this.ParseExpression(); - if (cond == null) - { - return null; - } - - if (this.scanner.CurrentToken != (int)Token.THEN) - { - Console.WriteLine("expected then"); - } - - this.scanner.GetNextToken(); // eat the then - - ExprAST then = this.ParseExpression(); - if (then == null) - { - return null; - } - - if (this.scanner.CurrentToken != (int)Token.ELSE) - { - Console.WriteLine("expected else"); - return null; - } - - this.scanner.GetNextToken(); - - ExprAST @else = this.ParseExpression(); - if (@else == null) - { - return null; - } - - return new IfExpAST(cond, then, @else); - } - - // forexpr ::= 'for' identifier '=' expr ',' expr (',' expr)? 'in' expression - public ExprAST ParseForExpr() - { - this.scanner.GetNextToken(); // eat the for. - - if (this.scanner.CurrentToken != (int)Token.IDENTIFIER) - { - Console.WriteLine("expected identifier after for"); - return null; - } - - string idName = this.scanner.GetLastIdentifier(); - this.scanner.GetNextToken(); // eat identifier. - - if (this.scanner.CurrentToken != '=') - { - Console.WriteLine("expected '=' after for"); - return null; - } - - this.scanner.GetNextToken(); // eat '='. - - ExprAST start = this.ParseExpression(); - if (start == null) - { - return null; - } - - if (this.scanner.CurrentToken != ',') - { - Console.WriteLine("expected ',' after for start value"); - return null; - } - - this.scanner.GetNextToken(); - - ExprAST end = this.ParseExpression(); - if (end == null) - { - return null; - } - - // The step value is optional; - ExprAST step = null; - if (this.scanner.CurrentToken == ',') - { - this.scanner.GetNextToken(); - step = this.ParseExpression(); - if (step == null) - { - return null; - } - } - - if (this.scanner.CurrentToken != (int)Token.IN) - { - Console.WriteLine("expected 'in' after for"); - return null; - } - - this.scanner.GetNextToken(); - ExprAST body = this.ParseExpression(); - if (body == null) - { - return null; - } - - return new ForExprAST(idName, start, end, step, body); - } - - // primary - // ::= identifierexpr - // ::= numberexpr - // ::= parenexpr - private ExprAST ParsePrimary() - { - switch (this.scanner.CurrentToken) - { - case (int)Token.IDENTIFIER: - return this.ParseIdentifierExpr(); - case (int)Token.NUMBER: - return this.ParseNumberExpr(); - case '(': - return this.ParseParenExpr(); - case (int)Token.IF: - return this.ParseIfExpr(); - case (int)Token.FOR: - return this.ParseForExpr(); - default: - Console.WriteLine("unknown token when expecting an expression"); - return null; - } - } - - // binoprhs - // ::= ('+' primary)* - private ExprAST ParseBinOpRHS(int exprPrec, ExprAST lhs) - { - // If this is a binop, find its precedence. - while (true) - { - int tokPrec = this.scanner.GetTokPrecedence(); - - // If this is a binop that binds at least as tightly as the current binop, - // consume it, otherwise we are done. - if (tokPrec < exprPrec) - { - return lhs; - } - - // Okay, we know this is a binop. - int binOp = this.scanner.CurrentToken; - this.scanner.GetNextToken(); // eat binop - - // Parse the primary expression after the binary operator. - ExprAST rhs = this.ParsePrimary(); - if (rhs == null) - { - return null; - } - - // If BinOp binds less tightly with RHS than the operator after RHS, let - // the pending operator take RHS as its LHS. - int nextPrec = this.scanner.GetTokPrecedence(); - if (tokPrec < nextPrec) - { - rhs = this.ParseBinOpRHS(tokPrec + 1, rhs); - if (rhs == null) - { - return null; - } - } - - // Merge LHS/RHS. - lhs = new BinaryExprAST((char)binOp, lhs, rhs); - } - } - - // expression - // ::= primary binoprhs - // - private ExprAST ParseExpression() - { - ExprAST lhs = this.ParsePrimary(); - if (lhs == null) - { - return null; - } - - return this.ParseBinOpRHS(0, lhs); - } - - // prototype - // ::= id '(' id* ')' - private PrototypeAST ParsePrototype() - { - if (this.scanner.CurrentToken != (int)Token.IDENTIFIER) - { - Console.WriteLine("Expected function name in prototype"); - return null; - } - - string fnName = this.scanner.GetLastIdentifier(); - - this.scanner.GetNextToken(); - - if (this.scanner.CurrentToken != '(') - { - Console.WriteLine("Expected '(' in prototype"); - return null; - } - - List argNames = new List(); - while (this.scanner.GetNextToken() == (int)Token.IDENTIFIER) - { - argNames.Add(this.scanner.GetLastIdentifier()); - } - - if (this.scanner.CurrentToken != ')') - { - Console.WriteLine("Expected ')' in prototype"); - return null; - } - - this.scanner.GetNextToken(); // eat ')'. - - return new PrototypeAST(fnName, argNames); - } - - // definition ::= 'def' prototype expression - private FunctionAST ParseDefinition() - { - this.scanner.GetNextToken(); // eat def. - PrototypeAST proto = this.ParsePrototype(); - - if (proto == null) - { - return null; - } - - ExprAST body = this.ParseExpression(); - if (body == null) - { - return null; - } - - return new FunctionAST(proto, body); - } - - /// toplevelexpr ::= expression - private FunctionAST ParseTopLevelExpr() - { - ExprAST e = this.ParseExpression(); - if (e == null) - { - return null; - } - - // Make an anonymous proto. - PrototypeAST proto = new PrototypeAST(string.Empty, new List()); - return new FunctionAST(proto, e); - } - - /// external ::= 'extern' prototype - private PrototypeAST ParseExtern() - { - this.scanner.GetNextToken(); // eat extern. - return this.ParsePrototype(); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Token.cs b/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Token.cs deleted file mode 100644 index 9d9443ed..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/Kaleidoscope/Token.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Kaleidoscope -{ - public enum Token - { - EOF = -1, - DEF = -2, - EXTERN = -3, - IDENTIFIER = -4, - NUMBER = -5, - IF = -6, - THEN = -7, - ELSE = -8, - FOR = -9, - IN = -10 - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenParserListener.cs b/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenParserListener.cs deleted file mode 100644 index 8648340c..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenParserListener.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using LLVMSharp; - -namespace KaleidoscopeLLVM -{ - using Kaleidoscope; - using Kaleidoscope.AST; - - internal sealed class CodeGenParserListener : IParserListener - { - private readonly CodeGenVisitor visitor; - - private readonly LLVMExecutionEngineRef ee; - - private readonly LLVMPassManagerRef passManager; - - public CodeGenParserListener(LLVMExecutionEngineRef ee, LLVMPassManagerRef passManager, CodeGenVisitor visitor) - { - this.visitor = visitor; - this.ee = ee; - this.passManager = passManager; - } - - public void EnterHandleDefinition(FunctionAST data) - { - } - - public void ExitHandleDefinition(FunctionAST data) - { - this.visitor.Visit(data); - var function = this.visitor.ResultStack.Pop(); - LLVM.DumpValue(function); - - LLVM.RunFunctionPassManager(this.passManager, function); - LLVM.DumpValue(function); // Dump the function for exposition purposes. - } - - public void EnterHandleExtern(PrototypeAST data) - { - } - - public void ExitHandleExtern(PrototypeAST data) - { - this.visitor.Visit(data); - LLVM.DumpValue(this.visitor.ResultStack.Pop()); - } - - public void EnterHandleTopLevelExpression(FunctionAST data) - { - } - - public void ExitHandleTopLevelExpression(FunctionAST data) - { - this.visitor.Visit(data); - var anonymousFunction = this.visitor.ResultStack.Pop(); - LLVM.DumpValue(anonymousFunction); // Dump the function for exposition purposes. - var dFunc = (Program.D)Marshal.GetDelegateForFunctionPointer(LLVM.GetPointerToGlobal(this.ee, anonymousFunction), typeof(Program.D)); - LLVM.RunFunctionPassManager(this.passManager, anonymousFunction); - - LLVM.DumpValue(anonymousFunction); // Dump the function for exposition purposes. - Console.WriteLine("Evaluated to " + dFunc()); - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenVisitor.cs deleted file mode 100644 index 46dea6e0..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/CodeGenVisitor.cs +++ /dev/null @@ -1,350 +0,0 @@ -namespace KaleidoscopeLLVM -{ - using System; - using System.Collections.Generic; - using Kaleidoscope.AST; - using LLVMSharp; - - internal sealed class CodeGenVisitor : ExprVisitor - { - private static readonly LLVMBool LLVMBoolFalse = new LLVMBool(0); - - private static readonly LLVMValueRef NullValue = new LLVMValueRef(IntPtr.Zero); - - private readonly LLVMModuleRef module; - - private readonly LLVMBuilderRef builder; - - private readonly Dictionary namedValues = new Dictionary(); - - private readonly Stack valueStack = new Stack(); - - public CodeGenVisitor(LLVMModuleRef module, LLVMBuilderRef builder) - { - this.module = module; - this.builder = builder; - } - - public Stack ResultStack { get { return valueStack; } } - - public void ClearResultStack() - { - this.valueStack.Clear(); - } - - protected override ExprAST VisitNumberExprAST(NumberExprAST node) - { - this.valueStack.Push(LLVM.ConstReal(LLVM.DoubleType(), node.Value)); - return node; - } - - protected override ExprAST VisitVariableExprAST(VariableExprAST node) - { - LLVMValueRef value; - - // Look this variable up in the function. - if (this.namedValues.TryGetValue(node.Name, out value)) - { - this.valueStack.Push(value); - } - else - { - throw new Exception("Unknown variable name"); - } - - return node; - } - - protected override ExprAST VisitBinaryExprAST(BinaryExprAST node) - { - this.Visit(node.Lhs); - this.Visit(node.Rhs); - - LLVMValueRef r = this.valueStack.Pop(); - LLVMValueRef l = this.valueStack.Pop(); - - LLVMValueRef n; - - switch (node.NodeType) - { - case ExprType.AddExpr: - n = LLVM.BuildFAdd(this.builder, l, r, "addtmp"); - break; - case ExprType.SubtractExpr: - n = LLVM.BuildFSub(this.builder, l, r, "subtmp"); - break; - case ExprType.MultiplyExpr: - n = LLVM.BuildFMul(this.builder, l, r, "multmp"); - break; - case ExprType.LessThanExpr: - // Convert bool 0/1 to double 0.0 or 1.0 - n = LLVM.BuildUIToFP(this.builder, LLVM.BuildFCmp(this.builder, LLVMRealPredicate.LLVMRealULT, l, r, "cmptmp"), LLVM.DoubleType(), "booltmp"); - break; - default: - throw new Exception("invalid binary operator"); - } - - this.valueStack.Push(n); - return node; - } - - protected override ExprAST VisitCallExprAST(CallExprAST node) - { - var calleeF = LLVM.GetNamedFunction(this.module, node.Callee); - if (calleeF.Pointer == IntPtr.Zero) - { - throw new Exception("Unknown function referenced"); - } - - if (LLVM.CountParams(calleeF) != node.Arguments.Count) - { - throw new Exception("Incorrect # arguments passed"); - } - - var argumentCount = (uint)node.Arguments.Count; - var argsV = new LLVMValueRef[Math.Max(argumentCount, 1)]; - for (int i = 0; i < argumentCount; ++i) - { - this.Visit(node.Arguments[i]); - argsV[i] = this.valueStack.Pop(); - } - - valueStack.Push(LLVM.BuildCall(this.builder, calleeF, argsV, "calltmp")); - - return node; - } - - protected override ExprAST VisitPrototypeAST(PrototypeAST node) - { - // Make the function type: double(double,double) etc. - var argumentCount = (uint)node.Arguments.Count; - var arguments = new LLVMTypeRef[Math.Max(argumentCount, 1)]; - - var function = LLVM.GetNamedFunction(this.module, node.Name); - - // If F conflicted, there was already something named 'Name'. If it has a - // body, don't allow redefinition or reextern. - if (function.Pointer != IntPtr.Zero) - { - // If F already has a body, reject this. - if (LLVM.CountBasicBlocks(function) != 0) - { - throw new Exception("redefinition of function."); - } - - // If F took a different number of args, reject. - if (LLVM.CountParams(function) != argumentCount) - { - throw new Exception("redefinition of function with different # args"); - } - } - else - { - for (int i = 0; i < argumentCount; ++i) - { - arguments[i] = LLVM.DoubleType(); - } - - function = LLVM.AddFunction(this.module, node.Name, LLVM.FunctionType(LLVM.DoubleType(), arguments, LLVMBoolFalse)); - LLVM.SetLinkage(function, LLVMLinkage.LLVMExternalLinkage); - } - - for (int i = 0; i < argumentCount; ++i) - { - string argumentName = node.Arguments[i]; - - LLVMValueRef param = LLVM.GetParam(function, (uint)i); - LLVM.SetValueName(param, argumentName); - - this.namedValues[argumentName] = param; - } - - this.valueStack.Push(function); - return node; - } - - protected override ExprAST VisitFunctionAST(FunctionAST node) - { - this.namedValues.Clear(); - - this.Visit(node.Proto); - - LLVMValueRef function = this.valueStack.Pop(); - - // Create a new basic block to start insertion into. - LLVM.PositionBuilderAtEnd(this.builder, LLVM.AppendBasicBlock(function, "entry")); - - try - { - this.Visit(node.Body); - } - catch (Exception) - { - LLVM.DeleteFunction(function); - throw; - } - - // Finish off the function. - LLVM.BuildRet(this.builder, this.valueStack.Pop()); - - // Validate the generated code, checking for consistency. - LLVM.VerifyFunction(function, LLVMVerifierFailureAction.LLVMPrintMessageAction); - - this.valueStack.Push(function); - - return node; - } - - protected override ExprAST VisitIfExprAST(IfExpAST node) - { - this.Visit(node.Condition); - var condv = LLVM.BuildFCmp(this.builder, LLVMRealPredicate.LLVMRealONE, this.valueStack.Pop(), LLVM.ConstReal(LLVM.DoubleType(), 0.0), "ifcond"); - - LLVMValueRef func = LLVM.GetBasicBlockParent(LLVM.GetInsertBlock(builder)); - - // Create blocks for the then and else cases. Insert the 'then' block at the - // end of the function. - LLVMBasicBlockRef thenBB = LLVM.AppendBasicBlock(func, "then"); - LLVMBasicBlockRef elseBB = LLVM.AppendBasicBlock(func, "else"); - LLVMBasicBlockRef mergeBB = LLVM.AppendBasicBlock(func, "ifcont"); - - LLVM.BuildCondBr(this.builder, condv, thenBB, elseBB); - - // Emit then value. - LLVM.PositionBuilderAtEnd(this.builder, thenBB); - - this.Visit(node.Then); - var thenV = this.valueStack.Pop(); - - LLVM.BuildBr(this.builder, mergeBB); - - // Codegen of 'Then' can change the current block, update ThenBB for the PHI. - thenBB = LLVM.GetInsertBlock(this.builder); - - // Emit else block. - - LLVM.PositionBuilderAtEnd(this.builder, elseBB); - - this.Visit(node.Else); - var elseV = this.valueStack.Pop(); - - LLVM.BuildBr(this.builder, mergeBB); - - // Codegen of 'Else' can change the current block, update ElseBB for the PHI. - elseBB = LLVM.GetInsertBlock(this.builder); - - // Emit merge block. - LLVM.PositionBuilderAtEnd(this.builder, mergeBB); - var phi = LLVM.BuildPhi(this.builder, LLVM.DoubleType(), "iftmp"); - - LLVM.AddIncoming(phi, new []{thenV}, new []{thenBB}, 1); - LLVM.AddIncoming(phi, new []{elseV}, new []{elseBB}, 1); - - this.valueStack.Push(phi); - - return node; - } - - protected override ExprAST VisitForExprAST(ForExprAST node) - { - // Output this as: - // ... - // start = startexpr - // goto loop - // loop: - // variable = phi [start, loopheader], [nextvariable, loopend] - // ... - // bodyexpr - // ... - // loopend: - // step = stepexpr - // nextvariable = variable + step - // endcond = endexpr - // br endcond, loop, endloop - // outloop: - - // Emit the start code first, without 'variable' in scope. - this.Visit(node.Start); - var startVal = this.valueStack.Pop(); - - // Make the new basic block for the loop header, inserting after current - // block. - var preheaderBB = LLVM.GetInsertBlock(this.builder); - var function = LLVM.GetBasicBlockParent(preheaderBB); - var loopBB = LLVM.AppendBasicBlock(function, "loop"); - - // Insert an explicit fall through from the current block to the LoopBB. - LLVM.BuildBr(this.builder, loopBB); - - // Start insertion in LoopBB. - LLVM.PositionBuilderAtEnd(this.builder, loopBB); - - // Start the PHI node with an entry for Start. - var variable = LLVM.BuildPhi(builder, LLVM.DoubleType(), node.VarName); - LLVM.AddIncoming(variable, new []{startVal}, new []{preheaderBB}, 1); - - // Within the loop, the variable is defined equal to the PHI node. If it - // shadows an existing variable, we have to restore it, so save it now. - LLVMValueRef oldVal; - if (this.namedValues.TryGetValue(node.VarName, out oldVal)) - { - this.namedValues[node.VarName] = variable; - } - else - { - this.namedValues.Add(node.VarName, variable); - } - - // Emit the body of the loop. This, like any other expr, can change the - // current BB. Note that we ignore the value computed by the body, but don't - // allow an error. - Visit(node.Body); - - // Emit the step value. - LLVMValueRef stepVal; - if (node.Step != null) - { - Visit(node.Step); - stepVal = valueStack.Pop(); - } - else - { - // If not specified, use 1.0. - stepVal = LLVM.ConstReal(LLVM.DoubleType(), 1.0); - } - - LLVMValueRef nextVar = LLVM.BuildFAdd(builder, variable, stepVal, "nextvar"); - - // Compute the end condition. - Visit(node.End); - LLVMValueRef endCond = LLVM.BuildFCmp(builder, LLVMRealPredicate.LLVMRealONE, valueStack.Pop(), LLVM.ConstReal(LLVM.DoubleType(), 0.0), "loopcond"); - - // Create the "after loop" block and insert it. - var loopEndBB = LLVM.GetInsertBlock(builder); - var afterBB = LLVM.AppendBasicBlock(function, "afterloop"); - - // Insert the conditional branch into the end of LoopEndBB. - LLVM.BuildCondBr(builder, endCond, loopBB, afterBB); - - // Any new code will be inserted in AfterBB. - LLVM.PositionBuilderAtEnd(builder, afterBB); - - // Add a new entry to the PHI node for the backedge. - LLVM.AddIncoming(variable, new []{nextVar}, new []{loopEndBB}, 1); - - // Restore the unshadowed variable. - if (oldVal.Pointer != IntPtr.Zero) - { - namedValues[node.VarName] = oldVal; - } - else - { - namedValues.Remove(node.VarName); - } - - valueStack.Push(LLVM.ConstReal(LLVM.DoubleType(), 0.0)); - - return node; - } - } -} \ No newline at end of file diff --git a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj b/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj deleted file mode 100644 index 96ff9d4b..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/KaleidoscopeLLVM.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net10.0 - - - - - - - - - - diff --git a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/Program.cs b/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/Program.cs deleted file mode 100644 index ea156bfd..00000000 --- a/samples/KaleidoscopeTutorial/Chapter5/KaleidoscopeLLVM/Program.cs +++ /dev/null @@ -1,114 +0,0 @@ -namespace KaleidoscopeLLVM -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Kaleidoscope; - using LLVMSharp; - - public sealed class Program - { - public delegate double D(); - - private static void Main(string[] args) - { - // Make the module, which holds all the code. - LLVMModuleRef module = LLVM.ModuleCreateWithName("my cool jit"); - LLVMBuilderRef builder = LLVM.CreateBuilder(); - - LLVM.LinkInMCJIT(); - LLVM.InitializeX86TargetInfo(); - LLVM.InitializeX86Target(); - LLVM.InitializeX86TargetMC(); - - if (LLVM.CreateExecutionEngineForModule(out var engine, module, out var errorMessage).Value == 1) - { - Console.WriteLine(errorMessage); - // LLVM.DisposeMessage(errorMessage); - return; - } - - // Create a function pass manager for this engine - LLVMPassManagerRef passManager = LLVM.CreateFunctionPassManagerForModule(module); - - // Set up the optimizer pipeline. Start with registering info about how the - // target lays out data structures. - // LLVM.DisposeTargetData(LLVM.GetExecutionEngineTargetData(engine)); - - // Provide basic AliasAnalysis support for GVN. - LLVM.AddBasicAliasAnalysisPass(passManager); - - // Promote allocas to registers. - LLVM.AddPromoteMemoryToRegisterPass(passManager); - - // Do simple "peephole" optimizations and bit-twiddling optzns. - LLVM.AddInstructionCombiningPass(passManager); - - // Reassociate expressions. - LLVM.AddReassociatePass(passManager); - - // Eliminate Common SubExpressions. - LLVM.AddGVNPass(passManager); - - // Simplify the control flow graph (deleting unreachable blocks, etc). - LLVM.AddCFGSimplificationPass(passManager); - - LLVM.InitializeFunctionPassManager(passManager); - - var codeGenlistener = new CodeGenParserListener(engine, passManager, new CodeGenVisitor(module, builder)); - - // Install standard binary operators. - // 1 is lowest precedence. - var binopPrecedence = new Dictionary - { - ['<'] = 10, - ['+'] = 20, - ['-'] = 20, - ['*'] = 40 - }; - // highest. - - var scanner = new Lexer(Console.In, binopPrecedence); - var parser = new Parser(scanner, codeGenlistener); - - // Prime the first token. - Console.Write("ready> "); - scanner.GetNextToken(); - - // Run the main "interpreter loop" now. - MainLoop(scanner, parser); - - // Print out all of the generated code. - LLVM.DumpModule(module); - - LLVM.DisposeModule(module); - LLVM.DisposePassManager(passManager); - } - - private static void MainLoop(ILexer lexer, IParser parser) - { - // top ::= definition | external | expression | ';' - while (true) - { - Console.Write("ready> "); - switch (lexer.CurrentToken) - { - case (int)Token.EOF: - return; - case ';': - lexer.GetNextToken(); - break; - case (int)Token.DEF: - parser.HandleDefinition(); - break; - case (int)Token.EXTERN: - parser.HandleExtern(); - break; - default: - parser.HandleTopLevelExpression(); - break; - } - } - } - } -} diff --git a/samples/KaleidoscopeTutorial/Chapter5/Parser.cs b/samples/KaleidoscopeTutorial/Chapter5/Parser.cs new file mode 100644 index 00000000..4ea67877 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter5/Parser.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; + +namespace Kaleidoscope.Chapter5; + +/// +/// Chapter 5 parser delta — adds the if/then/else and for/in primary expressions. Every +/// other production is inherited from the base . +/// +public class Parser(Lexer lexer) : Kaleidoscope.Parser(lexer) +{ + protected override bool TryParseKeywordPrimary(int token, out ExprAST? result) + { + switch (token) + { + case (int)Token.If: + { + result = ParseIfExpr(); + return true; + } + + case (int)Token.For: + { + result = ParseForExpr(); + return true; + } + + default: + { + return base.TryParseKeywordPrimary(token, out result); + } + } + } + + // ifexpr ::= 'if' expression 'then' expression 'else' expression + private ExprAST? ParseIfExpr() + { + Lexer.GetNextToken(); // eat the if. + + ExprAST? condition = ParseExpression(); + if (condition is null) + { + return null; + } + + if (Lexer.CurrentToken != (int)Token.Then) + { + return LogError("expected then"); + } + + Lexer.GetNextToken(); // eat the then. + + ExprAST? then = ParseExpression(); + if (then is null) + { + return null; + } + + if (Lexer.CurrentToken != (int)Token.Else) + { + return LogError("expected else"); + } + + Lexer.GetNextToken(); // eat the else. + + ExprAST? @else = ParseExpression(); + return @else is null ? null : new IfExprAST(condition, then, @else); + } + + // forexpr ::= 'for' identifier '=' expr ',' expr (',' expr)? 'in' expression + private ExprAST? ParseForExpr() + { + Lexer.GetNextToken(); // eat the for. + + if (Lexer.CurrentToken != (int)Token.Identifier) + { + return LogError("expected identifier after for"); + } + + string idName = Lexer.LastIdentifier; + Lexer.GetNextToken(); // eat the identifier. + + if (Lexer.CurrentToken != '=') + { + return LogError("expected '=' after for"); + } + + Lexer.GetNextToken(); // eat the '='. + + ExprAST? start = ParseExpression(); + if (start is null) + { + return null; + } + + if (Lexer.CurrentToken != ',') + { + return LogError("expected ',' after for start value"); + } + + Lexer.GetNextToken(); // eat the ','. + + ExprAST? end = ParseExpression(); + if (end is null) + { + return null; + } + + // The step is optional. + ExprAST? step = null; + if (Lexer.CurrentToken == ',') + { + Lexer.GetNextToken(); // eat the ','. + + step = ParseExpression(); + if (step is null) + { + return null; + } + } + + if (Lexer.CurrentToken != (int)Token.In) + { + return LogError("expected 'in' after for"); + } + + Lexer.GetNextToken(); // eat the in. + + ExprAST? body = ParseExpression(); + return body is null ? null : new ForExprAST(idName, start, end, step, body); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter5/Program.cs b/samples/KaleidoscopeTutorial/Chapter5/Program.cs new file mode 100644 index 00000000..3911861a --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter5/Program.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter4; +using Kaleidoscope.Chapter5; + +// Chapter 5 reuses chapter 4's JIT driver and only swaps in a parser and code generator that +// understand control flow (if/then/else and for/in). +var binaryOpPrecedence = new Dictionary +{ + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Kaleidoscope.Chapter5.Parser(lexer); +var visitor = new CodeGenVisitor(); + +using var driver = new JitReplDriver(lexer, parser, visitor); +driver.Run(); diff --git a/samples/KaleidoscopeTutorial/Chapter6/Chapter6.csproj b/samples/KaleidoscopeTutorial/Chapter6/Chapter6.csproj new file mode 100644 index 00000000..81229cb5 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter6/Chapter6.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter6/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter6/CodeGenVisitor.cs new file mode 100644 index 00000000..2528a6ca --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter6/CodeGenVisitor.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter6; + +/// +/// Chapter 6 code generation delta — user-defined operators lower to ordinary function calls. A unary +/// operator !x calls unary!; a binary operator a op b that isn't one of the +/// built-ins calls binary<op>. The operator functions themselves are just normal function +/// definitions, so they reuse the inherited . +/// +public class CodeGenVisitor : Chapter5.CodeGenVisitor +{ + protected override LLVMValueRef CodegenBinary(BinaryExprAST node) + { + // The built-in operators keep their inlined lowering from chapter 3. + switch (node.Op) + { + case '+': + case '-': + case '*': + case '<': + { + return base.CodegenBinary(node); + } + } + + // Anything else is a user-defined operator: emit a call to its binary function. + LLVMValueRef left = Codegen(node.Lhs); + LLVMValueRef right = Codegen(node.Rhs); + + LLVMValueRef function = GetFunction("binary" + node.Op); + if (function.Handle == IntPtr.Zero) + { + throw new InvalidOperationException($"binary operator '{node.Op}' not found"); + } + + LLVMTypeRef functionType = GetFunctionType(function); + return Builder.BuildCall2(functionType, function, new LLVMValueRef[] { left, right }, "binop"); + } + + protected override LLVMValueRef CodegenUnary(UnaryExprAST node) + { + LLVMValueRef operand = Codegen(node.Operand); + + LLVMValueRef function = GetFunction("unary" + node.Opcode); + if (function.Handle == IntPtr.Zero) + { + throw new InvalidOperationException($"unary operator '{node.Opcode}' not found"); + } + + LLVMTypeRef functionType = GetFunctionType(function); + return Builder.BuildCall2(functionType, function, new LLVMValueRef[] { operand }, "unop"); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter6/Parser.cs b/samples/KaleidoscopeTutorial/Chapter6/Parser.cs new file mode 100644 index 00000000..9b1b64a6 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter6/Parser.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; + +namespace Kaleidoscope.Chapter6; + +/// +/// Chapter 6 parser delta — user-defined operators. It adds a production for +/// prefix operators and extends to parse unary<op> and +/// binary<op> [precedence] definitions, registering a new binary operator's precedence into +/// the shared precedence table so subsequent input parses with the right binding. +/// +public class Parser(Lexer lexer, IDictionary binaryOpPrecedence) : Chapter5.Parser(lexer) +{ + private readonly IDictionary _binaryOpPrecedence = binaryOpPrecedence; + + // unary ::= primary | unary + protected override ExprAST? ParseUnary() + { + // If the current token isn't an operator character, it must be a primary expression. Negative + // tokens are keywords/identifiers/numbers; '(' and ',' start or separate primaries. + int token = Lexer.CurrentToken; + if (token < 0 || token == '(' || token == ',') + { + return ParsePrimary(); + } + + // Otherwise it's a prefix unary operator; read the operator and its operand. + int opcode = token; + Lexer.GetNextToken(); + + ExprAST? operand = ParseUnary(); + return operand is null ? null : new UnaryExprAST((char)opcode, operand); + } + + // prototype + // ::= id '(' id* ')' + // ::= 'unary' '(' id ')' + // ::= 'binary' [precedence] '(' id id ')' + protected override PrototypeAST? ParsePrototype() + { + string functionName; + int kind; // 0 = identifier, 1 = unary operator, 2 = binary operator. + int binaryPrecedence = 30; + + switch (Lexer.CurrentToken) + { + case (int)Token.Identifier: + { + functionName = Lexer.LastIdentifier; + kind = 0; + Lexer.GetNextToken(); + break; + } + + case (int)Token.Unary: + { + Lexer.GetNextToken(); + if (Lexer.CurrentToken <= 0) + { + return LogErrorProto("Expected unary operator"); + } + + functionName = "unary" + (char)Lexer.CurrentToken; + kind = 1; + Lexer.GetNextToken(); + break; + } + + case (int)Token.Binary: + { + Lexer.GetNextToken(); + if (Lexer.CurrentToken <= 0) + { + return LogErrorProto("Expected binary operator"); + } + + functionName = "binary" + (char)Lexer.CurrentToken; + kind = 2; + Lexer.GetNextToken(); + + // Read the optional precedence. + if (Lexer.CurrentToken == (int)Token.Number) + { + if (Lexer.LastNumber is < 1 or > 100) + { + return LogErrorProto("Invalid precedence: must be 1..100"); + } + + binaryPrecedence = (int)Lexer.LastNumber; + Lexer.GetNextToken(); + } + + break; + } + + default: + { + return LogErrorProto("Expected function name in prototype"); + } + } + + if (Lexer.CurrentToken != '(') + { + return LogErrorProto("Expected '(' in prototype"); + } + + var argNames = new List(); + while (Lexer.GetNextToken() == (int)Token.Identifier) + { + argNames.Add(Lexer.LastIdentifier); + } + + if (Lexer.CurrentToken != ')') + { + return LogErrorProto("Expected ')' in prototype"); + } + + Lexer.GetNextToken(); // eat ')'. + + if (kind != 0 && argNames.Count != kind) + { + return LogErrorProto("Invalid number of operands for operator"); + } + + var prototype = new PrototypeAST(functionName, argNames, IsOperator: kind != 0, binaryPrecedence); + + // Make a user-defined binary operator usable in the input that follows its definition. + if (prototype.IsBinaryOperator) + { + _binaryOpPrecedence[prototype.OperatorName] = prototype.Precedence; + } + + return prototype; + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter6/Program.cs b/samples/KaleidoscopeTutorial/Chapter6/Program.cs new file mode 100644 index 00000000..68f86779 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter6/Program.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter4; +using Kaleidoscope.Chapter6; + +// Chapter 6 reuses chapter 4's JIT driver and swaps in a parser/code generator that understand +// user-defined unary and binary operators. +var binaryOpPrecedence = new Dictionary +{ + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Kaleidoscope.Chapter6.Parser(lexer, binaryOpPrecedence); +var visitor = new CodeGenVisitor(); + +using var driver = new JitReplDriver(lexer, parser, visitor); +driver.Run(); diff --git a/samples/KaleidoscopeTutorial/Chapter7/Chapter7.csproj b/samples/KaleidoscopeTutorial/Chapter7/Chapter7.csproj new file mode 100644 index 00000000..e80700fa --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter7/Chapter7.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter7/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter7/CodeGenVisitor.cs new file mode 100644 index 00000000..45b5b3f6 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter7/CodeGenVisitor.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter7; + +/// +/// Chapter 7 code generation delta — mutable variables. Every variable now lives in a stack slot +/// (an alloca): reads become loads, assignment (=) becomes a store, function parameters +/// and loop variables get their own slots, and var/in introduces new ones. The optimizer's +/// mem2reg pass (already in the pipeline) promotes these back to SSA registers, so the emitted +/// code is as tight as the earlier chapters despite the extra loads/stores. +/// +public class CodeGenVisitor : Chapter6.CodeGenVisitor +{ + protected override LLVMValueRef CodegenVariable(VariableExprAST node) + { + if (!NamedValues.TryGetValue(node.Name, out LLVMValueRef alloca)) + { + throw new InvalidOperationException($"Unknown variable name '{node.Name}'"); + } + + // Variables are now stack slots, so a reference is a load from the slot. + return Builder.BuildLoad2(Module.Context.DoubleType, alloca, node.Name); + } + + protected override LLVMValueRef CodegenBinary(BinaryExprAST node) + { + // Assignment is special: it does not evaluate its left-hand side as an expression. + if (node.Op == '=') + { + if (node.Lhs is not VariableExprAST target) + { + throw new InvalidOperationException("destination of '=' must be a variable"); + } + + LLVMValueRef value = Codegen(node.Rhs); + if (!NamedValues.TryGetValue(target.Name, out LLVMValueRef alloca)) + { + throw new InvalidOperationException($"Unknown variable name '{target.Name}'"); + } + + Builder.BuildStore(value, alloca); + return value; + } + + return base.CodegenBinary(node); + } + + protected override LLVMValueRef CodegenFor(ForExprAST node) + { + LLVMValueRef function = Builder.InsertBlock.Parent; + + // Give the loop variable a stack slot and initialize it with the start value. + LLVMValueRef alloca = CreateEntryBlockAlloca(function, node.VarName); + LLVMValueRef startValue = Codegen(node.Start); + Builder.BuildStore(startValue, alloca); + + LLVMBasicBlockRef loopBlock = function.AppendBasicBlock("loop"); + Builder.BuildBr(loopBlock); + Builder.PositionAtEnd(loopBlock); + + // Bring the loop variable into scope, shadowing any outer binding. + bool hadOldValue = NamedValues.TryGetValue(node.VarName, out LLVMValueRef oldValue); + NamedValues[node.VarName] = alloca; + + // Emit the body; its value is discarded. + Codegen(node.Body); + + // Compute the step, then reload/increment/store the loop variable. + LLVMValueRef stepValue = node.Step is not null + ? Codegen(node.Step) + : LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 1.0); + + LLVMValueRef endCondition = Codegen(node.End); + + LLVMValueRef currentValue = Builder.BuildLoad2(Module.Context.DoubleType, alloca, node.VarName); + LLVMValueRef nextValue = Builder.BuildFAdd(currentValue, stepValue, "nextvar"); + Builder.BuildStore(nextValue, alloca); + + LLVMValueRef zero = LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + endCondition = Builder.BuildFCmp(LLVMRealPredicate.LLVMRealONE, endCondition, zero, "loopcond"); + + LLVMBasicBlockRef afterBlock = function.AppendBasicBlock("afterloop"); + Builder.BuildCondBr(endCondition, loopBlock, afterBlock); + Builder.PositionAtEnd(afterBlock); + + // Restore the shadowed variable. + if (hadOldValue) + { + NamedValues[node.VarName] = oldValue; + } + else + { + NamedValues.Remove(node.VarName); + } + + // A 'for' expression always evaluates to 0.0. + return LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + } + + protected override LLVMValueRef CodegenVar(VarExprAST node) + { + LLVMValueRef function = Builder.InsertBlock.Parent; + var oldBindings = new List<(string Name, LLVMValueRef OldValue, bool Existed)>(); + + foreach ((string name, ExprAST? init) in node.VarNames) + { + // Evaluate the initializer before this name enters scope, so 'var a = a' sees the outer a. + LLVMValueRef initValue = init is not null + ? Codegen(init) + : LLVMValueRef.CreateConstReal(Module.Context.DoubleType, 0.0); + + LLVMValueRef alloca = CreateEntryBlockAlloca(function, name); + Builder.BuildStore(initValue, alloca); + + bool existed = NamedValues.TryGetValue(name, out LLVMValueRef oldValue); + oldBindings.Add((name, oldValue, existed)); + NamedValues[name] = alloca; + } + + LLVMValueRef body = Codegen(node.Body); + + // Restore all shadowed bindings. + foreach ((string name, LLVMValueRef oldValue, bool existed) in oldBindings) + { + if (existed) + { + NamedValues[name] = oldValue; + } + else + { + NamedValues.Remove(name); + } + } + + return body; + } + + protected override void CreateParameterBindings(LLVMValueRef function, PrototypeAST proto) + { + // Each parameter gets a stack slot seeded with the incoming argument value. + NamedValues.Clear(); + for (int i = 0; i < proto.Arguments.Count; i++) + { + string name = proto.Arguments[i]; + LLVMValueRef alloca = CreateEntryBlockAlloca(function, name); + Builder.BuildStore(function.GetParam((uint)i), alloca); + NamedValues[name] = alloca; + } + } + + /// + /// Creates an alloca at the very start of the function's entry block. Keeping every alloca in + /// the entry block is what lets mem2reg promote them to SSA registers. + /// + protected LLVMValueRef CreateEntryBlockAlloca(LLVMValueRef function, string name) + { + LLVMBasicBlockRef entry = function.EntryBasicBlock; + + using LLVMBuilderRef temporary = Module.Context.CreateBuilder(); + LLVMValueRef firstInstruction = entry.FirstInstruction; + if (firstInstruction.Handle != IntPtr.Zero) + { + temporary.PositionBefore(firstInstruction); + } + else + { + temporary.PositionAtEnd(entry); + } + + return temporary.BuildAlloca(Module.Context.DoubleType, name); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter7/Parser.cs b/samples/KaleidoscopeTutorial/Chapter7/Parser.cs new file mode 100644 index 00000000..63e26f20 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter7/Parser.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; + +namespace Kaleidoscope.Chapter7; + +/// +/// Chapter 7 parser delta — adds the var/in primary for introducing mutable local variables. +/// Assignment (=) needs no new grammar: it is registered as a low-precedence binary operator in +/// the precedence table, so the inherited binary-operator parsing handles it. +/// +public class Parser(Lexer lexer, IDictionary binaryOpPrecedence) + : Chapter6.Parser(lexer, binaryOpPrecedence) +{ + protected override bool TryParseKeywordPrimary(int token, out ExprAST? result) + { + if (token == (int)Token.Var) + { + result = ParseVarExpr(); + return true; + } + + return base.TryParseKeywordPrimary(token, out result); + } + + // varexpr ::= 'var' identifier ('=' expression)? (',' identifier ('=' expression)?)* 'in' expression + private ExprAST? ParseVarExpr() + { + Lexer.GetNextToken(); // eat the var. + + var varNames = new List<(string Name, ExprAST? Init)>(); + + if (Lexer.CurrentToken != (int)Token.Identifier) + { + return LogError("expected identifier after var"); + } + + while (true) + { + string name = Lexer.LastIdentifier; + Lexer.GetNextToken(); // eat the identifier. + + // Read the optional initializer. + ExprAST? init = null; + if (Lexer.CurrentToken == '=') + { + Lexer.GetNextToken(); // eat the '='. + + init = ParseExpression(); + if (init is null) + { + return null; + } + } + + varNames.Add((name, init)); + + if (Lexer.CurrentToken != ',') + { + break; // end of the var list. + } + + Lexer.GetNextToken(); // eat the ','. + + if (Lexer.CurrentToken != (int)Token.Identifier) + { + return LogError("expected identifier list after var"); + } + } + + if (Lexer.CurrentToken != (int)Token.In) + { + return LogError("expected 'in' keyword after 'var'"); + } + + Lexer.GetNextToken(); // eat the in. + + ExprAST? body = ParseExpression(); + return body is null ? null : new VarExprAST(varNames, body); + } +} diff --git a/samples/KaleidoscopeTutorial/Chapter7/Program.cs b/samples/KaleidoscopeTutorial/Chapter7/Program.cs new file mode 100644 index 00000000..7bef134e --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter7/Program.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter4; +using Kaleidoscope.Chapter7; + +// Chapter 7 reuses chapter 4's JIT driver and swaps in a parser/code generator that support mutable +// variables. Assignment is a low-precedence binary operator, so '=' is added to the precedence table. +var binaryOpPrecedence = new Dictionary +{ + ['='] = 2, + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Kaleidoscope.Chapter7.Parser(lexer, binaryOpPrecedence); +var visitor = new CodeGenVisitor(); + +using var driver = new JitReplDriver(lexer, parser, visitor); +driver.Run(); diff --git a/samples/KaleidoscopeTutorial/Chapter8/Chapter8.csproj b/samples/KaleidoscopeTutorial/Chapter8/Chapter8.csproj new file mode 100644 index 00000000..8694967e --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter8/Chapter8.csproj @@ -0,0 +1,8 @@ + + + Exe + + + + + diff --git a/samples/KaleidoscopeTutorial/Chapter8/ObjectFileDriver.cs b/samples/KaleidoscopeTutorial/Chapter8/ObjectFileDriver.cs new file mode 100644 index 00000000..2aa57ba1 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter8/ObjectFileDriver.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope.Chapter8; + +/// +/// Chapter 8's driver: instead of JIT-executing top-level expressions, it generates every definition, +/// extern, and top-level expression into a single module. The program then hands that module to a +/// target machine to emit a native object file. There is no execution here — the output is a `.o`. +/// +public sealed class ObjectFileDriver : ReplDriver +{ + private readonly CodeGenVisitorBase _visitor; + + public ObjectFileDriver(Lexer lexer, Parser parser, CodeGenVisitorBase visitor, LLVMModuleRef module) + : base(lexer, parser) + { + _visitor = visitor; + _visitor.SetModule(module); + } + + protected override void OnDefinition(FunctionAST function) => _visitor.CodegenFunction(function); + + protected override void OnExtern(PrototypeAST prototype) => _visitor.CodegenExtern(prototype); + + protected override void OnTopLevelExpression(FunctionAST function) => _visitor.CodegenFunction(function); +} diff --git a/samples/KaleidoscopeTutorial/Chapter8/Program.cs b/samples/KaleidoscopeTutorial/Chapter8/Program.cs new file mode 100644 index 00000000..63f0bc9d --- /dev/null +++ b/samples/KaleidoscopeTutorial/Chapter8/Program.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope; +using Kaleidoscope.Chapter8; +using LLVMSharp.Interop; + +// Chapter 8 is a batch compiler rather than a REPL: it reads a whole Kaleidoscope program, generates +// every definition into one module, and emits a native object file that a C driver can link against. +var binaryOpPrecedence = new Dictionary +{ + ['='] = 2, + ['<'] = 10, + ['+'] = 20, + ['-'] = 20, + ['*'] = 40, +}; + +string outputPath = args.Length > 1 ? args[1] : "output.o"; + +using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In; + +var lexer = new Lexer(reader, binaryOpPrecedence); +var parser = new Kaleidoscope.Chapter7.Parser(lexer, binaryOpPrecedence); +var visitor = new Kaleidoscope.Chapter7.CodeGenVisitor(); + +LlvmSupport.EnsureTargetsInitialized(); +LLVMTargetMachineRef targetMachine = LlvmSupport.CreateHostTargetMachine(); + +LLVMContextRef context = LLVMContextRef.Create(); +LLVMModuleRef module = context.CreateModuleWithName("KaleidoscopeModule"); +LlvmSupport.PrepareModuleForEmit(module, targetMachine); + +var driver = new ObjectFileDriver(lexer, parser, visitor, module); +driver.Run(); + +Console.WriteLine("=== Module IR ==="); +Console.Write(module.PrintToString()); +Console.WriteLine(); + +if (targetMachine.TryEmitToFile(module, outputPath, LLVMCodeGenFileType.LLVMObjectFile, out string message)) +{ + Console.WriteLine($"Wrote object file to '{outputPath}' (target {module.Target})."); +} +else +{ + Console.Error.WriteLine($"Failed to emit object file: {message}"); + Environment.Exit(1); +} diff --git a/samples/KaleidoscopeTutorial/Directory.Build.props b/samples/KaleidoscopeTutorial/Directory.Build.props new file mode 100644 index 00000000..f9d11666 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Directory.Build.props @@ -0,0 +1,25 @@ + + + + + + + net10.0 + enable + enable + true + latest + + false + + + + + + + + diff --git a/samples/KaleidoscopeTutorial/Directory.Build.targets b/samples/KaleidoscopeTutorial/Directory.Build.targets new file mode 100644 index 00000000..5f35364f --- /dev/null +++ b/samples/KaleidoscopeTutorial/Directory.Build.targets @@ -0,0 +1,13 @@ + + + + + + $(NETCoreSdkRuntimeIdentifier) + + + diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs new file mode 100644 index 00000000..494142aa --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +namespace Kaleidoscope.AST; + +/// Base class for all expression nodes. +public abstract record ExprAST; + +/// A numeric literal, e.g. 1.0 (chapter 3). +public sealed record NumberExprAST(double Value) : ExprAST; + +/// A reference to a variable, e.g. a (chapter 3). +public sealed record VariableExprAST(string Name) : ExprAST; + +/// A binary operator expression, e.g. a + b (chapter 3; user operators in chapter 6). +public sealed record BinaryExprAST(char Op, ExprAST Lhs, ExprAST Rhs) : ExprAST; + +/// A unary operator expression, e.g. !a (chapter 6). +public sealed record UnaryExprAST(char Opcode, ExprAST Operand) : ExprAST; + +/// A function call, e.g. foo(a, b) (chapter 3). +public sealed record CallExprAST(string Callee, IReadOnlyList Arguments) : ExprAST; + +/// An if/then/else expression (chapter 5). +public sealed record IfExprAST(ExprAST Condition, ExprAST Then, ExprAST Else) : ExprAST; + +/// A for/in loop expression; is null when omitted (chapter 5). +public sealed record ForExprAST(string VarName, ExprAST Start, ExprAST End, ExprAST? Step, ExprAST Body) : ExprAST; + +/// A var/in expression introducing mutable locals (chapter 7). +public sealed record VarExprAST(IReadOnlyList<(string Name, ExprAST? Init)> VarNames, ExprAST Body) : ExprAST; diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs new file mode 100644 index 00000000..10b0ff97 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +namespace Kaleidoscope.AST; + +/// +/// The "prototype" for a function — its name and argument names. From chapter 6 a prototype can +/// also declare a user-defined operator, in which case it carries the operator precedence. +/// +public sealed record PrototypeAST( + string Name, + IReadOnlyList Arguments, + bool IsOperator = false, + int Precedence = 0) +{ + public bool IsUnaryOperator => IsOperator && Arguments.Count == 1; + + public bool IsBinaryOperator => IsOperator && Arguments.Count == 2; + + /// The operator character for an operator prototype (the last char of the name). + public char OperatorName => Name[^1]; +} + +/// A function definition: a prototype plus the body expression it evaluates to. +public sealed record FunctionAST(PrototypeAST Proto, ExprAST Body); diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs new file mode 100644 index 00000000..8a07b91a --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; +using LLVMSharp.Interop; + +namespace Kaleidoscope; + +/// +/// Base class for the per-chapter code generators. It owns the state shared by every chapter — the +/// current , its , the in-scope , +/// and the map used to re-declare functions across modules — and routes +/// each to a protected virtual that a chapter overrides. Nodes a chapter +/// doesn't support fall through to a throwing default. +/// +public abstract class CodeGenVisitorBase +{ + /// Remembered prototypes so a function can be re-declared in a later module. + protected Dictionary FunctionProtos { get; } = new(); + + /// The symbols currently in scope while generating a function body. + protected Dictionary NamedValues { get; } = new(); + + /// The module the generator is currently emitting into. + protected LLVMModuleRef Module { get; private set; } + + /// A builder positioned within . + protected LLVMBuilderRef Builder { get; private set; } + + /// Points the generator at a new module, creating a builder in that module's context. + public void SetModule(LLVMModuleRef module) + { + Module = module; + Builder = module.Context.CreateBuilder(); + } + + /// Generates code for an expression, dispatching to the chapter-specific override. + public LLVMValueRef Codegen(ExprAST node) => node switch + { + NumberExprAST n => CodegenNumber(n), + VariableExprAST n => CodegenVariable(n), + BinaryExprAST n => CodegenBinary(n), + UnaryExprAST n => CodegenUnary(n), + CallExprAST n => CodegenCall(n), + IfExprAST n => CodegenIf(n), + ForExprAST n => CodegenFor(n), + VarExprAST n => CodegenVar(n), + _ => throw Unsupported(node), + }; + + /// Emits a declaration for an extern and remembers it for later modules. + public LLVMValueRef CodegenExtern(PrototypeAST node) + { + RegisterPrototype(node); + return CodegenPrototype(node); + } + + /// + /// Remembers a prototype without emitting anything. The JIT driver uses this for extern: + /// a declaration isn't a definition, so it needs no module of its own — the declaration is emitted + /// lazily by into whichever module first references it. + /// + public void RegisterPrototype(PrototypeAST node) => FunctionProtos[node.Name] = node; + + /// Declares a function (its prototype) in the current module. + public abstract LLVMValueRef CodegenPrototype(PrototypeAST node); + + /// Defines a function (prototype plus body) in the current module. + public abstract LLVMValueRef CodegenFunction(FunctionAST node); + + protected virtual LLVMValueRef CodegenNumber(NumberExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenVariable(VariableExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenBinary(BinaryExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenUnary(UnaryExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenCall(CallExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenIf(IfExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenFor(ForExprAST node) => throw Unsupported(node); + + protected virtual LLVMValueRef CodegenVar(VarExprAST node) => throw Unsupported(node); + + /// + /// Returns the named function from the current module, declaring it from a remembered prototype + /// when it isn't present. This is what lets a call resolve a callee that was defined in an + /// earlier, separate module (each top-level item is its own module once the JIT is involved). + /// + protected LLVMValueRef GetFunction(string name) + { + LLVMValueRef existing = Module.GetNamedFunction(name); + if (existing.Handle != IntPtr.Zero) + { + return existing; + } + + return FunctionProtos.TryGetValue(name, out PrototypeAST? proto) ? CodegenPrototype(proto) : default; + } + + /// + /// Recovers a function's type from the function value. Using this avoids the friendly + /// LLVMValueRef.FunctionType, which depends on the (unshipped) libLLVMSharp native + /// helper; works against the stock libLLVM. + /// + protected static unsafe LLVMTypeRef GetFunctionType(LLVMValueRef function) => LLVM.GlobalGetValueType(function); + + private static NotSupportedException Unsupported(object node) => + new($"This chapter's code generator does not support {node.GetType().Name}."); +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/HostFunctions.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/HostFunctions.cs new file mode 100644 index 00000000..14c0c583 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/HostFunctions.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Kaleidoscope; + +/// +/// The host functions the tutorial exposes to JIT'd Kaleidoscope code — putchard (print a +/// character) and printd (print a value). Declaring them with extern in Kaleidoscope and +/// having them resolve to these managed methods is how the tutorial does I/O (upstream issues #69 and +/// #133). They are registered as absolute symbols in . +/// +public static unsafe class HostFunctions +{ + /// putchard(x): prints the character whose code is ; returns 0. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static double Putchard(double value) + { + Console.Write((char)(int)value); + return 0.0; + } + + /// printd(x): prints followed by a newline; returns 0. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static double Printd(double value) + { + Console.WriteLine(value); + return 0.0; + } + + /// Registers every host function with so externs resolve. + public static void DefineAll(KaleidoscopeJit jit) + { + jit.DefineSymbol("putchard", (nint)(delegate* unmanaged[Cdecl])&Putchard); + jit.DefineSymbol("printd", (nint)(delegate* unmanaged[Cdecl])&Printd); + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Kaleidoscope.Common.csproj b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Kaleidoscope.Common.csproj new file mode 100644 index 00000000..80f0ccba --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Kaleidoscope.Common.csproj @@ -0,0 +1,5 @@ + + + Library + + diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/KaleidoscopeJit.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/KaleidoscopeJit.cs new file mode 100644 index 00000000..96fd4492 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/KaleidoscopeJit.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using LLVMSharp.Interop; + +namespace Kaleidoscope; + +/// +/// A minimal ORC "LLJIT" wrapper — the modern replacement for the removed ExecutionEngine/MCJIT +/// path. It creates an LLJIT instance, wires up a generator so JIT'd code can call symbols already in +/// the host process (libc sin/cos, …), and lets the driver add a module per top-level +/// item. Each top-level expression is added under its own resource tracker so it can be removed after +/// it runs, which is what lets the REPL evaluate more than one expression (upstream issue #1). +/// +public sealed unsafe class KaleidoscopeJit : IDisposable +{ + private LLVMOrcOpaqueLLJIT* _jit; + private readonly LLVMOrcOpaqueJITDylib* _mainJD; + + public KaleidoscopeJit() + { + LlvmSupport.EnsureTargetsInitialized(); + + LLVMOrcOpaqueLLJIT* jit; + LlvmSupport.ThrowIfError(LLVM.OrcCreateLLJIT(&jit, null), "Failed to create LLJIT"); + _jit = jit; + + _mainJD = LLVM.OrcLLJITGetMainJITDylib(jit); + + // Resolve symbols already present in the host process (e.g. libc math functions). + LLVMOrcOpaqueDefinitionGenerator* generator; + LlvmSupport.ThrowIfError( + LLVM.OrcCreateDynamicLibrarySearchGeneratorForProcess(&generator, LLVM.OrcLLJITGetGlobalPrefix(jit), null, null), + "Failed to create process-symbol generator"); + LLVM.OrcJITDylibAddGenerator(_mainJD, generator); + } + + /// The data layout string of the JIT, so emitted modules can match it. + public string DataLayout => new(LLVM.OrcLLJITGetDataLayoutStr(_jit)); + + /// The target triple of the JIT. + public string Triple => new(LLVM.OrcLLJITGetTripleString(_jit)); + + /// Stamps a module with the JIT's data layout and triple before it is added. + public void ConfigureModule(LLVMModuleRef module) + { + using var dataLayout = new MarshaledString(DataLayout); + using var triple = new MarshaledString(Triple); + LLVM.SetDataLayout(module, dataLayout); + LLVM.SetTarget(module, triple); + } + + /// Adds a module permanently (used for def so the function stays callable). + public void AddModule(LLVMModuleRef module) + { + LLVMOrcOpaqueThreadSafeModule* threadSafeModule = WrapModule(module); + LlvmSupport.ThrowIfError(LLVM.OrcLLJITAddLLVMIRModule(_jit, _mainJD, threadSafeModule), "Failed to add module"); + } + + /// + /// Adds a module under a fresh resource tracker and returns that tracker. Used for the anonymous + /// top-level expression so the caller can it after evaluating, freeing + /// the __anon_expr name for the next expression. + /// + public nint AddModuleRemovable(LLVMModuleRef module) + { + LLVMOrcOpaqueResourceTracker* tracker = LLVM.OrcJITDylibCreateResourceTracker(_mainJD); + LLVMOrcOpaqueThreadSafeModule* threadSafeModule = WrapModule(module); + LlvmSupport.ThrowIfError(LLVM.OrcLLJITAddLLVMIRModuleWithRT(_jit, tracker, threadSafeModule), "Failed to add module"); + return (nint)tracker; + } + + /// Removes a module previously added via . + public void RemoveModule(nint tracker) + { + var resourceTracker = (LLVMOrcOpaqueResourceTracker*)tracker; + LlvmSupport.ThrowIfError(LLVM.OrcResourceTrackerRemove(resourceTracker), "Failed to remove module"); + LLVM.OrcReleaseResourceTracker(resourceTracker); + } + + /// Looks up a symbol's address in the JIT. + public ulong Lookup(string name) + { + ulong address; + using var marshaled = new MarshaledString(name); + LlvmSupport.ThrowIfError(LLVM.OrcLLJITLookup(_jit, &address, marshaled), $"Symbol '{name}' not found"); + return address; + } + + /// + /// Defines an absolute symbol pointing at a host function address. This is how the tutorial exposes + /// its own putchard/printd helpers to JIT'd Kaleidoscope code (issues #69 and #133). + /// + public void DefineSymbol(string name, nint address) + { + LLVMOrcOpaqueSymbolStringPoolEntry* entry; + using (var marshaled = new MarshaledString(name)) + { + entry = LLVM.OrcLLJITMangleAndIntern(_jit, marshaled); + } + + LLVMOrcCSymbolMapPair pair; + pair.Name = entry; + pair.Sym.Address = (ulong)address; + pair.Sym.Flags.GenericFlags = (byte)(LLVMJITSymbolGenericFlags.LLVMJITSymbolGenericFlagsExported | LLVMJITSymbolGenericFlags.LLVMJITSymbolGenericFlagsCallable); + pair.Sym.Flags.TargetFlags = 0; + + LLVMOrcOpaqueMaterializationUnit* unit = LLVM.OrcAbsoluteSymbols(&pair, 1); + LlvmSupport.ThrowIfError(LLVM.OrcJITDylibDefine(_mainJD, unit), $"Failed to define '{name}'"); + } + + private static LLVMOrcOpaqueThreadSafeModule* WrapModule(LLVMModuleRef module) + { + // Wrap the module (built in its own LLVMContext) in a thread-safe module the JIT can take. The + // LLVM 21 API takes ownership of the context we pass, so we dispose our handle afterwards. + LLVMOrcOpaqueThreadSafeContext* threadSafeContext = LLVM.OrcCreateNewThreadSafeContextFromLLVMContext(module.Context); + LLVMOrcOpaqueThreadSafeModule* threadSafeModule = LLVM.OrcCreateNewThreadSafeModule(module, threadSafeContext); + LLVM.OrcDisposeThreadSafeContext(threadSafeContext); + return threadSafeModule; + } + + public void Dispose() + { + if (_jit is not null) + { + _ = LLVM.OrcDisposeLLJIT(_jit); + _jit = null; + } + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs new file mode 100644 index 00000000..a39a8caa --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs @@ -0,0 +1,134 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Globalization; +using System.Text; + +namespace Kaleidoscope; + +/// +/// Turns a character stream into Kaleidoscope tokens (tutorial chapter 1). One lexer serves every +/// chapter: it always recognizes the full keyword set, and the per-chapter parser decides which of +/// those keywords it is willing to accept. +/// +public sealed class Lexer +{ + private const int Eof = -1; + + private readonly TextReader _reader; + private readonly IReadOnlyDictionary _binaryOpPrecedence; + private readonly StringBuilder _builder = new(); + + private int _lastChar = ' '; + + public Lexer(TextReader reader, IReadOnlyDictionary binaryOpPrecedence) + { + _reader = reader; + _binaryOpPrecedence = binaryOpPrecedence; + } + + /// The most recently scanned token. Positive values are literal characters. + public int CurrentToken { get; private set; } + + /// The identifier text for the most recent . + public string LastIdentifier { get; private set; } = string.Empty; + + /// The value for the most recent . + public double LastNumber { get; private set; } + + /// Precedence of the current token if it is a known binary operator, otherwise -1. + public int GetTokenPrecedence() + { + if (CurrentToken > 0 && _binaryOpPrecedence.TryGetValue((char)CurrentToken, out int precedence)) + { + return precedence; + } + + return -1; + } + + /// Scans and returns the next token, also exposed via . + public int GetNextToken() + { + CurrentToken = ReadToken(); + return CurrentToken; + } + + private int ReadToken() + { + // Skip any whitespace. + while (char.IsWhiteSpace((char)_lastChar)) + { + _lastChar = _reader.Read(); + } + + // identifier: [a-zA-Z][a-zA-Z0-9]* + if (char.IsLetter((char)_lastChar)) + { + _builder.Clear(); + do + { + _builder.Append((char)_lastChar); + _lastChar = _reader.Read(); + } + while (char.IsLetterOrDigit((char)_lastChar)); + + LastIdentifier = _builder.ToString(); + + return LastIdentifier switch + { + "def" => (int)Token.Def, + "extern" => (int)Token.Extern, + "if" => (int)Token.If, + "then" => (int)Token.Then, + "else" => (int)Token.Else, + "for" => (int)Token.For, + "in" => (int)Token.In, + "binary" => (int)Token.Binary, + "unary" => (int)Token.Unary, + "var" => (int)Token.Var, + _ => (int)Token.Identifier, + }; + } + + // number: [0-9.]+ + if (char.IsDigit((char)_lastChar) || _lastChar == '.') + { + _builder.Clear(); + do + { + _builder.Append((char)_lastChar); + _lastChar = _reader.Read(); + } + while (char.IsDigit((char)_lastChar) || _lastChar == '.'); + + LastNumber = double.Parse(_builder.ToString(), CultureInfo.InvariantCulture); + return (int)Token.Number; + } + + // comment until end of line + if (_lastChar == '#') + { + do + { + _lastChar = _reader.Read(); + } + while (_lastChar != Eof && _lastChar != '\n' && _lastChar != '\r'); + + if (_lastChar != Eof) + { + return ReadToken(); + } + } + + // Don't eat the EOF. + if (_lastChar == Eof) + { + return (int)Token.Eof; + } + + // Otherwise, return the character as its ASCII value. + int thisChar = _lastChar; + _lastChar = _reader.Read(); + return thisChar; + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/LlvmSupport.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/LlvmSupport.cs new file mode 100644 index 00000000..ac1d8acd --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/LlvmSupport.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using LLVMSharp.Interop; + +namespace Kaleidoscope; + +/// +/// Small helpers over the raw LLVM-C API that every JIT/codegen chapter needs: one-time native +/// target initialization, a host , and turning an +/// LLVMErrorRef into a managed exception. Kept here so the chapters read like the tutorial. +/// +public static unsafe class LlvmSupport +{ + private static bool s_targetsInitialized; + + /// Initializes the native targets (idempotent). Required before JIT or object emission. + public static void EnsureTargetsInitialized() + { + if (s_targetsInitialized) + { + return; + } + + LLVM.InitializeAllTargetInfos(); + LLVM.InitializeAllTargets(); + LLVM.InitializeAllTargetMCs(); + LLVM.InitializeAllAsmPrinters(); + LLVM.InitializeAllAsmParsers(); + + s_targetsInitialized = true; + } + + /// Creates a target machine for the host, used by the optimizer and object emitter. + public static LLVMTargetMachineRef CreateHostTargetMachine() + { + EnsureTargetsInitialized(); + + string triple = LLVMTargetRef.DefaultTriple; + LLVMTargetRef target = LLVMTargetRef.GetTargetFromTriple(triple); + + return target.CreateTargetMachine( + triple, + cpu: "generic", + features: "", + LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault, + LLVMRelocMode.LLVMRelocDefault, + LLVMCodeModel.LLVMCodeModelDefault); + } + + /// Stamps a module with the target triple and data layout for object-file emission. + public static void PrepareModuleForEmit(LLVMModuleRef module, LLVMTargetMachineRef targetMachine) + { + module.Target = LLVMTargetRef.DefaultTriple; + LLVM.SetModuleDataLayout(module, targetMachine.CreateTargetDataLayout()); + } + + /// Throws if is non-null, consuming and freeing the error message. + public static void ThrowIfError(LLVMOpaqueError* error, string context) + { + if (error is null) + { + return; + } + + sbyte* message = LLVM.GetErrorMessage(error); + string text = new(message); + LLVM.DisposeErrorMessage(message); + + throw new InvalidOperationException($"{context}: {text}"); + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs new file mode 100644 index 00000000..8630bd8e --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs @@ -0,0 +1,256 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; + +namespace Kaleidoscope; + +/// +/// A recursive-descent parser producing the (tutorial chapter 2). It exposes a +/// handful of protected virtual seams — , +/// , , and — +/// so later chapters can add new grammar productions without copying the whole parser. +/// +public class Parser +{ + /// The name given to the anonymous function wrapping a top-level expression. + public const string AnonymousExpressionName = "__anon_expr"; + + public Parser(Lexer lexer) => Lexer = lexer; + + protected Lexer Lexer { get; } + + // definition ::= 'def' prototype expression + public FunctionAST? ParseDefinition() + { + Lexer.GetNextToken(); // eat def. + + PrototypeAST? proto = ParsePrototype(); + if (proto is null) + { + return null; + } + + ExprAST? body = ParseExpression(); + return body is null ? null : new FunctionAST(proto, body); + } + + // external ::= 'extern' prototype + public PrototypeAST? ParseExtern() + { + Lexer.GetNextToken(); // eat extern. + return ParsePrototype(); + } + + // toplevelexpr ::= expression + public FunctionAST? ParseTopLevelExpr() + { + ExprAST? expr = ParseExpression(); + if (expr is null) + { + return null; + } + + // Make an anonymous prototype so a top-level expression can be JIT-compiled and called. + var proto = new PrototypeAST(AnonymousExpressionName, Array.Empty()); + return new FunctionAST(proto, expr); + } + + protected static ExprAST? LogError(string message) + { + Console.Error.WriteLine($"Error: {message}"); + return null; + } + + protected static PrototypeAST? LogErrorProto(string message) + { + Console.Error.WriteLine($"Error: {message}"); + return null; + } + + // expression ::= unary binoprhs + protected ExprAST? ParseExpression() + { + ExprAST? lhs = ParseUnary(); + return lhs is null ? null : ParseBinOpRHS(0, lhs); + } + + // primary + // ::= identifierexpr + // ::= numberexpr + // ::= parenexpr + // ::= + protected virtual ExprAST? ParsePrimary() + { + switch (Lexer.CurrentToken) + { + case (int)Token.Identifier: + return ParseIdentifierExpr(); + + case (int)Token.Number: + return ParseNumberExpr(); + + case '(': + return ParseParenExpr(); + + default: + if (TryParseKeywordPrimary(Lexer.CurrentToken, out ExprAST? result)) + { + return result; + } + + return LogError("unknown token when expecting an expression"); + } + } + + /// Hook for chapters that add primary forms (if/for in ch5, var in ch7). + protected virtual bool TryParseKeywordPrimary(int token, out ExprAST? result) + { + result = null; + return false; + } + + // unary ::= primary | unary. Chapter 6 overrides this to parse user-defined unary operators. + protected virtual ExprAST? ParseUnary() => ParsePrimary(); + + // binoprhs ::= ( unary)* + protected ExprAST? ParseBinOpRHS(int expressionPrecedence, ExprAST lhs) + { + while (true) + { + int tokenPrecedence = Lexer.GetTokenPrecedence(); + + // If this binop binds less tightly than the current one, we are done. + if (tokenPrecedence < expressionPrecedence) + { + return lhs; + } + + int binaryOp = Lexer.CurrentToken; + Lexer.GetNextToken(); // eat binop. + + ExprAST? rhs = ParseUnary(); + if (rhs is null) + { + return null; + } + + // If the operator after RHS binds more tightly, let it take RHS as its LHS. + int nextPrecedence = Lexer.GetTokenPrecedence(); + if (tokenPrecedence < nextPrecedence) + { + rhs = ParseBinOpRHS(tokenPrecedence + 1, rhs); + if (rhs is null) + { + return null; + } + } + + lhs = new BinaryExprAST((char)binaryOp, lhs, rhs); + } + } + + // prototype ::= id '(' id* ')'. Chapter 6 overrides this to parse operator prototypes. + protected virtual PrototypeAST? ParsePrototype() + { + if (Lexer.CurrentToken != (int)Token.Identifier) + { + return LogErrorProto("Expected function name in prototype"); + } + + string fnName = Lexer.LastIdentifier; + Lexer.GetNextToken(); + + if (Lexer.CurrentToken != '(') + { + return LogErrorProto("Expected '(' in prototype"); + } + + var argNames = new List(); + while (Lexer.GetNextToken() == (int)Token.Identifier) + { + argNames.Add(Lexer.LastIdentifier); + } + + if (Lexer.CurrentToken != ')') + { + return LogErrorProto("Expected ')' in prototype"); + } + + Lexer.GetNextToken(); // eat ')'. + return new PrototypeAST(fnName, argNames); + } + + // numberexpr ::= number + private ExprAST ParseNumberExpr() + { + var result = new NumberExprAST(Lexer.LastNumber); + Lexer.GetNextToken(); // consume the number. + return result; + } + + // parenexpr ::= '(' expression ')' + private ExprAST? ParseParenExpr() + { + Lexer.GetNextToken(); // eat (. + + ExprAST? v = ParseExpression(); + if (v is null) + { + return null; + } + + if (Lexer.CurrentToken != ')') + { + return LogError("expected ')'"); + } + + Lexer.GetNextToken(); // eat ). + return v; + } + + // identifierexpr + // ::= identifier + // ::= identifier '(' expression* ')' + private ExprAST? ParseIdentifierExpr() + { + string idName = Lexer.LastIdentifier; + Lexer.GetNextToken(); // eat identifier. + + if (Lexer.CurrentToken != '(') + { + return new VariableExprAST(idName); // simple variable ref. + } + + Lexer.GetNextToken(); // eat (. + var args = new List(); + + if (Lexer.CurrentToken != ')') + { + while (true) + { + ExprAST? arg = ParseExpression(); + if (arg is null) + { + return null; + } + + args.Add(arg); + + if (Lexer.CurrentToken == ')') + { + break; + } + + if (Lexer.CurrentToken != ',') + { + return LogError("Expected ')' or ',' in argument list"); + } + + Lexer.GetNextToken(); + } + } + + Lexer.GetNextToken(); // eat ). + return new CallExprAST(idName, args); + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/ReplDriver.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/ReplDriver.cs new file mode 100644 index 00000000..f296997c --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/ReplDriver.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using Kaleidoscope.AST; + +namespace Kaleidoscope; + +/// +/// The top-level "read a token, decide what to parse" loop (tutorial chapter 2). It is unchanged from +/// chapter to chapter, so it lives here: it drives the lexer, recovers from a parse error by skipping a +/// token, and hands each parsed construct to a protected abstract that the concrete driver +/// implements (dump IR, JIT and run, or emit an object file). +/// +public abstract class ReplDriver +{ + protected ReplDriver(Lexer lexer, Parser parser) + { + Lexer = lexer; + Parser = parser; + } + + protected Lexer Lexer { get; } + + protected Parser Parser { get; } + + /// Runs the loop until end-of-file. + public void Run() + { + Lexer.GetNextToken(); // prime the first token. + + while (true) + { + switch (Lexer.CurrentToken) + { + case (int)Token.Eof: + { + return; + } + + case ';': + { + Lexer.GetNextToken(); // ignore top-level semicolons. + break; + } + + case (int)Token.Def: + { + HandleDefinition(); + break; + } + + case (int)Token.Extern: + { + HandleExtern(); + break; + } + + default: + { + HandleTopLevelExpression(); + break; + } + } + } + } + + /// Consumes and code-generates a def. + protected abstract void OnDefinition(FunctionAST function); + + /// Consumes and registers an extern. + protected abstract void OnExtern(PrototypeAST prototype); + + /// Consumes and evaluates a top-level expression. + protected abstract void OnTopLevelExpression(FunctionAST function); + + private void HandleDefinition() + { + FunctionAST? function = Parser.ParseDefinition(); + if (function is null) + { + Lexer.GetNextToken(); // skip token for error recovery. + return; + } + + Dispatch(() => OnDefinition(function)); + } + + private void HandleExtern() + { + PrototypeAST? prototype = Parser.ParseExtern(); + if (prototype is null) + { + Lexer.GetNextToken(); // skip token for error recovery. + return; + } + + Dispatch(() => OnExtern(prototype)); + } + + private void HandleTopLevelExpression() + { + FunctionAST? function = Parser.ParseTopLevelExpr(); + if (function is null) + { + Lexer.GetNextToken(); // skip token for error recovery. + return; + } + + Dispatch(() => OnTopLevelExpression(function)); + } + + private static void Dispatch(Action action) + { + try + { + action(); + } + catch (Exception exception) + { + Console.Error.WriteLine($"Error: {exception.Message}"); + } + } +} diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Token.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Token.cs new file mode 100644 index 00000000..3dd228c3 --- /dev/null +++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Token.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +namespace Kaleidoscope; + +/// +/// The token kinds produced by the . Any positive value returned by the +/// lexer is a literal character (an operator or punctuation); the negative values below are +/// the "known" tokens. The full set for every chapter lives here so a single lexer can serve +/// all of them — early chapters simply never ask the parser to accept the later keywords. +/// +public enum Token +{ + Eof = -1, + + // commands + Def = -2, + Extern = -3, + + // primary + Identifier = -4, + Number = -5, + + // control flow (chapter 5) + If = -6, + Then = -7, + Else = -8, + For = -9, + In = -10, + + // user-defined operators (chapter 6) + Binary = -11, + Unary = -12, + + // mutable variables (chapter 7) + Var = -13, +} diff --git a/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx b/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx new file mode 100644 index 00000000..a1e16ef7 --- /dev/null +++ b/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/samples/KaleidoscopeTutorial/README.md b/samples/KaleidoscopeTutorial/README.md new file mode 100644 index 00000000..7100a10b --- /dev/null +++ b/samples/KaleidoscopeTutorial/README.md @@ -0,0 +1,103 @@ +# Kaleidoscope tutorial (LLVMSharp) + +A C# port of LLVM's [Kaleidoscope tutorial](https://llvm.org/docs/tutorial/), built on +[LLVMSharp.Interop](../../sources/LLVMSharp.Interop). Kaleidoscope is a small functional language +(every value is a `double`) that the tutorial grows one feature at a time while introducing the LLVM +APIs for building IR, optimizing it, JIT-executing it, and finally emitting native object code. + +## Layout + +Unlike the upstream C++ tutorial — which copies the whole compiler into each chapter — this port keeps +the invariant frontend in a shared `Kaleidoscope.Common` library, and each chapter project contains +**only its delta** over the previous one. That makes the incremental steps easy to see: a chapter's +`Parser`/`CodeGenVisitor` derive from the previous chapter's and override just the new productions. + +``` +Kaleidoscope.Common/ Lexer, Token, the full AST, base Parser + base CodeGenVisitor, + the ORC LLJIT wrapper, the REPL loop, and host functions. +Chapter3/ Emit LLVM IR (no execution) — dumps the module. +Chapter4/ Add the JIT and the optimizer (new pass manager). +Chapter5/ Add control flow: if/then/else and for loops. +Chapter6/ Add user-defined unary and binary operators. +Chapter7/ Add mutable variables (alloca + mem2reg) and assignment. +Chapter8/ Compile a whole program to a native object file. +``` + +Each chapter references the previous one, so `Chapter7` transitively sees `Chapter3`–`Chapter6` and +`Kaleidoscope.Common`. Chapters 4 and 8 are purely a new driver over the previous chapter's frontend; +Chapters 5–7 add grammar and codegen. + +### Chapter ↔ tutorial mapping + +| Chapter | Tutorial | Adds | +| --- | --- | --- | +| 3 | Ch. 2–3 | Lexer, parser, AST, IR generation (dump only) | +| 4 | Ch. 4 | ORC LLJIT execution + optimization passes | +| 5 | Ch. 5 | `if`/`then`/`else`, `for` loops | +| 6 | Ch. 6 | User-defined operators, operator precedence | +| 7 | Ch. 7 | Mutable variables, `var`/`in`, `=` assignment | +| 8 | Ch. 8 | Native object-file emission | + +The tutorial's Chapter 9 (debug info / DWARF) and Chapter 10 (conclusion) are not ported. + +## Running + +Requires the .NET 10 preview SDK (resolved via the repo's `global.json`). From this directory: + +``` +dotnet build KaleidoscopeTutorial.slnx -c Release +dotnet run --project Chapter4 -c Release +``` + +Chapters 3–7 read Kaleidoscope from stdin (or a file passed as the first argument) as a REPL. Try: + +``` +def fib(x) if x < 3 then 1 else fib(x - 1) + fib(x - 2); +fib(10); + +extern sin(x); +sin(1.0); + +# user-defined operators (chapter 6+) +def unary!(v) if v then 0 else 1; +!0; + +# mutable variables (chapter 7+) +def binary : 1 (x y) y; +def fibi(x) var a = 1, b = 1, c in (for i = 3, i < x in c = a + b : a = b : b = c) : b; +fibi(10); +``` + +Chapter 8 is a batch compiler: it reads a whole program and writes an object file (default `output.o`, +or a second argument): + +``` +echo "def average(x y) (x + y) * 0.5;" | dotnet run --project Chapter8 -c Release +``` + +The resulting object exports `average` with C ABI (`double average(double, double)`), so it can be +linked into a C/C++ program. + +## Tests + +These samples are part of the root `LLVMSharp.slnx`, so they build and are validated in CI. The +`LLVMSharp.KaleidoscopeTests` project under `tests/` launches each chapter as a subprocess, feeds it a +Kaleidoscope script, and asserts on the emitted IR / evaluated results / object file. Run them with: + +``` +dotnet build -c Release +dotnet test -c Release --no-build --filter "FullyQualifiedName~KaleidoscopeTests" +``` + +## Notes + +- **JIT.** Execution uses ORC LLJIT (`Kaleidoscope.Common/KaleidoscopeJit.cs`). Each top-level + expression is added as its own module under a resource tracker, executed, then removed, so entering + several expressions in a row works correctly. +- **Extern / host functions.** `extern` declarations resolve against the host process (e.g. libc `sin`, + `cos`), and `putchard`/`printd` are injected as absolute symbols (`Kaleidoscope.Common/HostFunctions.cs`) + so the tutorial's `printstar`/`printd` examples work. +- **Optimizer.** Chapters 4+ run the new pass-manager pipeline + (`mem2reg,instcombine,reassociate,gvn,simplifycfg`) via `LLVM.RunPasses`. +- The interop under `../../sources/LLVMSharp.Interop/llvm` is auto-generated; these samples only use the + hand-written friendly wrappers and the raw ORC C API. diff --git a/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs b/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs new file mode 100644 index 00000000..ce76d2fe --- /dev/null +++ b/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs @@ -0,0 +1,225 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using NUnit.Framework; + +namespace LLVMSharp.KaleidoscopeTests; + +/// +/// Smoke tests that each Kaleidoscope tutorial chapter still builds and produces the expected output. +/// Every chapter is an executable REPL (or, for chapter 8, a batch compiler), so each test launches the +/// chapter as a subprocess, feeds it a Kaleidoscope script on stdin, and asserts on what it prints. +/// Running each chapter in its own process matches how a user runs the samples and keeps the native LLVM +/// state isolated between chapters. +/// +public sealed class ChapterTests +{ + private const int TimeoutMilliseconds = 120_000; + + [Test] + public void Chapter3_EmitsIr() + { + var output = RunChapter("Chapter3", """ + extern sin(x); + def foo(a b) a + b; + foo(2, 3); + """); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("declare double @sin(double)")); + Assert.That(output, Does.Contain("define double @foo(double %a, double %b)")); + }); + } + + [Test] + public void Chapter4_JitsAndOptimizes() + { + // Two calls to the same function must give distinct results in one session (upstream #1), and + // an extern libc function must resolve from the host process (#69). + var output = RunChapter("Chapter4", """ + def foo(a b) a*a + 2*a*b + b*b; + foo(2, 3); + foo(4, 5); + extern cos(x); + cos(0); + """); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("Evaluated to 25")); + Assert.That(output, Does.Contain("Evaluated to 81")); + Assert.That(output, Does.Contain("Evaluated to 1")); + }); + } + + [Test] + public void Chapter5_ControlFlowAndHostOutput() + { + // fib exercises if/else; printstar exercises the for loop plus the putchard host function (#133). + var output = RunChapter("Chapter5", """ + def fib(x) if x < 3 then 1 else fib(x - 1) + fib(x - 2); + fib(10); + extern putchard(x); + def printstar(n) for i = 1, i < n in putchard(42); + printstar(6); + """); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("Evaluated to 55")); + Assert.That(output.Count(c => c == '*'), Is.EqualTo(6), "printstar(6) runs the loop while i < 6 starting at i = 1, printing six '*' characters"); + }); + } + + [Test] + public void Chapter6_UserDefinedOperators() + { + var output = RunChapter("Chapter6", """ + def unary!(v) if v then 0 else 1; + !0; + !1; + """); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("Evaluated to 1")); + Assert.That(output, Does.Contain("Evaluated to 0")); + }); + } + + [Test] + public void Chapter7_MutableVariables() + { + // test mutates its parameter; fib is the iterative mutable-variable formulation. + var output = RunChapter("Chapter7", """ + def binary : 1 (x y) y; + def test(x) (x = x + 1) + x; + test(3); + def fib(x) var a = 1, b = 1, c in (for i = 3, i < x in c = a + b : a = b : b = c) : b; + fib(10); + """); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("Evaluated to 8")); + Assert.That(output, Does.Contain("Evaluated to 55")); + }); + } + + [Test] + public void Chapter8_EmitsObjectFile() + { + var workingDirectory = Directory.CreateTempSubdirectory("kaleidoscope-ch8-").FullName; + + try + { + var output = RunChapter("Chapter8", "def average(x y) (x + y) * 0.5;", workingDirectory); + var objectFile = Path.Combine(workingDirectory, "output.o"); + + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("define double @average")); + Assert.That(File.Exists(objectFile), Is.True, "expected output.o to be emitted"); + Assert.That(new FileInfo(objectFile).Length, Is.GreaterThan(0), "output.o should not be empty"); + }); + } + finally + { + Directory.Delete(workingDirectory, recursive: true); + } + } + + private static string RunChapter(string chapter, string input, string? workingDirectory = null) + { + var assembly = LocateChapterAssembly(chapter); + + var startInfo = new ProcessStartInfo("dotnet") + { + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(assembly)!, + }; + startInfo.ArgumentList.Add("exec"); + startInfo.ArgumentList.Add(assembly); + + using var process = new Process { StartInfo = startInfo }; + var standardOutput = new StringBuilder(); + var standardError = new StringBuilder(); + process.OutputDataReceived += (_, e) => { if (e.Data is not null) { _ = standardOutput.AppendLine(e.Data); } }; + process.ErrorDataReceived += (_, e) => { if (e.Data is not null) { _ = standardError.AppendLine(e.Data); } }; + + _ = process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.StandardInput.Write(input); + process.StandardInput.Close(); + + if (!process.WaitForExit(TimeoutMilliseconds)) + { + process.Kill(entireProcessTree: true); + Assert.Fail($"{chapter} did not exit within {TimeoutMilliseconds}ms.\nstdout:\n{standardOutput}\nstderr:\n{standardError}"); + } + + process.WaitForExit(); // flush the async output readers. + + Assert.That(process.ExitCode, Is.Zero, $"{chapter} exited with {process.ExitCode}.\nstdout:\n{standardOutput}\nstderr:\n{standardError}"); + return standardOutput.ToString(); + } + + private static string LocateChapterAssembly(string chapter) + { + var repositoryRoot = FindRepositoryRoot(); + var configuration = GetBuildConfiguration(); + var chapterBin = Path.Combine(repositoryRoot, "samples", "KaleidoscopeTutorial", chapter, "bin", configuration, "net10.0"); + + if (Directory.Exists(chapterBin)) + { + // The chapter builds to a runtime-identifier subdirectory (e.g. .../net10.0/win-x64/), so + // search rather than hard-coding the RID. + var assembly = Directory.EnumerateFiles(chapterBin, chapter + ".dll", SearchOption.AllDirectories).FirstOrDefault(); + if (assembly is not null) + { + return assembly; + } + } + + Assert.Fail($"Could not find {chapter}.dll under '{chapterBin}'. Build the '{configuration}' configuration of the solution before running these tests."); + return string.Empty; // unreachable; Assert.Fail throws. + } + + private static string FindRepositoryRoot() + { + for (var directory = new DirectoryInfo(AppContext.BaseDirectory); directory is not null; directory = directory.Parent) + { + if (File.Exists(Path.Combine(directory.FullName, "LLVMSharp.slnx")) && + Directory.Exists(Path.Combine(directory.FullName, "samples"))) + { + return directory.FullName; + } + } + + Assert.Fail("Could not locate the repository root (no ancestor directory contains LLVMSharp.slnx and samples/)."); + return string.Empty; // unreachable; Assert.Fail throws. + } + + private static string GetBuildConfiguration() + { + foreach (var metadata in typeof(ChapterTests).Assembly.GetCustomAttributes()) + { + if ((metadata.Key == "BuildConfiguration") && !string.IsNullOrEmpty(metadata.Value)) + { + return metadata.Value; + } + } + + return "Release"; + } +} diff --git a/tests/LLVMSharp.KaleidoscopeTests/LLVMSharp.KaleidoscopeTests.csproj b/tests/LLVMSharp.KaleidoscopeTests/LLVMSharp.KaleidoscopeTests.csproj new file mode 100644 index 00000000..4bb50c50 --- /dev/null +++ b/tests/LLVMSharp.KaleidoscopeTests/LLVMSharp.KaleidoscopeTests.csproj @@ -0,0 +1,23 @@ + + + + + net10.0 + + + + + + + + + $(NoWarn);CA1515;CA1707;CA1711;CA1724;IDE0130 + + + + + + + +