Skip to content
Snippets Groups Projects
reader.go 108 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	meth.SetNointerface(name.Func.Pragma&ir.Nointerface != 0)
    
    
    	return meth
    }
    
    func (r *reader) qualifiedIdent() (pkg *types.Pkg, sym *types.Sym) {
    
    	if name := r.String(); name != "" {
    
    		sym = pkg.Lookup(name)
    	}
    	return
    }
    
    func (r *reader) localIdent() (pkg *types.Pkg, sym *types.Sym) {
    
    	r.Sync(pkgbits.SyncLocalIdent)
    
    	if name := r.String(); name != "" {
    
    		sym = pkg.Lookup(name)
    	}
    	return
    }
    
    func (r *reader) selector() (origPkg *types.Pkg, sym *types.Sym) {
    
    	r.Sync(pkgbits.SyncSelector)
    
    	pkg := origPkg
    	if types.IsExported(name) {
    		pkg = types.LocalPkg
    	}
    	sym = pkg.Lookup(name)
    	return
    }
    
    
    	return r.dict.hasTypeParams()
    }
    
    func (dict *readerDict) hasTypeParams() bool {
    	return dict != nil && len(dict.targs) != 0
    
    func (r *reader) funcExt(name *ir.Name, method *types.Sym) {
    
    	r.Sync(pkgbits.SyncFuncExt)
    
    
    	name.Class = 0 // so MarkFunc doesn't complain
    	ir.MarkFunc(name)
    
    	fn := name.Func
    
    	// XXX: Workaround because linker doesn't know how to copy Pos.
    	if !fn.Pos().IsKnown() {
    		fn.SetPos(name.Pos())
    	}
    
    
    	// Normally, we only compile local functions, which saves redundant compilation work.
    	// n.Defn is not nil for local functions, and is nil for imported function. But for
    	// generic functions, we might have an instantiation that no other package has seen before.
    	// So we need to be conservative and compile it again.
    	//
    	// That's why name.Defn is set here, so ir.VisitFuncsBottomUp can analyze function.
    	// TODO(mdempsky,cuonglm): find a cleaner way to handle this.
    
    	if name.Sym().Pkg == types.LocalPkg || r.hasTypeParams() {
    
    		name.Defn = fn
    	}
    
    	fn.Pragma = r.pragmaFlag()
    	r.linkname(name)
    
    
    		fn.ABI = obj.ABI(r.Uint64())
    
    
    		// Escape analysis.
    		for _, fs := range &types.RecvsParams {
    			for _, f := range fs(name.Type()).FieldSlice() {
    
    				Cost:            int32(r.Len()),
    				CanDelayResults: r.Bool(),
    
    }
    
    func (r *reader) typeExt(name *ir.Name) {
    
    	r.Sync(pkgbits.SyncTypeExt)
    
    		// Set "RParams" (really type arguments here, not parameters) so
    		// this type is treated as "fully instantiated". This ensures the
    		// type descriptor is written out as DUPOK and method wrappers are
    		// generated even for imported types.
    		var targs []*types.Type
    
    		targs = append(targs, r.dict.targs...)
    
    		typ.SetRParams(targs)
    	}
    
    	name.SetPragma(r.pragmaFlag())
    
    
    	typecheck.SetBaseTypeIndex(typ, r.Int64(), r.Int64())
    
    }
    
    func (r *reader) varExt(name *ir.Name) {
    
    	r.Sync(pkgbits.SyncVarExt)
    
    	r.linkname(name)
    }
    
    func (r *reader) linkname(name *ir.Name) {
    	assert(name.Op() == ir.ONAME)
    
    	r.Sync(pkgbits.SyncLinkname)
    
    	if idx := r.Int64(); idx >= 0 {
    
    		lsym := name.Linksym()
    		lsym.SymIdx = int32(idx)
    		lsym.Set(obj.AttrIndexed, true)
    	} else {
    
    		name.Sym().Linkname = r.String()
    
    	}
    }
    
    func (r *reader) pragmaFlag() ir.PragmaFlag {
    
    	r.Sync(pkgbits.SyncPragma)
    	return ir.PragmaFlag(r.Int())
    
    // bodyReader tracks where the serialized IR for a local or imported,
    // generic function's body can be found.
    
    var bodyReader = map[*ir.Func]pkgReaderIndex{}
    
    
    // importBodyReader tracks where the serialized IR for an imported,
    // static (i.e., non-generic) function body can be read.
    var importBodyReader = map[*types.Sym]pkgReaderIndex{}
    
    // bodyReaderFor returns the pkgReaderIndex for reading fn's
    // serialized IR, and whether one was found.
    func bodyReaderFor(fn *ir.Func) (pri pkgReaderIndex, ok bool) {
    	if fn.Nname.Defn != nil {
    		pri, ok = bodyReader[fn]
    
    		base.AssertfAt(ok, base.Pos, "must have bodyReader for %v", fn) // must always be available
    
    // todoDicts holds the list of dictionaries that still need their
    // runtime dictionary objects constructed.
    var todoDicts []func()
    
    
    // todoBodies holds the list of function bodies that still need to be
    // constructed.
    var todoBodies []*ir.Func
    
    
    // addBody reads a function body reference from the element bitstream,
    // and associates it with fn.
    
    func (r *reader) addBody(fn *ir.Func, method *types.Sym) {
    
    	// addBody should only be called for local functions or imported
    	// generic functions; see comment in funcExt.
    	assert(fn.Nname.Defn != nil)
    
    
    	pri := pkgReaderIndex{r.p, idx, r.dict, method, nil}
    
    		todoBodies = append(todoBodies, fn)
    		return
    	}
    
    	pri.funcBody(fn)
    }
    
    func (pri pkgReaderIndex) funcBody(fn *ir.Func) {
    
    	r := pri.asReader(pkgbits.RelocBody, pkgbits.SyncFuncBody)
    
    // funcBody reads a function body definition from the element
    // bitstream, and populates fn with it.
    
    func (r *reader) funcBody(fn *ir.Func) {
    	r.curfn = fn
    
    	if len(r.closureVars) != 0 && r.hasTypeParams() {
    		r.dictParam = r.closureVars[len(r.closureVars)-1] // dictParam is last; see reader.funcLit
    	}
    
    		body := r.stmts()
    		if body == nil {
    
    			body = []ir.Node{typecheck.Stmt(ir.NewBlockStmt(src.NoXPos, nil))}
    
    		}
    		fn.Body = body
    		fn.Endlineno = r.pos()
    
    // syntheticBody adds a synthetic body to r.curfn if appropriate, and
    // reports whether it did.
    func (r *reader) syntheticBody(pos src.XPos) bool {
    	if r.synthetic != nil {
    		r.synthetic(pos, r)
    		return true
    	}
    
    	// If this function has type parameters and isn't shaped, then we
    	// just tail call its corresponding shaped variant.
    	if r.hasTypeParams() && !r.dict.shaped {
    		r.callShaped(pos)
    		return true
    	}
    
    	return false
    }
    
    
    // callShaped emits a tail call to r.shapedFn, passing along the
    // arguments to the current function.
    func (r *reader) callShaped(pos src.XPos) {
    
    	shapedObj := r.dict.shapedObj
    	assert(shapedObj != nil)
    
    	var shapedFn ir.Node
    	if r.methodSym == nil {
    		// Instantiating a generic function; shapedObj is the shaped
    		// function itself.
    		assert(shapedObj.Op() == ir.ONAME && shapedObj.Class == ir.PFUNC)
    		shapedFn = shapedObj
    	} else {
    		// Instantiating a generic type's method; shapedObj is the shaped
    		// type, so we need to select it's corresponding method.
    		shapedFn = shapedMethodExpr(pos, shapedObj, r.methodSym)
    	}
    
    	recvs, params := r.syntheticArgs(pos)
    
    	// Construct the arguments list: receiver (if any), then runtime
    	// dictionary, and finally normal parameters.
    	//
    	// Note: For simplicity, shaped methods are added as normal methods
    	// on their shaped types. So existing code (e.g., packages ir and
    	// typecheck) expects the shaped type to appear as the receiver
    	// parameter (or first parameter, as a method expression). Hence
    	// putting the dictionary parameter after that is the least invasive
    	// solution at the moment.
    
    	args.Append(recvs...)
    	args.Append(typecheck.Expr(ir.NewAddrExpr(pos, r.p.dictNameOf(r.dict))))
    	args.Append(params...)
    
    	r.syntheticTailCall(pos, shapedFn, args)
    }
    
    // syntheticArgs returns the recvs and params arguments passed to the
    // current function.
    func (r *reader) syntheticArgs(pos src.XPos) (recvs, params ir.Nodes) {
    	sig := r.curfn.Nname.Type()
    
    	inlVarIdx := 0
    	addParams := func(out *ir.Nodes, params []*types.Field) {
    		for _, param := range params {
    
    			var arg ir.Node
    			if param.Nname != nil {
    				name := param.Nname.(*ir.Name)
    				if !ir.IsBlank(name) {
    					if r.inlCall != nil {
    						// During inlining, we want the respective inlvar where we
    						// assigned the callee's arguments.
    
    					} else {
    						// Otherwise, we can use the parameter itself directly.
    						base.AssertfAt(name.Curfn == r.curfn, name.Pos(), "%v has curfn %v, but want %v", name, name.Curfn, r.curfn)
    						arg = name
    					}
    				}
    			}
    
    			// For anonymous and blank parameters, we don't have an *ir.Name
    			// to use as the argument. However, since we know the shaped
    			// function won't use the value either, we can just pass the
    			// zero value. (Also unfortunately, we don't have an easy
    			// zero-value IR node; so we use a default-initialized temporary
    			// variable.)
    			if arg == nil {
    				tmp := typecheck.TempAt(pos, r.curfn, param.Type)
    				r.curfn.Body.Append(
    					typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp)),
    					typecheck.Stmt(ir.NewAssignStmt(pos, tmp, nil)),
    				)
    				arg = tmp
    			}
    
    
    	addParams(&recvs, sig.Recvs().FieldSlice())
    	addParams(&params, sig.Params().FieldSlice())
    	return
    }
    
    // syntheticTailCall emits a tail call to fn, passing the given
    // arguments list.
    func (r *reader) syntheticTailCall(pos src.XPos, fn ir.Node, args ir.Nodes) {
    
    	// Mark the function as a wrapper so it doesn't show up in stack
    	// traces.
    	r.curfn.SetWrapper(true)
    
    
    	call := typecheck.Call(pos, fn, args, fn.Type().IsVariadic()).(*ir.CallExpr)
    
    		stmt = typecheck.Stmt(ir.NewReturnStmt(pos, []ir.Node{call}))
    	} else {
    		stmt = call
    	}
    	r.curfn.Body.Append(stmt)
    }
    
    
    // dictNameOf returns the runtime dictionary corresponding to dict.
    func (pr *pkgReader) dictNameOf(dict *readerDict) *ir.Name {
    	pos := base.AutogeneratedPos
    
    	// Check that we only instantiate runtime dictionaries with real types.
    	base.AssertfAt(!dict.shaped, pos, "runtime dictionary of shaped object %v", dict.baseSym)
    
    	sym := dict.baseSym.Pkg.Lookup(objabi.GlobalDictPrefix + "." + dict.baseSym.Name)
    	if sym.Def != nil {
    		return sym.Def.(*ir.Name)
    
    	name := ir.NewNameAt(pos, sym)
    	name.Class = ir.PEXTERN
    	sym.Def = name // break cycles with mutual subdictionaries
    
    	lsym := name.Linksym()
    	ot := 0
    
    	assertOffset := func(section string, offset int) {
    		base.AssertfAt(ot == offset*types.PtrSize, pos, "writing section %v at offset %v, but it should be at %v*%v", section, ot, offset, types.PtrSize)
    
    	assertOffset("type param method exprs", dict.typeParamMethodExprsOffset())
    	for _, info := range dict.typeParamMethodExprs {
    		typeParam := dict.targs[info.typeParamIdx]
    		method := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(typeParam), info.method)).(*ir.SelectorExpr)
    		assert(method.Op() == ir.OMETHEXPR)
    
    		rsym := method.FuncName().Linksym()
    		assert(rsym.ABI() == obj.ABIInternal) // must be ABIInternal; see ir.OCFUNC in ssagen/ssa.go
    
    	assertOffset("subdictionaries", dict.subdictsOffset())
    	for _, info := range dict.subdicts {
    		explicits := pr.typListIdx(info.explicits, dict)
    
    		// Careful: Due to subdictionary cycles, name may not be fully
    		// initialized yet.
    		name := pr.objDictName(info.idx, dict.targs, explicits)
    
    		ot = objw.SymPtr(lsym, ot, name.Linksym(), 0)
    	}
    
    	assertOffset("rtypes", dict.rtypesOffset())
    	for _, info := range dict.rtypes {
    		typ := pr.typIdx(info, dict, true)
    		ot = objw.SymPtr(lsym, ot, reflectdata.TypeLinksym(typ), 0)
    
    		// TODO(mdempsky): Double check this.
    		reflectdata.MarkTypeUsedInInterface(typ, lsym)
    
    	// For each (typ, iface) pair, we write the *runtime.itab pointer
    	// for the pair. For pairs that don't actually require an itab
    	// (i.e., typ is an interface, or iface is an empty interface), we
    	// write a nil pointer instead. This is wasteful, but rare in
    	// practice (e.g., instantiating a type parameter with an interface
    	// type).
    
    	assertOffset("itabs", dict.itabsOffset())
    	for _, info := range dict.itabs {
    		typ := pr.typIdx(info.typ, dict, true)
    		iface := pr.typIdx(info.iface, dict, true)
    
    
    		if !typ.IsInterface() && iface.IsInterface() && !iface.IsEmptyInterface() {
    
    			ot = objw.SymPtr(lsym, ot, reflectdata.ITabLsym(typ, iface), 0)
    		} else {
    			ot += types.PtrSize
    		}
    
    		// TODO(mdempsky): Double check this.
    		reflectdata.MarkTypeUsedInInterface(typ, lsym)
    		reflectdata.MarkTypeUsedInInterface(iface, lsym)
    	}
    
    	objw.Global(lsym, int32(ot), obj.DUPOK|obj.RODATA)
    
    	name.SetType(dict.varType())
    	name.SetTypecheck(1)
    
    	return name
    }
    
    // typeParamMethodExprsOffset returns the offset of the runtime
    // dictionary's type parameter method expressions section, in words.
    func (dict *readerDict) typeParamMethodExprsOffset() int {
    	return 0
    }
    
    // subdictsOffset returns the offset of the runtime dictionary's
    // subdictionary section, in words.
    func (dict *readerDict) subdictsOffset() int {
    	return dict.typeParamMethodExprsOffset() + len(dict.typeParamMethodExprs)
    }
    
    // rtypesOffset returns the offset of the runtime dictionary's rtypes
    // section, in words.
    func (dict *readerDict) rtypesOffset() int {
    	return dict.subdictsOffset() + len(dict.subdicts)
    }
    
    // itabsOffset returns the offset of the runtime dictionary's itabs
    // section, in words.
    func (dict *readerDict) itabsOffset() int {
    	return dict.rtypesOffset() + len(dict.rtypes)
    
    // numWords returns the total number of words that comprise dict's
    // runtime dictionary variable.
    
    func (dict *readerDict) numWords() int64 {
    
    	return int64(dict.itabsOffset() + len(dict.itabs))
    
    }
    
    // varType returns the type of dict's runtime dictionary variable.
    func (dict *readerDict) varType() *types.Type {
    	return types.NewArray(types.Types[types.TUINTPTR], dict.numWords())
    }
    
    
    func (r *reader) funcargs(fn *ir.Func) {
    	sig := fn.Nname.Type()
    
    	if recv := sig.Recv(); recv != nil {
    		r.funcarg(recv, recv.Sym, ir.PPARAM)
    	}
    	for _, param := range sig.Params().FieldSlice() {
    		r.funcarg(param, param.Sym, ir.PPARAM)
    	}
    
    	for i, param := range sig.Results().FieldSlice() {
    		sym := types.OrigSym(param.Sym)
    
    		if sym == nil || sym.IsBlank() {
    			prefix := "~r"
    			if r.inlCall != nil {
    				prefix = "~R"
    			} else if sym != nil {
    				prefix = "~b"
    			}
    			sym = typecheck.LookupNum(prefix, i)
    		}
    
    		r.funcarg(param, sym, ir.PPARAMOUT)
    	}
    }
    
    func (r *reader) funcarg(param *types.Field, sym *types.Sym, ctxt ir.Class) {
    	if sym == nil {
    		assert(ctxt == ir.PPARAM)
    		if r.inlCall != nil {
    			r.inlvars.Append(ir.BlankNode)
    		}
    		return
    	}
    
    
    	name := ir.NewNameAt(r.inlPos(param.Pos), sym)
    
    	setType(name, param.Type)
    
    	r.addLocal(name, ctxt)
    
    	if r.inlCall == nil {
    		if !r.funarghack {
    			param.Sym = sym
    			param.Nname = name
    		}
    	} else {
    		if ctxt == ir.PPARAMOUT {
    			r.retvars.Append(name)
    		} else {
    			r.inlvars.Append(name)
    		}
    	}
    }
    
    func (r *reader) addLocal(name *ir.Name, ctxt ir.Class) {
    	assert(ctxt == ir.PAUTO || ctxt == ir.PPARAM || ctxt == ir.PPARAMOUT)
    
    
    	if name.Sym().Name == dictParamName {
    		r.dictParam = name
    	} else {
    
    			r.Sync(pkgbits.SyncAddLocal)
    			if r.p.SyncMarkers() {
    				want := r.Int()
    				if have := len(r.locals); have != want {
    					base.FatalfAt(name.Pos(), "locals table has desynced")
    				}
    
    		r.locals = append(r.locals, name)
    
    	}
    
    	name.SetUsed(true)
    
    	// TODO(mdempsky): Move earlier.
    	if ir.IsBlank(name) {
    		return
    	}
    
    	if r.inlCall != nil {
    		if ctxt == ir.PAUTO {
    			name.SetInlLocal(true)
    		} else {
    			name.SetInlFormal(true)
    			ctxt = ir.PAUTO
    		}
    
    		// TODO(mdempsky): Rethink this hack.
    		if strings.HasPrefix(name.Sym().Name, "~") || base.Flag.GenDwarfInl == 0 {
    			name.SetPos(r.inlCall.Pos())
    			name.SetInlFormal(false)
    			name.SetInlLocal(false)
    		}
    	}
    
    	name.Class = ctxt
    	name.Curfn = r.curfn
    
    	r.curfn.Dcl = append(r.curfn.Dcl, name)
    
    	if ctxt == ir.PAUTO {
    		name.SetFrameOffset(0)
    	}
    }
    
    func (r *reader) useLocal() *ir.Name {
    
    	r.Sync(pkgbits.SyncUseObjLocal)
    	if r.Bool() {
    		return r.locals[r.Len()]
    
    	return r.closureVars[r.Len()]
    
    	r.Sync(pkgbits.SyncOpenScope)
    
    	pos := r.pos()
    
    	if base.Flag.Dwarf {
    		r.scopeVars = append(r.scopeVars, len(r.curfn.Dcl))
    		r.marker.Push(pos)
    	}
    }
    
    func (r *reader) closeScope() {
    
    	r.Sync(pkgbits.SyncCloseScope)
    
    	r.lastCloseScopePos = r.pos()
    
    	r.closeAnotherScope()
    }
    
    // closeAnotherScope is like closeScope, but it reuses the same mark
    // position as the last closeScope call. This is useful for "for" and
    // "if" statements, as their implicit blocks always end at the same
    // position as an explicit block.
    func (r *reader) closeAnotherScope() {
    
    	r.Sync(pkgbits.SyncCloseAnotherScope)
    
    
    	if base.Flag.Dwarf {
    		scopeVars := r.scopeVars[len(r.scopeVars)-1]
    		r.scopeVars = r.scopeVars[:len(r.scopeVars)-1]
    
    
    		// Quirkish: noder decides which scopes to keep before
    		// typechecking, whereas incremental typechecking during IR
    		// construction can result in new autotemps being allocated. To
    		// produce identical output, we ignore autotemps here for the
    		// purpose of deciding whether to retract the scope.
    		//
    		// This is important for net/http/fcgi, because it contains:
    		//
    		//	var body io.ReadCloser
    		//	if len(content) > 0 {
    		//		body, req.pw = io.Pipe()
    		//	} else { … }
    		//
    		// Notably, io.Pipe is inlinable, and inlining it introduces a ~R0
    		// variable at the call site.
    		//
    		// Noder does not preserve the scope where the io.Pipe() call
    		// resides, because it doesn't contain any declared variables in
    		// source. So the ~R0 variable ends up being assigned to the
    		// enclosing scope instead.
    		//
    		// However, typechecking this assignment also introduces
    		// autotemps, because io.Pipe's results need conversion before
    		// they can be assigned to their respective destination variables.
    		//
    		// TODO(mdempsky): We should probably just keep all scopes, and
    		// let dwarfgen take care of pruning them instead.
    		retract := true
    		for _, n := range r.curfn.Dcl[scopeVars:] {
    			if !n.AutoTemp() {
    				retract = false
    				break
    			}
    		}
    
    		if retract {
    
    			// no variables were declared in this scope, so we can retract it.
    			r.marker.Unpush()
    		} else {
    			r.marker.Pop(r.lastCloseScopePos)
    		}
    	}
    }
    
    // @@@ Statements
    
    func (r *reader) stmt() ir.Node {
    	switch stmts := r.stmts(); len(stmts) {
    	case 0:
    		return nil
    	case 1:
    		return stmts[0]
    	default:
    		return ir.NewBlockStmt(stmts[0].Pos(), stmts)
    	}
    }
    
    func (r *reader) stmts() []ir.Node {
    
    		tag := codeStmt(r.Code(pkgbits.SyncStmt1))
    
    			r.Sync(pkgbits.SyncStmtsEnd)
    
    			return res
    		}
    
    		if n := r.stmt1(tag, &res); n != nil {
    
    		}
    	}
    }
    
    func (r *reader) stmt1(tag codeStmt, out *ir.Nodes) ir.Node {
    	var label *types.Sym
    	if n := len(*out); n > 0 {
    		if ls, ok := (*out)[n-1].(*ir.LabelStmt); ok {
    			label = ls.Label
    		}
    	}
    
    	switch tag {
    	default:
    		panic("unexpected statement")
    
    	case stmtAssign:
    		pos := r.pos()
    
    
    		if len(rhs) == 0 {
    			for _, name := range names {
    				as := ir.NewAssignStmt(pos, name, nil)
    				as.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, name))
    
    			}
    			return nil
    		}
    
    		if len(lhs) == 1 && len(rhs) == 1 {
    			n := ir.NewAssignStmt(pos, lhs[0], rhs[0])
    			n.Def = r.initDefn(n, names)
    			return n
    		}
    
    		n := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs)
    		n.Def = r.initDefn(n, names)
    		return n
    
    	case stmtAssignOp:
    		op := r.op()
    		lhs := r.expr()
    		pos := r.pos()
    		rhs := r.expr()
    		return ir.NewAssignOpStmt(pos, op, lhs, rhs)
    
    	case stmtIncDec:
    		op := r.op()
    		lhs := r.expr()
    		pos := r.pos()
    		n := ir.NewAssignOpStmt(pos, op, lhs, ir.NewBasicLit(pos, one))
    		n.IncDec = true
    		return n
    
    	case stmtBlock:
    		out.Append(r.blockStmt()...)
    		return nil
    
    	case stmtBranch:
    		pos := r.pos()
    		op := r.op()
    		sym := r.optLabel()
    		return ir.NewBranchStmt(pos, op, sym)
    
    	case stmtCall:
    		pos := r.pos()
    		op := r.op()
    		call := r.expr()
    		return ir.NewGoDeferStmt(pos, op, call)
    
    	case stmtExpr:
    		return r.expr()
    
    	case stmtFor:
    		return r.forStmt(label)
    
    	case stmtIf:
    		return r.ifStmt()
    
    	case stmtLabel:
    		pos := r.pos()
    		sym := r.label()
    		return ir.NewLabelStmt(pos, sym)
    
    	case stmtReturn:
    		pos := r.pos()
    
    		return ir.NewReturnStmt(pos, results)
    
    	case stmtSelect:
    		return r.selectStmt(label)
    
    	case stmtSend:
    		pos := r.pos()
    		ch := r.expr()
    		value := r.expr()
    		return ir.NewSendStmt(pos, ch, value)
    
    	case stmtSwitch:
    		return r.switchStmt(label)
    	}
    }
    
    func (r *reader) assignList() ([]*ir.Name, []ir.Node) {
    
    	lhs := make([]ir.Node, r.Len())
    
    	var names []*ir.Name
    
    	for i := range lhs {
    
    		expr, def := r.assign()
    		lhs[i] = expr
    		if def {
    			names = append(names, expr.(*ir.Name))
    
    // assign returns an assignee expression. It also reports whether the
    // returned expression is a newly declared variable.
    func (r *reader) assign() (ir.Node, bool) {
    	switch tag := codeAssign(r.Code(pkgbits.SyncAssign)); tag {
    	default:
    		panic("unhandled assignee expression")
    
    	case assignBlank:
    		return typecheck.AssignExpr(ir.BlankNode), false
    
    	case assignDef:
    		pos := r.pos()
    
    		_, sym := r.localIdent()
    		typ := r.typ()
    
    		name := ir.NewNameAt(pos, sym)
    		setType(name, typ)
    		r.addLocal(name, ir.PAUTO)
    		return name, true
    
    	case assignExpr:
    		return r.expr(), false
    	}
    }
    
    
    func (r *reader) blockStmt() []ir.Node {
    
    	r.Sync(pkgbits.SyncBlockStmt)
    
    	r.openScope()
    	stmts := r.stmts()
    	r.closeScope()
    	return stmts
    }
    
    func (r *reader) forStmt(label *types.Sym) ir.Node {
    
    	r.Sync(pkgbits.SyncForStmt)
    
    		rang := ir.NewRangeStmt(pos, nil, nil, nil, nil)
    		rang.Label = label
    
    		if len(lhs) >= 1 {
    			rang.Key = lhs[0]
    			if len(lhs) >= 2 {
    				rang.Value = lhs[1]
    			}
    		}
    		rang.Def = r.initDefn(rang, names)
    
    		rang.X = r.expr()
    		if rang.X.Type().IsMap() {
    			rang.RType = r.rtype(pos)
    		}
    
    		if rang.Key != nil && !ir.IsBlank(rang.Key) {
    			rang.KeyTypeWord, rang.KeySrcRType = r.convRTTI(pos)
    		}
    		if rang.Value != nil && !ir.IsBlank(rang.Value) {
    			rang.ValueTypeWord, rang.ValueSrcRType = r.convRTTI(pos)
    
    		rang.Body = r.blockStmt()
    		r.closeAnotherScope()
    
    
    		return rang
    	}
    
    	pos := r.pos()
    	init := r.stmt()
    
    	post := r.stmt()
    	body := r.blockStmt()
    	r.closeAnotherScope()
    
    	stmt := ir.NewForStmt(pos, init, cond, post, body)
    	stmt.Label = label
    	return stmt
    }
    
    func (r *reader) ifStmt() ir.Node {
    
    	r.Sync(pkgbits.SyncIfStmt)
    
    	r.openScope()
    	pos := r.pos()
    	init := r.stmts()
    	cond := r.expr()
    	then := r.blockStmt()
    	els := r.stmts()
    	n := ir.NewIfStmt(pos, cond, then, els)
    	n.SetInit(init)
    	r.closeAnotherScope()
    	return n
    }
    
    func (r *reader) selectStmt(label *types.Sym) ir.Node {
    
    	r.Sync(pkgbits.SyncSelectStmt)
    
    	clauses := make([]*ir.CommClause, r.Len())
    
    	for i := range clauses {
    		if i > 0 {
    			r.closeScope()
    		}
    		r.openScope()
    
    		pos := r.pos()
    		comm := r.stmt()
    		body := r.stmts()
    
    
    		// "case i = <-c: ..." may require an implicit conversion (e.g.,
    		// see fixedbugs/bug312.go). Currently, typecheck throws away the
    		// implicit conversion and relies on it being reinserted later,
    		// but that would lose any explicit RTTI operands too. To preserve
    		// RTTI, we rewrite this as "case tmp := <-c: i = tmp; ...".
    		if as, ok := comm.(*ir.AssignStmt); ok && as.Op() == ir.OAS && !as.Def {
    			if conv, ok := as.Y.(*ir.ConvExpr); ok && conv.Op() == ir.OCONVIFACE {
    				base.AssertfAt(conv.Implicit(), conv.Pos(), "expected implicit conversion: %v", conv)
    
    				recv := conv.X
    				base.AssertfAt(recv.Op() == ir.ORECV, recv.Pos(), "expected receive expression: %v", recv)
    
    				tmp := r.temp(pos, recv.Type())
    
    				// Replace comm with `tmp := <-c`.
    				tmpAs := ir.NewAssignStmt(pos, tmp, recv)
    				tmpAs.Def = true
    				tmpAs.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, tmp))
    				comm = tmpAs
    
    				// Change original assignment to `i = tmp`, and prepend to body.
    				conv.X = tmp
    				body = append([]ir.Node{as}, body...)
    			}
    		}
    
    
    		// multiExpr will have desugared a comma-ok receive expression
    		// into a separate statement. However, the rest of the compiler
    		// expects comm to be the OAS2RECV statement itself, so we need to
    		// shuffle things around to fit that pattern.
    		if as2, ok := comm.(*ir.AssignListStmt); ok && as2.Op() == ir.OAS2 {
    			init := ir.TakeInit(as2.Rhs[0])
    			base.AssertfAt(len(init) == 1 && init[0].Op() == ir.OAS2RECV, as2.Pos(), "unexpected assignment: %+v", as2)
    
    			comm = init[0]
    			body = append([]ir.Node{as2}, body...)
    		}
    
    
    		clauses[i] = ir.NewCommStmt(pos, comm, body)
    	}
    	if len(clauses) > 0 {
    		r.closeScope()
    	}
    	n := ir.NewSelectStmt(pos, clauses)
    	n.Label = label
    	return n
    }
    
    func (r *reader) switchStmt(label *types.Sym) ir.Node {
    
    	r.Sync(pkgbits.SyncSwitchStmt)
    
    
    	r.openScope()
    	pos := r.pos()
    	init := r.stmt()
    
    	var ident *ir.Ident
    	var iface *types.Type
    
    		tag = ir.NewTypeSwitchGuard(pos, ident, x)
    	} else {
    
    	clauses := make([]*ir.CaseClause, r.Len())
    
    	for i := range clauses {
    		if i > 0 {
    			r.closeScope()
    		}
    		r.openScope()
    
    		pos := r.pos()
    
    		if iface != nil {
    			cases = make([]ir.Node, r.Len())
    			if len(cases) == 0 {
    				cases = nil // TODO(mdempsky): Unclear if this matters.
    			}