// cmd/9l/noop.c, cmd/9l/pass.c, cmd/9l/span.c from Vita Nuova.
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2008 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2008 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package ppc64

import (
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
	"cmd/internal/sys"
	"internal/abi"
	"internal/buildcfg"
	"log"
	"math"
	"math/bits"
	"strings"
)

// Test if this value can encoded as a mask for
// li -1, rx; rlic rx,rx,sh,mb.
// Masks can also extend from the msb and wrap to
// the lsb too. That is, the valid masks are 32 bit strings
// of the form: 0..01..10..0 or 1..10..01..1 or 1...1
func isPPC64DoublewordRotateMask(v64 int64) bool {
	// Isolate rightmost 1 (if none 0) and add.
	v := uint64(v64)
	vp := (v & -v) + v
	// Likewise, for the wrapping case.
	vn := ^v
	vpn := (vn & -vn) + vn
	return (v&vp == 0 || vn&vpn == 0) && v != 0
}

// Encode a doubleword rotate mask into mb (mask begin) and
// me (mask end, inclusive). Note, POWER ISA labels bits in
// big endian order.
func encodePPC64RLDCMask(mask int64) (mb, me int) {
	// Determine boundaries and then decode them
	mb = bits.LeadingZeros64(uint64(mask))
	me = 64 - bits.TrailingZeros64(uint64(mask))
	mbn := bits.LeadingZeros64(^uint64(mask))
	men := 64 - bits.TrailingZeros64(^uint64(mask))
	// Check for a wrapping mask (e.g bits at 0 and 63)
	if mb == 0 && me == 64 {
		// swap the inverted values
		mb, me = men, mbn
	}
	// Note, me is inclusive.
	return mb, me - 1
}

// Is this a symbol which should never have a TOC prologue generated?
// These are special functions which should not have a TOC regeneration
// prologue.
func isNOTOCfunc(name string) bool {
	switch {
	case name == "runtime.duffzero":
		return true
	case name == "runtime.duffcopy":
		return true
	case strings.HasPrefix(name, "runtime.elf_"):
		return true
	default:
		return false
	}
}

// Try converting FMOVD/FMOVS to XXSPLTIDP. If it is converted,
// return true.
func convertFMOVtoXXSPLTIDP(p *obj.Prog) bool {
	if p.From.Type != obj.TYPE_FCONST || buildcfg.GOPPC64 < 10 {
		return false
	}
	v := p.From.Val.(float64)
	if float64(float32(v)) != v {
		return false
	}
	// Secondly, is this value a normal value?
	ival := int64(math.Float32bits(float32(v)))
	isDenorm := ival&0x7F800000 == 0 && ival&0x007FFFFF != 0
	if !isDenorm {
		p.As = AXXSPLTIDP
		p.From.Type = obj.TYPE_CONST
		p.From.Offset = ival
		// Convert REG_Fx into equivalent REG_VSx
		p.To.Reg = REG_VS0 + (p.To.Reg & 31)
	}
	return !isDenorm
}

