// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Only build this file if libffi is supported.

//go:build libffi
// +build libffi

package runtime

import "unsafe"

// This file contains the code that converts a Go type to an FFI type.
// This has to be written in Go because it allocates memory in the Go heap.

// C functions to return pointers to libffi variables.

func ffi_type_pointer() *__ffi_type
func ffi_type_sint8() *__ffi_type
func ffi_type_sint16() *__ffi_type
func ffi_type_sint32() *__ffi_type
func ffi_type_sint64() *__ffi_type
func ffi_type_uint8() *__ffi_type
func ffi_type_uint16() *__ffi_type
func ffi_type_uint32() *__ffi_type
func ffi_type_uint64() *__ffi_type
func ffi_type_float() *__ffi_type
func ffi_type_double() *__ffi_type
func ffi_supports_complex() bool
func ffi_type_complex_float() *__ffi_type
func ffi_type_complex_double() *__ffi_type
func ffi_type_void() *__ffi_type

// C functions defined in libffi.

//extern ffi_prep_cif
func ffi_prep_cif(*_ffi_cif, _ffi_abi, uint32, *__ffi_type, **__ffi_type) _ffi_status

// ffiFuncToCIF is called from C code.
//go:linkname ffiFuncToCIF

// ffiFuncToCIF builds an _ffi_cif struct for function described by ft.
func ffiFuncToCIF(ft *functype, isInterface bool, isMethod bool, cif *_ffi_cif) {
	nparams := len(ft.in)
	nargs := nparams
	if isInterface {
		nargs++
	}
	args := make([]*__ffi_type, nargs)
	i := 0
	off := 0
	if isInterface {
		args[0] = ffi_type_pointer()
		off = 1
	} else if isMethod {
		args[0] = ffi_type_pointer()
		i = 1
	}
	for ; i < nparams; i++ {
		args[i+off] = typeToFFI(ft.in[i])
	}

	rettype := funcReturnFFI(ft)

	var pargs **__ffi_type
	if len(args) > 0 {
		pargs = &args[0]
	}
	status := ffi_prep_cif(cif, _FFI_DEFAULT_ABI, uint32(nargs), rettype, pargs)
	if status != _FFI_OK {
		throw("ffi_prep_cif failed")
	}
}

