detached c9e35fa

purego: support float arguments in RegisterFunc (#69)

This commit adds proper float support into RegisterFunc. It puts the first 8 in registers and the 9th onto the stack. It also checks beforehand that there aren't too many arguments.
commit
c9e35fa8a6a11df664933e8d7ce2451ecee3e4f0
author
TotallyGamerJet · · 2022-11-15 11:51 UTC
committer
GitHub · · 2022-11-15 11:51 UTC
parent
c99251d
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
4 files · +128 -45
M func_darwin.go +87 -10
@@ -68,6 +68,40 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
6868 if ty.NumOut() > 1 {
6969 panic("purego: function can only return zero or one values")
7070 }
71+ if cfn == 0 {
72+ panic("purego: cfn is nil")
73+ }
74+ {
75+ // this code checks how many registers and stack this function will use
76+ // to avoid crashing with too many arguments
77+ var ints int
78+ var floats int
79+ var stack int
80+ for i := 0; i < ty.NumIn(); i++ {
81+ arg := ty.In(i)
82+ switch arg.Kind() {
83+ case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
84+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
85+ reflect.Func, reflect.Bool:
86+ if ints < numOfIntegerRegisters() {
87+ ints++
88+ } else {
89+ stack++
90+ }
91+ case reflect.Float32, reflect.Float64:
92+ if floats < 8 {
93+ floats++
94+ } else {
95+ stack++
96+ }
97+ default:
98+ panic("purego: unsupported kind " + arg.Kind().String())
99+ }
100+ }
101+ if ints+stack > maxArgs || floats+stack > maxArgs {
102+ panic("purego: too many arguments")
103+ }
104+ }
71105 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
72106 if len(args) > 0 {
73107 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
@@ -81,37 +115,69 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
81115 args = tmp
82116 }
83117 }
84- var sysargs = make([]uintptr, len(args))
118+ var sysargs [maxArgs]uintptr
119+ var stack = sysargs[numOfIntegerRegisters():]
120+ var floats [8]float64
121+ var numInts int
122+ var numFloats int
123+ var numStack int
124+ addStack := func(x uintptr) {
125+ stack[numStack] = x
126+ numStack++
127+ }
128+ addInt := func(x uintptr) {
129+ if numInts >= numOfIntegerRegisters() {
130+ addStack(x)
131+ } else {
132+ sysargs[numInts] = x
133+ numInts++
134+ }
135+ }
85136 var keepAlive []interface{}
86137 defer func() {
87138 runtime.KeepAlive(keepAlive)
88139 }()
89- for i, v := range args {
140+ for _, v := range args {
90141 switch v.Kind() {
91142 case reflect.String:
92143 ptr := strings.CString(v.String())
93144 keepAlive = append(keepAlive, ptr)
94- sysargs[i] = uintptr(unsafe.Pointer(ptr))
145+ addInt(uintptr(unsafe.Pointer(ptr)))
95146 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
96- sysargs[i] = uintptr(v.Uint())
147+ addInt(uintptr(v.Uint()))
97148 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
98- sysargs[i] = uintptr(v.Int())
149+ addInt(uintptr(v.Int()))
99150 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
100151 keepAlive = append(keepAlive, v.Pointer())
101- sysargs[i] = v.Pointer()
152+ addInt(v.Pointer())
102153 case reflect.Func:
103- sysargs[i] = NewCallback(v.Interface())
154+ addInt(NewCallback(v.Interface()))
104155 case reflect.Bool:
105156 if v.Bool() {
106- sysargs[i] = 1
157+ addInt(1)
158+ } else {
159+ addInt(0)
160+ }
161+ case reflect.Float32, reflect.Float64:
162+ if numFloats < len(floats) {
163+ floats[numFloats] = v.Float()
164+ numFloats++
107165 } else {
108- sysargs[i] = 0
166+ addStack(uintptr(math.Float64bits(v.Float())))
109167 }
110168 default:
111169 panic("purego: unsupported kind: " + v.Kind().String())
112170 }
113171 }
114- r1, r2, _ := SyscallN(cfn, sysargs...) //TODO: handle float32/64 and struct types
172+ // TODO: support structs
173+ syscall := syscall9Args{
174+ cfn,
175+ sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
176+ floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
177+ 0, 0, 0}
178+ runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
179+ r1, r2 := syscall.r1, syscall.r2
180+
115181 if ty.NumOut() == 0 {
116182 return nil
117183 }
@@ -146,3 +212,14 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
146212 })
147213 fn.Set(v)
148214 }
215+
216+func numOfIntegerRegisters() int {
217+ switch runtime.GOARCH {
218+ case "arm64":
219+ return 8
220+ case "amd64":
221+ return 6
222+ default:
223+ panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
224+ }
225+}
@@ -68,6 +68,40 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
68 if ty.NumOut() > 1 {68 if ty.NumOut() > 1 {
69 panic("purego: function can only return zero or one values")69 panic("purego: function can only return zero or one values")
70 }70 }
71+ if cfn == 0 {
72+ panic("purego: cfn is nil")
73+ }
74+ {
75+ // this code checks how many registers and stack this function will use
76+ // to avoid crashing with too many arguments
77+ var ints int
78+ var floats int
79+ var stack int
80+ for i := 0; i < ty.NumIn(); i++ {
81+ arg := ty.In(i)
82+ switch arg.Kind() {
83+ case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
84+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
85+ reflect.Func, reflect.Bool:
86+ if ints < numOfIntegerRegisters() {
87+ ints++
88+ } else {
89+ stack++
90+ }
91+ case reflect.Float32, reflect.Float64:
92+ if floats < 8 {
93+ floats++
94+ } else {
95+ stack++
96+ }
97+ default:
98+ panic("purego: unsupported kind " + arg.Kind().String())
99+ }
100+ }
101+ if ints+stack > maxArgs || floats+stack > maxArgs {
102+ panic("purego: too many arguments")
103+ }
104+ }
71 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {105 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
72 if len(args) > 0 {106 if len(args) > 0 {
73 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {107 if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
@@ -81,37 +115,69 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
81 args = tmp115 args = tmp
82 }116 }
83 }117 }
84- var sysargs = make([]uintptr, len(args))118+ var sysargs [maxArgs]uintptr
119+ var stack = sysargs[numOfIntegerRegisters():]
120+ var floats [8]float64
121+ var numInts int
122+ var numFloats int
123+ var numStack int
124+ addStack := func(x uintptr) {
125+ stack[numStack] = x
126+ numStack++
127+ }
128+ addInt := func(x uintptr) {
129+ if numInts >= numOfIntegerRegisters() {
130+ addStack(x)
131+ } else {
132+ sysargs[numInts] = x
133+ numInts++
134+ }
135+ }
85 var keepAlive []interface{}136 var keepAlive []interface{}
86 defer func() {137 defer func() {
87 runtime.KeepAlive(keepAlive)138 runtime.KeepAlive(keepAlive)
88 }()139 }()
89- for i, v := range args {140+ for _, v := range args {
90 switch v.Kind() {141 switch v.Kind() {
91 case reflect.String:142 case reflect.String:
92 ptr := strings.CString(v.String())143 ptr := strings.CString(v.String())
93 keepAlive = append(keepAlive, ptr)144 keepAlive = append(keepAlive, ptr)
94- sysargs[i] = uintptr(unsafe.Pointer(ptr))145+ addInt(uintptr(unsafe.Pointer(ptr)))
95 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:146 case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
96- sysargs[i] = uintptr(v.Uint())147+ addInt(uintptr(v.Uint()))
97 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:148 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
98- sysargs[i] = uintptr(v.Int())149+ addInt(uintptr(v.Int()))
99 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:150 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
100 keepAlive = append(keepAlive, v.Pointer())151 keepAlive = append(keepAlive, v.Pointer())
101- sysargs[i] = v.Pointer()152+ addInt(v.Pointer())
102 case reflect.Func:153 case reflect.Func:
103- sysargs[i] = NewCallback(v.Interface())154+ addInt(NewCallback(v.Interface()))
104 case reflect.Bool:155 case reflect.Bool:
105 if v.Bool() {156 if v.Bool() {
106- sysargs[i] = 1157+ addInt(1)
158+ } else {
159+ addInt(0)
160+ }
161+ case reflect.Float32, reflect.Float64:
162+ if numFloats < len(floats) {
163+ floats[numFloats] = v.Float()
164+ numFloats++
107 } else {165 } else {
108- sysargs[i] = 0166+ addStack(uintptr(math.Float64bits(v.Float())))
109 }167 }
110 default:168 default:
111 panic("purego: unsupported kind: " + v.Kind().String())169 panic("purego: unsupported kind: " + v.Kind().String())
112 }170 }
113 }171 }
114- r1, r2, _ := SyscallN(cfn, sysargs...) //TODO: handle float32/64 and struct types172+ // TODO: support structs
173+ syscall := syscall9Args{
174+ cfn,
175+ sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
176+ floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
177+ 0, 0, 0}
178+ runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
179+ r1, r2 := syscall.r1, syscall.r2
180+
115 if ty.NumOut() == 0 {181 if ty.NumOut() == 0 {
116 return nil182 return nil
117 }183 }
@@ -146,3 +212,14 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
146 })212 })
147 fn.Set(v)213 fn.Set(v)
148 }214 }
215+
216+func numOfIntegerRegisters() int {
217+ switch runtime.GOARCH {
218+ case "arm64":
219+ return 8
220+ case "amd64":
221+ return 6
222+ default:
223+ panic("purego: unknown GOARCH (" + runtime.GOARCH + ")")
224+ }
225+}
M sys_darwin_amd64.s +21 -22
@@ -31,28 +31,27 @@ TEXT syscall9X(SB), NOSPLIT, $0
3131 PUSHQ BP
3232 MOVQ SP, BP
3333 SUBQ $32, SP
34- MOVQ DI, 24(BP) // save the pointer
35- MOVQ syscall9Args_fn(DI), R10 // fn
36- MOVQ syscall9Args_a2(DI), SI // a2
37- MOVQ syscall9Args_a3(DI), DX // a3
38- MOVQ syscall9Args_a4(DI), CX // a4
39- MOVQ syscall9Args_a5(DI), R8 // a5
40- MOVQ syscall9Args_a6(DI), R9 // a6
41- MOVQ syscall9Args_a7(DI), R11 // a7
42- MOVQ syscall9Args_a8(DI), R12 // a8
43- MOVQ syscall9Args_a9(DI), R13 // a9
44- MOVQ syscall9Args_a1(DI), DI // a1
45-
46- // these may be float arguments
47- // so we put them also where C expects floats
48- MOVQ DI, X0 // a1
49- MOVQ SI, X1 // a2
50- MOVQ DX, X2 // a3
51- MOVQ CX, X3 // a4
52- MOVQ R8, X4 // a5
53- MOVQ R9, X5 // a6
54- MOVQ R11, X6 // a7
55- MOVQ R12, X7 // a8
34+ MOVQ DI, 24(BP) // save the pointer
35+
36+ MOVQ syscall9Args_f1(DI), X0 // f1
37+ MOVQ syscall9Args_f2(DI), X1 // f2
38+ MOVQ syscall9Args_f3(DI), X2 // f3
39+ MOVQ syscall9Args_f4(DI), X3 // f4
40+ MOVQ syscall9Args_f5(DI), X4 // f5
41+ MOVQ syscall9Args_f6(DI), X5 // f6
42+ MOVQ syscall9Args_f7(DI), X6 // f7
43+ MOVQ syscall9Args_f8(DI), X7 // f8
44+
45+ MOVQ syscall9Args_fn(DI), R10 // fn
46+ MOVQ syscall9Args_a2(DI), SI // a2
47+ MOVQ syscall9Args_a3(DI), DX // a3
48+ MOVQ syscall9Args_a4(DI), CX // a4
49+ MOVQ syscall9Args_a5(DI), R8 // a5
50+ MOVQ syscall9Args_a6(DI), R9 // a6
51+ MOVQ syscall9Args_a7(DI), R11 // a7
52+ MOVQ syscall9Args_a8(DI), R12 // a8
53+ MOVQ syscall9Args_a9(DI), R13 // a9
54+ MOVQ syscall9Args_a1(DI), DI // a1
5655
5756 // push the remaining paramters onto the stack
5857 MOVQ R11, 0(SP) // push a7
@@ -31,28 +31,27 @@ TEXT syscall9X(SB), NOSPLIT, $0
31 PUSHQ BP31 PUSHQ BP
32 MOVQ SP, BP32 MOVQ SP, BP
33 SUBQ $32, SP33 SUBQ $32, SP
34- MOVQ DI, 24(BP) // save the pointer34+ MOVQ DI, 24(BP) // save the pointer
35- MOVQ syscall9Args_fn(DI), R10 // fn35+
36- MOVQ syscall9Args_a2(DI), SI // a236+ MOVQ syscall9Args_f1(DI), X0 // f1
37- MOVQ syscall9Args_a3(DI), DX // a337+ MOVQ syscall9Args_f2(DI), X1 // f2
38- MOVQ syscall9Args_a4(DI), CX // a438+ MOVQ syscall9Args_f3(DI), X2 // f3
39- MOVQ syscall9Args_a5(DI), R8 // a539+ MOVQ syscall9Args_f4(DI), X3 // f4
40- MOVQ syscall9Args_a6(DI), R9 // a640+ MOVQ syscall9Args_f5(DI), X4 // f5
41- MOVQ syscall9Args_a7(DI), R11 // a741+ MOVQ syscall9Args_f6(DI), X5 // f6
42- MOVQ syscall9Args_a8(DI), R12 // a842+ MOVQ syscall9Args_f7(DI), X6 // f7
43- MOVQ syscall9Args_a9(DI), R13 // a943+ MOVQ syscall9Args_f8(DI), X7 // f8
44- MOVQ syscall9Args_a1(DI), DI // a144+
45-45+ MOVQ syscall9Args_fn(DI), R10 // fn
46- // these may be float arguments46+ MOVQ syscall9Args_a2(DI), SI // a2
47- // so we put them also where C expects floats47+ MOVQ syscall9Args_a3(DI), DX // a3
48- MOVQ DI, X0 // a148+ MOVQ syscall9Args_a4(DI), CX // a4
49- MOVQ SI, X1 // a249+ MOVQ syscall9Args_a5(DI), R8 // a5
50- MOVQ DX, X2 // a350+ MOVQ syscall9Args_a6(DI), R9 // a6
51- MOVQ CX, X3 // a451+ MOVQ syscall9Args_a7(DI), R11 // a7
52- MOVQ R8, X4 // a552+ MOVQ syscall9Args_a8(DI), R12 // a8
53- MOVQ R9, X5 // a653+ MOVQ syscall9Args_a9(DI), R13 // a9
54- MOVQ R11, X6 // a754+ MOVQ syscall9Args_a1(DI), DI // a1
55- MOVQ R12, X7 // a8
56 55
57 // push the remaining paramters onto the stack56 // push the remaining paramters onto the stack
58 MOVQ R11, 0(SP) // push a757 MOVQ R11, 0(SP) // push a7
M sys_darwin_arm64.s +9 -11
@@ -31,6 +31,15 @@ TEXT syscall9X(SB), NOSPLIT, $0
3131 SUB $16, RSP // push structure pointer
3232 MOVD R0, 8(RSP)
3333
34+ FMOVD syscall9Args_f1(R0), F0 // f1
35+ FMOVD syscall9Args_f2(R0), F1 // f2
36+ FMOVD syscall9Args_f3(R0), F2 // f3
37+ FMOVD syscall9Args_f4(R0), F3 // f4
38+ FMOVD syscall9Args_f5(R0), F4 // f5
39+ FMOVD syscall9Args_f6(R0), F5 // f6
40+ FMOVD syscall9Args_f7(R0), F6 // f7
41+ FMOVD syscall9Args_f8(R0), F7 // f8
42+
3443 MOVD syscall9Args_fn(R0), R12 // fn
3544 MOVD syscall9Args_a2(R0), R1 // a2
3645 MOVD syscall9Args_a3(R0), R2 // a3
@@ -42,17 +51,6 @@ TEXT syscall9X(SB), NOSPLIT, $0
4251 MOVD syscall9Args_a9(R0), R8 // a9
4352 MOVD syscall9Args_a1(R0), R0 // a1
4453
45- // these may be float arguments
46- // so we put them also where C expects floats
47- FMOVD R0, F0 // a1
48- FMOVD R1, F1 // a2
49- FMOVD R2, F2 // a3
50- FMOVD R3, F3 // a4
51- FMOVD R4, F4 // a5
52- FMOVD R5, F5 // a6
53- FMOVD R6, F6 // a7
54- FMOVD R7, F7 // a8
55-
5654 MOVD R8, (RSP) // push a9 onto stack
5755
5856 BL (R12)
@@ -31,6 +31,15 @@ TEXT syscall9X(SB), NOSPLIT, $0
31 SUB $16, RSP // push structure pointer31 SUB $16, RSP // push structure pointer
32 MOVD R0, 8(RSP)32 MOVD R0, 8(RSP)
33 33
34+ FMOVD syscall9Args_f1(R0), F0 // f1
35+ FMOVD syscall9Args_f2(R0), F1 // f2
36+ FMOVD syscall9Args_f3(R0), F2 // f3
37+ FMOVD syscall9Args_f4(R0), F3 // f4
38+ FMOVD syscall9Args_f5(R0), F4 // f5
39+ FMOVD syscall9Args_f6(R0), F5 // f6
40+ FMOVD syscall9Args_f7(R0), F6 // f7
41+ FMOVD syscall9Args_f8(R0), F7 // f8
42+
34 MOVD syscall9Args_fn(R0), R12 // fn43 MOVD syscall9Args_fn(R0), R12 // fn
35 MOVD syscall9Args_a2(R0), R1 // a244 MOVD syscall9Args_a2(R0), R1 // a2
36 MOVD syscall9Args_a3(R0), R2 // a345 MOVD syscall9Args_a3(R0), R2 // a3
@@ -42,17 +51,6 @@ TEXT syscall9X(SB), NOSPLIT, $0
42 MOVD syscall9Args_a9(R0), R8 // a951 MOVD syscall9Args_a9(R0), R8 // a9
43 MOVD syscall9Args_a1(R0), R0 // a152 MOVD syscall9Args_a1(R0), R0 // a1
44 53
45- // these may be float arguments
46- // so we put them also where C expects floats
47- FMOVD R0, F0 // a1
48- FMOVD R1, F1 // a2
49- FMOVD R2, F2 // a3
50- FMOVD R3, F3 // a4
51- FMOVD R4, F4 // a5
52- FMOVD R5, F5 // a6
53- FMOVD R6, F6 // a7
54- FMOVD R7, F7 // a8
55-
56 MOVD R8, (RSP) // push a9 onto stack54 MOVD R8, (RSP) // push a9 onto stack
57 55
58 BL (R12)56 BL (R12)
M syscall_darwin.go +11 -2
@@ -4,6 +4,7 @@
44 package purego
55
66 import (
7+ "math"
78 "reflect"
89 "runtime"
910 "sync"
@@ -12,11 +13,19 @@ import (
1213
1314 var syscall9XABI0 uintptr
1415
15-type syscall9Args struct{ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, r1, r2, err uintptr }
16+type syscall9Args struct {
17+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr
18+ f1, f2, f3, f4, f5, f6, f7, f8 float64
19+ r1, r2, err uintptr
20+}
1621
1722 //go:nosplit
1823 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
19- args := syscall9Args{fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, r1, r2, err}
24+ args := syscall9Args{fn, a1, a2, a3, a4, a5, a6, a7, a8, a9,
25+ math.Float64frombits(uint64(a1)), math.Float64frombits(uint64(a2)), math.Float64frombits(uint64(a3)),
26+ math.Float64frombits(uint64(a4)), math.Float64frombits(uint64(a5)), math.Float64frombits(uint64(a6)),
27+ math.Float64frombits(uint64(a7)), math.Float64frombits(uint64(a8)),
28+ r1, r2, err}
2029 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args))
2130 return args.r1, args.r2, args.err
2231 }
@@ -4,6 +4,7 @@
4 package purego4 package purego
5 5
6 import (6 import (
7+ "math"
7 "reflect"8 "reflect"
8 "runtime"9 "runtime"
9 "sync"10 "sync"
@@ -12,11 +13,19 @@ import (
12 13
13 var syscall9XABI0 uintptr14 var syscall9XABI0 uintptr
14 15
15-type syscall9Args struct{ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, r1, r2, err uintptr }16+type syscall9Args struct {
17+ fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr
18+ f1, f2, f3, f4, f5, f6, f7, f8 float64
19+ r1, r2, err uintptr
20+}
16 21
17 //go:nosplit22 //go:nosplit
18 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {23 func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
19- args := syscall9Args{fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, r1, r2, err}24+ args := syscall9Args{fn, a1, a2, a3, a4, a5, a6, a7, a8, a9,
25+ math.Float64frombits(uint64(a1)), math.Float64frombits(uint64(a2)), math.Float64frombits(uint64(a3)),
26+ math.Float64frombits(uint64(a4)), math.Float64frombits(uint64(a5)), math.Float64frombits(uint64(a6)),
27+ math.Float64frombits(uint64(a7)), math.Float64frombits(uint64(a8)),
28+ r1, r2, err}
20 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args))29 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args))
21 return args.r1, args.r2, args.err30 return args.r1, args.r2, args.err
22 }31 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close