Skip to content

Commit 9593560

Browse files
committed
SCP: Fix scp_asynch_check cross-thread interference
Address a Clang/LLVM sanitizer warning that scp_asynch_check is written by both the AIO and main threads, one thread potentially clobbering the other's value. The "scp_asynch_check = 0" at scp.c:239 is where the thread sanitizer detects the issue. This check is supposed to cause the main thread to process I/O updates on the next AIO_CHECK_EVENT code's execution. To preserve that behavior, AIO_CHECK_EVENT now executes AIO_UPDATE_QUEUE when either sim_asynch_check decrements below 0 or there is work to be done on the AIO queue (sim_asynch_queue != QUEUE_HEAD.) Code refactoring: - Eliminate the asymmetry between the lock-based (mutex) and lock-free implementations. - Lock-free: AIO_ILOCK/AIO_IUNLOCK do not reacquire sim_asynch_lock when compiler intrinsics are present (GCC, Clang, MS C and DEC C on Itanium.) - Lock-based: If DONT_USE_AIO_INTRINSICS is defined, the AIO implementation becomes lock-based via mutexes and AIO_ILOCK/- AIO_IUNLOCK recursively acquire/release sim_asynch_lock. - AIO defaults to the lock-based implementation if compiler intrinsics are not available. - GCC, Clang: Prefer the __atomic intrinsics over the deprecated __sync intrinsics. The __sync intrinics still exist for older GCC compilers. - sim_asynch_lock is a recursive mutex for both lock-based and lock-free implementations. Eliminates implementation asymmetry. - AIO_CHECK_EVENT invokes AIO_ILOCK and AIO_IUNLOCK so that the lock-based code cannot alter sim_asynch_queue when checking for pending I/O work. - sim_debug_io_lock: Debug output serialization lock. Previously, sim_asynch_lock was semantically overloaded to serialize output from _sim_debug_write_flush. This lock provides better semantic clarity. - New builder script flag to disable AIO lock-free, force AIO lock-based code: - cmake-builder.ps1 -noaiointrinsics ... - cmake-builder.sh -no-aio-intrinics ...
1 parent 1a1396d commit 9593560

6 files changed

Lines changed: 265 additions & 142 deletions

