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 flush := func() {
80 flushed = true
81 if class == _SSE {
82 addFloat(uintptr(val))
83 } else {
84 addInt(uintptr(val))
85 }
86 val = 0
87 shift = 0
88 class = _NO_CLASS
89 }
90 var place func(v reflect.Value)
91 place = func(v reflect.Value) {
92 var numFields int
93 if v.Kind() == reflect.Struct {
94 numFields = v.Type().NumField()
95 } else {
96 numFields = v.Type().Len()
97 }
98
99 for i := 0; i < numFields; i++ {
100 flushed = false
101 var f reflect.Value
102 if v.Kind() == reflect.Struct {
103 f = v.Field(i)
104 } else {
105 f = v.Index(i)
106 }
107 switch f.Kind() {
108 case reflect.Struct:
109 place(f)
110 case reflect.Bool:
111 if f.Bool() {
112 val |= 1
113 }
114 shift += 8
115 class |= _INTEGER
116 case reflect.Pointer:
117 ok = false
118 return
119 case reflect.Int8:
120 val |= uint64(f.Int()&0xFF) << shift
121 shift += 8
122 class |= _INTEGER
123 case reflect.Int16:
124 val |= uint64(f.Int()&0xFFFF) << shift
125 shift += 16
126 class |= _INTEGER
127 case reflect.Int32:
128 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
129 shift += 32
130 class |= _INTEGER
131 case reflect.Int64:
132 val = uint64(f.Int())
133 shift = 64
134 class = _INTEGER
135 case reflect.Uint8:
136 val |= f.Uint() << shift
137 shift += 8
138 class |= _INTEGER
139 case reflect.Uint16:
140 val |= f.Uint() << shift
141 shift += 16
142 class |= _INTEGER
143 case reflect.Uint32:
144 val |= f.Uint() << shift
145 shift += 32
146 class |= _INTEGER
147 case reflect.Uint64:
148 val = f.Uint()
149 shift = 64
150 class = _INTEGER
151 case reflect.Float32:
152 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
153 shift += 32
154 class |= _SSE
155 case reflect.Float64:
156 if v.Type().Size() > 16 {
157 ok = false
158 return
159 }
160 val = uint64(math.Float64bits(f.Float()))
161 shift = 64
162 class = _SSE
163 case reflect.Array:
164 place(f)
165 default:
166 panic("purego: unsupported kind " + f.Kind().String())
167 }
168
169 if shift == 64 {
170 flush()
171 } else if shift > 64 {
172 // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
173 // better fall apart here, than corrupt arguments.
174 panic("purego: tryPlaceRegisters shift > 64")
175 }
176 }
177 }
178
179 place(v)
180 if !flushed {
181 flush()
182 }
183 return ok
184}
185
186func placeStack(v reflect.Value, addStack func(uintptr)) {
187 for i := 0; i < v.Type().NumField(); i++ {
188 f := v.Field(i)
189 switch f.Kind() {
190 case reflect.Pointer:
191 addStack(f.Pointer())
192 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
193 addStack(uintptr(f.Int()))
194 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
195 addStack(uintptr(f.Uint()))
196 case reflect.Float32:
197 addStack(uintptr(math.Float32bits(float32(f.Float()))))
198 case reflect.Float64:
199 addStack(uintptr(math.Float64bits(f.Float())))
200 case reflect.Struct:
201 placeStack(f, addStack)
202 default:
203 panic("purego: unsupported kind " + f.Kind().String())
204 }
205 }
206}