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