func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
	p.From.Class = 0
	p.To.Class = 0

	c := ctxt9{ctxt: ctxt, newprog: newprog}

	// Rewrite BR/BL to symbol as TYPE_BRANCH.
	switch p.As {
	case ABR,
		ABL,
		obj.ARET,
		obj.ADUFFZERO,
		obj.ADUFFCOPY:
		if p.To.Sym != nil {
			p.To.Type = obj.TYPE_BRANCH
		}
	}

	// Rewrite float constants to values stored in memory.
	switch p.As {
	case AFMOVS:
		if p.From.Type == obj.TYPE_FCONST && !convertFMOVtoXXSPLTIDP(p) {
			f32 := float32(p.From.Val.(float64))
			p.From.Type = obj.TYPE_MEM
			p.From.Sym = ctxt.Float32Sym(f32)
			p.From.Name = obj.NAME_EXTERN
			p.From.Offset = 0
		}

	case AFMOVD:
		if p.From.Type == obj.TYPE_FCONST {
			f64 := p.From.Val.(float64)
			// Constant not needed in memory for float +/- 0
			if f64 != 0 && !convertFMOVtoXXSPLTIDP(p) {
				p.From.Type = obj.TYPE_MEM
				p.From.Sym = ctxt.Float64Sym(f64)
				p.From.Name = obj.NAME_EXTERN
				p.From.Offset = 0
			}
		}

	case AMOVW, AMOVWZ:
		// Note, for backwards compatibility, MOVW $const, Rx and MOVWZ $const, Rx are identical.
		if p.From.Type == obj.TYPE_CONST && p.From.Offset != 0 && p.From.Offset&0xFFFF == 0 {
			// This is a constant shifted 16 bits to the left, convert it to ADDIS/ORIS $const,...
			p.As = AADDIS
			// Use ORIS for large constants which should not be sign extended.
			if p.From.Offset >= 0x80000000 {
				p.As = AORIS
			}
			p.Reg = REG_R0
			p.From.Offset >>= 16
		}

	case AMOVD:
		// Skip this opcode if it is not a constant load.
		if p.From.Type != obj.TYPE_CONST || p.From.Name != obj.NAME_NONE || p.From.Reg != 0 {
			break
		}

		// 32b constants (signed and unsigned) can be generated via 1 or 2 instructions. They can be assembled directly.
		isS32 := int64(int32(p.From.Offset)) == p.From.Offset
		isU32 := uint64(uint32(p.From.Offset)) == uint64(p.From.Offset)
		// If prefixed instructions are supported, a 34b signed constant can be generated by one pli instruction.
		isS34 := pfxEnabled && (p.From.Offset<<30)>>30 == p.From.Offset

		// Try converting MOVD $const,Rx into ADDIS/ORIS $s32>>16,R0,Rx
		switch {
		case isS32 && p.From.Offset&0xFFFF == 0 && p.From.Offset != 0:
			p.As = AADDIS
			p.From.Offset >>= 16
			p.Reg = REG_R0

		case isU32 && p.From.Offset&0xFFFF == 0 && p.From.Offset != 0:
			p.As = AORIS
			p.From.Offset >>= 16
			p.Reg = REG_R0

		case isS32 || isU32 || isS34:
			// The assembler can generate this opcode in 1 (on Power10) or 2 opcodes.

		// Otherwise, see if the large constant can be generated with 2 instructions. If not, load it from memory.
		default:
			// Is this a shifted 16b constant? If so, rewrite it to avoid a creating and loading a constant.
			val := p.From.Offset
			shift := bits.TrailingZeros64(uint64(val))
			mask := int64(0xFFFF) << shift
			if val&mask == val || (val>>(shift+16) == -1 && (val>>shift)<<shift == val) {
				// Rewrite this value into MOVD $const>>shift, Rto; SLD $shift, Rto
				q := obj.Appendp(p, c.newprog)
				q.As = ASLD
				q.From.SetConst(int64(shift))
				q.To = p.To
				p.From.Offset >>= shift
				p = q
			} else if isPPC64DoublewordRotateMask(val) {
				// This constant is a mask value, generate MOVD $-1, Rto; RLDIC Rto, ^me, mb, Rto
				mb, me := encodePPC64RLDCMask(val)
				q := obj.Appendp(p, c.newprog)
				q.As = ARLDC
				q.AddRestSourceConst((^int64(me)) & 0x3F)
				q.AddRestSourceConst(int64(mb))
				q.From = p.To
				q.To = p.To
				p.From.Offset = -1
				p = q
			} else {
				// Load the constant from memory.
				p.From.Type = obj.TYPE_MEM
				p.From.Sym = ctxt.Int64Sym(p.From.Offset)
				p.From.Name = obj.NAME_EXTERN
				p.From.Offset = 0
			}
		}
	}

	switch p.As {
	// Rewrite SUB constants into ADD.
	case ASUBC:
		if p.From.Type == obj.TYPE_CONST {
			p.From.Offset = -p.From.Offset
			p.As = AADDC
		}

	case ASUBCCC:
		if p.From.Type == obj.TYPE_CONST {
			p.From.Offset = -p.From.Offset
			p.As = AADDCCC
		}

	case ASUB:
		if p.From.Type != obj.TYPE_CONST {
			break
		}
		// Rewrite SUB $const,... into ADD $-const,...
		p.From.Offset = -p.From.Offset
		p.As = AADD
		// This is now an ADD opcode, try simplifying it below.
		fallthrough

	// Rewrite ADD/OR/XOR/ANDCC $const,... forms into ADDIS/ORIS/XORIS/ANDISCC
	case AADD:
		// Don't rewrite if this is not adding a constant value, or is not an int32
		if p.From.Type != obj.TYPE_CONST || p.From.Offset == 0 || int64(int32(p.From.Offset)) != p.From.Offset {
			break
		}
		if p.From.Offset&0xFFFF == 0 {
			// The constant can be added using ADDIS
			p.As = AADDIS
			p.From.Offset >>= 16
		} else if buildcfg.GOPPC64 >= 10 {
			// Let the assembler generate paddi for large constants.
			break
		} else if (p.From.Offset < -0x8000 && int64(int32(p.From.Offset)) == p.From.Offset) || (p.From.Offset > 0xFFFF && p.From.Offset < 0x7FFF8000) {
			// For a constant x, 0xFFFF (UINT16_MAX) < x < 0x7FFF8000 or -0x80000000 (INT32_MIN) <= x < -0x8000 (INT16_MIN)
			// This is not done for 0x7FFF < x < 0x10000; the assembler will generate a slightly faster instruction sequence.
			//
			// The constant x can be rewritten as ADDIS + ADD as follows:
			//     ADDIS $x>>16 + (x>>15)&1, rX, rY
			//     ADD   $int64(int16(x)), rY, rY
			// The range is slightly asymmetric as 0x7FFF8000 and above overflow the sign bit, whereas for
			// negative values, this would happen with constant values between -1 and -32768 which can
			// assemble into a single addi.
			is := p.From.Offset>>16 + (p.From.Offset>>15)&1
			i := int64(int16(p.From.Offset))
			p.As = AADDIS
			p.From.Offset = is
			q := obj.Appendp(p, c.newprog)
			q.As = AADD
			q.From.SetConst(i)
			q.Reg = p.To.Reg
			q.To = p.To
			p = q
		}
	case AOR:
		if p.From.Type == obj.TYPE_CONST && uint64(p.From.Offset)&0xFFFFFFFF0000FFFF == 0 && p.From.Offset != 0 {
			p.As = AORIS
			p.From.Offset >>= 16
		}
	case AXOR:
		if p.From.Type == obj.TYPE_CONST && uint64(p.From.Offset)&0xFFFFFFFF0000FFFF == 0 && p.From.Offset != 0 {
			p.As = AXORIS
			p.From.Offset >>= 16
		}
	case AANDCC:
		if p.From.Type == obj.TYPE_CONST && uint64(p.From.Offset)&0xFFFFFFFF0000FFFF == 0 && p.From.Offset != 0 {
			p.As = AANDISCC
			p.From.Offset >>= 16
		}

	// To maintain backwards compatibility, we accept some 4 argument usage of
	// several opcodes which was likely not intended, but did work. These are not
	// added to optab to avoid the chance this behavior might be used with newer
	// instructions.
	//
	// Rewrite argument ordering like "ADDEX R3, $3, R4, R5" into
	//                                "ADDEX R3, R4, $3, R5"
	case AVSHASIGMAW, AVSHASIGMAD, AADDEX, AXXSLDWI, AXXPERMDI:
		if len(p.RestArgs) == 2 && p.Reg == 0 && p.RestArgs[0].Addr.Type == obj.TYPE_CONST && p.RestArgs[1].Addr.Type == obj.TYPE_REG {
			p.Reg = p.RestArgs[1].Addr.Reg
			p.RestArgs = p.RestArgs[:1]
		}
	}

	if c.ctxt.Headtype == objabi.Haix {
		c.rewriteToUseTOC(p)
	} else if c.ctxt.Flag_dynlink {
		c.rewriteToUseGot(p)
	}
}

