Skip to content

Commit 2c7a278

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 assignment 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 sim_asynch_queue != QUEUE_HEAD, i.e., there is work to be done in the queue. Code refactoring: - Convert preprocessor macro code to inline functions unless impractical. - 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 detected (GCC, Clang, MS C and DEC C on Itanium.) - Lock-based: If DONT_USE_AIO_INTRINSICS is defined or intrinsics are not detected, 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 detected. - 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. sim_asynch_lock was semantically overloaded to serialize output from _sim_debug_write_flush. This lock provides better semantic clarity. - Removed sim_asynch_check. It is no longer necessary. - New builder script flag to disable AIO lock-free, force AIO lock-based code: - cmake-builder.ps1 -noaiointrinsics ... - cmake-builder.sh -no-aio-intrinics ... "-DDONT_USE_AIO_INTRINSICS:Bool=On" is the command line CMake configuration option.
1 parent 29d3900 commit 2c7a278

8 files changed

Lines changed: 524 additions & 224 deletions

File tree

CMakeLists.txt

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

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

README-CMake.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -517,25 +517,22 @@ or video support.
517517
518518
# List the supported command line flags:
519519
$ cmake/cmake-builder.sh --help
520-
Configure and build simh simulators on Linux and *nix-like platforms.
520+
** cmake version 3.18.4
521521
522-
Subdirectories:
523-
cmake/build-unix: Makefile-based build simulators
524-
cmake/build-ninja: Ninja build-based simulators
522+
CMake suite maintained and supported by Kitware (kitware.com/cmake).
523+
Configure and build simh simulators on Linux and *nix-like platforms.
525524
526-
Options:
527-
--------
525+
Compile/Build options:
526+
----------------------
528527
--clean (-x) Remove the build subdirectory
529528
--generate (-g) Generate the build environment, don't compile/build
530529
--parallel (-p) Enable build parallelism (parallel builds)
531-
--nonetwork Build simulators without network support
532-
--novideo Build simulators without video support
533530
--notest Do not execute 'ctest' test cases
534531
--noinstall Do not install SIMH simulators.
535532
--testonly Do not build, execute the 'ctest' test cases
536533
--installonly Do not build, install the SIMH simulators
537534

538-
--flavor (-f) Specifies the build flavor. Valid flavors are:
535+
--flavor (-f) [Required] Specifies the build flavor. Valid flavors are:
539536
unix
540537
ninja
541538
xcode
@@ -547,8 +544,7 @@ or video support.
547544
--config (-c) Specifies the build configuration: 'Release' or 'Debug'
548545

