Repository: goplus/gop
Pull request #452 (2020-07-12 14:18:00)

Defer support
have some defer feature should to test，commit pr first.

codecov-commenter: # [Codecov](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=h1) Report
> Merging [#452](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=desc) into [master](https://codecov.io/gh/goplus/gop/commit/3469a002a8555513d8ada27c39e2820759654f11&el=desc) will **decrease** coverage by `0.10%`.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/goplus/gop/pull/452/graphs/tree.svg?width=650&height=150&src=pr&token=4qln9Wk3Pn)](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #452      +/-   ##
==========================================
- Coverage   88.69%   88.58%   -0.11%     
==========================================
  Files          37       37              
  Lines        9454     9474      +20     
==========================================
+ Hits         8385     8393       +8     
- Misses        791      801      +10     
- Partials      278      280       +2     
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cl/stmt.go](https://codecov.io/gh/goplus/gop/pull/452/diff?src=pr&el=tree#diff-Y2wvc3RtdC5nbw==) | `90.51% <47.36%> (-2.56%)` | :arrow_down: |
| [cl/compile.go](https://codecov.io/gh/goplus/gop/pull/452/diff?src=pr&el=tree#diff-Y2wvY29tcGlsZS5nbw==) | `75.36% <60.00%> (-0.31%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=footer). Last update [3469a00...0c6db11](https://codecov.io/gh/goplus/gop/pull/452?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

JessonChan: 1、defer 要满足后进先出特点，你的实现在block和return两个地方进行defer的编译这点无法保证。
2、defer也是普通语句，可以用到任何地方，也不只有block中。比如
```go
package main

import "fmt"

func main() {
	func() {
		for i := 0; i < 10; i++ {
			defer fmt.Println(i)
		}
	}()
}
```
go-wyvern: > 1、defer 要满足后进先出特点，你的实现在block和return两个地方进行defer的编译这点无法保证。
> 2、defer也是普通语句，可以用到任何地方，也不只有block中。比如
> 
> ```go
> package main
> 
> import "fmt"
> 
> func main() {
> 	func() {
> 		for i := 0; i < 10; i++ {
> 			defer fmt.Println(i)
> 		}
> 	}()
> }
> ```

thanks for your comments.  
I will carefully read the code in the compile part, which is not very familiar
xushiwei: You may can refer `defer` of qlang:
* https://github.com/xushiwei/qlang/blob/v1.5/exec/code.go#L287
* https://github.com/xushiwei/qlang/blob/v1.5/exec/code.go#L345
* https://github.com/xushiwei/qlang/blob/v1.5/exec/function.go#L85
* https://github.com/xushiwei/qlang/blob/v1.5/cl/function2.go#L56

However, there are some differences between golang's `defer` and qlang's. For example:

```go
f, err := os.Open(...)
if err != nil {
   return
}
defer f.Close()

use(f)
f = nil // NOTE!
```

In qlang, `f.Close()` will be executed when function exists. The value of variable `f` is got until that time. So the above code will crash.

In go, `f.Close()` will be executed when function exists, but all the values of variables are got before adding `f.Close()` to `defer` chain of the function.
xushiwei: And let's see another example (https://play.golang.org/p/BbjlrYxe126):

```go
package main

import (
	"fmt"
)

func main() {
	defer fmt.Println(fmt.Println("Hello, playground"))
	fmt.Println("start")
}
```

The output of go is:

```
Hello, playground
start
18 <nil>
```

And then you can know that `defer` chain is only a simple function calling chain.

go-wyvern: > You may can refer `defer` of qlang:
> 
> * https://github.com/xushiwei/qlang/blob/v1.5/exec/code.go#L287
> * https://github.com/xushiwei/qlang/blob/v1.5/exec/code.go#L345
> * https://github.com/xushiwei/qlang/blob/v1.5/exec/function.go#L85
> * https://github.com/xushiwei/qlang/blob/v1.5/cl/function2.go#L56
> 
> However, there are some differences between golang's `defer` and qlang's. For example:
> 
> ```go
> f, err := os.Open(...)
> if err != nil {
>    return
> }
> defer f.Close()
> 
> use(f)
> f = nil // NOTE!
> ```
> 
> In qlang, `f.Close()` will be executed when function exists. The value of variable `f` is got until that time. So the above code will crash.
> 
> In go, `f.Close()` will be executed when function exists, but all the values of variables are got before adding `f.Close()` to `defer` chain of the function.

thanks for your comments.

Your comments have inspired me a lot, I have made changes to my code, please review
xushiwei: code cover is still not enough.
go-wyvern: Okay, I will add some test cases later
go-wyvern: > code cover is still not enough.

I have added some unit tests, and now the code cover has successfully run

Patch:
diff --git a/cl/ctx_check.go b/cl/ctx_check.go
index 319460d1..c029f796 100644
--- a/cl/ctx_check.go
+++ b/cl/ctx_check.go
@@ -78,6 +78,8 @@ func isNoExecCtxStmt(ctx *blockCtx, stmt ast.Stmt) bool {
 		return true
 	case *ast.LabeledStmt:
 		return true
+	case *ast.DeferStmt:
+		return isNoExecCtxCallExpr(ctx, v.Call)
 	default:
 		log.Panicln("isNoExecCtxStmt failed: unknown -", reflect.TypeOf(v))
 	}
diff --git a/cl/stmt.go b/cl/stmt.go
index 7b242c3b..3015994e 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -71,6 +71,8 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 		compileBranchStmt(ctx, v)
 	case *ast.LabeledStmt:
 		compileLabeledStmt(ctx, v)
+	case *ast.DeferStmt:
+		compileDeferStmt(ctx, v)
 	case *ast.EmptyStmt:
 		// do nothing
 	default:
@@ -229,6 +231,95 @@ func compileLabeledStmt(ctx *blockCtx, v *ast.LabeledStmt) {
 	compileStmt(ctx, v.Stmt)
 }
 
+func compileDeferStmt(ctx *blockCtx, v *ast.DeferStmt) {
+	var instr exec.Reserved
+	out := ctx.out
+	start := ctx.NewLabel("")
+	end := ctx.NewLabel("")
+
+	var f func()
+	exprFun := compileExpr(ctx, v.Call.Fun)
+	fn := ctx.infer.Pop()
+	switch vfn := fn.(type) {
+	case *qlFunc:
+		ret := vfn.Results()
+		ctx.infer.Push(ret)
+
+		for _, arg := range v.Call.Args {
+			compileExpr(ctx, arg)()
+		}
+		instr = ctx.out.Reserve()
+		out.Label(start)
+		f = func() {
+			arity := checkFuncCall(vfn.Proto(), 0, v.Call, ctx)
+			fun := vfn.FuncInfo()
+			if fun.IsVariadic() {
+				ctx.out.CallFuncv(fun, len(v.Call.Args), arity)
+			} else {
+				ctx.out.CallFunc(fun, len(v.Call.Args))
+			}
+		}
+	case *goFunc:
+		ret := vfn.Results()
+		ctx.infer.Push(ret)
+
+		for _, arg := range v.Call.Args {
+			compileExpr(ctx, arg)()
+		}
+		instr = ctx.out.Reserve()
+		out.Label(start)
+		f = func() {
+			if vfn.isMethod != 0 {
+				compileExpr(ctx, v.Call.Fun.(*ast.SelectorExpr).X)()
+			}
+			nexpr := len(v.Call.Args) + vfn.isMethod
+			arity := checkFuncCall(vfn.Proto(), vfn.isMethod, v.Call, ctx)
+			switch vfn.kind {
+			case exec.SymbolFunc:
+				ctx.out.CallGoFunc(exec.GoFuncAddr(vfn.addr), nexpr)
+			case exec.SymbolFuncv:
+				ctx.out.CallGoFuncv(exec.GoFuncvAddr(vfn.addr), nexpr, arity)
+			}
+		}
+	case *goValue:
+		if vfn.t.Kind() != reflect.Func {
+			log.Panicln("compileCallExpr failed: call a non function.")
+		}
+		ret := newFuncResults(vfn.t)
+		ctx.infer.Push(ret)
+
+		for _, arg := range v.Call.Args {
+			compileExpr(ctx, arg)()
+		}
+		instr = ctx.out.Reserve()
+		out.Label(start)
+		f = func() {
+			exprFun()
+			arity, ellipsis := checkFuncCall(vfn.t, 0, v.Call, ctx), false
+			if arity == -1 {
+				arity, ellipsis = len(v.Call.Args), true
+			}
+			ctx.out.CallGoClosure(len(v.Call.Args), arity, ellipsis)
+		}
+	case *nonValue:
+		instr = ctx.out.Reserve()
+		out.Label(start)
+		// TODO compile args before compileCallExpr
+		switch nv := vfn.v.(type) {
+		case goInstr:
+			f = nv(ctx, v.Call)
+		case reflect.Type:
+			f = compileTypeCast(nv, ctx, v.Call)
+		}
+	}
+
+	f()
+	ctx.infer.Pop()
+
+	out.Label(end)
+	instr.Set(out, out.Defer(start, end))
+}
+
 func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 	var defaultBody []ast.Stmt
 	var ctxSw *blockCtx
diff --git a/exec.spec/interface.go b/exec.spec/interface.go
index ae4695bf..83293713 100644
--- a/exec.spec/interface.go
+++ b/exec.spec/interface.go
@@ -43,6 +43,11 @@ type Label interface {
 	Name() string
 }
 
+type Instr interface {
+	// The val of instr
+	Val() interface{}
+}
+
 // Reserved represents a reserved instruction position.
 type Reserved int
 
@@ -54,6 +59,10 @@ func (p Reserved) Push(b Builder, val interface{}) {
 	b.ReservedAsPush(p, val)
 }
 
+func (p Reserved) Set(b Builder, instr Instr) {
+	b.ReservedAsInstr(p, instr)
+}
+
 // BreakAsReturn
 const BreakAsReturn = -2
 
@@ -343,11 +352,17 @@ type Builder interface {
 	// ReservedAsPush sets Reserved as Push(v)
 	ReservedAsPush(r Reserved, v interface{})
 
+	// ReservedAsInstr set Reserved as instr
+	ReservedAsInstr(r Reserved, instr Instr)
+
 	// GetPackage returns the Go+ package that the Builder works for.
 	GetPackage() Package
 
 	// Resolve resolves all unresolved labels/functions/consts/etc.
 	Resolve() Code
+
+	// Defer instr
+	Defer(start, end Label) Instr
 }
 
 // Package represents a Go+ package.
diff --git a/exec/bytecode/code.go b/exec/bytecode/code.go
index 82a0e037..3a6e705b 100644
--- a/exec/bytecode/code.go
+++ b/exec/bytecode/code.go
@@ -114,6 +114,7 @@ const (
 	opGoBuiltin     = 41 // op(26)
 	opErrWrap       = 42 // idx(26)
 	opWrapIfErr     = 43 // reserved(2) offset(24)
+	opDeferOp       = 44 // reserved(2) offset(24)
 )
 
 const (
@@ -198,6 +199,7 @@ var instrInfos = []InstrInfo{
 	opGoBuiltin:     {"goBuiltin", "", "op", 26},                          // op(26)
 	opErrWrap:       {"errWrap", "", "idx", 26},                           // idx(26)
 	opWrapIfErr:     {"wrapIfErr", "", "offset", 26},                      // reserved(2) offset(24)
+	opDeferOp:       {"opDeferOp", "", "offset", 26},                      // reserved(2) offset(24)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/exec/bytecode/const.go b/exec/bytecode/const.go
index bd41d11e..eea9c5ae 100644
--- a/exec/bytecode/const.go
+++ b/exec/bytecode/const.go
@@ -19,6 +19,7 @@ package bytecode
 import (
 	"reflect"
 
+	"github.com/goplus/gop/exec.spec"
 	"github.com/qiniu/x/log"
 )
 
@@ -156,6 +157,11 @@ func (p *Builder) ReservedAsPush(r Reserved, val interface{}) {
 	p.code.data[r] = p.pushInstr(val)
 }
 
+// ReservedAsInstr sets Reserved as Instr
+func (p *Builder) ReservedAsInstr(r Reserved, instr exec.Instr) {
+	p.code.data[r] = instr.Val().(Instr)
+}
+
 // Pop instr
 func (p *Builder) Pop(n int) *Builder {
 	i := (opPop << bitsOpShift) | uint32(n)
diff --git a/exec/bytecode/context.go b/exec/bytecode/context.go
index d120f9a8..a714c2e6 100644
--- a/exec/bytecode/context.go
+++ b/exec/bytecode/context.go
@@ -101,9 +101,10 @@ type varScope struct {
 type Context struct {
 	Stack
 	varScope
-	code *Code
-	ip   int
-	base int
+	code   *Code
+	defers *theDefer
+	ip     int
+	base   int
 }
 
 func newSimpleContext(data []interface{}) *Context {
@@ -184,6 +185,8 @@ func (ctx *Context) Exec(ip, ipEnd int) {
 			}
 		case opBuiltinOp:
 			execBuiltinOp(i, ctx)
+		case opDeferOp:
+			execDeferOp(i, ctx)
 		case opCallFunc:
 			fun := ctx.code.funs[i&bitsOperand]
 			fun.exec(ctx, ctx.getScope(fun.nestDepth > 1))
@@ -222,6 +225,7 @@ func (ctx *Context) Exec(ip, ipEnd int) {
 		}
 	}
 finished:
+	execDefers(ctx)
 	if allowProfile && doProfile {
 		if lastInstr != 0 {
 			instrProfile(lastInstr, time.Since(start))
@@ -270,6 +274,7 @@ var _execTable = [...]func(i Instr, p *Context){
 	opGoBuiltin:     execGoBuiltin,
 	opErrWrap:       execErrWrap,
 	opWrapIfErr:     execWrapIfErr,
+	opDeferOp:       execDeferOp,
 }
 
 var execTable []func(i Instr, p *Context)
diff --git a/exec/bytecode/defer.go b/exec/bytecode/defer.go
new file mode 100644
index 00000000..d2e027a7
--- /dev/null
+++ b/exec/bytecode/defer.go
@@ -0,0 +1,78 @@
+/*
+ Copyright 2020 The GoPlus Authors (goplus.org)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+// Package bytecode implements a bytecode backend for the Go+ language.
+package bytecode
+
+import (
+	"github.com/goplus/gop/exec.spec"
+)
+
+type theDefer struct {
+	next *theDefer
+	Stack
+	start int
+	end   int
+}
+
+const (
+	bitsOpDefer        = bitsOp + 2
+	bitsOpDeferOperand = bitsOpDefer - 1
+)
+
+func execDefers(ctx *Context) {
+	d := ctx.defers
+	if d == nil {
+		return
+	}
+
+	ctx.defers = nil
+	for {
+		ctx.Stack = d.Stack
+		ctx.Exec(d.start, d.end)
+		d = d.next
+		if d == nil {
+			break
+		}
+	}
+}
+
+func execDeferOp(i Instr, p *Context) {
+	end := i & bitsOpDeferOperand << bitsOpDefer >> bitsOpDefer
+	def := &theDefer{
+		Stack: p.Stack,
+		next:  p.defers,
+		start: p.ip,
+	}
+
+	p.ip += int(end)
+	def.end = p.ip
+	p.defers = def
+}
+
+func (p *Builder) Defer(start, end *Label) exec.Instr {
+	return &iDefer{start: p.labels[start], end: p.labels[end]}
+}
+
+type iDefer struct {
+	start int
+	end   int
+}
+
+func (c *iDefer) Val() interface{} {
+	instr := opDeferOp<<bitsOpShift | uint32(c.end-c.start)&bitsOpDeferOperand
+	return Instr(instr)
+}
diff --git a/exec/bytecode/interface.go b/exec/bytecode/interface.go
index f6a9baf0..ba7f5542 100644
--- a/exec/bytecode/interface.go
+++ b/exec/bytecode/interface.go
@@ -127,6 +127,11 @@ func (p *iBuilder) Default() exec.Builder {
 	return p
 }
 
+// Default instr
+func (p *iBuilder) Defer(start, end exec.Label) exec.Instr {
+	return ((*Builder)(p)).Defer(start.(*Label), end.(*Label))
+}
+
 // WrapIfErr instr
 func (p *iBuilder) WrapIfErr(nret int, l exec.Label) exec.Builder {
 	((*Builder)(p)).WrapIfErr(nret, l.(*Label))
@@ -417,6 +422,11 @@ func (p *iBuilder) ReservedAsPush(r exec.Reserved, v interface{}) {
 	((*Builder)(p)).ReservedAsPush(r, v)
 }
 
+// ReservedAsInstr sets Reserved as Instr
+func (p *iBuilder) ReservedAsInstr(r exec.Reserved, instr exec.Instr) {
+	((*Builder)(p)).ReservedAsInstr(r, instr)
+}
+
 // GetPackage returns the Go+ package that the Builder works for.
 func (p *iBuilder) GetPackage() exec.Package {
 	return NewPackage(p.code)
diff --git a/exec/golang/builder.go b/exec/golang/builder.go
index eb91b534..e08ba492 100644
--- a/exec/golang/builder.go
+++ b/exec/golang/builder.go
@@ -322,6 +322,11 @@ func (p *Builder) ReservedAsPush(r exec.Reserved, v interface{}) {
 	p.reserveds[r].Expr = Const(p, v)
 }
 
+// ReservedAsInstr sets Reserved as Instr
+func (p *Builder) ReservedAsInstr(r exec.Reserved, v exec.Instr) {
+	log.Panicln("todo")
+}
+
 // Pop instr
 func (p *Builder) Pop(n int) *Builder {
 	log.Panicln("todo")
diff --git a/exec/golang/interface.go b/exec/golang/interface.go
index bbe2643b..38891c7d 100644
--- a/exec/golang/interface.go
+++ b/exec/golang/interface.go
@@ -166,6 +166,11 @@ func (p *iBuilder) ForPhrase(f exec.ForPhrase, key, val exec.Var, hasExecCtx ...
 	return p
 }
 
+// Defer instr
+func (p *iBuilder) Defer(start, end exec.Label) exec.Instr {
+	return ((*Builder)(p)).Defer(end.(*Label), end.(*Label))
+}
+
 func toVar(v exec.Var) *Var {
 	if v == nil {
 		return nil
@@ -433,6 +438,11 @@ func (p *iBuilder) ReservedAsPush(r exec.Reserved, v interface{}) {
 	((*Builder)(p)).ReservedAsPush(r, v)
 }
 
+// ReservedAsInstr sets Reserved as Instr
+func (p *iBuilder) ReservedAsInstr(r exec.Reserved, instr exec.Instr) {
+	((*Builder)(p)).ReservedAsInstr(r, instr)
+}
+
 // GetPackage returns the Go+ package that the Builder works for.
 func (p *iBuilder) GetPackage() exec.Package {
 	return defaultImpl
diff --git a/exec/golang/stmt.go b/exec/golang/stmt.go
index ba3bdf8b..539253ae 100644
--- a/exec/golang/stmt.go
+++ b/exec/golang/stmt.go
@@ -393,4 +393,8 @@ func (p *Builder) EndComprehension(c *Comprehension) *Builder {
 	return p
 }
 
+func (p *Builder) Defer(start, end *Label) exec.Instr {
+	panic("The method defer under the builder of golang is not yet supported")
+}
+
 // ----------------------------------------------------------------------------
diff --git a/tutorial/22-Defer/defer.gop b/tutorial/22-Defer/defer.gop
new file mode 100644
index 00000000..ee323868
--- /dev/null
+++ b/tutorial/22-Defer/defer.gop
@@ -0,0 +1,18 @@
+func test() {
+	defer func() {
+		println("Hello, test defer!")
+	}()
+	println("Hello, test!")
+}
+
+defer func() {
+	println("Hello, defer1!")
+}()
+defer func() {
+	println("Hello, defer2!")
+}()
+defer func() {
+	println("Hello, defer3!")
+}()
+println("Hello, world!")
+test()


Test patch:
diff --git a/cl/stmt_test.go b/cl/stmt_test.go
index 14257fc1..afdc9468 100644
--- a/cl/stmt_test.go
+++ b/cl/stmt_test.go
@@ -1154,3 +1154,154 @@ var testRangeMapWithBranchClauses = map[string]testData{
 func TestMapForPhraseWithBranch(t *testing.T) {
 	testScripts(t, "TestMapForPhraseWithBranch", testRangeMapWithBranchClauses)
 }
+
+// -----------------------------------------------------------------------------
+
+var testDeferClauses = map[string]testData{
+	"func_in_defer_func": {clause: `
+		defer println(println("hello world"))
+		println("test defer")
+		`, want: "hello world\ntest defer\n12 <nil>\n"},
+	"func_in_defer_for": {clause: `
+		for i:=0;i<10;i++ {
+			defer println(i)
+		}
+		println("test defer")
+		`, want: "test defer\n9\n8\n7\n6\n5\n4\n3\n2\n1\n0\n"},
+	"multi_defer": {clause: `
+	func test() {
+		defer println("Hello, test defer!")
+		println("Hello, test!")
+	}
+	
+	defer println("Hello, defer1!")
+	defer println("Hello, defer2!")
+	defer println("Hello, defer3!")
+	defer test()
+	println("Hello, world!")
+		`, want: "Hello, world!\nHello, test!\nHello, test defer!\nHello, defer3!\nHello, defer2!\nHello, defer1!\n"},
+	"multi_defer_args": {clause: `
+	func test(i int) {
+		defer println("Hello, test defer!")
+		println("Hello, test!",i)
+	}
+	
+	defer println("Hello, defer1!")
+	defer println("Hello, defer2!")
+	defer println("Hello, defer3!")
+	defer test(1)
+	println("Hello, world!")
+		`, want: "Hello, world!\nHello, test! 1\nHello, test defer!\nHello, defer3!\nHello, defer2!\nHello, defer1!\n"},
+	"multi_defer_goval": {clause: `
+	import "fmt"
+	import gostrings "strings"
+	func test(i int) {
+		defer println("Hello, test defer!")
+		println("Hello, test!",i)
+	}
+
+    defer println(gostrings.NewReplacer("?", "!").Replace("hello, world???"))
+	defer println("Hello, defer1!")
+	defer println("Hello, defer2!")
+	defer println("Hello, defer3!")
+	defer test(1)
+	println("Hello, world!")
+		`, want: "Hello, world!\nHello, test! 1\nHello, test defer!\nHello, defer3!\nHello, defer2!\nHello, defer1!\nhello, world!!!\n"},
+
+	"unnamed_func": {clause: `
+		f := func(i int) {
+			defer println(i)
+			println("hello")
+		}
+		defer f(1+1)
+		println("hello1")
+		`, want: "hello1\nhello\n2\n"},
+	"unnamed_func2": {clause: `
+		f2:= func(i int,err error) {
+			defer println(i)
+			println(err)
+			println("hello")
+		}
+		defer f2(println("hello world"))
+		println("hello1")
+		`, want: "hello world\nhello1\n<nil>\nhello\n12\n"},
+	"unnamed_func3": {clause: `
+		import (
+			"fmt"
+		)
+		
+		myprint := func(format string, a ...interface{}) {
+			format = "this is test print: " + format
+			fmt.Printf(format, a...)
+		}
+		
+		defer myprint("hello %s\n", "defer")
+		myprint("hello %s\n", "world")
+		`, want: "this is test print: hello world\nthis is test print: hello defer\n"},
+	"unnamed_func4": {clause: `
+	import "strings"
+
+	f2 := func(s string, s2 string) {
+		defer func() {
+			println(s)
+			println(s2)
+		}()
+		s = strings.Replace(s, "?", "!", -1)
+		s2 = strings.Replace(s2, "?", "!", -1)
+	}
+	s := "hello world???"
+	s2 := "hello world????"
+	defer f2(s, s2)
+	
+	println(s)
+	println(s2)
+		`, want: "hello world???\nhello world????\nhello world!!!\nhello world!!!!\n"},
+	"unnamed_func5": {clause: `
+	import (
+		"fmt"
+	)
+	
+	defer func(format string, a ...interface{}) {
+		format = format
+		fmt.Printf(format, a...)
+	}("hello %s\n", "defer")
+	
+	printf("hello %s\n", "world")
+		`, want: "hello world\nhello defer\n"},
+	"unnamed_func6": {clause: `
+	import (
+		"fmt"
+	)
+	
+	myprint := func() {
+		fmt.Printf("hello defer\n")
+	}
+	
+	defer myprint()
+	printf("hello %s\n", "world")
+		`, want: "hello world\nhello defer\n"},
+	"unnamed_func7": {clause: `
+	import (
+		"fmt"
+	)
+	
+	myprint := func() {
+		fmt.Printf("hello world\n")
+	}
+	
+	defer fmt.Println("hello defer")
+	myprint()
+		`, want: "hello world\nhello defer\n"},
+	"nonvalue": {clause: `
+	a := [1, 2, 3]
+	b := [4, 5, 6]
+	defer func() {
+		println(b)
+	}()
+	defer copy(b, a)
+		`, want: "[1 2 3]\n"},
+}
+
+func TestDeferStmt(t *testing.T) {
+	testScripts(t, "TestDeferStmt", testDeferClauses)
+}
diff --git a/exec/bytecode/defer_test.go b/exec/bytecode/defer_test.go
new file mode 100644
index 00000000..303e5511
--- /dev/null
+++ b/exec/bytecode/defer_test.go
@@ -0,0 +1,60 @@
+/*
+ Copyright 2020 The GoPlus Authors (goplus.org)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+package bytecode
+
+import (
+	"testing"
+)
+
+func TestDefer1(t *testing.T) {
+	b := newBuilder()
+	off := b.Reserve()
+	b.ReservedAsInstr(off, &iDefer{start: 1, end: 3})
+	code := b.Resolve()
+
+	ctx := NewContext(code)
+
+	if i := ctx.code.data[0] >> bitsOpShift; i != opDeferOp {
+		t.Fatal("opDeferOp != opDeferOp, ret =", i)
+	}
+}
+
+func TestDefer2(t *testing.T) {
+	b := newBuilder()
+	l1 := NewLabel("")
+	l2 := NewLabel("")
+	off := b.Reserve()
+	b.Label(l1)
+	b.Push(1)
+	b.Push(2)
+	b.Push(3)
+	b.Label(l2)
+	b.ReservedAsInstr(off, b.Defer(l1, l2))
+	code := b.Resolve()
+	ctx := NewContext(code)
+	ctx.Exec(0, code.Len())
+	var i = 3
+	for {
+		if ctx.Len() < 1 {
+			break
+		}
+		v := ctx.Pop()
+		if v != i {
+			t.Fatalf("v != %d, ret = %v", i, v)
+		}
+		i--
+	}
+}
diff --git a/exec/golang/builder_test.go b/exec/golang/builder_test.go
index 65badbe5..3dda5dde 100644
--- a/exec/golang/builder_test.go
+++ b/exec/golang/builder_test.go
@@ -18,6 +18,8 @@ package golang
 
 import (
 	"fmt"
+	"go/ast"
+	"reflect"
 	"testing"
 
 	"github.com/goplus/gop/cl"
@@ -124,3 +126,37 @@ func main() {
 }
 
 // -----------------------------------------------------------------------------
+
+func TestReserved(t *testing.T) {
+	code := NewBuilder("main", nil, nil)
+	off := code.Reserve()
+	code.ReservedAsPush(off, 123)
+	if !reflect.DeepEqual(code.reserveds[off].Expr.(*ast.BasicLit), IntConst(123)) {
+		t.Fatal("TestReserved failed: reserveds is not set to", 123)
+	}
+}
+
+func TestReserved2(t *testing.T) {
+	code := NewBuilder("main", nil, nil)
+	defer func() {
+		err := recover()
+		if !reflect.DeepEqual(err, "The method defer under the builder of golang is not yet supported") {
+			t.Fatal("TestReserved failed: Defer is not yet supported now")
+		}
+	}()
+	var start = NewLabel("")
+	var end = NewLabel("")
+	code.Interface().Defer(start, end)
+}
+
+func TestReserved3(t *testing.T) {
+	code := NewBuilder("main", nil, nil)
+	off := code.Reserve()
+	defer func() {
+		err := recover()
+		if !reflect.DeepEqual(err, "todo\n") {
+			t.Fatal("TestReserved failed: ReservedAsInstr is todo now")
+		}
+	}()
+	code.Interface().ReservedAsInstr(off, nil)
+}