Skip to content

Commit b378657

Browse files
authored
Added support to indicate if function redifitinion is supported. (#342)
* For LazyJIT re-definition is NOT supported. - If it is possible to do, the solution , using only LLVM-C API is far from obvious. The asynchronous nature of resolution makes it a VERY complex problem. Fortunately, redefinition is a feature of dynamic runtimes that don't normally use lazy code execution so it's mostly an acedemic excercise. This implementation deals with it as an error at the AST generation with an error. The `DynamicRuntimeState` is used to indicate if the runtime supports re-definitions. * Minor doc comment updates
1 parent 4838941 commit b378657

File tree

12 files changed

+140
-27
lines changed

12 files changed

+140
-27
lines changed

src/Samples/Kaleidoscope/Chapter7.1/ReplEngine.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ namespace Kaleidoscope.Chapter71
1414
internal class ReplEngine
1515
: ReadEvaluatePrintLoopBase<Value>
1616
{
17+
// This uses Lazy JIT evaluation so redefinition is not supported.
1718
public ReplEngine( )
18-
: base( LanguageLevel.MutableVariables )
19+
: base( LanguageLevel.MutableVariables, functionRedfinitionIsAnError: true )
1920
{
2021
}
2122

src/Samples/Kaleidoscope/Kaleidoscope.Grammar/AST/AstBuilder.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,28 @@ public override IAstNode VisitFunctionDefinition( FunctionDefinitionContext cont
133133
);
134134

135135
// only add valid definitions to the runtime state.
136-
if(errors.Length == 0)
137-
{
138-
RuntimeState.FunctionDefinitions.AddOrReplaceItem( retVal );
139-
}
140-
else
136+
if(errors.Length > 0)
141137
{
142138
// remove the prototype implicitly added for this definition
143139
// as the definition has errors
144140
RuntimeState.FunctionDeclarations.Remove( sig );
145141
}
142+
else
143+
{
144+
if(RuntimeState.SupportsRedefinition)
145+
{
146+
RuntimeState.FunctionDefinitions.AddOrReplaceItem( retVal );
147+
}
148+
else
149+
{
150+
if(RuntimeState.FunctionDefinitions.Contains(retVal.Name))
151+
{
152+
return new ErrorNode(retVal.Location, (int)DiagnosticCode.RedclarationNotSupported, "Duplicate function name, redefinitions not allowed." );
153+
}
154+
155+
RuntimeState.FunctionDefinitions.Add( retVal );
156+
}
157+
}
146158

147159
return retVal;
148160
}

src/Samples/Kaleidoscope/Kaleidoscope.Grammar/AST/DiagnosticCode.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,8 @@ public enum DiagnosticCode
4242

4343
/// <summary>Parse was cancelled</summary>
4444
ParseCanceled = 2000,
45+
46+
/// <summary>Re-declaration is not supported in the runtime</summary>
47+
RedclarationNotSupported,
4548
}
4649
}