// funcReturnFFI returns the FFI definition of the return type of ft.
func funcReturnFFI(ft *functype) *__ffi_type {
	c := len(ft.out)
	if c == 0 {
		return ffi_type_void()
	}

	// Compile a function that returns a zero-sized value as
	// though it returns void. This works around a problem in
	// libffi: it can't represent a zero-sized value.
	var size uintptr
	for _, v := range ft.out {
		size += v.size
	}
	if size == 0 {
		return ffi_type_void()
	}

	if c == 1 {
		return typeToFFI(ft.out[0])
	}

	elements := make([]*__ffi_type, c+1)
	for i, v := range ft.out {
		elements[i] = typeToFFI(v)
	}
	elements[c] = nil

	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// typeToFFI returns the __ffi_type for a Go type.
func typeToFFI(typ *_type) *__ffi_type {
	switch typ.kind & kindMask {
	case kindBool:
		switch unsafe.Sizeof(false) {
		case 1:
			return ffi_type_uint8()
		case 4:
			return ffi_type_uint32()
		default:
			throw("bad bool size")
			return nil
		}
	case kindInt:
		return intToFFI()
	case kindInt8:
		return ffi_type_sint8()
	case kindInt16:
		return ffi_type_sint16()
	case kindInt32:
		return ffi_type_sint32()
	case kindInt64:
		return ffi_type_sint64()
	case kindUint:
		switch unsafe.Sizeof(uint(0)) {
		case 4:
			return ffi_type_uint32()
		case 8:
			return ffi_type_uint64()
		default:
			throw("bad uint size")
			return nil
		}
	case kindUint8:
		return ffi_type_uint8()
	case kindUint16:
		return ffi_type_uint16()
	case kindUint32:
		return ffi_type_uint32()
	case kindUint64:
		return ffi_type_uint64()
	case kindUintptr:
		switch unsafe.Sizeof(uintptr(0)) {
		case 4:
			return ffi_type_uint32()
		case 8:
			return ffi_type_uint64()
		default:
			throw("bad uinptr size")
			return nil
		}
	case kindFloat32:
		return ffi_type_float()
	case kindFloat64:
		return ffi_type_double()
	case kindComplex64:
		if ffi_supports_complex() {
			return ffi_type_complex_float()
		} else {
			return complexToFFI(ffi_type_float())
		}
	case kindComplex128:
		if ffi_supports_complex() {
			return ffi_type_complex_double()
		} else {
			return complexToFFI(ffi_type_double())
		}
	case kindArray:
		return arrayToFFI((*arraytype)(unsafe.Pointer(typ)))
	case kindChan, kindFunc, kindMap, kindPtr, kindUnsafePointer:
		// These types are always simple pointers, and for FFI
		// purposes nothing else matters.
		return ffi_type_pointer()
	case kindInterface:
		return interfaceToFFI()
	case kindSlice:
		return sliceToFFI((*slicetype)(unsafe.Pointer(typ)))
	case kindString:
		return stringToFFI()
	case kindStruct:
		return structToFFI((*structtype)(unsafe.Pointer(typ)))
	default:
		throw("unknown type kind")
		return nil
	}
}

// interfaceToFFI returns an ffi_type for a Go interface type.
// This is used for both empty and non-empty interface types.
func interfaceToFFI() *__ffi_type {
	elements := make([]*__ffi_type, 3)
	elements[0] = ffi_type_pointer()
	elements[1] = elements[0]
	elements[2] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// stringToFFI returns an ffi_type for a Go string type.
func stringToFFI() *__ffi_type {
	elements := make([]*__ffi_type, 3)
	elements[0] = ffi_type_pointer()
	elements[1] = intToFFI()
	elements[2] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// structToFFI returns an ffi_type for a Go struct type.
func structToFFI(typ *structtype) *__ffi_type {
	c := len(typ.fields)
	if typ.typ.kind&kindDirectIface != 0 {
		return ffi_type_pointer()
	}

	fields := make([]*__ffi_type, 0, c+1)
	checkPad := false
	lastzero := false
	sawnonzero := false
	for i, v := range typ.fields {
		// Skip zero-sized fields; they confuse libffi,
		// and there is no value to pass in any case.
		// We do have to check whether the alignment of the
		// zero-sized field introduces any padding for the
		// next field.
		if v.typ.size == 0 {
			checkPad = true
			if v.name == nil || *v.name != "_" {
				lastzero = true
			}
			continue
		}
		lastzero = false
		sawnonzero = true

		if checkPad {
			off := uintptr(0)
			for j := i - 1; j >= 0; j-- {
				if typ.fields[j].typ.size > 0 {
					off = typ.fields[j].offset() + typ.fields[j].typ.size
					break
				}
			}
			off += uintptr(v.typ.align) - 1
			off &^= uintptr(v.typ.align) - 1
			if off != v.offset() {
				fields = append(fields, padFFI(v.offset()-off))
			}
			checkPad = false
		}

		fields = append(fields, typeToFFI(v.typ))
	}

	if !sawnonzero {
		return emptyStructToFFI()
	}

	if lastzero {
		// The compiler adds one byte padding to non-empty struct ending
		// with a zero-sized field (types.cc:get_backend_struct_fields).
		// Add this padding to the FFI type.
		fields = append(fields, ffi_type_uint8())
	}

	fields = append(fields, nil)

	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &fields[0],
	}
}

// sliceToFFI returns an ffi_type for a Go slice type.
func sliceToFFI(typ *slicetype) *__ffi_type {
	elements := make([]*__ffi_type, 4)
	elements[0] = ffi_type_pointer()
	elements[1] = intToFFI()
	elements[2] = elements[1]
	elements[3] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// complexToFFI returns an ffi_type for a Go complex type.
// This is only used if libffi does not support complex types internally
// for this target.
func complexToFFI(ffiFloatType *__ffi_type) *__ffi_type {
	elements := make([]*__ffi_type, 3)
	elements[0] = ffiFloatType
	elements[1] = ffiFloatType
	elements[2] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// arrayToFFI returns an ffi_type for a Go array type.
func arrayToFFI(typ *arraytype) *__ffi_type {
	if typ.len == 0 {
		return emptyStructToFFI()
	}
	if typ.typ.kind&kindDirectIface != 0 {
		return ffi_type_pointer()
	}
	elements := make([]*__ffi_type, typ.len+1)
	et := typeToFFI(typ.elem)
	for i := uintptr(0); i < typ.len; i++ {
		elements[i] = et
	}
	elements[typ.len] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// intToFFI returns an ffi_type for the Go int type.
func intToFFI() *__ffi_type {
	switch unsafe.Sizeof(0) {
	case 4:
		return ffi_type_sint32()
	case 8:
		return ffi_type_sint64()
	default:
		throw("bad int size")
		return nil
	}
}

// emptyStructToFFI returns an ffi_type for an empty struct.
// The libffi library won't accept a struct with no fields.
func emptyStructToFFI() *__ffi_type {
	elements := make([]*__ffi_type, 2)
	elements[0] = ffi_type_void()
	elements[1] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

// padFFI returns a padding field of the given size
func padFFI(size uintptr) *__ffi_type {
	elements := make([]*__ffi_type, size+1)
	for i := uintptr(0); i < size; i++ {
		elements[i] = ffi_type_uint8()
	}
	elements[size] = nil
	return &__ffi_type{
		_type:    _FFI_TYPE_STRUCT,
		elements: &elements[0],
	}
}

//go:linkname makeCIF reflect.makeCIF

// makeCIF is used by the reflect package to allocate a CIF.
func makeCIF(ft *functype) *_ffi_cif {
	cif := new(_ffi_cif)
	ffiFuncToCIF(ft, false, false, cif)
	return cif
}
