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, reflect.UnsafePointer:
 173  				val = uint64(f.Pointer())
 174  				shift = 64
 175  				class = _INTEGER
 176  			case reflect.Int8:
 177  				val |= uint64(f.Int()&0xFF) << shift
 178  				shift += 8
 179  				class |= _INTEGER
 180  			case reflect.Int16:
 181  				val |= uint64(f.Int()&0xFFFF) << shift
 182  				shift += 16
 183  				class |= _INTEGER
 184  			case reflect.Int32:
 185  				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
 186  				shift += 32
 187  				class |= _INTEGER
 188  			case reflect.Int64, reflect.Int:
 189  				val = uint64(f.Int())
 190  				shift = 64
 191  				class = _INTEGER
 192  			case reflect.Uint8:
 193  				val |= f.Uint() << shift
 194  				shift += 8
 195  				class |= _INTEGER
 196  			case reflect.Uint16:
 197  				val |= f.Uint() << shift
 198  				shift += 16
 199  				class |= _INTEGER
 200  			case reflect.Uint32:
 201  				val |= f.Uint() << shift
 202  				shift += 32
 203  				class |= _INTEGER
 204  			case reflect.Uint64, reflect.Uint, reflect.Uintptr:
 205  				val = f.Uint()
 206  				shift = 64
 207  				class = _INTEGER
 208  			case reflect.Float32:
 209  				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
 210  				shift += 32
 211  				class |= _SSE
 212  			case reflect.Float64:
 213  				if v.Type().Size() > 16 {
 214  					ok = false
 215  					return
 216  				}
 217  				val = uint64(math.Float64bits(f.Float()))
 218  				shift = 64
 219  				class = _SSE
 220  			case reflect.Array:
 221  				place(f)
 222  			default:
 223  				panic("purego: unsupported kind " + f.Kind().String())
 224  			}
 225  
 226  			if shift == 64 {
 227  				flushIfNeeded()
 228  			} else if shift > 64 {
 229  				// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
 230  				// better fall apart here, than corrupt arguments.
 231  				panic("purego: tryPlaceRegisters shift > 64")
 232  			}
 233  		}
 234  	}
 235  
 236  	place(v)
 237  	flushIfNeeded()
 238  	return ok
 239  }
 240  
 241  func placeStack(v reflect.Value, addStack func(uintptr)) {
 242  	// Copy the struct as a contiguous block of memory in eightbyte (8-byte)
 243  	// chunks. The x86-64 ABI requires structs passed on the stack to be
 244  	// laid out exactly as in memory, including padding and field packing
 245  	// within eightbytes. Decomposing field-by-field would place each field
 246  	// as a separate stack slot, breaking structs with mixed-type fields
 247  	// that share an eightbyte (e.g. int32 + float32).
 248  	if !v.CanAddr() {
 249  		tmp := reflect.New(v.Type()).Elem()
 250  		tmp.Set(v)
 251  		v = tmp
 252  	}
 253  	ptr := v.Addr().UnsafePointer()
 254  	size := v.Type().Size()
 255  	for off := uintptr(0); off < size; off += 8 {
 256  		chunk := *(*uintptr)(unsafe.Add(ptr, off))
 257  		addStack(chunk)
 258  	}
 259  }
 260  
 261  func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
 262  	panic("purego: placeRegisters not implemented on amd64")
 263  }
 264  
 265  // shouldBundleStackArgs always returns false on non-Darwin platforms
 266  // since C-style stack argument bundling is only needed on Darwin ARM64.
 267  func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
 268  	return false
 269  }
 270  
 271  // structFitsInRegisters is not used on amd64.
 272  func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
 273  	panic("purego: structFitsInRegisters should not be called on amd64")
 274  }
 275  
 276  // collectStackArgs is not used on amd64.
 277  func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
 278  	keepAlive []any, addInt, addFloat, addStack func(uintptr),
 279  	pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
 280  	panic("purego: collectStackArgs should not be called on amd64")
 281  }
 282  
 283  // bundleStackArgs is not used on amd64.
 284  func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
 285  	panic("purego: bundleStackArgs should not be called on amd64")
 286  }
 287