cmd/internal/obj/mips: support NOFRAME

This passes toolstash -cmp with one exception: assembly functions that
were declared with a frame size of -4 (or -8) used to record
locals=0xfffffffffffffffc in the object file and now record
locals=0x0. This doesn't affect anything.

Change-Id: I0d15e81770e54222ae329ce4496da06016736771
Reviewed-on: https://go-review.googlesource.com/92041
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/src/cmd/internal/obj/mips/obj0.go b/src/cmd/internal/obj/mips/obj0.go
index ae9d128..477f553 100644
--- a/src/cmd/internal/obj/mips/obj0.go
+++ b/src/cmd/internal/obj/mips/obj0.go
@@ -139,6 +139,19 @@
 
 	p := c.cursym.Func.Text
 	textstksiz := p.To.Offset
+	if textstksiz == -ctxt.FixedFrameSize() {
+		// Historical way to mark NOFRAME.
+		p.From.Sym.Set(obj.AttrNoFrame, true)
+		textstksiz = 0
+	}
+	if textstksiz < 0 {
+		c.ctxt.Diag("negative frame size %d - did you mean NOFRAME?", textstksiz)
+	}
+	if p.From.Sym.NoFrame() {
+		if textstksiz != 0 {
+			c.ctxt.Diag("NOFRAME functions must have a frame size of 0, not %d", textstksiz)
+		}
+	}
 
 	c.cursym.Func.Args = p.To.Val.(int32)
 	c.cursym.Func.Locals = int32(textstksiz)
@@ -278,15 +291,42 @@
 		o := p.As
 		switch o {
 		case obj.ATEXT:
-			autosize = int32(textstksiz + ctxt.FixedFrameSize())
-			if (p.Mark&LEAF != 0) && autosize <= int32(ctxt.FixedFrameSize()) {
-				autosize = 0
-			} else if autosize&4 != 0 && c.ctxt.Arch.Family == sys.MIPS64 {
+			autosize = int32(textstksiz)
+
+			if p.Mark&LEAF != 0 && autosize == 0 {
+				// A leaf function with no locals has no frame.
+				p.From.Sym.Set(obj.AttrNoFrame, true)
+			}
+
+			if !p.From.Sym.NoFrame() {
+				// If there is a stack frame at all, it includes
+				// space to save the LR.
+				autosize += int32(c.ctxt.FixedFrameSize())
+			}
+
+			if autosize&4 != 0 && c.ctxt.Arch.Family == sys.MIPS64 {
 				autosize += 4
 			}
 
+			if autosize == 0 && c.cursym.Func.Text.Mark&LEAF == 0 {
+				if c.cursym.Func.Text.From.Sym.NoSplit() {
+					if ctxt.Debugvlog {
+						ctxt.Logf("save suppressed in: %s\n", c.cursym.Name)
+					}
+
+					c.cursym.Func.Text.Mark |= LEAF
+				}
+			}
+
 			p.To.Offset = int64(autosize) - ctxt.FixedFrameSize()
 
+			if c.cursym.Func.Text.Mark&LEAF != 0 {
+				c.cursym.Set(obj.AttrLeaf, true)
+				if p.From.Sym.NoFrame() {
+					break
+				}
+			}
+
 			if !p.From.Sym.NoSplit() {
 				p = c.stacksplit(p, autosize) // emit split check
 			}
@@ -316,22 +356,9 @@
 				q.To.Type = obj.TYPE_REG
 				q.To.Reg = REGSP
 				q.Spadj = +autosize
-			} else if c.cursym.Func.Text.Mark&LEAF == 0 {
-				if c.cursym.Func.Text.From.Sym.NoSplit() {
-					if ctxt.Debugvlog {
-						ctxt.Logf("save suppressed in: %s\n", c.cursym.Name)
-					}
-
-					c.cursym.Func.Text.Mark |= LEAF
-				}
 			}
 
-			if c.cursym.Func.Text.Mark&LEAF != 0 {
-				c.cursym.Set(obj.AttrLeaf, true)
-				break
-			}
-
-			if c.cursym.Func.Text.From.Sym.Wrapper() {
+			if c.cursym.Func.Text.From.Sym.Wrapper() && c.cursym.Func.Text.Mark&LEAF == 0 {
 				// if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame
 				//
 				//	MOV	g_panic(g), R1
@@ -346,6 +373,9 @@
 				//
 				// The NOP is needed to give the jumps somewhere to land.
 				// It is a liblink NOP, not an mips NOP: it encodes to 0 instruction bytes.
+				//
+				// We don't generate this for leafs because that means the wrapped
+				// function was inlined into the wrapper.
 
 				q = obj.Appendp(q, newprog)
 
@@ -604,11 +634,6 @@
 }
 
 func (c *ctxt0) stacksplit(p *obj.Prog, framesize int32) *obj.Prog {
-	// Leaf function with no frame is effectively NOSPLIT.
-	if framesize == 0 {
-		return p
-	}
-
 	var mov, add, sub obj.As
 
 	if c.ctxt.Arch.Family == sys.MIPS64 {