// Rewrite p, if necessary, to access a symbol using its TOC anchor.
// This code is for AIX only.
func (c *ctxt9) rewriteToUseTOC(p *obj.Prog) {
	if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ACALL || p.As == obj.ARET || p.As == obj.AJMP {
		return
	}

	if p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO {
		// ADUFFZERO/ADUFFCOPY is considered as an ABL except in dynamic
		// link where it should be an indirect call.
		if !c.ctxt.Flag_dynlink {
			return
		}
		//     ADUFFxxx $offset
		// becomes
		//     MOVD runtime.duffxxx@TOC, R12
		//     ADD $offset, R12
		//     MOVD R12, LR
		//     BL (LR)
		var sym *obj.LSym
		if p.As == obj.ADUFFZERO {
			sym = c.ctxt.Lookup("runtime.duffzero")
		} else {
			sym = c.ctxt.Lookup("runtime.duffcopy")
		}
		// Retrieve or create the TOC anchor.
		symtoc := c.ctxt.LookupInit("TOC."+sym.Name, func(s *obj.LSym) {
			s.Type = objabi.SDATA
			s.Set(obj.AttrDuplicateOK, true)
			s.Set(obj.AttrStatic, true)
			c.ctxt.Data = append(c.ctxt.Data, s)
			s.WriteAddr(c.ctxt, 0, 8, sym, 0)
		})

		offset := p.To.Offset
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Name = obj.NAME_TOCREF
		p.From.Sym = symtoc
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R12
		p.To.Name = obj.NAME_NONE
		p.To.Offset = 0
		p.To.Sym = nil
		p1 := obj.Appendp(p, c.newprog)
		p1.As = AADD
		p1.From.Type = obj.TYPE_CONST
		p1.From.Offset = offset
		p1.To.Type = obj.TYPE_REG
		p1.To.Reg = REG_R12
		p2 := obj.Appendp(p1, c.newprog)
		p2.As = AMOVD
		p2.From.Type = obj.TYPE_REG
		p2.From.Reg = REG_R12
		p2.To.Type = obj.TYPE_REG
		p2.To.Reg = REG_LR
		p3 := obj.Appendp(p2, c.newprog)
		p3.As = obj.ACALL
		p3.To.Type = obj.TYPE_REG
		p3.To.Reg = REG_LR
	}

	var source *obj.Addr
	if p.From.Name == obj.NAME_EXTERN || p.From.Name == obj.NAME_STATIC {
		if p.From.Type == obj.TYPE_ADDR {
			if p.As == ADWORD {
				// ADWORD $sym doesn't need TOC anchor
				return
			}
			if p.As != AMOVD {
				c.ctxt.Diag("do not know how to handle TYPE_ADDR in %v", p)
				return
			}
			if p.To.Type != obj.TYPE_REG {
				c.ctxt.Diag("do not know how to handle LEAQ-type insn to non-register in %v", p)
				return
			}
		} else if p.From.Type != obj.TYPE_MEM {
			c.ctxt.Diag("do not know how to handle %v without TYPE_MEM", p)
			return
		}
		source = &p.From

	} else if p.To.Name == obj.NAME_EXTERN || p.To.Name == obj.NAME_STATIC {
		if p.To.Type != obj.TYPE_MEM {
			c.ctxt.Diag("do not know how to handle %v without TYPE_MEM", p)
			return
		}
		if source != nil {
			c.ctxt.Diag("cannot handle symbols on both sides in %v", p)
			return
		}
		source = &p.To
	} else {
		return

	}

	if source.Sym == nil {
		c.ctxt.Diag("do not know how to handle nil symbol in %v", p)
		return
	}

	if source.Sym.Type == objabi.STLSBSS {
		return
	}

	// Retrieve or create the TOC anchor.
	symtoc := c.ctxt.LookupInit("TOC."+source.Sym.Name, func(s *obj.LSym) {
		s.Type = objabi.SDATA
		s.Set(obj.AttrDuplicateOK, true)
		s.Set(obj.AttrStatic, true)
		c.ctxt.Data = append(c.ctxt.Data, s)
		s.WriteAddr(c.ctxt, 0, 8, source.Sym, 0)
	})

	if source.Type == obj.TYPE_ADDR {
		// MOVD $sym, Rx becomes MOVD symtoc, Rx
		// MOVD $sym+<off>, Rx becomes MOVD symtoc, Rx; ADD <off>, Rx
		p.From.Type = obj.TYPE_MEM
		p.From.Sym = symtoc
		p.From.Name = obj.NAME_TOCREF

		if p.From.Offset != 0 {
			q := obj.Appendp(p, c.newprog)
			q.As = AADD
			q.From.Type = obj.TYPE_CONST
			q.From.Offset = p.From.Offset
			p.From.Offset = 0
			q.To = p.To
		}
		return

	}

	// MOVx sym, Ry becomes MOVD symtoc, REGTMP; MOVx (REGTMP), Ry
	// MOVx Ry, sym becomes MOVD symtoc, REGTMP; MOVx Ry, (REGTMP)
	// An addition may be inserted between the two MOVs if there is an offset.

	q := obj.Appendp(p, c.newprog)
	q.As = AMOVD
	q.From.Type = obj.TYPE_MEM
	q.From.Sym = symtoc
	q.From.Name = obj.NAME_TOCREF
	q.To.Type = obj.TYPE_REG
	q.To.Reg = REGTMP

	q = obj.Appendp(q, c.newprog)
	q.As = p.As
	q.From = p.From
	q.To = p.To
	if p.From.Name != obj.NAME_NONE {
		q.From.Type = obj.TYPE_MEM
		q.From.Reg = REGTMP
		q.From.Name = obj.NAME_NONE
		q.From.Sym = nil
	} else if p.To.Name != obj.NAME_NONE {
		q.To.Type = obj.TYPE_MEM
		q.To.Reg = REGTMP
		q.To.Name = obj.NAME_NONE
		q.To.Sym = nil
	} else {
		c.ctxt.Diag("unreachable case in rewriteToUseTOC with %v", p)
	}

	obj.Nopout(p)
}

