1// Derived from Inferno utils/5c/swt.c
2// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/5c/swt.c
3//
4//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
5//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
6//	Portions Copyright © 1997-1999 Vita Nuova Limited
7//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
8//	Portions Copyright © 2004,2006 Bruce Ellis
9//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
10//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
11//	Portions Copyright © 2009 The Go Authors. All rights reserved.
12//
13// Permission is hereby granted, free of charge, to any person obtaining a copy
14// of this software and associated documentation files (the "Software"), to deal
15// in the Software without restriction, including without limitation the rights
16// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17// copies of the Software, and to permit persons to whom the Software is
18// furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included in
21// all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
26// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29// THE SOFTWARE.
30
31package arm
32
33import (
34	"cmd/internal/obj"
35	"cmd/internal/objabi"
36	"cmd/internal/sys"
37	"internal/buildcfg"
38	"log"
39)
40
41var progedit_tlsfallback *obj.LSym
42
43func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
44	p.From.Class = 0
45	p.To.Class = 0
46
47	c := ctxt5{ctxt: ctxt, newprog: newprog}
48
49	// Rewrite B/BL to symbol as TYPE_BRANCH.
50	switch p.As {
51	case AB, ABL, obj.ADUFFZERO, obj.ADUFFCOPY:
52		if p.To.Type == obj.TYPE_MEM && (p.To.Name == obj.NAME_EXTERN || p.To.Name == obj.NAME_STATIC) && p.To.Sym != nil {
53			p.To.Type = obj.TYPE_BRANCH
54		}
55	}
56
57	// Replace TLS register fetches on older ARM processors.
58	switch p.As {
59	// Treat MRC 15, 0, <reg>, C13, C0, 3 specially.
60	case AMRC:
61		if p.To.Offset&0xffff0fff == 0xee1d0f70 {
62			// Because the instruction might be rewritten to a BL which returns in R0
63			// the register must be zero.
64			if p.To.Offset&0xf000 != 0 {
65				ctxt.Diag("%v: TLS MRC instruction must write to R0 as it might get translated into a BL instruction", p.Line())
66			}
67
68			if buildcfg.GOARM < 7 {
69				// Replace it with BL runtime.read_tls_fallback(SB) for ARM CPUs that lack the tls extension.
70				if progedit_tlsfallback == nil {
71					progedit_tlsfallback = ctxt.Lookup("runtime.read_tls_fallback")
72				}
73
74				// MOVW	LR, R11
75				p.As = AMOVW
76
77				p.From.Type = obj.TYPE_REG
78				p.From.Reg = REGLINK
79				p.To.Type = obj.TYPE_REG
80				p.To.Reg = REGTMP
81
82				// BL	runtime.read_tls_fallback(SB)
83				p = obj.Appendp(p, newprog)
84
85				p.As = ABL
86				p.To.Type = obj.TYPE_BRANCH
87				p.To.Sym = progedit_tlsfallback
88				p.To.Offset = 0
89
90				// MOVW	R11, LR
91				p = obj.Appendp(p, newprog)
92
93				p.As = AMOVW
94				p.From.Type = obj.TYPE_REG
95				p.From.Reg = REGTMP
96				p.To.Type = obj.TYPE_REG
97				p.To.Reg = REGLINK
98				break
99			}
100		}
101
102		// Otherwise, MRC/MCR instructions need no further treatment.
103		p.As = AWORD
104	}
105
106	// Rewrite float constants to values stored in memory.
107	switch p.As {
108	case AMOVF:
109		if p.From.Type == obj.TYPE_FCONST && c.chipfloat5(p.From.Val.(float64)) < 0 && (c.chipzero5(p.From.Val.(float64)) < 0 || p.Scond&C_SCOND != C_SCOND_NONE) {
110			f32 := float32(p.From.Val.(float64))
111			p.From.Type = obj.TYPE_MEM
112			p.From.Sym = ctxt.Float32Sym(f32)
113			p.From.Name = obj.NAME_EXTERN
114			p.From.Offset = 0
115		}
116
117	case AMOVD:
118		if p.From.Type == obj.TYPE_FCONST && c.chipfloat5(p.From.Val.(float64)) < 0 && (c.chipzero5(p.From.Val.(float64)) < 0 || p.Scond&C_SCOND != C_SCOND_NONE) {
119			p.From.Type = obj.TYPE_MEM
120			p.From.Sym = ctxt.Float64Sym(p.From.Val.(float64))
121			p.From.Name = obj.NAME_EXTERN
122			p.From.Offset = 0
123		}
124	}
125
126	if ctxt.Flag_dynlink {
127		c.rewriteToUseGot(p)
128	}
129}
130
131// Rewrite p, if necessary, to access global data via the global offset table.
132func (c *ctxt5) rewriteToUseGot(p *obj.Prog) {
133	if p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO {
134		//     ADUFFxxx $offset
135		// becomes
136		//     MOVW runtime.duffxxx@GOT, R9
137		//     ADD $offset, R9
138		//     CALL (R9)
139		var sym *obj.LSym
140		if p.As == obj.ADUFFZERO {
141			sym = c.ctxt.Lookup("runtime.duffzero")
142		} else {
143			sym = c.ctxt.Lookup("runtime.duffcopy")
144		}
145		offset := p.To.Offset
146		p.As = AMOVW
147		p.From.Type = obj.TYPE_MEM
148		p.From.Name = obj.NAME_GOTREF
149		p.From.Sym = sym
150		p.To.Type = obj.TYPE_REG
151		p.To.Reg = REG_R9
152		p.To.Name = obj.NAME_NONE
153		p.To.Offset = 0
154		p.To.Sym = nil
155		p1 := obj.Appendp(p, c.newprog)
156		p1.As = AADD
157		p1.From.Type = obj.TYPE_CONST
158		p1.From.Offset = offset
159		p1.To.Type = obj.TYPE_REG
160		p1.To.Reg = REG_R9
161		p2 := obj.Appendp(p1, c.newprog)
162		p2.As = obj.ACALL
163		p2.To.Type = obj.TYPE_MEM
164		p2.To.Reg = REG_R9
165		return
166	}
167
168	// We only care about global data: NAME_EXTERN means a global
169	// symbol in the Go sense, and p.Sym.Local is true for a few
170	// internally defined symbols.
171	if p.From.Type == obj.TYPE_ADDR && p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
172		// MOVW $sym, Rx becomes MOVW sym@GOT, Rx
173		// MOVW $sym+<off>, Rx becomes MOVW sym@GOT, Rx; ADD <off>, Rx
174		if p.As != AMOVW {
175			c.ctxt.Diag("do not know how to handle TYPE_ADDR in %v with -dynlink", p)
176		}
177		if p.To.Type != obj.TYPE_REG {
178			c.ctxt.Diag("do not know how to handle LEAQ-type insn to non-register in %v with -dynlink", p)
179		}
180		p.From.Type = obj.TYPE_MEM
181		p.From.Name = obj.NAME_GOTREF
182		if p.From.Offset != 0 {
183			q := obj.Appendp(p, c.newprog)
184			q.As = AADD
185			q.From.Type = obj.TYPE_CONST
186			q.From.Offset = p.From.Offset
187			q.To = p.To
188			p.From.Offset = 0
189		}
190	}
191	if p.GetFrom3() != nil && p.GetFrom3().Name == obj.NAME_EXTERN {
192		c.ctxt.Diag("don't know how to handle %v with -dynlink", p)
193	}
194	var source *obj.Addr
195	// MOVx sym, Ry becomes MOVW sym@GOT, R9; MOVx (R9), Ry
196	// MOVx Ry, sym becomes MOVW sym@GOT, R9; MOVx Ry, (R9)
197	// An addition may be inserted between the two MOVs if there is an offset.
198	if p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
199		if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
200			c.ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p)
201		}
202		source = &p.From
203	} else if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
204		source = &p.To
205	} else {
206		return
207	}
208	if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ACALL || p.As == obj.ARET || p.As == obj.AJMP {
209		return
210	}
211	if source.Sym.Type == objabi.STLSBSS {
212		return
213	}
214	if source.Type != obj.TYPE_MEM {
215		c.ctxt.Diag("don't know how to handle %v with -dynlink", p)
216	}
217	p1 := obj.Appendp(p, c.newprog)
218	p2 := obj.Appendp(p1, c.newprog)
219
220	p1.As = AMOVW
221	p1.From.Type = obj.TYPE_MEM
222	p1.From.Sym = source.Sym
223	p1.From.Name = obj.NAME_GOTREF
224	p1.To.Type = obj.TYPE_REG
225	p1.To.Reg = REG_R9
226
227	p2.As = p.As
228	p2.From = p.From
229	p2.To = p.To
230	if p.From.Name == obj.NAME_EXTERN {
231		p2.From.Reg = REG_R9
232		p2.From.Name = obj.NAME_NONE
233		p2.From.Sym = nil
234	} else if p.To.Name == obj.NAME_EXTERN {
235		p2.To.Reg = REG_R9
236		p2.To.Name = obj.NAME_NONE
237		p2.To.Sym = nil
238	} else {
239		return
240	}
241	obj.Nopout(p)
242}
243
244// Prog.mark
245const (
246	FOLL  = 1 << 0
247	LABEL = 1 << 1
248	LEAF  = 1 << 2
249)
250
251func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
252	autosize := int32(0)
253
254	if cursym.Func().Text == nil || cursym.Func().Text.Link == nil {
255		return
256	}
257
258	c := ctxt5{ctxt: ctxt, cursym: cursym, newprog: newprog}
259
260	p := c.cursym.Func().Text
261	autoffset := int32(p.To.Offset)
262	if autoffset == -4 {
263		// Historical way to mark NOFRAME.
264		p.From.Sym.Set(obj.AttrNoFrame, true)
265		autoffset = 0
266	}
267	if autoffset < 0 || autoffset%4 != 0 {
268		c.ctxt.Diag("frame size %d not 0 or a positive multiple of 4", autoffset)
269	}
270	if p.From.Sym.NoFrame() {
271		if autoffset != 0 {
272			c.ctxt.Diag("NOFRAME functions must have a frame size of 0, not %d", autoffset)
273		}
274	}
275
276	cursym.Func().Locals = autoffset
277	cursym.Func().Args = p.To.Val.(int32)
278
279	/*
280	 * find leaf subroutines
281	 */
282	for p := cursym.Func().Text; p != nil; p = p.Link {
283		switch p.As {
284		case obj.ATEXT:
285			p.Mark |= LEAF
286
287		case ADIV, ADIVU, AMOD, AMODU:
288			cursym.Func().Text.Mark &^= LEAF
289
290		case ABL,
291			ABX,
292			obj.ADUFFZERO,
293			obj.ADUFFCOPY:
294			cursym.Func().Text.Mark &^= LEAF
295		}
296	}
297
298	var q2 *obj.Prog
299	for p := cursym.Func().Text; p != nil; p = p.Link {
300		o := p.As
301		switch o {
302		case obj.ATEXT:
303			autosize = autoffset
304
305			if p.Mark&LEAF != 0 && autosize == 0 {
306				// A leaf function with no locals has no frame.
307				p.From.Sym.Set(obj.AttrNoFrame, true)
308			}
309
310			if !p.From.Sym.NoFrame() {
311				// If there is a stack frame at all, it includes
312				// space to save the LR.
313				autosize += 4
314			}
315
316			if autosize == 0 && cursym.Func().Text.Mark&LEAF == 0 {
317				// A very few functions that do not return to their caller
318				// are not identified as leaves but still have no frame.
319				if ctxt.Debugvlog {
320					ctxt.Logf("save suppressed in: %s\n", cursym.Name)
321				}
322
323				cursym.Func().Text.Mark |= LEAF
324			}
325
326			// FP offsets need an updated p.To.Offset.
327			p.To.Offset = int64(autosize) - 4
328
329			if cursym.Func().Text.Mark&LEAF != 0 {
330				cursym.Set(obj.AttrLeaf, true)
331				if p.From.Sym.NoFrame() {
332					break
333				}
334			}
335
336			if !p.From.Sym.NoSplit() {
337				p = c.stacksplit(p, autosize) // emit split check
338			}
339
340			// MOVW.W		R14,$-autosize(SP)
341			p = obj.Appendp(p, c.newprog)
342
343			p.As = AMOVW
344			p.Scond |= C_WBIT
345			p.From.Type = obj.TYPE_REG
346			p.From.Reg = REGLINK
347			p.To.Type = obj.TYPE_MEM
348			p.To.Offset = int64(-autosize)
349			p.To.Reg = REGSP
350			p.Spadj = autosize
351
352			if cursym.Func().Text.From.Sym.Wrapper() {
353				// if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame
354				//
355				//	MOVW g_panic(g), R1
356				//	CMP  $0, R1
357				//	B.NE checkargp
358				// end:
359				//	NOP
360				// ... function ...
361				// checkargp:
362				//	MOVW panic_argp(R1), R2
363				//	ADD  $(autosize+4), R13, R3
364				//	CMP  R2, R3
365				//	B.NE end
366				//	ADD  $4, R13, R4
367				//	MOVW R4, panic_argp(R1)
368				//	B    end
369				//
370				// The NOP is needed to give the jumps somewhere to land.
371				// It is a liblink NOP, not an ARM NOP: it encodes to 0 instruction bytes.
372
373				p = obj.Appendp(p, newprog)
374				p.As = AMOVW
375				p.From.Type = obj.TYPE_MEM
376				p.From.Reg = REGG
377				p.From.Offset = 4 * int64(ctxt.Arch.PtrSize) // G.panic
378				p.To.Type = obj.TYPE_REG
379				p.To.Reg = REG_R1
380
381				p = obj.Appendp(p, newprog)
382				p.As = ACMP
383				p.From.Type = obj.TYPE_CONST
384				p.From.Offset = 0
385				p.Reg = REG_R1
386
387				// B.NE checkargp
388				bne := obj.Appendp(p, newprog)
389				bne.As = ABNE
390				bne.To.Type = obj.TYPE_BRANCH
391
392				// end: NOP
393				end := obj.Appendp(bne, newprog)
394				end.As = obj.ANOP
395
396				// find end of function
397				var last *obj.Prog
398				for last = end; last.Link != nil; last = last.Link {
399				}
400
401				// MOVW panic_argp(R1), R2
402				mov := obj.Appendp(last, newprog)
403				mov.As = AMOVW
404				mov.From.Type = obj.TYPE_MEM
405				mov.From.Reg = REG_R1
406				mov.From.Offset = 0 // Panic.argp
407				mov.To.Type = obj.TYPE_REG
408				mov.To.Reg = REG_R2
409
410				// B.NE branch target is MOVW above
411				bne.To.SetTarget(mov)
412
413				// ADD $(autosize+4), R13, R3
414				p = obj.Appendp(mov, newprog)
415				p.As = AADD
416				p.From.Type = obj.TYPE_CONST
417				p.From.Offset = int64(autosize) + 4
418				p.Reg = REG_R13
419				p.To.Type = obj.TYPE_REG
420				p.To.Reg = REG_R3
421
422				// CMP R2, R3
423				p = obj.Appendp(p, newprog)
424				p.As = ACMP
425				p.From.Type = obj.TYPE_REG
426				p.From.Reg = REG_R2
427				p.Reg = REG_R3
428
429				// B.NE end
430				p = obj.Appendp(p, newprog)
431				p.As = ABNE
432				p.To.Type = obj.TYPE_BRANCH
433				p.To.SetTarget(end)
434
435				// ADD $4, R13, R4
436				p = obj.Appendp(p, newprog)
437				p.As = AADD
438				p.From.Type = obj.TYPE_CONST
439				p.From.Offset = 4
440				p.Reg = REG_R13
441				p.To.Type = obj.TYPE_REG
442				p.To.Reg = REG_R4
443
444				// MOVW R4, panic_argp(R1)
445				p = obj.Appendp(p, newprog)
446				p.As = AMOVW
447				p.From.Type = obj.TYPE_REG
448				p.From.Reg = REG_R4
449				p.To.Type = obj.TYPE_MEM
450				p.To.Reg = REG_R1
451				p.To.Offset = 0 // Panic.argp
452
453				// B end
454				p = obj.Appendp(p, newprog)
455				p.As = AB
456				p.To.Type = obj.TYPE_BRANCH
457				p.To.SetTarget(end)
458
459				// reset for subsequent passes
460				p = end
461			}
462
463		case obj.ARET:
464			nocache(p)
465			if cursym.Func().Text.Mark&LEAF != 0 {
466				if autosize == 0 {
467					p.As = AB
468					p.From = obj.Addr{}
469					if p.To.Sym != nil { // retjmp
470						p.To.Type = obj.TYPE_BRANCH
471					} else {
472						p.To.Type = obj.TYPE_MEM
473						p.To.Offset = 0
474						p.To.Reg = REGLINK
475					}
476
477					break
478				}
479			}
480
481			p.As = AMOVW
482			p.Scond |= C_PBIT
483			p.From.Type = obj.TYPE_MEM
484			p.From.Offset = int64(autosize)
485			p.From.Reg = REGSP
486			p.To.Type = obj.TYPE_REG
487			p.To.Reg = REGPC
488
489			// If there are instructions following
490			// this ARET, they come from a branch
491			// with the same stackframe, so no spadj.
492
493			if p.To.Sym != nil { // retjmp
494				p.To.Reg = REGLINK
495				q2 = obj.Appendp(p, newprog)
496				q2.As = AB
497				q2.To.Type = obj.TYPE_BRANCH
498				q2.To.Sym = p.To.Sym
499				p.To.Sym = nil
500				p.To.Name = obj.NAME_NONE
501				p = q2
502			}
503
504		case AADD:
505			if p.From.Type == obj.TYPE_CONST && p.From.Reg == 0 && p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP {
506				p.Spadj = int32(-p.From.Offset)
507			}
508
509		case ASUB:
510			if p.From.Type == obj.TYPE_CONST && p.From.Reg == 0 && p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP {
511				p.Spadj = int32(p.From.Offset)
512			}
513
514		case ADIV, ADIVU, AMOD, AMODU:
515			if cursym.Func().Text.From.Sym.NoSplit() {
516				ctxt.Diag("cannot divide in NOSPLIT function")
517			}
518			const debugdivmod = false
519			if debugdivmod {
520				break
521			}
522			if p.From.Type != obj.TYPE_REG {
523				break
524			}
525			if p.To.Type != obj.TYPE_REG {
526				break
527			}
528
529			// Make copy because we overwrite p below.
530			q1 := *p
531			if q1.Reg == REGTMP || q1.Reg == 0 && q1.To.Reg == REGTMP {
532				ctxt.Diag("div already using REGTMP: %v", p)
533			}
534
535			/* MOV m(g),REGTMP */
536			p.As = AMOVW
537			p.Pos = q1.Pos
538			p.From.Type = obj.TYPE_MEM
539			p.From.Reg = REGG
540			p.From.Offset = 6 * 4 // offset of g.m
541			p.Reg = 0
542			p.To.Type = obj.TYPE_REG
543			p.To.Reg = REGTMP
544
545			/* MOV a,m_divmod(REGTMP) */
546			p = obj.Appendp(p, newprog)
547			p.As = AMOVW
548			p.Pos = q1.Pos
549			p.From.Type = obj.TYPE_REG
550			p.From.Reg = q1.From.Reg
551			p.To.Type = obj.TYPE_MEM
552			p.To.Reg = REGTMP
553			p.To.Offset = 8 * 4 // offset of m.divmod
554
555			/* MOV b, R8 */
556			p = obj.Appendp(p, newprog)
557			p.As = AMOVW
558			p.Pos = q1.Pos
559			p.From.Type = obj.TYPE_REG
560			p.From.Reg = q1.Reg
561			if q1.Reg == 0 {
562				p.From.Reg = q1.To.Reg
563			}
564			p.To.Type = obj.TYPE_REG
565			p.To.Reg = REG_R8
566			p.To.Offset = 0
567
568			/* CALL appropriate */
569			p = obj.Appendp(p, newprog)
570			p.As = ABL
571			p.Pos = q1.Pos
572			p.To.Type = obj.TYPE_BRANCH
573			switch o {
574			case ADIV:
575				p.To.Sym = symdiv
576			case ADIVU:
577				p.To.Sym = symdivu
578			case AMOD:
579				p.To.Sym = symmod
580			case AMODU:
581				p.To.Sym = symmodu
582			}
583
584			/* MOV REGTMP, b */
585			p = obj.Appendp(p, newprog)
586			p.As = AMOVW
587			p.Pos = q1.Pos
588			p.From.Type = obj.TYPE_REG
589			p.From.Reg = REGTMP
590			p.From.Offset = 0
591			p.To.Type = obj.TYPE_REG
592			p.To.Reg = q1.To.Reg
593
594		case AMOVW:
595			if (p.Scond&C_WBIT != 0) && p.To.Type == obj.TYPE_MEM && p.To.Reg == REGSP {
596				p.Spadj = int32(-p.To.Offset)
597			}
598			if (p.Scond&C_PBIT != 0) && p.From.Type == obj.TYPE_MEM && p.From.Reg == REGSP && p.To.Reg != REGPC {
599				p.Spadj = int32(-p.From.Offset)
600			}
601			if p.From.Type == obj.TYPE_ADDR && p.From.Reg == REGSP && p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP {
602				p.Spadj = int32(-p.From.Offset)
603			}
604
605		case obj.AGETCALLERPC:
606			if cursym.Leaf() {
607				/* MOVW LR, Rd */
608				p.As = AMOVW
609				p.From.Type = obj.TYPE_REG
610				p.From.Reg = REGLINK
611			} else {
612				/* MOVW (RSP), Rd */
613				p.As = AMOVW
614				p.From.Type = obj.TYPE_MEM
615				p.From.Reg = REGSP
616			}
617		}
618
619		if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 {
620			f := c.cursym.Func()
621			if f.FuncFlag&objabi.FuncFlag_SPWRITE == 0 {
622				c.cursym.Func().FuncFlag |= objabi.FuncFlag_SPWRITE
623				if ctxt.Debugvlog || !ctxt.IsAsm {
624					ctxt.Logf("auto-SPWRITE: %s %v\n", c.cursym.Name, p)
625					if !ctxt.IsAsm {
626						ctxt.Diag("invalid auto-SPWRITE in non-assembly")
627						ctxt.DiagFlush()
628						log.Fatalf("bad SPWRITE")
629					}
630				}
631			}
632		}
633	}
634}
635
636func (c *ctxt5) stacksplit(p *obj.Prog, framesize int32) *obj.Prog {
637	if c.ctxt.Flag_maymorestack != "" {
638		// Save LR and make room for REGCTXT.
639		const frameSize = 8
640		// MOVW.W R14,$-8(SP)
641		p = obj.Appendp(p, c.newprog)
642		p.As = AMOVW
643		p.Scond |= C_WBIT
644		p.From.Type = obj.TYPE_REG
645		p.From.Reg = REGLINK
646		p.To.Type = obj.TYPE_MEM
647		p.To.Offset = -frameSize
648		p.To.Reg = REGSP
649		p.Spadj = frameSize
650
651		// MOVW REGCTXT, 4(SP)
652		p = obj.Appendp(p, c.newprog)
653		p.As = AMOVW
654		p.From.Type = obj.TYPE_REG
655		p.From.Reg = REGCTXT
656		p.To.Type = obj.TYPE_MEM
657		p.To.Offset = 4
658		p.To.Reg = REGSP
659
660		// CALL maymorestack
661		p = obj.Appendp(p, c.newprog)
662		p.As = obj.ACALL
663		p.To.Type = obj.TYPE_BRANCH
664		// See ../x86/obj6.go
665		p.To.Sym = c.ctxt.LookupABI(c.ctxt.Flag_maymorestack, c.cursym.ABI())
666
667		// Restore REGCTXT and LR.
668
669		// MOVW 4(SP), REGCTXT
670		p = obj.Appendp(p, c.newprog)
671		p.As = AMOVW
672		p.From.Type = obj.TYPE_MEM
673		p.From.Offset = 4
674		p.From.Reg = REGSP
675		p.To.Type = obj.TYPE_REG
676		p.To.Reg = REGCTXT
677
678		// MOVW.P 8(SP), R14
679		p.As = AMOVW
680		p.Scond |= C_PBIT
681		p.From.Type = obj.TYPE_MEM
682		p.From.Offset = frameSize
683		p.From.Reg = REGSP
684		p.To.Type = obj.TYPE_REG
685		p.To.Reg = REGLINK
686		p.Spadj = -frameSize
687	}
688
689	// Jump back to here after morestack returns.
690	startPred := p
691
692	// MOVW g_stackguard(g), R1
693	p = obj.Appendp(p, c.newprog)
694
695	p.As = AMOVW
696	p.From.Type = obj.TYPE_MEM
697	p.From.Reg = REGG
698	p.From.Offset = 2 * int64(c.ctxt.Arch.PtrSize) // G.stackguard0
699	if c.cursym.CFunc() {
700		p.From.Offset = 3 * int64(c.ctxt.Arch.PtrSize) // G.stackguard1
701	}
702	p.To.Type = obj.TYPE_REG
703	p.To.Reg = REG_R1
704
705	// Mark the stack bound check and morestack call async nonpreemptible.
706	// If we get preempted here, when resumed the preemption request is
707	// cleared, but we'll still call morestack, which will double the stack
708	// unnecessarily. See issue #35470.
709	p = c.ctxt.StartUnsafePoint(p, c.newprog)
710
711	if framesize <= objabi.StackSmall {
712		// small stack: SP < stackguard
713		//	CMP	stackguard, SP
714		p = obj.Appendp(p, c.newprog)
715
716		p.As = ACMP
717		p.From.Type = obj.TYPE_REG
718		p.From.Reg = REG_R1
719		p.Reg = REGSP
720	} else if framesize <= objabi.StackBig {
721		// large stack: SP-framesize < stackguard-StackSmall
722		//	MOVW $-(framesize-StackSmall)(SP), R2
723		//	CMP stackguard, R2
724		p = obj.Appendp(p, c.newprog)
725
726		p.As = AMOVW
727		p.From.Type = obj.TYPE_ADDR
728		p.From.Reg = REGSP
729		p.From.Offset = -(int64(framesize) - objabi.StackSmall)
730		p.To.Type = obj.TYPE_REG
731		p.To.Reg = REG_R2
732
733		p = obj.Appendp(p, c.newprog)
734		p.As = ACMP
735		p.From.Type = obj.TYPE_REG
736		p.From.Reg = REG_R1
737		p.Reg = REG_R2
738	} else {
739		// Such a large stack we need to protect against underflow.
740		// The runtime guarantees SP > objabi.StackBig, but
741		// framesize is large enough that SP-framesize may
742		// underflow, causing a direct comparison with the
743		// stack guard to incorrectly succeed. We explicitly
744		// guard against underflow.
745		//
746		//	// Try subtracting from SP and check for underflow.
747		//	// If this underflows, it sets C to 0.
748		//	SUB.S $(framesize-StackSmall), SP, R2
749		//	// If C is 1 (unsigned >=), compare with guard.
750		//	CMP.HS stackguard, R2
751
752		p = obj.Appendp(p, c.newprog)
753		p.As = ASUB
754		p.Scond = C_SBIT
755		p.From.Type = obj.TYPE_CONST
756		p.From.Offset = int64(framesize) - objabi.StackSmall
757		p.Reg = REGSP
758		p.To.Type = obj.TYPE_REG
759		p.To.Reg = REG_R2
760
761		p = obj.Appendp(p, c.newprog)
762		p.As = ACMP
763		p.Scond = C_SCOND_HS
764		p.From.Type = obj.TYPE_REG
765		p.From.Reg = REG_R1
766		p.Reg = REG_R2
767	}
768
769	// BLS call-to-morestack (C is 0 or Z is 1)
770	bls := obj.Appendp(p, c.newprog)
771	bls.As = ABLS
772	bls.To.Type = obj.TYPE_BRANCH
773
774	end := c.ctxt.EndUnsafePoint(bls, c.newprog, -1)
775
776	var last *obj.Prog
777	for last = c.cursym.Func().Text; last.Link != nil; last = last.Link {
778	}
779
780	// Now we are at the end of the function, but logically
781	// we are still in function prologue. We need to fix the
782	// SP data and PCDATA.
783	spfix := obj.Appendp(last, c.newprog)
784	spfix.As = obj.ANOP
785	spfix.Spadj = -framesize
786
787	pcdata := c.ctxt.EmitEntryStackMap(c.cursym, spfix, c.newprog)
788	pcdata = c.ctxt.StartUnsafePoint(pcdata, c.newprog)
789
790	// MOVW	LR, R3
791	movw := obj.Appendp(pcdata, c.newprog)
792	movw.As = AMOVW
793	movw.From.Type = obj.TYPE_REG
794	movw.From.Reg = REGLINK
795	movw.To.Type = obj.TYPE_REG
796	movw.To.Reg = REG_R3
797
798	bls.To.SetTarget(movw)
799
800	// BL runtime.morestack
801	call := obj.Appendp(movw, c.newprog)
802	call.As = obj.ACALL
803	call.To.Type = obj.TYPE_BRANCH
804	morestack := "runtime.morestack"
805	switch {
806	case c.cursym.CFunc():
807		morestack = "runtime.morestackc"
808	case !c.cursym.Func().Text.From.Sym.NeedCtxt():
809		morestack = "runtime.morestack_noctxt"
810	}
811	call.To.Sym = c.ctxt.Lookup(morestack)
812
813	pcdata = c.ctxt.EndUnsafePoint(call, c.newprog, -1)
814
815	// B start
816	b := obj.Appendp(pcdata, c.newprog)
817	b.As = obj.AJMP
818	b.To.Type = obj.TYPE_BRANCH
819	b.To.SetTarget(startPred.Link)
820	b.Spadj = +framesize
821
822	return end
823}
824
825var unaryDst = map[obj.As]bool{
826	ASWI:  true,
827	AWORD: true,
828}
829
830var Linkarm = obj.LinkArch{
831	Arch:           sys.ArchARM,
832	Init:           buildop,
833	Preprocess:     preprocess,
834	Assemble:       span5,
835	Progedit:       progedit,
836	UnaryDst:       unaryDst,
837	DWARFRegisters: ARMDWARFRegisters,
838}
839