549546
--target Build a specific simulator or simulators. Separate multiple
550-
targets by separating with a comma,
551-
e.g. "--target pdp8,pdp11,vax750,altairz80,3b2"
547+
targets with a comma, e.g. "--target pdp8,pdp11,vax750,altairz80,3b2"
552548
--lto Enable Link Time Optimization (LTO) in Release builds
553549
--debugWall Enable maximal warnings in Debug builds
554550
--cppcheck Enable cppcheck static code analysis rules
@@ -559,6 +555,17 @@ or video support.
559555
560556
--verbose Turn on verbose build output
561557
558+
SIMH feature control options:
559+
-----------------------------
560+
--nonetwork Build simulators without network support
561+
--novideo Build simulators without video support
562+
--no-aio-intrinsics
563+
Do not use compiler/platform intrinsics to implement AIO
564+
functions (aka "lock-free" AIO), reverts to lock-based AIO
565+
if threading libraries are detected.
566+
567+
Other options:
568+
--------------
562569
--help (-h) Print this help.
563570
```
564571
@@ -575,17 +582,17 @@ or video support.
575582
PS C:\...\open-simh> Get-Help -deatailed cmake\cmake-builder.ps1
576583
577584
NAME
578-
C:\Users\bsm21317\play\open-simh\cmake\cmake-builder.ps1
585+
C:\...\play\open-simh\cmake\cmake-builder.ps1
579586
580587
SYNOPSIS
581588
Configure and build SIMH's dependencies and simulators using the Microsoft Visual
582589
Studio C compiler or MinGW-W64-based gcc compiler.
583590

584591

585592
SYNTAX
586-
C:\Users\bsm21317\play\open-simh\cmake\cmake-builder.ps1 [[-flavor] <String>] [[-config] <String>] [[-cpack_suffix] <String>] [[-target] <String>]
587-
[-clean] [-help] [-nonetwork] [-novideo] [-notest] [-noinstall] [-parallel] [-generate] [-regenerate] [-testonly] [-installOnly] [-windeprecation]
588-
[-package] [-lto] [-debugWall] [-cppcheck] [<CommonParameters>]
593+
C:\...\play\open-simh\cmake\cmake-builder.ps1 [[-flavor] <String>] [[-config] <String>] [[-cpack_suffix] <String>] [[-target]
594+
<String[]>] [-clean] [-help] [-nonetwork] [-novideo] [-noaioinstrinsics] [-notest] [-noinstall] [-parallel] [-generate] [-testonly]
595+
[-installOnly] [-windeprecation] [-lto] [-debugWall] [-cppcheck] [<CommonParameters>]
589596

590597

591598
DESCRIPTION
@@ -594,9 +601,9 @@ or video support.
594601

595602
1. Configure and generate the build environment selected by '-flavor' option.
596603
2. Build missing runtime dependencies and the simulator suite with the compiler
597-
configuration selected by the '-config' option. The "Release" configuration
598-
generates optimized executables; the "Debug" configuration generates
599-
development executables with debugger information.
604+
configuration selected by the '-config' option. The "Release" configuration
605+
generates optimized executables; the "Debug" configuration generates
606+
development executables with debugger information.
600607
3. Test the simulators
601608

602609
There is an install phase that can be invoked separately as part of the SIMH
@@ -630,6 +637,9 @@ or video support.
630637
mingw-make MinGW GCC/mingw32-make
631638
mingw-ninja MinGW GCC/ninja
632639
640+
-config <String>
641+
The target build configuration. Valid values: "Release" and "Debug"
642+
633643
[...truncated for brevity...]
634644
```
635645

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()

makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2110,7 +2110,7 @@ KL10 = ${KL10D}/kx10_cpu.c ${KL10D}/kx10_sys.c ${KL10D}/kx10_df.c \
21102110
${KL10D}/kx10_rp.c ${KL10D}/kx10_tu.c ${KL10D}/kx10_rs.c \
21112111
${KL10D}/kx10_imp.c ${KL10D}/kl10_fe.c ${KL10D}/ka10_pd.c \
21122112
${KL10D}/ka10_ch10.c ${KL10D}/kl10_nia.c ${KL10D}/kx10_disk.c
2113-
KL10_OPT = -DKL=1 -DUSE_INT64 -I $(KL10D) -DUSE_SIM_CARD ${NETWORK_OPT} ${AIO_CCDEFS}
2113+
KL10_OPT = -DKL=1 -DUSE_INT64 -I $(KL10D) -DUSE_SIM_CARD ${NETWORK_OPT} ${AIO_CCDEFS}
21142114

21152115
KS10D = ${SIMHD}/PDP10
21162116
KS10 = ${KS10D}/kx10_cpu.c ${KS10D}/kx10_sys.c ${KS10D}/kx10_disk.c \

scp.c

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@
284284
#define UPDATE_SIM_TIME \
285285
if (1) { \
286286
int32 _x; \
287-
AIO_LOCK; \
288287
if (sim_clock_queue == QUEUE_LIST_END) \
289288
_x = noqueue_time; \
290289
else \
@@ -295,7 +294,6 @@
295294
noqueue_time = sim_interval; \
296295
else \
297296
sim_clock_queue->time = sim_interval; \
298-
AIO_UNLOCK; \
299297
} \
300298
else \
301299
(void)0
@@ -378,43 +376,73 @@ pthread_mutex_t sim_tmxr_poll_lock = PTHREAD_MUTEX_INITIALIZER;
378376
pthread_cond_t sim_tmxr_poll_cond = PTHREAD_COND_INITIALIZER;
379377
int32 sim_tmxr_poll_count;
380378
pthread_t sim_asynch_main_threadid;
381-
UNIT * volatile sim_asynch_queue;
379+
sim_unit_atomic_t sim_asynch_queue;
382380
t_bool sim_asynch_enabled = TRUE;
383-
int32 sim_asynch_check;
384381
int32 sim_asynch_latency = 4000; /* 4 usec interrupt latency */
385382
int32 sim_asynch_inst_latency = 20; /* assume 5 mip simulator */
386383

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

