github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/struct_arm64.go (about)

     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 []interface{}) []interface{} {
    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() > numOfFloats {
    77  			*numFloats = numOfFloats
    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  			if shift >= 64 {
   111  				shift = 0
   112  				flushed = true
   113  				if class == _FLOAT {
   114  					addFloat(uintptr(val))
   115  				} else {
   116  					addInt(uintptr(val))
   117  				}
   118  			}
   119  			switch f.Type().Kind() {
   120  			case reflect.Struct:
   121  				place(f)
   122  			case reflect.Bool:
   123  				if f.Bool() {
   124  					val |= 1
   125  				}
   126  				shift += 8
   127  				class |= _INT
   128  			case reflect.Uint8:
   129  				val |= f.Uint() << shift
   130  				shift += 8
   131  				class |= _INT
   132  			case reflect.Uint16:
   133  				val |= f.Uint() << shift
   134  				shift += 16
   135  				class |= _INT
   136  			case reflect.Uint32:
   137  				val |= f.Uint() << shift
   138  				shift += 32
   139  				class |= _INT
   140  			case reflect.Uint64:
   141  				addInt(uintptr(f.Uint()))
   142  				shift = 0
   143  				flushed = true
   144  			case reflect.Int8:
   145  				val |= uint64(f.Int()&0xFF) << shift
   146  				shift += 8
   147  				class |= _INT
   148  			case reflect.Int16:
   149  				val |= uint64(f.Int()&0xFFFF) << shift
   150  				shift += 16
   151  				class |= _INT
   152  			case reflect.Int32:
   153  				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
   154  				shift += 32
   155  				class |= _INT
   156  			case reflect.Int64:
   157  				addInt(uintptr(f.Int()))
   158  				shift = 0
   159  				flushed = true
   160  			case reflect.Float32:
   161  				if class == _FLOAT {
   162  					addFloat(uintptr(val))
   163  					val = 0
   164  					shift = 0
   165  				}
   166  				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
   167  				shift += 32
   168  				class |= _FLOAT
   169  			case reflect.Float64:
   170  				addFloat(uintptr(math.Float64bits(float64(f.Float()))))
   171  				shift = 0
   172  				flushed = true
   173  			case reflect.Array:
   174  				place(f)
   175  			default:
   176  				panic("purego: unsupported kind " + f.Kind().String())
   177  			}
   178  		}
   179  	}
   180  	place(v)
   181  	if !flushed {
   182  		if class == _FLOAT {
   183  			addFloat(uintptr(val))
   184  		} else {
   185  			addInt(uintptr(val))
   186  		}
   187  	}
   188  }
   189  
   190  func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} {
   191  	// Struct is too big to be placed in registers.
   192  	// Copy to heap and place the pointer in register
   193  	ptrStruct := reflect.New(v.Type())
   194  	ptrStruct.Elem().Set(v)
   195  	ptr := ptrStruct.Elem().Addr().UnsafePointer()
   196  	keepAlive = append(keepAlive, ptr)
   197  	addInt(uintptr(ptr))
   198  	return keepAlive
   199  }
   200  
   201  // isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
   202  // Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
   203  // This type of struct will be placed more compactly than the individual fields.
   204  //
   205  // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
   206  func isHFA(t reflect.Type) bool {
   207  	// round up struct size to nearest 8 see section B.4
   208  	structSize := roundUpTo8(t.Size())
   209  	if structSize == 0 || t.NumField() > 4 {
   210  		return false
   211  	}
   212  	first := t.Field(0)
   213  	switch first.Type.Kind() {
   214  	case reflect.Float32, reflect.Float64:
   215  		firstKind := first.Type.Kind()
   216  		for i := 0; i < t.NumField(); i++ {
   217  			if t.Field(i).Type.Kind() != firstKind {
   218  				return false
   219  			}
   220  		}
   221  		return true
   222  	case reflect.Array:
   223  		switch first.Type.Elem().Kind() {
   224  		case reflect.Float32, reflect.Float64:
   225  			return true
   226  		default:
   227  			return false
   228  		}
   229  	case reflect.Struct:
   230  		for i := 0; i < first.Type.NumField(); i++ {
   231  			if !isHFA(first.Type) {
   232  				return false
   233  			}
   234  		}
   235  		return true
   236  	default:
   237  		return false
   238  	}
   239  }
   240  
   241  // isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
   242  // and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
   243  // A short vector is a machine type that is composed of repeated instances of one fundamental integral or
   244  // floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
   245  // This type of struct will be placed more compactly than the individual fields.
   246  //
   247  // [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
   248  func isHVA(t reflect.Type) bool {
   249  	// round up struct size to nearest 8 see section B.4
   250  	structSize := roundUpTo8(t.Size())
   251  	if structSize == 0 || (structSize != 8 && structSize != 16) {
   252  		return false
   253  	}
   254  	first := t.Field(0)
   255  	switch first.Type.Kind() {
   256  	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
   257  		firstKind := first.Type.Kind()
   258  		for i := 0; i < t.NumField(); i++ {
   259  			if t.Field(i).Type.Kind() != firstKind {
   260  				return false
   261  			}
   262  		}
   263  		return true
   264  	case reflect.Array:
   265  		switch first.Type.Elem().Kind() {
   266  		case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
   267  			return true
   268  		default:
   269  			return false
   270  		}
   271  	default:
   272  		return false
   273  	}
   274  }