Skip to content

Commit 08eff15

Browse files
committed
Implement closures and LOAD_CLOSURE, LOAD_DEREF, MAKE_CLOSURE opcodes
1 parent c85199b commit 08eff15

File tree

6 files changed

+65
-32
lines changed

6 files changed

+65
-32
lines changed

builtin/builtin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ func builtin___build_class__(self py.Object, args py.Tuple, kwargs py.StringDict
278278
// fmt.Printf("Calling %v with %p and %p\n", fn.Name, fn.Globals, ns)
279279
// fmt.Printf("Code = %#v\n", fn.Code)
280280
locals := fn.LocalsForCall(py.Tuple{ns})
281-
cell, err := vm.Run(fn.Globals, locals, fn.Code) // FIXME PyFunction_GET_CLOSURE(fn))
281+
cell, err := vm.Run(fn.Globals, locals, fn.Code, fn.Closure)
282282

283283
// fmt.Printf("result = %#v err = %s\n", cell, err)
284284
// fmt.Printf("locals = %#v\n", locals)

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func main() {
6262
}
6363
code := obj.(*py.Code)
6464
module := py.NewModule("__main__", "", nil, nil)
65-
res, err := vm.Run(module.Globals, module.Globals, code)
65+
res, err := vm.Run(module.Globals, module.Globals, code, nil)
6666
if err != nil {
6767
log.Fatal(err)
6868
}

py/frame.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ type Frame struct {
1717
Globals StringDict // global symbol table
1818
Locals StringDict // local symbol table
1919
Stack []Object // Valuestack
20+
Closure Tuple // Tuple of Cells that this function is closed over
2021
// Next free slot in f_valuestack. Frame creation sets to f_valuestack.
2122
// Frame evaluation usually NULLs it, but a frame that yields sets it
2223
// to the current stack top.
2324
// Stacktop *Object
24-
Yielded bool // set if the function yielded, cleared otherwise
25-
Trace Object // Trace function
25+
Yielded bool // set if the function yielded, cleared otherwise
26+
// Trace Object // Trace function
2627

2728
// In a generator, we need to be able to swap between the exception
2829
// state inside the generator and the exception state of the calling
@@ -31,11 +32,11 @@ type Frame struct {
3132
// These three fields exist exactly for that, and are unused for
3233
// non-generator frames. See the save_exc_state and swap_exc_state
3334
// functions in ceval.c for details of their use.
34-
Exc_type Object
35-
Exc_value *Object
36-
Exc_traceback *Object
35+
// Exc_type Object
36+
// Exc_value *Object
37+
// Exc_traceback *Object
3738
// Borrowed reference to a generator, or NULL
38-
Gen Object
39+
// Gen Object
3940

4041
// FIXME Tstate *PyThreadState
4142
Lasti int32 // Last instruction if called
@@ -44,12 +45,12 @@ type Frame struct {
4445
// active (i.e. when f_trace is set). At other times we use
4546
// PyCode_Addr2Line to calculate the line from the current
4647
// bytecode index.
47-
Lineno int // Current line number
48-
Iblock int // index in f_blockstack
49-
Executing byte // whether the frame is still executing
48+
// Lineno int // Current line number
49+
// Iblock int // index in f_blockstack
50+
// Executing byte // whether the frame is still executing
5051
Blockstack []TryBlock // for try and loop blocks
5152
Block *TryBlock // pointer to current block or nil
52-
Localsplus []Object // locals+stack, dynamically sized
53+
// Localsplus []Object // locals+stack, dynamically sized
5354
}
5455

5556
var FrameType = NewType("frame", "Represents a stack frame")
@@ -60,11 +61,12 @@ func (o *Frame) Type() *Type {
6061
}
6162

6263
// Make a new frame for a code object
63-
func NewFrame(globals, locals StringDict, code *Code) *Frame {
64+
func NewFrame(globals, locals StringDict, code *Code, closure Tuple) *Frame {
6465
return &Frame{
6566
Globals: globals,
6667
Locals: locals,
6768
Code: code,
69+
Closure: closure,
6870
Builtins: Builtins.Globals,
6971
Stack: make([]Object, 0, code.Stacksize),
7072
}

py/function.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (f *Function) M__call__(args Tuple, kwargs StringDict) Object {
123123
} else {
124124
locals = f.LocalsForCall(args)
125125
}
126-
result, err := Run(f.Globals, locals, f.Code)
126+
result, err := Run(f.Globals, locals, f.Code, f.Closure)
127127
if err != nil {
128128
// Propagate the error
129129
panic(err)

py/py.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type Object interface {
1010
var (
1111
Ellipsis Object
1212
// See vm/eval.go - set to avoid circular import
13-
Run func(globals, locals StringDict, code *Code) (res Object, err error)
13+
Run func(globals, locals StringDict, code *Code, closure Tuple) (res Object, err error)
1414
RunFrame func(frame *Frame) (res Object, err error)
1515
)
1616

vm/eval.go

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,21 +1005,45 @@ func do_DELETE_FAST(vm *Vm, var_num int32) {
10051005
}
10061006
}
10071007

1008+
// Name of slot for LOAD_CLOSURE / LOAD_DEREF / etc
1009+
//
1010+
// Returns name of variable and bool, true for free var, false for
1011+
// cell var
1012+
func _var_name(vm *Vm, i int32) (string, bool) {
1013+
cellvars := vm.frame.Code.Cellvars
1014+
if int(i) < len(cellvars) {
1015+
return cellvars[i], false
1016+
}
1017+
return vm.frame.Code.Freevars[int(i)-len(cellvars)], true
1018+
}
1019+
10081020
// Pushes a reference to the cell contained in slot i of the cell and
10091021
// free variable storage. The name of the variable is co_cellvars[i]
10101022
// if i is less than the length of co_cellvars. Otherwise it is
10111023
// co_freevars[i - len(co_cellvars)].
10121024
func do_LOAD_CLOSURE(vm *Vm, i int32) {
10131025
defer vm.CheckException()
1014-
vm.NotImplemented("LOAD_CLOSURE", i)
1026+
varname, _ := _var_name(vm, i)
1027+
// FIXME this is making a new cell each time rather than
1028+
// returning a reference to an old one
1029+
vm.PUSH(py.NewCell(vm.frame.Locals[varname]))
10151030
}
10161031

10171032
// Loads the cell contained in slot i of the cell and free variable
10181033
// storage. Pushes a reference to the object the cell contains on the
10191034
// stack.
10201035
func do_LOAD_DEREF(vm *Vm, i int32) {
10211036
defer vm.CheckException()
1022-
vm.NotImplemented("LOAD_DEREF", i)
1037+
res := vm.frame.Closure[i].(*py.Cell).Get()
1038+
if res == nil {
1039+
varname, free := _var_name(vm, i)
1040+
if free {
1041+
vm.SetException(py.ExceptionNewf(py.UnboundLocalError, unboundFreeErrorMsg, varname))
1042+
} else {
1043+
vm.SetException(py.ExceptionNewf(py.UnboundLocalError, unboundLocalErrorMsg, varname))
1044+
}
1045+
}
1046+
vm.PUSH(res)
10231047
}
10241048

10251049
// Much like LOAD_DEREF but first checks the locals dictionary before
@@ -1111,24 +1135,18 @@ func do_CALL_FUNCTION(vm *Vm, argc int32) {
11111135
vm.Call(fn, args, kwargs)
11121136
}
11131137

1114-
// Pushes a new function object on the stack. TOS is the code
1115-
// associated with the function. The function object is defined to
1116-
// have argc default parameters, which are found below TOS.
1117-
//
1118-
// FIXME these docs are slightly wrong.
1119-
func do_MAKE_FUNCTION(vm *Vm, argc int32) {
1120-
defer vm.CheckException()
1138+
// Implementation for MAKE_FUNCTION and MAKE_CLOSURE
1139+
func _make_function(vm *Vm, argc int32, opcode byte) {
11211140
posdefaults := argc & 0xff
11221141
kwdefaults := (argc >> 8) & 0xff
11231142
num_annotations := (argc >> 16) & 0x7fff
11241143
qualname := vm.POP()
11251144
code := vm.POP()
11261145
function := py.NewFunction(code.(*py.Code), vm.frame.Globals, string(qualname.(py.String)))
11271146

1128-
// FIXME share code with MAKE_CLOSURE
1129-
// if opcode == MAKE_CLOSURE {
1130-
// function.Closure = vm.POP();
1131-
// }
1147+
if opcode == MAKE_CLOSURE {
1148+
function.Closure = vm.POP().(py.Tuple)
1149+
}
11321150

11331151
if num_annotations > 0 {
11341152
names := vm.POP().(py.Tuple) // names of args with annotations
@@ -1167,15 +1185,24 @@ func do_MAKE_FUNCTION(vm *Vm, argc int32) {
11671185
vm.PUSH(function)
11681186
}
11691187

1188+
// Pushes a new function object on the stack. TOS is the code
1189+
// associated with the function. The function object is defined to
1190+
// have argc default parameters, which are found below TOS.
1191+
//
1192+
// FIXME these docs are slightly wrong.
1193+
func do_MAKE_FUNCTION(vm *Vm, argc int32) {
1194+
defer vm.CheckException()
1195+
_make_function(vm, argc, MAKE_FUNCTION)
1196+
}
1197+
11701198
// Creates a new function object, sets its func_closure slot, and
11711199
// pushes it on the stack. TOS is the code associated with the
11721200
// function, TOS1 the tuple containing cells for the closure’s free
11731201
// variables. The function also has argc default parameters, which are
11741202
// found below the cells.
11751203
func do_MAKE_CLOSURE(vm *Vm, argc int32) {
11761204
defer vm.CheckException()
1177-
vm.NotImplemented("MAKE_CLOSURE", argc)
1178-
// see MAKE_FUNCTION
1205+
_make_function(vm, argc, MAKE_CLOSURE)
11791206
}
11801207

11811208
// Pushes a slice object on the stack. argc must be 2 or 3. If it is
@@ -1278,6 +1305,8 @@ func (vm *Vm) UnwindExceptHandler(frame *py.Frame, block *py.TryBlock) {
12781305
// FIXME figure out how we are going to signal exceptions!
12791306
//
12801307
// Returns an Object and an error. The error will be a py.ExceptionInfo
1308+
//
1309+
// This is the equivalent of PyEval_EvalFrame
12811310
func RunFrame(frame *py.Frame) (res py.Object, err error) {
12821311
vm := NewVm(frame)
12831312
// defer func() {
@@ -1412,8 +1441,10 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
14121441
// Any parameters are expected to have been decoded into locals
14131442
//
14141443
// Returns an Object and an error. The error will be a py.ExceptionInfo
1415-
func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error) {
1416-
frame := py.NewFrame(globals, locals, code)
1444+
//
1445+
// This is the equivalent of PyEval_EvalCode with closure support
1446+
func Run(globals, locals py.StringDict, code *py.Code, closure py.Tuple) (res py.Object, err error) {
1447+
frame := py.NewFrame(globals, locals, code, closure)
14171448

14181449
// If this is a generator then make a generator object from
14191450
// the frame and return that instead

0 commit comments

Comments
 (0)