391420
AIO_ILOCK;
392-
if (AIO_QUEUE_VAL != QUEUE_LIST_END) { /* List !Empty */
393-
UNIT *q, *uptr;
421+
if (!AIO_QUEUE_EMPTY()) {
422+
sim_unit_atomic_t q;
423+
UNIT *uptr;
394424
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);
425+
for (q = aio_queue_worklist(); q != QUEUE_LIST_END; /* empty */) {
426+
uptr = unit_ptr_load_atomic(&q);
427+
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Migrating Asynch event for %s after %d %s\n",
428+
sim_uname(uptr), uptr->a_event_time, sim_vm_interval_units);
400429
++migrated;
401-
uptr = q;
402430
q = q->a_next;
403-
uptr->a_next = NULL; /* hygiene */
431+
uptr->a_next = NULL; /* hygiene */
404432
if (uptr->a_activate_call != &sim_activate_notbefore) {
405-
a_event_time = uptr->a_event_time-((sim_asynch_inst_latency+1)/2);
433+
a_event_time = uptr->a_event_time - ((sim_asynch_inst_latency + 1) / 2);
406434
if (a_event_time < 0)
407435
a_event_time = 0;
408436
}
409437
else
410438
a_event_time = uptr->a_event_time;
411-
AIO_IUNLOCK;
439+
412440
uptr->a_activate_call (uptr, a_event_time);
441+
413442
if (uptr->a_check_completion) {
414443
sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Calling Completion Check for asynch event on %s\n", sim_uname(uptr));
415444
uptr->a_check_completion (uptr);
416445
}
417-
AIO_ILOCK;
418446
}
419447
}
420448
AIO_IUNLOCK;
@@ -423,22 +451,19 @@ return migrated;
423451

424452
void sim_aio_activate (ACTIVATE_API caller, UNIT *uptr, int32 event_time)
425453
{
426-
AIO_ILOCK;
427454
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) {
455+
456+
AIO_ILOCK;
457+
if (NULL != uptr->a_next) {
429458
uptr->a_activate_call = sim_activate_abs;
430459
}
431460
else {
432-
UNIT *q;
433461
uptr->a_event_time = event_time;
434462
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));
463+
aio_enqueue_unit(uptr);
439464
}
440465
AIO_IUNLOCK;
441-
sim_asynch_check = 0; /* try to force check */
466+
442467
if (sim_idle_wait) {
443468
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);
444469
pthread_cond_signal (&sim_asynch_wake);
@@ -2808,7 +2833,7 @@ sim_quiet = sim_switches & SWMASK ('Q'); /* -q means quiet */
28082833
sim_on_inherit = sim_switches & SWMASK ('O'); /* -o means inherit on state */
28092834

