Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
//
// These conversions describe how a Go type in the fptr will be used to call
// the C function. It is important to note that there is no way to verify that fptr
// matches the C function. This also holds true for struct types where the padding
// needs to be ensured to match that of C; RegisterFunc does not verify this.
// matches the C function. This also holds true for struct types, whose memory layout
// must match the C one; RegisterFunc does not verify this.
//
// # Type Conversions (Go <=> C)
//
Expand Down Expand Up @@ -101,9 +101,11 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
//
// # Structs
//
// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc.
// Each field is placed at the offset it has in the Go struct's memory image, so the padding the Go compiler
// inserts is preserved and explicit padding fields are not needed. The Go struct must still be declared with the
// same fields, in the same order, as the C one, and should embed [structs.HostLayout] to guarantee that layout.
// Purego does not verify that the two match.
//
// On Apple ARM64 platforms (macOS and iOS), purego handles proper alignment of struct arguments
// when passing them on the stack, following the C ABI's byte-level packing rules.
Expand Down
83 changes: 70 additions & 13 deletions struct_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
shift = 0
class = _NO_CLASS
}
var place func(v reflect.Value)
place = func(v reflect.Value) {
var place func(v reflect.Value, base uintptr)
// curEight tracks the eightbyte index of the pending accumulator.
var curEight uintptr
place = func(v reflect.Value, base uintptr) {
var numFields int
if v.Kind() == reflect.Struct {
numFields = v.Type().NumField()
Expand All @@ -226,57 +228,106 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
}
flushed = false
var f reflect.Value
var fieldOff uintptr
if v.Kind() == reflect.Struct {
f = v.Field(i)
fieldOff = base + v.Type().Field(i).Offset
} else {
f = v.Index(i)
fieldOff = base + uintptr(i)*f.Type().Size()
}
// A field in a later eightbyte than the pending accumulator ends
// it, so flush before a wide field overwrites the small fields
// accumulated so far (e.g. the int64 in {int8; int64}).
needFresh := shift != 0 && fieldOff/8 != curEight
if needFresh {
flushIfNeeded()
Comment thread
kumagi marked this conversation as resolved.
// The fresh accumulator must still be flushed.
flushed = false
}
curEight = fieldOff / 8
// Realign the bit cursor over padding, e.g. for the int32 at
// offset 4 in {int8; int32}.
alignTo := func(off uintptr) {
want := byte((off % 8) * 8)
if want > shift {
shift = want
}
}
switch f.Kind() {
case reflect.Struct:
place(f)
// The nested tail stays pending in the accumulator, so
// curEight must not be restored here.
place(f, fieldOff)
case reflect.Bool:
alignTo(fieldOff)
if f.Bool() {
val |= 1 << shift
}
shift += 8
class |= _INTEGER
case reflect.Pointer, reflect.UnsafePointer:
val = uint64(f.Pointer())
shift = 64
if !needFresh {
val = uint64(f.Pointer())
shift = 64
} else {
flushed = false
addInt(uintptr(f.Pointer()))
flushed = true
}
class = _INTEGER
case reflect.Int8:
alignTo(fieldOff)
val |= uint64(f.Int()&0xFF) << shift
shift += 8
class |= _INTEGER
case reflect.Int16:
alignTo(fieldOff)
val |= uint64(f.Int()&0xFFFF) << shift
shift += 16
class |= _INTEGER
case reflect.Int32:
alignTo(fieldOff)
val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32
class |= _INTEGER
case reflect.Int64, reflect.Int:
val = uint64(f.Int())
shift = 64
if !needFresh {
val = uint64(f.Int())
shift = 64
} else {
flushed = false
addInt(uintptr(f.Int()))
flushed = true
}
class = _INTEGER
case reflect.Uint8:
alignTo(fieldOff)
val |= f.Uint() << shift
shift += 8
class |= _INTEGER
case reflect.Uint16:
alignTo(fieldOff)
val |= f.Uint() << shift
shift += 16
class |= _INTEGER
case reflect.Uint32:
alignTo(fieldOff)
val |= f.Uint() << shift
shift += 32
class |= _INTEGER
case reflect.Uint64, reflect.Uint, reflect.Uintptr:
val = f.Uint()
shift = 64
if !needFresh {
val = f.Uint()
shift = 64
} else {
flushed = false
addInt(uintptr(f.Uint()))
flushed = true
}
class = _INTEGER
case reflect.Float32:
alignTo(fieldOff)
val |= uint64(math.Float32bits(float32(f.Float()))) << shift
shift += 32
class |= _SSE
Expand All @@ -285,11 +336,17 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
ok = false
return
}
val = uint64(math.Float64bits(f.Float()))
shift = 64
if !needFresh {
val = uint64(math.Float64bits(f.Float()))
shift = 64
} else {
flushed = false
addFloat(uintptr(math.Float64bits(f.Float())))
flushed = true
}
class = _SSE
case reflect.Array:
place(f)
place(f, fieldOff)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
Expand All @@ -304,7 +361,7 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
}
}