// Rewrite p, if necessary, to access global data via the global offset table.
func (c *ctxt9) rewriteToUseGot(p *obj.Prog) {
	if p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO {
		//     ADUFFxxx $offset
		// becomes
		//     MOVD runtime.duffxxx@GOT, R12
		//     ADD $offset, R12
		//     MOVD R12, LR
		//     BL (LR)
		var sym *obj.LSym
		if p.As == obj.ADUFFZERO {
			sym = c.ctxt.LookupABI("runtime.duffzero", obj.ABIInternal)
		} else {
			sym = c.ctxt.LookupABI("runtime.duffcopy", obj.ABIInternal)
		}
		offset := p.To.Offset
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Name = obj.NAME_GOTREF
		p.From.Sym = sym
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R12
		p.To.Name = obj.NAME_NONE
		p.To.Offset = 0
		p.To.Sym = nil
		p1 := obj.Appendp(p, c.newprog)
		p1.As = AADD
		p1.From.Type = obj.TYPE_CONST
		p1.From.Offset = offset
		p1.To.Type = obj.TYPE_REG
		p1.To.Reg = REG_R12
		p2 := obj.Appendp(p1, c.newprog)
		p2.As = AMOVD
		p2.From.Type = obj.TYPE_REG
		p2.From.Reg = REG_R12
		p2.To.Type = obj.TYPE_REG
		p2.To.Reg = REG_LR
		p3 := obj.Appendp(p2, c.newprog)
		p3.As = obj.ACALL
		p3.To.Type = obj.TYPE_REG
		p3.To.Reg = REG_LR
	}

	// We only care about global data: NAME_EXTERN means a global
	// symbol in the Go sense, and p.Sym.Local is true for a few
	// internally defined symbols.
	if p.From.Type == obj.TYPE_ADDR && p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
		// MOVD $sym, Rx becomes MOVD sym@GOT, Rx
		// MOVD $sym+<off>, Rx becomes MOVD sym@GOT, Rx; ADD <off>, Rx
		if p.As != AMOVD {
			c.ctxt.Diag("do not know how to handle TYPE_ADDR in %v with -dynlink", p)
		}
		if p.To.Type != obj.TYPE_REG {
			c.ctxt.Diag("do not know how to handle LEAQ-type insn to non-register in %v with -dynlink", p)
		}
		p.From.Type = obj.TYPE_MEM
		p.From.Name = obj.NAME_GOTREF
		if p.From.Offset != 0 {
			q := obj.Appendp(p, c.newprog)
			q.As = AADD
			q.From.Type = obj.TYPE_CONST
			q.From.Offset = p.From.Offset
			q.To = p.To
			p.From.Offset = 0
		}
	}
	if p.GetFrom3() != nil && p.GetFrom3().Name == obj.NAME_EXTERN {
		c.ctxt.Diag("don't know how to handle %v with -dynlink", p)
	}
	var source *obj.Addr
	// MOVx sym, Ry becomes MOVD sym@GOT, REGTMP; MOVx (REGTMP), Ry
	// MOVx Ry, sym becomes MOVD sym@GOT, REGTMP; MOVx Ry, (REGTMP)
	// An addition may be inserted between the two MOVs if there is an offset.
	if p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
		if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
			c.ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p)
		}
		source = &p.From
	} else if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
		source = &p.To
	} else {
		return
	}
	if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ACALL || p.As == obj.ARET || p.As == obj.AJMP {
		return
	}
	if source.Sym.Type == objabi.STLSBSS {
		return
	}
	if source.Type != obj.TYPE_MEM {
		c.ctxt.Diag("don't know how to handle %v with -dynlink", p)
	}
	p1 := obj.Appendp(p, c.newprog)
	p2 := obj.Appendp(p1, c.newprog)

	p1.As = AMOVD
	p1.From.Type = obj.TYPE_MEM
	p1.From.Sym = source.Sym
	p1.From.Name = obj.NAME_GOTREF
	p1.To.Type = obj.TYPE_REG
	p1.To.Reg = REGTMP

	p2.As = p.As
	p2.From = p.From
	p2.To = p.To
	if p.From.Name == obj.NAME_EXTERN {
		p2.From.Reg = REGTMP
		p2.From.Name = obj.NAME_NONE
		p2.From.Sym = nil
	} else if p.To.Name == obj.NAME_EXTERN {
		p2.To.Reg = REGTMP
		p2.To.Name = obj.NAME_NONE
		p2.To.Sym = nil
	} else {
		return
	}
	obj.Nopout(p)
}

