chore: switch purego dependency to bnema/purego fork
Replace local path replace directive with GitHub URL pointing to bnema/purego@91aa177 to avoid accidental pushes to upstream.
- commit
- 8b029581aa8aebd90aa84a2b638058062afa9bdd
- author
- brice · · 2026-03-23 19:51 UTC
- parent
- f14f9b6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
51 files · +1860 -488
M go.mod +2 -0
| @@ -3,3 +3,5 @@ module github.com/bnema/puregotk | |||
| 3 | 3 | go 1.26.1 | |
| 4 | 4 | ||
| 5 | 5 | require github.com/ebitengine/purego v0.10.0 | |
| 6 | + | ||
| 7 | + | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | |
| @@ -3,3 +3,5 @@ module github.com/bnema/puregotk | |||||
| 3 | go 1.26.1 | 3 | go 1.26.1 | ||
| 4 | 4 | ||||
| 5 | require github.com/ebitengine/purego v0.10.0 | 5 | require github.com/ebitengine/purego v0.10.0 | ||
| 6 | + | ||||
| 7 | + | replace github.com/ebitengine/purego => github.com/bnema/purego v0.0.0-20260323095903-91aa177a2704 | |||
M go.sum +2 -2 generated file: collapsed
M vendor/github.com/ebitengine/purego/README.md +3 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/dlfcn_nocgo_netbsd.go +2 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/func.go +220 -107 generated file: collapsed
A vendor/github.com/ebitengine/purego/hostlayout_go123.go +12 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/hostlayout_nogo123.go +11 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/abi_riscv64.h +72 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/asm_riscv64.s +14 -55 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go +4 -25 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go +2 -2 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/linux.go +184 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go +1 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_386.s +18 -4 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s +1 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm.s +44 -3 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s +1 -4 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_386.s +78 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_amd64.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_arm.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_arm64.s +60 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_loong64.s +60 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_ppc64le.s +69 -0 generated file: collapsed
A vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_linux_riscv64.s +60 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s +1 -11 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_ppc64le.s +30 -129 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_riscv64.s +11 -7 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/zsymbols_linux.go +110 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/internal/fakecgo/ztrampolines_linux.s +30 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_386.go +12 -1 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_amd64.go +162 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_arm.go +8 -0 generated file: collapsed
M vendor/github.com/ebitengine/purego/struct_arm64.go +203 -7
| @@ -24,7 +24,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 24 | 24 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 25 | 25 | r1 = syscall.f1 | |
| 26 | 26 | if numFields == 2 { | |
| 27 | - | r1 = syscall.f2<<32 | syscall.f1 | |
| 27 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 | |
| 28 | 28 | } | |
| 29 | 29 | } | |
| 30 | 30 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| @@ -33,16 +33,16 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 33 | 33 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 34 | 34 | switch numFields { | |
| 35 | 35 | case 4: | |
| 36 | - | r1 = syscall.f2<<32 | syscall.f1 | |
| 37 | - | r2 = syscall.f4<<32 | syscall.f3 | |
| 36 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 | |
| 37 | + | r2 = syscall.f4<<32 | syscall.f3&math.MaxUint32 | |
| 38 | 38 | case 3: | |
| 39 | - | r1 = syscall.f2<<32 | syscall.f1 | |
| 39 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 | |
| 40 | 40 | r2 = syscall.f3 | |
| 41 | 41 | case 2: | |
| 42 | 42 | r1 = syscall.f1 | |
| 43 | 43 | r2 = syscall.f2 | |
| 44 | 44 | default: | |
| 45 | - | panic("unreachable") | |
| 45 | + | panic("not reached") | |
| 46 | 46 | } | |
| 47 | 47 | } | |
| 48 | 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| @@ -54,7 +54,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 54 | 54 | case 3: | |
| 55 | 55 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() | |
| 56 | 56 | default: | |
| 57 | - | panic("unreachable") | |
| 57 | + | panic("not reached") | |
| 58 | 58 | } | |
| 59 | 59 | } | |
| 60 | 60 | // create struct from the Go pointer created in arm64_r8 | |
| @@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||
| 190 | 190 | shift = 0 | |
| 191 | 191 | flushed = true | |
| 192 | 192 | class = _NO_CLASS | |
| 193 | - | case reflect.Ptr, reflect.UnsafePointer: | |
| 193 | + | case reflect.Pointer, reflect.UnsafePointer: | |
| 194 | 194 | addInt(f.Pointer()) | |
| 195 | 195 | shift = 0 | |
| 196 | 196 | flushed = true | |
| @@ -547,3 +547,199 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 547 | 547 | size := structType.Size() | |
| 548 | 548 | copyStruct8ByteChunks(ptr, size, addStack) | |
| 549 | 549 | } | |
| 550 | + | ||
| 551 | + | // getCallbackStruct reads a struct argument from the callback frame on arm64. | |
| 552 | + | // It mirrors the AAPCS64 rules used by addStruct for the Go→C path. | |
| 553 | + | // | |
| 554 | + | // getCallbackStruct is only used on Unix. On Windows, callbacks are handled by | |
| 555 | + | // the runtime's own callback mechanism, so this function is compiled but unused. | |
| 556 | + | // | |
| 557 | + | // AAPCS64 struct argument passing rules: | |
| 558 | + | // - HFA (Homogeneous Float Aggregate): each float field in a separate float register | |
| 559 | + | // - Non-HFA ≤ 16 bytes: packed into 1–2 integer registers | |
| 560 | + | // - > 16 bytes (not HFA): passed by pointer in integer register | |
| 561 | + | // - Register overflow: struct goes on the stack | |
| 562 | + | // - Darwin ARM64: byte-level packing on the stack | |
| 563 | + | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 564 | + | switch runtime.GOOS { | |
| 565 | + | case "darwin", "freebsd", "linux", "netbsd": | |
| 566 | + | default: | |
| 567 | + | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | |
| 568 | + | } | |
| 569 | + | ||
| 570 | + | f := (*[callbackMaxFrame]uintptr)(frame) | |
| 571 | + | ||
| 572 | + | if isHFA(inType) { | |
| 573 | + | _, numFloatFields := isAllSameFloat(inType) | |
| 574 | + | if *floatsN+numFloatFields <= numOfFloatRegisters() { | |
| 575 | + | return readHFAFromRegisters(inType, f, floatsN, numFloatFields) | |
| 576 | + | } | |
| 577 | + | // Not enough float registers: entire struct goes on stack. | |
| 578 | + | // Exhaust float registers per AAPCS64. | |
| 579 | + | *floatsN = numOfFloatRegisters() | |
| 580 | + | return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset) | |
| 581 | + | } | |
| 582 | + | ||
| 583 | + | if size := inType.Size(); size <= 16 { | |
| 584 | + | // Non-HFA composite ≤ 16 bytes: packed into 1–2 integer registers. | |
| 585 | + | numSlots := int((size + 7) / 8) | |
| 586 | + | if *intsN+numSlots <= numOfIntegerRegisters() { | |
| 587 | + | pos := numOfFloatRegisters() + *intsN | |
| 588 | + | var r1, r2 uintptr | |
| 589 | + | r1 = f[pos] | |
| 590 | + | *intsN++ | |
| 591 | + | if numSlots == 2 { | |
| 592 | + | r2 = f[pos+1] | |
| 593 | + | *intsN++ | |
| 594 | + | } | |
| 595 | + | if numSlots == 1 { | |
| 596 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| 597 | + | } | |
| 598 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 599 | + | } | |
| 600 | + | // Not enough int registers: goes on stack. | |
| 601 | + | return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset) | |
| 602 | + | } | |
| 603 | + | ||
| 604 | + | // > 16 bytes, not HFA: passed by pointer in integer register. | |
| 605 | + | if *intsN < numOfIntegerRegisters() { | |
| 606 | + | ptr := f[numOfFloatRegisters()+*intsN] | |
| 607 | + | *intsN++ | |
| 608 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |
| 609 | + | } | |
| 610 | + | ||
| 611 | + | // Pointer on stack (rare: all integer registers exhausted). | |
| 612 | + | if runtime.GOOS == "darwin" { | |
| 613 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | |
| 614 | + | ptr := uintptr(ptrVal.Uint()) | |
| 615 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |
| 616 | + | } | |
| 617 | + | ptr := f[*stackSlot] | |
| 618 | + | *stackSlot++ | |
| 619 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |
| 620 | + | } | |
| 621 | + | ||
| 622 | + | // readHFAFromRegisters reads an HFA (Homogeneous Float Aggregate) struct from | |
| 623 | + | // float registers. Each float field occupies one float register slot. | |
| 624 | + | // For float32, the value is in the low 32 bits of the register. | |
| 625 | + | func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, floatsN *int, numFields int) reflect.Value { | |
| 626 | + | size := inType.Size() | |
| 627 | + | ||
| 628 | + | // Determine the element type | |
| 629 | + | root := inType.Field(0).Type | |
| 630 | + | for root.Kind() == reflect.Struct { | |
| 631 | + | root = root.Field(0).Type | |
| 632 | + | } | |
| 633 | + | isFloat32 := root.Kind() == reflect.Float32 | |
| 634 | + | ||
| 635 | + | if isFloat32 { | |
| 636 | + | // Each float32 is in a separate register (low 32 bits). | |
| 637 | + | // Pack pairs into uintptrs to match the struct memory layout. | |
| 638 | + | switch { | |
| 639 | + | case size <= 8: | |
| 640 | + | var r1 uintptr | |
| 641 | + | switch numFields { | |
| 642 | + | case 1: | |
| 643 | + | r1 = f[*floatsN] | |
| 644 | + | case 2: | |
| 645 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |
| 646 | + | } | |
| 647 | + | *floatsN += numFields | |
| 648 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| 649 | + | case size <= 16: | |
| 650 | + | var r1, r2 uintptr | |
| 651 | + | switch numFields { | |
| 652 | + | case 3: | |
| 653 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |
| 654 | + | r2 = f[*floatsN+2] | |
| 655 | + | case 4: | |
| 656 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |
| 657 | + | r2 = f[*floatsN+3]<<32 | f[*floatsN+2]&math.MaxUint32 | |
| 658 | + | } | |
| 659 | + | *floatsN += numFields | |
| 660 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 661 | + | default: | |
| 662 | + | panic("not reached") | |
| 663 | + | } | |
| 664 | + | } | |
| 665 | + | ||
| 666 | + | // float64: each field occupies one full register. | |
| 667 | + | switch numFields { | |
| 668 | + | case 1: | |
| 669 | + | r1 := f[*floatsN] | |
| 670 | + | *floatsN++ | |
| 671 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| 672 | + | case 2: | |
| 673 | + | r1, r2 := f[*floatsN], f[*floatsN+1] | |
| 674 | + | *floatsN += 2 | |
| 675 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 676 | + | case 3: | |
| 677 | + | r1, r2, r3 := f[*floatsN], f[*floatsN+1], f[*floatsN+2] | |
| 678 | + | *floatsN += 3 | |
| 679 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c uintptr }{r1, r2, r3})).Elem() | |
| 680 | + | case 4: | |
| 681 | + | r1, r2, r3, r4 := f[*floatsN], f[*floatsN+1], f[*floatsN+2], f[*floatsN+3] | |
| 682 | + | *floatsN += 4 | |
| 683 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{r1, r2, r3, r4})).Elem() | |
| 684 | + | default: | |
| 685 | + | panic("purego: HFA with more than 4 fields is not supported") | |
| 686 | + | } | |
| 687 | + | } | |
| 688 | + | ||
| 689 | + | // readStructFromStackArm64 reads a struct argument from the stack area of the | |
| 690 | + | // callback frame. On Darwin ARM64, arguments are byte-packed on the stack. | |
| 691 | + | // On Linux ARM64, arguments are 8-byte aligned. | |
| 692 | + | func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 693 | + | if runtime.GOOS == "darwin" { | |
| 694 | + | return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType) | |
| 695 | + | } | |
| 696 | + | // Linux ARM64: 8-byte aligned slots. | |
| 697 | + | numSlots := int((inType.Size() + 7) / 8) | |
| 698 | + | v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem() | |
| 699 | + | *stackSlot += numSlots | |
| 700 | + | return v | |
| 701 | + | } | |
| 702 | + | ||
| 703 | + | func setStruct(a *callbackArgs, ret reflect.Value) { | |
| 704 | + | outSize := ret.Type().Size() | |
| 705 | + | switch { | |
| 706 | + | case outSize == 0: | |
| 707 | + | return | |
| 708 | + | case outSize <= 8: | |
| 709 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |
| 710 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields == 2 { | |
| 711 | + | a.result[1] = a.result[0] >> 32 // expanding two float32s into a.result[0] and a.result[1] | |
| 712 | + | a.result[0] &= math.MaxUint32 // clear the top bits since they contain the second argument | |
| 713 | + | } | |
| 714 | + | return | |
| 715 | + | case outSize <= 16: | |
| 716 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |
| 717 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats { | |
| 718 | + | switch numFields { | |
| 719 | + | case 4: | |
| 720 | + | a.result[3] = a.result[1] >> 32 | |
| 721 | + | a.result[2] = a.result[1] & math.MaxUint32 | |
| 722 | + | a.result[1] = a.result[0] >> 32 | |
| 723 | + | a.result[0] &= math.MaxUint32 | |
| 724 | + | case 3: | |
| 725 | + | a.result[2] = a.result[1] & math.MaxUint32 | |
| 726 | + | a.result[1] = a.result[0] >> 32 | |
| 727 | + | a.result[0] &= math.MaxUint32 | |
| 728 | + | case 2: | |
| 729 | + | // two float64s are already in a.result[0] and a.result[1] | |
| 730 | + | default: | |
| 731 | + | panic("not reached") | |
| 732 | + | } | |
| 733 | + | } | |
| 734 | + | return | |
| 735 | + | default: | |
| 736 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields <= 4 { | |
| 737 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |
| 738 | + | return | |
| 739 | + | } | |
| 740 | + | // The caller passed the address to place the return struct, | |
| 741 | + | // so copy the Go struct into the provided memory. | |
| 742 | + | reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret) | |
| 743 | + | return | |
| 744 | + | } | |
| 745 | + | } | |
| @@ -24,7 +24,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 24 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 24 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | ||
| 25 | r1 = syscall.f1 | 25 | r1 = syscall.f1 | ||
| 26 | if numFields == 2 { | 26 | if numFields == 2 { | ||
| 27 | - | r1 = syscall.f2<<32 | syscall.f1 | 27 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 |
| 28 | } | 28 | } | ||
| 29 | } | 29 | } | ||
| 30 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | 30 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | ||
| @@ -33,16 +33,16 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 33 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 33 | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | ||
| 34 | switch numFields { | 34 | switch numFields { | ||
| 35 | case 4: | 35 | case 4: | ||
| 36 | - | r1 = syscall.f2<<32 | syscall.f1 | 36 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 |
| 37 | - | r2 = syscall.f4<<32 | syscall.f3 | 37 | + | r2 = syscall.f4<<32 | syscall.f3&math.MaxUint32 |
| 38 | case 3: | 38 | case 3: | ||
| 39 | - | r1 = syscall.f2<<32 | syscall.f1 | 39 | + | r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32 |
| 40 | r2 = syscall.f3 | 40 | r2 = syscall.f3 | ||
| 41 | case 2: | 41 | case 2: | ||
| 42 | r1 = syscall.f1 | 42 | r1 = syscall.f1 | ||
| 43 | r2 = syscall.f2 | 43 | r2 = syscall.f2 | ||
| 44 | default: | 44 | default: | ||
| 45 | - | panic("unreachable") | 45 | + | panic("not reached") |
| 46 | } | 46 | } | ||
| 47 | } | 47 | } | ||
| 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | 48 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | ||
| @@ -54,7 +54,7 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||||
| 54 | case 3: | 54 | case 3: | ||
| 55 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() | 55 | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() | ||
| 56 | default: | 56 | default: | ||
| 57 | - | panic("unreachable") | 57 | + | panic("not reached") |
| 58 | } | 58 | } | ||
| 59 | } | 59 | } | ||
| 60 | // create struct from the Go pointer created in arm64_r8 | 60 | // create struct from the Go pointer created in arm64_r8 | ||
| @@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||||
| 190 | shift = 0 | 190 | shift = 0 | ||
| 191 | flushed = true | 191 | flushed = true | ||
| 192 | class = _NO_CLASS | 192 | class = _NO_CLASS | ||
| 193 | - | case reflect.Ptr, reflect.UnsafePointer: | 193 | + | case reflect.Pointer, reflect.UnsafePointer: |
| 194 | addInt(f.Pointer()) | 194 | addInt(f.Pointer()) | ||
| 195 | shift = 0 | 195 | shift = 0 | ||
| 196 | flushed = true | 196 | flushed = true | ||
| @@ -547,3 +547,199 @@ func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||||
| 547 | size := structType.Size() | 547 | size := structType.Size() | ||
| 548 | copyStruct8ByteChunks(ptr, size, addStack) | 548 | copyStruct8ByteChunks(ptr, size, addStack) | ||
| 549 | } | 549 | } | ||
| 550 | + | ||||
| 551 | + | // getCallbackStruct reads a struct argument from the callback frame on arm64. | |||
| 552 | + | // It mirrors the AAPCS64 rules used by addStruct for the Go→C path. | |||
| 553 | + | // | |||
| 554 | + | // getCallbackStruct is only used on Unix. On Windows, callbacks are handled by | |||
| 555 | + | // the runtime's own callback mechanism, so this function is compiled but unused. | |||
| 556 | + | // | |||
| 557 | + | // AAPCS64 struct argument passing rules: | |||
| 558 | + | // - HFA (Homogeneous Float Aggregate): each float field in a separate float register | |||
| 559 | + | // - Non-HFA ≤ 16 bytes: packed into 1–2 integer registers | |||
| 560 | + | // - > 16 bytes (not HFA): passed by pointer in integer register | |||
| 561 | + | // - Register overflow: struct goes on the stack | |||
| 562 | + | // - Darwin ARM64: byte-level packing on the stack | |||
| 563 | + | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |||
| 564 | + | switch runtime.GOOS { | |||
| 565 | + | case "darwin", "freebsd", "linux", "netbsd": | |||
| 566 | + | default: | |||
| 567 | + | panic("purego: getCallbackStruct is not supported on " + runtime.GOOS) | |||
| 568 | + | } | |||
| 569 | + | ||||
| 570 | + | f := (*[callbackMaxFrame]uintptr)(frame) | |||
| 571 | + | ||||
| 572 | + | if isHFA(inType) { | |||
| 573 | + | _, numFloatFields := isAllSameFloat(inType) | |||
| 574 | + | if *floatsN+numFloatFields <= numOfFloatRegisters() { | |||
| 575 | + | return readHFAFromRegisters(inType, f, floatsN, numFloatFields) | |||
| 576 | + | } | |||
| 577 | + | // Not enough float registers: entire struct goes on stack. | |||
| 578 | + | // Exhaust float registers per AAPCS64. | |||
| 579 | + | *floatsN = numOfFloatRegisters() | |||
| 580 | + | return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset) | |||
| 581 | + | } | |||
| 582 | + | ||||
| 583 | + | if size := inType.Size(); size <= 16 { | |||
| 584 | + | // Non-HFA composite ≤ 16 bytes: packed into 1–2 integer registers. | |||
| 585 | + | numSlots := int((size + 7) / 8) | |||
| 586 | + | if *intsN+numSlots <= numOfIntegerRegisters() { | |||
| 587 | + | pos := numOfFloatRegisters() + *intsN | |||
| 588 | + | var r1, r2 uintptr | |||
| 589 | + | r1 = f[pos] | |||
| 590 | + | *intsN++ | |||
| 591 | + | if numSlots == 2 { | |||
| 592 | + | r2 = f[pos+1] | |||
| 593 | + | *intsN++ | |||
| 594 | + | } | |||
| 595 | + | if numSlots == 1 { | |||
| 596 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |||
| 597 | + | } | |||
| 598 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |||
| 599 | + | } | |||
| 600 | + | // Not enough int registers: goes on stack. | |||
| 601 | + | return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset) | |||
| 602 | + | } | |||
| 603 | + | ||||
| 604 | + | // > 16 bytes, not HFA: passed by pointer in integer register. | |||
| 605 | + | if *intsN < numOfIntegerRegisters() { | |||
| 606 | + | ptr := f[numOfFloatRegisters()+*intsN] | |||
| 607 | + | *intsN++ | |||
| 608 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |||
| 609 | + | } | |||
| 610 | + | ||||
| 611 | + | // Pointer on stack (rare: all integer registers exhausted). | |||
| 612 | + | if runtime.GOOS == "darwin" { | |||
| 613 | + | ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeOf(uintptr(0))) | |||
| 614 | + | ptr := uintptr(ptrVal.Uint()) | |||
| 615 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |||
| 616 | + | } | |||
| 617 | + | ptr := f[*stackSlot] | |||
| 618 | + | *stackSlot++ | |||
| 619 | + | return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem() | |||
| 620 | + | } | |||
| 621 | + | ||||
| 622 | + | // readHFAFromRegisters reads an HFA (Homogeneous Float Aggregate) struct from | |||
| 623 | + | // float registers. Each float field occupies one float register slot. | |||
| 624 | + | // For float32, the value is in the low 32 bits of the register. | |||
| 625 | + | func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, floatsN *int, numFields int) reflect.Value { | |||
| 626 | + | size := inType.Size() | |||
| 627 | + | ||||
| 628 | + | // Determine the element type | |||
| 629 | + | root := inType.Field(0).Type | |||
| 630 | + | for root.Kind() == reflect.Struct { | |||
| 631 | + | root = root.Field(0).Type | |||
| 632 | + | } | |||
| 633 | + | isFloat32 := root.Kind() == reflect.Float32 | |||
| 634 | + | ||||
| 635 | + | if isFloat32 { | |||
| 636 | + | // Each float32 is in a separate register (low 32 bits). | |||
| 637 | + | // Pack pairs into uintptrs to match the struct memory layout. | |||
| 638 | + | switch { | |||
| 639 | + | case size <= 8: | |||
| 640 | + | var r1 uintptr | |||
| 641 | + | switch numFields { | |||
| 642 | + | case 1: | |||
| 643 | + | r1 = f[*floatsN] | |||
| 644 | + | case 2: | |||
| 645 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |||
| 646 | + | } | |||
| 647 | + | *floatsN += numFields | |||
| 648 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |||
| 649 | + | case size <= 16: | |||
| 650 | + | var r1, r2 uintptr | |||
| 651 | + | switch numFields { | |||
| 652 | + | case 3: | |||
| 653 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |||
| 654 | + | r2 = f[*floatsN+2] | |||
| 655 | + | case 4: | |||
| 656 | + | r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32 | |||
| 657 | + | r2 = f[*floatsN+3]<<32 | f[*floatsN+2]&math.MaxUint32 | |||
| 658 | + | } | |||
| 659 | + | *floatsN += numFields | |||
| 660 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |||
| 661 | + | default: | |||
| 662 | + | panic("not reached") | |||
| 663 | + | } | |||
| 664 | + | } | |||
| 665 | + | ||||
| 666 | + | // float64: each field occupies one full register. | |||
| 667 | + | switch numFields { | |||
| 668 | + | case 1: | |||
| 669 | + | r1 := f[*floatsN] | |||
| 670 | + | *floatsN++ | |||
| 671 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |||
| 672 | + | case 2: | |||
| 673 | + | r1, r2 := f[*floatsN], f[*floatsN+1] | |||
| 674 | + | *floatsN += 2 | |||
| 675 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |||
| 676 | + | case 3: | |||
| 677 | + | r1, r2, r3 := f[*floatsN], f[*floatsN+1], f[*floatsN+2] | |||
| 678 | + | *floatsN += 3 | |||
| 679 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c uintptr }{r1, r2, r3})).Elem() | |||
| 680 | + | case 4: | |||
| 681 | + | r1, r2, r3, r4 := f[*floatsN], f[*floatsN+1], f[*floatsN+2], f[*floatsN+3] | |||
| 682 | + | *floatsN += 4 | |||
| 683 | + | return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{r1, r2, r3, r4})).Elem() | |||
| 684 | + | default: | |||
| 685 | + | panic("purego: HFA with more than 4 fields is not supported") | |||
| 686 | + | } | |||
| 687 | + | } | |||
| 688 | + | ||||
| 689 | + | // readStructFromStackArm64 reads a struct argument from the stack area of the | |||
| 690 | + | // callback frame. On Darwin ARM64, arguments are byte-packed on the stack. | |||
| 691 | + | // On Linux ARM64, arguments are 8-byte aligned. | |||
| 692 | + | func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |||
| 693 | + | if runtime.GOOS == "darwin" { | |||
| 694 | + | return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType) | |||
| 695 | + | } | |||
| 696 | + | // Linux ARM64: 8-byte aligned slots. | |||
| 697 | + | numSlots := int((inType.Size() + 7) / 8) | |||
| 698 | + | v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem() | |||
| 699 | + | *stackSlot += numSlots | |||
| 700 | + | return v | |||
| 701 | + | } | |||
| 702 | + | ||||
| 703 | + | func setStruct(a *callbackArgs, ret reflect.Value) { | |||
| 704 | + | outSize := ret.Type().Size() | |||
| 705 | + | switch { | |||
| 706 | + | case outSize == 0: | |||
| 707 | + | return | |||
| 708 | + | case outSize <= 8: | |||
| 709 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |||
| 710 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields == 2 { | |||
| 711 | + | a.result[1] = a.result[0] >> 32 // expanding two float32s into a.result[0] and a.result[1] | |||
| 712 | + | a.result[0] &= math.MaxUint32 // clear the top bits since they contain the second argument | |||
| 713 | + | } | |||
| 714 | + | return | |||
| 715 | + | case outSize <= 16: | |||
| 716 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |||
| 717 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats { | |||
| 718 | + | switch numFields { | |||
| 719 | + | case 4: | |||
| 720 | + | a.result[3] = a.result[1] >> 32 | |||
| 721 | + | a.result[2] = a.result[1] & math.MaxUint32 | |||
| 722 | + | a.result[1] = a.result[0] >> 32 | |||
| 723 | + | a.result[0] &= math.MaxUint32 | |||
| 724 | + | case 3: | |||
| 725 | + | a.result[2] = a.result[1] & math.MaxUint32 | |||
| 726 | + | a.result[1] = a.result[0] >> 32 | |||
| 727 | + | a.result[0] &= math.MaxUint32 | |||
| 728 | + | case 2: | |||
| 729 | + | // two float64s are already in a.result[0] and a.result[1] | |||
| 730 | + | default: | |||
| 731 | + | panic("not reached") | |||
| 732 | + | } | |||
| 733 | + | } | |||
| 734 | + | return | |||
| 735 | + | default: | |||
| 736 | + | if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields <= 4 { | |||
| 737 | + | reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret) | |||
| 738 | + | return | |||
| 739 | + | } | |||
| 740 | + | // The caller passed the address to place the return struct, | |||
| 741 | + | // so copy the Go struct into the provided memory. | |||
| 742 | + | reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret) | |||
| 743 | + | return | |||
| 744 | + | } | |||
| 745 | + | } | |||