place(v)
place(v, 0)
flushIfNeeded()
return ok
}
Expand Down
49 changes: 41 additions & 8 deletions struct_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,23 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
var shift byte
var flushed bool
class := _NO_CLASS
var place func(v reflect.Value)
place = func(v reflect.Value) {
// slotOff is the in-memory offset bit 0 of val corresponds to, so that
// the cursor can be realigned after a composite with trailing padding.
var slotOff uintptr
advanceSlot := func() {
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
val = 0
shift = 0
class = _NO_CLASS
slotOff += 8
flushed = true
}
var place func(v reflect.Value, base uintptr)
place = func(v reflect.Value, base uintptr) {
var numFields int
if v.Kind() == reflect.Struct {
numFields = v.Type().NumField()
Expand All @@ -125,27 +140,42 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
}
flushed = false
var f reflect.Value
var fieldOff uintptr
if v.Kind() == reflect.Struct {
f = v.Field(k)
fieldOff = base + v.Type().Field(k).Offset
} else {
f = v.Index(k)
fieldOff = base + uintptr(k)*f.Type().Size()
}
align := byte(f.Type().Align()*8 - 1)
shift = (shift + align) &^ align
if shift >= 64 {
shift = 0
flushed = true
// Keep flushed false so the field placed below is still
// emitted by the final flush.
flushed = false
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
val = 0
class = _NO_CLASS
slotOff += 8
}
switch f.Type().Kind() {
case reflect.Struct:
place(f)
case reflect.Struct, reflect.Array:
place(f, fieldOff)
// Skip the composite's trailing padding so that the next
// sibling lands at its own in-memory offset.
for end := fieldOff + f.Type().Size(); end > slotOff+uintptr(shift)/8; {
if bits := (end - slotOff) * 8; bits < 64 {
shift = byte(bits)
break
}
advanceSlot()
}
case reflect.Bool:
if f.Bool() {
val |= 1 << shift
Expand All @@ -168,6 +198,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
addInt(uintptr(f.Uint()))
shift = 0
flushed = true
slotOff += 8
class = _NO_CLASS
case reflect.Int8:
val |= uint64(f.Int()&0xFF) << shift
Expand All @@ -185,12 +216,14 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
addInt(uintptr(f.Int()))
shift = 0
flushed = true
slotOff += 8
class = _NO_CLASS
case reflect.Float32:
if class == _FLOAT {
addFloat(uintptr(val))
val = 0
shift = 0
slotOff += 4
}
val |= uint64(math.Float32bits(float32(f.Float()))) << shift
shift += 32
Expand All @@ -199,20 +232,20 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui
addFloat(uintptr(math.Float64bits(float64(f.Float()))))
shift = 0
flushed = true
slotOff += 8
class = _NO_CLASS
case reflect.Pointer, reflect.UnsafePointer:
addInt(f.Pointer())
shift = 0
flushed = true
slotOff += 8
class = _NO_CLASS
case reflect.Array:
place(f)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
}
}
place(v)
place(v, 0)
if !flushed {
if class == _FLOAT {
addFloat(uintptr(val))
Expand Down
Loading
Loading