[dev.ssa] cmd/compile: implement ODOTTYPE and OAS2DOTTYPE

Taken over and completed from Josh's change
https://go-review.googlesource.com/#/c/14524/

Change-Id: If5d4f732843cc3e99bd5edda54458f0a8be73e91
Reviewed-on: https://go-review.googlesource.com/14690
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index c053eab..7268a34 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -259,12 +259,17 @@
 func (s *state) Fatalf(msg string, args ...interface{})         { s.config.Fatalf(msg, args...) }
 func (s *state) Unimplementedf(msg string, args ...interface{}) { s.config.Unimplementedf(msg, args...) }
 
-// dummy node for the memory variable
-var memvar = Node{Op: ONAME, Sym: &Sym{Name: "mem"}}
+var (
+	// dummy node for the memory variable
+	memvar = Node{Op: ONAME, Sym: &Sym{Name: "mem"}}
 
-// dummy nodes for temporary variables
-var ptrvar = Node{Op: ONAME, Sym: &Sym{Name: "ptr"}}
-var capvar = Node{Op: ONAME, Sym: &Sym{Name: "cap"}}
+	// dummy nodes for temporary variables
+	ptrvar   = Node{Op: ONAME, Sym: &Sym{Name: "ptr"}}
+	capvar   = Node{Op: ONAME, Sym: &Sym{Name: "cap"}}
+	typVar   = Node{Op: ONAME, Sym: &Sym{Name: "typ"}}
+	idataVar = Node{Op: ONAME, Sym: &Sym{Name: "idata"}}
+	okVar    = Node{Op: ONAME, Sym: &Sym{Name: "ok"}}
+)
 
 // startBlock sets the current block we're generating code in to b.
 func (s *state) startBlock(b *ssa.Block) {
@@ -474,6 +479,12 @@
 	case OPROC:
 		s.call(n.Left, callGo)
 
+	case OAS2DOTTYPE:
+		res, resok := s.dottype(n.Rlist.N, true)
+		s.assign(n.List.N, res, false)
+		s.assign(n.List.Next.N, resok, false)
+		return
+
 	case ODCL:
 		if n.Left.Class&PHEAP == 0 {
 			return
@@ -1471,6 +1482,10 @@
 		s.Unimplementedf("unhandled OCONV %s -> %s", Econv(int(n.Left.Type.Etype), 0), Econv(int(n.Type.Etype), 0))
 		return nil
 
+	case ODOTTYPE:
+		res, _ := s.dottype(n, false)
+		return res
+
 	// binary ops
 	case OLT, OEQ, ONE, OLE, OGE, OGT:
 		a := s.expr(n.Left)
@@ -2723,6 +2738,122 @@
 	return s.variable(n, n.Type)
 }
 
+// ifaceType returns the value for the word containing the type.
+// n is the node for the interface expression.
+// v is the corresponding value.
+func (s *state) ifaceType(n *Node, v *ssa.Value) *ssa.Value {
+	byteptr := Ptrto(Types[TUINT8]) // type used in runtime prototypes for runtime type (*byte)
+
+	if isnilinter(n.Type) {
+		// Have *eface. The type is the first word in the struct.
+		return s.newValue1(ssa.OpITab, byteptr, v)
+	}
+
+	// Have *iface.
+	// The first word in the struct is the *itab.
+	// If the *itab is nil, return 0.
+	// Otherwise, the second word in the *itab is the type.
+
+	tab := s.newValue1(ssa.OpITab, byteptr, v)
+	s.vars[&typVar] = tab
+	isnonnil := s.newValue2(ssa.OpNeqPtr, Types[TBOOL], tab, s.entryNewValue0(ssa.OpConstNil, byteptr))
+	b := s.endBlock()
+	b.Kind = ssa.BlockIf
+	b.Control = isnonnil
+	b.Likely = ssa.BranchLikely
+
+	bLoad := s.f.NewBlock(ssa.BlockPlain)
+	bEnd := s.f.NewBlock(ssa.BlockPlain)
+
+	b.AddEdgeTo(bLoad)
+	b.AddEdgeTo(bEnd)
+	bLoad.AddEdgeTo(bEnd)
+
+	s.startBlock(bLoad)
+	off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(Widthptr), tab)
+	s.vars[&typVar] = s.newValue2(ssa.OpLoad, byteptr, off, s.mem())
+	s.endBlock()
+
+	s.startBlock(bEnd)
+	typ := s.variable(&typVar, byteptr)
+	delete(s.vars, &typVar)
+	return typ
+}
+
+// dottype generates SSA for a type assertion node.
+// commaok indicates whether to panic or return a bool.
+// If commaok is false, resok will be nil.
+func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
+	iface := s.expr(n.Left)
+	typ := s.ifaceType(n.Left, iface)  // actual concrete type
+	target := s.expr(typename(n.Type)) // target type
+	if !isdirectiface(n.Type) {
+		// walk rewrites ODOTTYPE/OAS2DOTTYPE into runtime calls except for this case.
+		Fatalf("dottype needs a direct iface type %s", n.Type)
+	}
+
+	// TODO:  If we have a nonempty interface and its itab field is nil,
+	// then this test is redundant and ifaceType should just branch directly to bFail.
+	cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], typ, target)
+	b := s.endBlock()
+	b.Kind = ssa.BlockIf
+	b.Control = cond
+	b.Likely = ssa.BranchLikely
+
+	byteptr := Ptrto(Types[TUINT8])
+
+	bOk := s.f.NewBlock(ssa.BlockPlain)
+	bFail := s.f.NewBlock(ssa.BlockPlain)
+	b.AddEdgeTo(bOk)
+	b.AddEdgeTo(bFail)
+
+	if !commaok {
+		// on failure, panic by calling panicdottype
+		s.startBlock(bFail)
+
+		spplus1 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(Widthptr), s.sp)
+		spplus2 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(2*Widthptr), s.sp)
+		taddr := s.newValue1A(ssa.OpAddr, byteptr, &ssa.ExternSymbol{byteptr, typenamesym(n.Left.Type)}, s.sb)
+		s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), s.sp, typ, s.mem())       // actual dynamic type
+		s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus1, target, s.mem()) // type we're casting to
+		s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus2, taddr, s.mem())  // static source type
+		call := s.newValue1A(ssa.OpStaticCall, ssa.TypeMem, syslook("panicdottype", 0).Sym, s.mem())
+		s.endBlock()
+		bFail.Kind = ssa.BlockExit
+		bFail.Control = call
+
+		// on success, return idata field
+		s.startBlock(bOk)
+		return s.newValue1(ssa.OpIData, n.Type, iface), nil
+	}
+
+	// commaok is the more complicated case because we have
+	// a control flow merge point.
+	bEnd := s.f.NewBlock(ssa.BlockPlain)
+
+	// type assertion succeeded
+	s.startBlock(bOk)
+	s.vars[&idataVar] = s.newValue1(ssa.OpIData, n.Type, iface)
+	s.vars[&okVar] = s.constBool(true)
+	s.endBlock()
+	bOk.AddEdgeTo(bEnd)
+
+	// type assertion failed
+	s.startBlock(bFail)
+	s.vars[&idataVar] = s.entryNewValue0(ssa.OpConstNil, byteptr)
+	s.vars[&okVar] = s.constBool(false)
+	s.endBlock()
+	bFail.AddEdgeTo(bEnd)
+
+	// merge point
+	s.startBlock(bEnd)
+	res = s.variable(&idataVar, byteptr)
+	resok = s.variable(&okVar, Types[TBOOL])
+	delete(s.vars, &idataVar)
+	delete(s.vars, &okVar)
+	return res, resok
+}
+
 // checkgoto checks that a goto from from to to does not
 // jump into a block or jump over variable declarations.
 // It is a copy of checkgoto in the pre-SSA backend,