Repository: goplus/gop
Pull request #524 (2020-08-04 04:59:08)

goroutine

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

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

```diff
@@            Coverage Diff             @@
##           master     #524      +/-   ##
==========================================
- Coverage   89.42%   89.02%   -0.41%     
==========================================
  Files          40       40              
  Lines        9828     9885      +57     
==========================================
+ Hits         8789     8800      +11     
- Misses        759      805      +46     
  Partials      280      280              
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/524?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [exec/bytecode/code.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb2RlLmdv) | `100.00% <ø> (ø)` | |
| [exec/bytecode/context.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb250ZXh0Lmdv) | `94.00% <ø> (ø)` | |
| [exec/bytecode/interface.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9pbnRlcmZhY2UuZ28=) | `98.13% <0.00%> (-0.93%)` | :arrow_down: |
| [exec/golang/builder.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvYnVpbGRlci5nbw==) | `91.08% <ø> (ø)` | |
| [exec/golang/interface.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvaW50ZXJmYWNlLmdv) | `92.01% <0.00%> (-1.32%)` | :arrow_down: |
| [exec/bytecode/defer\_go.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9kZWZlcl9nby5nbw==) | `34.54% <34.54%> (ø)` | |
| [exec/golang/stmt.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvc3RtdC5nbw==) | `94.61% <50.00%> (-1.11%)` | :arrow_down: |
| [exec/golang/expr.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvZXhwci5nbw==) | `92.57% <63.63%> (-1.06%)` | :arrow_down: |
| [cl/goinstr.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-Y2wvZ29pbnN0ci5nbw==) | `81.21% <85.10%> (-0.10%)` | :arrow_down: |
| [cl/expr.go](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree#diff-Y2wvZXhwci5nbw==) | `84.29% <90.47%> (+0.06%)` | :arrow_up: |
| ... and [4 more](https://codecov.io/gh/goplus/gop/pull/524/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/524?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/524?src=pr&el=footer). Last update [4267977...2fe66c5](https://codecov.io/gh/goplus/gop/pull/524?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cl/ctx_check.go b/cl/ctx_check.go
index 6e7120dd..0b8b9976 100644
--- a/cl/ctx_check.go
+++ b/cl/ctx_check.go
@@ -80,6 +80,8 @@ func isNoExecCtxStmt(ctx *blockCtx, stmt ast.Stmt) bool {
 		return true
 	case *ast.DeferStmt:
 		return isNoExecCtxCallExpr(ctx, v.Call)
+	case *ast.GoStmt:
+		return isNoExecCtxCallExpr(ctx, v.Call)
 	default:
 		log.Panicln("isNoExecCtxStmt failed: unknown -", reflect.TypeOf(v))
 	}
diff --git a/cl/expr.go b/cl/expr.go
index 6589d12d..6f1e1413 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -60,7 +60,7 @@ func compileExpr(ctx *blockCtx, expr ast.Expr) func() {
 	case *ast.BasicLit:
 		return compileBasicLit(ctx, v)
 	case *ast.CallExpr:
-		return compileCallExpr(ctx, v, false)
+		return compileCallExpr(ctx, v, 0)
 	case *ast.BinaryExpr:
 		return compileBinaryExpr(ctx, v)
 	case *ast.UnaryExpr:
@@ -616,7 +616,7 @@ var binaryOps = [...]exec.Operator{
 	token.LOR:     exec.OpLOr,
 }
 
-func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
+func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
 	var exprFun func()
 	switch f := v.Fun.(type) {
 	case *ast.SelectorExpr:
@@ -624,14 +624,14 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 	default:
 		exprFun = compileExpr(ctx, f)
 	}
-	return compileCallExprCall(ctx, exprFun, v, isDefer)
+	return compileCallExprCall(ctx, exprFun, v, ct)
 }
 
-func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, isDefer bool) func() {
+func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, ct callType) func() {
 	fn := ctx.infer.Pop()
 	switch vfn := fn.(type) {
 	case *qlFunc:
-		if !isDefer {
+		if ct == callExpr {
 			ret := vfn.Results()
 			ctx.infer.Push(ret)
 		}
@@ -642,13 +642,13 @@ func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, isDefer
 			arity := checkFuncCall(vfn.Proto(), 0, v, ctx)
 			fun := vfn.FuncInfo()
 			if fun.IsVariadic() {
-				builder(ctx, isDefer).CallFuncv(fun, len(v.Args), arity)
+				builder(ctx, ct).CallFuncv(fun, len(v.Args), arity)
 			} else {
-				builder(ctx, isDefer).CallFunc(fun, len(v.Args))
+				builder(ctx, ct).CallFunc(fun, len(v.Args))
 			}
 		}
 	case *goFunc:
-		if !isDefer {
+		if ct == callExpr {
 			ret := vfn.Results()
 			ctx.infer.Push(ret)
 		}
@@ -663,16 +663,16 @@ func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, isDefer
 			arity := checkFuncCall(vfn.Proto(), vfn.isMethod, v, ctx)
 			switch vfn.kind {
 			case exec.SymbolFunc:
-				builder(ctx, isDefer).CallGoFunc(exec.GoFuncAddr(vfn.addr), nexpr)
+				builder(ctx, ct).CallGoFunc(exec.GoFuncAddr(vfn.addr), nexpr)
 			case exec.SymbolFuncv:
-				builder(ctx, isDefer).CallGoFuncv(exec.GoFuncvAddr(vfn.addr), nexpr, arity)
+				builder(ctx, ct).CallGoFuncv(exec.GoFuncvAddr(vfn.addr), nexpr, arity)
 			}
 		}
 	case *goValue:
 		if vfn.t.Kind() != reflect.Func {
 			log.Panicln("compileCallExpr failed: call a non function.")
 		}
-		if !isDefer {
+		if ct == callExpr {
 			ret := newFuncResults(vfn.t)
 			ctx.infer.Push(ret)
 		}
@@ -685,15 +685,15 @@ func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, isDefer
 			if arity == -1 {
 				arity, ellipsis = len(v.Args), true
 			}
-			builder(ctx, isDefer).CallGoClosure(len(v.Args), arity, ellipsis)
+			builder(ctx, ct).CallGoClosure(len(v.Args), arity, ellipsis)
 		}
 	case *nonValue:
 		switch nv := vfn.v.(type) {
 		case goInstr:
-			return nv(ctx, v, isDefer)
+			return nv(ctx, v, ct)
 		case reflect.Type:
-			if isDefer {
-				log.Panicln("defer requires function call, not conversion")
+			if ct != callExpr {
+				log.Panicf("%s requires function call, not conversion\n", gCallTypes[ct])
 			}
 			return compileTypeCast(nv, ctx, v)
 		}
@@ -702,9 +702,12 @@ func compileCallExprCall(ctx *blockCtx, exprFun func(), v *ast.CallExpr, isDefer
 	return nil
 }
 
-func builder(ctx *blockCtx, isDefer bool) (out exec.Builder) {
-	if out = ctx.out; isDefer {
-		out.Defer()
+func builder(ctx *blockCtx, ct callType) (out exec.Builder) {
+	switch out = ctx.out; ct {
+	case callByDefer:
+		return out.Defer()
+	case callByGo:
+		return out.Go()
 	}
 	return
 }
@@ -1047,7 +1050,7 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, allowAutoCall bool)
 			call := &ast.CallExpr{Fun: &copy}
 			v.X = call
 			v.Sel = nil
-			return compileCallExprCall(ctx, nil, call, false)
+			return compileCallExprCall(ctx, nil, call, 0)
 		}
 		return func() {
 			log.Panicln("compileSelectorExpr: todo")
diff --git a/cl/goinstr.go b/cl/goinstr.go
index 1e1ddaa9..55db8bd5 100644
--- a/cl/goinstr.go
+++ b/cl/goinstr.go
@@ -26,7 +26,7 @@ import (
 
 // -----------------------------------------------------------------------------
 
-type goInstr = func(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func()
+type goInstr = func(ctx *blockCtx, v *ast.CallExpr, ct callType) func()
 
 type goInstrInfo struct {
 	instr goInstr
@@ -65,7 +65,10 @@ func checkSliceType(args []interface{}, i int) (typ reflect.Type, ok bool) {
 */
 
 // func append(slice []Type, elems ...Type) []Type
-func igoAppend(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
+func igoAppend(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
+	}
 	if len(v.Args) < 2 {
 		log.Panicln("append: argument count not enough")
 	}
@@ -74,9 +77,6 @@ func igoAppend(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 	if sliceTy.Kind() != reflect.Slice {
 		log.Panicln("append: first argument not a slice")
 	}
-	if isDefer {
-		ctx.infer.PopN(1)
-	}
 	return func() {
 		sliceExpr()
 		n1 := len(v.Args) - 1
@@ -91,17 +91,17 @@ func igoAppend(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 			}
 			checkType(sliceTy, args[0], ctx.out)
 			ctx.infer.PopN(1)
-			builder(ctx, isDefer).Append(elem, -1)
+			ctx.out.Append(elem, -1)
 		} else {
 			checkElementType(elem, args, 0, n1, 1, ctx.out)
 			ctx.infer.PopN(n1)
-			builder(ctx, isDefer).Append(elem, n1+1)
+			ctx.out.Append(elem, n1+1)
 		}
 	}
 }
 
 // func copy(dst, src []Type) int
-func igoCopy(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
+func igoCopy(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
 	if len(v.Args) < 2 {
 		log.Panicln("not enough arguments in call to copy")
 	}
@@ -113,7 +113,7 @@ func igoCopy(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 	if dstTy.Kind() != reflect.Slice {
 		log.Panicln("arguments to copy must be slices; have ", dstTy.Kind())
 	}
-	if !isDefer {
+	if ct == callExpr {
 		ctx.infer.Ret(1, &goValue{exec.TyInt})
 	}
 	return func() {
@@ -133,43 +133,41 @@ func igoCopy(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 			log.Panicln("second argument to copy should be slice or string; have", srcTy.Kind())
 		}
 		ctx.infer.Pop()
-		builder(ctx, isDefer).GoBuiltin(dstTy, exec.GobCopy)
+		builder(ctx, ct).GoBuiltin(dstTy, exec.GobCopy)
 	}
 }
 
 // func delete(m map[Type]Type1, key Type)
-func igoDelete(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if len(v.Args) < 2 {
-		log.Panicln("missing second (key) argument to delete")
-	}
-	if len(v.Args) > 2 {
+func igoDelete(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	narg := len(v.Args)
+	if narg != 2 {
+		if narg < 2 {
+			log.Panicln("missing second (key) argument to delete")
+		}
 		log.Panicln("too many arguments to delete")
 	}
 	mapExpr := compileExpr(ctx, v.Args[0])
-	mapType := ctx.infer.Get(-1).(iValue).Type()
+	mapType := ctx.infer.Pop().(iValue).Type()
 	if mapType.Kind() != reflect.Map {
-		log.Panicln(" first argument to delete must be map; have", mapType.Kind())
+		log.Panicln("first argument to delete must be map; have", mapType)
 	}
+	ctx.infer.Push(&nonValue{})
 	return func() {
 		mapExpr()
-		n1 := len(v.Args) - 1
-		for i := 1; i <= n1; i++ {
-			compileExpr(ctx, v.Args[i])()
-		}
-		args := ctx.infer.GetArgs(n1)
-		elem := mapType.Key()
-		checkType(elem, args[0], ctx.out)
-		ctx.infer.PopN(n1)
-		builder(ctx, isDefer).GoBuiltin(mapType, exec.GobDelete)
+		compileExpr(ctx, v.Args[1])()
+		key := ctx.infer.Pop().(iValue)
+		checkType(mapType.Key(), key, ctx.out)
+		builder(ctx, ct).GoBuiltin(mapType, exec.GobDelete)
 	}
 }
 
 // func len/cap(v Type) int
 func igoLenOrCap(ctx *blockCtx, v *ast.CallExpr, op exec.GoBuiltin) func() {
-	if len(v.Args) < 1 {
-		logPanic(ctx, v, `missing argument to %v: %v`, op, ctx.code(v))
-	}
-	if len(v.Args) > 1 {
+	narg := len(v.Args)
+	if narg != 1 {
+		if narg < 1 {
+			logPanic(ctx, v, `missing argument to %v: %v`, op, ctx.code(v))
+		}
 		logPanic(ctx, v, `too many arguments to %v: %v`, op, ctx.code(v))
 	}
 	expr := compileExpr(ctx, v.Args[0])
@@ -213,25 +211,25 @@ func igoLenOrCap(ctx *blockCtx, v *ast.CallExpr, op exec.GoBuiltin) func() {
 }
 
 // func len(v Type) int
-func igoLen(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoLen(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	return igoLenOrCap(ctx, v, exec.GobLen)
 }
 
 // func cap(v Type) int
-func igoCap(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoCap(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	return igoLenOrCap(ctx, v, exec.GobCap)
 }
 
 // func make(t Type, size ...IntegerType) Type
-func igoMake(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoMake(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	if len(v.Args) < 1 {
 		logPanic(ctx, v, `missing argument to make: %v`, ctx.code(v))
@@ -259,9 +257,9 @@ func igoMake(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 }
 
 // func new(Type) *Type
-func igoNew(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoNew(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	if n := len(v.Args); n != 1 {
 		if n == 0 {
@@ -278,36 +276,36 @@ func igoNew(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
 }
 
 // func complex(r, i FloatType) ComplexType
-func igoComplex(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoComplex(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	panic("todo")
 }
 
 // func real(c ComplexType) FloatType
-func igoReal(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoReal(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	panic("todo")
 }
 
 // func imag(c ComplexType) FloatType
-func igoImag(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
-	if isDefer {
-		log.Panicln("defer discards result of", ctx.code(v))
+func igoImag(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
+	if ct != callExpr {
+		log.Panicf("%s discards result of %s\n", gCallTypes[ct], ctx.code(v))
 	}
 	panic("todo")
 }
 
 // func close(c chan<- Type)
-func igoClose(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
+func igoClose(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
 	panic("todo")
 }
 
 // func recover() interface{}
-func igoRecover(ctx *blockCtx, v *ast.CallExpr, isDefer bool) func() {
+func igoRecover(ctx *blockCtx, v *ast.CallExpr, ct callType) func() {
 	panic("todo")
 }
 
diff --git a/cl/stmt.go b/cl/stmt.go
index f73afa16..34bfbea5 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -79,6 +79,8 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 		compileLabeledStmt(ctx, v)
 	case *ast.DeferStmt:
 		compileDeferStmt(ctx, v)
+	case *ast.GoStmt:
+		compileGoStmt(ctx, v)
 	case *ast.EmptyStmt:
 		// do nothing
 	default:
@@ -239,8 +241,26 @@ func compileLabeledStmt(ctx *blockCtx, v *ast.LabeledStmt) {
 	compileStmt(ctx, v.Stmt)
 }
 
+type callType int
+
+const (
+	callExpr callType = iota
+	callByDefer
+	callByGo
+)
+
+var gCallTypes = []string{
+	"",
+	"defer",
+	"go",
+}
+
+func compileGoStmt(ctx *blockCtx, v *ast.GoStmt) {
+	compileCallExpr(ctx, v.Call, callByGo)()
+}
+
 func compileDeferStmt(ctx *blockCtx, v *ast.DeferStmt) {
-	compileCallExpr(ctx, v.Call, true)()
+	compileCallExpr(ctx, v.Call, callByDefer)()
 }
 
 func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
diff --git a/exec.spec/interface.go b/exec.spec/interface.go
index 6c5b4d1e..b0870580 100644
--- a/exec.spec/interface.go
+++ b/exec.spec/interface.go
@@ -250,15 +250,15 @@ type Builder interface {
 	// CallGoFuncv instr
 	CallGoFuncv(fun GoFuncvAddr, nexpr, arity int) Builder
 
-	// Append instr
-	Append(typ reflect.Type, arity int) Builder
-
 	// GoBuiltin instr
 	GoBuiltin(typ reflect.Type, op GoBuiltin) Builder
 
 	// Defer instr
 	Defer() Builder
 
+	// Go instr
+	Go() Builder
+
 	// DefineFunc instr
 	DefineFunc(fun FuncInfo) Builder
 
@@ -310,6 +310,9 @@ type Builder interface {
 	// Make instr
 	Make(typ reflect.Type, arity int) Builder
 
+	// Append instr
+	Append(typ reflect.Type, arity int) Builder
+
 	// MapIndex instr
 	MapIndex() Builder
 
diff --git a/exec/bytecode/code.go b/exec/bytecode/code.go
index 335599cb..17f9b88f 100644
--- a/exec/bytecode/code.go
+++ b/exec/bytecode/code.go
@@ -122,7 +122,8 @@ const (
 	opGoBuiltin     = 41 // op(26)
 	opErrWrap       = 42 // idx(26)
 	opWrapIfErr     = 43 // reserved(2) offset(24)
-	opDefer         = 44
+	opDefer         = 44 // reserved(26)
+	opGo            = 45 // arity(26)
 )
 
 const (
@@ -208,7 +209,8 @@ var instrInfos = []InstrInfo{
 	opGoBuiltin:     {"goBuiltin", "", "op", 26},                          // op(26)
 	opErrWrap:       {"errWrap", "", "idx", 26},                           // idx(26)
 	opWrapIfErr:     {"wrapIfErr", "", "offset", 26},                      // reserved(2) offset(24)
-	opDefer:         {"defer", "", "", 0},
+	opDefer:         {"defer", "", "", 0},                                 // reserved(26)
+	opGo:            {"go", "", "arity", 26},                              // arity(26)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/exec/bytecode/context.go b/exec/bytecode/context.go
index fad405fd..e19fa3a4 100644
--- a/exec/bytecode/context.go
+++ b/exec/bytecode/context.go
@@ -57,6 +57,20 @@ func NewContext(in exec.Code) *Context {
 	return p
 }
 
+// Go starts a new goroutine to run.
+func (ctx *Context) Go(arity int, f func(goctx *Context)) {
+	goctx := &Context{
+		code: ctx.code,
+	}
+	goctx.Init()
+	base := len(ctx.data) - arity
+	parent := ctx.varScope
+	goctx.parent = &parent
+	goctx.data = append(goctx.data, ctx.data[base:]...)
+	ctx.data = ctx.data[:base]
+	go f(goctx)
+}
+
 // CloneSetVarScope clone already set varScope to new context
 func (ctx *Context) CloneSetVarScope(new *Context) {
 	if !ctx.vars.IsValid() {
@@ -224,6 +238,7 @@ var _execTable = [...]func(i Instr, p *Context){
 	opErrWrap:       execErrWrap,
 	opWrapIfErr:     execWrapIfErr,
 	opDefer:         execDefer,
+	opGo:            execGo,
 }
 
 var execTable []func(i Instr, p *Context)
diff --git a/exec/bytecode/defer.go b/exec/bytecode/defer.go
deleted file mode 100644
index 73664884..00000000
--- a/exec/bytecode/defer.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- 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
-
-// -----------------------------------------------------------------------------
-
-type theDefer struct {
-	next  *theDefer
-	state varScope // block state
-	n     int      // stack len
-	i     Instr
-}
-
-func (ctx *Context) execDefers() {
-	d := ctx.defers
-	ctx.defers = nil
-	for d != nil {
-		ctx.data = ctx.data[:d.n]
-		ctx.varScope = d.state
-		execTable[d.i>>bitsOpShift](d.i, ctx)
-		d = d.next
-	}
-}
-
-func execDefer(i Instr, ctx *Context) {
-	i = ctx.code.data[ctx.ip]
-	ctx.ip++
-	ctx.defers = &theDefer{
-		next:  ctx.defers,
-		state: ctx.varScope,
-		n:     len(ctx.data),
-		i:     i,
-	}
-}
-
-// Defer instr
-func (p *Builder) Defer() *Builder {
-	p.code.data = append(p.code.data, opDefer<<bitsOpShift)
-	return p
-}
-
-// -----------------------------------------------------------------------------
diff --git a/exec/bytecode/defer_go.go b/exec/bytecode/defer_go.go
new file mode 100644
index 00000000..d8caf9f5
--- /dev/null
+++ b/exec/bytecode/defer_go.go
@@ -0,0 +1,134 @@
+/*
+ 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 (
+	"reflect"
+
+	"github.com/goplus/gop/exec.spec"
+)
+
+// -----------------------------------------------------------------------------
+
+type theDefer struct {
+	next  *theDefer
+	state varScope // block state
+	n     int      // stack len
+	i     Instr
+}
+
+func (ctx *Context) execDefers() {
+	d := ctx.defers
+	ctx.defers = nil
+	for d != nil {
+		ctx.data = ctx.data[:d.n]
+		ctx.varScope = d.state
+		execTable[d.i>>bitsOpShift](d.i, ctx)
+		d = d.next
+	}
+}
+
+func execDefer(i Instr, ctx *Context) {
+	i = ctx.code.data[ctx.ip]
+	ctx.ip++
+	ctx.defers = &theDefer{
+		next:  ctx.defers,
+		state: ctx.varScope,
+		n:     len(ctx.data),
+		i:     i,
+	}
+}
+
+// Defer instr
+func (p *Builder) Defer() *Builder {
+	p.code.data = append(p.code.data, opDefer<<bitsOpShift)
+	return p
+}
+
+// -----------------------------------------------------------------------------
+
+func execGo(i Instr, ctx *Context) {
+	arity := int(i & bitsOperand)
+	i = ctx.code.data[ctx.ip]
+	ctx.ip++
+	ctx.Go(arity, func(goctx *Context) {
+		execTable[i>>bitsOpShift](i, goctx)
+	})
+}
+
+type goBuilder struct {
+	*iBuilder
+}
+
+func (p goBuilder) builder(arity int) *iBuilder {
+	p.code.data = append(p.code.data, (opGo<<bitsOpShift)|Instr(arity))
+	return p.iBuilder
+}
+
+// CallClosure instr
+func (p goBuilder) CallClosure(nexpr, arity int, ellipsis bool) exec.Builder {
+	return p.builder(arity).CallClosure(nexpr, arity, ellipsis)
+}
+
+// CallGoClosure instr
+func (p goBuilder) CallGoClosure(nexpr, arity int, ellipsis bool) exec.Builder {
+	return p.builder(arity).CallGoClosure(nexpr, arity, ellipsis)
+}
+
+// CallFunc instr
+func (p goBuilder) CallFunc(fun exec.FuncInfo, nexpr int) exec.Builder {
+	return p.builder(fun.NumIn()).CallFunc(fun, nexpr)
+}
+
+// CallFuncv instr
+func (p goBuilder) CallFuncv(fun exec.FuncInfo, nexpr, arity int) exec.Builder {
+	return p.builder(arity).CallFuncv(fun, nexpr, arity)
+}
+
+// CallGoFunc instr
+func (p goBuilder) CallGoFunc(fun GoFuncAddr, nexpr int) exec.Builder {
+	arity := NewPackage(p.code).GetGoFuncType(fun).NumIn()
+	return p.builder(arity).CallGoFunc(fun, nexpr)
+}
+
+// CallGoFuncv instr
+func (p goBuilder) CallGoFuncv(fun GoFuncvAddr, nexpr, arity int) exec.Builder {
+	return p.builder(arity).CallGoFuncv(fun, nexpr, arity)
+}
+
+// GoBuiltin instr
+func (p goBuilder) GoBuiltin(typ reflect.Type, op exec.GoBuiltin) exec.Builder {
+	arity := goBuiltinArity(op)
+	return p.builder(arity).GoBuiltin(typ, op)
+}
+
+func goBuiltinArity(op exec.GoBuiltin) int {
+	switch op {
+	case exec.GobCopy, exec.GobDelete:
+		return 2
+	case exec.GobClose:
+		return 1
+	}
+	panic("no reach here")
+}
+
+// Go instr
+func (p *Builder) Go() exec.Builder {
+	return goBuilder{(*iBuilder)(p)}
+}
+
+// -----------------------------------------------------------------------------
diff --git a/exec/bytecode/defer_test.go b/exec/bytecode/defer_go_test.go
similarity index 100%
rename from exec/bytecode/defer_test.go
rename to exec/bytecode/defer_go_test.go
diff --git a/exec/bytecode/interface.go b/exec/bytecode/interface.go
index 3f46387e..f0acf5fb 100644
--- a/exec/bytecode/interface.go
+++ b/exec/bytecode/interface.go
@@ -127,12 +127,17 @@ func (p *iBuilder) Default() exec.Builder {
 	return p
 }
 
-// Default instr
+// Defer instr
 func (p *iBuilder) Defer() exec.Builder {
 	((*Builder)(p)).Defer()
 	return p
 }
 
+// Go instr
+func (p *iBuilder) Go() exec.Builder {
+	return ((*Builder)(p)).Go()
+}
+
 // WrapIfErr instr
 func (p *iBuilder) WrapIfErr(nret int, l exec.Label) exec.Builder {
 	((*Builder)(p)).WrapIfErr(nret, l.(*Label))
diff --git a/exec/bytecode/profile.go b/exec/bytecode/profile.go
index ca301d47..b63fee1b 100644
--- a/exec/bytecode/profile.go
+++ b/exec/bytecode/profile.go
@@ -3,12 +3,14 @@ package bytecode
 import (
 	"fmt"
 	"sort"
+	"sync"
 	"time"
 )
 
 // -----------------------------------------------------------------------------
 
 var (
+	mutexProf    sync.Mutex
 	doProfile    bool
 	gOpCalls     [64]uint64
 	gOpDurations [64]time.Duration
@@ -24,6 +26,8 @@ var (
 )
 
 func instrProfile(i Instr, dur time.Duration) {
+	mutexProf.Lock()
+	defer mutexProf.Unlock()
 	op := i >> bitsOpShift
 	gOpCalls[op]++
 	gOpDurations[op] += dur
diff --git a/exec/golang/builder.go b/exec/golang/builder.go
index 5434485b..1fb379d8 100644
--- a/exec/golang/builder.go
+++ b/exec/golang/builder.go
@@ -79,6 +79,14 @@ func (p *Code) String() string {
 
 // -----------------------------------------------------------------------------
 
+type callType int
+
+const (
+	callExpr callType = iota
+	callByDefer
+	callByGo
+)
+
 // Builder is a class that generates go code.
 type Builder struct {
 	lhs, rhs    exec.Stack
@@ -93,10 +101,10 @@ type Builder struct {
 	cfun        *FuncInfo         // current function
 	cstmt       interface{}       // current statement
 	reserveds   []*printer.ReservedExpr
-	comprehens  func() // current comprehension
-	identBase   int    // auo-increasement ident index
-	*scopeCtx          // current block scope
-	inDefer     bool   // in defer statement currently
+	comprehens  func()   // current comprehension
+	identBase   int      // auo-increasement ident index
+	*scopeCtx            // current block scope
+	inDeferOrGo callType // in defer/go statement currently
 }
 
 // NewBuilder creates a new Code Builder instance.
diff --git a/exec/golang/expr.go b/exec/golang/expr.go
index 44e02d66..39ed22be 100644
--- a/exec/golang/expr.go
+++ b/exec/golang/expr.go
@@ -366,13 +366,19 @@ func (p *Builder) Call(narg int, ellipsis bool, args ...ast.Expr) *Builder {
 	if ellipsis {
 		expr.Ellipsis++
 	}
-	if p.inDefer {
-		p.inDefer = false
-		p.rhs.Push(&ast.DeferStmt{
-			Call: expr,
-		})
-	} else {
+	if ct := p.inDeferOrGo; ct == callExpr {
 		p.rhs.Push(expr)
+	} else {
+		p.inDeferOrGo = callExpr
+		if ct == callByDefer {
+			p.rhs.Push(&ast.DeferStmt{
+				Call: expr,
+			})
+		} else {
+			p.rhs.Push(&ast.GoStmt{
+				Call: expr,
+			})
+		}
 	}
 	return p
 }
diff --git a/exec/golang/interface.go b/exec/golang/interface.go
index 6ff6b984..293faf36 100644
--- a/exec/golang/interface.go
+++ b/exec/golang/interface.go
@@ -172,6 +172,12 @@ func (p *iBuilder) Defer() exec.Builder {
 	return p
 }
 
+// Go instr
+func (p *iBuilder) Go() exec.Builder {
+	((*Builder)(p)).Go()
+	return p
+}
+
 func toVar(v exec.Var) *Var {
 	if v == nil {
 		return nil
diff --git a/exec/golang/stmt.go b/exec/golang/stmt.go
index 55e34f43..e8918536 100644
--- a/exec/golang/stmt.go
+++ b/exec/golang/stmt.go
@@ -395,7 +395,13 @@ func (p *Builder) EndComprehension(c *Comprehension) *Builder {
 
 // Defer instr
 func (p *Builder) Defer() *Builder {
-	p.inDefer = true
+	p.inDeferOrGo = callByDefer
+	return p
+}
+
+// Go instr
+func (p *Builder) Go() *Builder {
+	p.inDeferOrGo = callByGo
 	return p
 }
 
diff --git a/tutorial/24-Goroutine/goroutine.gop b/tutorial/24-Goroutine/goroutine.gop
new file mode 100644
index 00000000..1bab570a
--- /dev/null
+++ b/tutorial/24-Goroutine/goroutine.gop
@@ -0,0 +1,7 @@
+import "time"
+
+go func() {
+	println("Hello, goroutine!")
+}()
+
+time.Sleep(1e8)


Test patch:
diff --git a/cl/expr_test.go b/cl/expr_test.go
index 73a3b20e..15d29d24 100644
--- a/cl/expr_test.go
+++ b/cl/expr_test.go
@@ -175,7 +175,50 @@ func TestTypeCast(t *testing.T) {
 	`).Equal([]byte("hello"))
 }
 