src/Samples/Kaleidoscope/Kaleidoscope.Grammar/DynamicRuntimeState.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,40 @@ namespace Kaleidoscope.Grammar
3434
/// </remarks>
3535
public class DynamicRuntimeState
3636
{
37+
/// <remarks>This overload supports function re-definition for the normal case</remarks>
38+
/// <inheritdoc cref="DynamicRuntimeState.DynamicRuntimeState(LanguageLevel, bool)"/>
39+
public DynamicRuntimeState( LanguageLevel languageLevel )
40+
: this(languageLevel, functionRedefinitionIsAnError: false)
41+
{
42+
}
43+
3744
/// <summary>Initializes a new instance of the <see cref="DynamicRuntimeState"/> class.</summary>
45+
/// <param name="functionRedefinitionIsAnError">Flag to indicate if function definitions are an error</param>
3846
/// <param name="languageLevel">Language level supported for this instance</param>
39-
public DynamicRuntimeState( LanguageLevel languageLevel )
47+
/// <remarks>
48+
/// <see cref="SupportsRedefinition"/> is used to indicate if this language implementation supports
49+
/// redefinition of functions. This is ordinarily allowed for interactive languages but if an extreme
50+
/// lazy JIT is used, the asynchronous nature of materialization makes it a very difficult (maybe
51+
/// impossible) task to accomplish. So Such runtimes will generally not support re-definition.
52+
/// </remarks>
53+
public DynamicRuntimeState( LanguageLevel languageLevel, bool functionRedefinitionIsAnError = false )
4054
{
4155
LanguageLevel = languageLevel;
56+
SupportsRedefinition = !functionRedefinitionIsAnError;
4257
}
4358

4459
/// <summary>Gets or sets the Language level the application supports</summary>
4560
public LanguageLevel LanguageLevel { get; set; }
4661

62+
/// <summary>Gets a value indicating whether this runtime supports redefinition of functions</summary>
63+
/// <remarks>
64+
/// Extreme Lazy JIT realization does not support redifinition at this point. It's s complicated problem
65+
/// that is not entirely clear how to solve using the LLVM-C API. Thus, it is currently not supported. This
66+
/// is generally not an issue as such lazy JIT execution is ordinarily not used for an interactive runtime
67+
/// where function re-definition is used.
68+
/// </remarks>
69+
public bool SupportsRedefinition { get; init; }
70+
4771
/// <summary>Gets a collection of function definitions parsed but not yet generated</summary>
4872
/// <remarks>
4973
/// This is a potentially dynamic set as parsing can add new entries. Also, when fully lazy

src/Samples/Kaleidoscope/Kaleidoscope.Grammar/Parser.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,16 @@ public class Parser
3030
/// <param name="level"><see cref="LanguageLevel"/> for the parser</param>
3131
/// <param name="visualizer">Visualizer to use for the parse (Includes possible error nodes)</param>
3232
public Parser( LanguageLevel level, IVisualizer? visualizer = null )
33-
: this( new DynamicRuntimeState( level ), visualizer )
33+
: this( new DynamicRuntimeState( level, functionRedefinitionIsAnError: false ), visualizer )
34+
{
35+
}
36+
37+
/// <summary>Initializes a new instance of the <see cref="Parser"/> class.</summary>
38+
/// <param name="level"><see cref="LanguageLevel"/> for the parser</param>
39+
/// <param name="functionRedefinitionIsAnError">flag to indicate if redefinition of a function is an error</param>
40+
/// <param name="visualizer">Visualizer to use for the parse (Includes possible error nodes)</param>
41+
public Parser( LanguageLevel level, bool functionRedefinitionIsAnError, IVisualizer? visualizer = null )
42+
: this( new DynamicRuntimeState( level, functionRedefinitionIsAnError ), visualizer )
3443
{
3544
}
3645

src/Samples/Kaleidoscope/Kaleidoscope.Runtime/ReadEvaluatePrintLoopBase.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public abstract class ReadEvaluatePrintLoopBase<T>
2121
/// <summary>Gets the Kaleidoscope language level for this instance</summary>
2222
public LanguageLevel LanguageFeatureLevel { get; }
2323

24+
/// <summary>Gets a value indicating whether this variant of the language runtime supports function re-definition</summary>
25+
public bool SupportsRedefinition { get; }
26+
2427
/// <summary>Gets the <see cref="TextWriter"/> to use of output from the loop</summary>
2528
public TextWriter Output { get; init; } = Console.Out;
2629

@@ -59,31 +62,34 @@ public sealed override void ShowPrompt( ReadyState state )
5962
/// <returns><see cref="Task"/>for the REPL operation</returns>
6063
public async Task Run( TextReader input, IVisualizer? visualizer, CancellationToken cancelToken = default )
6164
{
62-
var parser = new Kaleidoscope.Grammar.Parser(LanguageFeatureLevel, visualizer);
65+
var parser = new Kaleidoscope.Grammar.Parser(LanguageFeatureLevel, functionRedefinitionIsAnError: !SupportsRedefinition, visualizer);
6366
using ICodeGenerator<T> generator = CreateGenerator( parser.GlobalState );
6467
await Run( input, parser, generator, cancelToken );
6568
}
6669

6770
/// <summary>Initializes a new instance of the <see cref="ReadEvaluatePrintLoopBase{T}"/> class.</summary>
6871
/// <param name="level">Language level supported by this REPL instance</param>
72+
/// <param name="functionRedfinitionIsAnError">Flag to indicate whether function redefinition is an error or allowed</param>
6973
/// <remarks>
7074
/// This is protected to prevent use by anything other than a derived type.
7175
/// </remarks>
72-
protected ReadEvaluatePrintLoopBase( LanguageLevel level )
73-
: this(level, new ParseErrorDiagnosticAdapter(new ColoredConsoleReporter(), "KLS"))
76+
protected ReadEvaluatePrintLoopBase( LanguageLevel level, bool functionRedfinitionIsAnError = false )
77+
: this(level, new ParseErrorDiagnosticAdapter(new ColoredConsoleReporter(), "KLS"), functionRedfinitionIsAnError )
7478
{
7579
}
7680

7781
/// <summary>Initializes a new instance of the <see cref="ReadEvaluatePrintLoopBase{T}"/> class.</summary>
7882
/// <param name="level">Language level supported by this REPL instance</param>
7983
/// <param name="logger">Logger to report any issues parsing the input.</param>
84+
/// <param name="functionRedfinitionIsAnError">Flag to indicate whether function redefinition is an error or allowed</param>
8085
/// <remarks>
8186
/// This is protected to prevent use by anything other than a derived type.
8287
/// </remarks>
83-
protected ReadEvaluatePrintLoopBase( LanguageLevel level, IParseErrorReporter logger )
88+
protected ReadEvaluatePrintLoopBase( LanguageLevel level, IParseErrorReporter logger, bool functionRedfinitionIsAnError = false )
8489
: base( logger )
8590
{
8691
LanguageFeatureLevel = level;
92+
SupportsRedefinition = !functionRedfinitionIsAnError;
8793
}
8894
}
8995
}

