Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
-
c9015f09
by sheaf at 2026-06-11T12:40:28-04:00
-
7ab90288
by Rodrigo Mesquita at 2026-06-11T12:41:11-04:00
-
a6549ab1
by Marc Scholten at 2026-06-11T22:32:39-04:00
-
f18757ae
by Duncan Coutts at 2026-06-11T22:32:39-04:00
-
9660ff53
by Duncan Coutts at 2026-06-11T22:32:39-04:00
-
863a8d25
by Duncan Coutts at 2026-06-11T22:32:39-04:00
-
92461617
by Duncan Coutts at 2026-06-11T22:32:39-04:00
-
f52f601b
by Duncan Coutts at 2026-06-11T22:32:39-04:00
-
6f8d5318
by Duncan Coutts at 2026-06-11T22:32:40-04:00
-
faf06ab1
by Duncan Coutts at 2026-06-11T22:32:40-04:00
19 changed files:
- + changelog.d/T27046
- compiler/GHC/CmmToAsm/AArch64/CodeGen.hs
- rts/Capability.c
- rts/Schedule.c
- rts/Timer.c
- rts/eventlog/EventLog.c
- rts/eventlog/EventLog.h
- rts/include/rts/OSThreads.h
- + testsuite/tests/codeGen/should_run/T27046.hs
- + testsuite/tests/codeGen/should_run/T27046_cmm.cmm
- testsuite/tests/codeGen/should_run/all.T
- testsuite/tests/rts/T27131.hs
- testsuite/tests/rts/T27131.stdout
- utils/haddock/haddock-api/haddock-api.cabal
- utils/haddock/haddock-api/src/Haddock.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hyperlinker.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Xhtml.hs
- utils/haddock/haddock-api/src/Haddock/Options.hs
- utils/haddock/haddock-api/src/Haddock/Utils.hs
Changes:
| 1 | +section: compiler
|
|
| 2 | +issues: #27046
|
|
| 3 | +mrs: !16031
|
|
| 4 | +synopsis:
|
|
| 5 | + Avoid AArch64 register clobbering bug in MUL2
|
|
| 6 | +description:
|
|
| 7 | + Fixes an issue in which, on AArch64, code generation for the MUL2 operation
|
|
| 8 | + could clobber one of the input operands when computing the lower bits, which
|
|
| 9 | + rendered invalid the subsequent computation of the high bits. |
| ... | ... | @@ -2300,11 +2300,19 @@ genCCall target dest_regs arg_regs = do |
| 2300 | 2300 | let lo = getRegisterReg platform (CmmLocal dst_lo)
|
| 2301 | 2301 | hi = getRegisterReg platform (CmmLocal dst_hi)
|
| 2302 | 2302 | nd = getRegisterReg platform (CmmLocal dst_needed)
|
| 2303 | + |
|
| 2304 | + -- Generate a fresh virtual register for the low word computation.
|
|
| 2305 | + -- This avoids clobbering reg_a or reg_b in the first MUL instruction,
|
|
| 2306 | + -- which could for example happen if 'lo' and 'reg_a' are the same
|
|
| 2307 | + -- virtual register.
|
|
| 2308 | + tmp_lo <- getNewRegNat II64
|
|
| 2309 | + |
|
| 2303 | 2310 | return $
|
| 2304 | 2311 | code_x `appOL`
|
| 2305 | 2312 | code_y `snocOL`
|
| 2306 | - MUL II64 (OpReg W64 lo) (OpReg W64 reg_a) (OpReg W64 reg_b) `snocOL`
|
|
| 2313 | + MUL II64 (OpReg W64 tmp_lo) (OpReg W64 reg_a) (OpReg W64 reg_b) `snocOL`
|
|
| 2307 | 2314 | SMULH (OpReg W64 hi) (OpReg W64 reg_a) (OpReg W64 reg_b) `snocOL`
|
| 2315 | + MOV (OpReg W64 lo) (OpReg W64 tmp_lo) `snocOL`
|
|
| 2308 | 2316 | -- Are all high bits equal to the sign bit of the low word?
|
| 2309 | 2317 | -- nd = (hi == ASR(lo,width-1)) ? 1 : 0
|
| 2310 | 2318 | CMP (OpReg W64 hi) (OpRegShift W64 lo SASR (widthInBits w - 1)) `snocOL`
|
| ... | ... | @@ -443,13 +443,6 @@ void |
| 443 | 443 | moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
|
| 444 | 444 | {
|
| 445 | 445 | #if defined(THREADED_RTS)
|
| 446 | - // We must disable the timer while we do this since the tick handler may
|
|
| 447 | - // call contextSwitchAllCapabilities, which may see the capabilities array
|
|
| 448 | - // as we free it. The alternative would be to protect the capabilities
|
|
| 449 | - // array with a lock but this seems more expensive than necessary.
|
|
| 450 | - // See #17289.
|
|
| 451 | - stopTimer();
|
|
| 452 | - |
|
| 453 | 446 | if (to == 1) {
|
| 454 | 447 | // THREADED_RTS must work on builds that don't have a mutable
|
| 455 | 448 | // BaseReg (eg. unregisterised), so in this case
|
| ... | ... | @@ -470,8 +463,6 @@ moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS) |
| 470 | 463 | }
|
| 471 | 464 | |
| 472 | 465 | debugTrace(DEBUG_sched, "allocated %d more capabilities", to - from);
|
| 473 | - |
|
| 474 | - startTimer();
|
|
| 475 | 466 | #endif
|
| 476 | 467 | }
|
| 477 | 468 |
| ... | ... | @@ -37,6 +37,7 @@ |
| 37 | 37 | #include "win32/AsyncWinIO.h"
|
| 38 | 38 | #endif
|
| 39 | 39 | #include "Trace.h"
|
| 40 | +#include "eventlog/EventLog.h"
|
|
| 40 | 41 | #include "RaiseAsync.h"
|
| 41 | 42 | #include "Threads.h"
|
| 42 | 43 | #include "Timer.h"
|
| ... | ... | @@ -2100,24 +2101,31 @@ forkProcess(HsStablePtr *entry |
| 2100 | 2101 | ACQUIRE_LOCK(&all_tasks_mutex);
|
| 2101 | 2102 | #endif
|
| 2102 | 2103 | |
| 2103 | - stopTimer(); // See #4074
|
|
| 2104 | - |
|
| 2105 | 2104 | #if defined(TRACING)
|
| 2106 | - flushAllCapsEventsBufs(); // so that child won't inherit dirty file buffers
|
|
| 2105 | +#if defined(HAVE_PREEMPTION)
|
|
| 2106 | + // We must hold the eventlog global mutex over the fork to prevent the
|
|
| 2107 | + // timer thread from trying to post events. While holding the mutex we need
|
|
| 2108 | + // to flush the eventlogs (global and per-cap) so that child won't inherit
|
|
| 2109 | + // dirty eventlog buffers or file buffers.
|
|
| 2110 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 2111 | +#endif
|
|
| 2112 | + flushAllCapsEventsBufs_();
|
|
| 2107 | 2113 | #endif
|
| 2108 | 2114 | |
| 2109 | 2115 | pid = fork();
|
| 2110 | 2116 | |
| 2111 | 2117 | if (pid) { // parent
|
| 2112 | 2118 | |
| 2113 | - startTimer(); // #4074
|
|
| 2114 | - |
|
| 2115 | 2119 | RELEASE_LOCK(&sched_mutex);
|
| 2116 | 2120 | RELEASE_LOCK(&sm_mutex);
|
| 2117 | 2121 | RELEASE_LOCK(&stable_ptr_mutex);
|
| 2118 | 2122 | RELEASE_LOCK(&stable_name_mutex);
|
| 2119 | 2123 | RELEASE_LOCK(&task->lock);
|
| 2120 | 2124 | |
| 2125 | +#if defined(TRACING) && defined(HAVE_PREEMPTION)
|
|
| 2126 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 2127 | +#endif
|
|
| 2128 | + |
|
| 2121 | 2129 | #if defined(THREADED_RTS)
|
| 2122 | 2130 | /* N.B. releaseCapability_ below may need to take all_tasks_mutex */
|
| 2123 | 2131 | RELEASE_LOCK(&all_tasks_mutex);
|
| ... | ... | @@ -2303,12 +2311,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2303 | 2311 | cap = rts_lock();
|
| 2304 | 2312 | task = cap->running_task;
|
| 2305 | 2313 | |
| 2306 | - |
|
| 2307 | - // N.B. We must stop the interval timer while we are changing the
|
|
| 2308 | - // capabilities array lest handle_tick may try to context switch
|
|
| 2309 | - // an old capability. See #17289.
|
|
| 2310 | - stopTimer();
|
|
| 2311 | - |
|
| 2312 | 2314 | stopAllCapabilities(&cap, task);
|
| 2313 | 2315 | |
| 2314 | 2316 | if (new_n_capabilities < enabled_capabilities)
|
| ... | ... | @@ -2364,9 +2366,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2364 | 2366 | tracingAddCapabilities(n_capabilities, new_n_capabilities);
|
| 2365 | 2367 | #endif
|
| 2366 | 2368 | |
| 2367 | - // Resize the capabilities array
|
|
| 2368 | - // NB. after this, capabilities points somewhere new. Any pointers
|
|
| 2369 | - // of type (Capability *) are now invalid.
|
|
| 2369 | + // Allocate and initialise the extra capabilities
|
|
| 2370 | 2370 | moreCapabilities(n_capabilities, new_n_capabilities);
|
| 2371 | 2371 | |
| 2372 | 2372 | // Resize and update storage manager data structures
|
| ... | ... | @@ -2394,8 +2394,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2394 | 2394 | // Notify IO manager that the number of capabilities has changed.
|
| 2395 | 2395 | notifyIOManagerCapabilitiesChanged(&cap);
|
| 2396 | 2396 | |
| 2397 | - startTimer();
|
|
| 2398 | - |
|
| 2399 | 2397 | rts_unlock(cap);
|
| 2400 | 2398 | |
| 2401 | 2399 | #endif // THREADED_RTS
|
| ... | ... | @@ -28,11 +28,6 @@ |
| 28 | 28 | #include "RtsSignals.h"
|
| 29 | 29 | #include "rts/EventLogWriter.h"
|
| 30 | 30 | |
| 31 | -// See Note [No timer on wasm32]
|
|
| 32 | -#if !defined(wasm32_HOST_ARCH)
|
|
| 33 | -#define HAVE_PREEMPTION
|
|
| 34 | -#endif
|
|
| 35 | - |
|
| 36 | 31 | // This global counter is used to allow multiple threads to stop the
|
| 37 | 32 | // timer temporarily with a stopTimer()/startTimer() pair. If
|
| 38 | 33 | // timer_enabled == 0 timer is enabled
|
| ... | ... | @@ -129,8 +129,11 @@ typedef struct _EventsBuf { |
| 129 | 129 | static EventsBuf *capEventBuf; // one EventsBuf for each Capability
|
| 130 | 130 | |
| 131 | 131 | static EventsBuf eventBuf; // an EventsBuf not associated with any Capability
|
| 132 | -#if defined(THREADED_RTS)
|
|
| 133 | -static Mutex eventBufMutex; // protected by this mutex
|
|
| 132 | +#if defined(HAVE_PREEMPTION)
|
|
| 133 | +// Note that this mutex is used even in the non-threaded RTS, since the timer
|
|
| 134 | +// thread posts events and flushes. So _all_ uses of this mutex must use
|
|
| 135 | +// ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
|
|
| 136 | +Mutex eventBufMutex; // protects eventBuf above
|
|
| 134 | 137 | #endif
|
| 135 | 138 | |
| 136 | 139 | // Event type
|
| ... | ... | @@ -393,8 +396,10 @@ initEventLogging(void) |
| 393 | 396 | moreCapEventBufs(0, get_n_capabilities());
|
| 394 | 397 | |
| 395 | 398 | initEventsBuf(&eventBuf, EVENT_LOG_SIZE, (EventCapNo)(-1));
|
| 396 | -#if defined(THREADED_RTS)
|
|
| 399 | +#if defined(HAVE_PREEMPTION)
|
|
| 397 | 400 | initMutex(&eventBufMutex);
|
| 401 | +#endif
|
|
| 402 | +#if defined(THREADED_RTS)
|
|
| 398 | 403 | initMutex(&state_change_mutex);
|
| 399 | 404 | #endif
|
| 400 | 405 | }
|
| ... | ... | @@ -416,7 +421,7 @@ startEventLogging_(void) |
| 416 | 421 | {
|
| 417 | 422 | initEventLogWriter();
|
| 418 | 423 | |
| 419 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 424 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 420 | 425 | postHeaderEvents();
|
| 421 | 426 | |
| 422 | 427 | /*
|
| ... | ... | @@ -425,7 +430,7 @@ startEventLogging_(void) |
| 425 | 430 | */
|
| 426 | 431 | printAndClearEventBuf(&eventBuf);
|
| 427 | 432 | |
| 428 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 433 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 429 | 434 | |
| 430 | 435 | return true;
|
| 431 | 436 | }
|
| ... | ... | @@ -495,7 +500,7 @@ endEventLogging(void) |
| 495 | 500 | |
| 496 | 501 | flushEventLog_(NULL);
|
| 497 | 502 | |
| 498 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 503 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 499 | 504 | |
| 500 | 505 | // Mark end of events (data).
|
| 501 | 506 | postEventTypeNum(&eventBuf, EVENT_DATA_END);
|
| ... | ... | @@ -503,7 +508,7 @@ endEventLogging(void) |
| 503 | 508 | // Flush the end of data marker.
|
| 504 | 509 | printAndClearEventBuf(&eventBuf);
|
| 505 | 510 | |
| 506 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 511 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 507 | 512 | |
| 508 | 513 | stopEventLogWriter();
|
| 509 | 514 | event_log_writer = NULL;
|
| ... | ... | @@ -666,7 +671,7 @@ void |
| 666 | 671 | postCapEvent (EventTypeNum tag,
|
| 667 | 672 | EventCapNo capno)
|
| 668 | 673 | {
|
| 669 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 674 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 670 | 675 | ensureRoomForEvent(&eventBuf, tag);
|
| 671 | 676 | |
| 672 | 677 | postEventHeader(&eventBuf, tag);
|
| ... | ... | @@ -685,14 +690,14 @@ postCapEvent (EventTypeNum tag, |
| 685 | 690 | barf("postCapEvent: unknown event tag %d", tag);
|
| 686 | 691 | }
|
| 687 | 692 | |
| 688 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 693 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 689 | 694 | }
|
| 690 | 695 | |
| 691 | 696 | void postCapsetEvent (EventTypeNum tag,
|
| 692 | 697 | EventCapsetID capset,
|
| 693 | 698 | StgWord info)
|
| 694 | 699 | {
|
| 695 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 700 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 696 | 701 | ensureRoomForEvent(&eventBuf, tag);
|
| 697 | 702 | |
| 698 | 703 | postEventHeader(&eventBuf, tag);
|
| ... | ... | @@ -726,7 +731,7 @@ void postCapsetEvent (EventTypeNum tag, |
| 726 | 731 | barf("postCapsetEvent: unknown event tag %d", tag);
|
| 727 | 732 | }
|
| 728 | 733 | |
| 729 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 734 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 730 | 735 | }
|
| 731 | 736 | |
| 732 | 737 | void postCapsetStrEvent (EventTypeNum tag,
|
| ... | ... | @@ -740,14 +745,14 @@ void postCapsetStrEvent (EventTypeNum tag, |
| 740 | 745 | return;
|
| 741 | 746 | }
|
| 742 | 747 | |
| 743 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 748 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 744 | 749 | |
| 745 | 750 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 746 | 751 | printAndClearEventBuf(&eventBuf);
|
| 747 | 752 | |
| 748 | 753 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 749 | 754 | errorBelch("Event size exceeds buffer size, bail out");
|
| 750 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 755 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 751 | 756 | return;
|
| 752 | 757 | }
|
| 753 | 758 | }
|
| ... | ... | @@ -758,7 +763,7 @@ void postCapsetStrEvent (EventTypeNum tag, |
| 758 | 763 | |
| 759 | 764 | postBuf(&eventBuf, (StgWord8*) msg, strsize);
|
| 760 | 765 | |
| 761 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 766 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 762 | 767 | }
|
| 763 | 768 | |
| 764 | 769 | void postCapsetVecEvent (EventTypeNum tag,
|
| ... | ... | @@ -783,14 +788,14 @@ void postCapsetVecEvent (EventTypeNum tag, |
| 783 | 788 | }
|
| 784 | 789 | }
|
| 785 | 790 | |
| 786 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 791 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 787 | 792 | |
| 788 | 793 | if (!hasRoomForVariableEvent(&eventBuf, size)){
|
| 789 | 794 | printAndClearEventBuf(&eventBuf);
|
| 790 | 795 | |
| 791 | 796 | if(!hasRoomForVariableEvent(&eventBuf, size)){
|
| 792 | 797 | errorBelch("Event size exceeds buffer size, bail out");
|
| 793 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 798 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 794 | 799 | return;
|
| 795 | 800 | }
|
| 796 | 801 | }
|
| ... | ... | @@ -804,7 +809,7 @@ void postCapsetVecEvent (EventTypeNum tag, |
| 804 | 809 | postBuf(&eventBuf, (StgWord8*) argv[i], 1 + strlen(argv[i]));
|
| 805 | 810 | }
|
| 806 | 811 | |
| 807 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 812 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 808 | 813 | }
|
| 809 | 814 | |
| 810 | 815 | void postWallClockTime (EventCapsetID capset)
|
| ... | ... | @@ -813,7 +818,7 @@ void postWallClockTime (EventCapsetID capset) |
| 813 | 818 | StgWord64 sec;
|
| 814 | 819 | StgWord32 nsec;
|
| 815 | 820 | |
| 816 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 821 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 817 | 822 | |
| 818 | 823 | /* The EVENT_WALL_CLOCK_TIME event is intended to allow programs
|
| 819 | 824 | reading the eventlog to match up the event timestamps with wall
|
| ... | ... | @@ -846,7 +851,7 @@ void postWallClockTime (EventCapsetID capset) |
| 846 | 851 | postWord64(&eventBuf, sec);
|
| 847 | 852 | postWord32(&eventBuf, nsec);
|
| 848 | 853 | |
| 849 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 854 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 850 | 855 | }
|
| 851 | 856 | |
| 852 | 857 | /*
|
| ... | ... | @@ -885,7 +890,7 @@ void postEventHeapInfo (EventCapsetID heap_capset, |
| 885 | 890 | W_ mblockSize,
|
| 886 | 891 | W_ blockSize)
|
| 887 | 892 | {
|
| 888 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 893 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 889 | 894 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_INFO_GHC);
|
| 890 | 895 | |
| 891 | 896 | postEventHeader(&eventBuf, EVENT_HEAP_INFO_GHC);
|
| ... | ... | @@ -899,7 +904,7 @@ void postEventHeapInfo (EventCapsetID heap_capset, |
| 899 | 904 | postWord64(&eventBuf, mblockSize);
|
| 900 | 905 | postWord64(&eventBuf, blockSize);
|
| 901 | 906 | |
| 902 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 907 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 903 | 908 | }
|
| 904 | 909 | |
| 905 | 910 | void postEventGcStats (Capability *cap,
|
| ... | ... | @@ -952,7 +957,7 @@ void postTaskCreateEvent (EventTaskId taskId, |
| 952 | 957 | EventCapNo capno,
|
| 953 | 958 | EventKernelThreadId tid)
|
| 954 | 959 | {
|
| 955 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 960 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 956 | 961 | ensureRoomForEvent(&eventBuf, EVENT_TASK_CREATE);
|
| 957 | 962 | |
| 958 | 963 | postEventHeader(&eventBuf, EVENT_TASK_CREATE);
|
| ... | ... | @@ -961,14 +966,14 @@ void postTaskCreateEvent (EventTaskId taskId, |
| 961 | 966 | postCapNo(&eventBuf, capno);
|
| 962 | 967 | postKernelThreadId(&eventBuf, tid);
|
| 963 | 968 | |
| 964 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 969 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 965 | 970 | }
|
| 966 | 971 | |
| 967 | 972 | void postTaskMigrateEvent (EventTaskId taskId,
|
| 968 | 973 | EventCapNo capno,
|
| 969 | 974 | EventCapNo new_capno)
|
| 970 | 975 | {
|
| 971 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 976 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 972 | 977 | ensureRoomForEvent(&eventBuf, EVENT_TASK_MIGRATE);
|
| 973 | 978 | |
| 974 | 979 | postEventHeader(&eventBuf, EVENT_TASK_MIGRATE);
|
| ... | ... | @@ -977,28 +982,28 @@ void postTaskMigrateEvent (EventTaskId taskId, |
| 977 | 982 | postCapNo(&eventBuf, capno);
|
| 978 | 983 | postCapNo(&eventBuf, new_capno);
|
| 979 | 984 | |
| 980 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 985 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 981 | 986 | }
|
| 982 | 987 | |
| 983 | 988 | void postTaskDeleteEvent (EventTaskId taskId)
|
| 984 | 989 | {
|
| 985 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 990 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 986 | 991 | ensureRoomForEvent(&eventBuf, EVENT_TASK_DELETE);
|
| 987 | 992 | |
| 988 | 993 | postEventHeader(&eventBuf, EVENT_TASK_DELETE);
|
| 989 | 994 | /* EVENT_TASK_DELETE (taskID) */
|
| 990 | 995 | postTaskId(&eventBuf, taskId);
|
| 991 | 996 | |
| 992 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 997 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 993 | 998 | }
|
| 994 | 999 | |
| 995 | 1000 | void
|
| 996 | 1001 | postEventNoCap (EventTypeNum tag)
|
| 997 | 1002 | {
|
| 998 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1003 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 999 | 1004 | ensureRoomForEvent(&eventBuf, tag);
|
| 1000 | 1005 | postEventHeader(&eventBuf, tag);
|
| 1001 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1006 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1002 | 1007 | }
|
| 1003 | 1008 | |
| 1004 | 1009 | void
|
| ... | ... | @@ -1042,9 +1047,9 @@ void postLogMsg(EventsBuf *eb, EventTypeNum type, char *msg, va_list ap) |
| 1042 | 1047 | |
| 1043 | 1048 | void postMsg(char *msg, va_list ap)
|
| 1044 | 1049 | {
|
| 1045 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1050 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1046 | 1051 | postLogMsg(&eventBuf, EVENT_LOG_MSG, msg, ap);
|
| 1047 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1052 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1048 | 1053 | }
|
| 1049 | 1054 | |
| 1050 | 1055 | void postCapMsg(Capability *cap, char *msg, va_list ap)
|
| ... | ... | @@ -1138,32 +1143,32 @@ void postConcUpdRemSetFlush(Capability *cap) |
| 1138 | 1143 | |
| 1139 | 1144 | void postConcMarkEnd(StgWord32 marked_obj_count)
|
| 1140 | 1145 | {
|
| 1141 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1146 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1142 | 1147 | ensureRoomForEvent(&eventBuf, EVENT_CONC_MARK_END);
|
| 1143 | 1148 | postEventHeader(&eventBuf, EVENT_CONC_MARK_END);
|
| 1144 | 1149 | postWord32(&eventBuf, marked_obj_count);
|
| 1145 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1150 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1146 | 1151 | }
|
| 1147 | 1152 | |
| 1148 | 1153 | void postNonmovingHeapCensus(uint16_t blk_size,
|
| 1149 | 1154 | const struct NonmovingAllocCensus *census)
|
| 1150 | 1155 | {
|
| 1151 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1156 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1152 | 1157 | postEventHeader(&eventBuf, EVENT_NONMOVING_HEAP_CENSUS);
|
| 1153 | 1158 | postWord16(&eventBuf, blk_size);
|
| 1154 | 1159 | postWord32(&eventBuf, census->n_active_segs);
|
| 1155 | 1160 | postWord32(&eventBuf, census->n_filled_segs);
|
| 1156 | 1161 | postWord32(&eventBuf, census->n_live_blocks);
|
| 1157 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1162 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1158 | 1163 | }
|
| 1159 | 1164 | |
| 1160 | 1165 | void postNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments)
|
| 1161 | 1166 | {
|
| 1162 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1167 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1163 | 1168 | postEventHeader(&eventBuf, EVENT_NONMOVING_PRUNED_SEGMENTS);
|
| 1164 | 1169 | postWord32(&eventBuf, pruned_segments);
|
| 1165 | 1170 | postWord32(&eventBuf, free_segments);
|
| 1166 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1171 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1167 | 1172 | }
|
| 1168 | 1173 | |
| 1169 | 1174 | void closeBlockMarker (EventsBuf *ebuf)
|
| ... | ... | @@ -1224,7 +1229,7 @@ static HeapProfBreakdown getHeapProfBreakdown(void) |
| 1224 | 1229 | |
| 1225 | 1230 | void postHeapProfBegin(void)
|
| 1226 | 1231 | {
|
| 1227 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1232 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1228 | 1233 | PROFILING_FLAGS *flags = &RtsFlags.ProfFlags;
|
| 1229 | 1234 | StgWord modSelector_len =
|
| 1230 | 1235 | flags->modSelector ? strlen(flags->modSelector) : 0;
|
| ... | ... | @@ -1258,42 +1263,42 @@ void postHeapProfBegin(void) |
| 1258 | 1263 | postStringLen(&eventBuf, flags->ccsSelector, ccsSelector_len);
|
| 1259 | 1264 | postStringLen(&eventBuf, flags->retainerSelector, retainerSelector_len);
|
| 1260 | 1265 | postStringLen(&eventBuf, flags->bioSelector, bioSelector_len);
|
| 1261 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1266 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1262 | 1267 | }
|
| 1263 | 1268 | |
| 1264 | 1269 | void postHeapProfSampleBegin(StgInt era)
|
| 1265 | 1270 | {
|
| 1266 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1271 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1267 | 1272 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
|
| 1268 | 1273 | postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
|
| 1269 | 1274 | postWord64(&eventBuf, era);
|
| 1270 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1275 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1271 | 1276 | }
|
| 1272 | 1277 | |
| 1273 | 1278 | |
| 1274 | 1279 | void postHeapBioProfSampleBegin(StgInt era, StgWord64 time)
|
| 1275 | 1280 | {
|
| 1276 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1281 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1277 | 1282 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
|
| 1278 | 1283 | postEventHeader(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
|
| 1279 | 1284 | postWord64(&eventBuf, era);
|
| 1280 | 1285 | postWord64(&eventBuf, time);
|
| 1281 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1286 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1282 | 1287 | }
|
| 1283 | 1288 | |
| 1284 | 1289 | void postHeapProfSampleEnd(StgInt era)
|
| 1285 | 1290 | {
|
| 1286 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1291 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1287 | 1292 | ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
|
| 1288 | 1293 | postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
|
| 1289 | 1294 | postWord64(&eventBuf, era);
|
| 1290 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1295 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1291 | 1296 | }
|
| 1292 | 1297 | |
| 1293 | 1298 | void postHeapProfSampleString(const char *label,
|
| 1294 | 1299 | StgWord64 residency)
|
| 1295 | 1300 | {
|
| 1296 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1301 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1297 | 1302 | StgWord label_len = strlen(label);
|
| 1298 | 1303 | StgWord len = 1+8+label_len+1;
|
| 1299 | 1304 | CHECK(!ensureRoomForVariableEvent(&eventBuf, len));
|
| ... | ... | @@ -1303,7 +1308,7 @@ void postHeapProfSampleString(const char *label, |
| 1303 | 1308 | postWord8(&eventBuf, 0);
|
| 1304 | 1309 | postWord64(&eventBuf, residency);
|
| 1305 | 1310 | postStringLen(&eventBuf, label, label_len);
|
| 1306 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1311 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1307 | 1312 | }
|
| 1308 | 1313 | |
| 1309 | 1314 | #if defined(PROFILING)
|
| ... | ... | @@ -1313,7 +1318,7 @@ void postHeapProfCostCentre(StgWord32 ccID, |
| 1313 | 1318 | const char *srcloc,
|
| 1314 | 1319 | StgBool is_caf)
|
| 1315 | 1320 | {
|
| 1316 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1321 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1317 | 1322 | StgWord label_len = strlen(label);
|
| 1318 | 1323 | StgWord module_len = strlen(module);
|
| 1319 | 1324 | StgWord srcloc_len = strlen(srcloc);
|
| ... | ... | @@ -1326,13 +1331,13 @@ void postHeapProfCostCentre(StgWord32 ccID, |
| 1326 | 1331 | postStringLen(&eventBuf, module, module_len);
|
| 1327 | 1332 | postStringLen(&eventBuf, srcloc, srcloc_len);
|
| 1328 | 1333 | postWord8(&eventBuf, is_caf);
|
| 1329 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1334 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1330 | 1335 | }
|
| 1331 | 1336 | |
| 1332 | 1337 | void postHeapProfSampleCostCentre(CostCentreStack *stack,
|
| 1333 | 1338 | StgWord64 residency)
|
| 1334 | 1339 | {
|
| 1335 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1340 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1336 | 1341 | StgWord depth = 0;
|
| 1337 | 1342 | CostCentreStack *ccs;
|
| 1338 | 1343 | for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
|
| ... | ... | @@ -1351,7 +1356,7 @@ void postHeapProfSampleCostCentre(CostCentreStack *stack, |
| 1351 | 1356 | depth>0 && ccs != NULL && ccs != CCS_MAIN;
|
| 1352 | 1357 | ccs = ccs->prevStack, depth--)
|
| 1353 | 1358 | postWord32(&eventBuf, ccs->cc->ccID);
|
| 1354 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1359 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1355 | 1360 | }
|
| 1356 | 1361 | |
| 1357 | 1362 | |
| ... | ... | @@ -1359,7 +1364,7 @@ void postProfSampleCostCentre(Capability *cap, |
| 1359 | 1364 | CostCentreStack *stack,
|
| 1360 | 1365 | StgWord64 tick)
|
| 1361 | 1366 | {
|
| 1362 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1367 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1363 | 1368 | StgWord depth = 0;
|
| 1364 | 1369 | CostCentreStack *ccs;
|
| 1365 | 1370 | for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
|
| ... | ... | @@ -1377,7 +1382,7 @@ void postProfSampleCostCentre(Capability *cap, |
| 1377 | 1382 | depth>0 && ccs != NULL && ccs != CCS_MAIN;
|
| 1378 | 1383 | ccs = ccs->prevStack, depth--)
|
| 1379 | 1384 | postWord32(&eventBuf, ccs->cc->ccID);
|
| 1380 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1385 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1381 | 1386 | }
|
| 1382 | 1387 | |
| 1383 | 1388 | // This event is output at the start of profiling so the tick interval can
|
| ... | ... | @@ -1385,11 +1390,11 @@ void postProfSampleCostCentre(Capability *cap, |
| 1385 | 1390 | // can be calculated from how many samples there are.
|
| 1386 | 1391 | void postProfBegin(void)
|
| 1387 | 1392 | {
|
| 1388 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1393 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1389 | 1394 | postEventHeader(&eventBuf, EVENT_PROF_BEGIN);
|
| 1390 | 1395 | // The interval that each tick was sampled, in nanoseconds
|
| 1391 | 1396 | postWord64(&eventBuf, TimeToNS(RtsFlags.MiscFlags.tickInterval));
|
| 1392 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1397 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1393 | 1398 | }
|
| 1394 | 1399 | #endif /* PROFILING */
|
| 1395 | 1400 | |
| ... | ... | @@ -1415,11 +1420,11 @@ static void postTickyCounterDef(EventsBuf *eb, StgEntCounter *p) |
| 1415 | 1420 | |
| 1416 | 1421 | void postTickyCounterDefs(StgEntCounter *counters)
|
| 1417 | 1422 | {
|
| 1418 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1423 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1419 | 1424 | for (StgEntCounter *p = counters; p != NULL; p = p->link) {
|
| 1420 | 1425 | postTickyCounterDef(&eventBuf, p);
|
| 1421 | 1426 | }
|
| 1422 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1427 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1423 | 1428 | }
|
| 1424 | 1429 | |
| 1425 | 1430 | static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p)
|
| ... | ... | @@ -1443,13 +1448,13 @@ static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p) |
| 1443 | 1448 | |
| 1444 | 1449 | void postTickyCounterSamples(StgEntCounter *counters)
|
| 1445 | 1450 | {
|
| 1446 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1451 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1447 | 1452 | ensureRoomForEvent(&eventBuf, EVENT_TICKY_COUNTER_SAMPLE);
|
| 1448 | 1453 | postEventHeader(&eventBuf, EVENT_TICKY_COUNTER_BEGIN_SAMPLE);
|
| 1449 | 1454 | for (StgEntCounter *p = counters; p != NULL; p = p->link) {
|
| 1450 | 1455 | postTickyCounterSample(&eventBuf, p);
|
| 1451 | 1456 | }
|
| 1452 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1457 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1453 | 1458 | }
|
| 1454 | 1459 | #endif /* TICKY_TICKY */
|
| 1455 | 1460 | void postIPE(const InfoProvEnt *ipe)
|
| ... | ... | @@ -1459,7 +1464,7 @@ void postIPE(const InfoProvEnt *ipe) |
| 1459 | 1464 | |
| 1460 | 1465 | // See Note [Maximum event length].
|
| 1461 | 1466 | const StgWord MAX_IPE_STRING_LEN = 65535;
|
| 1462 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1467 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1463 | 1468 | StgWord table_name_len = MIN(strlen(ipe->prov.table_name), MAX_IPE_STRING_LEN);
|
| 1464 | 1469 | StgWord closure_desc_len = MIN(strlen(closure_desc_buf), MAX_IPE_STRING_LEN);
|
| 1465 | 1470 | StgWord ty_desc_len = MIN(strlen(ipe->prov.ty_desc), MAX_IPE_STRING_LEN);
|
| ... | ... | @@ -1489,7 +1494,7 @@ void postIPE(const InfoProvEnt *ipe) |
| 1489 | 1494 | postBuf(&eventBuf, &colon, 1);
|
| 1490 | 1495 | postStringLen(&eventBuf, ipe->prov.src_span, src_span_len);
|
| 1491 | 1496 | |
| 1492 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1497 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1493 | 1498 | }
|
| 1494 | 1499 | |
| 1495 | 1500 | void printAndClearEventBuf (EventsBuf *ebuf)
|
| ... | ... | @@ -1601,14 +1606,21 @@ void flushLocalEventsBuf(Capability *cap) |
| 1601 | 1606 | // Flush all capabilities' event buffers when we already hold all capabilities.
|
| 1602 | 1607 | // Used during forkProcess.
|
| 1603 | 1608 | void flushAllCapsEventsBufs(void)
|
| 1609 | +{
|
|
| 1610 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1611 | + flushAllCapsEventsBufs_();
|
|
| 1612 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1613 | +}
|
|
| 1614 | + |
|
| 1615 | +// Unsafe version that does not acquire/release eventBufMutex. You must
|
|
| 1616 | +// hold the eventBufMutex, which you must acquire with ACQUIRE_LOCK_ALWAYS!
|
|
| 1617 | +void flushAllCapsEventsBufs_(void)
|
|
| 1604 | 1618 | {
|
| 1605 | 1619 | if (!event_log_writer) {
|
| 1606 | 1620 | return;
|
| 1607 | 1621 | }
|
| 1608 | 1622 | |
| 1609 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1610 | 1623 | printAndClearEventBuf(&eventBuf);
|
| 1611 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1612 | 1624 | |
| 1613 | 1625 | for (unsigned int i=0; i < getNumCapabilities(); i++) {
|
| 1614 | 1626 | flushLocalEventsBuf(getCapability(i));
|
| ... | ... | @@ -1641,9 +1653,9 @@ static void flushEventLog_(Capability **cap USED_IF_THREADS) |
| 1641 | 1653 | return;
|
| 1642 | 1654 | }
|
| 1643 | 1655 | |
| 1644 | - ACQUIRE_LOCK(&eventBufMutex);
|
|
| 1656 | + ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1645 | 1657 | printAndClearEventBuf(&eventBuf);
|
| 1646 | - RELEASE_LOCK(&eventBufMutex);
|
|
| 1658 | + RELEASE_LOCK_ALWAYS(&eventBufMutex);
|
|
| 1647 | 1659 | |
| 1648 | 1660 | #if defined(THREADED_RTS)
|
| 1649 | 1661 | Task *task = newBoundTask();
|
| ... | ... | @@ -18,6 +18,13 @@ |
| 18 | 18 | #if defined(TRACING)
|
| 19 | 19 | |
| 20 | 20 | extern bool eventlog_enabled;
|
| 21 | +#if defined(HAVE_PREEMPTION)
|
|
| 22 | +// Avoid using this mutex directly if at all possible. It is needed in the
|
|
| 23 | +// implementation of forkProcess.
|
|
| 24 | +//
|
|
| 25 | +// All uses of this mutex must use ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
|
|
| 26 | +extern Mutex eventBufMutex;
|
|
| 27 | +#endif
|
|
| 21 | 28 | |
| 22 | 29 | void initEventLogging(void);
|
| 23 | 30 | void restartEventLogging(void);
|
| ... | ... | @@ -27,6 +34,7 @@ void abortEventLogging(void); // #4512 - after fork child needs to abort |
| 27 | 34 | void moreCapEventBufs (uint32_t from, uint32_t to);
|
| 28 | 35 | void flushLocalEventsBuf(Capability *cap);
|
| 29 | 36 | void flushAllCapsEventsBufs(void);
|
| 37 | +void flushAllCapsEventsBufs_(void);
|
|
| 30 | 38 | void flushAllEventsBufs(Capability *cap);
|
| 31 | 39 | |
| 32 | 40 | typedef void (*EventlogInitPost)(void);
|
| ... | ... | @@ -14,6 +14,46 @@ |
| 14 | 14 | |
| 15 | 15 | #pragma once
|
| 16 | 16 | |
| 17 | +/* Note [Threads and preemption]
|
|
| 18 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 19 | + All full-fat OSs that GHC works on have OS threads, and we use them even in
|
|
| 20 | + the non-threaded RTS for a few features:
|
|
| 21 | + * Haskell thread preemption;
|
|
| 22 | + * sample-based profiling;
|
|
| 23 | + * idle GC;
|
|
| 24 | + * periodic eventlog flushing.
|
|
| 25 | + |
|
| 26 | + We use defined(HAVE_PREEMPTION) to decide if these features are implemented
|
|
| 27 | + via OS threads.
|
|
| 28 | + |
|
| 29 | + On platforms like WASM/js we do not have OS threads in any conventional
|
|
| 30 | + sense, and the features above are either not available or are implemented
|
|
| 31 | + differently. See Note [No timer on wasm32].
|
|
| 32 | + |
|
| 33 | + In future if GHC is ported to platforms like bare-metal micro-controllers,
|
|
| 34 | + RTOSs or to run directly under hypervisors then such platforms may also not
|
|
| 35 | + have threads available and they should not define HAVE_PREEMPTION here. Or
|
|
| 36 | + for some micro-controller RTOSs like Zeypher one may have a choice about
|
|
| 37 | + whether to use threads or not (at a size cost). Here would be the right
|
|
| 38 | + place to control whether the feature list above is supported.
|
|
| 39 | + */
|
|
| 40 | +#if defined(wasm32_HOST_ARCH)
|
|
| 41 | + // See Note [No timer on wasm32]
|
|
| 42 | + // To confuse matters, WASM _does_ have pthread.h but it doesnt work.
|
|
| 43 | +#elif defined(HAVE_PTHREAD_H) || defined(HAVE_WINDOWS_H)
|
|
| 44 | +#define HAVE_PREEMPTION
|
|
| 45 | +#else
|
|
| 46 | +#error Decide if this platform has threads and pre-emption or not.
|
|
| 47 | +#endif
|
|
| 48 | +// And JS does all of this differently, without using this bit of the RTS.
|
|
| 49 | + |
|
| 50 | +// Configuration sanity check
|
|
| 51 | +#if defined(THREADED_RTS) && !defined(HAVE_PREEMPTION)
|
|
| 52 | +//TODO we would like to be able to assert this:
|
|
| 53 | +// #error Configuration error: THREADED_RTS should imply HAVE_PREEMPTION
|
|
| 54 | +// however at the moment we cannot due to issue #27346.
|
|
| 55 | +#endif
|
|
| 56 | + |
|
| 17 | 57 | #if defined(HAVE_PTHREAD_H) && !defined(mingw32_HOST_OS)
|
| 18 | 58 | |
| 19 | 59 | #if defined(CMINUSMINUS)
|
| ... | ... | @@ -210,9 +250,29 @@ extern bool timedWaitCondition ( Condition* pCond, Mutex* pMut, Time timeout) |
| 210 | 250 | //
|
| 211 | 251 | // Mutexes
|
| 212 | 252 | //
|
| 253 | +// Even in the non-threaded RTS we use threads and mutexes! In particular the
|
|
| 254 | +// timer/ticker is implemented using a thread. And using threads needs locks.
|
|
| 255 | +// In particular we need locks for the data shared between the timer/ticker
|
|
| 256 | +// thread and the thread running the main capability.
|
|
| 257 | +#if defined(HAVE_PREEMPTION)
|
|
| 213 | 258 | extern void initMutex ( Mutex* pMut );
|
| 214 | 259 | extern void closeMutex ( Mutex* pMut );
|
| 215 | 260 | |
| 261 | +// The "always" variants do locking in the threaded and non-threaded RTS.
|
|
| 262 | +// The normal variants below are no-ops in the non-threaded RTS.
|
|
| 263 | +#define ACQUIRE_LOCK_ALWAYS(l) OS_ACQUIRE_LOCK(l)
|
|
| 264 | +#define TRY_ACQUIRE_LOCK_ALWAYS(l) OS_TRY_ACQUIRE_LOCK(l)
|
|
| 265 | +#define RELEASE_LOCK_ALWAYS(l) OS_RELEASE_LOCK(l)
|
|
| 266 | +#define ASSERT_LOCK_HELD_ALWAYS(l) OS_ASSERT_LOCK_HELD(l)
|
|
| 267 | +#else
|
|
| 268 | +// And just to be a bit confusing, the always variants are still no-ops when we
|
|
| 269 | +// do not HAVE_PREEMPTION, since then we don't have threads or mutexes at all.
|
|
| 270 | +#define ACQUIRE_LOCK_ALWAYS(l)
|
|
| 271 | +#define TRY_ACQUIRE_LOCK_ALWAYS(l) 0
|
|
| 272 | +#define RELEASE_LOCK_ALWAYS(l)
|
|
| 273 | +#define ASSERT_LOCK_HELD_ALWAYS(l)
|
|
| 274 | +#endif
|
|
| 275 | + |
|
| 216 | 276 | // Processors and affinity
|
| 217 | 277 | void setThreadAffinity (uint32_t n, uint32_t m);
|
| 218 | 278 | void setThreadNode (uint32_t node);
|
| ... | ... | @@ -228,6 +288,7 @@ void releaseThreadNode (void); |
| 228 | 288 | |
| 229 | 289 | #else
|
| 230 | 290 | |
| 291 | +// No-ops in the non-threaded RTS. See also the _ALWAYS variants above.
|
|
| 231 | 292 | #define ACQUIRE_LOCK(l)
|
| 232 | 293 | #define TRY_ACQUIRE_LOCK(l) 0
|
| 233 | 294 | #define RELEASE_LOCK(l)
|
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | +{-# LANGUAGE ForeignFunctionInterface, GHCForeignImportPrim, UnliftedFFITypes #-}
|
|
| 3 | + |
|
| 4 | +module Main where
|
|
| 5 | + |
|
| 6 | +import Control.Monad
|
|
| 7 | + ( unless )
|
|
| 8 | +import Data.Bits
|
|
| 9 | + ( shiftL )
|
|
| 10 | +import GHC.Exts
|
|
| 11 | + ( Int64# )
|
|
| 12 | +import GHC.Int
|
|
| 13 | + ( Int64(..) )
|
|
| 14 | + |
|
| 15 | +foreign import prim "test_mul2_clobber"
|
|
| 16 | + test_mul2_clobber :: Int64# -> Int64# -> Int64#
|
|
| 17 | + |
|
| 18 | +main :: IO ()
|
|
| 19 | +main = do
|
|
| 20 | + let
|
|
| 21 | + I64# x = 1 `shiftL` 32
|
|
| 22 | + hi = I64# $ test_mul2_clobber x x
|
|
| 23 | + |
|
| 24 | + unless ( hi == 1 ) $
|
|
| 25 | + error $ unlines
|
|
| 26 | + [ "Incorrect result for Mul2 operation."
|
|
| 27 | + , "Expected high word: 1"
|
|
| 28 | + , " Actual high word: " ++ show hi
|
|
| 29 | + ] |
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// Test for #27046
|
|
| 4 | +test_mul2_clobber (bits64 x, bits64 y)
|
|
| 5 | +{
|
|
| 6 | + bits64 hi, nd;
|
|
| 7 | + |
|
| 8 | + // Deliberately alias the destination 'lo' with the source 'x'
|
|
| 9 | + // This forces the NCG to use the same virtual register for both.
|
|
| 10 | + (nd, hi, x) = prim %mul2_64(x, y);
|
|
| 11 | + |
|
| 12 | + return (hi);
|
|
| 13 | +} |
| ... | ... | @@ -260,6 +260,13 @@ test('T25364', normal, compile_and_run, ['']) |
| 260 | 260 | test('T26061', normal, compile_and_run, [''])
|
| 261 | 261 | test('T26537', normal, compile_and_run, ['-O2 -fregs-graph'])
|
| 262 | 262 | test('T24016', normal, compile_and_run, ['-O1 -fPIC'])
|
| 263 | +test('T27046',
|
|
| 264 | + [ req_cmm
|
|
| 265 | + , when(arch('i386'), skip) # i386 does not support MO_S_Mul2 W64
|
|
| 266 | + , when(arch('wasm32'), skip)
|
|
| 267 | + , js_skip
|
|
| 268 | + , when(unregisterised(), skip) # pprCallishMachOp_for_C: MO_S_Mul2 W64 not supported
|
|
| 269 | + ], compile_and_run, ['T27046_cmm.cmm'])
|
|
| 263 | 270 | |
| 264 | 271 | # Check that GHC-generated finalizers run on Darwin. The Apple linker doesn't
|
| 265 | 272 | # support --wrap, so we can't intercept hs_spt_remove directly. Instead we
|
| ... | ... | @@ -30,16 +30,22 @@ foreign import ccall unsafe "has_local_stop_after_return" |
| 30 | 30 | main :: IO ()
|
| 31 | 31 | main = do
|
| 32 | 32 | setNumCapabilities 2
|
| 33 | - checkFlag
|
|
| 34 | - "TSO_STOP_NEXT_BREAKPOINT"
|
|
| 35 | - rts_enableStopNextBreakpoint
|
|
| 36 | - rts_disableStopNextBreakpoint
|
|
| 37 | - c_hasLocalStopNextBreakpoint
|
|
| 38 | - checkFlag
|
|
| 39 | - "TSO_STOP_AFTER_RETURN"
|
|
| 40 | - rts_enableStopAfterReturn
|
|
| 41 | - rts_disableStopAfterReturn
|
|
| 42 | - c_hasLocalStopAfterReturn
|
|
| 33 | + -- Bind to capability 0 so it can't float between capabilities while the
|
|
| 34 | + -- target thread runs on capability 1.
|
|
| 35 | + doneVar <- newEmptyMVar
|
|
| 36 | + _ <- forkOn 0 $ do
|
|
| 37 | + checkFlag
|
|
| 38 | + "TSO_STOP_NEXT_BREAKPOINT"
|
|
| 39 | + rts_enableStopNextBreakpoint
|
|
| 40 | + rts_disableStopNextBreakpoint
|
|
| 41 | + c_hasLocalStopNextBreakpoint
|
|
| 42 | + checkFlag
|
|
| 43 | + "TSO_STOP_AFTER_RETURN"
|
|
| 44 | + rts_enableStopAfterReturn
|
|
| 45 | + rts_disableStopAfterReturn
|
|
| 46 | + c_hasLocalStopAfterReturn
|
|
| 47 | + putMVar doneVar ()
|
|
| 48 | + takeMVar doneVar
|
|
| 43 | 49 | |
| 44 | 50 | checkFlag
|
| 45 | 51 | :: String
|
| ... | ... | @@ -58,6 +64,7 @@ checkFlag label enable disable isMyThreadFlagSet = do |
| 58 | 64 | ThreadId tid# <- forkOn 1 $ do
|
| 59 | 65 | replicateM_ 2 $ do
|
| 60 | 66 | replyVar <- takeMVar targetCheckVar
|
| 67 | + yield -- make sure we reprocess the mailbox
|
|
| 61 | 68 | isSet <- (/= 0) <$> isMyThreadFlagSet
|
| 62 | 69 | putMVar replyVar isSet
|
| 63 | 70 |
| 1 | -(0,False)
|
|
| 1 | +(0,True)
|
|
| 2 | 2 | TSO_STOP_NEXT_BREAKPOINT set: ok
|
| 3 | 3 | TSO_STOP_NEXT_BREAKPOINT unset: ok
|
| 4 | -(0,False)
|
|
| 4 | +(0,True)
|
|
| 5 | 5 | TSO_STOP_AFTER_RETURN set: ok
|
| 6 | 6 | TSO_STOP_AFTER_RETURN unset: ok |
| ... | ... | @@ -97,6 +97,7 @@ library |
| 97 | 97 | , filepath
|
| 98 | 98 | , ghc-boot
|
| 99 | 99 | , mtl
|
| 100 | + , semaphore-compat
|
|
| 100 | 101 | , transformers
|
| 101 | 102 | , text
|
| 102 | 103 |
| ... | ... | @@ -29,6 +29,7 @@ module Haddock ( |
| 29 | 29 | withGhc
|
| 30 | 30 | ) where
|
| 31 | 31 | |
| 32 | +import Control.Concurrent.MVar (modifyMVar, modifyMVar_, newMVar)
|
|
| 32 | 33 | import Control.DeepSeq (force)
|
| 33 | 34 | import Control.Monad hiding (forM_)
|
| 34 | 35 | import Control.Monad.IO.Class (MonadIO(..))
|
| ... | ... | @@ -41,6 +42,7 @@ import Data.Maybe |
| 41 | 42 | import Data.IORef
|
| 42 | 43 | import Data.Map.Strict (Map)
|
| 43 | 44 | import Data.Version (makeVersion)
|
| 45 | +import GHC.Conc (getNumProcessors)
|
|
| 44 | 46 | import GHC.Parser.Lexer (ParserOpts)
|
| 45 | 47 | import qualified GHC.Driver.Config.Parser as Parser
|
| 46 | 48 | import qualified Data.Map.Strict as Map
|
| ... | ... | @@ -84,11 +86,55 @@ import Haddock.Options |
| 84 | 86 | import Haddock.Utils
|
| 85 | 87 | import Haddock.GhcUtils (modifySessionDynFlags, setOutputDir)
|
| 86 | 88 | import Haddock.Compat (getProcessID)
|
| 89 | +import System.Semaphore (AbstractSem(..), openSemaphore, releaseSemaphoreToken, waitOnSemaphore)
|
|
| 87 | 90 | |
| 88 | 91 | --------------------------------------------------------------------------------
|
| 89 | 92 | -- * Exception handling
|
| 90 | 93 | --------------------------------------------------------------------------------
|
| 91 | 94 | |
| 95 | +concSemChoiceFromFlags :: [Flag] -> Maybe (Either FilePath (Maybe Int))
|
|
| 96 | +concSemChoiceFromFlags =
|
|
| 97 | + List.foldl' step Nothing
|
|
| 98 | + where
|
|
| 99 | + step _ (Flag_ParCount n) = Just (Right n)
|
|
| 100 | + step _ (Flag_ParSemaphore sem) = Just (Left sem)
|
|
| 101 | + step acc _ = acc
|
|
| 102 | + |
|
| 103 | +-- | Build the render concurrency semaphore selected by Haddock's parallelism flags.
|
|
| 104 | +-- Without an explicit flag, render sequentially; @-j@ uses the host processor
|
|
| 105 | +-- count, @-jN@ uses a local bounded semaphore, and @-jsem@ joins the external
|
|
| 106 | +-- semaphore used for GHC jobserver coordination.
|
|
| 107 | +concSemFromChoice :: Maybe (Either FilePath (Maybe Int)) -> IO AbstractSem
|
|
| 108 | +concSemFromChoice choice =
|
|
| 109 | + case choice of
|
|
| 110 | + Nothing -> newBoundedSem 1
|
|
| 111 | + Just (Right Nothing) -> newBoundedSem =<< getNumProcessors
|
|
| 112 | + Just (Right (Just n)) -> newBoundedSem n
|
|
| 113 | + Just (Left semName) -> do
|
|
| 114 | + openSemaphore semName >>= \case
|
|
| 115 | + Left err -> throwIO err
|
|
| 116 | + Right sem -> do
|
|
| 117 | + tokens <- newMVar []
|
|
| 118 | + pure
|
|
| 119 | + AbstractSem
|
|
| 120 | + { acquireSem = mask $ \restore -> do
|
|
| 121 | + token <- restore (waitOnSemaphore sem)
|
|
| 122 | + modifyMVar_ tokens $ \held -> pure (token : held)
|
|
| 123 | + , releaseSem = mask_ $ do
|
|
| 124 | + token <- modifyMVar tokens $ \case
|
|
| 125 | + [] -> pure ([], Nothing)
|
|
| 126 | + heldToken : heldTokens -> pure (heldTokens, Just heldToken)
|
|
| 127 | + forM_ token releaseSemaphoreToken
|
|
| 128 | + }
|
|
| 129 | + |
|
| 130 | +injectParFlags :: Maybe (Either FilePath (Maybe Int)) -> [Flag] -> [Flag]
|
|
| 131 | +injectParFlags choice flags =
|
|
| 132 | + case choice of
|
|
| 133 | + Nothing -> flags
|
|
| 134 | + Just (Right Nothing) -> Flag_OptGhc "-j" : flags
|
|
| 135 | + Just (Right (Just n)) -> Flag_OptGhc ("-j" ++ show n) : flags
|
|
| 136 | + Just (Left sem) -> Flag_OptGhc "-jsem" : Flag_OptGhc sem : flags
|
|
| 137 | + |
|
| 92 | 138 | |
| 93 | 139 | handleTopExceptions :: IO a -> IO a
|
| 94 | 140 | handleTopExceptions =
|
| ... | ... | @@ -177,11 +223,12 @@ haddockWithGhc ghc args = handleTopExceptions $ do |
| 177 | 223 | Just "YES" | not noCompilation -> return $ Flag_OptGhc "-dynamic-too" : flags
|
| 178 | 224 | _ -> return flags
|
| 179 | 225 | |
| 180 | - -- Inject `-j` into ghc options, if given to Haddock
|
|
| 181 | - flags' <- pure $ case optParCount flags'' of
|
|
| 182 | - Nothing -> flags''
|
|
| 183 | - Just Nothing -> Flag_OptGhc "-j" : flags''
|
|
| 184 | - Just (Just n) -> Flag_OptGhc ("-j" ++ show n) : flags''
|
|
| 226 | + let parChoice = concSemChoiceFromFlags flags''
|
|
| 227 | + |
|
| 228 | + -- Inject parallelism flags into ghc options, if given to Haddock
|
|
| 229 | + flags' <- pure $ injectParFlags parChoice flags''
|
|
| 230 | + |
|
| 231 | + concSem <- concSemFromChoice parChoice
|
|
| 185 | 232 | |
| 186 | 233 | -- Whether or not to bypass the interface version check
|
| 187 | 234 | let noChecks = Flag_BypassInterfaceVersonCheck `elem` flags
|
| ... | ... | @@ -238,7 +285,7 @@ haddockWithGhc ghc args = handleTopExceptions $ do |
| 238 | 285 | }
|
| 239 | 286 | |
| 240 | 287 | -- Render the interfaces.
|
| 241 | - liftIO $ renderStep dflags parserOpts logger unit_state flags sinceQual qual packages ifaces
|
|
| 288 | + liftIO $ renderStep dflags parserOpts logger unit_state flags sinceQual qual concSem packages ifaces
|
|
| 242 | 289 | |
| 243 | 290 | -- If we were not given any input files, error if documentation was
|
| 244 | 291 | -- requested
|
| ... | ... | @@ -251,7 +298,7 @@ haddockWithGhc ghc args = handleTopExceptions $ do |
| 251 | 298 | packages <- liftIO $ readInterfaceFiles name_cache (readIfaceArgs flags) noChecks
|
| 252 | 299 | |
| 253 | 300 | -- Render even though there are no input files (usually contents/index).
|
| 254 | - liftIO $ renderStep dflags parserOpts logger unit_state flags sinceQual qual packages []
|
|
| 301 | + liftIO $ renderStep dflags parserOpts logger unit_state flags sinceQual qual concSem packages []
|
|
| 255 | 302 | |
| 256 | 303 | -- | Run the GHC action using a temporary output directory
|
| 257 | 304 | withTempOutputDir :: Ghc a -> Ghc a
|
| ... | ... | @@ -311,10 +358,11 @@ renderStep |
| 311 | 358 | -> [Flag]
|
| 312 | 359 | -> SinceQual
|
| 313 | 360 | -> QualOption
|
| 361 | + -> AbstractSem
|
|
| 314 | 362 | -> [(DocPaths, Visibility, FilePath, InterfaceFile)]
|
| 315 | 363 | -> [Interface]
|
| 316 | 364 | -> IO ()
|
| 317 | -renderStep dflags parserOpts logger unit_state flags sinceQual nameQual pkgs interfaces = do
|
|
| 365 | +renderStep dflags parserOpts logger unit_state flags sinceQual nameQual concSem pkgs interfaces = do
|
|
| 318 | 366 | updateHTMLXRefs (map (\(docPath, _ifaceFilePath, _showModules, ifaceFile) ->
|
| 319 | 367 | ( case baseUrl flags of
|
| 320 | 368 | Nothing -> docPathsHtml docPath
|
| ... | ... | @@ -330,7 +378,7 @@ renderStep dflags parserOpts logger unit_state flags sinceQual nameQual pkgs int |
| 330 | 378 | (DocPaths {docPathsSources=Just path}, _, _, ifile) <- pkgs
|
| 331 | 379 | iface <- ifInstalledIfaces ifile
|
| 332 | 380 | return (instMod iface, path)
|
| 333 | - render dflags parserOpts logger unit_state flags sinceQual nameQual interfaces installedIfaces extSrcMap
|
|
| 381 | + render dflags parserOpts logger unit_state flags sinceQual nameQual concSem interfaces installedIfaces extSrcMap
|
|
| 334 | 382 | where
|
| 335 | 383 | -- get package name from unit-id
|
| 336 | 384 | packageName :: Unit -> String
|
| ... | ... | @@ -348,11 +396,12 @@ render |
| 348 | 396 | -> [Flag]
|
| 349 | 397 | -> SinceQual
|
| 350 | 398 | -> QualOption
|
| 399 | + -> AbstractSem
|
|
| 351 | 400 | -> [Interface]
|
| 352 | 401 | -> [(FilePath, PackageInterfaces)]
|
| 353 | 402 | -> Map Module FilePath
|
| 354 | 403 | -> IO ()
|
| 355 | -render dflags parserOpts logger unit_state flags sinceQual qual ifaces packages extSrcMap = do
|
|
| 404 | +render dflags parserOpts logger unit_state flags sinceQual qual concSem ifaces packages extSrcMap = do
|
|
| 356 | 405 | let
|
| 357 | 406 | packageInfo = PackageInfo { piPackageName = fromMaybe (PackageName mempty)
|
| 358 | 407 | $ optPackageName flags
|
| ... | ... | @@ -516,7 +565,7 @@ render dflags parserOpts logger unit_state flags sinceQual qual ifaces packages |
| 516 | 565 | prologue
|
| 517 | 566 | themes opt_mathjax sourceUrls' opt_wiki_urls opt_base_url
|
| 518 | 567 | opt_contents_url opt_index_url unicode sincePkg packageInfo
|
| 519 | - qual pretty withQuickjump
|
|
| 568 | + qual pretty concSem withQuickjump
|
|
| 520 | 569 | return ()
|
| 521 | 570 | unless (withBaseURL || isJust (optOneShot flags)) $ do
|
| 522 | 571 | copyHtmlBits odir libDir themes withQuickjump
|
| ... | ... | @@ -555,7 +604,7 @@ render dflags parserOpts logger unit_state flags sinceQual qual ifaces packages |
| 555 | 604 | when (Flag_HyperlinkedSource `elem` flags && not (null ifaces)) $ do
|
| 556 | 605 | withTiming logger "ppHyperlinkedSource" (const ()) $ do
|
| 557 | 606 | _ <- {-# SCC ppHyperlinkedSource #-}
|
| 558 | - ppHyperlinkedSource (verbosity flags) (isJust (optOneShot flags)) odir libDir opt_source_css pretty srcMap ifaces
|
|
| 607 | + ppHyperlinkedSource (verbosity flags) (isJust (optOneShot flags)) odir libDir opt_source_css pretty concSem srcMap ifaces
|
|
| 559 | 608 | return ()
|
| 560 | 609 | |
| 561 | 610 | |
| ... | ... | @@ -842,4 +891,3 @@ getPrologue parserOpts flags = |
| 842 | 891 | rightOrThrowE :: Either String b -> IO b
|
| 843 | 892 | rightOrThrowE (Left msg) = throwE msg
|
| 844 | 893 | rightOrThrowE (Right x) = pure x |
| 845 | - |
| ... | ... | @@ -31,7 +31,8 @@ import Haddock.Backends.Hyperlinker.Utils |
| 31 | 31 | import Haddock.Backends.Xhtml.Utils (renderToBuilder)
|
| 32 | 32 | import Haddock.InterfaceFile
|
| 33 | 33 | import Haddock.Types
|
| 34 | -import Haddock.Utils (Verbosity, out, verbose)
|
|
| 34 | +import Haddock.Utils (Verbosity, out, verbose, mapConcurrentlyWith_)
|
|
| 35 | +import System.Semaphore (AbstractSem)
|
|
| 35 | 36 | import qualified Data.ByteString.Builder as Builder
|
| 36 | 37 | |
| 37 | 38 | -- | Generate hyperlinked source for given interfaces.
|
| ... | ... | @@ -51,19 +52,21 @@ ppHyperlinkedSource |
| 51 | 52 | -- ^ Custom CSS file path
|
| 52 | 53 | -> Bool
|
| 53 | 54 | -- ^ Flag indicating whether to pretty-print HTML
|
| 55 | + -> AbstractSem
|
|
| 56 | + -- ^ Concurrency semaphore for module renders
|
|
| 54 | 57 | -> M.Map Module SrcPath
|
| 55 | 58 | -- ^ Paths to sources
|
| 56 | 59 | -> [Interface]
|
| 57 | 60 | -- ^ Interfaces for which we create source
|
| 58 | 61 | -> IO ()
|
| 59 | -ppHyperlinkedSource verbosity isOneShot outdir libdir mstyle pretty srcs' ifaces = do
|
|
| 62 | +ppHyperlinkedSource verbosity isOneShot outdir libdir mstyle pretty concSem srcs' ifaces = do
|
|
| 60 | 63 | createDirectoryIfMissing True srcdir
|
| 61 | 64 | unless isOneShot $ do
|
| 62 | 65 | let cssFile = fromMaybe (defaultCssFile libdir) mstyle
|
| 63 | 66 | copyFile cssFile $ srcdir </> srcCssFile
|
| 64 | 67 | copyFile (libdir </> "html" </> highlightScript) $
|
| 65 | 68 | srcdir </> highlightScript
|
| 66 | - mapM_ (ppHyperlinkedModuleSource verbosity srcdir pretty srcs) ifaces
|
|
| 69 | + mapConcurrentlyWith_ concSem (ppHyperlinkedModuleSource verbosity srcdir pretty srcs) ifaces
|
|
| 67 | 70 | where
|
| 68 | 71 | srcdir = outdir </> hypSrcDir
|
| 69 | 72 | srcs = (srcs', M.mapKeys moduleName srcs')
|
| ... | ... | @@ -69,6 +69,7 @@ import Haddock.ModuleTree |
| 69 | 69 | import Haddock.Options (Visibility (..))
|
| 70 | 70 | import Haddock.Types
|
| 71 | 71 | import Haddock.Utils
|
| 72 | +import System.Semaphore (AbstractSem)
|
|
| 72 | 73 | import Haddock.Utils.Json
|
| 73 | 74 | import Haddock.Version
|
| 74 | 75 | |
| ... | ... | @@ -115,6 +116,8 @@ ppHtml |
| 115 | 116 | -- ^ How to qualify names
|
| 116 | 117 | -> Bool
|
| 117 | 118 | -- ^ Output pretty html (newlines and indenting)
|
| 119 | + -> AbstractSem
|
|
| 120 | + -- ^ Concurrency semaphore for module renders
|
|
| 118 | 121 | -> Bool
|
| 119 | 122 | -- ^ Also write Quickjump index
|
| 120 | 123 | -> IO ()
|
| ... | ... | @@ -138,6 +141,7 @@ ppHtml |
| 138 | 141 | packageInfo
|
| 139 | 142 | qual
|
| 140 | 143 | debug
|
| 144 | + concSem
|
|
| 141 | 145 | withQuickjump = do
|
| 142 | 146 | let
|
| 143 | 147 | visible_ifaces = filter visible ifaces
|
| ... | ... | @@ -192,7 +196,7 @@ ppHtml |
| 192 | 196 | visible_ifaces
|
| 193 | 197 | []
|
| 194 | 198 | |
| 195 | - mapM_
|
|
| 199 | + mapConcurrentlyWith_ concSem
|
|
| 196 | 200 | ( ppHtmlModule
|
| 197 | 201 | odir
|
| 198 | 202 | doctitle
|
| ... | ... | @@ -29,6 +29,7 @@ module Haddock.Options |
| 29 | 29 | , wikiUrls
|
| 30 | 30 | , baseUrl
|
| 31 | 31 | , optParCount
|
| 32 | + , optParSemaphore
|
|
| 32 | 33 | , optDumpInterfaceFile
|
| 33 | 34 | , optShowInterfaceFile
|
| 34 | 35 | , optLaTeXStyle
|
| ... | ... | @@ -48,7 +49,7 @@ module Haddock.Options |
| 48 | 49 | |
| 49 | 50 | import Control.Applicative
|
| 50 | 51 | import qualified Data.Char as Char
|
| 51 | -import Data.List (dropWhileEnd)
|
|
| 52 | +import Data.List (dropWhileEnd, isPrefixOf)
|
|
| 52 | 53 | import Data.Map (Map)
|
| 53 | 54 | import qualified Data.Map as Map
|
| 54 | 55 | import Data.Set (Set)
|
| ... | ... | @@ -122,6 +123,7 @@ data Flag |
| 122 | 123 | | Flag_SinceQualification String
|
| 123 | 124 | | Flag_IgnoreLinkSymbol String
|
| 124 | 125 | | Flag_ParCount (Maybe Int)
|
| 126 | + | Flag_ParSemaphore FilePath
|
|
| 125 | 127 | | Flag_TraceArgs
|
| 126 | 128 | | Flag_OneShot String
|
| 127 | 129 | | Flag_NoCompilation
|
| ... | ... | @@ -406,6 +408,11 @@ options backwardsCompat = |
| 406 | 408 | []
|
| 407 | 409 | (OptArg (\count -> Flag_ParCount (fmap read count)) "n")
|
| 408 | 410 | "load modules in parallel"
|
| 411 | + , Option
|
|
| 412 | + []
|
|
| 413 | + ["jsem"]
|
|
| 414 | + (ReqArg Flag_ParSemaphore "SEM")
|
|
| 415 | + "use semaphore SEM to limit parallelism"
|
|
| 409 | 416 | , Option
|
| 410 | 417 | []
|
| 411 | 418 | ["trace-args"]
|
| ... | ... | @@ -423,7 +430,7 @@ getUsage = do |
| 423 | 430 | |
| 424 | 431 | parseHaddockOpts :: [String] -> IO ([Flag], [String])
|
| 425 | 432 | parseHaddockOpts params =
|
| 426 | - case getOpt Permute (options True) params of
|
|
| 433 | + case getOpt Permute (options True) (normalizeJsemArgs params) of
|
|
| 427 | 434 | (flags, args, []) -> return (flags, args)
|
| 428 | 435 | (_, _, errors) -> do
|
| 429 | 436 | usage <- getUsage
|
| ... | ... | @@ -498,6 +505,18 @@ optMathjax flags = optLast [str | Flag_Mathjax str <- flags] |
| 498 | 505 | optParCount :: [Flag] -> Maybe (Maybe Int)
|
| 499 | 506 | optParCount flags = optLast [n | Flag_ParCount n <- flags]
|
| 500 | 507 | |
| 508 | +optParSemaphore :: [Flag] -> Maybe FilePath
|
|
| 509 | +optParSemaphore flags = optLast [s | Flag_ParSemaphore s <- flags]
|
|
| 510 | + |
|
| 511 | +normalizeJsemArgs :: [String] -> [String]
|
|
| 512 | +normalizeJsemArgs = map rewrite
|
|
| 513 | + where
|
|
| 514 | + rewrite arg
|
|
| 515 | + | arg == "-jsem" = "--jsem"
|
|
| 516 | + | "-jsem=" `isPrefixOf` arg = "--jsem=" ++ drop 6 arg
|
|
| 517 | + | "-jsem" `isPrefixOf` arg = "--jsem=" ++ drop 5 arg
|
|
| 518 | + | otherwise = arg
|
|
| 519 | + |
|
| 501 | 520 | qualification :: [Flag] -> Either String QualOption
|
| 502 | 521 | qualification flags =
|
| 503 | 522 | case map (map Char.toLower) [str | Flag_Qualification str <- flags] of
|
| ... | ... | @@ -54,6 +54,10 @@ module Haddock.Utils |
| 54 | 54 | , replace
|
| 55 | 55 | , spanWith
|
| 56 | 56 | |
| 57 | + -- * Concurrency utilities
|
|
| 58 | + , mapConcurrentlyWith_
|
|
| 59 | + , newBoundedSem
|
|
| 60 | + |
|
| 57 | 61 | -- * Logging
|
| 58 | 62 | , parseVerbosity
|
| 59 | 63 | , Verbosity (..)
|
| ... | ... | @@ -86,6 +90,13 @@ import Haddock.Types |
| 86 | 90 | import Data.Text.Lazy (Text)
|
| 87 | 91 | import qualified Data.Text.Lazy as LText
|
| 88 | 92 | |
| 93 | +import Control.Concurrent (forkFinally)
|
|
| 94 | +import Control.Concurrent.QSem (newQSem, signalQSem, waitQSem)
|
|
| 95 | +import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
|
|
| 96 | +import Control.Exception (throwIO)
|
|
| 97 | +import Control.Monad (void)
|
|
| 98 | +import System.Semaphore (AbstractSem (..))
|
|
| 99 | + |
|
| 89 | 100 | --------------------------------------------------------------------------------
|
| 90 | 101 | |
| 91 | 102 | -- * Logging
|
| ... | ... | @@ -334,6 +345,43 @@ html_xrefs = unsafePerformIO (readIORef html_xrefs_ref) |
| 334 | 345 | html_xrefs' :: Map ModuleName FilePath
|
| 335 | 346 | html_xrefs' = unsafePerformIO (readIORef html_xrefs_ref')
|
| 336 | 347 | |
| 348 | +-- * Concurrency utilities
|
|
| 349 | + |
|
| 350 | +--------------------------------------------------------------------------------
|
|
| 351 | + |
|
| 352 | +mapConcurrentlyWith_ :: AbstractSem -> (a -> IO ()) -> [a] -> IO ()
|
|
| 353 | +mapConcurrentlyWith_ _ _ [] = return ()
|
|
| 354 | +mapConcurrentlyWith_ concSem f xs = do
|
|
| 355 | + -- Create MVars to wait for completion and collect results
|
|
| 356 | + resultMVars <- mapM (const newEmptyMVar) xs
|
|
| 357 | + |
|
| 358 | + -- Fork a thread for each element
|
|
| 359 | + mapM_ (forkThread concSem) (zip xs resultMVars)
|
|
| 360 | + |
|
| 361 | + -- Wait for all threads and collect any errors
|
|
| 362 | + results <- mapM takeMVar resultMVars
|
|
| 363 | + |
|
| 364 | + -- Re-throw the first exception if any
|
|
| 365 | + case [err | Left err <- results] of
|
|
| 366 | + (err:_) -> throwIO err
|
|
| 367 | + [] -> return ()
|
|
| 368 | + where
|
|
| 369 | + forkThread concSem' (x, resultMVar) = do
|
|
| 370 | + acquireSem concSem'
|
|
| 371 | + void $ forkFinally (f x) $ \res -> do
|
|
| 372 | + releaseSem concSem'
|
|
| 373 | + putMVar resultMVar res
|
|
| 374 | + |
|
| 375 | +newBoundedSem :: Int -> IO AbstractSem
|
|
| 376 | +newBoundedSem maxThreads = do
|
|
| 377 | + sem <- newQSem (max 1 maxThreads)
|
|
| 378 | + pure
|
|
| 379 | + AbstractSem
|
|
| 380 | + { acquireSem = waitQSem sem
|
|
| 381 | + , releaseSem = signalQSem sem
|
|
| 382 | + }
|
|
| 383 | + |
|
| 384 | + |
|
| 337 | 385 | -----------------------------------------------------------------------------
|
| 338 | 386 | |
| 339 | 387 | -- * List utils
|