+func TestAppendErr(t *testing.T) {
+	cltest.Expect(t, `
+		append()
+		`,
+		"",
+		"append: argument count not enough\n",
+	)
+	cltest.Expect(t, `
+		x := 1
+		append(x, 2)
+		`,
+		"",
+		"append: first argument not a slice\n",
+	)
+	cltest.Expect(t, `
+		defer append([]int{1}, 2)
+		`,
+		"",
+		"defer discards result of append([]int{1}, 2)\n",
+	)
+}
+
+func TestLenErr(t *testing.T) {
+	cltest.Expect(t, `
+		len()
+		`,
+		"",
+		"missing argument to len: len()\n",
+	)
+	cltest.Expect(t, `
+		len("a", "b")
+		`,
+		"",
+		`too many arguments to len: len("a", "b")`+"\n",
+	)
+}
+
 func TestMake(t *testing.T) {
+	cltest.Expect(t, `
+		make()
+		`,
+		"",
+		"missing argument to make: make()\n",
+	)
 	cltest.Expect(t, `
 		a := make([]int, 0, 4)
 		a = append(a, 1, 2, 3)
diff --git a/cl/goinstr_test.go b/cl/goinstr_test.go
index 1f987614..7d9c7974 100644
--- a/cl/goinstr_test.go
+++ b/cl/goinstr_test.go
@@ -32,7 +32,7 @@ func TestDeferFileNotFound(t *testing.T) {
 	ts.New(t).Call(func() {
 		fn := &ast.Ident{Name: "len"}
 		expr := &ast.CallExpr{Fun: fn}
-		igoLen(ctx, expr, true)
+		igoLen(ctx, expr, callByDefer)
 	}).Panic(
 		"pkgCtx.getCodeInfo failed: file not found - \n",
 	)
@@ -56,7 +56,7 @@ func TestDeferDiscardsResult(t *testing.T) {
 		ts.New(t).Call(func() {
 			fn := &ast.Ident{NamePos: 1, Name: k}
 			expr := &ast.CallExpr{Fun: fn, Rparen: token.Pos(len(k) + 2)}
-			v.instr(ctx, expr, true)
+			v.instr(ctx, expr, callByDefer)
 		}).Panic(
 			"defer discards result of " + k + "()\n",
 		)
diff --git a/cl/stmt_test.go b/cl/stmt_test.go
index 44749468..56226e87 100644
--- a/cl/stmt_test.go
+++ b/cl/stmt_test.go
@@ -1389,6 +1389,24 @@ func TestDefer7(t *testing.T) {
 
 // -----------------------------------------------------------------------------
 
+func TestGo(t *testing.T) {
+	cltest.Expect(t, `
+		import "time"
+
+		n := 1
+		go func() {
+			println("Hello, goroutine!")
+			n++
+			println(n)
+		}()
+		time.Sleep(1e8)
+		`,
+		"Hello, goroutine!\n2\n",
+	)
+}
+
+// -----------------------------------------------------------------------------
+
 var testVarScopeClauses = map[string]testData{
 	"variable_redefinition_#issue304": {`
 						a := []float64{1, 2, 3.4}
diff --git a/exec/bytecode/testdata_test.go b/exec/bytecode/testdata_test.go
index 35296106..9981dd53 100644
--- a/exec/bytecode/testdata_test.go
+++ b/exec/bytecode/testdata_test.go
@@ -36,4 +36,14 @@ func TestFromTestdata(t *testing.T) {
 	cltest.FromTestdata(t, dir, sel, exclude)
 }
 
+func TestFromTestdata2(t *testing.T) {
+	sel, exclude := "", ""
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir = path.Join(dir, "../golang/testdata2")
+	cltest.FromTestdata(t, dir, sel, exclude)
+}
+
 // -----------------------------------------------------------------------------
diff --git a/exec/golang/gogen_test.go b/exec/golang/gogen_test.go
index faf925f6..f93a7190 100644
--- a/exec/golang/gogen_test.go
+++ b/exec/golang/gogen_test.go
@@ -93,4 +93,20 @@ func TestGenGofile(t *testing.T) {
 	}
 }
 
+func TestGenGofile2(t *testing.T) {
+	sel, exclude := "", ""
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir += "/testdata2"
+	fis, err := ioutil.ReadDir(dir)
+	if err != nil {
+		t.Fatal("ReadDir failed:", err)
+	}
+	for _, fi := range fis {
+		testGenGo(t, dir+"/"+fi.Name(), sel, exclude)
+	}
+}
+
 // -----------------------------------------------------------------------------
diff --git a/exec/golang/testdata2/01.goroutine/goroutine.gop b/exec/golang/testdata2/01.goroutine/goroutine.gop
new file mode 100644
index 00000000..9b40e41f
--- /dev/null
+++ b/exec/golang/testdata2/01.goroutine/goroutine.gop
@@ -0,0 +1,23 @@
+import (
+	"os"
+	"time"
+)
+
+func f(args ...interface{}) {
+	println(args...)
+}
+
+go func() {
+	println("Hello, goroutine!")
+}()
+
+a := [1, 2]
+go copy(a, [3, 4])
+
+go println("Hello, goroutine2!")
+
+go os.Stdout.Write([]byte("Hello, goroutine3!"))
+
+go f("Hello, goroutine4!")
+
+time.Sleep(1e8)