@@ -369,4 +369,65 @@ TEST(ImageRemoval, NativePrograms) {
369369 EXPECT_TRUE (PM.getNativePrograms ().count (ProgramA) > 0 );
370370 EXPECT_TRUE (PM.getNativePrograms ().count (ProgramB) > 0 );
371371}
372+
373+ // Verify that removeImages cleans up device global initializer entries so that
374+ // a reused program handle does not collide with stale state.
375+ TEST (ImageRemoval, DeviceGlobalInitializerCleanupOnRemoveImages) {
376+ ProgramManagerExposed PM;
377+
378+ // An image with device globals that we will add and then remove.
379+ sycl_device_binary_struct NativeImagesForRemoval[ImagesToRemove.size ()];
380+ sycl_device_binaries_struct TestBinaries;
381+ convertAndAddImages (PM, ImagesToRemove, NativeImagesForRemoval, TestBinaries);
382+
383+ PM.addOrInitDeviceGlobalEntry (&DeviceGlobalC,
384+ generateRefName (" C" , " DeviceGlobal" ).c_str ());
385+
386+ sycl::platform Plt = sycl::platform ();
387+ const sycl::device Dev = Plt.get_devices ()[0 ];
388+ sycl::queue Queue{Dev};
389+ auto Ctx = Queue.get_context ();
390+ auto CtxImpl = sycl::detail::getSyclObjImpl (Ctx);
391+
392+ // Grab the RTDeviceBinaryImage* for the "C" image that was just added.
393+ ASSERT_EQ (PM.getDeviceImages ().size (), 1u );
394+ const sycl::detail::RTDeviceBinaryImage *BinImg =
395+ PM.getDeviceImages ().begin ()->second .get ();
396+
397+ ur_program_handle_t FakeProgram =
398+ reinterpret_cast <ur_program_handle_t >(static_cast <uintptr_t >(0xDEADBEEF ));
399+
400+ // Register the fake program in NativePrograms so that removeImages can find
401+ // it and trigger removeDeviceGlobalInitializer, mirroring what
402+ // ProgramManager::build() does.
403+ PM.getNativePrograms ().insert ({FakeProgram, {CtxImpl, BinImg}});
404+
405+ CtxImpl->addDeviceGlobalInitializer (FakeProgram, CtxImpl->getDevices (),
406+ BinImg);
407+
408+ EXPECT_EQ (CtxImpl->getDeviceGlobalNotInitializedCnt (), 1u )
409+ << " Counter should be 1 after adding the initializer" ;
410+
411+ // Simulate program teardown.
412+ PM.removeImages (&TestBinaries);
413+
414+ EXPECT_EQ (CtxImpl->getDeviceGlobalNotInitializedCnt (), 0u )
415+ << " Counter should be 0 after removeImages cleans up the initializer" ;
416+
417+ // Re-add the same BinImg under the same (reused) program handle — this is
418+ // what happens when the adapter allocates a fresh program with the same
419+ // handle value. The counter must go back up to 1 cleanly.
420+ sycl_device_binary_struct NativeImagesSecond[ImagesToRemove.size ()];
421+ sycl_device_binaries_struct TestBinariesSecond;
422+ convertAndAddImages (PM, ImagesToRemove, NativeImagesSecond,
423+ TestBinariesSecond);
424+ const sycl::detail::RTDeviceBinaryImage *BinImgSecond =
425+ PM.getDeviceImages ().begin ()->second .get ();
426+
427+ CtxImpl->addDeviceGlobalInitializer (FakeProgram, CtxImpl->getDevices (),
428+ BinImgSecond);
429+
430+ EXPECT_EQ (CtxImpl->getDeviceGlobalNotInitializedCnt (), 1u )
431+ << " Counter should be 1 after re-registering the reused program handle" ;
432+ }
372433} // anonymous namespace
0 commit comments