Haskell.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

ghc-commits

Thread Start a new thread
Download
Threads by month
  • ----- 2026 -----
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2025 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
ghc-commits@haskell.org

September 2026

  • 1 participants
  • 601 discussions
[Git][ghc/ghc][wip/dcoutts/io-manager-io-primop-exceptions] 2 commits: FIXUP: try to fix wasm symbol type for ghc_hs_iface
by Duncan Coutts (@dcoutts) 01 Sep '26

01 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC Commits: 6fbe14e1 by Duncan Coutts at 2026-09-01T15:30:54+01:00 FIXUP: try to fix wasm symbol type for ghc_hs_iface - - - - - 7f15ba30 by Duncan Coutts at 2026-09-01T15:31:22+01:00 EXPERIMENT: try forward declaring shared helper and switch style to handle the result of I/O manager ccalls, so that we can reuse it more with other I/O primops. - - - - - 2 changed files: - rts/HeapStackCheck.cmm - rts/PrimOps.cmm Changes: ===================================== rts/HeapStackCheck.cmm ===================================== @@ -20,6 +20,7 @@ import ReleaseSRWLockExclusives; #if !defined(UnregisterisedCompiler) import CLOSURE g0; import CLOSURE large_alloc_lim; +import CLOSURE ghc_hs_iface; import CLOSURE stg_MSG_THROWTO_info; import CLOSURE stg_MVAR_DIRTY_info; import CLOSURE stg_WHITEHOLE_info; ===================================== rts/PrimOps.cmm ===================================== @@ -2297,7 +2297,31 @@ stg_whereFromzh (P_ clos, W_ buf) Thread I/O blocking primitives -------------------------------------------------------------------------- */ -stg_waitReadyFd ( W_ fd, CInt rw ) +import stg_handleIOSubmitResult; // Forward decl + +stg_waitReadzh ( W_ fd ) +{ + CInt result; + + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", + /* IORead */ 0::CInt, fd); + + jump stg_handleIOSubmitResult(result); +} + +stg_waitWritezh ( W_ fd ) +{ + CInt result; + + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", + /* IOWrite */ 1::CInt, fd); + + jump stg_handleIOSubmitResult(result); +} + +stg_handleIOSubmitResult ( W_ fd, CInt rw ) { CInt result; @@ -2313,7 +2337,7 @@ stg_waitReadyFd ( W_ fd, CInt rw ) } /* case IOSubmitResultSyncSuccess*/ - if (result == 1::CInt) { + if (result == 1::CInt) (likely: True) { /* Success, don't even go via scheduler. */ return (); } @@ -2336,16 +2360,6 @@ stg_waitReadyFd ( W_ fd, CInt rw ) ccall sbarf("syncIOWaitReady result encoding error") never returns; } -stg_waitReadzh ( W_ fd ) -{ - jump stg_waitReadyFd(fd, /* IORead */ 0::CInt); -} - -stg_waitWritezh ( W_ fd ) -{ - jump stg_waitReadyFd(fd, /* IOWrite */ 1::CInt); -} - stg_delayzh ( W_ us_delay ) { CBool ok; /* Ok, or heap alloc failure. */ View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/c10ec9432fd7d06aece3be9e7cd0b6… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/c10ec9432fd7d06aece3be9e7cd0b6… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/sjakobi/T25450-march-native] rts: Export the cpu_model symbols behind __builtin_cpu_supports
by Simon Jakobi (@sjakobi) 01 Sep '26