func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
	// TODO(minux): add morestack short-cuts with small fixed frame-size.
	if cursym.Func().Text == nil || cursym.Func().Text.Link == nil {
		return
	}

	c := ctxt9{ctxt: ctxt, cursym: cursym, newprog: newprog}

	p := c.cursym.Func().Text
	textstksiz := p.To.Offset
	if textstksiz == -8 {
		// Compatibility hack.
		p.From.Sym.Set(obj.AttrNoFrame, true)
		textstksiz = 0
	}
	if textstksiz%8 != 0 {
		c.ctxt.Diag("frame size %d not a multiple of 8", 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)

	/*
	 * find leaf subroutines
	 * expand RET
	 * expand BECOME pseudo
	 */

	var q *obj.Prog
	var q1 *obj.Prog
	for p := c.cursym.Func().Text; p != nil; p = p.Link {
		switch p.As {
		/* too hard, just leave alone */
		case obj.ATEXT:
			q = p

			p.Mark |= LABEL | LEAF | SYNC
			if p.Link != nil {
				p.Link.Mark |= LABEL
			}

		case ANOR:
			q = p
			if p.To.Type == obj.TYPE_REG {
				if p.To.Reg == REGZERO {
					p.Mark |= LABEL | SYNC
				}
			}

		case ALWAR,
			ALBAR,
			ASTBCCC,
			ASTWCCC,
			AEIEIO,
			AICBI,
			AISYNC,
			ATLBIE,
			ATLBIEL,
			ASLBIA,
			ASLBIE,
			ASLBMFEE,
			ASLBMFEV,
			ASLBMTE,
			ADCBF,
			ADCBI,
			ADCBST,
			ADCBT,
			ADCBTST,
			ADCBZ,
			ASYNC,
			ATLBSYNC,
			APTESYNC,
			ALWSYNC,
			ATW,
			AWORD,
			ARFI,
			ARFCI,
			ARFID,
			AHRFID:
			q = p
			p.Mark |= LABEL | SYNC
			continue

		case AMOVW, AMOVWZ, AMOVD:
			q = p
			if p.From.Reg >= REG_SPECIAL || p.To.Reg >= REG_SPECIAL {
				p.Mark |= LABEL | SYNC
			}
			continue

		case AFABS,
			AFABSCC,
			AFADD,
			AFADDCC,
			AFCTIW,
			AFCTIWCC,
			AFCTIWZ,
			AFCTIWZCC,
			AFDIV,
			AFDIVCC,
			AFMADD,
			AFMADDCC,
			AFMOVD,
			AFMOVDU,
			/* case AFMOVDS: */
			AFMOVS,
			AFMOVSU,

			/* case AFMOVSD: */
			AFMSUB,
			AFMSUBCC,
			AFMUL,
			AFMULCC,
			AFNABS,
			AFNABSCC,
			AFNEG,
			AFNEGCC,
			AFNMADD,
			AFNMADDCC,
			AFNMSUB,
			AFNMSUBCC,
			AFRSP,
			AFRSPCC,
			AFSUB,
			AFSUBCC:
			q = p

			p.Mark |= FLOAT
			continue

		case ABL,
			ABCL,
			obj.ADUFFZERO,
			obj.ADUFFCOPY:
			c.cursym.Func().Text.Mark &^= LEAF
			fallthrough

		case ABC,
			ABEQ,
			ABGE,
			ABGT,
			ABLE,
			ABLT,
			ABNE,
			ABR,
			ABVC,
			ABVS:
			p.Mark |= BRANCH
			q = p
			q1 = p.To.Target()
			if q1 != nil {
				// NOPs are not removed due to #40689.

				if q1.Mark&LEAF == 0 {
					q1.Mark |= LABEL
				}
			} else {
				p.Mark |= LABEL
			}
			q1 = p.Link
			if q1 != nil {
				q1.Mark |= LABEL
			}
			continue

		case AFCMPO, AFCMPU:
			q = p
			p.Mark |= FCMP | FLOAT
			continue

		case obj.ARET:
			q = p
			if p.Link != nil {
				p.Link.Mark |= LABEL
			}
			continue

		case obj.ANOP:
			// NOPs are not removed due to
			// #40689
			continue

		default:
			q = p
			continue
		}
	}

	autosize := int32(0)
	var p1 *obj.Prog
	var p2 *obj.Prog
	for p := c.cursym.Func().Text; p != nil; p = p.Link {
		o := p.As
		switch o {
		case obj.ATEXT:
			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.Arch.FixedFrameSize)
			}

			if p.Mark&LEAF != 0 && autosize < abi.StackSmall {
				// A leaf function with a small stack can be marked
				// NOSPLIT, avoiding a stack check.
				p.From.Sym.Set(obj.AttrNoSplit, true)
			}

			p.To.Offset = int64(autosize)

			q = p

			if NeedTOCpointer(c.ctxt) && !isNOTOCfunc(c.cursym.Name) {
				// When compiling Go into PIC, without PCrel support, all functions must start
				// with instructions to load the TOC pointer into r2:
				//
				//	addis r2, r12, .TOC.-func@ha
				//	addi r2, r2, .TOC.-func@l+4
				//
				// We could probably skip this prologue in some situations
				// but it's a bit subtle. However, it is both safe and
				// necessary to leave the prologue off duffzero and
				// duffcopy as we rely on being able to jump to a specific
				// instruction offset for them.
				//
				// These are AWORDS because there is no (afaict) way to
				// generate the addis instruction except as part of the
				// load of a large constant, and in that case there is no
				// way to use r12 as the source.
				//
				// Note that the same condition is tested in
				// putelfsym in cmd/link/internal/ld/symtab.go
				// where we set the st_other field to indicate
				// the presence of these instructions.
				q = obj.Appendp(q, c.newprog)
				q.As = AWORD
				q.Pos = p.Pos
				q.From.Type = obj.TYPE_CONST
				q.From.Offset = 0x3c4c0000
				q = obj.Appendp(q, c.newprog)
				q.As = AWORD
				q.Pos = p.Pos
				q.From.Type = obj.TYPE_CONST
				q.From.Offset = 0x38420000
				c.cursym.AddRel(c.ctxt, obj.Reloc{
					Type: objabi.R_ADDRPOWER_PCREL,
					Off:  0,
					Siz:  8,
					Sym:  c.ctxt.Lookup(".TOC."),
				})
			}

			if !c.cursym.Func().Text.From.Sym.NoSplit() {
				q = c.stacksplit(q, autosize) // emit split check
			}

			if autosize != 0 {
				var prologueEnd *obj.Prog
				// Save the link register and update the SP.  MOVDU is used unless
				// the frame size is too large.  The link register must be saved
				// even for non-empty leaf functions so that traceback works.
				if autosize >= -BIG && autosize <= BIG {
					// Use MOVDU to adjust R1 when saving R31, if autosize is small.
					q = obj.Appendp(q, c.newprog)
					q.As = AMOVD
					q.Pos = p.Pos
					q.From.Type = obj.TYPE_REG
					q.From.Reg = REG_LR
					q.To.Type = obj.TYPE_REG
					q.To.Reg = REGTMP
					prologueEnd = q

					q = obj.Appendp(q, c.newprog)
					q.As = AMOVDU
					q.Pos = p.Pos
					q.From.Type = obj.TYPE_REG
					q.From.Reg = REGTMP
					q.To.Type = obj.TYPE_MEM
					q.To.Offset = int64(-autosize)
					q.To.Reg = REGSP
					q.Spadj = autosize
				} else {
					// Frame size is too large for a MOVDU instruction.
					// Store link register before decrementing SP, so if a signal comes
					// during the execution of the function prologue, the traceback
					// code will not see a half-updated stack frame.
					// This sequence is not async preemptible, as if we open a frame
					// at the current SP, it will clobber the saved LR.
					q = obj.Appendp(q, c.newprog)
					q.As = AMOVD
					q.Pos = p.Pos
					q.From.Type = obj.TYPE_REG
					q.From.Reg = REG_LR
					q.To.Type = obj.TYPE_REG
					q.To.Reg = REG_R29 // REGTMP may be used to synthesize large offset in the next instruction

					q = c.ctxt.StartUnsafePoint(q, c.newprog)

					q = obj.Appendp(q, c.newprog)
					q.As = AMOVD
					q.Pos = p.Pos
					q.From.Type = obj.TYPE_REG
					q.From.Reg = REG_R29
					q.To.Type = obj.TYPE_MEM
					q.To.Offset = int64(-autosize)
					q.To.Reg = REGSP

					prologueEnd = q

					q = obj.Appendp(q, c.newprog)
					q.As = AADD
					q.Pos = p.Pos
					q.From.Type = obj.TYPE_CONST
					q.From.Offset = int64(-autosize)
					q.To.Type = obj.TYPE_REG
					q.To.Reg = REGSP
					q.Spadj = +autosize

					q = c.ctxt.EndUnsafePoint(q, c.newprog, -1)
				}
				prologueEnd.Pos = prologueEnd.Pos.WithXlogue(src.PosPrologueEnd)
			} else if c.cursym.Func().Text.Mark&LEAF == 0 {
				// A very few functions that do not return to their caller
				// (e.g. gogo) are not identified as leaves but still have
				// no frame.
				c.cursym.Func().Text.Mark |= LEAF
			}

			if c.cursym.Func().Text.Mark&LEAF != 0 {
				c.cursym.Set(obj.AttrLeaf, true)
				break
			}

			if NeedTOCpointer(c.ctxt) {
				q = obj.Appendp(q, c.newprog)
				q.As = AMOVD
				q.Pos = p.Pos
				q.From.Type = obj.TYPE_REG
				q.From.Reg = REG_R2
				q.To.Type = obj.TYPE_MEM
				q.To.Reg = REGSP
				q.To.Offset = 24
			}

			if c.cursym.Func().Text.From.Sym.Wrapper() {
				// if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame
				//
				//	MOVD g_panic(g), R22
				//	CMP R22, $0
				//	BEQ end
				//	MOVD panic_argp(R22), R23
				//	ADD $(autosize+8), R1, R24
				//	CMP R23, R24
				//	BNE end
				//	ADD $8, R1, R25
				//	MOVD R25, panic_argp(R22)
				// end:
				//	NOP
				//
				// The NOP is needed to give the jumps somewhere to land.
				// It is a liblink NOP, not a ppc64 NOP: it encodes to 0 instruction bytes.

				q = obj.Appendp(q, c.newprog)

				q.As = AMOVD
				q.From.Type = obj.TYPE_MEM
				q.From.Reg = REGG
				q.From.Offset = 4 * int64(c.ctxt.Arch.PtrSize) // G.panic
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REG_R22

				q = obj.Appendp(q, c.newprog)
				q.As = ACMP
				q.From.Type = obj.TYPE_REG
				q.From.Reg = REG_R22
				q.To.Type = obj.TYPE_CONST
				q.To.Offset = 0

				q = obj.Appendp(q, c.newprog)
				q.As = ABEQ
				q.To.Type = obj.TYPE_BRANCH
				p1 = q

				q = obj.Appendp(q, c.newprog)
				q.As = AMOVD
				q.From.Type = obj.TYPE_MEM
				q.From.Reg = REG_R22
				q.From.Offset = 0 // Panic.argp
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REG_R23

				q = obj.Appendp(q, c.newprog)
				q.As = AADD
				q.From.Type = obj.TYPE_CONST
				q.From.Offset = int64(autosize) + c.ctxt.Arch.FixedFrameSize
				q.Reg = REGSP
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REG_R24

				q = obj.Appendp(q, c.newprog)
				q.As = ACMP
				q.From.Type = obj.TYPE_REG
				q.From.Reg = REG_R23
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REG_R24

				q = obj.Appendp(q, c.newprog)
				q.As = ABNE
				q.To.Type = obj.TYPE_BRANCH
				p2 = q

				q = obj.Appendp(q, c.newprog)
				q.As = AADD
				q.From.Type = obj.TYPE_CONST
				q.From.Offset = c.ctxt.Arch.FixedFrameSize
				q.Reg = REGSP
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REG_R25

				q = obj.Appendp(q, c.newprog)
				q.As = AMOVD
				q.From.Type = obj.TYPE_REG
				q.From.Reg = REG_R25
				q.To.Type = obj.TYPE_MEM
				q.To.Reg = REG_R22
				q.To.Offset = 0 // Panic.argp

				q = obj.Appendp(q, c.newprog)

				q.As = obj.ANOP
				p1.To.SetTarget(q)
				p2.To.SetTarget(q)
			}

		case obj.ARET:
			if p.From.Type == obj.TYPE_CONST {
				c.ctxt.Diag("using BECOME (%v) is not supported!", p)
				break
			}

			retTarget := p.To.Sym

			if c.cursym.Func().Text.Mark&LEAF != 0 {
				if autosize == 0 {
					p.As = ABR
					p.From = obj.Addr{}
					if retTarget == nil {
						p.To.Type = obj.TYPE_REG
						p.To.Reg = REG_LR
					} else {
						p.To.Type = obj.TYPE_BRANCH
						p.To.Sym = retTarget
					}
					p.Mark |= BRANCH
					break
				}

				p.As = AADD
				p.From.Type = obj.TYPE_CONST
				p.From.Offset = int64(autosize)
				p.To.Type = obj.TYPE_REG
				p.To.Reg = REGSP
				p.Spadj = -autosize

				q = c.newprog()
				q.As = ABR
				q.Pos = p.Pos
				if retTarget == nil {
					q.To.Type = obj.TYPE_REG
					q.To.Reg = REG_LR
				} else {
					q.To.Type = obj.TYPE_BRANCH
					q.To.Sym = retTarget
				}
				q.Mark |= BRANCH
				q.Spadj = +autosize

				q.Link = p.Link
				p.Link = q
				break
			}

			p.As = AMOVD
			p.From.Type = obj.TYPE_MEM
			p.From.Offset = 0
			p.From.Reg = REGSP
			p.To.Type = obj.TYPE_REG
			p.To.Reg = REGTMP

			q = c.newprog()
			q.As = AMOVD
			q.Pos = p.Pos
			q.From.Type = obj.TYPE_REG
			q.From.Reg = REGTMP
			q.To.Type = obj.TYPE_REG
			q.To.Reg = REG_LR

			q.Link = p.Link
			p.Link = q
			p = q

			if false {
				// Debug bad returns
				q = c.newprog()

				q.As = AMOVD
				q.Pos = p.Pos
				q.From.Type = obj.TYPE_MEM
				q.From.Offset = 0
				q.From.Reg = REGTMP
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REGTMP

				q.Link = p.Link
				p.Link = q
				p = q
			}
			prev := p
			if autosize != 0 {
				q = c.newprog()
				q.As = AADD
				q.Pos = p.Pos
				q.From.Type = obj.TYPE_CONST
				q.From.Offset = int64(autosize)
				q.To.Type = obj.TYPE_REG
				q.To.Reg = REGSP
				q.Spadj = -autosize

				q.Link = p.Link
				prev.Link = q
				prev = q
			}

			q1 = c.newprog()
			q1.As = ABR
			q1.Pos = p.Pos
			if retTarget == nil {
				q1.To.Type = obj.TYPE_REG
				q1.To.Reg = REG_LR
			} else {
				q1.To.Type = obj.TYPE_BRANCH
				q1.To.Sym = retTarget
			}
			q1.Mark |= BRANCH
			q1.Spadj = +autosize

			q1.Link = q.Link
			prev.Link = q1
		case AADD:
			if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.From.Type == obj.TYPE_CONST {
				p.Spadj = int32(-p.From.Offset)
			}
		case AMOVDU:
			if p.To.Type == obj.TYPE_MEM && p.To.Reg == REGSP {
				p.Spadj = int32(-p.To.Offset)
			}
			if p.From.Type == obj.TYPE_MEM && p.From.Reg == REGSP {
				p.Spadj = int32(-p.From.Offset)
			}
		case obj.AGETCALLERPC:
			if cursym.Leaf() {
				/* MOVD LR, Rd */
				p.As = AMOVD
				p.From.Type = obj.TYPE_REG
				p.From.Reg = REG_LR
			} else {
				/* MOVD (RSP), Rd */
				p.As = AMOVD
				p.From.Type = obj.TYPE_MEM
				p.From.Reg = REGSP
			}
		}

		if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 && p.As != ACMPU {
			f := c.cursym.Func()
			if f.FuncFlag&abi.FuncFlagSPWrite == 0 {
				c.cursym.Func().FuncFlag |= abi.FuncFlagSPWrite
				if ctxt.Debugvlog || !ctxt.IsAsm {
					ctxt.Logf("auto-SPWRITE: %s %v\n", c.cursym.Name, p)
					if !ctxt.IsAsm {
						ctxt.Diag("invalid auto-SPWRITE in non-assembly")
						ctxt.DiagFlush()
						log.Fatalf("bad SPWRITE")
					}
				}
			}
		}
	}
}

