struct_amd64.go RawHistoryFolder
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4package purego
5
6import (
7 "math"
8 "reflect"
9)
10
11// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
12// https://gitlab.com/x86-psABIs/x86-64-ABI
13// Class determines where the 8 byte value goes.
14// Higher value classes win over lower value classes
15const (
16 _NO_CLASS = 0b0000
17 _SSE = 0b0001
18 _X87 = 0b0011 // long double not used in Go
19 _INTEGER = 0b0111
20 _MEMORY = 0b1111
21)
22
23func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
24 if v.Type().Size() == 0 {
25 return keepAlive
26 }
27
28 // if greater than 64 bytes place on stack
29 if v.Type().Size() > 8*8 {
30 placeStack(v, addStack)
31 return keepAlive
32 }
33 var (
34 savedNumFloats = *numFloats
35 savedNumInts = *numInts
36 savedNumStack = *numStack
37 )
38 placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
39 if placeOnStack {
40 // reset any values placed in registers
41 *numFloats = savedNumFloats
42 *numInts = savedNumInts
43 *numStack = savedNumStack
44 placeStack(v, addStack)
45 }
46 return keepAlive
47}
48
49func postMerger(t reflect.Type) bool {
50 // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
51 // eightbyte isn’t SSEUP, the whole argument is passed in memory.
52 if t.Kind() != reflect.Struct {
53 return false
54 }
55 if t.Size() <= 2*8 {
56 return false
57 }
58 first := getFirst(t).Kind()
59 if first != reflect.Float32 && first != reflect.Float64 {
60 return false
61 }
62 return true
63}
64
65func getFirst(t reflect.Type) reflect.Type {
66 first := t.Field(0).Type
67 if first.Kind() == reflect.Struct {
68 return getFirst(first)
69 }
70 return first
71}
72
73func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
74 ok = true
75 var val uint64
76 var shift byte // # of bits to shift
77 var flushed bool
78 class := _NO_CLASS
79 flushIfNeeded := func() {
80 if flushed {
81 return
82 }
83 flushed = true
84 if class == _SSE {
85 addFloat(uintptr(val))
86 } else {
87 addInt(uintptr(val))
88 }
89 val = 0
90 shift = 0
91 class = _NO_CLASS
92 }
93 var place func(v reflect.Value)
94 place = func(v reflect.Value) {
95 var numFields int
96 if v.Kind() == reflect.Struct {
97 numFields = v.Type().NumField()
98 } else {
99 numFields = v.Type().Len()
100 }
101
102 for i := 0; i < numFields; i++ {
103 flushed = false
104 var f reflect.Value
105 if v.Kind() == reflect.Struct {
106 f = v.Field(i)
107 } else {
108 f = v.Index(i)
109 }
110 switch f.Kind() {
111 case reflect.Struct:
112 place(f)
113 case reflect.Bool:
114 if f.Bool() {
115 val |= 1
116 }
117 shift += 8
118 class |= _INTEGER
119 case reflect.Pointer:
120 ok = false
121 return
122 case reflect.Int8:
123 val |= uint64(f.Int()&0xFF) << shift
124 shift += 8
125 class |= _INTEGER
126 case reflect.Int16:
127 val |= uint64(f.Int()&0xFFFF) << shift
128 shift += 16
129 class |= _INTEGER
130 case reflect.Int32:
131 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
132 shift += 32
133 class |= _INTEGER
134 case reflect.Int64:
135 val = uint64(f.Int())
136 shift = 64
137 class = _INTEGER
138 case reflect.Uint8:
139 val |= f.Uint() << shift
140 shift += 8
141 class |= _INTEGER
142 case reflect.Uint16:
143 val |= f.Uint() << shift
144 shift += 16
145 class |= _INTEGER
146 case reflect.Uint32:
147 val |= f.Uint() << shift
148 shift += 32
149 class |= _INTEGER
150 case reflect.Uint64:
151 val = f.Uint()
152 shift = 64
153 class = _INTEGER
154 case reflect.Float32:
155 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
156 shift += 32
157 class |= _SSE
158 case reflect.Float64:
159 if v.Type().Size() > 16 {
160 ok = false
161 return
162 }
163 val = uint64(math.Float64bits(f.Float()))
164 shift = 64
165 class = _SSE
166 case reflect.Array:
167 place(f)
168 default:
169 panic("purego: unsupported kind " + f.Kind().String())
170 }
171
172 if shift == 64 {
173 flushIfNeeded()
174 } else if shift > 64 {
175 // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
176 // better fall apart here, than corrupt arguments.
177 panic("purego: tryPlaceRegisters shift > 64")
178 }
179 }
180 }
181
182 place(v)
183 flushIfNeeded()
184 return ok
185}
186
187func placeStack(v reflect.Value, addStack func(uintptr)) {
188 for i := 0; i < v.Type().NumField(); i++ {
189 f := v.Field(i)
190 switch f.Kind() {
191 case reflect.Pointer:
192 addStack(f.Pointer())
193 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
194 addStack(uintptr(f.Int()))
195 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
196 addStack(uintptr(f.Uint()))
197 case reflect.Float32:
198 addStack(uintptr(math.Float32bits(float32(f.Float()))))
199 case reflect.Float64:
200 addStack(uintptr(math.Float64bits(f.Float())))
201 case reflect.Struct:
202 placeStack(f, addStack)
203 default:
204 panic("purego: unsupported kind " + f.Kind().String())
205 }
206 }
207}