01 Sep '26
Simon Jakobi pushed to branch wip/sjakobi/T25450-march-native at Glasgow Haskell Compiler / GHC Commits: 03897676 by Simon Jakobi at 2026-09-01T14:26:37+02:00 rts: Export the cpu_model symbols behind __builtin_cpu_supports Since -march=native's CPU probe (compiler/cbits/cpu_features_x86.c) switched to __builtin_cpu_supports, its object code references libgcc/compiler-rt internals: __cpu_indicator_init, __cpu_model and, under GCC >= 11 resp. LLVM >= 12, __cpu_features2. A normal static link resolves these, but GHCi's runtime linker only knows the symbols the RTS exports, so it could no longer load the ghc library from a static archive. This broke 15 plugin tests in the x86_64-windows-validate CI job, all with unknown symbol `__cpu_indicator_init' Export the three symbols from the RTS symbol table on x86, following the precedent of RTS_LIBGCC_SYMBOLS and the AArch64 outline-atomics symbols. This also covers other configurations where GHCi links statically, such as musl-based builds. Context: #25450 Assisted-by: Claude Fable 5 - - - - - 2 changed files: - compiler/cbits/cpu_features_x86.c - rts/RtsSymbols.c Changes: ===================================== compiler/cbits/cpu_features_x86.c ===================================== @@ -4,7 +4,12 @@ (-march=native, #25450). The bit positions in the returned mask are GHC's own; they must match - cpuFeatureBitLayout in GHC.Driver.CpuFeatures. */ + cpuFeatureBitLayout in GHC.Driver.CpuFeatures. + + The __builtin_cpu_* calls below compile to references to libgcc/compiler-rt + symbols (__cpu_model & co.), which GHCi's runtime linker can only resolve + because the RTS exports them: see RTS_X86_CPU_MODEL_SYMBOLS in + rts/RtsSymbols.c and keep its guards in sync with this file's. */ enum { GHC_X86_FEAT_SSE2 = 0, ===================================== rts/RtsSymbols.c ===================================== @@ -1039,6 +1039,30 @@ extern char **environ; #define RTS_ARM_OUTLINE_ATOMIC_SYMBOLS #endif +// libgcc/compiler-rt symbols behind __builtin_cpu_supports/__builtin_cpu_init, +// referenced by compiler/cbits/cpu_features_x86.c (-march=native, #25450). +// Without them the runtime linker cannot load the ghc library's object code +// (e.g. for plugins in a statically linked GHCi). Keep the guards in sync +// with cpu_features_x86.c. +#if (defined(i386_HOST_ARCH) || defined(x86_64_HOST_ARCH)) \ + && (defined(__GNUC__) || defined(__clang__)) +// __cpu_features2 holds the feature words beyond the first 32 (e.g. GFNI) +// and only exists in the runtimes of GCC >= 11 resp. LLVM >= 12. +#if (defined(__clang_major__) && __clang_major__ >= 12) \ + || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 11) +#define RTS_X86_CPU_FEATURES2_SYMBOLS \ + SymI_NeedsDataProto(__cpu_features2) +#else +#define RTS_X86_CPU_FEATURES2_SYMBOLS +#endif +#define RTS_X86_CPU_MODEL_SYMBOLS \ + SymI_NeedsProto(__cpu_indicator_init) \ + SymI_NeedsDataProto(__cpu_model) \ + RTS_X86_CPU_FEATURES2_SYMBOLS +#else +#define RTS_X86_CPU_MODEL_SYMBOLS +#endif + // Symbols defined by libc #define RTS_LIBC_SYMBOLS \ SymI_HasProto_redirect(atexit, atexit, STRENGTH_STRONG, SYM_TYPE_CODE) /* See Note [Strong symbols] */ \ @@ -1199,6 +1223,7 @@ RTS_OPENBSD_ONLY_SYMBOLS RTS_LIBC_SYMBOLS RTS_LIBGCC_SYMBOLS RTS_ARCH_LIBGCC_SYMBOLS +RTS_X86_CPU_MODEL_SYMBOLS RTS_FINI_ARRAY_SYMBOLS RTS_LIBFFI_SYMBOLS RTS_ARM_OUTLINE_ATOMIC_SYMBOLS @@ -1254,6 +1279,7 @@ void initLinkerRtsSyms (StrHashTable *symhash) { RTS_OPENBSD_ONLY_SYMBOLS RTS_LIBGCC_SYMBOLS RTS_ARCH_LIBGCC_SYMBOLS + RTS_X86_CPU_MODEL_SYMBOLS RTS_FINI_ARRAY_SYMBOLS RTS_LIBFFI_SYMBOLS RTS_ARM_OUTLINE_ATOMIC_SYMBOLS View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/0389767693f10e8989cdefab12a8b79… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/0389767693f10e8989cdefab12a8b79… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/dcoutts/printf-warnings] Enable printf warnings for trace functions and fix resulting warnings
by Duncan Coutts (@dcoutts) 01 Sep '26

01 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/printf-warnings at Glasgow Haskell Compiler / GHC Commits: 8966dcde by Duncan Coutts at 2026-09-01T13:11:32+01:00 Enable printf warnings for trace functions and fix resulting warnings Most of the existing printf-style functions are annotated with attributes to enable gcc/clang warnings for the printf format string, but several trace functions in Trace.h were missing this annotation. Enable them, and fix the resulting warnings. - - - - - 18 changed files: - rts/Capability.c - rts/IOManager.c - rts/Printer.c - rts/STM.c - rts/StableName.c - rts/ThreadPaused.c - rts/Threads.c - rts/Trace.h - rts/posix/Poll.c - rts/posix/Timeout.c - rts/sm/Compact.c - rts/sm/GC.c - rts/sm/GCUtils.c - rts/sm/MBlock.c - rts/sm/NonMoving.c - rts/sm/NonMovingMark.c - rts/sm/Storage.c - rts/sm/Sweep.c Changes: ===================================== rts/Capability.c ===================================== @@ -1396,7 +1396,9 @@ bool checkSparkCountInvariant (void) /* The invariant is * created = converted + remaining + gcd + fizzled */ - debugTrace(DEBUG_sparks,"spark invariant: %ld == %ld + %ld + %ld + %ld " + debugTrace(DEBUG_sparks,"spark invariant: %" FMT_Word " == " + "%" FMT_Word " + %" FMT_Word64 " + " + "%" FMT_Word " + %" FMT_Word " " "(created == converted + remaining + gcd + fizzled)", sparks.created, sparks.converted, remaining, sparks.gcd, sparks.fizzled); ===================================== rts/IOManager.c ===================================== @@ -848,7 +848,9 @@ static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime /* CMM primop. Result is true on success, or false on allocation failure. */ bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) { - debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id, us_delay); + debugTrace(DEBUG_iomanager, "thread %" FMT_StgThreadID + " waiting for %" FMT_Word " us", + tso->id, us_delay); ASSERT(tso->why_blocked == NotBlocked); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) ===================================== rts/Printer.c ===================================== @@ -554,7 +554,7 @@ printSmallBitmap( StgPtr spBottom, StgPtr payload, StgWord bitmap, uint32_t i; for(i = 0; i < size; i++, bitmap >>= 1 ) { - debugBelch(" stk[%ld] (%p) = ", (long)(spBottom-(payload+i)), payload+i); + debugBelch(" stk[%td] (%p) = ", spBottom-(payload+i), payload+i); if ((bitmap & 1) == 0) { printPtr((P_)payload[i]); debugBelch(" -- "); @@ -577,7 +577,7 @@ printLargeBitmap( StgPtr spBottom, StgPtr payload, StgLargeBitmap* large_bitmap, StgWord bitmap = large_bitmap->bitmap[bmp]; j = 0; for(; i < size && j < BITS_IN(W_); j++, i++, bitmap >>= 1 ) { - debugBelch(" stk[%" FMT_Word "] (%p) = ", (W_)(spBottom-(payload+i)), payload+i); + debugBelch(" stk[%td] (%p) = ", spBottom-(payload+i), payload+i); if ((bitmap & 1) == 0) { printPtr((P_)payload[i]); debugBelch(" -- "); ===================================== rts/STM.c ===================================== @@ -134,7 +134,8 @@ static int shake(void) { StgTRecHeader *__t = (_t); \ StgTRecChunk *__c = __t -> current_chunk; \ StgWord __limit = __c -> next_entry_idx; \ - TRACE("%p : FOR_EACH_ENTRY, current_chunk=%p limit=%ld", __t, __c, __limit); \ + TRACE("%p : FOR_EACH_ENTRY, current_chunk=%p limit=%" FMT_Word, \ + __t, __c, __limit); \ while (__c != END_STM_CHUNK_LIST) { \ StgWord __i; \ for (__i = 0; __i < __limit; __i ++) { \ ===================================== rts/StableName.c ===================================== @@ -195,7 +195,7 @@ lookupStableName (StgPtr p) if (sn != 0) { ASSERT(stable_name_table[sn].addr == p); - debugTrace(DEBUG_stable, "cached stable name %ld at %p",sn,p); + debugTrace(DEBUG_stable, "cached stable name %" FMT_Word " at %p", sn, p); stableNameUnlock(); return sn; } @@ -275,8 +275,8 @@ gcStableNameTable( void ) p->sn_obj = isAlive(p->sn_obj); if (p->sn_obj == NULL) { // StableName object died - debugTrace(DEBUG_stable, "GC'd StableName %ld (addr=%p)", - (long)(p - stable_name_table), p->addr); + debugTrace(DEBUG_stable, "GC'd StableName %td (addr=%p)", + p - stable_name_table, p->addr); freeSnEntry(p); } else if (p->addr != NULL) { // sn_obj is alive, update pointee ===================================== rts/ThreadPaused.c ===================================== @@ -315,8 +315,8 @@ threadPaused(Capability *cap, StgTSO *tso) || (bh_info == &stg_WHITEHOLE_info)) { debugTrace(DEBUG_squeeze, - "suspending duplicate work: %ld words of stack", - (long)((StgPtr)frame - tso->stackobj->sp)); + "suspending duplicate work: %td words of stack", + (StgPtr)frame - tso->stackobj->sp); // If this closure is already an indirection, then // suspend the computation up to this point. ===================================== rts/Threads.c ===================================== @@ -609,7 +609,7 @@ threadStackOverflow (Capability *cap, StgTSO *tso) debugTrace(DEBUG_gc, "threadStackOverflow of TSO %" FMT_StgThreadID " (%p): stack" - " too large (now %ld; max is %ld)", tso->id, tso, + " too large (now %ld; max is %u)", tso->id, tso, (long)tso->stackobj->stack_size, RtsFlags.GcFlags.maxStkSize); IF_DEBUG(gc, /* If we're debugging, just print out the top of the stack */ @@ -667,7 +667,7 @@ threadStackOverflow (Capability *cap, StgTSO *tso) } debugTraceCap(DEBUG_sched, cap, - "allocating new stack chunk of size %d bytes", + "allocating new stack chunk of size %" FMT_Word " bytes", chunk_size * sizeof(W_)); // Charge the current thread for allocating stack. Stack usage is @@ -966,7 +966,8 @@ printThreadBlockage(StgTSO *tso) switch (UntagWhyBlocked(ACQUIRE_LOAD(&tso->why_blocked))) { #if defined(mingw32_HOST_OS) case BlockedOnDoProc: - debugBelch("is blocked on proc (request: %" FMT_Word ")", tso->block_info.async_reqID); + debugBelch("is blocked on proc (request: %" FMT_Word ")", + tso->block_info.async_reqID); break; #endif #if !defined(THREADED_RTS) ===================================== rts/Trace.h ===================================== @@ -113,7 +113,7 @@ extern RUNTIME_TRACE_FLAG_CACHE RuntimeTraceFlagCache; // ----------------------------------------------------------------------------- #if defined(DEBUG) -void traceBegin (const char *str, ...); +void traceBegin (const char *str, ...) STG_PRINTF_ATTR(1, 2); void traceEnd (void); #endif @@ -265,7 +265,8 @@ void traceSparkEvent_ (Capability *cap, EventTypeNum tag, StgWord info1); traceCap_(cap, msg, ##__VA_ARGS__); \ } -void traceCap_(Capability *cap, char *msg, ...); +void traceCap_(Capability *cap, char *msg, ...) + STG_PRINTF_ATTR(2, 3); /* * Emit a trace message @@ -275,7 +276,8 @@ void traceCap_(Capability *cap, char *msg, ...); trace_(msg, ##__VA_ARGS__); \ } -void trace_(char *msg, ...); +void trace_(char *msg, ...) + STG_PRINTF_ATTR(1, 2); /* * A message or event emitted by the program ===================================== rts/posix/Poll.c ===================================== @@ -431,13 +431,13 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) int res = ppoll(poll_table, nfds, &tv, NULL); debugTrace(DEBUG_iomanager, - "ppoll(nfds = %d, timeout.sec = 0, timeout.nsec = 0) = %d", + "ppoll(nfds = %lu, timeout.sec = 0, timeout.nsec = 0) = %d", nfds, res); #else int res = poll(poll_table, nfds, 0); debugTrace(DEBUG_iomanager, - "poll(nfds = %d, timeout_ms = 0) = %d", + "poll(nfds = %lu, timeout_ms = 0) = %d", nfds, res); #endif if (res == 0) { @@ -512,7 +512,7 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) int res = ppoll(poll_table, nfds, timeout_ns, NULL); debugTrace(DEBUG_iomanager, - "ppoll(nfds = %d, timeout.sec = %d, timeout.nsec = %d) = %d", + "ppoll(nfds = %lu, timeout.sec = %lu, timeout.nsec = %lu) = %d", nfds, timeout_ns == NULL ? -1 : timeout_ns->tv_sec, timeout_ns == NULL ? 0 : timeout_ns->tv_nsec, res); @@ -520,7 +520,7 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) int res = poll(poll_table, nfds, timeout_ms); debugTrace(DEBUG_iomanager, - "poll(nfds = %d, timeout_ms = %d) = %d", + "poll(nfds = %lu, timeout_ms = %d) = %d", nfds, timeout_ms, res); #endif ===================================== rts/posix/Timeout.c ===================================== @@ -54,7 +54,8 @@ bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) insertTimeoutQueue(&iomgr->timeout_queue, timeout, target); debugTrace(DEBUG_iomanager, - "timer for delay of %lld usec installed at time %lld ns", + "timer for delay of %" FMT_Int " usec installed " + "at time %" FMT_Int64 " ns", us_delay, target); return true; } @@ -98,7 +99,8 @@ void processTimeoutCompletions(CapIOManager *iomgr, Time now) if (now < waketime) { break; } - debugTrace(DEBUG_iomanager,"timer expired at %lld ns", waketime); + debugTrace(DEBUG_iomanager, "timer expired at %" FMT_Int64 " ns", + waketime); StgTimeout *timeout; deleteMinTimeoutQueue(&iomgr->timeout_queue, &timeout); notifyTimeoutCompletion(iomgr, timeout); ===================================== rts/sm/Compact.c ===================================== @@ -1049,7 +1049,7 @@ compact(StgClosure *static_objects, // 2. update forward ptrs for (W_ g = 0; g < RtsFlags.GcFlags.generations; g++) { generation *gen = &generations[g]; - debugTrace(DEBUG_gc, "update_fwd: %d", g); + debugTrace(DEBUG_gc, "update_fwd: %" FMT_Word, g); update_fwd(gen->blocks); for (W_ n = 0; n < getNumCapabilities(); n++) { @@ -1059,7 +1059,7 @@ compact(StgClosure *static_objects, update_fwd_large(gen->scavenged_large_objects); update_fwd_cnf(gen->live_compact_objects); if (g == RtsFlags.GcFlags.generations-1 && gen->old_blocks != NULL) { - debugTrace(DEBUG_gc, "update_fwd: %d (compact)", g); + debugTrace(DEBUG_gc, "update_fwd: %" FMT_Word " (compact)", g); update_fwd_compact(gen->old_blocks); } } @@ -1069,7 +1069,8 @@ compact(StgClosure *static_objects, if (gen->old_blocks != NULL) { W_ blocks = update_bkwd_compact(gen); debugTrace(DEBUG_gc, - "update_bkwd: %d (compact, old: %d blocks, now %d blocks)", + "update_bkwd: %d (compact, old: %" FMT_Word " blocks, " + "now %" FMT_Word " blocks)", gen->no, gen->n_old_blocks, blocks); gen->n_old_blocks = blocks; } ===================================== rts/sm/GC.c ===================================== @@ -694,7 +694,15 @@ GarbageCollect (struct GcConfig config, #if defined(DEBUG) debugTrace(DEBUG_gc, - "mut_list_size: %lu (%d vars, %d arrays, %d MVARs, %d TVARs, %d TVAR_WATCH_QUEUEs, %d TREC_CHUNKs, %d TREC_HEADERs, %d others)", + "mut_list_size: %lu (" + "%" FMT_Word " vars, " + "%" FMT_Word " arrays, " + "%" FMT_Word " MVARs, " + "%" FMT_Word " TVARs, " + "%" FMT_Word " TVAR_WATCH_QUEUEs, " + "%" FMT_Word " TREC_CHUNKs, " + "%" FMT_Word " TREC_HEADERs, " + "%" FMT_Word " others)", (unsigned long)(mut_list_size * sizeof(W_)), mutlist_scav_stats.n_MUTVAR, mutlist_scav_stats.n_MUTARR, @@ -914,7 +922,7 @@ GarbageCollect (struct GcConfig config, // Free the mark stack. if (mark_stack_top_bd != NULL) { - debugTrace(DEBUG_gc, "mark stack: %d blocks", + debugTrace(DEBUG_gc, "mark stack: %" FMT_Word " blocks", countBlocks(mark_stack_top_bd)); freeChain(mark_stack_top_bd); } @@ -1018,7 +1026,9 @@ GarbageCollect (struct GcConfig config, need_copied_live = BLOCK_ROUND_UP(need_copied_live) / BLOCK_SIZE_W; need_uncopied_live = BLOCK_ROUND_UP(need_uncopied_live) / BLOCK_SIZE_W; - debugTrace(DEBUG_gc, "(before) copied_live: %d; uncopied_live: %d", need_copied_live, need_uncopied_live ); + debugTrace(DEBUG_gc, "(before) copied_live: %" FMT_Word "; " + "uncopied_live: %" FMT_Word, + need_copied_live, need_uncopied_live); // minOldGenSize states that the size of the oldest generation must be at least @@ -1027,7 +1037,8 @@ GarbageCollect (struct GcConfig config, if (RtsFlags.GcFlags.minOldGenSize >= need_copied_live + need_uncopied_live){ extra_needed = RtsFlags.GcFlags.minOldGenSize - (need_copied_live + need_uncopied_live); } - debugTrace(DEBUG_gc, "(minOldGen: %d; extra_needed: %d", RtsFlags.GcFlags.minOldGenSize, extra_needed); + debugTrace(DEBUG_gc, "(minOldGen: %d; extra_needed: %" FMT_Word, + RtsFlags.GcFlags.minOldGenSize, extra_needed); // If oldest gen is uncopying in some manner (compact or non-moving) then // add the extra requested by minOldGenSize to uncopying portion of memory. @@ -1041,7 +1052,9 @@ GarbageCollect (struct GcConfig config, ASSERT(need_uncopied_live + need_copied_live >= RtsFlags.GcFlags.minOldGenSize ); - debugTrace(DEBUG_gc, "(after) copied_live: %d; uncopied_live: %d", need_copied_live, need_uncopied_live ); + debugTrace(DEBUG_gc, "(after) copied_live: %" FMT_Word "; " + "uncopied_live: %" FMT_Word, + need_copied_live, need_uncopied_live); need_prealloc = 0; for (i = 0; i < n_nurseries; i++) { @@ -1098,7 +1111,7 @@ GarbageCollect (struct GcConfig config, need = BLOCKS_TO_MBLOCKS(need); got = mblocks_allocated; - debugTrace(DEBUG_gc,"Returning: %d %d", got, need); + debugTrace(DEBUG_gc, "Returning: %" FMT_Word " %" FMT_Word, got, need); uint32_t returned = 0; if (got > need) { @@ -1772,7 +1785,7 @@ prepare_collected_gen (generation *gen) gen->bitmap = bitmap_bdescr; bitmap = bitmap_bdescr->start; - debugTrace(DEBUG_gc, "bitmap_size: %d, bitmap: %p", + debugTrace(DEBUG_gc, "bitmap_size: %" FMT_Word ", bitmap: %p", bitmap_size, bitmap); // don't forget to fill it with zeros! @@ -2109,7 +2122,8 @@ resize_nursery (void) adjusted_blocks = (RtsFlags.GcFlags.maxHeapSize - 2 * blocks); - debugTrace(DEBUG_gc, "near maximum heap size of 0x%x blocks, blocks = %d, adjusted to %ld", + debugTrace(DEBUG_gc, "near maximum heap size of 0x%x blocks, " + "blocks = %" FMT_Word ", adjusted to %ld", RtsFlags.GcFlags.maxHeapSize, blocks, adjusted_blocks); pc_free = adjusted_blocks * 100 / RtsFlags.GcFlags.maxHeapSize; ===================================== rts/sm/GCUtils.c ===================================== @@ -173,9 +173,10 @@ push_scanned_block (bdescr *bd, gen_workspace *ws) void push_todo_block(bdescr *bd, gen_workspace *ws) { - debugTrace(DEBUG_gc, "push todo block %p (%ld words), step %d, todo_q: %ld", - bd->start, (unsigned long)(bd->free - bd->u.scan), - ws->gen->no, dequeElements(ws->todo_q)); + debugTrace(DEBUG_gc, "push todo block %p (%ld words), step %d, " + "todo_q: %" FMT_Int, + bd->start, (unsigned long)(bd->free - bd->u.scan), + ws->gen->no, dequeElements(ws->todo_q)); ASSERT(bd->link == NULL); ===================================== rts/sm/MBlock.c ===================================== @@ -398,7 +398,8 @@ void releaseFreeMemory(void) // Do nothing if USE_LARGE_ADDRESS_SPACE, we never want // to release address space - debugTrace(DEBUG_gc, "mblock_high_watermark: %p\n", mblock_high_watermark); + debugTrace(DEBUG_gc, "mblock_high_watermark: %p\n", + (void*)mblock_high_watermark); } #else // !USE_LARGE_ADDRESS_SPACE ===================================== rts/sm/NonMoving.c ===================================== @@ -707,7 +707,7 @@ void nonmovingPruneFreeSegmentList(void) oldest_gen->n_words -= pruned_segments * NONMOVING_SEGMENT_SIZE; nonmovingHeap.saved_free = NULL; debugTrace(DEBUG_nonmoving_gc, - "Pruned %d free segments, leaving %d on the free segment list.", + "Pruned %zd free segments, leaving %zd on the free segment list.", pruned_segments, new_length); traceNonmovingPrunedSegments(pruned_segments, new_length); trace(TRACE_nonmoving_gc, "Finished pruning free segment list."); ===================================== rts/sm/NonMovingMark.c ===================================== @@ -370,7 +370,8 @@ void nonmovingBeginFlush(Task *task) bool nonmovingWaitForFlush(void) { ACQUIRE_LOCK(&upd_rem_set_lock); - debugTrace(DEBUG_nonmoving_gc, "Flush count %d", upd_rem_set_flush_count); + debugTrace(DEBUG_nonmoving_gc, "Flush count %" FMT_Word, + upd_rem_set_flush_count); bool finished = upd_rem_set_flush_count == getNumCapabilities(); if (!finished) { waitCondition(&upd_rem_set_flushed_cond, &upd_rem_set_lock); @@ -1872,7 +1873,8 @@ nonmovingMark (MarkBudget* budget, MarkQueue *queue) RELEASE_SM_LOCK; } else { // Nothing more to do - debugTrace(DEBUG_nonmoving_gc, "Finished mark pass: %d", count); + debugTrace(DEBUG_nonmoving_gc, + "Finished mark pass: %" FMT_Word64, count); traceConcMarkEnd(count); return; } ===================================== rts/sm/Storage.c ===================================== @@ -874,7 +874,8 @@ resizeNurseriesEach (W_ blocks) node = capNoToNumaNode(i); if (nursery_blocks < blocks) { - debugTrace(DEBUG_gc, "increasing size of nursery from %d to %d blocks", + debugTrace(DEBUG_gc, "increasing size of nursery from %" FMT_Word + " to %" FMT_Word " blocks", nursery_blocks, blocks); nursery->blocks = allocNursery(node, nursery->blocks, blocks-nursery_blocks); @@ -883,7 +884,8 @@ resizeNurseriesEach (W_ blocks) { bdescr *next_bd; - debugTrace(DEBUG_gc, "decreasing size of nursery from %d to %d blocks", + debugTrace(DEBUG_gc, "decreasing size of nursery from %" FMT_Word + " to %" FMT_Word " blocks", nursery_blocks, blocks); bd = nursery->blocks; @@ -898,7 +900,8 @@ resizeNurseriesEach (W_ blocks) // might have gone just under, by freeing a large block, so make // up the difference. if (nursery_blocks < blocks) { - debugTrace(DEBUG_gc, "reincreasing size of nursery from %d to %d blocks", + debugTrace(DEBUG_gc, "reincreasing size of nursery from" + " %" FMT_Word " to %" FMT_Word " blocks", nursery_blocks, blocks); nursery->blocks = allocNursery(node, nursery->blocks, blocks-nursery_blocks); ===================================== rts/sm/Sweep.c ===================================== @@ -74,7 +74,11 @@ sweep(generation *gen) gen->live_estimate = live; - debugTrace(DEBUG_gc, "sweeping: %d blocks, %d were copied, %d freed (%d%%), %d are fragmented, live estimate: %ld%%", + debugTrace(DEBUG_gc, "sweeping: %" FMT_Word " blocks, " + "%" FMT_Word " were copied, " + "%" FMT_Word " freed (%" FMT_Word "%%), " + "%" FMT_Word " are fragmented, " + "live estimate: %lu%%", gen->n_old_blocks + freed, gen->n_old_blocks - blocks + freed, freed, View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8966dcdee562cd231f7d698a7b2bf64… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8966dcdee562cd231f7d698a7b2bf64… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/io-manager-deadlock-detection] DEBUG: attempts to debug wasm poll failures
by Duncan Coutts (@dcoutts) 01 Sep '26

01 Sep '26
Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC Commits: 7b991089 by Duncan Coutts at 2026-08-30T13:18:58+01:00 DEBUG: attempts to debug wasm poll failures - - - - - 1 changed file: - rts/posix/Poll.c Changes: ===================================== rts/posix/Poll.c ===================================== @@ -38,6 +38,10 @@ #include <signal.h> #include <poll.h> #endif +#if defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif + #include "IOManagerInternals.h" #include "Timeout.h" @@ -424,13 +428,13 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) int res = ppoll(poll_table, nfds, &tv, NULL); debugTrace(DEBUG_iomanager, - "ppoll(nfds = %d, timeout.sec = 0, timeout.nsec = 0) = %d", + "ppoll(nfds = %lu, timeout.sec = 0, timeout.nsec = 0) = %d", nfds, res); #else int res = poll(poll_table, nfds, 0); debugTrace(DEBUG_iomanager, - "poll(nfds = %d, timeout_ms = 0) = %d", + "poll(nfds = %lu, timeout_ms = 0) = %d", nfds, res); #endif if (res == 0) { @@ -510,15 +514,30 @@ bool awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) int res = ppoll(poll_table, nfds, timeout_ns, NULL); debugTrace(DEBUG_iomanager, - "ppoll(nfds = %d, timeout.sec = %d, timeout.nsec = %d) = %d", + "ppoll(nfds = %lu, timeout.sec = %d, timeout.nsec = %d) = %d", nfds, timeout_ns == NULL ? -1 : timeout_ns->tv_sec, timeout_ns == NULL ? 0 : timeout_ns->tv_nsec, res); +#elif defined(wasm32_HOST_ARCH) + /* Debug hack: on wasm CI we observe failures like + * T5866: poll res = -1: Not supported + * We want to get more detail. + */ + int res = poll(poll_table, nfds, timeout_ms); + if (res < 0) { + if (errno == ENOTSUP && nfds == 0 && timeout_ms == -1) { + pause(); + } else { + sysErrorBelch("poll res = %d, errno = %d (ENOTSUP == %d), nfds = %lu, timeout_ms = %d", + res, errno, ENOTSUP, nfds, timeout_ms); + stg_exit(EXIT_FAILURE); + } + } #else int res = poll(poll_table, nfds, timeout_ms); debugTrace(DEBUG_iomanager, - "poll(nfds = %d, timeout_ms = %d) = %d", + "poll(nfds = %lu, timeout_ms = %d) = %d", nfds, timeout_ms, res); #endif @@ -579,7 +598,8 @@ static void reportPollError(int res, nfds_t nfds) // as soon as we get thread submitting an operation that would exceed // the limit. } else { - sysErrorBelch("poll res = %d", res); + sysErrorBelch("poll res = %d, errno = %d, nfds = %lu ", + res, errno, (unsigned long) nfds); stg_exit(EXIT_FAILURE); } } View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/7b99108940a6eba52960363634ddb48… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/7b99108940a6eba52960363634ddb48… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/sjakobi/T27653] 3 commits: testsuite: Migrate perf tests off collect_compiler_stats('all')
by Simon Jakobi (@sjakobi) 01 Sep '26

01 Sep '26
Simon Jakobi pushed to branch wip/sjakobi/T27653 at Glasgow Haskell Compiler / GHC Commits: 4ea94bee by Simon Jakobi at 2026-09-01T13:08:30+02:00 testsuite: Migrate perf tests off collect_compiler_stats('all') The 'all' metric argument applies a single tolerance to bytes allocated, max_bytes_used and peak_megabytes_allocated, although their noise profiles are incompatible (#27653): allocations are nearly deterministic, residency needs 10-20%, and peak is quantized to 1 MB. Any single tolerance is too tight for one metric or too slack for another. This migrates the remaining users of 'all' (and of the 'all' default) to explicit per-metric collection, ahead of removing 'all' from the driver. peak_megabytes_allocated is dropped everywhere: its 1 MB granularity makes tight relative windows meaningless (#27613), and it is sensitive to GC timing. In #27489 it drifted by -5.3% while max_bytes_used moved by less than 0.1%. Where a test guards a memory property, max_bytes_used covers it at byte granularity. Where the motivating ticket was about compile-time memory (T11545, T15304, T26425), residency remains gated via max_bytes_used, now with a residency-appropriate tolerance. max_bytes_used is dropped where residency was only ever an accident of 'all': * T15630, T15630a, T20261: the underlying tickets (#15630, #20261) contain no memory data at all. One is a simplifier-ticks blowup and the other is stated entirely in allocation numbers, so the 20% window never had teeth. * T21839c: #21839's measurements show residency essentially flat (+0.16%) while allocations moved +7%, so allocations are the discriminating metric. They are already gated at 1% via collect_compiler_runtime. The ghc/max gate had previously broken CI spuriously (9fd11585eb widened it from 1% to 10% for that reason). Allocation tolerances are tightened to the testsuite's conventional 2% where 'all' previously left them at 10-20%. Assisted-by: Claude Fable 5 - - - - - 0c70adce by Simon Jakobi at 2026-09-01T13:08:31+02:00 testsuite: Remove the 'all' metric argument of collect_stats 'all' gated bytes allocated, max_bytes_used and peak_megabytes_allocated at a single tolerance, although their noise profiles are incompatible, making such tests either flaky or toothless (#27653). Closes #27653. Assisted-by: Claude Fable 5 - - - - - 9ec03eef by Simon Jakobi at 2026-09-01T13:08:31+02:00 testsuite: Make the deviation argument of collect_stats mandatory Almost every caller passes an explicit tolerance matched to the metric's noise profile, and the silent 20% default is far slacker than 'bytes allocated' merits. Only two tests relied on it. They now state their tolerance explicitly: large-project gets 10%, in line with other large compile-time tests. T9848 gets 2%: its metric is byte-for-byte deterministic across CI jobs and platforms of a given test_env, has drifted only about 2.5% since 2015, and the fusion failure it guards against would show up as a roughly +30000% jump. Assisted-by: Claude Fable 5 - - - - - 6 changed files: - libraries/base/tests/all.T - testsuite/driver/README.md - testsuite/driver/testlib.py - testsuite/tests/perf/compiler/all.T - testsuite/tests/perf/compiler/large-project/all.T - testsuite/tests/perf/space_leaks/all.T Changes: ===================================== libraries/base/tests/all.T ===================================== @@ -243,7 +243,7 @@ test('T8684', expect_broken(8684), compile_and_run, ['']) test('hWaitForInput-accurate-stdin', [js_broken(22349), expect_broken_for(16535, threaded_ways), req_process], compile_and_run, ['']) test('T9826',normal, compile_and_run,['']) test('T9848', - [ collect_stats('bytes allocated') + [ collect_stats('bytes allocated', 2) , only_ways(['normal']) , js_broken(22261) ], ===================================== testsuite/driver/README.md ===================================== @@ -35,11 +35,13 @@ differences, and things such as that are not necessary to be considered by the test writer anymore. This is due to the fact that the test comparison relies entirely on locally collected metrics on the testing machine. -As such, it is perfectly sufficient to write `collect_stats('all',20)` in the -".T" files to measure the 3 potential stats that can be collected for that test -and automatically test them for regressions, failing if there is more than a 20% -change in any direction. In fact, even that is not necessary as -`collect_stats()` defaults to 'all', and 20% deviation allowed. +A test states which metric to measure and how much deviation to allow, e.g. +`collect_stats('bytes allocated', 2)` in the ".T" files. Such a test fails if +the metric changes by more than 2% in either direction. To gate several metrics, +use one `collect_stats` call per metric, so that each gets a tolerance matched +to its noise profile: allocations are nearly deterministic and support tight +windows, while the residency metrics need considerably slacker ones (see +Note [Measuring residency] in testlib.py). The function `collect_compiler_stats()` is completely equivalent in every way to `collect_stats` except that it measures the performance of the compiler itself ===================================== testsuite/driver/testlib.py ===================================== @@ -24,7 +24,7 @@ import subprocess from testglobals import config, ghc_env, default_testopts, brokens, t, \ TestRun, TestResult, TestOptions, PerfMetric from testutil import strip_quotes, lndir, link_or_copy_file, passed, \ - failBecause, testing_metrics, residency_testing_metrics, \ + failBecause, residency_testing_metrics, \ stable_perf_counters, \ PassFail, badResult, str_warn, str_removeprefix from term_color import Color, colored_if @@ -816,28 +816,20 @@ def _collect_generic_stat(name : TestName, opts, metric_infos): # ----- -# Defaults to "test everything, and only break on extreme cases" -# -# The inputs to this function are slightly interesting: -# metric can be either: -# - 'all', in which case all 3 possible metrics are collected and compared. -# - The specific metric one wants to use in the test. -# - A set of the metrics one wants to use in the test. -# -# Deviation defaults to 20% because the goal is correctness over performance. -# The testsuite should avoid breaking when there is not an actual error. -# Instead, the testsuite should notify of regressions in a non-breaking manner. +# metric is either a single metric name or a set of metric names. Use one +# call per metric so each gets a tolerance matched to its noise profile. +# See Note [Measuring residency] for the residency metrics. # # collect_compiler_stats is used when the metrics collected are about the compiler. # collect_stats is used in the majority case when the metrics to be collected # are about the performance of the runtime code generated by the compiler. -def collect_compiler_stats(metric='all',deviation=20): +def collect_compiler_stats(metric, deviation): def f(name, opts, m=metric, d=deviation): no_lint(name, opts) return _collect_stats(name, opts, m, d, None, True) return f -def collect_stats(metric='all', deviation=20, static_stats_file=None): +def collect_stats(metric, deviation, static_stats_file=None): return lambda name, opts, m=metric, d=deviation, s=static_stats_file: _collect_stats(name, opts, m, d, s) def statsFile(comp_test: bool, name: str) -> str: @@ -864,12 +856,9 @@ def _collect_stats(name: TestName, opts, metrics, deviation: Optional[int], # This is a bit weird, though. return - # Normalize metrics to a list of strings. + # Normalize metrics to a set of strings. if isinstance(metrics, str): - if metrics == 'all': - metrics = testing_metrics() - else: - metrics = { metrics } + metrics = { metrics } opts.is_stats_test = True if is_compiler_stats_test: ===================================== testsuite/tests/perf/compiler/all.T ===================================== @@ -639,13 +639,15 @@ test ('T15164', ], compile, ['-v0 -O']) +# T15630/T15630a guard against exponential simplifier blowup when inlining +# join points (#15630). test('T15630', - [collect_compiler_stats() + [collect_compiler_stats('bytes allocated', 2) ], compile, ['-O2']) test('T15630a', - [collect_compiler_stats() + [collect_compiler_stats('bytes allocated', 2) ], compile, ['-O2']) @@ -769,12 +771,17 @@ test ('T9198', compile, ['']) +# Guards against quadratic demand-analysis cost on wide recursive data +# types, which manifested as a compile-time memory blowup (#11545). test('T11545', - [ collect_compiler_stats('all', 15) ], + [ collect_compiler_stats('bytes allocated', 2) + , collect_compiler_residency(15) ], compile, ['-O']) +# Guards against the compile-time memory blowup of #15304 test('T15304', - [ collect_compiler_stats('all', 10) ], + [ collect_compiler_stats('bytes allocated', 2) + , collect_compiler_residency(10) ], compile, ['-O']) test ('T20049', [ collect_compiler_stats('bytes allocated',2) ], @@ -796,8 +803,10 @@ test('T16875', # Testing one hole-fit with a lot in scope for #16875 collect_compiler_runtime(4), compile, ['-fdefer-type-errors -fno-max-valid-hole-fits -package ghc']) +# Guards against renamer/typechecker allocation regressions on very large +# generated modules (#20261, a Happy-generated parser). test ('T20261', - [collect_compiler_stats('all')], + [collect_compiler_stats('bytes allocated', 2)], compile, ['']) @@ -806,8 +815,7 @@ test ('T20261', # does not sensibly handle one test acting as both # a compile-time and a run-time performance test test('T21839c', - [ collect_compiler_stats('all', 10), - collect_compiler_runtime(1), + [ collect_compiler_runtime(1), only_ways(['normal'])], compile, ['-O']) @@ -873,8 +881,10 @@ test('interpreter_steplocal', ghci_script, ['interpreter_steplocal.script']) +# Guards against the compile-time memory blowup of #26425 test ('T26425', - [ collect_compiler_stats('all',20) ], + [ collect_compiler_stats('bytes allocated', 2) + , collect_compiler_residency(20) ], compile, ['-O']) ===================================== testsuite/tests/perf/compiler/large-project/all.T ===================================== @@ -7,7 +7,7 @@ def large_project_makedepend(num): return test( f'large-project-makedepend-{num}', [ - collect_compiler_stats('bytes allocated'), + collect_compiler_stats('bytes allocated', 10), pre_cmd(f'./large-project.sh {num}'), extra_files(['large-project.sh']), ignore_stderr, ===================================== testsuite/tests/perf/space_leaks/all.T ===================================== @@ -1,9 +1,6 @@ setTestOpts(js_skip) test('space_leak_001', - # This could potentially be replaced with - # collect_stats('all',5) to test all 3 with - # 5% possible deviation. [ collect_stats('bytes allocated',5), collect_runtime_residency(15), omit_ways(['profasm','profthreaded','threaded1','threaded2', View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/90e010e3e0ef8b9aeea7e2424d7632… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/90e010e3e0ef8b9aeea7e2424d7632… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/dcoutts/io-manager-io-primop-exceptions] 6 commits: Add new blocking functions for I/O primops
by Duncan Coutts (@dcoutts) 01 Sep '26

01 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC Commits: 7defd56e by Duncan Coutts at 2026-09-01T11:15:41+01:00 Add new blocking functions for I/O primops See the long Note. The point is, it will allow us to report synchronous exceptions from I/O primops, and do so much more flexibly. Previously the I/O managers could only report async exceptions and only nullary exceptions. This was OK historically, but no good as we add more I/O managers and expand the range of I/O operations we support. - - - - - 67846132 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Change the encoding of results from the I/O manager to I/O primops Previously we just had async continue or heap overflow. We now extend what we can report with synchronous success, and synchronous failure with an errno. See Note [Encoding of result of I/O manager operations] We don't use these two new cases yet, but we will. In particular an epoll I/O manager needs to be able to report synchronous success or failure for waitRead#/waitWrite#. - - - - - dc074de3 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Switch waitRead/Write# to use new blocking return frames and update the I/O managers to set the result before resuming the blocked threads. This makes it possible for I/O managers to report synchronous exceptions from the I/O primops, but that will be done in a subsequent commit. - - - - - f4e12d5e by Duncan Coutts at 2026-09-01T11:15:42+01:00 Switch Poll and Select I/O managers to report sync exceptions rather than using raiseAsync with blockedOnBadFD_closure. This uses the new mechanism in the blocking frame return code to report synchronous exceptions. - - - - - c34dcf3a by Duncan Coutts at 2026-09-01T11:15:42+01:00 Remove now-unused blockedOnBadFD It was previously thrown by the select and poll I/O managers, but now they use raisePrimIOException (with an EBADF errno). - - - - - c10ec943 by Duncan Coutts at 2026-09-01T11:15:42+01:00 Improve the docs for delay# waitRead# and waitWrite# Document that the waitRead/Write# can throw exceptions (this was true before too), and that all of them are async exception cancellation points. - - - - - 14 changed files: - compiler/GHC/Builtin/primops.txt.pp - libraries/ghc-internal/include/RtsIfaceSymbols.h - libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs - rts/HeapStackCheck.cmm - rts/IOManager.c - rts/IOManager.h - rts/IOManagerInternals.h - rts/Prelude.h - rts/PrimOps.cmm - rts/include/rts/RtsToHsIface.h - rts/include/stg/MiscClosures.h - rts/posix/Poll.c - rts/posix/Poll.h - rts/posix/Select.c Changes: ===================================== compiler/GHC/Builtin/primops.txt.pp ===================================== @@ -3305,21 +3305,28 @@ section "Delay/wait operations" primop DelayOp "delay#" GenPrimOp Int# -> State# s -> State# s - {Sleep specified number of microseconds.} + {Suspend the calling thread for a specified number of microseconds. This + operation is /interruptible/ by async exceptions.} with effect = ReadWriteEffect out_of_line = True primop WaitReadOp "waitRead#" GenPrimOp Int# -> State# s -> State# s - {Block until input is available on specified file descriptor.} + {Suspend the calling thread until input is available (or an I/O error + occurs) on the given file descriptor. This operation can throw exceptions + of type 'IOException'. The operation is also /interruptible/ by async + exceptions.} with effect = ReadWriteEffect out_of_line = True primop WaitWriteOp "waitWrite#" GenPrimOp Int# -> State# s -> State# s - {Block until output is possible on specified file descriptor.} + {Suspend the calling thread until output is possible (or an I/O error + occurs) on the given file descriptor. This operation can throw exceptions + of type 'IOException'. The operation is also /interruptible/ by async + exceptions.} with effect = ReadWriteEffect out_of_line = True ===================================== libraries/ghc-internal/include/RtsIfaceSymbols.h ===================================== @@ -23,11 +23,6 @@ CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure) CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure) -#if defined(mingw32_HOST_OS) -UNDEF_CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure) -#else -CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure) -#endif CLOSURE(GHCziInternalziConcziSync, runSparks_closure) CLOSURE(GHCziInternalziConcziIO, ensureIOManagerIsRunning_closure) CLOSURE(GHCziInternalziConcziIO, interruptIOManager_closure) ===================================== libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs ===================================== @@ -23,14 +23,13 @@ import GHC.Internal.Types () , closeFdWith , threadDelay , registerDelay - , blockedOnBadFD -- used by RTS ) where -- TODO: Use new Windows I/O manager import qualified GHC.Internal.Stack.Types as Rebindable import GHC.Internal.Base -import GHC.Internal.Control.Exception (finally, SomeException, toException) +import GHC.Internal.Control.Exception (finally) import GHC.Internal.Data.Foldable (forM_, mapM_, sequence_) import GHC.Internal.Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicWriteIORef) import GHC.Internal.Data.Maybe (fromMaybe) @@ -185,10 +184,6 @@ threadWait evt fd = mask_ $ do then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing else return () --- used at least by RTS in 'select()' IO manager backend -blockedOnBadFD :: SomeException -blockedOnBadFD = toException $ errnoToIOError "awaitEvent" eBADF Nothing Nothing - threadWaitSTM :: Event -> Fd -> IO (STM (), IO ()) threadWaitSTM evt fd = mask_ $ do m <- newTVarIO Nothing ===================================== rts/HeapStackCheck.cmm ===================================== @@ -40,6 +40,8 @@ import CLOSURE stg_ret_l_info; import CLOSURE stg_ret_n_info; import CLOSURE stg_ret_p_info; import CLOSURE stg_stack_save_entries; +import CLOSURE stg_block_io_unit_info; +import CLOSURE stg_block_io_int_info; #endif /* Stack/Heap Check Failure @@ -791,3 +793,169 @@ stg_block_async } #endif +/* Note [Thread blocking for new I/O primops] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The classic I/O primops are: + + waitRead#, waitWrite# :: Int# -> State# s -> State# s + +Note that they have no result, with wrapper types returning IO (). Each one +simply suspends the calling thread until a condition occurs (I/O or time). + +Despite this external simplicity, internally there is more complexity with a +number of cases to handle: + +At submission time: + - synchronous failure, e.g. bad fd + - synchronous success, e.g. waiting on a regular file, which is always ready + - proceed asynchronously: block thread, return to scheduler + +Later, at I/O completion, I/O error, or async exception time: + - reported failure, e.g. stale fd + - success + +The waitRead/Write# also have non-reported failures, such as I/O errors, +but these are treated as the success case since these primops are defined to +wait for I/O or an error. We only report failures, via exceptions, for cases +where it would be unsafe to proceed with a subsequent I/O operation on the +same fd. In particular this includes when the fd was closed while a thread was +waiting on the fd (since it's very unsafe to read/write an fd that is closed or +worse: has been recycled to refer to a different file). + +The synchronous success and failure cases are handled by the initiating +primops. In the blocking case, it relies on one of these stg_block_ functions, +stack frames, and stack frame info table entry code. The key bits of the +mechanism are: + + - The stg_block_ cmm function sets up the stack frame for the blocked thread. + - The stack frame entries can be overwritten by the I/O manager, e.g. upon I/O + completion or error. + - The stack frame's info table entry code is invoked when the thread is + resumed. + - The entries in the stack frame become arguments to the info table entry code. + - The result(s) returned by the info table entry code _are_ the result(s) of + the I/O primop. + +It is important to keep clear the distinction between the stack frame entries +and the final primop result(s): the stack frame's info table entry code +transforms the former into the latter. + +The convention we follow is that the stack frame contains two fields: + - an outcome, using IOOpOutcome values: in-flight, success, failed, cancelled. + - a result or error code. For outcome success it is a result. For failed it is + the error code (using posix errno codes), while for cancelled and in-flight + outcomes there is no result. + +This outcome+result/failure encoding is exactly the same as the corresponding +fields in the StgAsyncIOOp. One day we might just use the StgAsyncIOOp itself, +once all I/O managers use them. + +The waitRead/waitWrite# primops do not use a result, but later I/O primops will +do. In particular read/write on files return a length, and other I/O primitives +also have a single result. + +So this gives us two block functions and corresponding info tables: + + 1. stg_block_io_unit: with a stack frame containing outcome+result/failure. + On resume, throw exception on failure and otherwise return no args. + This is for primops with result IO (). + + 2. stg_block_io_int: with a stack frame containing outcome+result/failure. + On resume, throw exception on failure and otherwise return result. + This is for primops with result IO Int or IO Word. + + 3. stg_block_io_aiop: in future if we add async I/O primitives that return an + StgAsyncIOOp we would also need versions that return that (since even + submitting async I/O can block if there's too much I/O in flight). + +The stack frame layout used is: + Sp[1] stg_block_io_unit_info or stg_block_io_int_info + Sp[1] outcome + Sp[2] result or error + +C code should use setTsoIOOpOutcome to set this. But update this if the layout +changes! + + +Note [Calling convention for raisePrimIOException] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In cases 1 & 2 we check the outcome and, on failure, we throw an exception. Our +approach is to use a helper function implemeted in generated Haskell code, in +ghc-internal. We need to perform a tail call to this helper function: + + raisePrimIOException :: Int# -> State# s -> (# State# s, a #) + +The calling convention for top-level generated Haskell function like this uses +R2 because it has two arguments -- the Int# and State# -- but the State# +argument has a zero-width repesentation. The cmm call uses R1, R2, ... etc so +to pass in R2 we have to use a dummy first argument which will be passed in R1 +(and ignored). +*/ + +/* stg_block_io_unit_info : the return info table for stg_block_io_unit */ +INFO_TABLE_RET ( stg_block_io_unit, RET_SMALL, W_ info_ptr, + W_ outcome, W_ result ) + return () +{ + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; + dummyR1 = 0; + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, result); + } else { + /* success or cancelled outcomes */ + return (); + } +} + +/* Blocking for I/O primops with result IO (). + * See Note [Thread blocking for new I/O primops]. + */ +stg_block_io_unit +{ + /* Fill out the stack frame with dummy values. The IO manager will + * overwrite these with the actual results when the I/O operation + * completes, fails, or is cancelled. + */ + Sp_adj(-3); + Sp(0) = stg_block_io_unit_info; + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight + Sp(2) = 0; // result/errno + BLOCK_GENERIC; +} + +/* stg_block_io_int_info : the return info table for stg_block_io_int */ +INFO_TABLE_RET ( stg_block_io_int, RET_SMALL, W_ info_ptr, + W_ outcome, W_ result ) + return () +{ + if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; + dummyR1 = 0; + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, result); + } else { + /* success or cancelled outcomes */ + return (result); + } +} + +/* Blocking for I/O primops with result IO Int or Word (or rather Int#/Word#). + * See Note [Thread blocking for new I/O primops]. + */ +stg_block_io_int +{ + /* Fill out the stack frame with dummy values. The IO manager will + * overwrite these with the actual results when the I/O operation + * completes, fails, or is cancelled. + */ + Sp_adj(-3); + Sp(0) = stg_block_io_int_info; + Sp(1) = 0; // outcome: 0 indicates IOOpOutcomeInFlight + Sp(2) = 0; // result/errno + BLOCK_GENERIC; +} ===================================== rts/IOManager.c ===================================== @@ -399,21 +399,6 @@ void startIOManager(void) switch (iomgr_type) { -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL) -#if defined(IOMGR_ENABLED_SELECT) - case IO_MANAGER_SELECT: -#endif -#if defined(IOMGR_ENABLED_POLL) - case IO_MANAGER_POLL: -#endif - /* Make the exception CAF a GC root. See initBuiltinGcRoots for - * similar examples. We throw this exception if a thread tries to - * wait on an invalid FD. - */ - getStablePtr((StgPtr)blockedOnBadFD_closure); - break; -#endif - #if defined(IOMGR_ENABLED_MIO_POSIX) case IO_MANAGER_MIO_POSIX: /* Posix implementation in posix/Signals.c @@ -776,10 +761,10 @@ void interruptIOManager(CapIOManager *iomgr) /* CMM primop. Result is true on success, or false on allocation failure. */ -bool syncIOWaitReady(CapIOManager *iomgr, - StgTSO *tso, - IOReadOrWrite rw, - HsInt fd) +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, + StgTSO *tso, + IOReadOrWrite rw, + HsInt fd) { debugTrace(DEBUG_iomanager, "thread %ld waiting for %s I/O readiness on fd %d", @@ -795,7 +780,7 @@ bool syncIOWaitReady(CapIOManager *iomgr, tso->block_info.fd = fd; appendToIOBlockedQueue(iomgr, tso); RELEASE_STORE(&tso->why_blocked, why_blocked); - return true; + return IOSubmitResultAsyncContinue; } #endif #if defined(IOMGR_ENABLED_POLL) ===================================== rts/IOManager.h ===================================== @@ -325,8 +325,33 @@ typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite; * false on heap allocation failure. */ +/* Note [Encoding of result of I/O manager operations] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We have quite a bit of information that to return from the I/O manager calls +back to the I/O primops. It (somewhat uncomfortably) fits into a C int. + +We use the following encoding of the result: + + * negative: synchronous failure, with -errno as the result. + * 0: ok, result successful, proceed to async: suspend the TSO. + * 1: synchronous success, return without suspending the TSO. This can occur + for example when waiting on readiness of a regular file (which is always + ready). + * 2: heap overflow + * >2: unallocated status codes +*/ +typedef int IOSubmitResult; + +/* Provide constants for IOSubmitResult */ +enum IOSubmitResultCodes { + /* negative numbers are -errno error codes */ + IOSubmitResultAsyncContinue = 0, + IOSubmitResultSyncSuccess = 1, + IOSubmitResultHeapOverflow = 2 +}; + /* Called from CMM primop */ -bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); void syncIOCancel(CapIOManager *iomgr, StgTSO *tso); ===================================== rts/IOManagerInternals.h ===================================== @@ -81,5 +81,25 @@ struct _CapIOManager { }; +/* Fill in the outcome and result/error on the TSO's stack frame. + * The TSO must be blocked using one of the stg_block_io_* blocking actions. + * + * Once the thread is resumed (by the scheduler) the code for stg_block_*_info + * will pick up the info from the stack frame and use it to see if there's been + * a failure, and if so to raise a PrimIOException. + * + * See Note [Thread blocking for new I/O primops]. + */ +INLINE_HEADER void setTsoIOOpOutcome (StgTSO *tso, + enum IOOpOutcome outcome, + uint32_t result) +{ + ASSERT((StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_unit_info + || (StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_int_info); + + tso->stackobj->sp[1] = (W_)outcome; + tso->stackobj->sp[2] = (W_)result; +} + #include "EndPrivate.h" ===================================== rts/Prelude.h ===================================== @@ -65,8 +65,6 @@ extern StgClosure ZCMain_main_closure; #define overflowException_closure ghc_hs_iface->overflowException_closure #define divZeroException_closure ghc_hs_iface->divZZeroException_closure -#define blockedOnBadFD_closure ghc_hs_iface->blockedOnBadFD_closure - #define Czh_con_info ghc_hs_iface->Czh_con_info #define Izh_con_info ghc_hs_iface->Izh_con_info #define Fzh_con_info ghc_hs_iface->Fzh_con_info ===================================== rts/PrimOps.cmm ===================================== @@ -2297,32 +2297,53 @@ stg_whereFromzh (P_ clos, W_ buf) Thread I/O blocking primitives -------------------------------------------------------------------------- */ -stg_waitReadzh ( W_ fd ) +stg_waitReadyFd ( W_ fd, CInt rw ) { - CBool ok; /* Ok, or heap alloc failure. */ + CInt result; - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", - CurrentTSO "ptr", - /* IORead */ 0::CInt, fd); - if (ok != 0::CBool) (likely: True) { - jump stg_block_noregs(); - } else { + (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", rw, fd); + + /* See Note [Encoding of result of I/O manager operations] */ + + /* case IOSubmitResultAsyncContinue */ + if (result == 0::CInt) (likely: True) { + /* See Note [Thread blocking for new I/O primops] */ + jump stg_block_io_unit(); + } + + /* case IOSubmitResultSyncSuccess*/ + if (result == 1::CInt) { + /* Success, don't even go via scheduler. */ + return (); + } + + /* negative numbers are -errno error codes */ + if (result < 0::CInt) { + /* See Note [Calling convention for raisePrimIOException] */ + W_ dummyR1; W_ errno; + dummyR1 = 0; + errno = TO_W_(-result); + jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface])) + (dummyR1, errno); + } + + /* case IOSubmitResultHeapOverflow */ + if (result == 2::CInt) { jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface])); + } + ccall sbarf("syncIOWaitReady result encoding error") never returns; } -stg_waitWritezh ( W_ fd ) +stg_waitReadzh ( W_ fd ) { - CBool ok; /* Ok, or heap alloc failure. */ + jump stg_waitReadyFd(fd, /* IORead */ 0::CInt); +} - (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", - CurrentTSO "ptr", - /* IOWrite */ 1::CInt, fd); - if (ok != 0::CBool) (likely: True) { - jump stg_block_noregs(); - } else { - jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface])); - } +stg_waitWritezh ( W_ fd ) +{ + jump stg_waitReadyFd(fd, /* IOWrite */ 1::CInt); } stg_delayzh ( W_ us_delay ) ===================================== rts/include/rts/RtsToHsIface.h ===================================== @@ -28,7 +28,6 @@ typedef struct { StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure - StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure StgClosure *raisePrimIOException_info; // GHC.Internal.Conc.IO.raisePrimIOException_info StgClosure *runSparks_closure; // GHC.Internal.Conc.Sync.runSparks_closure StgClosure *ensureIOManagerIsRunning_closure; // GHC.Internal.Conc.IO.ensureIOManagerIsRunning_closure ===================================== rts/include/stg/MiscClosures.h ===================================== @@ -386,6 +386,10 @@ RTS_FUN_DECL(stg_block_throwto); RTS_RET(stg_block_throwto); /* Blocking for I/O primops */ +RTS_FUN_DECL(stg_block_io_unit); +RTS_RET(stg_block_io_unit); +RTS_FUN_DECL(stg_block_io_int); +RTS_RET(stg_block_io_int); #if defined(mingw32_HOST_OS) RTS_FUN_DECL(stg_block_async); RTS_RET(stg_block_async); ===================================== rts/posix/Poll.c ===================================== @@ -20,7 +20,6 @@ #include "Prelude.h" #include "RtsUtils.h" #include "rts/Time.h" -#include "RaiseAsync.h" #include "Trace.h" #include "Poll.h" @@ -171,14 +170,13 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr) } -/* Used to implement syncIOWaitReady. - * Result is true on success, or false on allocation failure. */ -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, - IOReadOrWrite rw, HsInt fd) +/* Used to implement syncIOWaitReady. */ +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, + IOReadOrWrite rw, HsInt fd) { StgAsyncIOOp *aiop; aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp)); - if (RTS_UNLIKELY(aiop == NULL)) return false; + if (RTS_UNLIKELY(aiop == NULL)) return IOSubmitResultHeapOverflow; SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS); aiop->notify.tso = tso; aiop->notify_type = NotifyTSO; @@ -189,13 +187,12 @@ bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd); } -/* Result is true on success, or false on allocation failure. */ -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, - IOReadOrWrite rw, int fd) +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, + IOReadOrWrite rw, int fd) { if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) { bool ok = enlargeTables(iomgr); - if (RTS_UNLIKELY(!ok)) return false; + if (RTS_UNLIKELY(!ok)) return IOSubmitResultHeapOverflow; } int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop); @@ -216,7 +213,7 @@ bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, .events = rw == IORead ? POLLIN : POLLOUT, .revents = 0 }; - return true; + return IOSubmitResultAsyncContinue; } @@ -226,6 +223,7 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso) ASSERT(aiop->notify_type == NotifyTSO); ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop); ioCancel(iomgr, aiop); + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result); /* We cannot use the normal notifyIOCompletion here. We are in the context * of throwTo, interrupting a thread blocked on IO via an async exception. * We don't put the TSO back on the run queue or change the why_blocked @@ -284,26 +282,18 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) switch (aiop->notify_type) { case NotifyTSO: { - if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) { - /* The fd is invalid: raise an IOError exception in the blocked - * thread. (See bug #4934 for what happens without this.) - */ - StgTSO *tso = aiop->notify.tso; - debugTrace(DEBUG_iomanager, - "Raising exception in thread %" FMT_StgThreadID - " blocked on an invalid fd", tso->id); - raiseAsync(iomgr->cap, tso, - (StgClosure *)blockedOnBadFD_closure, - false, NULL); - } else { - /* We should be guaranteed that the tso is still on the same - * cap because the tso was not on the run queue of any cap and - * so is not subject to thread migration. - */ - StgTSO *tso = aiop->notify.tso; - pushOnRunQueue(iomgr->cap, tso); - RELEASE_STORE(&tso->why_blocked, NotBlocked); - } + /* We should be guaranteed that the tso is still on the same + * cap because the tso was not on the run queue of any cap and + * so is not subject to thread migration. + */ + StgTSO *tso = aiop->notify.tso; + ASSERT(tso->cap == iomgr->cap); + + /* Fill in the outcome and result/error on the TSO's stack frame */ + setTsoIOOpOutcome(tso, aiop->outcome, aiop->result); + pushOnRunQueue(iomgr->cap, tso); + RELEASE_STORE(&tso->why_blocked, NotBlocked); + /* For the TSO case, the aiop was only reachable from the TSO * itself, and thus it is now no longer be reachable at all. */ ===================================== rts/posix/Poll.h ===================================== @@ -20,13 +20,13 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr); void freeCapabilityIOManagerPoll(CapIOManager *iomgr); /* Synchronous I/O and timer operations */ -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, - IOReadOrWrite rw, HsInt fd); +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, + IOReadOrWrite rw, HsInt fd); void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso); /* Asynchronous operations */ -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, - IOReadOrWrite rw, int fd); +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, + IOReadOrWrite rw, int fd); void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop); /* Scheduler operations */ ===================================== rts/posix/Select.c ===================================== @@ -15,7 +15,6 @@ #include "Signals.h" #include "Schedule.h" #include "Prelude.h" -#include "RaiseAsync.h" #include "RtsUtils.h" #include "Capability.h" #include "Select.h" @@ -480,13 +479,18 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) IF_DEBUG(scheduler, debugBelch("Killing blocked thread %" FMT_StgThreadID " on bad fd=%i\n", tso->id, fd)); - raiseAsync(iomgr->cap, tso, - (StgClosure *)blockedOnBadFD_closure, false, NULL); + + /* Fill in the outcome and error on the TSO's stack frame */ + setTsoIOOpOutcome(tso, IOOpOutcomeFailed, EBADF); + pushOnRunQueue(iomgr->cap,tso); + RELEASE_STORE(&tso->why_blocked, NotBlocked); break; case RTS_FD_IS_READY: IF_DEBUG(scheduler, debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n", tso->id)); + /* Fill in the outcome and result on the TSO's stack frame */ + setTsoIOOpOutcome(tso, IOOpOutcomeSuccess, 0); pushOnRunQueue(iomgr->cap,tso); RELEASE_STORE(&tso->why_blocked, NotBlocked); break; View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/54810171fe3167719d4ccd32ad4bf0… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/54810171fe3167719d4ccd32ad4bf0… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/dcoutts/printf-warnings] FIXUP: more such issues on more platforms
by Duncan Coutts (@dcoutts) 01 Sep '26

01 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/printf-warnings at Glasgow Haskell Compiler / GHC Commits: 42c40d8c by Duncan Coutts at 2026-09-01T11:05:14+01:00 FIXUP: more such issues on more platforms - - - - - 4 changed files: - rts/Printer.c - rts/STM.c - rts/StableName.c - rts/ThreadPaused.c Changes: ===================================== rts/Printer.c ===================================== @@ -554,7 +554,7 @@ printSmallBitmap( StgPtr spBottom, StgPtr payload, StgWord bitmap, uint32_t i; for(i = 0; i < size; i++, bitmap >>= 1 ) { - debugBelch(" stk[%ld] (%p) = ", (long)(spBottom-(payload+i)), payload+i); + debugBelch(" stk[%td] (%p) = ", spBottom-(payload+i), payload+i); if ((bitmap & 1) == 0) { printPtr((P_)payload[i]); debugBelch(" -- "); @@ -577,7 +577,7 @@ printLargeBitmap( StgPtr spBottom, StgPtr payload, StgLargeBitmap* large_bitmap, StgWord bitmap = large_bitmap->bitmap[bmp]; j = 0; for(; i < size && j < BITS_IN(W_); j++, i++, bitmap >>= 1 ) { - debugBelch(" stk[%" FMT_Word "] (%p) = ", (W_)(spBottom-(payload+i)), payload+i); + debugBelch(" stk[%td] (%p) = ", spBottom-(payload+i), payload+i); if ((bitmap & 1) == 0) { printPtr((P_)payload[i]); debugBelch(" -- "); ===================================== rts/STM.c ===================================== @@ -134,7 +134,8 @@ static int shake(void) { StgTRecHeader *__t = (_t); \ StgTRecChunk *__c = __t -> current_chunk; \ StgWord __limit = __c -> next_entry_idx; \ - TRACE("%p : FOR_EACH_ENTRY, current_chunk=%p limit=%ld", __t, __c, __limit); \ + TRACE("%p : FOR_EACH_ENTRY, current_chunk=%p limit=%" FMT_Word, \ + __t, __c, __limit); \ while (__c != END_STM_CHUNK_LIST) { \ StgWord __i; \ for (__i = 0; __i < __limit; __i ++) { \ ===================================== rts/StableName.c ===================================== @@ -195,7 +195,7 @@ lookupStableName (StgPtr p) if (sn != 0) { ASSERT(stable_name_table[sn].addr == p); - debugTrace(DEBUG_stable, "cached stable name %ld at %p",sn,p); + debugTrace(DEBUG_stable, "cached stable name %" FMT_Word " at %p", sn, p); stableNameUnlock(); return sn; } @@ -275,8 +275,8 @@ gcStableNameTable( void ) p->sn_obj = isAlive(p->sn_obj); if (p->sn_obj == NULL) { // StableName object died - debugTrace(DEBUG_stable, "GC'd StableName %ld (addr=%p)", - (long)(p - stable_name_table), p->addr); + debugTrace(DEBUG_stable, "GC'd StableName %td (addr=%p)", + p - stable_name_table, p->addr); freeSnEntry(p); } else if (p->addr != NULL) { // sn_obj is alive, update pointee ===================================== rts/ThreadPaused.c ===================================== @@ -315,8 +315,8 @@ threadPaused(Capability *cap, StgTSO *tso) || (bh_info == &stg_WHITEHOLE_info)) { debugTrace(DEBUG_squeeze, - "suspending duplicate work: %ld words of stack", - (long)((StgPtr)frame - tso->stackobj->sp)); + "suspending duplicate work: %td words of stack", + (StgPtr)frame - tso->stackobj->sp); // If this closure is already an indirection, then // suspend the computation up to this point. View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/42c40d8c4602ef13f0a8ed4f6646123… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/42c40d8c4602ef13f0a8ed4f6646123… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][wip/sjakobi/T27368-cbe-compress] Cmm: delete losing blocks in common block elimination
by Simon Jakobi (@sjakobi) 01 Sep '26

01 Sep '26
Simon Jakobi pushed to branch wip/sjakobi/T27368-cbe-compress at Glasgow Haskell Compiler / GHC Commits: a0539f8e by Simon Jakobi at 2026-09-01T09:46:06+02:00 Cmm: delete losing blocks in common block elimination Delete each merge's losing block in the pass itself, instead of leaving it in the graph, unreachable, until stack layout drops it. This is safe: replaceLabels rewrites every in-graph reference to a loser to the end of its substitution chain, and chain ends never lie in the substitution's domain. The one reference replaceLabels does not rewrite, info_tbls, holds only the entry block's info table at this stage, and the entry block never loses a merge -- see Note [Retain entry block during common block elimination.] Also rewrite Note [unreachable blocks] in GHC.Cmm.Pipeline to state who actually removes unreachable blocks and when, and point at it from GHC.Cmm.ContFlowOpt. Assisted-by: Claude Fable 5 - - - - - 3 changed files: - compiler/GHC/Cmm/CommonBlockElim.hs - compiler/GHC/Cmm/ContFlowOpt.hs - compiler/GHC/Cmm/Pipeline.hs Changes: ===================================== compiler/GHC/Cmm/CommonBlockElim.hs ===================================== @@ -59,18 +59,25 @@ import qualified Data.List.NonEmpty as NE -- hashes, and at most once otherwise. Previously, we were slower, and people -- rightfully complained: #10397 +-- The input graph may contain unreachable blocks (see +-- Note [unreachable blocks] in GHC.Cmm.Pipeline). They take part in +-- deduplication like any other block, and stay in the graph unless +-- eliminated by a merge (deleteLosers removes those). + -- TODO: Use optimization fuel elimCommonBlocks :: CmmGraph -> CmmGraph elimCommonBlocks g = assert (g_entry g == g_entry g') g' where - g' = replaceLabels env $ copyTicks env g + g' = replaceLabels env $ deleteLosers $ copyTicks env g + deleteLosers g0 + | mapNull env = g0 + | otherwise = ofBlockMap (g_entry g0) + (toBlockMap g0 `mapDifference` env) env = resolveSubst (iterate mapEmpty blocks_with_key) - -- The order of blocks doesn't matter here. While we could use - -- revPostorder which drops unreachable blocks this is done in - -- ContFlowOpt already which runs before this pass. So we use - -- toBlockList since it is faster. - -- One exception: The entry block most come first or we risk eliminating it + -- The order of blocks doesn't matter here, so we use toBlockList, + -- which is faster than revPostorder. + -- One exception: The entry block must come first or we risk eliminating it -- in favour of another block. See Note [Retain entry block during common block elimination.] groups = groupByInt hash_block (toBlockListEntryFirst g) :: [[CmmBlock]] blocks_with_key = [ [ (successors b, [b]) | b <- bs] | bs <- groups] ===================================== compiler/GHC/Cmm/ContFlowOpt.hs ===================================== @@ -42,9 +42,10 @@ import Control.Monad -- L2: goto L3; -- L3: ... -- --- In this situation we say that we shortcut L2 to L3. One of +-- In this situation we say that we shortcut L2 to L3. One of the -- consequences of shortcutting is that some blocks of code may become --- unreachable (in the example above this is true for L2). +-- unreachable (in the example above this is true for L2). See +-- Note [unreachable blocks] in GHC.Cmm.Pipeline. -- Note [Control-flow optimisations] @@ -66,7 +67,7 @@ import Control.Monad -- -- Blocks are processed using postorder DFS traversal. A side effect -- of determining traversal order with a graph search is elimination --- of any blocks that are unreachable. +-- of any blocks that are unreachable in the input. -- -- Transformations are improved by working from the end of the graph -- towards the beginning, because we may be able to perform many @@ -341,8 +342,7 @@ blockConcat splitting_procs g@CmmGraph { g_entry = entry_id } -- Invariant: if a block has no predecessors it should be dropped from the -- graph because it is unreachable. maybe_concat is constructed to maintain -- that invariant, but calling replaceLabels may introduce unreachable blocks. --- We rely on subsequent passes in the Cmm pipeline to remove unreachable --- blocks. +-- See Note [unreachable blocks] in GHC.Cmm.Pipeline. incPreds, decPreds :: BlockId -> LabelMap Int -> LabelMap Int incPreds bid edges = mapInsertWith (+) bid 1 edges decPreds bid edges = case mapLookup bid edges of ===================================== compiler/GHC/Cmm/Pipeline.hs ===================================== @@ -358,6 +358,20 @@ containing junk code. These aren't necessarily a problem, but removing them is good because it might save time in the native code generator later. +Who removes them, and when? Any pass that rebuilds the graph from +revPostorder drops them as a side effect. Normally the first pass to +do so is stack layout. In addition, removeUnreachableBlocksProc at the +very end of cpsTop removes the blocks that the second round of +control-flow optimisation orphans, and prunes their info tables. No +pass between the first control-flow optimisation and stack layout +removes unreachable blocks (common block elimination deletes the losing +blocks of its merges, but leaves other unreachable code alone), so every +pass in that window may encounter them. + +Note that unreachable blocks are not entirely harmless, though: some later +passes read them. callProcPoints, for example, folds over the whole block map, +so a stale edge in an unreachable block caused the panic in #27368. + To make unreachable blocks visible in -ddump-cmm-* output, add -dppr-debug. -} View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a0539f8ed059a72186dc6fdeb5649ad… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a0539f8ed059a72186dc6fdeb5649ad… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][master] Incorporate the `rethrowSTM` reexport into the `stm` submodule
by Marge Bot (@marge-bot) 31 Aug '26

31 Aug '26
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC Commits: 4117e5ae by Wolfgang Jeltsch at 2026-08-31T23:49:35-04:00 Incorporate the `rethrowSTM` reexport into the `stm` submodule - - - - - 1 changed file: - libraries/stm Changes: ===================================== libraries/stm ===================================== @@ -1 +1 @@ -Subproject commit d28b4a9c56e712d356897c29cd945129f83a02e1 +Subproject commit 8048fdc77007a8c37156218a2429feb7f23d945c View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4117e5ae711b9d7f6dd1f59362916aa… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4117e5ae711b9d7f6dd1f59362916aa… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
[Git][ghc/ghc][master] 5 commits: Never make an absent filler for a constraint type
by Marge Bot (@marge-bot) 31 Aug '26

31 Aug '26
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC Commits: 7bf546fc by Simon Peyton Jones at 2026-08-31T23:48:53-04:00 Never make an absent filler for a constraint type mkAbsentFiller used isTerminatingType to decide, but that is not enough. Consider class Eq a => UC a where {} let u :: UC Int -- UC Int is a "non-terminating type" u = error "Absent" let e :: Eq Int -- Eq Int is a "terminating type" e = $p1UC u We clearly must not make a filler for `e`, because we speculatively evaluate it. But speculatively evaluating `e` forces `u`, so we must not make one for `u` either. Asking isDictTy instead is not enough either, because it does not catch a constraint hidden behind an unreduced type family application: type family F a :: Constraint type instance F W = TC W a :: F W => Int -> Int -- (F W) argument is absent Oops! Entered absent arg Arg: irred Type: F W So play safe and use isPredTy: never make an absent filler for any constraint-kinded type. Fixes #27627 - - - - - 5f474953 by Zubin Duggal at 2026-08-31T23:48:53-04:00 Add tests for absent fillers at dictionary types T27627 a unary class whose superclass is a non-unary class T27627a ...whose superclass is a Constraint-kinded type family T27627b ...whose superclass is a quantified constraint T27627c a unary class applied to itself, (UC (UC (TC a))) T27627e a (forall b. P b) dictionary that loops - - - - - cd5c6bcc by Zubin Duggal at 2026-08-31T23:48:53-04:00 An abstract TyCon may hide a unary class A class declared in an hs-boot file is an AbstractTyCon inside the module loop, and compiling the real declaration may reveal it to be a UnaryClassTyCon. - isTerminatingType returned True for such AbstractTyCons - IfaceToCore set the unary flag to False in the DFunId So we could end up speculating bottom dictionaries because inside a module loop we see an UnaryClassTyCon as an AbstractTyCon Use isTerminatingTyCon, which returns False for an abstract TyCon. The Bool in DFunId is now a cache for isTerminatingTyCon, set in mkDFunIdDetails. Fixes #27704 - - - - - abfc224a by Zubin Duggal at 2026-08-31T23:48:53-04:00 Specialise: don't replace dead args with absent fillers specHeader decides an argument is dead by calling isDeadBinder on a binder of the /optimised RHS/, then applies the filler to the /stable unfolding/ template instead. The two may differ, so the argument can be dead in the RHS and not in the template. The specialised function's unfolding then has an absent filler, and any call site that inlines it evaluates the error thunk. Dropping dead args in the specialiser is rarely worth it, to quote Simon, "The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler." So instead of trying to check if the arg really is dead in the stable unfolding, just drop the logic for dropping dead args in the specialiser altogeher. Fixes #27703 - - - - - 1557fd1c by Zubin Duggal at 2026-08-31T23:48:53-04:00 CorePrep: don't speculate a call across an hs-boot edge We take care not to evaluate things that might be bottom, like a looping dictionary group, but our analysis is defeated by boot files. We only track recursion within a module, so two dictionaries that depend on each other across a module loop each look non-recursive, and we might speculate them. Any recursion we cannot see must cross an hs-boot edge, so refuse to speculate calls that cross one. Fixes #27717 - - - - - 72 changed files: - + changelog.d/27627 - + changelog.d/27703 - + changelog.d/27704 - + changelog.d/27717 - compiler/GHC/Core.hs - compiler/GHC/Core/Opt/Specialise.hs - compiler/GHC/Core/Opt/WorkWrap/Utils.hs - compiler/GHC/Core/TyCon.hs - compiler/GHC/Core/Type.hs - compiler/GHC/Core/Utils.hs - compiler/GHC/CoreToStg/Prep.hs - compiler/GHC/IfaceToCore.hs - compiler/GHC/Types/Demand.hs - compiler/GHC/Types/Id/Info.hs - compiler/GHC/Types/Id/Make.hs - compiler/GHC/Types/Literal.hs - + testsuite/tests/core-to-stg/T27627/Callee.hs - + testsuite/tests/core-to-stg/T27627/Caller.hs - + testsuite/tests/core-to-stg/T27627/Main.hs - + testsuite/tests/core-to-stg/T27627/T27627.stdout - + testsuite/tests/core-to-stg/T27627/all.T - + testsuite/tests/core-to-stg/T27627a/Callee.hs - + testsuite/tests/core-to-stg/T27627a/Caller.hs - + testsuite/tests/core-to-stg/T27627a/Main.hs - + testsuite/tests/core-to-stg/T27627a/T27627a.stdout - + testsuite/tests/core-to-stg/T27627a/all.T - + testsuite/tests/core-to-stg/T27627b/Callee.hs - + testsuite/tests/core-to-stg/T27627b/Caller.hs - + testsuite/tests/core-to-stg/T27627b/Main.hs - + testsuite/tests/core-to-stg/T27627b/T27627b.stdout - + testsuite/tests/core-to-stg/T27627b/all.T - + testsuite/tests/core-to-stg/T27627c/Callee.hs - + testsuite/tests/core-to-stg/T27627c/Caller.hs - + testsuite/tests/core-to-stg/T27627c/Main.hs - + testsuite/tests/core-to-stg/T27627c/T27627c.stdout - + testsuite/tests/core-to-stg/T27627c/all.T - + testsuite/tests/core-to-stg/T27627e.hs - + testsuite/tests/core-to-stg/T27627e.stdout - + testsuite/tests/core-to-stg/T27627f/Callee.hs - + testsuite/tests/core-to-stg/T27627f/Caller.hs - + testsuite/tests/core-to-stg/T27627f/Inst.hs - + testsuite/tests/core-to-stg/T27627f/Main.hs - + testsuite/tests/core-to-stg/T27627f/T27627f.stdout - + testsuite/tests/core-to-stg/T27627f/all.T - + testsuite/tests/core-to-stg/T27704/Callee.hs - + testsuite/tests/core-to-stg/T27704/Callee.hs-boot - + testsuite/tests/core-to-stg/T27704/Main.hs - + testsuite/tests/core-to-stg/T27704/Mid.hs - + testsuite/tests/core-to-stg/T27704/T27704.stdout - + testsuite/tests/core-to-stg/T27704/all.T - + testsuite/tests/core-to-stg/T27704a/Callee.hs - + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot - + testsuite/tests/core-to-stg/T27704a/Main.hs - + testsuite/tests/core-to-stg/T27704a/Mid.hs - + testsuite/tests/core-to-stg/T27704a/T27704a.stdout - + testsuite/tests/core-to-stg/T27704a/all.T - + testsuite/tests/core-to-stg/T27717/Callee.hs - + testsuite/tests/core-to-stg/T27717/Callee.hs-boot - + testsuite/tests/core-to-stg/T27717/Main.hs - + testsuite/tests/core-to-stg/T27717/Mid.hs - + testsuite/tests/core-to-stg/T27717/T27717.stdout - + testsuite/tests/core-to-stg/T27717/Ty.hs - + testsuite/tests/core-to-stg/T27717/all.T - testsuite/tests/core-to-stg/all.T - testsuite/tests/simplCore/should_compile/T17966.stderr - testsuite/tests/simplCore/should_compile/T7785.stderr - testsuite/tests/simplCore/should_compile/spec004.hs - testsuite/tests/simplCore/should_compile/spec004.stderr - + testsuite/tests/simplCore/should_run/T27703/Lib.hs - + testsuite/tests/simplCore/should_run/T27703/Main.hs - + testsuite/tests/simplCore/should_run/T27703/T27703.stdout - + testsuite/tests/simplCore/should_run/T27703/all.T The diff was not included because it is too large. View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/578bd18509f0d2aeb004231a197f7f… -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/578bd18509f0d2aeb004231a197f7f… You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1 0
0 0
  • ← Newer
  • 1
  • ...
  • 57
  • 58
  • 59
  • 60
  • 61
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.