1// Copyright 2023 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package runtime_test 6 7import ( 8 "runtime" 9 "testing" 10 "time" 11 "unsafe" 12) 13 14type obj struct { 15 x int64 16 y int64 17 z int64 18} 19 20type objWith[T any] struct { 21 x int64 22 y int64 23 z int64 24 o T 25} 26 27var ( 28 globalUintptr uintptr 29 globalPtrToObj = &obj{} 30 globalPtrToObjWithPtr = &objWith[*uintptr]{} 31 globalPtrToRuntimeObj = func() *obj { return &obj{} }() 32 globalPtrToRuntimeObjWithPtr = func() *objWith[*uintptr] { return &objWith[*uintptr]{} }() 33) 34 35func assertDidPanic(t *testing.T) { 36 if recover() == nil { 37 t.Fatal("did not panic") 38 } 39} 40 41func assertCgoCheckPanics(t *testing.T, p any) { 42 defer func() { 43 if recover() == nil { 44 t.Fatal("cgoCheckPointer() did not panic, make sure the tests run with cgocheck=1") 45 } 46 }() 47 runtime.CgoCheckPointer(p, true) 48} 49 50func TestPinnerSimple(t *testing.T) { 51 var pinner runtime.Pinner 52 p := new(obj) 53 addr := unsafe.Pointer(p) 54 if runtime.IsPinned(addr) { 55 t.Fatal("already marked as pinned") 56 } 57 pinner.Pin(p) 58 if !runtime.IsPinned(addr) { 59 t.Fatal("not marked as pinned") 60 } 61 if runtime.GetPinCounter(addr) != nil { 62 t.Fatal("pin counter should not exist") 63 } 64 pinner.Unpin() 65 if runtime.IsPinned(addr) { 66 t.Fatal("still marked as pinned") 67 } 68} 69 70func TestPinnerPinKeepsAliveAndReleases(t *testing.T) { 71 var pinner runtime.Pinner 72 p := new(obj) 73 done := make(chan struct{}) 74 runtime.SetFinalizer(p, func(any) { 75 done <- struct{}{} 76 }) 77 pinner.Pin(p) 78 p = nil 79 runtime.GC() 80 runtime.GC() 81 select { 82 case <-done: 83 t.Fatal("Pin() didn't keep object alive") 84 case <-time.After(time.Millisecond * 10): 85 break 86 } 87 pinner.Unpin() 88 runtime.GC() 89 runtime.GC() 90 select { 91 case <-done: 92 break 93 case <-time.After(time.Second): 94 t.Fatal("Unpin() didn't release object") 95 } 96} 97 98func TestPinnerMultiplePinsSame(t *testing.T) { 99 const N = 100 100 var pinner runtime.Pinner 101 p := new(obj) 102 addr := unsafe.Pointer(p) 103 if runtime.IsPinned(addr) { 104 t.Fatal("already marked as pinned") 105 } 106 for i := 0; i < N; i++ { 107 pinner.Pin(p) 108 } 109 if !runtime.IsPinned(addr) { 110 t.Fatal("not marked as pinned") 111 } 112 if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != N-1 { 113 t.Fatalf("pin counter incorrect: %d", *cnt) 114 } 115 pinner.Unpin() 116 if runtime.IsPinned(addr) { 117 t.Fatal("still marked as pinned") 118 } 119 if runtime.GetPinCounter(addr) != nil { 120 t.Fatal("pin counter was not deleted") 121 } 122} 123 124func TestPinnerTwoPinner(t *testing.T) { 125 var pinner1, pinner2 runtime.Pinner 126 p := new(obj) 127 addr := unsafe.Pointer(p) 128 if runtime.IsPinned(addr) { 129 t.Fatal("already marked as pinned") 130 } 131 pinner1.Pin(p) 132 if !runtime.IsPinned(addr) { 133 t.Fatal("not marked as pinned") 134 } 135 if runtime.GetPinCounter(addr) != nil { 136 t.Fatal("pin counter should not exist") 137 } 138 pinner2.Pin(p) 139 if !runtime.IsPinned(addr) { 140 t.Fatal("not marked as pinned") 141 } 142 if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != 1 { 143 t.Fatalf("pin counter incorrect: %d", *cnt) 144 } 145 pinner1.Unpin() 146 if !runtime.IsPinned(addr) { 147 t.Fatal("not marked as pinned") 148 } 149 if runtime.GetPinCounter(addr) != nil { 150 t.Fatal("pin counter should not exist") 151 } 152 pinner2.Unpin() 153 if runtime.IsPinned(addr) { 154 t.Fatal("still marked as pinned") 155 } 156 if runtime.GetPinCounter(addr) != nil { 157 t.Fatal("pin counter was not deleted") 158 } 159} 160 161func TestPinnerPinZerosizeObj(t *testing.T) { 162 var pinner runtime.Pinner 163 defer pinner.Unpin() 164 p := new(struct{}) 165 pinner.Pin(p) 166 if !runtime.IsPinned(unsafe.Pointer(p)) { 167 t.Fatal("not marked as pinned") 168 } 169} 170 171func TestPinnerPinGlobalPtr(t *testing.T) { 172 var pinner runtime.Pinner 173 defer pinner.Unpin() 174 pinner.Pin(globalPtrToObj) 175 pinner.Pin(globalPtrToObjWithPtr) 176 pinner.Pin(globalPtrToRuntimeObj) 177 pinner.Pin(globalPtrToRuntimeObjWithPtr) 178} 179 180func TestPinnerPinTinyObj(t *testing.T) { 181 var pinner runtime.Pinner 182 const N = 64 183 var addr [N]unsafe.Pointer 184 for i := 0; i < N; i++ { 185 p := new(bool) 186 addr[i] = unsafe.Pointer(p) 187 pinner.Pin(p) 188 pinner.Pin(p) 189 if !runtime.IsPinned(addr[i]) { 190 t.Fatalf("not marked as pinned: %d", i) 191 } 192 if cnt := runtime.GetPinCounter(addr[i]); cnt == nil || *cnt == 0 { 193 t.Fatalf("pin counter incorrect: %d, %d", *cnt, i) 194 } 195 } 196 pinner.Unpin() 197 for i := 0; i < N; i++ { 198 if runtime.IsPinned(addr[i]) { 199 t.Fatal("still marked as pinned") 200 } 201 if runtime.GetPinCounter(addr[i]) != nil { 202 t.Fatal("pin counter should not exist") 203 } 204 } 205} 206 207func TestPinnerInterface(t *testing.T) { 208 var pinner runtime.Pinner 209 o := new(obj) 210 ifc := any(o) 211 pinner.Pin(&ifc) 212 if !runtime.IsPinned(unsafe.Pointer(&ifc)) { 213 t.Fatal("not marked as pinned") 214 } 215 if runtime.IsPinned(unsafe.Pointer(o)) { 216 t.Fatal("marked as pinned") 217 } 218 pinner.Unpin() 219 pinner.Pin(ifc) 220 if !runtime.IsPinned(unsafe.Pointer(o)) { 221 t.Fatal("not marked as pinned") 222 } 223 if runtime.IsPinned(unsafe.Pointer(&ifc)) { 224 t.Fatal("marked as pinned") 225 } 226 pinner.Unpin() 227} 228 229func TestPinnerPinNonPtrPanics(t *testing.T) { 230 var pinner runtime.Pinner 231 defer pinner.Unpin() 232 var i int 233 defer assertDidPanic(t) 234 pinner.Pin(i) 235} 236 237func TestPinnerReuse(t *testing.T) { 238 var pinner runtime.Pinner 239 p := new(obj) 240 p2 := &p 241 assertCgoCheckPanics(t, p2) 242 pinner.Pin(p) 243 runtime.CgoCheckPointer(p2, true) 244 pinner.Unpin() 245 assertCgoCheckPanics(t, p2) 246 pinner.Pin(p) 247 runtime.CgoCheckPointer(p2, true) 248 pinner.Unpin() 249} 250 251func TestPinnerEmptyUnpin(t *testing.T) { 252 var pinner runtime.Pinner 253 pinner.Unpin() 254 pinner.Unpin() 255} 256 257func TestPinnerLeakPanics(t *testing.T) { 258 old := runtime.GetPinnerLeakPanic() 259 func() { 260 defer assertDidPanic(t) 261 old() 262 }() 263 done := make(chan struct{}) 264 runtime.SetPinnerLeakPanic(func() { 265 done <- struct{}{} 266 }) 267 func() { 268 var pinner runtime.Pinner 269 p := new(obj) 270 pinner.Pin(p) 271 }() 272 runtime.GC() 273 runtime.GC() 274 select { 275 case <-done: 276 break 277 case <-time.After(time.Second): 278 t.Fatal("leak didn't make GC to panic") 279 } 280 runtime.SetPinnerLeakPanic(old) 281} 282 283func TestPinnerCgoCheckPtr2Ptr(t *testing.T) { 284 var pinner runtime.Pinner 285 defer pinner.Unpin() 286 p := new(obj) 287 p2 := &objWith[*obj]{o: p} 288 assertCgoCheckPanics(t, p2) 289 pinner.Pin(p) 290 runtime.CgoCheckPointer(p2, true) 291} 292 293func TestPinnerCgoCheckPtr2UnsafePtr(t *testing.T) { 294 var pinner runtime.Pinner 295 defer pinner.Unpin() 296 p := unsafe.Pointer(new(obj)) 297 p2 := &objWith[unsafe.Pointer]{o: p} 298 assertCgoCheckPanics(t, p2) 299 pinner.Pin(p) 300 runtime.CgoCheckPointer(p2, true) 301} 302 303func TestPinnerCgoCheckPtr2UnknownPtr(t *testing.T) { 304 var pinner runtime.Pinner 305 defer pinner.Unpin() 306 p := unsafe.Pointer(new(obj)) 307 p2 := &p 308 func() { 309 defer assertDidPanic(t) 310 runtime.CgoCheckPointer(p2, nil) 311 }() 312 pinner.Pin(p) 313 runtime.CgoCheckPointer(p2, nil) 314} 315 316func TestPinnerCgoCheckInterface(t *testing.T) { 317 var pinner runtime.Pinner 318 defer pinner.Unpin() 319 var ifc any 320 var o obj 321 ifc = &o 322 p := &ifc 323 assertCgoCheckPanics(t, p) 324 pinner.Pin(&o) 325 runtime.CgoCheckPointer(p, true) 326} 327 328func TestPinnerCgoCheckSlice(t *testing.T) { 329 var pinner runtime.Pinner 330 defer pinner.Unpin() 331 sl := []int{1, 2, 3} 332 assertCgoCheckPanics(t, &sl) 333 pinner.Pin(&sl[0]) 334 runtime.CgoCheckPointer(&sl, true) 335} 336 337func TestPinnerCgoCheckString(t *testing.T) { 338 var pinner runtime.Pinner 339 defer pinner.Unpin() 340 b := []byte("foobar") 341 str := unsafe.String(&b[0], 6) 342 assertCgoCheckPanics(t, &str) 343 pinner.Pin(&b[0]) 344 runtime.CgoCheckPointer(&str, true) 345} 346 347func TestPinnerCgoCheckPinned2UnpinnedPanics(t *testing.T) { 348 var pinner runtime.Pinner 349 defer pinner.Unpin() 350 p := new(obj) 351 p2 := &objWith[*obj]{o: p} 352 assertCgoCheckPanics(t, p2) 353 pinner.Pin(p2) 354 assertCgoCheckPanics(t, p2) 355} 356 357func TestPinnerCgoCheckPtr2Pinned2Unpinned(t *testing.T) { 358 var pinner runtime.Pinner 359 defer pinner.Unpin() 360 p := new(obj) 361 p2 := &objWith[*obj]{o: p} 362 p3 := &objWith[*objWith[*obj]]{o: p2} 363 assertCgoCheckPanics(t, p2) 364 assertCgoCheckPanics(t, p3) 365 pinner.Pin(p2) 366 assertCgoCheckPanics(t, p2) 367 assertCgoCheckPanics(t, p3) 368 pinner.Pin(p) 369 runtime.CgoCheckPointer(p2, true) 370 runtime.CgoCheckPointer(p3, true) 371} 372 373func BenchmarkPinnerPinUnpinBatch(b *testing.B) { 374 const Batch = 1000 375 var data [Batch]*obj 376 for i := 0; i < Batch; i++ { 377 data[i] = new(obj) 378 } 379 b.ResetTimer() 380 for n := 0; n < b.N; n++ { 381 var pinner runtime.Pinner 382 for i := 0; i < Batch; i++ { 383 pinner.Pin(data[i]) 384 } 385 pinner.Unpin() 386 } 387} 388 389func BenchmarkPinnerPinUnpinBatchDouble(b *testing.B) { 390 const Batch = 1000 391 var data [Batch]*obj 392 for i := 0; i < Batch; i++ { 393 data[i] = new(obj) 394 } 395 b.ResetTimer() 396 for n := 0; n < b.N; n++ { 397 var pinner runtime.Pinner 398 for i := 0; i < Batch; i++ { 399 pinner.Pin(data[i]) 400 pinner.Pin(data[i]) 401 } 402 pinner.Unpin() 403 } 404} 405 406func BenchmarkPinnerPinUnpinBatchTiny(b *testing.B) { 407 const Batch = 1000 408 var data [Batch]*bool 409 for i := 0; i < Batch; i++ { 410 data[i] = new(bool) 411 } 412 b.ResetTimer() 413 for n := 0; n < b.N; n++ { 414 var pinner runtime.Pinner 415 for i := 0; i < Batch; i++ { 416 pinner.Pin(data[i]) 417 } 418 pinner.Unpin() 419 } 420} 421 422func BenchmarkPinnerPinUnpin(b *testing.B) { 423 p := new(obj) 424 for n := 0; n < b.N; n++ { 425 var pinner runtime.Pinner 426 pinner.Pin(p) 427 pinner.Unpin() 428 } 429} 430 431func BenchmarkPinnerPinUnpinTiny(b *testing.B) { 432 p := new(bool) 433 for n := 0; n < b.N; n++ { 434 var pinner runtime.Pinner 435 pinner.Pin(p) 436 pinner.Unpin() 437 } 438} 439 440func BenchmarkPinnerPinUnpinDouble(b *testing.B) { 441 p := new(obj) 442 for n := 0; n < b.N; n++ { 443 var pinner runtime.Pinner 444 pinner.Pin(p) 445 pinner.Pin(p) 446 pinner.Unpin() 447 } 448} 449 450func BenchmarkPinnerPinUnpinParallel(b *testing.B) { 451 b.RunParallel(func(pb *testing.PB) { 452 p := new(obj) 453 for pb.Next() { 454 var pinner runtime.Pinner 455 pinner.Pin(p) 456 pinner.Unpin() 457 } 458 }) 459} 460 461func BenchmarkPinnerPinUnpinParallelTiny(b *testing.B) { 462 b.RunParallel(func(pb *testing.PB) { 463 p := new(bool) 464 for pb.Next() { 465 var pinner runtime.Pinner 466 pinner.Pin(p) 467 pinner.Unpin() 468 } 469 }) 470} 471 472func BenchmarkPinnerPinUnpinParallelDouble(b *testing.B) { 473 b.RunParallel(func(pb *testing.PB) { 474 p := new(obj) 475 for pb.Next() { 476 var pinner runtime.Pinner 477 pinner.Pin(p) 478 pinner.Pin(p) 479 pinner.Unpin() 480 } 481 }) 482} 483 484func BenchmarkPinnerIsPinnedOnPinned(b *testing.B) { 485 var pinner runtime.Pinner 486 ptr := new(obj) 487 pinner.Pin(ptr) 488 b.ResetTimer() 489 for n := 0; n < b.N; n++ { 490 runtime.IsPinned(unsafe.Pointer(ptr)) 491 } 492 pinner.Unpin() 493} 494 495func BenchmarkPinnerIsPinnedOnUnpinned(b *testing.B) { 496 ptr := new(obj) 497 b.ResetTimer() 498 for n := 0; n < b.N; n++ { 499 runtime.IsPinned(unsafe.Pointer(ptr)) 500 } 501} 502 503func BenchmarkPinnerIsPinnedOnPinnedParallel(b *testing.B) { 504 var pinner runtime.Pinner 505 ptr := new(obj) 506 pinner.Pin(ptr) 507 b.ResetTimer() 508 b.RunParallel(func(pb *testing.PB) { 509 for pb.Next() { 510 runtime.IsPinned(unsafe.Pointer(ptr)) 511 } 512 }) 513 pinner.Unpin() 514} 515 516func BenchmarkPinnerIsPinnedOnUnpinnedParallel(b *testing.B) { 517 ptr := new(obj) 518 b.ResetTimer() 519 b.RunParallel(func(pb *testing.PB) { 520 for pb.Next() { 521 runtime.IsPinned(unsafe.Pointer(ptr)) 522 } 523 }) 524} 525 526// const string data is not in span. 527func TestPinnerConstStringData(t *testing.T) { 528 var pinner runtime.Pinner 529 str := "test-const-string" 530 p := unsafe.StringData(str) 531 addr := unsafe.Pointer(p) 532 if !runtime.IsPinned(addr) { 533 t.Fatal("not marked as pinned") 534 } 535 pinner.Pin(p) 536 pinner.Unpin() 537 if !runtime.IsPinned(addr) { 538 t.Fatal("not marked as pinned") 539 } 540} 541