/*
// instruction scheduling

	if(debug['Q'] == 0)
		return;

	curtext = nil;
	q = nil;	// p - 1
	q1 = firstp;	// top of block
	o = 0;		// count of instructions
	for(p = firstp; p != nil; p = p1) {
		p1 = p->link;
		o++;
		if(p->mark & NOSCHED){
			if(q1 != p){
				sched(q1, q);
			}
			for(; p != nil; p = p->link){
				if(!(p->mark & NOSCHED))
					break;
				q = p;
			}
			p1 = p;
			q1 = p;
			o = 0;
			continue;
		}
		if(p->mark & (LABEL|SYNC)) {
			if(q1 != p)
				sched(q1, q);
			q1 = p;
			o = 1;
		}
		if(p->mark & (BRANCH|SYNC)) {
			sched(q1, p);
			q1 = p1;
			o = 0;
		}
		if(o >= NSCHED) {
			sched(q1, p);
			q1 = p1;
			o = 0;
		}
		q = p;
	}
*/
func (c *ctxt9) stacksplit(p *obj.Prog, framesize int32) *obj.Prog {
	if c.ctxt.Flag_maymorestack != "" {
		if c.ctxt.Flag_shared || c.ctxt.Flag_dynlink {
			// See the call to morestack for why these are
			// complicated to support.
			c.ctxt.Diag("maymorestack with -shared or -dynlink is not supported")
		}

		// Spill arguments. This has to happen before we open
		// any more frame space.
		p = c.cursym.Func().SpillRegisterArgs(p, c.newprog)

		// Save LR and REGCTXT
		frameSize := 8 + c.ctxt.Arch.FixedFrameSize

		// MOVD LR, REGTMP
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REG_LR
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REGTMP
		// MOVDU REGTMP, -16(SP)
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVDU
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REGTMP
		p.To.Type = obj.TYPE_MEM
		p.To.Offset = -frameSize
		p.To.Reg = REGSP
		p.Spadj = int32(frameSize)

		// MOVD REGCTXT, 8(SP)
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REGCTXT
		p.To.Type = obj.TYPE_MEM
		p.To.Offset = 8
		p.To.Reg = REGSP

		// BL maymorestack
		p = obj.Appendp(p, c.newprog)
		p.As = ABL
		p.To.Type = obj.TYPE_BRANCH
		// See ../x86/obj6.go
		p.To.Sym = c.ctxt.LookupABI(c.ctxt.Flag_maymorestack, c.cursym.ABI())

		// Restore LR and REGCTXT

		// MOVD 8(SP), REGCTXT
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Offset = 8
		p.From.Reg = REGSP
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REGCTXT

		// MOVD 0(SP), REGTMP
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Offset = 0
		p.From.Reg = REGSP
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REGTMP

		// MOVD REGTMP, LR
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REGTMP
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_LR

		// ADD $16, SP
		p = obj.Appendp(p, c.newprog)
		p.As = AADD
		p.From.Type = obj.TYPE_CONST
		p.From.Offset = frameSize
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REGSP
		p.Spadj = -int32(frameSize)

		// Unspill arguments.
		p = c.cursym.Func().UnspillRegisterArgs(p, c.newprog)
	}

	// save entry point, but skipping the two instructions setting R2 in shared mode and maymorestack
	startPred := p

	// MOVD	g_stackguard(g), R22
	p = obj.Appendp(p, c.newprog)

	p.As = AMOVD
	p.From.Type = obj.TYPE_MEM
	p.From.Reg = REGG
	p.From.Offset = 2 * int64(c.ctxt.Arch.PtrSize) // G.stackguard0
	if c.cursym.CFunc() {
		p.From.Offset = 3 * int64(c.ctxt.Arch.PtrSize) // G.stackguard1
	}
	p.To.Type = obj.TYPE_REG
	p.To.Reg = REG_R22

	// Mark the stack bound check and morestack call async nonpreemptible.
	// If we get preempted here, when resumed the preemption request is
	// cleared, but we'll still call morestack, which will double the stack
	// unnecessarily. See issue #35470.
	p = c.ctxt.StartUnsafePoint(p, c.newprog)

	var q *obj.Prog
	if framesize <= abi.StackSmall {
		// small stack: SP < stackguard
		//	CMP	stackguard, SP
		p = obj.Appendp(p, c.newprog)

		p.As = ACMPU
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REG_R22
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REGSP
	} else {
		// large stack: SP-framesize < stackguard-StackSmall
		offset := int64(framesize) - abi.StackSmall
		if framesize > abi.StackBig {
			// Such a large stack we need to protect against underflow.
			// The runtime guarantees SP > objabi.StackBig, but
			// framesize is large enough that SP-framesize may
			// underflow, causing a direct comparison with the
			// stack guard to incorrectly succeed. We explicitly
			// guard against underflow.
			//
			//	CMPU	SP, $(framesize-StackSmall)
			//	BLT	label-of-call-to-morestack
			if offset <= 0xffff {
				p = obj.Appendp(p, c.newprog)
				p.As = ACMPU
				p.From.Type = obj.TYPE_REG
				p.From.Reg = REGSP
				p.To.Type = obj.TYPE_CONST
				p.To.Offset = offset
			} else {
				// Constant is too big for CMPU.
				p = obj.Appendp(p, c.newprog)
				p.As = AMOVD
				p.From.Type = obj.TYPE_CONST
				p.From.Offset = offset
				p.To.Type = obj.TYPE_REG
				p.To.Reg = REG_R23

				p = obj.Appendp(p, c.newprog)
				p.As = ACMPU
				p.From.Type = obj.TYPE_REG
				p.From.Reg = REGSP
				p.To.Type = obj.TYPE_REG
				p.To.Reg = REG_R23
			}

			p = obj.Appendp(p, c.newprog)
			q = p
			p.As = ABLT
			p.To.Type = obj.TYPE_BRANCH
		}

		// Check against the stack guard. We've ensured this won't underflow.
		//	ADD  $-(framesize-StackSmall), SP, R4
		//	CMPU stackguard, R4
		p = obj.Appendp(p, c.newprog)

		p.As = AADD
		p.From.Type = obj.TYPE_CONST
		p.From.Offset = -offset
		p.Reg = REGSP
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R23

		p = obj.Appendp(p, c.newprog)
		p.As = ACMPU
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REG_R22
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R23
	}

	// q1: BLT	done
	p = obj.Appendp(p, c.newprog)
	q1 := p

	p.As = ABLT
	p.To.Type = obj.TYPE_BRANCH

	p = obj.Appendp(p, c.newprog)
	p.As = obj.ANOP // zero-width place holder

	if q != nil {
		q.To.SetTarget(p)
	}

	// Spill the register args that could be clobbered by the
	// morestack code.

	spill := c.cursym.Func().SpillRegisterArgs(p, c.newprog)

	// MOVD LR, R5
	p = obj.Appendp(spill, c.newprog)
	p.As = AMOVD
	p.From.Type = obj.TYPE_REG
	p.From.Reg = REG_LR
	p.To.Type = obj.TYPE_REG
	p.To.Reg = REG_R5

	p = c.ctxt.EmitEntryStackMap(c.cursym, p, c.newprog)

	var morestacksym *obj.LSym
	if c.cursym.CFunc() {
		morestacksym = c.ctxt.Lookup("runtime.morestackc")
	} else if !c.cursym.Func().Text.From.Sym.NeedCtxt() {
		morestacksym = c.ctxt.Lookup("runtime.morestack_noctxt")
	} else {
		morestacksym = c.ctxt.Lookup("runtime.morestack")
	}

	if NeedTOCpointer(c.ctxt) {
		// In PPC64 PIC code, R2 is used as TOC pointer derived from R12
		// which is the address of function entry point when entering
		// the function. We need to preserve R2 across call to morestack.
		// Fortunately, in shared mode, 8(SP) and 16(SP) are reserved in
		// the caller's frame, but not used (0(SP) is caller's saved LR,
		// 24(SP) is caller's saved R2). Use 8(SP) to save this function's R2.
		// MOVD R2, 8(SP)
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REG_R2
		p.To.Type = obj.TYPE_MEM
		p.To.Reg = REGSP
		p.To.Offset = 8
	}

	if c.ctxt.Flag_dynlink {
		// Avoid calling morestack via a PLT when dynamically linking. The
		// PLT stubs generated by the system linker on ppc64le when "std r2,
		// 24(r1)" to save the TOC pointer in their callers stack
		// frame. Unfortunately (and necessarily) morestack is called before
		// the function that calls it sets up its frame and so the PLT ends
		// up smashing the saved TOC pointer for its caller's caller.
		//
		// According to the ABI documentation there is a mechanism to avoid
		// the TOC save that the PLT stub does (put a R_PPC64_TOCSAVE
		// relocation on the nop after the call to morestack) but at the time
		// of writing it is not supported at all by gold and my attempt to
		// use it with ld.bfd caused an internal linker error. So this hack
		// seems preferable.

		// MOVD $runtime.morestack(SB), R12
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Sym = morestacksym
		p.From.Name = obj.NAME_GOTREF
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R12

		// MOVD R12, LR
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_REG
		p.From.Reg = REG_R12
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_LR

		// BL LR
		p = obj.Appendp(p, c.newprog)
		p.As = obj.ACALL
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_LR
	} else {
		// BL	runtime.morestack(SB)
		p = obj.Appendp(p, c.newprog)

		p.As = ABL
		p.To.Type = obj.TYPE_BRANCH
		p.To.Sym = morestacksym
	}

	if NeedTOCpointer(c.ctxt) {
		// MOVD 8(SP), R2
		p = obj.Appendp(p, c.newprog)
		p.As = AMOVD
		p.From.Type = obj.TYPE_MEM
		p.From.Reg = REGSP
		p.From.Offset = 8
		p.To.Type = obj.TYPE_REG
		p.To.Reg = REG_R2
	}

	// The instructions which unspill regs should be preemptible.
	p = c.ctxt.EndUnsafePoint(p, c.newprog, -1)
	unspill := c.cursym.Func().UnspillRegisterArgs(p, c.newprog)

	// BR	start
	p = obj.Appendp(unspill, c.newprog)
	p.As = ABR
	p.To.Type = obj.TYPE_BRANCH
	p.To.SetTarget(startPred.Link)

	// placeholder for q1's jump target
	p = obj.Appendp(p, c.newprog)

	p.As = obj.ANOP // zero-width place holder
	q1.To.SetTarget(p)

	return p
}

// MMA accumulator to/from instructions are slightly ambiguous since
// the argument represents both source and destination, specified as
// an accumulator. It is treated as a unary destination to simplify
// the code generation in ppc64map.
var unaryDst = map[obj.As]bool{
	AXXSETACCZ: true,
	AXXMTACC:   true,
	AXXMFACC:   true,
}

var Linkppc64 = obj.LinkArch{
	Arch:           sys.ArchPPC64,
	Init:           buildop,
	Preprocess:     preprocess,
	Assemble:       span9,
	Progedit:       progedit,
	UnaryDst:       unaryDst,
	DWARFRegisters: PPC64DWARFRegisters,
}

var Linkppc64le = obj.LinkArch{
	Arch:           sys.ArchPPC64LE,
	Init:           buildop,
	Preprocess:     preprocess,
	Assemble:       span9,
	Progedit:       progedit,
	UnaryDst:       unaryDst,
	DWARFRegisters: PPC64DWARFRegisters,
}
