struct_amd64.go raw

   1  // SPDX-License-Identifier: Apache-2.0
   2  // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
   3  
   4  package purego
   5  
   6  import (
   7  	"math"
   8  	"reflect"
   9  	"unsafe"
  10  )
  11  
  12  func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
  13  	outSize := outType.Size()
  14  	switch {
  15  	case outSize == 0:
  16  		return reflect.New(outType).Elem()
  17  	case outSize <= 8:
  18  		if isAllFloats(outType) {
  19  			// 2 float32s or 1 float64s are return in the float register
  20  			return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
  21  		}
  22  		// up to 8 bytes is returned in RAX
  23  		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
  24  	case outSize <= 16:
  25  		r1, r2 := syscall.a1, syscall.a2
  26  		if isAllFloats(outType) {
  27  			r1 = syscall.f1
  28  			r2 = syscall.f2
  29  		} else {
  30  			// check first 8 bytes if it's floats
  31  			hasFirstFloat := false
  32  			f1 := outType.Field(0).Type
  33  			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
  34  				r1 = syscall.f1
  35  				hasFirstFloat = true
  36  			}
  37  
  38  			// find index of the field that starts the second 8 bytes
  39  			var i int
  40  			for i = 0; i < outType.NumField(); i++ {
  41  				if outType.Field(i).Offset == 8 {
  42  					break
  43  				}
  44  			}
  45  
  46  			// check last 8 bytes if they are floats
  47  			f1 = outType.Field(i).Type
  48  			if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
  49  				r2 = syscall.f1
  50  			} else if hasFirstFloat {
  51  				// if the first field was a float then that means the second integer field
  52  				// comes from the first integer register
  53  				r2 = syscall.a1
  54  			}
  55  		}
  56  		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
  57  	default:
  58  		// create struct from the Go pointer created above
  59  		// weird pointer dereference to circumvent go vet
  60  		return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
  61  	}
  62  }
  63  
  64  func isAllFloats(ty reflect.Type) bool {
  65  	for i := 0; i < ty.NumField(); i++ {
  66  		f := ty.Field(i)
  67  		switch f.Type.Kind() {
  68  		case reflect.Float64, reflect.Float32:
  69  		default:
  70  			return false
  71  		}
  72  	}
  73  	return true
  74  }
  75  
  76  // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
  77  // https://gitlab.com/x86-psABIs/x86-64-ABI
  78  // Class determines where the 8 byte value goes.
  79  // Higher value classes win over lower value classes
  80  const (
  81  	_NO_CLASS = 0b0000
  82  	_SSE      = 0b0001
  83  	_X87      = 0b0011 // long double not used in Go
  84  	_INTEGER  = 0b0111
  85  	_MEMORY   = 0b1111
  86  )
  87  
  88  func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
  89  	if v.Type().Size() == 0 {
  90  		return keepAlive
  91  	}
  92  
  93  	// if greater than 64 bytes place on stack
  94  	if v.Type().Size() > 8*8 {
  95  		placeStack(v, addStack)
  96  		return keepAlive
  97  	}
  98  	var (
  99  		savedNumFloats = *numFloats
 100  		savedNumInts   = *numInts
 101  		savedNumStack  = *numStack
 102  	)
 103  	placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
 104  	if placeOnStack {
 105  		// reset any values placed in registers
 106  		*numFloats = savedNumFloats
 107  		*numInts = savedNumInts
 108  		*numStack = savedNumStack
 109  		placeStack(v, addStack)
 110  	}
 111  	return keepAlive
 112  }
 113  
 114  func postMerger(t reflect.Type) (passInMemory bool) {
 115  	// (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
 116  	// eightbyte isn’t SSEUP, the whole argument is passed in memory.
 117  	if t.Kind() != reflect.Struct {
 118  		return false
 119  	}
 120  	if t.Size() <= 2*8 {
 121  		return false
 122  	}
 123  	return true // Go does not have an SSE/SSEUP type so this is always true
 124  }
 125  
 126  func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
 127  	ok = true
 128  	var val uint64
 129  	var shift byte // # of bits to shift
 130  	var flushed bool
 131  	class := _NO_CLASS
 132  	flushIfNeeded := func() {
 133  		if flushed {
 134  			return
 135  		}
 136  		flushed = true
 137  		if class == _SSE {
 138  			addFloat(uintptr(val))
 139  		} else {
 140  			addInt(uintptr(val))
 141  		}
 142  		val = 0
 143  		shift = 0
 144  		class = _NO_CLASS
 145  	}
 146  	var place func(v reflect.Value)
 147  	place = func(v reflect.Value) {
 148  		var numFields int
 149  		if v.Kind() == reflect.Struct {
 150  			numFields = v.Type().NumField()
 151  		} else {
 152  			numFields = v.Type().Len()
 153  		}
 154  
 155  		for i := 0; i < numFields; i++ {
 156  			flushed = false
 157  			var f reflect.Value
 158  			if v.Kind() == reflect.Struct {
 159  				f = v.Field(i)
 160  			} else {
 161  				f = v.Index(i)
 162  			}
 163  			switch f.Kind() {
 164  			case reflect.Struct:
 165  				place(f)
 166  			case reflect.Bool:
 167  				if f.Bool() {
 168  					val |= 1 << shift
 169  				}
 170  				shift += 8
 171  				class |= _INTEGER
 172  			case reflect.Pointer:
 173  				ok = false
 174  				return
 175  			case reflect.Int8:
 176  				val |= uint64(f.Int()&0xFF) << shift
 177  				shift += 8
 178  				class |= _INTEGER
 179  			case reflect.Int16:
 180  				val |= uint64(f.Int()&0xFFFF) << shift
 181  				shift += 16
 182  				class |= _INTEGER
 183  			case reflect.Int32:
 184  				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
 185  				shift += 32
 186  				class |= _INTEGER
 187  			case reflect.Int64, reflect.Int:
 188  				val = uint64(f.Int())
 189  				shift = 64
 190  				class = _INTEGER
 191  			case reflect.Uint8:
 192  				val |= f.Uint() << shift
 193  				shift += 8
 194  				class |= _INTEGER
 195  			case reflect.Uint16:
 196  				val |= f.Uint() << shift
 197  				shift += 16
 198  				class |= _INTEGER
 199  			case reflect.Uint32:
 200  				val |= f.Uint() << shift
 201  				shift += 32
 202  				class |= _INTEGER
 203  			case reflect.Uint64, reflect.Uint, reflect.Uintptr:
 204  				val = f.Uint()
 205  				shift = 64
 206  				class = _INTEGER
 207  			case reflect.Float32:
 208  				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
 209  				shift += 32
 210  				class |= _SSE
 211  			case reflect.Float64:
 212  				if v.Type().Size() > 16 {
 213  					ok = false
 214  					return
 215  				}
 216  				val = uint64(math.Float64bits(f.Float()))
 217  				shift = 64
 218  				class = _SSE
 219  			case reflect.Array:
 220  				place(f)
 221  			default:
 222  				panic("purego: unsupported kind " + f.Kind().String())
 223  			}
 224  
 225  			if shift == 64 {
 226  				flushIfNeeded()
 227  			} else if shift > 64 {
 228  				// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
 229  				// better fall apart here, than corrupt arguments.
 230  				panic("purego: tryPlaceRegisters shift > 64")
 231  			}
 232  		}
 233  	}
 234  
 235  	place(v)
 236  	flushIfNeeded()
 237  	return ok
 238  }
 239  
 240  func placeStack(v reflect.Value, addStack func(uintptr)) {
 241  	for i := 0; i < v.Type().NumField(); i++ {
 242  		f := v.Field(i)
 243  		switch f.Kind() {
 244  		case reflect.Pointer:
 245  			addStack(f.Pointer())
 246  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 247  			addStack(uintptr(f.Int()))
 248  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 249  			addStack(uintptr(f.Uint()))
 250  		case reflect.Float32:
 251  			addStack(uintptr(math.Float32bits(float32(f.Float()))))
 252  		case reflect.Float64:
 253  			addStack(uintptr(math.Float64bits(f.Float())))
 254  		case reflect.Struct:
 255  			placeStack(f, addStack)
 256  		default:
 257  			panic("purego: unsupported kind " + f.Kind().String())
 258  		}
 259  	}
 260  }
 261  
 262  func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
 263  	panic("purego: not needed on amd64")
 264  }
 265