From 2431bfe1c0eab143d5fc894bfd29fef0c2091a63 Mon Sep 17 00:00:00 2001 From: vddCore Date: Sat, 25 May 2024 23:38:56 +0200 Subject: [PATCH] Minor fixes and optimizations, make some VM exceptions recoverable. --- Core/EVIL.Lexical/Lexer.cs | 1 + .../EVIL.Ceres.Runtime/EvilRuntime.cs | 4 +- .../EVIL.Ceres.Runtime/RuntimeModule.cs | 24 +++++- VirtualMachine/EVIL.Ceres/EVIL.Ceres.csproj | 2 +- .../ExecutionEngine/ArrayException.cs | 2 +- .../ChunkInvocationException.cs | 10 --- .../ExecutionEngine/Concurrency/Fiber.cs | 86 ++++++++++++------- .../DivisionByZeroException.cs | 2 +- .../RecoverableVirtualMachineException.cs | 17 ++++ .../TypeSystem/MalformedNumberException.cs | 2 +- ...supportedDynamicValueOperationException.cs | 2 +- .../EVIL.Ceres.LanguageTests/AssertModule.cs | 24 +++++- .../Tests/EVIL.Ceres.LanguageTests/Test.cs | 38 ++++---- .../EVIL.Ceres.LanguageTests/TestRunner.cs | 3 +- .../tests/023_try.vil | 4 +- 15 files changed, 145 insertions(+), 76 deletions(-) delete mode 100644 VirtualMachine/EVIL.Ceres/ExecutionEngine/ChunkInvocationException.cs create mode 100644 VirtualMachine/EVIL.Ceres/ExecutionEngine/RecoverableVirtualMachineException.cs diff --git a/Core/EVIL.Lexical/Lexer.cs b/Core/EVIL.Lexical/Lexer.cs index 5768504..25cd619 100644 --- a/Core/EVIL.Lexical/Lexer.cs +++ b/Core/EVIL.Lexical/Lexer.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Text; +using System.Threading.Tasks; namespace EVIL.Lexical { diff --git a/VirtualMachine/EVIL.Ceres.Runtime/EvilRuntime.cs b/VirtualMachine/EVIL.Ceres.Runtime/EvilRuntime.cs index b6b2ada..ed38546 100644 --- a/VirtualMachine/EVIL.Ceres.Runtime/EvilRuntime.cs +++ b/VirtualMachine/EVIL.Ceres.Runtime/EvilRuntime.cs @@ -76,8 +76,10 @@ public List RegisterBuiltInModules() public T RegisterModule(out DynamicValue table) where T : RuntimeModule, new() { var module = new T(); + table = module.AttachTo(Global); - + module.Registered(this); + return module; } diff --git a/VirtualMachine/EVIL.Ceres.Runtime/RuntimeModule.cs b/VirtualMachine/EVIL.Ceres.Runtime/RuntimeModule.cs index b19bfbe..83ced37 100644 --- a/VirtualMachine/EVIL.Ceres.Runtime/RuntimeModule.cs +++ b/VirtualMachine/EVIL.Ceres.Runtime/RuntimeModule.cs @@ -11,6 +11,8 @@ namespace EVIL.Ceres.Runtime { public abstract class RuntimeModule : PropertyTable { + public const string GlobalMergeKey = ""; + public abstract string FullyQualifiedName { get; } public RuntimeModule() @@ -24,14 +26,28 @@ public DynamicValue AttachTo(Table table) { var ret = this; - table.SetUsingPath( - FullyQualifiedName, - ret - ); + if (FullyQualifiedName == GlobalMergeKey) + { + foreach (var (k, v) in this) table[k] = v; + } + else + { + table.SetUsingPath
( + FullyQualifiedName, + ret + ); + } return ret; } + internal void Registered(EvilRuntime runtime) + => OnRegistered(runtime); + + protected virtual void OnRegistered(EvilRuntime runtime) + { + } + private void RegisterNativeFunctions() { var validFunctions = GetType().GetMethods( diff --git a/VirtualMachine/EVIL.Ceres/EVIL.Ceres.csproj b/VirtualMachine/EVIL.Ceres/EVIL.Ceres.csproj index 99c8675..51ec186 100644 --- a/VirtualMachine/EVIL.Ceres/EVIL.Ceres.csproj +++ b/VirtualMachine/EVIL.Ceres/EVIL.Ceres.csproj @@ -8,7 +8,7 @@ truetrue - 7.5.0 + 7.6.0false diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/ArrayException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/ArrayException.cs index ea05e0a..c45791d 100644 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/ArrayException.cs +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/ArrayException.cs @@ -2,7 +2,7 @@ namespace EVIL.Ceres.ExecutionEngine { - public class ArrayException : VirtualMachineException + public class ArrayException : RecoverableVirtualMachineException { internal ArrayException(string message, Exception innerException) : base(message, innerException) diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/ChunkInvocationException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/ChunkInvocationException.cs deleted file mode 100644 index 5607c70..0000000 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/ChunkInvocationException.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EVIL.Ceres.ExecutionEngine -{ - public class ChunkInvocationException : VirtualMachineException - { - internal ChunkInvocationException(string message) - : base(message) - { - } - } -} \ No newline at end of file diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/Concurrency/Fiber.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/Concurrency/Fiber.cs index bb999ab..0e641bc 100644 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/Concurrency/Fiber.cs +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/Concurrency/Fiber.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using EVIL.Ceres.ExecutionEngine.Collections; using EVIL.Ceres.ExecutionEngine.Diagnostics; using EVIL.Ceres.ExecutionEngine.Diagnostics.Debugging; using EVIL.Ceres.ExecutionEngine.TypeSystem; @@ -12,7 +13,7 @@ namespace EVIL.Ceres.ExecutionEngine.Concurrency { public sealed class Fiber { - private Queue<(Diagnostics.Chunk Chunk, DynamicValue[] Arguments)> _scheduledChunks; + private Queue<(Chunk Chunk, DynamicValue[] Arguments)> _scheduledChunks; private HashSet _waitingFor; private Stack _evaluationStack; @@ -21,6 +22,7 @@ public sealed class Fiber private ExecutionUnit _executionUnit; private FiberCrashHandler? _crashHandler; + private FiberState _state; internal ChunkInvokeHandler? OnChunkInvoke { get; private set; } internal NativeFunctionInvokeHandler? OnNativeFunctionInvoke { get; private set; } @@ -32,7 +34,8 @@ public sealed class Fiber public CeresVM VirtualMachine { get; } - public FiberState State { get; private set; } + public FiberState State => _state; + public bool ImmuneToCollection { get; private set; } public IReadOnlyCollection EvaluationStack @@ -75,7 +78,7 @@ internal Fiber(CeresVM virtualMachine, Dictionary? closu _callStack ); - State = FiberState.Fresh; + _state = FiberState.Fresh; } public void Schedule(Chunk chunk, params DynamicValue[] args) @@ -96,8 +99,8 @@ public void Schedule(Chunk chunk, bool resumeImmediately, params DynamicValue[] public void BlockUntilFinished() { - while (State != FiberState.Finished && - State != FiberState.Crashed) + while (_state != FiberState.Finished && + _state != FiberState.Crashed) { Thread.Sleep(1); } @@ -105,8 +108,8 @@ public void BlockUntilFinished() public async Task BlockUntilFinishedAsync() { - while (State != FiberState.Finished && - State != FiberState.Crashed) + while (_state != FiberState.Finished && + _state != FiberState.Crashed) { await Task.Delay(1); } @@ -150,7 +153,7 @@ public void Step() { try { - if (State != FiberState.Running) + if (_state != FiberState.Running) return; lock (_callStack) @@ -165,7 +168,7 @@ public void Step() } else { - State = FiberState.Finished; + _state = FiberState.Finished; return; } } @@ -176,33 +179,57 @@ public void Step() } catch (Exception e) { - EnterCrashState(); + if (e is RecoverableVirtualMachineException rvme) + { + try + { + ThrowFromNative( + new Error( + new Table { { "native_exception", DynamicValue.FromObject(rvme) } }, + rvme.Message + ) + ); + } + catch + { + EnterCrashState(); - if (_crashHandler != null) + if (_crashHandler != null) + { + _crashHandler(this, e); + } + } + } + else { - _crashHandler(this, e); + EnterCrashState(); + + if (_crashHandler != null) + { + _crashHandler(this, e); + } } } } public void Yield() { - if (State != FiberState.Running) + if (_state != FiberState.Running) { return; } - State = FiberState.Paused; + _state = FiberState.Paused; } public void Yield(Fiber to) { - if (State != FiberState.Running) + if (_state != FiberState.Running) { return; } - State = FiberState.Paused; + _state = FiberState.Paused; to.Resume(); } @@ -213,7 +240,7 @@ public void Await() return; } - State = FiberState.Awaiting; + _state = FiberState.Awaiting; } public void WaitFor(Fiber fiber) @@ -223,7 +250,7 @@ public void WaitFor(Fiber fiber) throw new FiberException("A fiber cannot wait for self."); } - if (fiber.State == FiberState.Finished) + if (fiber._state == FiberState.Finished) { return; } @@ -236,11 +263,11 @@ public void StopWaitingFor(Fiber fiber) { _waitingFor.Remove(fiber); - if (State == FiberState.Awaiting) + if (_state == FiberState.Awaiting) { if (!_waitingFor.Any()) { - State = FiberState.Running; + _state = FiberState.Running; } } } @@ -256,7 +283,7 @@ public void WaitForAll(params Fiber[] fibers) throw new FiberException("A fiber cannot wait for self."); } - if (fiber.State == FiberState.Finished) + if (fiber._state == FiberState.Finished) { continue; } @@ -269,7 +296,7 @@ public void WaitForAll(params Fiber[] fibers) public void Resume() { - if (State == FiberState.Awaiting) + if (_state == FiberState.Awaiting) { if (_waitingFor.Any()) { @@ -277,7 +304,7 @@ public void Resume() } } - State = FiberState.Running; + _state = FiberState.Running; } public void Stop() @@ -307,7 +334,7 @@ public void Reset() _waitingFor.Clear(); } - State = FiberState.Fresh; + _state = FiberState.Fresh; } public void Immunize() @@ -389,9 +416,7 @@ public DynamicValue ThrowFromNative(DynamicValue value) { lock (_callStack) lock (_evaluationStack) - { - var callStackCopy = _callStack.ToArray(); - + { var frame = _callStack.Peek(); if (frame is NativeStackFrame nsf) { @@ -400,7 +425,8 @@ public DynamicValue ThrowFromNative(DynamicValue value) } _evaluationStack.Push(value); - UnwindTryHandle(callStackCopy); + + UnwindTryHandle(_callStack.ToArray()); } return DynamicValue.Nil; @@ -438,12 +464,12 @@ internal void UnwindTryHandle(StackFrame[] callStackCopy) internal void RemoveFinishedAwaitees() { - _waitingFor.RemoveWhere(x => x.State == FiberState.Finished); + _waitingFor.RemoveWhere(x => x._state == FiberState.Finished); } private void EnterCrashState() { - State = FiberState.Crashed; + _state = FiberState.Crashed; } private void Invoke(Chunk chunk, DynamicValue[] args) diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/DivisionByZeroException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/DivisionByZeroException.cs index 6055633..c902e6a 100644 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/DivisionByZeroException.cs +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/DivisionByZeroException.cs @@ -1,6 +1,6 @@ namespace EVIL.Ceres.ExecutionEngine { - public class DivisionByZeroException : VirtualMachineException + public class DivisionByZeroException : RecoverableVirtualMachineException { internal DivisionByZeroException() : base("Attempt to divide by zero.") diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/RecoverableVirtualMachineException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/RecoverableVirtualMachineException.cs new file mode 100644 index 0000000..ac28846 --- /dev/null +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/RecoverableVirtualMachineException.cs @@ -0,0 +1,17 @@ +using System; + +namespace EVIL.Ceres.ExecutionEngine +{ + public class RecoverableVirtualMachineException : VirtualMachineException + { + internal RecoverableVirtualMachineException(string message, Exception innerException) + : base(message, innerException) + { + } + + internal RecoverableVirtualMachineException(string message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/MalformedNumberException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/MalformedNumberException.cs index 81d9d59..c05304e 100644 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/MalformedNumberException.cs +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/MalformedNumberException.cs @@ -2,7 +2,7 @@ namespace EVIL.Ceres.ExecutionEngine.TypeSystem { - public class MalformedNumberException : VirtualMachineException + public class MalformedNumberException : RecoverableVirtualMachineException { internal MalformedNumberException(string message, Exception innerException) : base(message, innerException) diff --git a/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/UnsupportedDynamicValueOperationException.cs b/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/UnsupportedDynamicValueOperationException.cs index 0184651..aa3ed61 100644 --- a/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/UnsupportedDynamicValueOperationException.cs +++ b/VirtualMachine/EVIL.Ceres/ExecutionEngine/TypeSystem/UnsupportedDynamicValueOperationException.cs @@ -1,6 +1,6 @@ namespace EVIL.Ceres.ExecutionEngine.TypeSystem { - public sealed class UnsupportedDynamicValueOperationException : VirtualMachineException + public sealed class UnsupportedDynamicValueOperationException : RecoverableVirtualMachineException { internal UnsupportedDynamicValueOperationException(string message) : base(message) diff --git a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/AssertModule.cs b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/AssertModule.cs index 2ff1ab6..58c05f1 100644 --- a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/AssertModule.cs +++ b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/AssertModule.cs @@ -14,15 +14,33 @@ public class AssertModule : RuntimeModule public AssertModule() { - var c = new Compiler(); - var rootChunk = c.Compile("fn __invk(t, expr) -> t.is_true(expr);"); + var compiler = new Compiler(); + var rootChunk = compiler.Compile("fn __invk(t, expr) -> t.is_true(expr);"); + this["throws"] = compiler.Compile( + "fn throws(func) {\n" + + " if (func !is Function) throw error('This function can only test Functions.');\n" + + "\n" + + " rw val threw = nil;" + + "\n" + + " try {\n" + + " func();\n" + + " threw = false;\n" + + " } catch { threw = true; }\n" + + "\n" + + " throw error { " + + " __should_have_thrown: true," + + " __threw: threw" + + " };\n" + + "}" + )["throws"]!; + MetaTable ??= new Table { { InvokeMetaKey, rootChunk.SubChunks[0] } }; } - + [RuntimeModuleFunction("is_true")] private static DynamicValue IsTrue(Fiber _, params DynamicValue[] args) { diff --git a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/Test.cs b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/Test.cs index ab8c17f..de116ae 100644 --- a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/Test.cs +++ b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/Test.cs @@ -14,9 +14,9 @@ public class Test { private readonly Chunk _chunk; private bool _processingCrash; - + public Fiber Fiber { get; } - + public bool CallsAnyAsserts { get; private set; } public bool Successful { get; private set; } = true; @@ -28,45 +28,43 @@ public Test(CeresVM vm, Chunk chunk) _chunk = chunk; Fiber = vm.Scheduler.CreateFiber( true, - TestCrashHandler, + TestCrashHandler, (Dictionary)vm.MainFiber.ClosureContexts ); - + Fiber.SetOnNativeFunctionInvoke(OnNativeFunctionInvoke); } public async Task Run() { Fiber.Schedule(_chunk); - - while (Fiber.State != FiberState.Crashed + + while (Fiber.State != FiberState.Crashed && Fiber.State != FiberState.Finished) { - await Task.Delay(1); + await Task.Delay(TimeSpan.FromMicroseconds(1)); } if (Fiber.State == FiberState.Crashed) { _processingCrash = true; } - + } + + public async Task WaitForCleanup() + { while (_processingCrash) { - await Task.Delay(1); + await Task.Delay(TimeSpan.FromMicroseconds(1)); } - + Fiber.DeImmunize(); } - private async void TestCrashHandler(Fiber fiber, Exception exception) + private void TestCrashHandler(Fiber fiber, Exception exception) { - while (!_processingCrash) - { - await Task.Delay(1); - } - Successful = false; - + if (exception is UserUnhandledExceptionException uuee) { if (uuee.EvilExceptionObject.Type == DynamicValueType.Error) @@ -88,7 +86,7 @@ private async void TestCrashHandler(Fiber fiber, Exception exception) else if (e["__should_not_have_thrown"] != DynamicValue.Nil) { CallsAnyAsserts = true; - + if (e["__threw"] == false) { Successful = true; @@ -112,12 +110,12 @@ private async void TestCrashHandler(Fiber fiber, Exception exception) _processingCrash = false; } - + private void OnNativeFunctionInvoke(Fiber fiber, NativeFunction nativeFunction) { var nativeFunctionType = nativeFunction.Method.DeclaringType; if (nativeFunctionType == null) return; - + if (nativeFunctionType.IsAssignableTo(typeof(AssertModule))) { CallsAnyAsserts = true; diff --git a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/TestRunner.cs b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/TestRunner.cs index 930596f..4d024cf 100644 --- a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/TestRunner.cs +++ b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/TestRunner.cs @@ -277,7 +277,8 @@ private async Task Execute(TestSet testSet) await test.Run(); } Stopwatch.Stop(); - + await test.WaitForCleanup(); + var stamp = $"{Stopwatch.Elapsed.TotalMicroseconds}μs"; if (test.Successful) diff --git a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/tests/023_try.vil b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/tests/023_try.vil index 5c8c362..6c61f0a 100644 --- a/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/tests/023_try.vil +++ b/VirtualMachine/Tests/EVIL.Ceres.LanguageTests/tests/023_try.vil @@ -177,7 +177,7 @@ fn try_error_implicit_message_and_userdata { } #[test] -fn try_no_local() { +fn try_no_local { rw val caught = false; try { @@ -189,4 +189,4 @@ fn try_no_local() { } catch { caught = true; } assert(caught); -} +} \ No newline at end of file