1/*
2 * gomacro - A Go interpreter with Lisp-like macros
3 *
4 * Copyright (C) 2019 Massimiliano Ghilardi
5 *
6 *     This Source Code Form is subject to the terms of the Mozilla Public
7 *     License, v. 2.0. If a copy of the MPL was not distributed with this
8 *     file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 *
11 * mov.go
12 *
13 *  Created on Feb 02, 2019
14 *      Author Massimiliano Ghilardi
15 */
16
17package arm64
18
19// ============================================================================
20type loadstore uint32
21
22const (
23	load  loadstore = 0x39400000
24	store loadstore = 0x39000000
25)
26
27func (arch Arm64) load(asm *Asm, src Mem, dst Reg) Arm64 {
28	return arch.loadstore(asm, load, src, dst)
29}
30
31func (arch Arm64) store(asm *Asm, src Reg, dst Mem) Arm64 {
32	return arch.loadstore(asm, store, dst, src)
33}
34
35func (arch Arm64) mov(asm *Asm, src Arg, dst Arg) Arm64 {
36	assert(SizeOf(src) == SizeOf(dst))
37
38	if dst.Const() {
39		errorf("destination cannot be a constant: %v %v, %v", MOV, src, dst)
40	}
41	if src == dst {
42		return arch
43	}
44
45	switch dst := dst.(type) {
46	case Reg:
47		switch src := src.(type) {
48		case Const:
49			arch.movConstReg(asm, src, dst)
50		case Reg:
51			if src.RegId() != dst.RegId() {
52				arch.movRegReg(asm, src, dst)
53			}
54		case Mem:
55			arch.load(asm, src, dst)
56		default:
57			errorf("unknown source type %T, expecting Const, Reg or Mem: %v %v, %v", src, MOV, src, dst)
58		}
59	case Mem:
60		switch src := src.(type) {
61		case Const:
62			arch.movConstMem(asm, src, dst)
63		case Reg:
64			arch.store(asm, src, dst)
65		case Mem:
66			arch.movMemMem(asm, src, dst)
67		default:
68			errorf("unknown source type %T, expecting Const, Reg or Mem: %v %v, %v", src, MOV, src, dst)
69		}
70	default:
71		errorf("unknown destination type %T, expecting Reg or Mem: %v %v, %v", dst, MOV, src, dst)
72	}
73	return arch
74}
75
76func (arch Arm64) movRegReg(asm *Asm, src Reg, dst Reg) Arm64 {
77	// arm64 implements "mov src,dst" as "orr xzr,src,dst"
78	asm.Uint32(kbit(dst) | 0x2A0003E0 | valOrX31(src.RegId(), true)<<16 | val(dst))
79	return arch
80}
81
82func (arch Arm64) movConstReg(asm *Asm, c Const, dst Reg) Arm64 {
83	cval := c.Val()
84	xzr := MakeReg(XZR, dst.Kind())
85	var immcval uint32
86	var movk bool
87	if cval >= 0 && cval < 0x10000 {
88		immcval = 0x40<<19 | uint32(cval)
89	} else if cval < 0 && cval >= -0x10000 {
90		immcval = uint32(^cval)
91	} else if arch.tryOp3RegConstReg(asm, OR3, xzr, uint64(cval), dst) {
92		return arch
93	} else if arch.tryOp3RegConstReg(asm, OR3, xzr, uint64(uint32(cval)), dst) {
94		if dst.Kind().Size() == 8 {
95			arch.movk(asm, uint16(cval>>32), 32, dst)
96			arch.movk(asm, uint16(cval>>48), 48, dst)
97		}
98		return arch
99	} else {
100		immcval = 0x40<<19 | uint32(cval&0xFFFF)
101		movk = true
102	}
103	asm.Uint32(kbit(dst) | 0x12800000 | immcval<<5 | val(dst))
104	if movk {
105		arch.movk(asm, uint16(cval>>16), 16, dst)
106		if dst.Kind().Size() == 8 {
107			arch.movk(asm, uint16(cval>>32), 32, dst)
108			arch.movk(asm, uint16(cval>>48), 48, dst)
109		}
110	}
111	return arch
112}
113
114// set some bits of dst, preserving others
115func (arch Arm64) movk(asm *Asm, cval uint16, shift uint8, dst Reg) Arm64 {
116	if cval != 0 {
117		asm.Uint32(kbit(dst) | 0xF2800000 | uint32(shift)<<17 | uint32(cval)<<5 | val(dst))
118	}
119	return arch
120}
121
122func (arch Arm64) loadstore(asm *Asm, op loadstore, m Mem, r Reg) Arm64 {
123	assert(SizeOf(m) == SizeOf(r))
124	off := m.Offset()
125	var sizebit uint32
126	mrid := m.RegId()
127	rid := r.RegId()
128
129	switch m.Kind().Size() {
130	case 1:
131		sizebit = 0
132		if off >= 0 && off <= 4095 {
133			asm.Uint32(sizebit | uint32(op) | uint32(off)<<10 | valOrX31(mrid, true)<<5 | valOrX31(rid, true))
134			return arch
135		}
136	case 2:
137		sizebit = 0x4 << 28
138		if off >= 0 && off <= 8190 && off%2 == 0 {
139			asm.Uint32(sizebit | uint32(op) | uint32(off)<<9 | valOrX31(mrid, true)<<5 | valOrX31(rid, true))
140			return arch
141		}
142	case 4:
143		sizebit = 0x8 << 28
144		if off >= 0 && off <= 16380 && off%4 == 0 {
145			asm.Uint32(sizebit | uint32(op) | uint32(off)<<8 | valOrX31(mrid, true)<<5 | valOrX31(rid, true))
146			return arch
147		}
148	case 8:
149		sizebit = 0xC << 28
150		if off >= 0 && off <= 32760 && off%8 == 0 {
151			asm.Uint32(sizebit | uint32(op) | uint32(off)<<7 | valOrX31(mrid, true)<<5 | valOrX31(rid, true))
152			return arch
153		}
154	}
155	// load offset in a register. we could also try "ldur" or "stur"...
156	tmp := asm.RegAlloc(Uint64)
157	arch.movConstReg(asm, ConstInt64(int64(off)), tmp)
158
159	asm.Uint32(sizebit | uint32(op^0x1206800) | val(tmp)<<16 | valOrX31(mrid, true)<<5 | val(r))
160
161	asm.RegFree(tmp)
162	return arch
163}
164
165func (arch Arm64) movConstMem(asm *Asm, c Const, dst Mem) Arm64 {
166	if c.Val() == 0 {
167		return arch.zeroMem(asm, dst)
168	}
169	r := asm.RegAlloc(dst.Kind())
170	arch.movConstReg(asm, c, r).store(asm, r, dst)
171	asm.RegFree(r)
172	return arch
173}
174
175func (arch Arm64) movMemMem(asm *Asm, src Mem, dst Mem) Arm64 {
176	r := asm.RegAlloc(src.Kind())
177	arch.load(asm, src, r).store(asm, r, dst)
178	asm.RegFree(r)
179	return arch
180}
181
182// ============================================================================
183func (arch Arm64) Cast(asm *Asm, src Arg, dst Arg) {
184	arch.Cast(asm, src, dst)
185}
186
187func (arch Arm64) cast(asm *Asm, src Arg, dst Arg) Arm64 {
188	if src == dst {
189		return arch
190	} else if SizeOf(src) == SizeOf(dst) {
191		return arch.mov(asm, src, dst)
192	}
193
194	switch dst := dst.(type) {
195	case Reg:
196		switch src := src.(type) {
197		case Reg:
198			arch.castRegReg(asm, src, dst)
199		case Mem:
200			arch.castMemReg(asm, src, dst)
201		case Const:
202			src = src.Cast(dst.Kind())
203			arch.movConstReg(asm, src, dst)
204		default:
205			errorf("unsupported source type %T, expecting Const, Reg or Mem: %v %v %v", src, CAST, src, dst)
206		}
207	case Mem:
208		switch src := src.(type) {
209		case Reg:
210			arch.castRegMem(asm, src, dst)
211		case Mem:
212			arch.castMemMem(asm, src, dst)
213		case Const:
214			src = src.Cast(dst.Kind())
215			arch.movConstMem(asm, src, dst)
216		default:
217			errorf("unsupported source type %T, expecting Const, Reg or Mem: %v %v %v", src, CAST, src, dst)
218		}
219	case Const:
220		errorf("destination cannot be a constant: %v %v %v", CAST, src, dst)
221	default:
222		errorf("unsupported destination type %T, expecting Reg or Mem: %v %v %v", dst, CAST, src, dst)
223	}
224	return arch
225}
226
227func (arch Arm64) castMemMem(asm *Asm, src Mem, dst Mem) Arm64 {
228	r1 := asm.RegAlloc(src.Kind())
229	r2 := MakeReg(r1.RegId(), dst.Kind())
230	arch.load(asm, src, r1).castRegReg(asm, r1, r2).store(asm, r2, dst)
231	asm.RegFree(r1)
232	return arch
233}
234
235func (arch Arm64) castMemReg(asm *Asm, src Mem, dst Reg) Arm64 {
236	r := MakeReg(dst.RegId(), src.Kind())
237	return arch.load(asm, src, r).castRegReg(asm, r, dst)
238}
239
240func (arch Arm64) castRegMem(asm *Asm, src Reg, dst Mem) Arm64 {
241	r := MakeReg(src.RegId(), dst.Kind())
242	if SizeOf(src) < SizeOf(dst) {
243		// extend src. we can safely overwrite its high bits: they are junk
244		return arch.castRegReg(asm, src, r).store(asm, r, dst)
245	} else {
246		// just ignore src high bits
247		return arch.store(asm, r, dst)
248	}
249}
250
251func (arch Arm64) castRegReg(asm *Asm, src Reg, dst Reg) Arm64 {
252	skind := src.Kind()
253	dkind := dst.Kind()
254	ssize := skind.Size()
255	dsize := dkind.Size()
256	if ssize >= dsize {
257		// truncate. easy, just ignore src high bits
258		return arch.mov(asm, MakeReg(src.RegId(), dst.Kind()), dst)
259	} else if skind.Signed() {
260		// sign-extend. use one of:
261		// "sxtb	src, dst"
262		// "sxth	src, dst"
263		// "sxtw	src, dst"
264		kbit := uint32(dsize&8) * 0x10080000
265		op := 0x13000C00 | uint32(ssize*2-1)<<12
266		asm.Uint32(kbit | op | val(src)<<5 | val(dst))
267		return arch
268	} else {
269		// zero-extend
270		if ssize == 4 {
271			// zero-extend 32 bit -> 64 bit: use
272			// "mov dst, src"
273			// must be kept even if src == dst to zero high bits,
274			// so use Asm.movRegReg() instead of too smart Asm.Mov()
275			return arch.movRegReg(asm, src, MakeReg(dst.RegId(), skind))
276		}
277		// zero-extend, src is 8 bit or 16 bit. use one of:
278		// "and dst, src, #0xff"
279		// "and dst, src, #0xffff"
280		if dsize <= 4 {
281			dkind = Uint32
282		}
283		r := MakeReg(src.RegId(), dkind)
284		c := MakeConst(int64(0xffff)>>(16-ssize*8), dkind)
285		return arch.op3RegConstReg(asm, AND3, r, c, dst)
286	}
287}
288