28102835
sim_init_sock (); /* init socket capabilities */
2811-
AIO_INIT; /* init Asynch I/O */
2836+
aio_initialization(); /* init Asynch I/O */
28122837
sim_finit (); /* init fio package */
28132838
sim_disk_init (); /* init disk package */
28142839
sim_tape_init (); /* init tape package */
@@ -2982,7 +3007,7 @@ sim_set_logoff (0, NULL); /* close log */
29823007
sim_set_notelnet (0, NULL); /* close Telnet */
29833008
vid_close_all (); /* close video */
29843009
sim_ttclose (); /* close console */
2985-
AIO_CLEANUP; /* Asynch I/O */
3010+
aio_termination (); /* Asynch I/O */
29863011
sim_cleanup_sock (); /* cleanup sockets */
29873012
fclose (stdnul); /* close bit bucket file handle */
29883013
free (targv); /* release any argv copy that was made */
@@ -7038,17 +7063,20 @@ sim_show_clock_queues (st, dnotused, unotused, flag, cptr);
70387063
pthread_mutex_lock (&sim_asynch_lock);
70397064
sim_mfile = &buf;
70407065
fprintf (st, "asynchronous pending event queue\n");
7041-
if (sim_asynch_queue == QUEUE_LIST_END)
7066+
if (AIO_QUEUE_EMPTY())
70427067
fprintf (st, " Empty\n");
70437068
else {
7044-
for (uptr = sim_asynch_queue; uptr != QUEUE_LIST_END; uptr = uptr->a_next) {
7045-
if ((dptr = find_dev_from_unit (uptr)) != NULL) {
7069+
UNIT *p_unit;
7070+
for (p_unit = unit_ptr_load_atomic(&sim_asynch_queue);
7071+
p_unit != QUEUE_LIST_END;
7072+
p_unit = unit_ptr_load_atomic(&p_unit->a_next)) {
7073+
if ((dptr = find_dev_from_unit (p_unit)) != NULL) {
70467074
fprintf (st, " %s", sim_dname (dptr));
70477075
if (dptr->numunits > 1) fprintf (st, " unit %d",
7048-
(int32) (uptr - dptr->units));
7076+
(int32) (p_unit - dptr->units));
70497077
}
70507078
else fprintf (st, " Unknown");
7051-
fprintf (st, " event delay %d\n", uptr->a_event_time);
7079+
fprintf (st, " event delay %d\n", p_unit->a_event_time);
70527080
}
70537081
}
70547082
fprintf (st, "asynch latency: %d nanoseconds\n", sim_asynch_latency);
@@ -13660,7 +13688,8 @@ if (sim_deb_switches & SWMASK ('F')) { /* filtering disabled? */
1366013688
_debug_fwrite (buf, len); /* output now. */
1366113689
return; /* done */
1366213690
}
13663-
AIO_LOCK;
13691+
13692+
AIO_DEBUG_IO_ACTIVE;
1366413693
if (debug_line_offset + len + 1 > debug_line_bufsize) {
1366513694
/* realloc(NULL, size) == malloc(size). Initialize the malloc()-ed space. Only
1366613695
need to test debug_line_buf since SIMH allocates both buffers at the same
@@ -13745,7 +13774,7 @@ while (NULL != (eol = strchr (debug_line_buf, '\n')) || flush) {
1374513774
memmove (debug_line_buf, eol + 1, debug_line_offset);
1374613775
debug_line_buf[debug_line_offset] = '\0';
1374713776
}
13748-
AIO_UNLOCK;
13777+
AIO_DEBUG_IO_DONE;
1374913778
}
1375013779

1375113780
static void _sim_debug_write (const char *buf, size_t len)
@@ -14656,15 +14685,15 @@ for (hblock = astrings; (htext = *hblock) != NULL; hblock++) {
1465614685
}
1465714686
excluded = FALSE;
1465814687
if (*start == '?') { /* Conditional topic? */
14659-
size_t n = 0;
14688+
size_t m = 0;
1466014689
start++;
1466114690
while (sim_isdigit (*start)) /* Get param # */
14662-
n += (n * 10) + (*start++ - '0');
14663-
if (!*start || *start == '\n'|| n == 0 || n >= VSMAX)
14691+
m += (m * 10) + (*start++ - '0');
14692+
if (!*start || *start == '\n'|| m == 0 || n >= VSMAX)
1466414693
FAIL (SCPE_ARG, Invalid parameter number, start);
14665-
while (n > vsnum) /* Get arg pointer if not cached */
14694+
while (m > vsnum) /* Get arg pointer if not cached */
1466614695
vstrings[vsnum++] = va_arg (ap, char *);
14667-
end = vstrings[n-1]; /* Check for True */
14696+
end = vstrings[m-1]; /* Check for True */
1466814697
if (!end || !(sim_toupper (*end) == 'T' || *end == '1')) {
1466914698
excluded = TRUE; /* False, skip topic this time */
1467014699
if (*htext)

0 commit comments

Comments
 (0)