2009-04-30 35 views
7

Bằng máy ảo không có ngăn xếp, tôi có nghĩa là triển khai thực hiện duy trì ngăn xếp của chính nó trên heap thay vì sử dụng hệ thống "C-stack". Điều này có rất nhiều ưu điểm như tiếp tục và trạng thái tuần tự hóa, nhưng cũng có một số nhược điểm khi nói đến các ràng buộc C, đặc biệt là các kiểu gọi lại C-VM-C (hoặc VM-C-VM).Vấn đề C-tích hợp nào nảy sinh với việc triển khai VM không có ngăn xếp?

Câu hỏi chính xác là những nhược điểm này là gì? Bất cứ ai có thể đưa ra một ví dụ tốt về một vấn đề thực sự?

Trả lời

5

Có vẻ như bạn đã quen thuộc với một số nhược điểm và lợi thế.

Một số người khác: a) Làm cho nó có thể để hỗ trợ thích hợp tối ưu hóa cuộc gọi đuôi ngay cả khi thực hiện cơ bản không có bất kỳ sự hỗ trợ cho nó b) dễ dàng hơn để xây dựng những thứ như một trình độ ngoại ngữ "stack trace" c) dễ dàng hơn để thêm các tiếp tục thích hợp, như bạn đã chỉ ra

Gần đây tôi đã viết một trình thông dịch "Đề án" đơn giản trong C#, ban đầu đã sử dụng chồng .NET. Sau đó tôi lại viết nó sử dụng một ngăn xếp rõ ràng - vì vậy có lẽ sau đây sẽ giúp bạn:

Phiên bản đầu tiên sử dụng ngăn xếp .NET runtime ngầm ...

Ban đầu, nó chỉ là một hệ thống phân cấp lớp, với các hình thức khác nhau (Lambda, Lết, vv) được triển khai của các giao diện sau:

// 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 trông như bạn mong muốn:

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

Để thêm " nội dung dựng sẵn "cho trình thông dịch Scheme của tôi, ban đầu tôi có giao diện sau:

/// <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); 
} 

Đó là khi sử dụng ngăn xếp .NET runtime ngầm định. Chắc chắn có ít mã hơn, nhưng không thể thêm những thứ như đệ quy đuôi thích hợp, và quan trọng nhất, nó là vụng về thông dịch viên của tôi để có thể cung cấp dấu vết ngăn xếp "ngôn ngữ" trong trường hợp lỗi thời gian chạy.

Vì vậy, tôi viết lại nó để có ngăn xếp (phân bổ đống) rõ ràng.

của tôi "IFunction" giao diện đã phải thay đổi để những điều sau đây, để tôi có thể thực hiện những điều như "bản đồ" và "áp dụng", mà gọi trở lại vào phiên dịch 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); 
} 

Và IForm đổi thành :

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

đâu IThreadState được như sau:

/// <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); 
} 

Và ITask là:

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

Và "sự kiện" chính của tôi vòng lặp là:

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 chỉ là một nhiệm vụ mà các cuộc gọi IForm.Evaluate với một môi trường cụ thể.

Cá nhân, tôi thấy phiên bản mới này "đẹp hơn" để làm việc với quan điểm thực hiện - dễ dàng có được dấu vết ngăn xếp, dễ dàng thực hiện tiếp tục đầy đủ (mặc dù ... tôi chưa làm điều này như được nêu ra - cần phải làm cho "ngăn xếp" liên tục của tôi-danh sách liên kết chứ không phải bằng cách sử dụng C# ngăn xếp, và ITask "trả về" mới ThreadState hơn là đột biến nó để tôi có thể có một "gọi tiếp tục" nhiệm vụ) ... vv v.v.

Về cơ bản, bạn chỉ ít phụ thuộc vào việc triển khai ngôn ngữ cơ bản.

Về nhược điểm duy nhất tôi có thể tìm thấy là hiệu suất ... Nhưng trong trường hợp của tôi, nó chỉ là một thông dịch viên vì vậy tôi không quan tâm nhiều về hiệu suất anyway.

Tôi cũng muốn chỉ cho bạn bài viết rất đẹp này về lợi ích của tái viết code đệ quy như mã lặp đi lặp lại với một chồng, bởi một trong những tác giả của KAI C++: Considering Recursion

+0

Thực ra, câu hỏi là về những nhược điểm chỉ xem xét tích hợp mã gốc. Nhưng cảm ơn câu chuyện. –

1

Sau e- mail cuộc hội thoại với Steve Dekorte (tác giả của ngôn ngữ lập trình Io) và Konstantin Olenin, tôi đã tìm thấy một vấn đề và một phần (một phần) giải pháp cho nó. Hãy tưởng tượng cuộc gọi từ máy ảo đến hàm C, gọi lại phương thức VM. Trong khoảng thời gian khi VM thực thi cuộc gọi lại, phần của trạng thái VM nằm bên ngoài máy ảo: trong ngăn xếp C và các thanh ghi. Nếu bạn sẽ lưu trạng thái VM tại thời điểm đó, nó được đảm bảo rằng bạn không thể khôi phục lại trạng thái chính xác vào lần sau khi VM được nạp.

Giải pháp là mô hình hóa VM làm diễn viên nhận tin nhắn: VM có thể gửi thông báo không đồng bộ tới mã gốc và mã gốc có thể gửi thông báo không đồng bộ tới máy ảo. Tức là, trong môi trường luồng đơn, khi VM giành quyền kiểm soát, không có trạng thái bổ sung nào được lưu trữ bên ngoài nó (ngoại trừ dữ liệu không liên quan đến thời gian chạy VM).

Điều này không có nghĩa là bạn có thể khôi phục chính xác trạng thái VM trong mọi trường hợp, nhưng ít nhất, bạn có thể xây dựng hệ thống đáng tin cậy của riêng mình trên đầu trang.

Các vấn đề liên quan