Skip to content

Commit 810fccf

Browse files
committed
Fix VM to use stack frames and not to re-instantiate itself
1 parent a217e4f commit 810fccf

File tree

6 files changed

+107
-151
lines changed

6 files changed

+107
-151
lines changed

py/function.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import (
1515
"fmt"
1616
)
1717

18-
var (
19-
// Function pointer for vm call to avoid circular reference
20-
VmRun func(StringDict, StringDict, *Code) error
21-
)
22-
2318
// A python Function object
2419
type Function struct {
2520
Code *Code // A code object, the __code__ attribute
@@ -88,9 +83,9 @@ func NewFunction(code *Code, globals StringDict, qualname string) *Function {
8883
}
8984
}
9085

91-
// Call the function with the given arguments
92-
func (f *Function) Call(self Object, args Tuple) Object {
93-
fmt.Printf("call f %#v with %v and %v\n", f, self, args)
86+
// Setup locals for calling the function with the given arguments
87+
func (f *Function) LocalsForCall(args Tuple) StringDict {
88+
fmt.Printf("call f %#v with %v\n", f, args)
9489
if len(args) != int(f.Code.Argcount) {
9590
// FIXME don't know how to deal with default args
9691
panic("Wrong number of arguments")
@@ -102,18 +97,12 @@ func (f *Function) Call(self Object, args Tuple) Object {
10297
locals[f.Code.Varnames[i]] = args[i]
10398
}
10499
fmt.Printf("locals = %v\n", locals)
105-
// FIXME return?
106-
err := VmRun(f.Globals, locals, f.Code)
107-
if err != nil {
108-
fmt.Printf("Error: %s\n", err)
109-
}
110-
return None
100+
return locals
111101
}
112102

113103
// Call the function with the given arguments
114-
func (f *Function) CallWithKeywords(self Object, args Tuple, kwargs StringDict) Object {
115-
return None
104+
func (f *Function) LocalsForCallWithKeywords(args Tuple, kwargs StringDict) StringDict {
105+
locals := NewStringDict()
106+
fmt.Printf("FIXME LocalsForCallWithKeywords NOT IMPLEMENTED\n")
107+
return locals
116108
}
117-
118-
// Check it implements the interface
119-
var _ Callable = (*Function)(nil)

py/method.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,3 @@ func (m *Method) CallWithKeywords(self Object, args Tuple, kwargs StringDict) Ob
134134
}
135135
return m.methodWithKeywords(self, args, kwargs)
136136
}
137-
138-
// Check it implements the interface
139-
var _ Callable = (*Method)(nil)

py/py.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,3 @@ func (o *BigInt) Type() *Type {
122122
var _ Object = (*BigInt)(nil)
123123

124124
// Interfaces satisfied by a subset of Objects
125-
type Callable interface {
126-
// Call the method with the args
127-
Call(self Object, args Tuple) Object
128-
129-
// Call the method with the args and kwargs
130-
CallWithKeywords(self Object, args Tuple, kwargs StringDict) Object
131-
}

vm/eval.go

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ func do_MAP_ADD(vm *Vm, i int32) {
312312

313313
// Returns with TOS to the caller of the function.
314314
func do_RETURN_VALUE(vm *Vm, arg int32) {
315-
vm.exit = true
315+
vm.PopFrame()
316316
}
317317

318318
// Pops TOS and delegates to it as a subiterator from a generator.
@@ -400,7 +400,7 @@ func do_WITH_CLEANUP(vm *Vm, arg int32) {
400400
// co_names of the code object. The compiler tries to use STORE_FAST
401401
// or STORE_GLOBAL if possible.
402402
func do_STORE_NAME(vm *Vm, namei int32) {
403-
vm.locals[vm.co.Names[namei]] = vm.POP()
403+
vm.frame.Locals[vm.frame.Code.Names[namei]] = vm.POP()
404404
}
405405

406406
// Implements del name, where namei is the index into co_names
@@ -438,13 +438,13 @@ func do_DELETE_GLOBAL(vm *Vm, namei int32) {
438438

439439
// Pushes co_consts[consti] onto the stack.
440440
func do_LOAD_CONST(vm *Vm, consti int32) {
441-
vm.PUSH(vm.co.Consts[consti])
441+
vm.PUSH(vm.frame.Code.Consts[consti])
442442
// fmt.Printf("LOAD_CONST %v\n", vm.TOP())
443443
}
444444

445445
// Pushes the value associated with co_names[namei] onto the stack.
446446
func do_LOAD_NAME(vm *Vm, namei int32) {
447-
vm.PUSH(py.String(vm.co.Names[namei]))
447+
vm.PUSH(py.String(vm.frame.Code.Names[namei]))
448448
}
449449

450450
// Creates a tuple consuming count items from the stack, and pushes
@@ -539,7 +539,7 @@ func do_FOR_ITER(vm *Vm, delta int32) {
539539
// Loads the global named co_names[namei] onto the stack.
540540
func do_LOAD_GLOBAL(vm *Vm, namei int32) {
541541
// FIXME this is looking in local scope too - is that correct?
542-
vm.PUSH(vm.lookup(vm.co.Names[namei]))
542+
vm.PUSH(vm.frame.Lookup(vm.frame.Code.Names[namei]))
543543
}
544544

545545
// Pushes a block for a loop onto the block stack. The block spans
@@ -568,12 +568,12 @@ func do_STORE_MAP(vm *Vm, arg int32) {
568568

569569
// Pushes a reference to the local co_varnames[var_num] onto the stack.
570570
func do_LOAD_FAST(vm *Vm, var_num int32) {
571-
vm.PUSH(vm.locals[vm.co.Varnames[var_num]])
571+
vm.PUSH(vm.frame.Locals[vm.frame.Code.Varnames[var_num]])
572572
}
573573

574574
// Stores TOS into the local co_varnames[var_num].
575575
func do_STORE_FAST(vm *Vm, var_num int32) {
576-
vm.locals[vm.co.Varnames[var_num]] = vm.POP()
576+
vm.frame.Locals[vm.frame.Code.Varnames[var_num]] = vm.POP()
577577
}
578578

579579
// Deletes local co_varnames[var_num].
@@ -633,8 +633,8 @@ func do_RAISE_VARARGS(vm *Vm, argc int32) {
633633
// pushes the return value.
634634
func do_CALL_FUNCTION(vm *Vm, argc int32) {
635635
fmt.Printf("Stack: %v\n", vm.stack)
636-
fmt.Printf("Locals: %v\n", vm.locals)
637-
fmt.Printf("Globals: %v\n", vm.globals)
636+
fmt.Printf("Locals: %v\n", vm.frame.Locals)
637+
fmt.Printf("Globals: %v\n", vm.frame.Globals)
638638
nargs := int(argc & 0xFF)
639639
nkwargs := int((argc >> 8) & 0xFF)
640640
p, q := len(vm.stack)-2*nkwargs, len(vm.stack)
@@ -643,9 +643,9 @@ func do_CALL_FUNCTION(vm *Vm, argc int32) {
643643
args := py.Tuple(vm.stack[p:q])
644644
p, q = p-1, p
645645
fn := vm.stack[p]
646-
vm.stack[p] = vm.call(fn, args, kwargs)
647-
// Drop the args off the stack
646+
// Drop everything off the stack
648647
vm.stack = vm.stack[:q]
648+
vm.Call(fn, args, kwargs)
649649
}
650650

651651
// Pushes a new function object on the stack. TOS is the code
@@ -659,7 +659,7 @@ func do_MAKE_FUNCTION(vm *Vm, argc int32) {
659659
num_annotations := (argc >> 16) & 0x7fff
660660
qualname := vm.POP()
661661
code := vm.POP()
662-
function := py.NewFunction(code.(*py.Code), vm.globals, string(qualname.(py.String)))
662+
function := py.NewFunction(code.(*py.Code), vm.frame.Globals, string(qualname.(py.String)))
663663

664664
// FIXME share code with MAKE_CLOSURE
665665
// if opcode == MAKE_CLOSURE {
@@ -756,17 +756,80 @@ func (vm *Vm) NotImplemented(name string, arg int32) {
756756
fmt.Printf("%s %d NOT IMPLEMENTED\n", name, arg)
757757
}
758758

759-
// Poke the vm.Run into py
760-
func init() {
761-
py.VmRun = Run
759+
// Calls function fn with args and kwargs
760+
//
761+
// fn can be a string in which case it will be looked up or another callable type
762+
//
763+
// kwargs is a sequence of name, value pairs
764+
//
765+
// The result is put on the stack
766+
func (vm *Vm) Call(fnObj py.Object, args []py.Object, kwargs []py.Object) {
767+
fmt.Printf("Call %T %v with args = %v, kwargs = %v\n", fnObj, fnObj, args, kwargs)
768+
var kwargsd py.StringDict
769+
if len(kwargs) > 0 {
770+
// Convert kwargs into dictionary
771+
if len(kwargs)%2 != 0 {
772+
panic("Odd length kwargs")
773+
}
774+
kwargsd = py.NewStringDict()
775+
for i := 0; i < len(kwargs); i += 2 {
776+
kwargsd[string(kwargs[i].(py.String))] = kwargs[i+1]
777+
}
778+
}
779+
try_again:
780+
switch fn := fnObj.(type) {
781+
case py.String:
782+
fnObj = vm.frame.Lookup(string(fn))
783+
goto try_again
784+
case *py.Method:
785+
self := py.None // FIXME should be the module
786+
if kwargsd != nil {
787+
vm.PUSH(fn.CallWithKeywords(self, args, kwargsd))
788+
} else {
789+
vm.PUSH(fn.Call(self, args))
790+
}
791+
case *py.Function:
792+
var locals py.StringDict
793+
if kwargsd != nil {
794+
locals = fn.LocalsForCallWithKeywords(args, kwargsd)
795+
} else {
796+
locals = fn.LocalsForCall(args)
797+
}
798+
vm.PushFrame(vm.frame.Globals, locals, fn.Code)
799+
default:
800+
// FIXME should be TypeError
801+
panic(fmt.Sprintf("TypeError: '%s' object is not callable", fnObj.Type().Name))
802+
}
803+
}
804+
805+
// Make a new Frame with globals, locals and Code on the frames stack
806+
func (vm *Vm) PushFrame(globals, locals py.StringDict, code *py.Code) {
807+
frame := py.Frame{
808+
Globals: globals,
809+
Locals: locals,
810+
Code: code,
811+
Builtins: py.Builtins.Globals,
812+
}
813+
vm.frames = append(vm.frames, frame)
814+
vm.frame = &frame
815+
}
816+
817+
// Drop the current frame
818+
func (vm *Vm) PopFrame() {
819+
vm.frames = vm.frames[:len(vm.frames)-1]
820+
if len(vm.frames) > 0 {
821+
vm.frame = &vm.frames[len(vm.frames)-1]
822+
} else {
823+
vm.frame = nil
824+
}
762825
}
763826

764827
// Run the virtual machine on the code object in the module
765828
//
766829
// FIXME figure out how we are going to signal exceptions!
767830
//
768831
// Any parameters are expected to have been decoded into locals
769-
func Run(globals, locals py.StringDict, co *py.Code) (err error) {
832+
func Run(globals, locals py.StringDict, code *py.Code) (err error) {
770833
defer func() {
771834
if r := recover(); r != nil {
772835
switch x := r.(type) {
@@ -779,25 +842,21 @@ func Run(globals, locals py.StringDict, co *py.Code) (err error) {
779842
}
780843
}
781844
}()
782-
_vm := Vm{
783-
stack: make([]py.Object, 0, 16),
784-
globals: globals,
785-
locals: locals,
786-
co: co,
787-
}
788-
vm := &_vm
789-
ip := 0
845+
vm := NewVm()
846+
vm.PushFrame(globals, locals, code)
847+
790848
var opcode byte
791849
var arg int32
792-
code := co.Code
793-
for !vm.exit {
794-
opcode = code[ip]
795-
ip++
850+
for vm.frame != nil {
851+
frame := vm.frame
852+
opcodes := frame.Code.Code
853+
opcode = opcodes[frame.Lasti]
854+
frame.Lasti++
796855
if HAS_ARG(opcode) {
797-
arg = int32(code[ip])
798-
ip++
799-
arg += int32(code[ip] << 8)
800-
ip++
856+
arg = int32(opcodes[frame.Lasti])
857+
frame.Lasti++
858+
arg += int32(opcodes[frame.Lasti] << 8)
859+
frame.Lasti++
801860
if vm.extended {
802861
arg += vm.ext << 16
803862
}

vm/lookup.go

Lines changed: 0 additions & 78 deletions
This file was deleted.

vm/vm.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,20 @@ import (
99
type Vm struct {
1010
// Object stack
1111
stack []py.Object
12-
// Current code object we are interpreting
13-
co *py.Code
14-
// Current globals
15-
globals py.StringDict
16-
// Current locals
17-
locals py.StringDict
12+
// Frame stack
13+
frames []py.Frame
14+
// Current frame
15+
frame *py.Frame
1816
// Whether ext should be added to the next arg
1917
extended bool
2018
// 16 bit extension for argument for next opcode
2119
ext int32
22-
// Whether we should exit
23-
exit bool
2420
}
2521

2622
// Make a new VM
2723
func NewVm() *Vm {
28-
vm := new(Vm)
29-
vm.stack = make([]py.Object, 0, 1024)
30-
vm.locals = py.NewStringDict()
31-
return vm
24+
return &Vm{
25+
stack: make([]py.Object, 0, 16),
26+
frames: make([]py.Frame, 0, 16),
27+
}
3228
}

0 commit comments

Comments
 (0)