src/Samples/Kaleidoscope/Kaleidoscope.Tests/BasicTests.cs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
33

44
using System;
5+
using System.Collections.Immutable;
56
using System.Diagnostics.CodeAnalysis;
67
using System.IO;
78
using System.Linq;
89
using System.Threading.Tasks;
910

1011
using Kaleidoscope.Grammar;
12+
using Kaleidoscope.Grammar.AST;
1113

1214
using Microsoft.VisualStudio.TestTools.UnitTesting;
1315

@@ -50,74 +52,108 @@ public async Task Chapter2( )
5052
public async Task Chapter3( )
5153
{
5254
using var input = File.OpenText( "simpleExpressions.kls" );
53-
await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter3.CodeGenerator( state ) );
55+
var errors = await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter3.CodeGenerator( state ) );
56+
Assert.IsEmpty( errors );
5457
}
5558

5659
[TestMethod]
5760
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
5861
public async Task Chapter3_5( )
5962
{
6063
using var input = File.OpenText( "simpleExpressions.kls" );
61-
await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter3_5.CodeGenerator( state ) );
64+
var errors = await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter3_5.CodeGenerator( state ) );
65+
Assert.IsEmpty( errors );
6266
}
6367

6468
[TestMethod]
6569
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
6670
public async Task Chapter4( )
6771
{
6872
using var input = File.OpenText( "simpleExpressions.kls" );
69-
await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter4.CodeGenerator( state, writer ) );
73+
var errors = await RunBasicReplLoop( LanguageLevel.SimpleExpressions, input, ( state, writer ) => new Chapter4.CodeGenerator( state, writer ) );
74+
Assert.IsEmpty( errors );
7075
}
7176

7277
[TestMethod]
7378
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
7479
public async Task Chapter5( )
7580
{
7681
using var input = File.OpenText( "ControlFlow.kls" );
77-
await RunBasicReplLoop( LanguageLevel.ControlFlow, input, ( state, writer ) => new Chapter5.CodeGenerator( state, writer ) );
82+
var errors = await RunBasicReplLoop( LanguageLevel.ControlFlow, input, ( state, writer ) => new Chapter5.CodeGenerator( state, writer ) );
83+
Assert.IsEmpty( errors );
7884
}
7985

8086
[TestMethod]
8187
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
8288
public async Task Chapter6( )
8389
{
8490
using var input = File.OpenText( "mandel.kls" );
85-
await RunBasicReplLoop( LanguageLevel.UserDefinedOperators, input, ( state, writer ) => new Chapter6.CodeGenerator( state, writer ) );
91+
var errors = await RunBasicReplLoop( LanguageLevel.UserDefinedOperators, input, ( state, writer ) => new Chapter6.CodeGenerator( state, writer ) );
92+
Assert.IsEmpty( errors );
8693
}
8794

8895
[TestMethod]
8996
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
9097
public async Task Chapter7( )
9198
{
9299
using var input = File.OpenText( "fibi.kls" );
93-
await RunBasicReplLoop( LanguageLevel.MutableVariables, input, ( state, writer ) => new Chapter7.CodeGenerator( state, writer ) );
100+
var errors = await RunBasicReplLoop( LanguageLevel.MutableVariables, input, ( state, writer ) => new Chapter7.CodeGenerator( state, writer ) );
101+
Assert.IsEmpty( errors );
94102
}
95103

