fix: call frame handling, logical short circuiting
This commit is contained in:
parent
5c133e5fa2
commit
236acc7d60
8 changed files with 301 additions and 159 deletions
165
src/Story.zig
165
src/Story.zig
|
|
@ -100,10 +100,13 @@ pub const Opcode = enum(u8) {
|
|||
};
|
||||
|
||||
pub const CallFrame = struct {
|
||||
/// Pointer to the knot that initiated the call.
|
||||
callee: *Object.Knot,
|
||||
caller_top: usize,
|
||||
/// Instruction pointer.
|
||||
ip: usize,
|
||||
sp: usize,
|
||||
bp: usize,
|
||||
/// Output stream base.
|
||||
output_base: usize,
|
||||
};
|
||||
|
||||
pub const Value = union(enum) {
|
||||
|
|
@ -377,11 +380,11 @@ fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value {
|
|||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?Value {
|
||||
return vm.stack[frame.sp + offset];
|
||||
return vm.stack[frame.bp + offset + 1];
|
||||
}
|
||||
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: Value) void {
|
||||
vm.stack[frame.sp + offset] = value;
|
||||
vm.stack[frame.bp + offset + 1] = value;
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
|
|
@ -426,68 +429,58 @@ pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
|
|||
return knot;
|
||||
}
|
||||
|
||||
fn call(vm: *Story, knot: *Object.Knot) !void {
|
||||
if (vm.call_stack_top >= vm.call_stack.len)
|
||||
return error.CallStackOverflow;
|
||||
|
||||
const locals_count = knot.code.locals_count;
|
||||
const args_count = knot.code.args_count;
|
||||
const sp = vm.stack_top - args_count;
|
||||
const caller_top = if (vm.call_stack_top == 0)
|
||||
sp
|
||||
else
|
||||
sp - 1;
|
||||
|
||||
const frame_top = sp + args_count + locals_count;
|
||||
if (frame_top > vm.stack.len) return error.StackOverflow;
|
||||
for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil;
|
||||
|
||||
vm.stack_top = frame_top;
|
||||
vm.call_stack[vm.call_stack_top] = .{
|
||||
.callee = knot,
|
||||
.ip = 0,
|
||||
.sp = sp,
|
||||
.caller_top = caller_top,
|
||||
};
|
||||
vm.call_stack_top += 1;
|
||||
fn callValue(vm: *Story, value: Value, args_count: u8) !void {
|
||||
switch (value) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => return call(vm, @ptrCast(object), args_count),
|
||||
else => return error.InvalidCallTarget,
|
||||
},
|
||||
else => return error.InvalidCallTarget,
|
||||
}
|
||||
}
|
||||
|
||||
// Diverts are essentially tail calls.
|
||||
fn divert(vm: *Story, knot: *Object.Knot) !void {
|
||||
const args_count = knot.code.args_count;
|
||||
const locals_count = knot.code.locals_count;
|
||||
|
||||
fn call(vm: *Story, knot: *Object.Knot, args_count: u8) !void {
|
||||
assert(knot.code.args_count == args_count);
|
||||
if (vm.call_stack_top >= vm.call_stack.len)
|
||||
return error.CallStackOverflow;
|
||||
if (!vm.can_advance)
|
||||
vm.can_advance = true;
|
||||
|
||||
if (vm.call_stack_top == 0)
|
||||
return vm.call(knot);
|
||||
|
||||
const args_start = vm.stack_top - args_count;
|
||||
const current_frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||
const sp = current_frame.sp;
|
||||
const caller_top = current_frame.caller_top;
|
||||
|
||||
if (args_count > 0) {
|
||||
std.mem.copyForwards(
|
||||
Value,
|
||||
vm.stack[sp .. sp + args_count],
|
||||
vm.stack[args_start .. args_start + args_count],
|
||||
);
|
||||
}
|
||||
|
||||
const frame_top = sp + args_count + locals_count;
|
||||
if (frame_top > vm.stack.len) return error.StackOverflow;
|
||||
|
||||
for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil;
|
||||
vm.stack_top = frame_top;
|
||||
|
||||
current_frame.* = .{
|
||||
vm.call_stack[vm.call_stack_top] = .{
|
||||
.callee = knot,
|
||||
.ip = 0,
|
||||
.sp = sp,
|
||||
.caller_top = caller_top,
|
||||
.bp = vm.stack_top - args_count - 1,
|
||||
.output_base = vm.output_buffer.items.len,
|
||||
};
|
||||
vm.call_stack_top += 1;
|
||||
vm.stack_top += knot.code.locals_count;
|
||||
}
|
||||
|
||||
fn divertValue(vm: *Story, value: Value, args_count: u8) !void {
|
||||
switch (value) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => return divert(vm, @ptrCast(object), args_count),
|
||||
else => return error.InvalidCallTarget,
|
||||
},
|
||||
else => return error.InvalidCallTarget,
|
||||
}
|
||||
}
|
||||
|
||||
// Diverts are essentially tail calls.
|
||||
fn divert(vm: *Story, knot: *Object.Knot, args_count: u8) !void {
|
||||
assert(knot.code.args_count == args_count);
|
||||
if (vm.call_stack_top == 0)
|
||||
return vm.call(knot, args_count);
|
||||
|
||||
const frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||
frame.* = .{
|
||||
.callee = knot,
|
||||
.ip = 0,
|
||||
.bp = vm.stack_top - args_count - 1,
|
||||
.output_base = vm.output_buffer.items.len,
|
||||
};
|
||||
|
||||
vm.stack_top += knot.code.locals_count;
|
||||
}
|
||||
|
||||
fn readAddress(code: []const Story.Opcode, offset: usize) u16 {
|
||||
|
|
@ -518,16 +511,32 @@ fn step(vm: *Story) !StepSignal {
|
|||
.exit => return .exit,
|
||||
.done => return .done,
|
||||
.ret => {
|
||||
const return_value = vm.stack[vm.stack_top - 1];
|
||||
|
||||
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
|
||||
vm.call_stack_top -= 1;
|
||||
const completed_frame = vm.call_stack[vm.call_stack_top];
|
||||
|
||||
vm.stack_top = completed_frame.caller_top;
|
||||
const resolved_stream: ?Value = if (vm.output_buffer.items.len > frame.output_base) blk: {
|
||||
const frame_output = vm.output_buffer.items[frame.output_base..];
|
||||
defer vm.output_buffer.shrinkRetainingCapacity(frame.output_base);
|
||||
|
||||
const str_bytes = try resolveOutputStream(vm, arena, frame_output);
|
||||
const str_object = try Object.String.create(vm, .{ .bytes = str_bytes });
|
||||
break :blk .{ .object = &str_object.base };
|
||||
} else blk: {
|
||||
break :blk null;
|
||||
};
|
||||
const return_value = if (resolved_stream) |stream| blk: {
|
||||
if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) {
|
||||
try vm.output_buffer.append(gpa, .{ .value = stream });
|
||||
break :blk popStack(vm).?;
|
||||
}
|
||||
break :blk stream;
|
||||
} else if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) blk: {
|
||||
break :blk popStack(vm).?;
|
||||
} else .nil;
|
||||
|
||||
vm.stack_top = frame.bp;
|
||||
vm.stack[vm.stack_top] = return_value;
|
||||
vm.stack_top += 1;
|
||||
|
||||
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
|
||||
frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||
},
|
||||
.pop => {
|
||||
|
|
@ -629,17 +638,11 @@ fn step(vm: *Story) !StepSignal {
|
|||
}
|
||||
},
|
||||
.call => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const args_count: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |value| {
|
||||
switch (value) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => try call(vm, @ptrCast(object)),
|
||||
else => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
if (peekStack(vm, args_count)) |value| {
|
||||
try callValue(vm, value, args_count);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -647,17 +650,11 @@ fn step(vm: *Story) !StepSignal {
|
|||
frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||
},
|
||||
.divert => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const args_count: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |value| {
|
||||
switch (value) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => try divert(vm, @ptrCast(object)),
|
||||
else => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
if (peekStack(vm, args_count)) |value| {
|
||||
try divertValue(vm, value, args_count);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -831,6 +828,7 @@ pub fn advance(story: *Story) !?[]const u8 {
|
|||
}
|
||||
}
|
||||
story.can_advance = false;
|
||||
if (output_buffer.items.len == 0) return null;
|
||||
return try resolveOutputStream(story, arena, output_buffer.items[0..]);
|
||||
}
|
||||
|
||||
|
|
@ -906,7 +904,8 @@ pub fn fromSourceBytes(
|
|||
|
||||
try comp.setupStoryRuntime(gpa, &story);
|
||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||
try story.divert(knot);
|
||||
try story.pushStack(.{ .object = &knot.base });
|
||||
try story.divert(knot, 0);
|
||||
}
|
||||
return story;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue