Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ dotnet test --filter "FullyQualifiedName~TestName"
- **StackContext** hierarchy: Abstract execution context (stack frames) with implementations:
- `FunctionContext`: Interpreted bytecode execution
- `CilFunctionContext`: JIT-compiled native execution
- `CoroutineContext`, `CooperativeContext`: Concurrency support
- **Instruction Set** (`Instruction.cs`): Stack-based opcodes (load, store, call, jump, operators)
- **PValue** (`PValue.cs`): Universal value container `(object Value, PType Type)` with dynamic dispatch

Expand Down
3 changes: 1 addition & 2 deletions Prexonite/Commands/Lazy/AsThunkCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
namespace Prexonite.Commands.Lazy;

/// <summary>
/// Turns values in WHNF into thunks and leaves existing thunks alone. This helps
/// building functions that can be callled with both strict and lazy arguments.
/// Wraps values in already-evaluated thunks and leaves existing thunks alone.
/// </summary>
public class AsThunkCommand : PCommand, ICilCompilerAware
{
Expand Down
206 changes: 53 additions & 153 deletions Prexonite/Commands/Lazy/ThunkCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Diagnostics;
using Prexonite.Compiler.Cil;

namespace Prexonite.Commands.Lazy;
Expand Down Expand Up @@ -58,75 +57,27 @@ void ICilCompilerAware.ImplementInCil(CompilerState state, Instruction ins)
}
}

/// <summary>
/// Represents a value that is computed at most once, on first access.
/// </summary>
public class Thunk : IIndirectCall, IObject
{
struct BlackHole
{
readonly bool _isActive;
readonly int _threadId;
readonly ManualResetEvent _evaluationDone;

BlackHole(int threadId)
{
_isActive = true;
_threadId = threadId;
_evaluationDone = new(false);
}

public static BlackHole Active(int threadId)
{
return new(threadId);
}

public BlackHole Inactivate()
{
_evaluationDone?.Set();
return _inactive();
}

static BlackHole _inactive()
{
return new();
}

public bool Trap()
{
if (_isActive)
{
if (_threadId == Thread.CurrentThread.ManagedThreadId)
{
throw new PrexoniteException("Thunk is already being evaluated!");
}
else
{
_evaluationDone.WaitOne(Timeout.Infinite, true);
return true;
}
}
return false;
}
}

BlackHole _blackHole;

(PValue Expr, PValue[] Parameters)? impl;

PValue? _value;
Exception? _exception;
readonly object _initializationLock = new();
PValue? _expression;
PValue[]? _parameters;
readonly PValue? _value;
Lazy<PValue>? _evaluation;

#region Construction

Thunk(PValue expr, PValue[] parameters)
{
impl = (
expr ?? throw new ArgumentNullException(nameof(expr)),
parameters ?? throw new ArgumentNullException(nameof(parameters))
);
_expression = expr ?? throw new ArgumentNullException(nameof(expr));
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
}

Thunk(PValue value)
{
impl = null;
_value = value ?? throw new ArgumentNullException(nameof(value));
}

Expand All @@ -146,10 +97,34 @@ public static Thunk NewExpression(PValue expr, PValue[] parameters)

public PValue Force(StackContext sctx)
{
return ((IIndirectCall)this).IndirectCall(sctx);
if (sctx == null)
throw new ArgumentNullException(nameof(sctx));

if (_value is { } value)
return value;

var evaluation = Volatile.Read(ref _evaluation);
if (evaluation == null)
{
lock (_initializationLock)
{
evaluation = _evaluation;
if (evaluation == null)
{
evaluation = new(
() => _evaluate(sctx),
LazyThreadSafetyMode.ExecutionAndPublication
);
Volatile.Write(ref _evaluation, evaluation);
}
}
}

return evaluation.Value;
}

public bool IsEvaluated => _value != null;
public bool IsEvaluated =>
_value != null || Volatile.Read(ref _evaluation)?.IsValueCreated == true;

public bool TryDynamicCall(
StackContext sctx,
Expand All @@ -176,105 +151,30 @@ public bool TryDynamicCall(

#endregion

IEnumerable<bool> _cooperativeForce(StackContext sctx, Action<PValue> setReturnValue)
PValue _evaluate(StackContext sctx)
{
if (sctx == null)
throw new ArgumentNullException(nameof(sctx));
var expression = _expression;
var parameters = _parameters;
if (expression == null || parameters == null)
throw new InvalidOperationException("Thunk expression is not available.");

while (true)
try
{
//Check if evaluation resulted in exception
if (_exception != null)
throw _exception;

//Check if value is available
if (_value != null)
break;

//Prevent infinite loops
if (_blackHole.Trap())
continue; //If we have been trapped, check exception again

//Tag thunk as being evaluated
_blackHole = BlackHole.Active(Thread.CurrentThread.ManagedThreadId);

Debug.Indent();
//We need to save stack space here, so try to invoke via IStackAware
// Since most expressions are closures, this has a high success rate
if (impl is { Expr.Value: IStackAware stackAware, Parameters: { } parameters })
{
//Exception handler defined in creation of cooperative context
var exprCtx = stackAware.CreateStackContext(sctx, parameters);
sctx.ParentEngine.Stack.AddLast(exprCtx);
yield return true;
_value = exprCtx.ReturnValue;
}
else if (impl is { Expr: var expr, Parameters: var dynParameters })
{
try
{
_value = expr.IndirectCall(sctx, dynParameters.AsSpan());
}
catch (Exception ex)
{
_blackHole = _blackHole.Inactivate();
_value = PType.Null;
_exception = ex;
throw;
}
}
else
{
throw new PrexoniteException("Thunk must have an implementation or a a value.");
}
Debug.Unindent();

if (_value.Value is Thunk t)
{
//Assimilate nested thunk
_blackHole = t._blackHole;
impl = t.impl;
_value = t._value;
_exception = t._exception;
}
else
{
//Release expression
impl = null;
_blackHole = _blackHole.Inactivate();
break;
}
var result = expression.IndirectCall(sctx, parameters.AsSpan());
while (result.Value is Thunk nested)
result = nested.Force(sctx);
return result;
}
finally
{
// Lazy caches both values and exceptions, so the factory inputs are no longer needed.
_expression = null;
_parameters = null;
}

setReturnValue(_value);
}

[SuppressMessage("ReSharper", "AccessToModifiedClosure")]
PValue IIndirectCall.IndirectCall(StackContext sctx, params ReadOnlySpan<PValue> args)
{
CooperativeContext coopCtx = null!;
coopCtx = new(sctx, f => _cooperativeForce(coopCtx, f))
{
ExceptionHandler = ex =>
{
_blackHole = _blackHole.Inactivate();
_value = PType.Null;
_exception = ex;
return false;
},
};

if (sctx is FunctionContext fctx)
{
//Turn CLR call into Prexonite stack call
fctx._UseVirtualMachineStackInstead();
sctx.ParentEngine.Stack.AddLast(coopCtx);
return PType.Null;
}
else
{
//Traditional implementation using the managed stack
return sctx.ParentEngine.Process(coopCtx);
}
return Force(sctx);
}
}
102 changes: 0 additions & 102 deletions Prexonite/Continuation.cs

This file was deleted.

Loading
Loading