无堆栈VM实现会出现哪些C集成问题?

时间:2009-04-30 09:44:07

标签: c binding vm-implementation stackless

无堆栈VM我指的是在堆上维护自己的堆栈而不是使用系统“C-stack”的实现。这有很多优点,如continuation和serializable状态,但在C-bindings方面也有一些缺点,特别是对于C-VM-C类型的回调(或VM-C-VM)。

问题是这些缺点到底是什么?任何人都可以举出真正问题的好例子吗?

2 个答案:

答案 0 :(得分:5)

听起来你已经熟悉了一些缺点和优点。

其他一些人: a)即使底层实现没有任何支持,也可以支持正确的尾调用优化 b)更容易构建语言级别“堆栈跟踪”之类的东西 c)如你所指出的那样,更容易添加适当的延续

我最近在C#中编写了一个简单的“Scheme”解释器,它最初使用的是.NET堆栈。然后我重新编写它以使用显式堆栈 - 所以可能以下内容将帮助您:

第一个版本使用了隐式.NET运行时堆栈......

最初,它只是一个类层次结构,不同的表单(Lambda,Let等)是以下接口的实现:

// A "form" is an expression that can be evaluted with
// respect to an environment
// e.g.
// "(* x 3)"
// "x"
// "3"
public interface IForm
{
    object Evaluate(IEnvironment environment);
}

IEnvironment看起来像你期望的那样:

/// <summary>
/// Fundamental interface for resolving "symbols" subject to scoping.
/// </summary>
public interface IEnvironment
{
    object Lookup(string name);
    IEnvironment Extend(string name, object value);
}

为了向我的Scheme解释器添加“builtins”,我最初有以下界面:

/// <summary>
/// A function is either a builtin function (i.e. implemented directly in CSharp)
/// or something that's been created by the Lambda form.
/// </summary>
public interface IFunction
{
    object Invoke(object[] args);
}

那时它使用了隐式.NET运行时堆栈。肯定有更少的代码,但是不可能添加正确的尾递归之类的东西,最重要的是,我的解释器在运行时错误的情况下能够提供“语言级”堆栈跟踪是很尴尬的。 p>

所以我把它重写为有一个显式的(堆分配的)堆栈。

我的“IFunction”界面必须更改为以下内容,以便我可以实现“map”和“apply”之类的东西,它们会回调到Scheme解释器中:

/// <summary>
/// A function that wishes to use the thread state to
/// evaluate its arguments. The function should either:
/// a) Push tasks on to threadState.Pending which, when evaluated, will
///   result in the result being placed on to threadState.Results
/// b) Push its result directly on to threadState.Results
/// </summary>
public interface IStackFunction
{
    void Evaluate(IThreadState threadState, object[] args);
}

并且IForm改为:

public interface IForm
{
    void Evaluate(IEnvironment environment, IThreadState s);
}

IThreadState如下:

/// <summary>
/// The state of the interpreter.
/// The implementation of a task which takes some arguments,
/// call them "x" and "y", and which returns an argument "z",
/// should follow the following protocol:
/// a) Call "PopResult" to get x and y
/// b) Either
///   i) push "z" directly onto IThreadState using PushResult OR
///   ii) push a "task" on to the stack which will result in "z" being
///       pushed on to the result stack.
/// 
/// Note that ii) is "recursive" in its definition - that is, a task
/// that is pushed on to the task stack may in turn push other tasks
/// on the task stack which, when evaluated, 
/// ... ultimately will end up pushing the result via PushResult.
/// </summary>
public interface IThreadState
{
    void PushTask(ITask task);
    object PopResult();
    void PushResult(object result);
}

ITask是:

public interface ITask
{
    void Execute(IThreadState s);
}

我的主要“事件”循环是:

ThreadState threadState = new ThreadState();
threadState.PushTask(null);
threadState.PushTask(new EvaluateForm(f, environment));
ITask next = null;

while ((next = threadState.PopTask()) != null)
    next.Execute(threadState);

return threadState.PopResult(); // Get what EvaluateForm evaluated to

EvaluateForm只是一个用特定环境调用IForm.Evaluate的任务。

就我个人而言,从实现的角度来看,我发现这个新版本更“好” - 容易获得堆栈跟踪,很容易实现完全延续(虽然......我没有这样做然而 - 需要使我的“堆栈”持久链接列表而不是使用C#Stack,并且ITask“返回”新的ThreadState而不是改变它以便我可以进行“调用 - 继续”任务)...等等。

基本上,您只是较少依赖底层语言实现。

我能找到的唯一缺点是表现......但就我而言,它只是一名翻译,所以我对表现并不在乎。

我还要向您介绍这篇非常好的文章,该文章介绍了使用KAI C ++编译器的作者之一将递归代码重写为带有堆栈的迭代代码的好处:Considering Recursion

答案 1 :(得分:1)

在与Steve Dekorte(Io编程语言的作者)和Konstantin Olenin的电子邮件对话之后,我发现了一个问题和一个(部分)解决方案。 想象一下从VM到C函数的调用,它调用VM方法。在VM执行回调的时间段内,VM状态的一部分位于VM外部:在C堆栈和寄存器中。如果您在此时保存VM状态,则可以保证在下次加载VM时无法正确恢复状态。

解决方案是将VM建模为消息接收参与者:VM可以向本机代码发送异步通知,本机代码可以向VM发送异步通知。也就是说,在单线程环境中,当VM获得控制权时,不会在其外部存储其他状态(除了与VM运行时无关的数据)。

这并不意味着您可以在任何情况下正确恢复VM状态,但至少,您可以在其上构建自己的可靠系统。