github.com/ebitengine/purego@v0.8.0-alpha.2.0.20240512170805-6cd12240d332/struct_amd64.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  		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 []interface{}) []interface{} {
    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) 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  	first := getFirst(t).Kind()
   124  	if first != reflect.Float32 && first != reflect.Float64 {
   125  		return false
   126  	}
   127  	return true
   128  }
   129  
   130  func getFirst(t reflect.Type) reflect.Type {
   131  	first := t.Field(0).Type
   132  	if first.Kind() == reflect.Struct {
   133  		return getFirst(first)
   134  	}
   135  	return first
   136  }
   137  
   138  func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
   139  	ok = true
   140  	var val uint64
   141  	var shift byte // # of bits to shift
   142  	var flushed bool
   143  	class := _NO_CLASS
   144  	flushIfNeeded := func() {
   145  		if flushed {
   146  			return
   147  		}
   148  		flushed = true
   149  		if class == _SSE {
   150  			addFloat(uintptr(val))
   151  		} else {
   152  			addInt(uintptr(val))
   153  		}
   154  		val = 0
   155  		shift = 0
   156  		class = _NO_CLASS
   157  	}
   158  	var place func(v reflect.Value)
   159  	place = func(v reflect.Value) {
   160  		var numFields int
   161  		if v.Kind() == reflect.Struct {
   162  			numFields = v.Type().NumField()
   163  		} else {
   164  			numFields = v.Type().Len()
   165  		}
   166  
   167  		for i := 0; i < numFields; i++ {
   168  			flushed = false
   169  			var f reflect.Value
   170  			if v.Kind() == reflect.Struct {
   171  				f = v.Field(i)
   172  			} else {
   173  				f = v.Index(i)
   174  			}
   175  			switch f.Kind() {
   176  			case reflect.Struct:
   177  				place(f)
   178  			case reflect.Bool:
   179  				if f.Bool() {
   180  					val |= 1
   181  				}
   182  				shift += 8
   183  				class |= _INTEGER
   184  			case reflect.Pointer:
   185  				ok = false
   186  				return
   187  			case reflect.Int8:
   188  				val |= uint64(f.Int()&0xFF) << shift
   189  				shift += 8
   190  				class |= _INTEGER
   191  			case reflect.Int16:
   192  				val |= uint64(f.Int()&0xFFFF) << shift
   193  				shift += 16
   194  				class |= _INTEGER
   195  			case reflect.Int32:
   196  				val |= uint64(f.Int()&0xFFFF_FFFF) << shift
   197  				shift += 32
   198  				class |= _INTEGER
   199  			case reflect.Int64:
   200  				val = uint64(f.Int())
   201  				shift = 64
   202  				class = _INTEGER
   203  			case reflect.Uint8:
   204  				val |= f.Uint() << shift
   205  				shift += 8
   206  				class |= _INTEGER
   207  			case reflect.Uint16:
   208  				val |= f.Uint() << shift
   209  				shift += 16
   210  				class |= _INTEGER
   211  			case reflect.Uint32:
   212  				val |= f.Uint() << shift
   213  				shift += 32
   214  				class |= _INTEGER
   215  			case reflect.Uint64:
   216  				val = f.Uint()
   217  				shift = 64
   218  				class = _INTEGER
   219  			case reflect.Float32:
   220  				val |= uint64(math.Float32bits(float32(f.Float()))) << shift
   221  				shift += 32
   222  				class |= _SSE
   223  			case reflect.Float64:
   224  				if v.Type().Size() > 16 {
   225  					ok = false
   226  					return
   227  				}
   228  				val = uint64(math.Float64bits(f.Float()))
   229  				shift = 64
   230  				class = _SSE
   231  			case reflect.Array:
   232  				place(f)
   233  			default:
   234  				panic("purego: unsupported kind " + f.Kind().String())
   235  			}
   236  
   237  			if shift == 64 {
   238  				flushIfNeeded()
   239  			} else if shift > 64 {
   240  				// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
   241  				// better fall apart here, than corrupt arguments.
   242  				panic("purego: tryPlaceRegisters shift > 64")
   243  			}
   244  		}
   245  	}
   246  
   247  	place(v)
   248  	flushIfNeeded()
   249  	return ok
   250  }
   251  
   252  func placeStack(v reflect.Value, addStack func(uintptr)) {
   253  	for i := 0; i < v.Type().NumField(); i++ {
   254  		f := v.Field(i)
   255  		switch f.Kind() {
   256  		case reflect.Pointer:
   257  			addStack(f.Pointer())
   258  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   259  			addStack(uintptr(f.Int()))
   260  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   261  			addStack(uintptr(f.Uint()))
   262  		case reflect.Float32:
   263  			addStack(uintptr(math.Float32bits(float32(f.Float()))))
   264  		case reflect.Float64:
   265  			addStack(uintptr(math.Float64bits(f.Float())))
   266  		case reflect.Struct:
   267  			placeStack(f, addStack)
   268  		default:
   269  			panic("purego: unsupported kind " + f.Kind().String())
   270  		}
   271  	}
   272  }