Skip to content
Snippets Groups Projects
writer.go 37.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • // UNREVIEWED
    
    // Copyright 2021 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package noder
    
    import (
    	"fmt"
    
    	"cmd/compile/internal/base"
    	"cmd/compile/internal/ir"
    	"cmd/compile/internal/syntax"
    	"cmd/compile/internal/types2"
    )
    
    type pkgWriter struct {
    	pkgEncoder
    
    	m      posMap
    	curpkg *types2.Package
    	info   *types2.Info
    
    	posBasesIdx map[*syntax.PosBase]int
    	pkgsIdx     map[*types2.Package]int
    	typsIdx     map[types2.Type]int
    	globalsIdx  map[types2.Object]int
    
    	funDecls map[*types2.Func]*syntax.FuncDecl
    	typDecls map[*types2.TypeName]typeDeclGen
    
    	linknames  map[types2.Object]string
    	cgoPragmas [][]string
    }
    
    func newPkgWriter(m posMap, pkg *types2.Package, info *types2.Info) *pkgWriter {
    	return &pkgWriter{
    		pkgEncoder: newPkgEncoder(),
    
    		m:      m,
    		curpkg: pkg,
    		info:   info,
    
    		pkgsIdx:    make(map[*types2.Package]int),
    		globalsIdx: make(map[types2.Object]int),
    		typsIdx:    make(map[types2.Type]int),
    
    		posBasesIdx: make(map[*syntax.PosBase]int),
    
    		funDecls: make(map[*types2.Func]*syntax.FuncDecl),
    		typDecls: make(map[*types2.TypeName]typeDeclGen),
    
    		linknames: make(map[types2.Object]string),
    	}
    }
    
    func (pw *pkgWriter) errorf(p poser, msg string, args ...interface{}) {
    	base.ErrorfAt(pw.m.pos(p), msg, args...)
    }
    
    func (pw *pkgWriter) fatalf(p poser, msg string, args ...interface{}) {
    	base.FatalfAt(pw.m.pos(p), msg, args...)
    }
    
    func (pw *pkgWriter) unexpected(what string, p poser) {
    	pw.fatalf(p, "unexpected %s: %v (%T)", what, p, p)
    }
    
    type writer struct {
    	p *pkgWriter
    
    	encoder
    
    	// TODO(mdempsky): We should be able to prune localsIdx whenever a
    	// scope closes, and then maybe we can just use the same map for
    	// storing the TypeParams too (as their TypeName instead).
    
    	// variables declared within this function
    
    	localsIdx map[*types2.Var]int
    
    	closureVars    []posObj
    	closureVarsIdx map[*types2.Var]int
    
    
    	dict    *writerDict
    	derived bool
    }
    
    // A writerDict tracks types and objects that are used by a declaration.
    type writerDict struct {
    	implicits []*types2.TypeName
    
    	// derived is a slice of type indices for computing derived types
    	// (i.e., types that depend on the declaration's type parameters).
    
    
    	// derivedIdx maps a Type to its corresponding index within the
    	// derived slice, if present.
    	derivedIdx map[types2.Type]int
    
    
    	// funcs lists references to generic functions that were
    	// instantiated with derived types (i.e., that require
    	// sub-dictionaries when called at run time).
    	funcs []objInfo
    }
    
    type derivedInfo struct {
    	idx    int
    	needed bool
    }
    
    type typeInfo struct {
    	idx     int
    	derived bool
    }
    
    type objInfo struct {
    	idx       int        // index for the generic function declaration
    	explicits []typeInfo // info for the type arguments
    }
    
    func (info objInfo) anyDerived() bool {
    	for _, explicit := range info.explicits {
    		if explicit.derived {
    			return true
    		}
    	}
    	return false
    }
    
    func (info objInfo) equals(other objInfo) bool {
    	if info.idx != other.idx {
    		return false
    	}
    	assert(len(info.explicits) == len(other.explicits))
    	for i, targ := range info.explicits {
    		if targ != other.explicits[i] {
    			return false
    		}
    	}
    	return true
    
    }
    
    func (pw *pkgWriter) newWriter(k reloc, marker syncMarker) *writer {
    	return &writer{
    		encoder: pw.newEncoder(k, marker),
    		p:       pw,
    	}
    }
    
    // @@@ Positions
    
    func (w *writer) pos(p poser) {
    	w.sync(syncPos)
    	pos := p.Pos()
    
    	// TODO(mdempsky): Track down the remaining cases here and fix them.
    	if !w.bool(pos.IsKnown()) {
    		return
    	}
    
    	// TODO(mdempsky): Delta encoding. Also, if there's a b-side, update
    	// its position base too (but not vice versa!).
    	w.posBase(pos.Base())
    	w.uint(pos.Line())
    	w.uint(pos.Col())
    }
    
    func (w *writer) posBase(b *syntax.PosBase) {
    	w.reloc(relocPosBase, w.p.posBaseIdx(b))
    }
    
    func (pw *pkgWriter) posBaseIdx(b *syntax.PosBase) int {
    	if idx, ok := pw.posBasesIdx[b]; ok {
    		return idx
    	}
    
    	w := pw.newWriter(relocPosBase, syncPosBase)
    	w.p.posBasesIdx[b] = w.idx
    
    
    	w.string(trimFilename(b))
    
    
    	if !w.bool(b.IsFileBase()) {
    		w.pos(b)
    		w.uint(b.Line())
    		w.uint(b.Col())
    	}
    
    	return w.flush()
    }
    
    // @@@ Packages
    
    func (w *writer) pkg(pkg *types2.Package) {
    	w.sync(syncPkg)
    	w.reloc(relocPkg, w.p.pkgIdx(pkg))
    }
    
    func (pw *pkgWriter) pkgIdx(pkg *types2.Package) int {
    	if idx, ok := pw.pkgsIdx[pkg]; ok {
    		return idx
    	}
    
    	w := pw.newWriter(relocPkg, syncPkgDef)
    	pw.pkgsIdx[pkg] = w.idx
    
    	if pkg == nil {
    		w.string("builtin")
    	} else {
    		var path string
    		if pkg != w.p.curpkg {
    			path = pkg.Path()
    		}
    		w.string(path)
    		w.string(pkg.Name())
    		w.len(pkg.Height())
    
    		w.len(len(pkg.Imports()))
    		for _, imp := range pkg.Imports() {
    			w.pkg(imp)
    		}
    	}
    
    	return w.flush()
    }
    
    // @@@ Types
    
    
    var anyTypeName = types2.Universe.Lookup("any").(*types2.TypeName)
    
    
    func (w *writer) typ(typ types2.Type) {
    
    // typIdx returns the index where the export data description of type
    // can be read back in. If no such index exists yet, it's created.
    //
    // typIdx also reports whether typ is a derived type; that is, whether
    // its identity depends on type parameters.
    
    func (pw *pkgWriter) typIdx(typ types2.Type, dict *writerDict) typeInfo {
    
    	if idx, ok := pw.typsIdx[typ]; ok {
    
    	}
    	if dict != nil {
    		if idx, ok := dict.derivedIdx[typ]; ok {
    
    	}
    
    	w := pw.newWriter(relocType, syncTypeIdx)
    
    
    	switch typ := typ.(type) {
    	default:
    		base.Fatalf("unexpected type: %v (%T)", typ, typ)
    
    	case *types2.Basic:
    
    		switch kind := typ.Kind(); {
    		case kind == types2.Invalid:
    			base.Fatalf("unexpected types2.Invalid")
    
    		case types2.Typ[kind] == typ:
    
    			w.code(typeBasic)
    			w.len(int(kind))
    
    
    		default:
    			// Handle "byte" and "rune" as references to their TypeName.
    			obj := types2.Universe.Lookup(typ.Name())
    			assert(obj.Type() == typ)
    
    
    	case *types2.Named:
    		// Type aliases can refer to uninstantiated generic types, so we
    		// might see len(TParams) != 0 && len(TArgs) == 0 here.
    		// TODO(mdempsky): Revisit after #46477 is resolved.
    
    		assert(typ.TypeParams().Len() == typ.TypeArgs().Len() || typ.TypeArgs().Len() == 0)
    
    
    		// TODO(mdempsky): Why do we need to loop here?
    		orig := typ
    
    		index := func() int {
    			for idx, name := range w.dict.implicits {
    				if name.Type().(*types2.TypeParam) == typ {
    					return idx
    				}
    			}
    
    			return len(w.dict.implicits) + typ.Index()
    		}()
    
    		w.derived = true
    
    
    	case *types2.Array:
    		w.code(typeArray)
    		w.uint64(uint64(typ.Len()))
    		w.typ(typ.Elem())
    
    	case *types2.Chan:
    		w.code(typeChan)
    		w.len(int(typ.Dir()))
    		w.typ(typ.Elem())
    
    	case *types2.Map:
    		w.code(typeMap)
    		w.typ(typ.Key())
    		w.typ(typ.Elem())
    
    	case *types2.Pointer:
    		w.code(typePointer)
    		w.typ(typ.Elem())
    
    	case *types2.Signature:
    
    		base.Assertf(typ.TypeParams() == nil, "unexpected type params: %v", typ)
    
    		w.code(typeSignature)
    		w.signature(typ)
    
    	case *types2.Slice:
    		w.code(typeSlice)
    		w.typ(typ.Elem())
    
    	case *types2.Struct:
    		w.code(typeStruct)
    		w.structType(typ)
    
    	case *types2.Interface:
    
    		if typ == anyTypeName.Type() {
    			w.code(typeNamed)
    			w.obj(anyTypeName, nil)
    			break
    		}
    
    
    		w.code(typeInterface)
    		w.interfaceType(typ)
    
    	case *types2.Union:
    		w.code(typeUnion)
    		w.unionType(typ)
    	}
    
    		dict.derived = append(dict.derived, derivedInfo{idx: w.flush()})
    
    	return typeInfo{idx: w.flush(), derived: false}
    
    }
    
    func (w *writer) structType(typ *types2.Struct) {
    	w.len(typ.NumFields())
    	for i := 0; i < typ.NumFields(); i++ {
    		f := typ.Field(i)
    		w.pos(f)
    		w.selector(f)
    		w.typ(f.Type())
    		w.string(typ.Tag(i))
    		w.bool(f.Embedded())
    	}
    }
    
    func (w *writer) unionType(typ *types2.Union) {
    
    	w.len(typ.Len())
    	for i := 0; i < typ.Len(); i++ {
    		t := typ.Term(i)
    		w.bool(t.Tilde())
    
    	}
    }
    
    func (w *writer) interfaceType(typ *types2.Interface) {
    	w.len(typ.NumExplicitMethods())
    	w.len(typ.NumEmbeddeds())
    
    	for i := 0; i < typ.NumExplicitMethods(); i++ {
    		m := typ.ExplicitMethod(i)
    		sig := m.Type().(*types2.Signature)
    
    
    		w.pos(m)
    		w.selector(m)
    		w.signature(sig)
    	}
    
    	for i := 0; i < typ.NumEmbeddeds(); i++ {
    		w.typ(typ.EmbeddedType(i))
    	}
    }
    
    func (w *writer) signature(sig *types2.Signature) {
    	w.sync(syncSignature)
    	w.params(sig.Params())
    	w.params(sig.Results())
    	w.bool(sig.Variadic())
    }
    
    func (w *writer) params(typ *types2.Tuple) {
    	w.sync(syncParams)
    	w.len(typ.Len())
    	for i := 0; i < typ.Len(); i++ {
    		w.param(typ.At(i))
    	}
    }
    
    func (w *writer) param(param *types2.Var) {
    	w.sync(syncParam)
    	w.pos(param)
    	w.localIdent(param)
    	w.typ(param.Type())
    }
    
    // @@@ Objects
    
    
    func (w *writer) obj(obj types2.Object, explicits *types2.TypeList) {
    	explicitInfos := make([]typeInfo, explicits.Len())
    	for i := range explicitInfos {
    		explicitInfos[i] = w.p.typIdx(explicits.At(i), w.dict)
    
    	}
    	info := objInfo{idx: w.p.objIdx(obj), explicits: explicitInfos}
    
    	if _, ok := obj.(*types2.Func); ok && info.anyDerived() {
    		idx := -1
    		for i, prev := range w.dict.funcs {
    			if prev.equals(info) {
    				idx = i
    			}
    		}
    		if idx < 0 {
    			idx = len(w.dict.funcs)
    			w.dict.funcs = append(w.dict.funcs, info)
    		}
    
    		// TODO(mdempsky): Push up into expr; this shouldn't appear
    		// outside of expression context.
    		w.sync(syncObject)
    		w.bool(true)
    		w.len(idx)
    		return
    	}
    
    	// TODO(mdempsky): Push up into typIdx; this shouldn't be needed
    	// except while writing out types.
    
    	if isDefinedType(obj) && obj.Pkg() == w.p.curpkg {
    		decl, ok := w.p.typDecls[obj.(*types2.TypeName)]
    		assert(ok)
    		if len(decl.implicits) != 0 {
    			w.derived = true
    		}
    
    	w.len(len(info.explicits))
    	for _, info := range info.explicits {
    		w.typInfo(info)
    
    func (pw *pkgWriter) objIdx(obj types2.Object) int {
    
    	if idx, ok := pw.globalsIdx[obj]; ok {
    		return idx
    	}
    
    
    	dict := &writerDict{
    		derivedIdx: make(map[types2.Type]int),
    	}
    
    	if isDefinedType(obj) && obj.Pkg() == pw.curpkg {
    		decl, ok := pw.typDecls[obj.(*types2.TypeName)]
    		assert(ok)
    		dict.implicits = decl.implicits
    	}
    
    
    	w := pw.newWriter(relocObj, syncObject1)
    
    	wext := pw.newWriter(relocObjExt, syncObject1)
    
    	wname := pw.newWriter(relocName, syncObject1)
    
    	wdict := pw.newWriter(relocObjDict, syncObject1)
    
    	pw.globalsIdx[obj] = w.idx // break cycles
    
    	wname.qualifiedIdent(obj)
    	wname.code(code)
    	wname.flush()
    
    	wdict.objDict(obj, w.dict)
    
    func (w *writer) doObj(wext *writer, obj types2.Object) codeObj {
    
    	if obj.Pkg() != w.p.curpkg {
    
    	}
    
    	switch obj := obj.(type) {
    	default:
    		w.p.unexpected("object", obj)
    
    		w.typ(obj.Type())
    		w.value(obj.Val())
    
    
    	case *types2.Func:
    		decl, ok := w.p.funDecls[obj]
    		assert(ok)
    		sig := obj.Type().(*types2.Signature)
    
    		w.pos(obj)
    
    		w.signature(sig)
    		w.pos(decl)
    
    
    	case *types2.TypeName:
    		decl, ok := w.p.typDecls[obj]
    		assert(ok)
    
    		if obj.IsAlias() {
    			w.pos(obj)
    			w.typ(obj.Type())
    
    		}
    
    		named := obj.Type().(*types2.Named)
    
    		w.typeParamNames(named.TypeParams())
    
    		w.typExpr(decl.Type)
    
    		w.len(named.NumMethods())
    		for i := 0; i < named.NumMethods(); i++ {
    
    	case *types2.Var:
    		w.pos(obj)
    		w.typ(obj.Type())
    
    	}
    }
    
    // typExpr writes the type represented by the given expression.
    func (w *writer) typExpr(expr syntax.Expr) {
    	tv, ok := w.p.info.Types[expr]
    	assert(ok)
    	assert(tv.IsType())
    	w.typ(tv.Type)
    }
    
    
    // objDict writes the dictionary needed for reading the given object.
    func (w *writer) objDict(obj types2.Object, dict *writerDict) {
    	// TODO(mdempsky): Split objDict into multiple entries? reader.go
    	// doesn't care about the type parameter bounds, and reader2.go
    	// doesn't care about referenced functions.
    
    	w.dict = dict // TODO(mdempsky): This is a bit sketchy.
    
    	ntparams := tparams.Len()
    	w.len(ntparams)
    	for i := 0; i < ntparams; i++ {
    
    
    	nderived := len(dict.derived)
    	w.len(nderived)
    	for _, typ := range dict.derived {
    		w.reloc(relocType, typ.idx)
    		w.bool(typ.needed)
    	}
    
    	nfuncs := len(dict.funcs)
    	w.len(nfuncs)
    	for _, fn := range dict.funcs {
    		w.reloc(relocObj, fn.idx)
    		w.len(len(fn.explicits))
    		for _, targ := range fn.explicits {
    			w.typInfo(targ)
    		}
    	}
    
    	assert(len(dict.derived) == nderived)
    	assert(len(dict.funcs) == nfuncs)
    
    func (w *writer) typeParamNames(tparams *types2.TypeParamList) {
    
    	ntparams := tparams.Len()
    	for i := 0; i < ntparams; i++ {
    
    func (w *writer) method(wext *writer, meth *types2.Func) {
    
    	decl, ok := w.p.funDecls[meth]
    	assert(ok)
    	sig := meth.Type().(*types2.Signature)
    
    	w.sync(syncMethod)
    	w.pos(meth)
    	w.selector(meth)
    
    	w.typeParamNames(sig.RecvTypeParams())
    
    	w.param(sig.Recv())
    	w.signature(sig)
    
    	w.pos(decl) // XXX: Hack to workaround linker limitations.
    
    }
    
    // qualifiedIdent writes out the name of an object declared at package
    // scope. (For now, it's also used to refer to local defined types.)
    func (w *writer) qualifiedIdent(obj types2.Object) {
    	w.sync(syncSym)
    
    	name := obj.Name()
    
    	if isDefinedType(obj) && obj.Pkg() == w.p.curpkg {
    
    		decl, ok := w.p.typDecls[obj.(*types2.TypeName)]
    		assert(ok)
    
    		if decl.gen != 0 {
    			// TODO(mdempsky): Find a better solution than embedding middle
    			// dot in the symbol name; this is terrible.
    			name = fmt.Sprintf("%s·%v", name, decl.gen)
    		}
    
    	}
    
    	w.pkg(obj.Pkg())
    	w.string(name)
    }
    
    // TODO(mdempsky): We should be able to omit pkg from both localIdent
    // and selector, because they should always be known from context.
    // However, past frustrations with this optimization in iexport make
    // me a little nervous to try it again.
    
    // localIdent writes the name of a locally declared object (i.e.,
    // objects that can only be accessed by name, within the context of a
    // particular function).
    func (w *writer) localIdent(obj types2.Object) {
    	assert(!isGlobal(obj))
    	w.sync(syncLocalIdent)
    	w.pkg(obj.Pkg())
    	w.string(obj.Name())
    }
    
    // selector writes the name of a field or method (i.e., objects that
    // can only be accessed using selector expressions).
    func (w *writer) selector(obj types2.Object) {
    	w.sync(syncSelector)
    	w.pkg(obj.Pkg())
    	w.string(obj.Name())
    }
    
    // @@@ Compiler extensions
    
    func (w *writer) funcExt(obj *types2.Func) {
    	decl, ok := w.p.funDecls[obj]
    	assert(ok)
    
    	// TODO(mdempsky): Extend these pragma validation flags to account
    	// for generics. E.g., linkname probably doesn't make sense at
    	// least.
    
    	pragma := asPragmaFlag(decl.Pragma)
    	if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
    		w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
    	}
    
    	if decl.Body != nil {
    		if pragma&ir.Noescape != 0 {
    			w.p.errorf(decl, "can only use //go:noescape with external func implementations")
    		}
    	} else {
    		if base.Flag.Complete || decl.Name.Value == "init" {
    			// Linknamed functions are allowed to have no body. Hopefully
    			// the linkname target has a body. See issue 23311.
    			if _, ok := w.p.linknames[obj]; !ok {
    				w.p.errorf(decl, "missing function body")
    			}
    		}
    	}
    
    
    	sig, block := obj.Type().(*types2.Signature), decl.Body
    
    	body, closureVars := w.p.bodyIdx(w.p.curpkg, sig, block, w.dict)
    
    	w.sync(syncFuncExt)
    	w.pragmaFlag(pragma)
    	w.linkname(obj)
    	w.bool(false) // stub extension
    
    	w.sync(syncEOF)
    }
    
    func (w *writer) typeExt(obj *types2.TypeName) {
    	decl, ok := w.p.typDecls[obj]
    	assert(ok)
    
    	w.sync(syncTypeExt)
    
    	w.pragmaFlag(asPragmaFlag(decl.Pragma))
    
    	// No LSym.SymIdx info yet.
    	w.int64(-1)
    	w.int64(-1)
    }
    
    func (w *writer) varExt(obj *types2.Var) {
    	w.sync(syncVarExt)
    	w.linkname(obj)
    }
    
    func (w *writer) linkname(obj types2.Object) {
    	w.sync(syncLinkname)
    	w.int64(-1)
    	w.string(w.p.linknames[obj])
    }
    
    func (w *writer) pragmaFlag(p ir.PragmaFlag) {
    	w.sync(syncPragma)
    	w.int(int(p))
    }
    
    // @@@ Function bodies
    
    
    func (pw *pkgWriter) bodyIdx(pkg *types2.Package, sig *types2.Signature, block *syntax.BlockStmt, dict *writerDict) (idx int, closureVars []posObj) {
    
    	w := pw.newWriter(relocBody, syncFuncBody)
    
    
    	w.funcargs(sig)
    	if w.bool(block != nil) {
    		w.stmts(block.List)
    		w.pos(block.Rbrace)
    	}
    
    
    }
    
    func (w *writer) funcargs(sig *types2.Signature) {
    	do := func(params *types2.Tuple, result bool) {
    		for i := 0; i < params.Len(); i++ {
    			w.funcarg(params.At(i), result)
    		}
    	}
    
    	if recv := sig.Recv(); recv != nil {
    		w.funcarg(recv, false)
    	}
    	do(sig.Params(), false)
    	do(sig.Results(), true)
    }
    
    func (w *writer) funcarg(param *types2.Var, result bool) {
    	if param.Name() != "" || result {
    		w.addLocal(param)
    	}
    }
    
    
    func (w *writer) addLocal(obj *types2.Var) {
    
    	w.sync(syncAddLocal)
    	idx := len(w.localsIdx)
    
    	if w.localsIdx == nil {
    		w.localsIdx = make(map[*types2.Var]int)
    	}
    
    func (w *writer) useLocal(pos syntax.Pos, obj *types2.Var) {
    
    
    	if idx, ok := w.localsIdx[obj]; w.bool(ok) {
    		w.len(idx)
    		return
    	}
    
    	idx, ok := w.closureVarsIdx[obj]
    	if !ok {
    		if w.closureVarsIdx == nil {
    			w.closureVarsIdx = make(map[*types2.Var]int)
    		}
    		idx = len(w.closureVars)
    		w.closureVars = append(w.closureVars, posObj{pos, obj})
    		w.closureVarsIdx[obj] = idx
    	}
    
    	w.len(idx)
    }
    
    func (w *writer) openScope(pos syntax.Pos) {
    	w.sync(syncOpenScope)
    	w.pos(pos)
    }
    
    func (w *writer) closeScope(pos syntax.Pos) {
    	w.sync(syncCloseScope)
    	w.pos(pos)
    	w.closeAnotherScope()
    }
    
    func (w *writer) closeAnotherScope() {
    	w.sync(syncCloseAnotherScope)
    }
    
    // @@@ Statements
    
    func (w *writer) stmt(stmt syntax.Stmt) {
    	var stmts []syntax.Stmt
    	if stmt != nil {
    		stmts = []syntax.Stmt{stmt}
    	}
    	w.stmts(stmts)
    }
    
    func (w *writer) stmts(stmts []syntax.Stmt) {
    	w.sync(syncStmts)
    	for _, stmt := range stmts {
    		w.stmt1(stmt)
    	}
    	w.code(stmtEnd)
    	w.sync(syncStmtsEnd)
    }
    
    func (w *writer) stmt1(stmt syntax.Stmt) {
    	switch stmt := stmt.(type) {
    	default:
    		w.p.unexpected("statement", stmt)
    
    	case nil, *syntax.EmptyStmt:
    		return
    
    	case *syntax.AssignStmt:
    		switch {
    		case stmt.Rhs == nil:
    			w.code(stmtIncDec)
    			w.op(binOps[stmt.Op])
    			w.expr(stmt.Lhs)
    			w.pos(stmt)
    
    		case stmt.Op != 0 && stmt.Op != syntax.Def:
    			w.code(stmtAssignOp)
    			w.op(binOps[stmt.Op])
    			w.expr(stmt.Lhs)
    			w.pos(stmt)
    			w.expr(stmt.Rhs)
    
    		default:
    			w.code(stmtAssign)
    			w.pos(stmt)
    			w.exprList(stmt.Rhs)
    
    		}
    
    	case *syntax.BlockStmt:
    		w.code(stmtBlock)
    		w.blockStmt(stmt)
    
    	case *syntax.BranchStmt:
    		w.code(stmtBranch)
    		w.pos(stmt)
    		w.op(branchOps[stmt.Tok])
    		w.optLabel(stmt.Label)
    
    	case *syntax.CallStmt:
    		w.code(stmtCall)
    		w.pos(stmt)
    		w.op(callOps[stmt.Tok])
    		w.expr(stmt.Call)
    
    	case *syntax.DeclStmt:
    		for _, decl := range stmt.DeclList {
    			w.declStmt(decl)
    		}
    
    	case *syntax.ExprStmt:
    		w.code(stmtExpr)
    		w.expr(stmt.X)
    
    	case *syntax.ForStmt:
    		w.code(stmtFor)
    		w.forStmt(stmt)
    
    	case *syntax.IfStmt:
    		w.code(stmtIf)
    		w.ifStmt(stmt)
    
    	case *syntax.LabeledStmt:
    		w.code(stmtLabel)
    		w.pos(stmt)
    		w.label(stmt.Label)
    		w.stmt1(stmt.Stmt)
    
    	case *syntax.ReturnStmt:
    		w.code(stmtReturn)
    		w.pos(stmt)
    		w.exprList(stmt.Results)
    
    	case *syntax.SelectStmt:
    		w.code(stmtSelect)
    		w.selectStmt(stmt)
    
    	case *syntax.SendStmt:
    		w.code(stmtSend)
    		w.pos(stmt)
    		w.expr(stmt.Chan)
    		w.expr(stmt.Value)
    
    	case *syntax.SwitchStmt:
    		w.code(stmtSwitch)
    		w.switchStmt(stmt)
    	}
    }
    
    func (w *writer) assignList(expr syntax.Expr) {
    	exprs := unpackListExpr(expr)
    	w.len(len(exprs))
    
    	for _, expr := range exprs {
    		if name, ok := expr.(*syntax.Name); ok && name.Value != "_" {
    			if obj, ok := w.p.info.Defs[name]; ok {
    
    				w.bool(true)
    				w.pos(obj)
    				w.localIdent(obj)
    				w.typ(obj.Type())
    
    				// TODO(mdempsky): Minimize locals index size by deferring
    				// this until the variables actually come into scope.
    				w.addLocal(obj)
    				continue
    			}
    		}
    
    		w.bool(false)
    		w.expr(expr)
    	}
    }
    
    func (w *writer) declStmt(decl syntax.Decl) {
    	switch decl := decl.(type) {
    	default:
    		w.p.unexpected("declaration", decl)
    
    
    	case *syntax.ConstDecl, *syntax.TypeDecl:
    
    
    	case *syntax.VarDecl:
    		w.code(stmtAssign)
    		w.pos(decl)
    		w.exprList(decl.Values)