File tree

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ option(SIMH_PACKAGE_SUFFIX
229229
option(MAC_UNIVERSAL
230230
"macOS universal binary flag: TRUE -> build universal binaries, FALSE -> don't."
231231
${MAC_UNIVERSAL_OPTVAL})
232+
option(DONT_USE_AIO_INTRINSICS
233+
"Don't use compiler/platform intrinsics for AIO, revert to lock-based AIO"
234+
FALSE)
232235

233236
# Places where CMake should look for dependent package configuration fragments and artifacts:
234237
set(SIMH_PREFIX_PATH_LIST)

README-CMake.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -511,25 +511,22 @@ or video support.
511511
512512
# List the supported command line flags:
513513
$ cmake/cmake-builder.sh --help
514-
Configure and build simh simulators on Linux and *nix-like platforms.
514+
** cmake version 3.18.4
515515
516-
Subdirectories:
517-
cmake/build-unix: Makefile-based build simulators
518-
cmake/build-ninja: Ninja build-based simulators
516+
CMake suite maintained and supported by Kitware (kitware.com/cmake).
517+
Configure and build simh simulators on Linux and *nix-like platforms.
519518
520-
Options:
521-
--------
519+
Compile/Build options:
520+
----------------------
522521
--clean (-x) Remove the build subdirectory
523522
--generate (-g) Generate the build environment, don't compile/build
524523
--parallel (-p) Enable build parallelism (parallel builds)
525-
--nonetwork Build simulators without network support
526-
--novideo Build simulators without video support
527524
--notest Do not execute 'ctest' test cases
528525
--noinstall Do not install SIMH simulators.
529526
--testonly Do not build, execute the 'ctest' test cases
530527
--installonly Do not build, install the SIMH simulators
531528

532-
--flavor (-f) Specifies the build flavor. Valid flavors are:
529+
--flavor (-f) [Required] Specifies the build flavor. Valid flavors are:
533530
unix
534531
ninja
535532
xcode
@@ -541,8 +538,7 @@ or video support.
541538
--config (-c) Specifies the build configuration: 'Release' or 'Debug'
542539

543540
--target Build a specific simulator or simulators. Separate multiple
544-
targets by separating with a comma,
545-
e.g. "--target pdp8,pdp11,vax750,altairz80,3b2"
541+
targets with a comma, e.g. "--target pdp8,pdp11,vax750,altairz80,3b2"
546542
--lto Enable Link Time Optimization (LTO) in Release builds
547543
--debugWall Enable maximal warnings in Debug builds
548544
--cppcheck Enable cppcheck static code analysis rules
@@ -553,6 +549,17 @@ or video support.
553549
554550
--verbose Turn on verbose build output
555551
552+
SIMH feature control options:
553+
-----------------------------
554+
--nonetwork Build simulators without network support
555+
--novideo Build simulators without video support
556+
--no-aio-intrinsics
557+
Do not use compiler/platform intrinsics to implement AIO
558+
functions (aka "lock-free" AIO), reverts to lock-based AIO
559+
if threading libraries are detected.
560+
561+
Other options:
562+
--------------
556563
--help (-h) Print this help.
557564
```
558565
@@ -569,17 +576,17 @@ or video support.
569576
PS C:\...\open-simh> Get-Help -deatailed cmake\cmake-builder.ps1
570577
571578
NAME
572-
C:\Users\bsm21317\play\open-simh\cmake\cmake-builder.ps1
579+
C:\...\play\open-simh\cmake\cmake-builder.ps1
573580
574581
SYNOPSIS
575582
Configure and build SIMH's dependencies and simulators using the Microsoft Visual
576583
Studio C compiler or MinGW-W64-based gcc compiler.
577584

578585

579586
SYNTAX
580-
C:\Users\bsm21317\play\open-simh\cmake\cmake-builder.ps1 [[-flavor] <String>] [[-config] <String>] [[-cpack_suffix] <String>] [[-target] <String>]
581-
[-clean] [-help] [-nonetwork] [-novideo] [-notest] [-noinstall] [-parallel] [-generate] [-regenerate] [-testonly] [-installOnly] [-windeprecation]
582-
[-package] [-lto] [-debugWall] [-cppcheck] [<CommonParameters>]
587+
C:\...\play\open-simh\cmake\cmake-builder.ps1 [[-flavor] <String>] [[-config] <String>] [[-cpack_suffix] <String>] [[-target]
588+
<String[]>] [-clean] [-help] [-nonetwork] [-novideo] [-noaioinstrinsics] [-notest] [-noinstall] [-parallel] [-generate] [-testonly]
589+
[-installOnly] [-windeprecation] [-lto] [-debugWall] [-cppcheck] [<CommonParameters>]
583590

584591

585592
DESCRIPTION
@@ -588,9 +595,9 @@ or video support.
588595

589596
1. Configure and generate the build environment selected by '-flavor' option.
590597
2. Build missing runtime dependencies and the simulator suite with the compiler
591-
configuration selected by the '-config' option. The "Release" configuration
592-
generates optimized executables; the "Debug" configuration generates
593-
development executables with debugger information.
598+
configuration selected by the '-config' option. The "Release" configuration
599+
generates optimized executables; the "Debug" configuration generates
600+
development executables with debugger information.
594601
3. Test the simulators
595602

596603
There is an install phase that can be invoked separately as part of the SIMH
@@ -624,6 +631,9 @@ or video support.
624631
mingw-make MinGW GCC/mingw32-make
625632
mingw-ninja MinGW GCC/ninja
626633
634+
-config <String>
635+
The target build configuration. Valid values: "Release" and "Debug"
636+
627637
[...truncated for brevity...]
628638
```
629639

cmake/cmake-builder.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ showHelp()
77
cat <<EOF
88
Configure and build simh simulators on Linux and *nix-like platforms.
99
10-
-Compile/Build options:
11-
-----------------------
10+
Compile/Build options:
1211
--clean (-x) Remove the build subdirectory
1312
--generate (-g) Generate the build environment, don't compile/build
1413
--cache '--generate' and show CMake's variable cache

cmake/pthreads-dep.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ if (WITH_ASYNC)
7070
else ()
7171
set(AIO_FLAGS DONT_USE_READER_THREAD)
7272
endif ()
73-
else()
73+
74+
if (DONT_USE_AIO_INTRINSICS)
75+
target_compile_definitions(thread_lib INTERFACE DONT_USE_AIO_INTRINSICS)
76+
endif ()
77+
else ()
7478
target_compile_definitions(thread_lib INTERFACE DONT_USE_READER_THREAD)
7579
set(THREADING_PKG_STATUS "asynchronous I/O disabled.")
7680
endif()

scp.c

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -384,37 +384,68 @@ int32 sim_asynch_check;
384384
int32 sim_asynch_latency = 4000; /* 4 usec interrupt latency */
385385
int32 sim_asynch_inst_latency = 20; /* assume 5 mip simulator */
386386

387+
/* Debug flush mutex to serialize debug output. */
388+
pthread_mutex_t sim_debug_io_lock = PTHREAD_MUTEX_INITIALIZER;
389+
390+
/* aio_queue_worklist: Grab the current queue and replace sim_asynch_queue with
391+
* the empty queue.
392+
*
393+
* Returns the UNIT worklist to which sim_asynch_queue previously pointed.
394+
*/
395+
static SIM_INLINE volatile UNIT *aio_queue_worklist()
396+
{
397+
volatile UNIT *q;
398+
399+
do {
400+
/* Atomically load, wash-rinse-repeat if there is thread interference */
401+
q = sim_sync_load_pointer((void * volatile *) &sim_asynch_queue);
402+
} while (!sim_sync_cmpxchg((void * volatile *) &sim_asynch_queue, QUEUE_LIST_END, (void *) q));
403+
404+
return q;
405+
}
406+
407+
/* aio_enqueue_unit: Atomically add a UNIT to the sim_asynch_queue list.
408+
*/
409+
static SIM_INLINE void aio_enqueue_unit(UNIT *unit)
410+
{
411+
volatile UNIT *q;
412+
413+
do {
414+
q = sim_sync_load_pointer((void * volatile *) &sim_asynch_queue);
415+
unit->a_next = (UNIT *) q; /* Mark as on list */
416+
} while (!sim_sync_cmpxchg((void * volatile *) &sim_asynch_queue, unit, (void *) q));
417+
}
418+
387419
int sim_aio_update_queue (void)
388420
{
389421
int migrated = 0;
390422

391423
AIO_ILOCK;
392-
if (AIO_QUEUE_VAL != QUEUE_LIST_END) { /* List !Empty */
393-
UNIT *q, *uptr;
424+
if (!AIO_QUEUE_EMPTY()) {
425+
volatile UNIT *q;
426+
UNIT *uptr;
394427
int32 a_event_time;
395-
do { /* Grab current queue */
396-
q = AIO_QUEUE_VAL;
397-
} while (q != AIO_QUEUE_SET(QUEUE_LIST_END, q));
398-
while (q != QUEUE_LIST_END) { /* List !Empty */
399-
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Migrating Asynch event for %s after %d %s\n", sim_uname(q), q->a_event_time, sim_vm_interval_units);
428+
for (q = aio_queue_worklist(); q != QUEUE_LIST_END; /* empty */) {
429+
uptr = (UNIT *) q;
430+
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Migrating Asynch event for %s after %d %s\n",
431+
sim_uname(uptr), uptr->a_event_time, sim_vm_interval_units);
400432
++migrated;
401-
uptr = q;
402433
q = q->a_next;
403-
uptr->a_next = NULL; /* hygiene */
434+
uptr->a_next = NULL; /* hygiene */
404435
if (uptr->a_activate_call != &sim_activate_notbefore) {
405-
a_event_time = uptr->a_event_time-((sim_asynch_inst_latency+1)/2);
436+
a_event_time = uptr->a_event_time - ((sim_asynch_inst_latency + 1) / 2);
406437
if (a_event_time < 0)
407438
a_event_time = 0;
408439
}
409440
else
410441
a_event_time = uptr->a_event_time;
411-
AIO_IUNLOCK;
442+
412443
uptr->a_activate_call (uptr, a_event_time);
444+
413445
if (uptr->a_check_completion) {
414446
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Calling Completion Check for asynch event on %s\n", sim_uname(uptr));
415447
uptr->a_check_completion (uptr);
416448
}
417-
AIO_ILOCK;
418449
}
419450
}
420451
AIO_IUNLOCK;
@@ -423,22 +454,19 @@ return migrated;
423454

424455
void sim_aio_activate (ACTIVATE_API caller, UNIT *uptr, int32 event_time)
425456
{
426-
AIO_ILOCK;
427457
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Queueing Asynch event for %s after %d %s\n", sim_uname(uptr), event_time, sim_vm_interval_units);
428-
if (uptr->a_next) {
458+
459+
AIO_ILOCK;
460+
if (NULL != uptr->a_next) {
429461
uptr->a_activate_call = sim_activate_abs;
430462
}
431463
else {
432-
UNIT *q;
433464
uptr->a_event_time = event_time;
434465
uptr->a_activate_call = caller;
435-
do {
436-
q = AIO_QUEUE_VAL;
437-
uptr->a_next = q; /* Mark as on list */
438-
} while (q != AIO_QUEUE_SET(uptr, q));
466+
aio_enqueue_unit(uptr);
439467
}
440468
AIO_IUNLOCK;
441-
sim_asynch_check = 0; /* try to force check */
469+
442470
if (sim_idle_wait) {
443471
sim_debug (TIMER_DBG_IDLE, &sim_timer_dev, "waking due to event on %s after %d %s\n", sim_uname(uptr), event_time, sim_vm_interval_units);
444472
pthread_cond_signal (&sim_asynch_wake);
@@ -7036,7 +7064,7 @@ sim_show_clock_queues (st, dnotused, unotused, flag, cptr);
70367064
pthread_mutex_lock (&sim_asynch_lock);
70377065
sim_mfile = &buf;
70387066
fprintf (st, "asynchronous pending event queue\n");
7039-
if (sim_asynch_queue == QUEUE_LIST_END)
7067+
if (AIO_QUEUE_EMPTY())
70407068
fprintf (st, " Empty\n");
70417069
else {
70427070
for (uptr = sim_asynch_queue; uptr != QUEUE_LIST_END; uptr = uptr->a_next) {
@@ -13653,7 +13681,8 @@ if (sim_deb_switches & SWMASK ('F')) { /* filtering disabled? */
1365313681
_debug_fwrite (buf, len); /* output now. */
1365413682
return; /* done */
1365513683
}
13656-
AIO_LOCK;
13684+
13685+
AIO_DEBUG_IO_ACTIVE;
1365713686
if (debug_line_offset + len + 1 > debug_line_bufsize) {
1365813687
/* realloc(NULL, size) == malloc(size). Initialize the malloc()-ed space. Only
1365913688
need to test debug_line_buf since SIMH allocates both buffers at the same
@@ -13738,7 +13767,7 @@ while (NULL != (eol = strchr (debug_line_buf, '\n')) || flush) {
1373813767
memmove (debug_line_buf, eol + 1, debug_line_offset);
1373913768
debug_line_buf[debug_line_offset] = '\0';
1374013769
}
13741-
AIO_UNLOCK;
13770+
AIO_DEBUG_IO_DONE;
1374213771
}
1374313772

1374413773
static void _sim_debug_write (const char *buf, size_t len)

0 commit comments

Comments
 (0)