96104
[TestMethod]
97105
[Description( "Basic test of Chapter parsing and code generation to ensure it doesn't crash on well-known good input [output is not validated in this test]" )]
98106
public async Task Chapter71( )
99107
{
100108
using var input = File.OpenText( "fibi.kls" );
101-
await RunBasicReplLoop( LanguageLevel.MutableVariables, input, ( state, writer ) => new Chapter71.CodeGenerator( state, writer ) );
109+
var errors = await RunBasicReplLoop( LanguageLevel.MutableVariables, input, ( state, writer ) => new Chapter71.CodeGenerator( state, writer ) );
110+
Assert.IsEmpty( errors );
102111
}
103112

104-
private async Task RunBasicReplLoop( LanguageLevel level
105-
, TextReader input
106-
, Func<DynamicRuntimeState, TextWriter, ICodeGenerator<Value>> generatorFactory
107-
)
113+
[TestMethod]
114+
[Description( "Test of redefinition not supported with lazy JIT [output is not validated in this test]" )]
115+
public async Task Chapter71_with_redefinition( )
116+
{
117+
using var input = File.OpenText( "Redefinition.kls" );
118+
var errors = await RunBasicReplLoop(
119+
LanguageLevel.MutableVariables,
120+
functionRedefinitionIsAnError: true,
121+
input,
122+
( state, writer ) => new Chapter71.CodeGenerator( state, writer )
123+
);
124+
Assert.HasCount( 1, errors );
125+
Assert.AreEqual( (int)DiagnosticCode.RedclarationNotSupported, errors[ 0 ].Code );
126+
}
127+
128+
private Task<ImmutableArray<ErrorNode>> RunBasicReplLoop( LanguageLevel level
129+
, TextReader input
130+
, Func<DynamicRuntimeState, TextWriter, ICodeGenerator<Value>> generatorFactory
131+
)
132+
{
133+
return RunBasicReplLoop( level, functionRedefinitionIsAnError: false, input, generatorFactory );
134+
}
135+
136+
private async Task<ImmutableArray<ErrorNode>> RunBasicReplLoop( LanguageLevel level
137+
, bool functionRedefinitionIsAnError
138+
, TextReader input
139+
, Func<DynamicRuntimeState, TextWriter, ICodeGenerator<Value>> generatorFactory
140+
)
108141
{
109-
var parser = new Parser( level );
142+
var parser = new Parser( level, functionRedefinitionIsAnError );
110143
using var outputWriter = new TestContextTextWriter( RuntimeContext );
111144
using var generator = generatorFactory( parser.GlobalState, outputWriter );
112145

113146
// Create sequence of parsed AST RootNodes to feed the 'REPL' loop
114-
var replSeq = from stmt in input.ToStatements( _=>{ }, RuntimeContext.CancellationTokenSource.Token )
147+
var replSeq = from stmt in input.ToStatements( _=>{ }, RuntimeContext.CancellationToken )
115148
select parser.Parse( stmt );
116149

117150
await foreach(IAstNode node in replSeq)
118151
{
119152
var errors = node.CollectErrors();
120-
Assert.IsEmpty( errors );
153+
if(errors.Length > 0)
154+
{
155+
return errors;
156+
}
121157

122158
var result = generator.Generate( node );
123159

@@ -139,6 +175,8 @@ private async Task RunBasicReplLoop( LanguageLevel level
139175
}
140176
}
141177
}
178+
179+
return [];
142180
}
143181

144182
private readonly TestContext RuntimeContext;

src/Samples/Kaleidoscope/Kaleidoscope.Tests/Kaleidoscope.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<None Update="mandel.kls">
3535
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3636
</None>
37+
<None Update="Redefinition.kls">
38+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
39+
</None>
3740
<None Update="SimpleExpressions.kls">
3841
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3942
</None>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# This is intended as a test case to identify issues with redefinition of
2+
# symbols in a LazyJIT scenario. (Either it works or is at least flagged
3+
# as an error before triggering an error deep in LLVM.
4+
5+
def foo(a b) a*a + 2*a*b + b*b;
6+
7+
# Ideally, this second definition should replace the first, or produce
8+
# an error not crash with a non-string LLVMErrorRef...
9+
def foo(a b) a*a + 2*a*b;

src/Samples/Kaleidoscope/Kaleidoscope.Tests/TestContextTextWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Kaleidoscope.Tests
1010
{
11-
// Used as a target to output text of the runs to the test context
11+
// Adapter - Used as a target to output text of the runs to the test context
1212
internal class TestContextTextWriter
1313
: TextWriter
1414
{

0 commit comments

Comments
 (0)