s.pushLine(fn.Lineno)
        defer s.popLine()
 
+       if fn.Func.Pragma&CgoUnsafeArgs != 0 {
+               s.cgoUnsafeArgs = true
+       }
        // TODO(khr): build config just once at the start of the compiler binary
 
        ssaExp.log = printssa
        s.decladdrs = map[*Node]*ssa.Value{}
        for _, n := range fn.Func.Dcl {
                switch n.Class {
-               case PPARAM:
+               case PPARAM, PPARAMOUT:
                        aux := s.lookupSymbol(n, &ssa.ArgSymbol{Typ: n.Type, Node: n})
                        s.decladdrs[n] = s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
+                       if n.Class == PPARAMOUT && s.canSSA(n) {
+                               // Save ssa-able PPARAMOUT variables so we can
+                               // store them back to the stack at the end of
+                               // the function.
+                               s.returns = append(s.returns, n)
+                       }
                case PAUTO | PHEAP:
                        // TODO this looks wrong for PAUTO|PHEAP, no vardef, but also no definition
                        aux := s.lookupSymbol(n, &ssa.AutoSymbol{Typ: n.Type, Node: n})
                        s.decladdrs[n] = s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
                case PPARAM | PHEAP, PPARAMOUT | PHEAP:
                // This ends up wrong, have to do it at the PARAM node instead.
-               case PAUTO, PPARAMOUT:
+               case PAUTO:
                        // processed at each use, to prevent Addr coming
                        // before the decl.
                case PFUNC:
 
        // list of FwdRef values.
        fwdRefs []*ssa.Value
+
+       // list of PPARAMOUT (return) variables.  Does not include PPARAM|PHEAP vars.
+       returns []*Node
+
+       cgoUnsafeArgs bool
 }
 
 type funcLine struct {
                s.call(n, callNormal)
                if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class == PFUNC &&
                        (compiling_runtime != 0 && n.Left.Sym.Name == "throw" ||
-                               n.Left.Sym.Pkg == Runtimepkg && (n.Left.Sym.Name == "gopanic" || n.Left.Sym.Name == "selectgo")) {
+                               n.Left.Sym.Pkg == Runtimepkg && (n.Left.Sym.Name == "gopanic" || n.Left.Sym.Name == "selectgo" || n.Left.Sym.Name == "block")) {
                        m := s.mem()
                        b := s.endBlock()
                        b.Kind = ssa.BlockExit
 
        case ORETURN:
                s.stmtList(n.List)
-               s.stmts(s.exitCode)
-               m := s.mem()
-               b := s.endBlock()
-               b.Kind = ssa.BlockRet
-               b.Control = m
+               s.exit()
        case ORETJMP:
                s.stmtList(n.List)
-               s.stmts(s.exitCode)
-               m := s.mem()
-               b := s.endBlock()
-               b.Kind = ssa.BlockRetJmp
+               b := s.exit()
+               b.Kind = ssa.BlockRetJmp // override BlockRet
                b.Aux = n.Left.Sym
-               b.Control = m
 
        case OCONTINUE, OBREAK:
                var op string
                // We only care about liveness info at call sites, so putting the
                // varkill in the store chain is enough to keep it correctly ordered
                // with respect to call ops.
-               if !canSSA(n.Left) {
+               if !s.canSSA(n.Left) {
                        s.vars[&memVar] = s.newValue1A(ssa.OpVarKill, ssa.TypeMem, n.Left, s.mem())
                }
 
        }
 }
 
+// exit processes any code that needs to be generated just before returning.
+// It returns a BlockRet block that ends the control flow.  Its control value
+// will be set to the final memory state.
+func (s *state) exit() *ssa.Block {
+       // Run exit code.  Typically, this code copies heap-allocated PPARAMOUT
+       // variables back to the stack.
+       s.stmts(s.exitCode)
+
+       // Store SSAable PPARAMOUT variables back to stack locations.
+       for _, n := range s.returns {
+               aux := &ssa.ArgSymbol{Typ: n.Type, Node: n}
+               addr := s.newValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
+               val := s.variable(n, n.Type)
+               s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, n, s.mem())
+               s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, n.Type.Size(), addr, val, s.mem())
+               // TODO: if val is ever spilled, we'd like to use the
+               // PPARAMOUT slot for spilling it.  That won't happen
+               // currently.
+       }
+
+       // Do actual return.
+       m := s.mem()
+       b := s.endBlock()
+       b.Kind = ssa.BlockRet
+       b.Control = m
+       return b
+}
+
 type opAndType struct {
        op    Op
        etype EType
                        aux := &ssa.ExternSymbol{n.Type, sym}
                        return s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sb)
                }
-               if canSSA(n) {
+               if s.canSSA(n) {
                        return s.variable(n, n.Type)
                }
                addr := s.addr(n, false)
        }
        t := left.Type
        dowidth(t)
-       if canSSA(left) {
+       if s.canSSA(left) {
                if deref {
                        s.Fatalf("can SSA LHS %s but not RHS %s", left, right)
                }
 
 // canSSA reports whether n is SSA-able.
 // n must be an ONAME (or an ODOT sequence with an ONAME base).
-func canSSA(n *Node) bool {
+func (s *state) canSSA(n *Node) bool {
        for n.Op == ODOT {
                n = n.Left
        }
                return false
        }
        switch n.Class {
-       case PEXTERN, PPARAMOUT, PPARAMREF:
+       case PEXTERN, PPARAMREF:
+               // TODO: maybe treat PPARAMREF with an Arg-like op to read from closure?
                return false
+       case PPARAMOUT:
+               if hasdefer {
+                       // TODO: handle this case?  Named return values must be
+                       // in memory so that the deferred function can see them.
+                       // Maybe do: if !strings.HasPrefix(n.String(), "~") { return false }
+                       return false
+               }
+               if s.cgoUnsafeArgs {
+                       // Cgo effectively takes the address of all result args,
+                       // but the compiler can't see that.
+                       return false
+               }
        }
        if n.Class == PPARAM && n.String() == ".this" {
                // wrappers generated by genwrapper need to update
                // the .this pointer in place.
+               // TODO: treat as a PPARMOUT?
                return false
        }
        return canSSAType(n.Type)
        v.Aux = nil
        if b == s.f.Entry {
                // Live variable at start of function.
-               if canSSA(name) {
+               if s.canSSA(name) {
                        v.Op = ssa.OpArg
                        v.Aux = name
                        return
                p.From.Node = n
                p.From.Sym = Linksym(n.Sym)
                p.From.Offset = off
-               if n.Class == PPARAM {
+               if n.Class == PPARAM || n.Class == PPARAMOUT {
                        p.From.Name = obj.NAME_PARAM
                        p.From.Offset += n.Xoffset
                } else {
                p.To.Node = n
                p.To.Sym = Linksym(n.Sym)
                p.To.Offset = off
-               if n.Class == PPARAM {
+               if n.Class == PPARAM || n.Class == PPARAMOUT {
                        p.To.Name = obj.NAME_PARAM
                        p.To.Offset += n.Xoffset
                } else {