struct_arm64.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  		r1 := syscall.a1
  19  		if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
  20  			r1 = syscall.f1
  21  			if numFields == 2 {
  22  				r1 = syscall.f2<<32 | syscall.f1
  23  			}
  24  		}
  25  		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
  26  	case outSize <= 16:
  27  		r1, r2 := syscall.a1, syscall.a2
  28  		if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
  29  			switch numFields {
  30  			case 4:
  31  				r1 = syscall.f2<<32 | syscall.f1
  32  				r2 = syscall.f4<<32 | syscall.f3
  33  			case 3:
  34  				r1 = syscall.f2<<32 | syscall.f1
  35  				r2 = syscall.f3
  36  			case 2:
  37  				r1 = syscall.f1
  38  				r2 = syscall.f2
  39  			default:
  40  				panic("unreachable")
  41  			}
  42  		}
  43  		return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
  44  	default:
  45  		if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
  46  			switch numFields {
  47  			case 4:
  48  				return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
  49  			case 3:
  50  				return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
  51  			default:
  52  				panic("unreachable")
  53  			}
  54  		}
  55  		// create struct from the Go pointer created in arm64_r8
  56  		// weird pointer dereference to circumvent go vet
  57  		return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
  58  	}
  59  }
  60  
  61  // https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
  62  const (
  63  	_NO_CLASS = 0b00
  64  	_FLOAT    = 0b01
  65  	_INT      = 0b11
  66  )
  67  
  68  func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
  69  	if v.Type().Size() == 0 {
  70  		return keepAlive
  71  	}
  72  
  73  	if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
  74  		// if this doesn't fit entirely in registers then
  75  		// each element goes onto the stack
  76  		if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
  77  			*numFloats = numOfFloatRegisters
  78  		} else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
  79  			*numInts = numOfIntegerRegisters()
  80  		}
  81  
  82  		placeRegisters(v, addFloat, addInt)
  83  	} else {
  84  		keepAlive = placeStack(v, keepAlive, addInt)
  85  	}
  86  	return keepAlive // the struct was allocated so don't panic
  87  }
  88  
  89  func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
  90  	var val uint64
  91  	var shift byte
  92  	var flushed bool
  93  	class := _NO_CLASS
  94  	var place func(v reflect.Value)
  95  	place = func(v reflect.Value) {
  96  		var numFields int
  97  		if v.Kind() == reflect.Struct {
  98  			numFields = v.Type().NumField()
  99  		} else {
 100  			numFields = v.Type().Len()
 101  		}
 102  		for k := 0; k < numFields; k++ {
 103  			flushed = false
 104  			var f reflect.Value
 105  			if v.Kind() == reflect.Struct {
 106  				f = v.Field(k)
 107  			} else {
 108  				f = v.Index(k)
 109  			}
 110  			align := byte(f.Type().Align()*8 - 1)
 111  			shift = (shift + align) &^ align
 112  			if shift >= 64 {
 113  				shift = 0
 114  				flushed = true
 115  				if class == _FLOAT {
 116  					addFloat(uintptr(val))
 117  				} else {
 118  					addInt(uintptr(val))
 119  				}
 120  				val = 0
 121  				class = _NO_CLASS
 122  			}
 123  			switch f.Type().Kind() {
 124  			case reflect.Struct:
 125  				place(f)
 126  			case reflect.Bool:
 127  				if f.Bool() {
 128  					val |= 1 << shift
 129  				}
 130  				shift += 8
 131  				class |= _INT
 132  			case reflect.Uint8:
 133  				val |= f.Uint() << shift
 134  				shift += 8
 135  				class |= _INT
 136  			case reflect.Uint16:
 137  				val |= f.Uint() << shift
 138  				shift += 16
 139  				class |= _INT
 140  			case reflect.Uint32:
 141  				val |= f.Uint() << shift
 142  				shift += 32
 143  				class |= _INT
 144  			case reflect.Uint64, reflect.Uint, reflect.Uintptr:
 145  				addInt(uintptr(f.Uint()))
 146  				shift = 0
 147  				flushed = true
 148  				class = _NO_CLASS
 149  			case reflect.Int8:
 150  				val |= uint64(f.Int()&0xFF) << shift
 151  				shift += 8
 152  				class |= _INT
 153  			case reflect.Int16:
 154  				val |= uint64(f.Int()&0xFFFF) << shift
 155  				shift += 16
 156  				class |= _INT
 157  			case reflect.Int32:
 158  				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
 159  				shift += 32
 160  				class |= _INT
 161  			case reflect.Int64, reflect.Int:
 162  				addInt(uintptr(f.Int()))
 163  				shift = 0
 164  				flushed = true
 165  				class = _NO_CLASS
 166  			case reflect.Float32:
 167  				if class == _FLOAT {
 168  					addFloat(uintptr(val))
 169  					val = 0
 170  					shift = 0
 171  				}
 172  				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
 173  				shift += 32
 174  				class |= _FLOAT
 175  			case reflect.Float64:
 176  				addFloat(uintptr(math.Float64bits(float64(f.Float()))))
 177  				shift = 0
 178  				flushed = true
 179  				class = _NO_CLASS
 180  			case reflect.Ptr:
 181  				addInt(f.Pointer())
 182  				shift = 0
 183  				flushed = true
 184  				class = _NO_CLASS
 185  			case reflect.Array:
 186  				place(f)
 187  			default:
 188  				panic("purego: unsupported kind " + f.Kind().String())
 189  			}
 190  		}
 191  	}
 192  	place(v)
 193  	if !flushed {
 194  		if class == _FLOAT {
 195  			addFloat(uintptr(val))
 196  		} else {
 197  			addInt(uintptr(val))
 198  		}
 199  	}
 200  }
 201  
 202  func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
 203  	// Struct is too big to be placed in registers.
 204  	// Copy to heap and place the pointer in register
 205  	ptrStruct := reflect.New(v.Type())
 206  	ptrStruct.Elem().Set(v)
 207  	ptr := ptrStruct.Elem().Addr().UnsafePointer()
 208  	keepAlive = append(keepAlive, ptr)
 209  	addInt(uintptr(ptr))
 210  	return keepAlive
 211  }
 212  
 213  // isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
 214  // Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
 215  // This type of struct will be placed more compactly than the individual fields.
 216  //
 217  // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
 218  func isHFA(t reflect.Type) bool {
 219  	// round up struct size to nearest 8 see section B.4
 220  	structSize := roundUpTo8(t.Size())
 221  	if structSize == 0 || t.NumField() > 4 {
 222  		return false
 223  	}
 224  	first := t.Field(0)
 225  	switch first.Type.Kind() {
 226  	case reflect.Float32, reflect.Float64:
 227  		firstKind := first.Type.Kind()
 228  		for i := 0; i < t.NumField(); i++ {
 229  			if t.Field(i).Type.Kind() != firstKind {
 230  				return false
 231  			}
 232  		}
 233  		return true
 234  	case reflect.Array:
 235  		switch first.Type.Elem().Kind() {
 236  		case reflect.Float32, reflect.Float64:
 237  			return true
 238  		default:
 239  			return false
 240  		}
 241  	case reflect.Struct:
 242  		for i := 0; i < first.Type.NumField(); i++ {
 243  			if !isHFA(first.Type) {
 244  				return false
 245  			}
 246  		}
 247  		return true
 248  	default:
 249  		return false
 250  	}
 251  }
 252  
 253  // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
 254  // and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
 255  // A short vector is a machine type that is composed of repeated instances of one fundamental integral or
 256  // floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
 257  // This type of struct will be placed more compactly than the individual fields.
 258  //
 259  // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
 260  func isHVA(t reflect.Type) bool {
 261  	// round up struct size to nearest 8 see section B.4
 262  	structSize := roundUpTo8(t.Size())
 263  	if structSize == 0 || (structSize != 8 && structSize != 16) {
 264  		return false
 265  	}
 266  	first := t.Field(0)
 267  	switch first.Type.Kind() {
 268  	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
 269  		firstKind := first.Type.Kind()
 270  		for i := 0; i < t.NumField(); i++ {
 271  			if t.Field(i).Type.Kind() != firstKind {
 272  				return false
 273  			}
 274  		}
 275  		return true
 276  	case reflect.Array:
 277  		switch first.Type.Elem().Kind() {
 278  		case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
 279  			return true
 280  		default:
 281  			return false
 282  		}
 283  	default:
 284  		return false
 285  	}
 286  }
 287