diff --git a/libpolyml/gc.cpp b/libpolyml/gc.cpp index 8a4a4b24..dbf380a8 100644 --- a/libpolyml/gc.cpp +++ b/libpolyml/gc.cpp @@ -1,424 +1,434 @@ /* Title: Multi-Threaded Garbage Collector - Copyright (c) 2010-12, 2019 David C. J. Matthews + Copyright (c) 2010-12, 2019, 2020 David C. J. Matthews Based on the original garbage collector code Copyright 2000-2008 Cambridge University Technical Services Limited This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_WIN32) #include "winconfig.h" #else #error "No configuration file" #endif #ifdef HAVE_ASSERT_H #include #define ASSERT(x) assert(x) #else #define ASSERT(x) #endif #include "globals.h" #include "run_time.h" #include "machine_dep.h" #include "diagnostics.h" #include "processes.h" #include "timing.h" #include "gc.h" #include "scanaddrs.h" #include "check_objects.h" #include "osmem.h" #include "bitmap.h" #include "rts_module.h" #include "memmgr.h" #include "gctaskfarm.h" #include "mpoly.h" #include "statistics.h" #include "profiling.h" #include "heapsizing.h" #include "gc_progress.h" static GCTaskFarm gTaskFarm; // Global task farm. GCTaskFarm *gpTaskFarm = &gTaskFarm; // If the GC converts a weak ref from SOME to NONE it sets this ref. It can be // cleared by the signal handler thread. There's no need for a lock since it // is only set during GC and only cleared when not GCing. bool convertedWeak = false; /* How the garbage collector works. The GC has two phases. The minor (quick) GC is a copying collector that copies data from the allocation area into the mutable and immutable area. The major collector is started when either the mutable or the immutable area is full. The major collector uses a mark/sweep scheme. The GC has three phases: 1. Mark phase. Working from the roots; which are the the permanent mutable segments and the RTS roots (e.g. thread stacks), mark all reachable cells. Marking involves setting bits in the bitmap for reachable words. 2. Compact phase. Marked objects are copied to try to compact, upwards, the heap segments. When an object is moved the length word of the object in the old location is set as a tombstone that points to its new location. In particular this means that we cannot reuse the space where an object previously was during the compaction phase. Immutable objects are moved into immutable segments. When an object is moved to a new location the bits are set in the bitmap as though the object had been marked at that location. 3. Update phase. The roots and objects marked during the first two phases are scanned and any addresses for moved objects are updated. The lowest address used in the area then becomes the base of the area for future allocations. There is a sharing phase which may be performed before the mark phase. This merges immutable cells with the same contents with the aim of reducing the size of the live data. It is expensive so is not performed by default. Updated DCJM 12/06/12 */ static bool doGC(const POLYUNSIGNED wordsRequiredToAllocate) { gHeapSizeParameters.RecordAtStartOfMajorGC(); gHeapSizeParameters.RecordGCTime(HeapSizeParameters::GCTimeStart); globalStats.incCount(PSC_GC_FULLGC); // Remove any empty spaces. There will not normally be any except // if we have triggered a full GC as a result of detecting paging in the // minor GC but in that case we want to try to stop the system writing // out areas that are now empty. gMem.RemoveEmptyLocals(); if (debugOptions & DEBUG_GC) Log("GC: Full GC, %lu words required %" PRI_SIZET " spaces\n", wordsRequiredToAllocate, gMem.lSpaces.size()); if (debugOptions & DEBUG_HEAPSIZE) gMem.ReportHeapSizes("Full GC (before)"); // Data sharing pass. if (gHeapSizeParameters.PerformSharingPass()) { globalStats.incCount(PSC_GC_SHARING); GCSharingPhase(); } gcProgressBeginMajorGC(); // The GC sharing phase is treated separately /* * There is a really weird bug somewhere. An extra bit may be set in the bitmap during * the mark phase. It seems to be related to heavy swapping activity. Duplicating the * bitmap causes it to occur only in one copy and write-protecting the bitmap apart from * when it is actually being updated does not result in a seg-fault. So far I've only * seen it on 64-bit Linux but it may be responsible for other crashes. The work-around * is to check the number of bits set in the bitmap and repeat the mark phase if it does * not match. */ for (unsigned p = 3; p > 0; p--) { for(std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; ASSERT (lSpace->top >= lSpace->upperAllocPtr); ASSERT (lSpace->upperAllocPtr >= lSpace->lowerAllocPtr); ASSERT (lSpace->lowerAllocPtr >= lSpace->bottom); // Set upper and lower limits of weak refs. lSpace->highestWeak = lSpace->bottom; lSpace->lowestWeak = lSpace->top; lSpace->fullGCLowerLimit = lSpace->top; // Put dummy objects in the unused space. This allows // us to scan over the whole of the space. gMem.FillUnusedSpace(lSpace->lowerAllocPtr, lSpace->upperAllocPtr-lSpace->lowerAllocPtr); } // Set limits of weak refs. for (std::vector::iterator i = gMem.pSpaces.begin(); i < gMem.pSpaces.end(); i++) { PermanentMemSpace *pSpace = *i; pSpace->highestWeak = pSpace->bottom; pSpace->lowestWeak = pSpace->top; } /* Mark phase */ GCMarkPhase(); uintptr_t bitCount = 0, markCount = 0; for (std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; markCount += lSpace->i_marked + lSpace->m_marked; bitCount += lSpace->bitmap.CountSetBits(lSpace->spaceSize()); } if (markCount == bitCount) break; else { // Report an error. If this happens again we crash. Log("GC: Count error mark count %lu, bitCount %lu\n", markCount, bitCount); if (p == 1) { ASSERT(markCount == bitCount); } } } for(std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; // Reset the allocation pointers. They will be set to the // limits of the retained data. #ifdef POLYML32IN64 lSpace->lowerAllocPtr = lSpace->bottom+1; // Must be odd-word aligned lSpace->lowerAllocPtr[-1] = PolyWord::FromUnsigned(0); #else lSpace->lowerAllocPtr = lSpace->bottom; #endif lSpace->upperAllocPtr = lSpace->top; } gcProgressSetPercent(25); if (debugOptions & DEBUG_GC) Log("GC: Check weak refs\n"); /* Detect unreferenced streams, windows etc. */ GCheckWeakRefs(); gcProgressSetPercent(50); // Check that the heap is not overfull. We make sure the marked // mutable and immutable data is no more than 90% of the // corresponding areas. This is a very coarse adjustment. { uintptr_t iMarked = 0, mMarked = 0; uintptr_t iSpace = 0, mSpace = 0; for (std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; iMarked += lSpace->i_marked; mMarked += lSpace->m_marked; if (! lSpace->allocationSpace) { if (lSpace->isMutable) mSpace += lSpace->spaceSize(); else iSpace += lSpace->spaceSize(); } } // Add space if necessary and possible. while (iMarked > iSpace - iSpace/10 && gHeapSizeParameters.AddSpaceBeforeCopyPhase(false) != 0) iSpace += gMem.DefaultSpaceSize(); while (mMarked > mSpace - mSpace/10 && gHeapSizeParameters.AddSpaceBeforeCopyPhase(true) != 0) mSpace += gMem.DefaultSpaceSize(); } /* Compact phase */ GCCopyPhase(); gHeapSizeParameters.RecordGCTime(HeapSizeParameters::GCTimeIntermediate, "Copy"); gcProgressSetPercent(75); // Update Phase. if (debugOptions & DEBUG_GC) Log("GC: Update\n"); GCUpdatePhase(); gHeapSizeParameters.RecordGCTime(HeapSizeParameters::GCTimeIntermediate, "Update"); { uintptr_t iUpdated = 0, mUpdated = 0, iMarked = 0, mMarked = 0; for(std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; iMarked += lSpace->i_marked; mMarked += lSpace->m_marked; if (lSpace->isMutable) mUpdated += lSpace->updated; else iUpdated += lSpace->updated; } ASSERT(iUpdated+mUpdated == iMarked+mMarked); } // Delete empty spaces. gMem.RemoveEmptyLocals(); if (debugOptions & DEBUG_GC_ENHANCED) { for(std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *lSpace = *i; Log("GC: %s space %p %" PRI_SIZET " free in %" PRI_SIZET " words %2.1f%% full\n", lSpace->spaceTypeString(), lSpace, lSpace->freeSpace(), lSpace->spaceSize(), ((float)lSpace->allocatedSpace()) * 100 / (float)lSpace->spaceSize()); } } // Compute values for statistics globalStats.setSize(PSS_AFTER_LAST_GC, 0); globalStats.setSize(PSS_AFTER_LAST_FULLGC, 0); globalStats.setSize(PSS_ALLOCATION, 0); globalStats.setSize(PSS_ALLOCATION_FREE, 0); for (std::vector::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++) { LocalMemSpace *space = *i; uintptr_t free = space->freeSpace(); globalStats.incSize(PSS_AFTER_LAST_GC, free*sizeof(PolyWord)); globalStats.incSize(PSS_AFTER_LAST_FULLGC, free*sizeof(PolyWord)); if (space->allocationSpace) { if (space->allocatedSpace() > space->freeSpace()) // It's more than half full gMem.ConvertAllocationSpaceToLocal(space); else { globalStats.incSize(PSS_ALLOCATION, free*sizeof(PolyWord)); globalStats.incSize(PSS_ALLOCATION_FREE, free*sizeof(PolyWord)); } } #ifdef FILL_UNUSED_MEMORY memset(space->bottom, 0xaa, (char*)space->upperAllocPtr - (char*)space->bottom); #endif if (debugOptions & DEBUG_GC_ENHANCED) Log("GC: %s space %p %" PRI_SIZET " free in %" PRI_SIZET " words %2.1f%% full\n", space->spaceTypeString(), space, space->freeSpace(), space->spaceSize(), ((float)space->allocatedSpace()) * 100 / (float)space->spaceSize()); } // End of garbage collection gHeapSizeParameters.RecordGCTime(HeapSizeParameters::GCTimeEnd); // Now we've finished we can adjust the heap sizes. gHeapSizeParameters.AdjustSizeAfterMajorGC(wordsRequiredToAllocate); gHeapSizeParameters.resetMajorTimingData(); bool haveSpace = gMem.CheckForAllocation(wordsRequiredToAllocate); // Invariant: the bitmaps are completely clean. if (debugOptions & DEBUG_GC) { if (haveSpace) Log("GC: Completed successfully\n"); else Log("GC: Completed with insufficient space\n"); } if (debugOptions & DEBUG_HEAPSIZE) gMem.ReportHeapSizes("Full GC (after)"); // if (profileMode == kProfileLiveData || profileMode == kProfileLiveMutables) // printprofile(); CheckMemory(); return haveSpace; // Completed } // Create the initial heap. hsize, isize and msize are the requested heap sizes // from the user arguments in units of kbytes. // Fills in the defaults and attempts to allocate the heap. If the heap size // is too large it allocates as much as it can. The default heap size is half the // physical memory. void CreateHeap() { // Create an initial allocation space. if (gMem.CreateAllocationSpace(gMem.DefaultSpaceSize()) == 0) Exit("Insufficient memory to allocate the heap"); // Create the task farm if required if (userOptions.gcthreads != 1) { if (! gTaskFarm.Initialise(userOptions.gcthreads, 100)) Crash("Unable to initialise the GC task farm"); } // Set up the stacks for the mark phase. initialiseMarkerTables(); } -// Set single threaded mode. This is only used in a child process after -// Posix fork in case there is a GC before the exec. -void GCSetSingleThreadAfterFork() -{ - gpTaskFarm->SetSingleThreaded(); - initialiseMarkerTables(); -} - class FullGCRequest: public MainThreadRequest { public: FullGCRequest(): MainThreadRequest(MTP_GCPHASEMARK) {} virtual void Perform() { doGC (0); } }; class QuickGCRequest: public MainThreadRequest { public: QuickGCRequest(POLYUNSIGNED words): MainThreadRequest(MTP_GCPHASEMARK), wordsRequired(words) {} virtual void Perform() { result = #ifndef DEBUG_ONLY_FULL_GC // If DEBUG_ONLY_FULL_GC is defined then we skip the partial GC. RunQuickGC(wordsRequired) || #endif doGC (wordsRequired); } bool result; POLYUNSIGNED wordsRequired; }; // Perform a full garbage collection. This is called either from ML via the full_gc RTS call // or from various RTS functions such as open_file to try to recover dropped file handles. void FullGC(TaskData *taskData) { FullGCRequest request; processes->MakeRootRequest(taskData, &request); if (convertedWeak) // Notify the signal thread to broadcast on the condition var when // the GC is complete. We mustn't call SignalArrived within the GC // because it locks schedLock and the main GC thread already holds schedLock. processes->SignalArrived(); } // This is the normal call when memory is exhausted and we need to garbage collect. bool QuickGC(TaskData *taskData, POLYUNSIGNED wordsRequiredToAllocate) { QuickGCRequest request(wordsRequiredToAllocate); processes->MakeRootRequest(taskData, &request); if (convertedWeak) processes->SignalArrived(); return request.result; } // Called in RunShareData. This is called as a root function void FullGCForShareCommonData(void) { doGC(0); } + +// RTS module for the GC. Only used for ForkChild. +class GarbageCollectModule : public RtsModule +{ +public: + virtual void ForkChild(void); +}; + +// Set single threaded mode. This is only used in a child process after +// Posix fork in case there is a GC before the exec. +void GarbageCollectModule::ForkChild(void) +{ + gpTaskFarm->SetSingleThreaded(); + initialiseMarkerTables(); +} + +// Declare this. It will be automatically added to the table. +static GarbageCollectModule gcModule; diff --git a/libpolyml/gc.h b/libpolyml/gc.h index 242e48f6..79d68d87 100644 --- a/libpolyml/gc.h +++ b/libpolyml/gc.h @@ -1,62 +1,60 @@ /* Title: gc.h - exports signature for gc.cpp Copyright (c) 2000-7 Cambridge University Technical Services Limited - Further development Copyright David C.J. Matthews 2010, 2019 + Further development Copyright David C.J. Matthews 2010, 2019, 2020 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef GC_H_INCLUDED #define GC_H_INCLUDED #include "globals.h" // For POLYUNSIGNED class TaskData; // Make a request for a full garbage collection. extern void FullGC(TaskData *taskData); // Make a request for a partial garbage collection. extern bool QuickGC(TaskData *taskData, POLYUNSIGNED words_needed); extern void CreateHeap(); extern void FullGCForShareCommonData(void); extern bool convertedWeak; // Multi-thread GC. extern void initialiseMarkerTables(); // The task farm for the GC. The threads are left waiting for the GC, class GCTaskFarm; extern GCTaskFarm *gpTaskFarm; extern void CopyObjectToNewAddress(PolyObject *srcAddress, PolyObject *destAddress, POLYUNSIGNED L); extern bool RunQuickGC(const POLYUNSIGNED wordsRequiredToAllocate); -extern void GCSetSingleThreadAfterFork(); - // GC Phases. extern void GCSharingPhase(void); extern void GCMarkPhase(void); extern void GCheckWeakRefs(void); extern void GCCopyPhase(void); extern void GCUpdatePhase(void); #endif diff --git a/libpolyml/processes.cpp b/libpolyml/processes.cpp index 7d48d89b..46ac7adc 100644 --- a/libpolyml/processes.cpp +++ b/libpolyml/processes.cpp @@ -1,2224 +1,2224 @@ /* Title: Thread functions Author: David C.J. Matthews Copyright (c) 2007,2008,2013-15, 2017, 2019, 2020 David C.J. Matthews This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_WIN32) #include "winconfig.h" #else #error "No configuration file" #endif #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_ASSERT_H #include #define ASSERT(x) assert(x) #else #define ASSERT(x) #endif #ifdef HAVE_PROCESS_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include // Want unistd for _SC_NPROCESSORS_ONLN at least #endif #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_WINDOWS_H #include #endif #if (!defined(_WIN32)) #include #endif #ifdef HAVE_SYS_SYSCTL_H // Used determine number of processors in Mac OS X. #include #endif #if (defined(_WIN32)) #include #endif #include #include /************************************************************************ * * Include runtime headers * ************************************************************************/ #include "globals.h" #include "gc.h" #include "mpoly.h" #include "arb.h" #include "machine_dep.h" #include "diagnostics.h" #include "processes.h" #include "run_time.h" #include "sys.h" #include "sighandler.h" #include "scanaddrs.h" #include "save_vec.h" #include "rts_module.h" #include "noreturn.h" #include "memmgr.h" #include "locking.h" #include "profiling.h" #include "sharedata.h" #include "exporter.h" #include "statistics.h" #include "rtsentry.h" #include "gc_progress.h" extern "C" { POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadGeneral(PolyObject *threadId, PolyWord code, PolyWord arg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadKillSelf(PolyObject *threadId); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadMutexBlock(PolyObject *threadId, PolyWord arg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadMutexUnlock(PolyObject *threadId, PolyWord arg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadCondVarWait(PolyObject *threadId, PolyWord arg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadCondVarWaitUntil(PolyObject *threadId, PolyWord lockArg, PolyWord timeArg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadCondVarWake(PolyWord targetThread); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadForkThread(PolyObject *threadId, PolyWord function, PolyWord attrs, PolyWord stack); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadIsActive(PolyWord targetThread); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadInterruptThread(PolyWord targetThread); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadKillThread(PolyWord targetThread); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadBroadcastInterrupt(PolyObject *threadId); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadTestInterrupt(PolyObject *threadId); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadNumProcessors(); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadNumPhysicalProcessors(); POLYEXTERNALSYMBOL POLYUNSIGNED PolyThreadMaxStackSize(PolyObject *threadId, PolyWord newSize); } #define SAVE(x) taskData->saveVec.push(x) #define SIZEOF(x) (sizeof(x)/sizeof(PolyWord)) // These values are stored in the second word of thread id object as // a tagged integer. They may be set and read by the thread in the ML // code. #define PFLAG_BROADCAST 1 // If set, accepts a broadcast // How to handle interrrupts #define PFLAG_IGNORE 0 // Ignore interrupts completely #define PFLAG_SYNCH 2 // Handle synchronously #define PFLAG_ASYNCH 4 // Handle asynchronously #define PFLAG_ASYNCH_ONCE 6 // First handle asynchronously then switch to synch. #define PFLAG_INTMASK 6 // Mask of the above bits struct _entrypts processesEPT[] = { { "PolyThreadGeneral", (polyRTSFunction)&PolyThreadGeneral}, { "PolyThreadKillSelf", (polyRTSFunction)&PolyThreadKillSelf}, { "PolyThreadMutexBlock", (polyRTSFunction)&PolyThreadMutexBlock}, { "PolyThreadMutexUnlock", (polyRTSFunction)&PolyThreadMutexUnlock}, { "PolyThreadCondVarWait", (polyRTSFunction)&PolyThreadCondVarWait}, { "PolyThreadCondVarWaitUntil", (polyRTSFunction)&PolyThreadCondVarWaitUntil}, { "PolyThreadCondVarWake", (polyRTSFunction)&PolyThreadCondVarWake}, { "PolyThreadForkThread", (polyRTSFunction)&PolyThreadForkThread}, { "PolyThreadIsActive", (polyRTSFunction)&PolyThreadIsActive}, { "PolyThreadInterruptThread", (polyRTSFunction)&PolyThreadInterruptThread}, { "PolyThreadKillThread", (polyRTSFunction)&PolyThreadKillThread}, { "PolyThreadBroadcastInterrupt", (polyRTSFunction)&PolyThreadBroadcastInterrupt}, { "PolyThreadTestInterrupt", (polyRTSFunction)&PolyThreadTestInterrupt}, { "PolyThreadNumProcessors", (polyRTSFunction)&PolyThreadNumProcessors}, { "PolyThreadNumPhysicalProcessors",(polyRTSFunction)&PolyThreadNumPhysicalProcessors}, { "PolyThreadMaxStackSize", (polyRTSFunction)&PolyThreadMaxStackSize}, { NULL, NULL} // End of list. }; class Processes: public ProcessExternal, public RtsModule { public: Processes(); + // RtsModule overrides virtual void Init(void); virtual void Stop(void); - void GarbageCollect(ScanAddress *process); + virtual void GarbageCollect(ScanAddress *process); + virtual void ForkChild(void) { singleThreaded = true; } // After a Unix fork this is single threaded public: void BroadcastInterrupt(void); void BeginRootThread(PolyObject *rootFunction); void RequestProcessExit(int n); // Request all ML threads to exit and set the process result code. // Called when a thread has completed - doesn't return. virtual NORETURNFN(void ThreadExit(TaskData *taskData)); // Called when a thread may block. Returns some time later when perhaps // the input is available. virtual void ThreadPauseForIO(TaskData *taskData, Waiter *pWait); // Return the task data for the current thread. virtual TaskData *GetTaskDataForThread(void); // Create a new task data object for the current thread. virtual TaskData *CreateNewTaskData(Handle threadId, Handle threadFunction, Handle args, PolyWord flags); // ForkFromRTS. Creates a new thread from within the RTS. virtual bool ForkFromRTS(TaskData *taskData, Handle proc, Handle arg); // Create a new thread. The "args" argument is only used for threads // created in the RTS by the signal handler. Handle ForkThread(TaskData *taskData, Handle threadFunction, Handle args, PolyWord flags, PolyWord stacksize); // Process general RTS requests from ML. Handle ThreadDispatch(TaskData *taskData, Handle args, Handle code); virtual void ThreadUseMLMemory(TaskData *taskData); virtual void ThreadReleaseMLMemory(TaskData *taskData); virtual poly_exn* GetInterrupt(void) { return interrupt_exn; } // If the schedule lock is already held we need to use these functions. void ThreadUseMLMemoryWithSchedLock(TaskData *taskData); void ThreadReleaseMLMemoryWithSchedLock(TaskData *taskData); // Requests from the threads for actions that need to be performed by // the root thread. Make the request and wait until it has completed. virtual void MakeRootRequest(TaskData *taskData, MainThreadRequest *request); // Deal with any interrupt or kill requests. virtual bool ProcessAsynchRequests(TaskData *taskData); // Process an interrupt request synchronously. virtual void TestSynchronousRequests(TaskData *taskData); // Process any events, synchronous or asynchronous. virtual void TestAnyEvents(TaskData *taskData); // Set a thread to be interrupted or killed. Wakes up the // thread if necessary. MUST be called with schedLock held. void MakeRequest(TaskData *p, ThreadRequests request); // Profiling control. virtual void StartProfiling(void); virtual void StopProfiling(void); #ifdef HAVE_WINDOWS_H // Windows: Called every millisecond while profiling is on. void ProfileInterrupt(void); #else // Unix: Start a profile timer for a thread. void StartProfilingTimer(void); #endif // Memory allocation. Tries to allocate space. If the allocation succeeds it // may update the allocation values in the taskData object. If the heap is exhausted // it may set this thread (or other threads) to raise an exception. PolyWord *FindAllocationSpace(TaskData *taskData, POLYUNSIGNED words, bool alwaysInSeg); // Get the task data value from the task reference. // The task data reference is a volatile ref containing the // address of the C++ task data. // N.B. This is updated when the thread exits and the TaskData object // is deleted. TaskData *TaskForIdentifier(PolyObject *taskId) { return *(TaskData**)(((ThreadObject*)taskId)->threadRef.AsObjPtr()); } // Signal handling support. The ML signal handler thread blocks until it is // woken up by the signal detection thread. virtual bool WaitForSignal(TaskData *taskData, PLock *sigLock); virtual void SignalArrived(void); - virtual void SetSingleThreaded(void) { singleThreaded = true; } - // Operations on mutexes void MutexBlock(TaskData *taskData, Handle hMutex); void MutexUnlock(TaskData *taskData, Handle hMutex); // Operations on condition variables. void WaitInfinite(TaskData *taskData, Handle hMutex); void WaitUntilTime(TaskData *taskData, Handle hMutex, Handle hTime); bool WakeThread(PolyObject *targetThread); // Generally, the system runs with multiple threads. After a // fork, though, there is only one thread. bool singleThreaded; // Each thread has an entry in this vector. std::vector taskArray; /* schedLock: This lock must be held when making scheduling decisions. It must also be held before adding items to taskArray, removing them or scanning the vector. It must also be held before deleting a TaskData object or using it in a thread other than the "owner" */ PLock schedLock; #if (!defined(_WIN32)) pthread_key_t tlsId; #else DWORD tlsId; #endif // We make an exception packet for Interrupt and store it here. // This exception can be raised if we run out of store so we need to // make sure we have the packet before we do. poly_exn *interrupt_exn; /* initialThreadWait: The initial thread waits on this for wake-ups from the ML threads requesting actions such as GC or close-down. */ PCondVar initialThreadWait; // A requesting thread sets this to indicate the request. This value // is only reset once the request has been satisfied. MainThreadRequest *threadRequest; PCondVar mlThreadWait; // All the threads block on here until the request has completed. int exitResult; bool exitRequest; #ifdef HAVE_WINDOWS_H /* Windows including Cygwin */ // Used in profiling HANDLE hStopEvent; /* Signalled to stop all threads. */ HANDLE profilingHd; HANDLE mainThreadHandle; // Handle for main thread LONGLONG lastCPUTime; // CPU used by main thread. #endif TaskData *sigTask; // Pointer to current signal task. }; // Global process data. static Processes processesModule; ProcessExternal *processes = &processesModule; Processes::Processes(): singleThreaded(false), schedLock("Scheduler"), interrupt_exn(0), threadRequest(0), exitResult(0), exitRequest(false), sigTask(0) { #ifdef HAVE_WINDOWS_H hStopEvent = NULL; profilingHd = NULL; lastCPUTime = 0; mainThreadHandle = NULL; #endif } enum _mainThreadPhase mainThreadPhase = MTP_USER_CODE; // Get the attribute flags. static POLYUNSIGNED ThreadAttrs(TaskData *taskData) { return UNTAGGED_UNSIGNED(taskData->threadObject->flags); } // General interface to thread. Ideally the various cases will be made into // separate functions. POLYUNSIGNED PolyThreadGeneral(PolyObject *threadId, PolyWord code, PolyWord arg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedCode = taskData->saveVec.push(code); Handle pushedArg = taskData->saveVec.push(arg); Handle result = 0; try { result = processesModule.ThreadDispatch(taskData, pushedArg, pushedCode); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); if (result == 0) return TAGGED(0).AsUnsigned(); else return result->Word().AsUnsigned(); } POLYUNSIGNED PolyThreadMutexBlock(PolyObject *threadId, PolyWord arg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedArg = taskData->saveVec.push(arg); if (profileMode == kProfileMutexContention) taskData->addProfileCount(1); try { processesModule.MutexBlock(taskData, pushedArg); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } POLYUNSIGNED PolyThreadMutexUnlock(PolyObject *threadId, PolyWord arg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedArg = taskData->saveVec.push(arg); try { processesModule.MutexUnlock(taskData, pushedArg); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } /* A mutex was locked i.e. the count was ~1 or less. We will have set it to ~1. This code blocks if the count is still ~1. It does actually return if another thread tries to lock the mutex and hasn't yet set the value to ~1 but that doesn't matter since whenever we return we simply try to get the lock again. */ void Processes::MutexBlock(TaskData *taskData, Handle hMutex) { PLocker lock(&schedLock); // We have to check the value again with schedLock held rather than // simply waiting because otherwise the unlocking thread could have // set the variable back to 0 (unlocked) and signalled any waiters // before we actually got to wait. if (UNTAGGED(DEREFHANDLE(hMutex)->Get(0)) > 0) { // Set this so we can see what we're blocked on. taskData->blockMutex = DEREFHANDLE(hMutex); // Now release the ML memory. A GC can start. ThreadReleaseMLMemoryWithSchedLock(taskData); // Wait until we're woken up. We mustn't block if we have been // interrupted, and are processing interrupts asynchronously, or // we've been killed. switch (taskData->requests) { case kRequestKill: // We've been killed. Handle this later. break; case kRequestInterrupt: { // We've been interrupted. POLYUNSIGNED attrs = ThreadAttrs(taskData) & PFLAG_INTMASK; if (attrs == PFLAG_ASYNCH || attrs == PFLAG_ASYNCH_ONCE) break; // If we're ignoring interrupts or handling them synchronously // we don't do anything here. } case kRequestNone: globalStats.incCount(PSC_THREADS_WAIT_MUTEX); taskData->threadLock.Wait(&schedLock); globalStats.decCount(PSC_THREADS_WAIT_MUTEX); } taskData->blockMutex = 0; // No longer blocked. ThreadUseMLMemoryWithSchedLock(taskData); } // Test to see if we have been interrupted and if this thread // processes interrupts asynchronously we should raise an exception // immediately. Perhaps we do that whenever we exit from the RTS. } /* Unlock a mutex. Called after decrementing the count and discovering that at least one other thread has tried to lock it. We may need to wake up threads that are blocked. */ void Processes::MutexUnlock(TaskData *taskData, Handle hMutex) { // The caller has already set the variable to 1 (unlocked). // We need to acquire schedLock so that we can // be sure that any thread that is trying to lock sees either // the updated value (and so doesn't wait) or has successfully // waited on its threadLock (and so will be woken up). PLocker lock(&schedLock); // Unlock any waiters. for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; // If the thread is blocked on this mutex we can signal the thread. if (p && p->blockMutex == DEREFHANDLE(hMutex)) p->threadLock.Signal(); } } POLYUNSIGNED PolyThreadCondVarWait(PolyObject *threadId, PolyWord arg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedArg = taskData->saveVec.push(arg); try { processesModule.WaitInfinite(taskData, pushedArg); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } POLYUNSIGNED PolyThreadCondVarWaitUntil(PolyObject *threadId, PolyWord lockArg, PolyWord timeArg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedLockArg = taskData->saveVec.push(lockArg); Handle pushedTimeArg = taskData->saveVec.push(timeArg); try { processesModule.WaitUntilTime(taskData, pushedLockArg, pushedTimeArg); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } // Atomically drop a mutex and wait for a wake up. // It WILL NOT RAISE AN EXCEPTION unless it is set to handle exceptions // asynchronously (which it shouldn't do if the ML caller code is correct). // It may return as a result of any of the following: // an explicit wake up. // an interrupt, either direct or broadcast // a trap i.e. a request to handle an asynchronous event. void Processes::WaitInfinite(TaskData *taskData, Handle hMutex) { PLocker lock(&schedLock); // Atomically release the mutex. This is atomic because we hold schedLock // so no other thread can call signal or broadcast. Handle decrResult = taskData->AtomicDecrement(hMutex); if (UNTAGGED(decrResult->Word()) != 0) { taskData->AtomicReset(hMutex); // The mutex was locked so we have to release any waiters. // Unlock any waiters. for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; // If the thread is blocked on this mutex we can signal the thread. if (p && p->blockMutex == DEREFHANDLE(hMutex)) p->threadLock.Signal(); } } // Wait until we're woken up. Don't block if we have been interrupted // or killed. if (taskData->requests == kRequestNone) { // Now release the ML memory. A GC can start. ThreadReleaseMLMemoryWithSchedLock(taskData); globalStats.incCount(PSC_THREADS_WAIT_CONDVAR); taskData->threadLock.Wait(&schedLock); globalStats.decCount(PSC_THREADS_WAIT_CONDVAR); // We want to use the memory again. ThreadUseMLMemoryWithSchedLock(taskData); } } // Atomically drop a mutex and wait for a wake up or a time to wake up void Processes::WaitUntilTime(TaskData *taskData, Handle hMutex, Handle hWakeTime) { // Convert the time into the correct format for WaitUntil before acquiring // schedLock. div_longc could do a GC which requires schedLock. #if (defined(_WIN32)) // On Windows it is the number of 100ns units since the epoch FILETIME tWake; getFileTimeFromArb(taskData, hWakeTime, &tWake); #else // Unix style times. struct timespec tWake; // On Unix we represent times as a number of microseconds. Handle hMillion = Make_arbitrary_precision(taskData, 1000000); tWake.tv_sec = get_C_ulong(taskData, DEREFWORD(div_longc(taskData, hMillion, hWakeTime))); tWake.tv_nsec = 1000*get_C_ulong(taskData, DEREFWORD(rem_longc(taskData, hMillion, hWakeTime))); #endif PLocker lock(&schedLock); // Atomically release the mutex. This is atomic because we hold schedLock // so no other thread can call signal or broadcast. Handle decrResult = taskData->AtomicDecrement(hMutex); if (UNTAGGED(decrResult->Word()) != 0) { taskData->AtomicReset(hMutex); // The mutex was locked so we have to release any waiters. // Unlock any waiters. for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; // If the thread is blocked on this mutex we can signal the thread. if (p && p->blockMutex == DEREFHANDLE(hMutex)) p->threadLock.Signal(); } } // Wait until we're woken up. Don't block if we have been interrupted // or killed. if (taskData->requests == kRequestNone) { // Now release the ML memory. A GC can start. ThreadReleaseMLMemoryWithSchedLock(taskData); globalStats.incCount(PSC_THREADS_WAIT_CONDVAR); (void)taskData->threadLock.WaitUntil(&schedLock, &tWake); globalStats.decCount(PSC_THREADS_WAIT_CONDVAR); // We want to use the memory again. ThreadUseMLMemoryWithSchedLock(taskData); } } bool Processes::WakeThread(PolyObject *targetThread) { bool result = false; // Default to failed. // Acquire the schedLock first. This ensures that this is // atomic with respect to waiting. PLocker lock(&schedLock); TaskData *p = TaskForIdentifier(targetThread); if (p && p->threadObject == targetThread) { POLYUNSIGNED attrs = ThreadAttrs(p) & PFLAG_INTMASK; if (p->requests == kRequestNone || (p->requests == kRequestInterrupt && attrs == PFLAG_IGNORE)) { p->threadLock.Signal(); result = true; } } return result; } POLYUNSIGNED PolyThreadCondVarWake(PolyWord targetThread) { if (processesModule.WakeThread(targetThread.AsObjPtr())) return TAGGED(1).AsUnsigned(); else return TAGGED(0).AsUnsigned(); } // Test if a thread is active. POLYUNSIGNED PolyThreadIsActive(PolyWord targetThread) { // There's a race here: the thread may be exiting but since we're not doing // anything with the TaskData object we don't need a lock. TaskData *p = processesModule.TaskForIdentifier(targetThread.AsObjPtr()); if (p != 0) return TAGGED(1).AsUnsigned(); else return TAGGED(0).AsUnsigned(); } // Send an interrupt to a specific thread POLYUNSIGNED PolyThreadInterruptThread(PolyWord targetThread) { // Must lock here because the thread may be exiting. processesModule.schedLock.Lock(); TaskData *p = processesModule.TaskForIdentifier(targetThread.AsObjPtr()); if (p) processesModule.MakeRequest(p, kRequestInterrupt); processesModule.schedLock.Unlock(); // If the thread cannot be identified return false. // The caller can then raise an exception if (p == 0) return TAGGED(0).AsUnsigned(); else return TAGGED(1).AsUnsigned(); } // Kill a specific thread POLYUNSIGNED PolyThreadKillThread(PolyWord targetThread) { processesModule.schedLock.Lock(); TaskData *p = processesModule.TaskForIdentifier(targetThread.AsObjPtr()); if (p) processesModule.MakeRequest(p, kRequestKill); processesModule.schedLock.Unlock(); // If the thread cannot be identified return false. // The caller can then raise an exception if (p == 0) return TAGGED(0).AsUnsigned(); else return TAGGED(1).AsUnsigned(); } POLYUNSIGNED PolyThreadBroadcastInterrupt(PolyObject * /*threadId*/) { processesModule.BroadcastInterrupt(); return TAGGED(0).AsUnsigned(); } POLYUNSIGNED PolyThreadTestInterrupt(PolyObject *threadId) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); try { processesModule.TestSynchronousRequests(taskData); // Also process any asynchronous requests that may be pending. // These will be handled "soon" but if we have just switched from deferring // interrupts this guarantees that any deferred interrupts will be handled now. if (processesModule.ProcessAsynchRequests(taskData)) throw IOException(); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } // Return the number of processors. // Returns 1 if there is any problem. POLYUNSIGNED PolyThreadNumProcessors(void) { return TAGGED(NumberOfProcessors()).AsUnsigned(); } // Return the number of physical processors. // Returns 0 if there is any problem. POLYUNSIGNED PolyThreadNumPhysicalProcessors(void) { return TAGGED(NumberOfPhysicalProcessors()).AsUnsigned(); } // Set the maximum stack size. POLYUNSIGNED PolyThreadMaxStackSize(PolyObject *threadId, PolyWord newSize) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); try { taskData->threadObject->mlStackSize = newSize; if (newSize != TAGGED(0)) { uintptr_t current = taskData->currentStackSpace(); // Current size in words uintptr_t newWords = getPolyUnsigned(taskData, newSize); if (current > newWords) raise_exception0(taskData, EXC_interrupt); } } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); return TAGGED(0).AsUnsigned(); } // Old dispatch function. This is only required because the pre-built compiler // may use some of these e.g. fork. Handle Processes::ThreadDispatch(TaskData *taskData, Handle args, Handle code) { unsigned c = get_C_unsigned(taskData, code->Word()); TaskData *ptaskData = taskData; switch (c) { case 1: MutexBlock(taskData, args); return SAVE(TAGGED(0)); case 2: MutexUnlock(taskData, args); return SAVE(TAGGED(0)); case 7: // Fork a new thread. The arguments are the function to run and the attributes. return ForkThread(ptaskData, SAVE(args->WordP()->Get(0)), (Handle)0, args->WordP()->Get(1), // For backwards compatibility we check the length here args->WordP()->Length() <= 2 ? TAGGED(0) : args->WordP()->Get(2)); case 10: // Broadcast an interrupt to all threads that are interested. BroadcastInterrupt(); return SAVE(TAGGED(0)); default: { char msg[100]; sprintf(msg, "Unknown thread function: %u", c); raise_fail(taskData, msg); return 0; } } } // Fill unused allocation space with a dummy object to preserve the invariant // that memory is always valid. void TaskData::FillUnusedSpace(void) { if (allocPointer > allocLimit) gMem.FillUnusedSpace(allocLimit, allocPointer-allocLimit); } TaskData::TaskData(): allocPointer(0), allocLimit(0), allocSize(MIN_HEAP_SIZE), allocCount(0), stack(0), threadObject(0), signalStack(0), inML(false), requests(kRequestNone), blockMutex(0), inMLHeap(false), runningProfileTimer(false) { #ifdef HAVE_WINDOWS_H lastCPUTime = 0; #endif #ifdef HAVE_WINDOWS_H threadHandle = 0; #endif threadExited = false; } TaskData::~TaskData() { if (signalStack) free(signalStack); if (stack) gMem.DeleteStackSpace(stack); #ifdef HAVE_WINDOWS_H if (threadHandle) CloseHandle(threadHandle); #endif } // Broadcast an interrupt to all relevant threads. void Processes::BroadcastInterrupt(void) { // If a thread is set to accept broadcast interrupts set it to // "interrupted". PLocker lock(&schedLock); for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; if (p) { POLYUNSIGNED attrs = ThreadAttrs(p); if (attrs & PFLAG_BROADCAST) MakeRequest(p, kRequestInterrupt); } } } // Set the asynchronous request variable for the thread. Must be called // with the schedLock held. Tries to wake the thread up if possible. void Processes::MakeRequest(TaskData *p, ThreadRequests request) { // We don't override a request to kill by an interrupt request. if (p->requests < request) { p->requests = request; p->InterruptCode(); p->threadLock.Signal(); // Set the value in the ML object as well so the ML code can see it p->threadObject->requestCopy = TAGGED(request); } } void Processes::ThreadExit(TaskData *taskData) { if (debugOptions & DEBUG_THREADS) Log("THREAD: Thread %p exiting\n", taskData); #if (!defined(_WIN32)) // Block any profile interrupt from now on. We're deleting the ML stack for this thread. sigset_t block_sigs; sigemptyset(&block_sigs); sigaddset(&block_sigs, SIGVTALRM); pthread_sigmask(SIG_BLOCK, &block_sigs, NULL); // Remove the thread-specific data since it's no // longer valid. pthread_setspecific(tlsId, 0); #endif if (singleThreaded) finish(0); schedLock.Lock(); ThreadReleaseMLMemoryWithSchedLock(taskData); // Allow a GC if it was waiting for us. taskData->threadExited = true; initialThreadWait.Signal(); // Tell it we've finished. schedLock.Unlock(); #if (!defined(_WIN32)) pthread_exit(0); #else ExitThread(0); #endif } // These two functions are used for calls from outside where // the lock has not yet been acquired. void Processes::ThreadUseMLMemory(TaskData *taskData) { // Trying to acquire the lock here may block if a GC is in progress PLocker lock(&schedLock); ThreadUseMLMemoryWithSchedLock(taskData); } void Processes::ThreadReleaseMLMemory(TaskData *taskData) { PLocker lock(&schedLock); ThreadReleaseMLMemoryWithSchedLock(taskData); } // Called when a thread wants to resume using the ML heap. That could // be after a wait for some reason or after executing some foreign code. // Since there could be a GC in progress already at this point we may either // be blocked waiting to acquire schedLock or we may need to wait until // we are woken up at the end of the GC. void Processes::ThreadUseMLMemoryWithSchedLock(TaskData *taskData) { TaskData *ptaskData = taskData; // If there is a request outstanding we have to wait for it to // complete. We notify the root thread and wait for it. while (threadRequest != 0) { initialThreadWait.Signal(); // Wait for the GC to happen mlThreadWait.Wait(&schedLock); } ASSERT(! ptaskData->inMLHeap); ptaskData->inMLHeap = true; } // Called to indicate that the thread has temporarily finished with the // ML memory either because it is going to wait for something or because // it is going to run foreign code. If there is an outstanding GC request // that can proceed. void Processes::ThreadReleaseMLMemoryWithSchedLock(TaskData *taskData) { TaskData *ptaskData = taskData; ASSERT(ptaskData->inMLHeap); ptaskData->inMLHeap = false; // Put a dummy object in any unused space. This maintains the // invariant that the allocated area is filled with valid objects. ptaskData->FillUnusedSpace(); // if (threadRequest != 0) initialThreadWait.Signal(); } // Make a request to the root thread. void Processes::MakeRootRequest(TaskData *taskData, MainThreadRequest *request) { if (singleThreaded) { mainThreadPhase = request->mtp; ThreadReleaseMLMemoryWithSchedLock(taskData); // Primarily to call FillUnusedSpace request->Perform(); ThreadUseMLMemoryWithSchedLock(taskData); mainThreadPhase = MTP_USER_CODE; } else { PLocker locker(&schedLock); // Wait for any other requests. while (threadRequest != 0) { // Deal with any pending requests. ThreadReleaseMLMemoryWithSchedLock(taskData); ThreadUseMLMemoryWithSchedLock(taskData); // Drops schedLock while waiting. } // Now the other requests have been dealt with (and we have schedLock). request->completed = false; threadRequest = request; // Wait for it to complete. while (! request->completed) { ThreadReleaseMLMemoryWithSchedLock(taskData); ThreadUseMLMemoryWithSchedLock(taskData); // Drops schedLock while waiting. } } } // Find space for an object. Returns a pointer to the start. "words" must include // the length word and the result points at where the length word will go. PolyWord *Processes::FindAllocationSpace(TaskData *taskData, POLYUNSIGNED words, bool alwaysInSeg) { bool triedInterrupt = false; #ifdef POLYML32IN64 if (words & 1) words++; // Must always be an even number of words. #endif while (1) { // After a GC allocPointer and allocLimit are zero and when allocating the // heap segment we request a minimum of zero words. if (taskData->allocPointer != 0 && taskData->allocPointer >= taskData->allocLimit + words) { // There's space in the current segment, taskData->allocPointer -= words; #ifdef POLYML32IN64 // Zero the last word. If we've rounded up an odd number the caller won't set it. if (words != 0) taskData->allocPointer[words-1] = PolyWord::FromUnsigned(0); ASSERT((uintptr_t)taskData->allocPointer & 4); // Must be odd-word aligned #endif return taskData->allocPointer; } else // Insufficient space in this area. { if (words > taskData->allocSize && ! alwaysInSeg) { // If the object we want is larger than the heap segment size // we allocate it separately rather than in the segment. PolyWord *foundSpace = gMem.AllocHeapSpace(words); if (foundSpace) return foundSpace; } else { // Fill in any unused space in the existing segment taskData->FillUnusedSpace(); // Get another heap segment with enough space for this object. uintptr_t requestSpace = taskData->allocSize+words; uintptr_t spaceSize = requestSpace; // Get the space and update spaceSize with the actual size. PolyWord *space = gMem.AllocHeapSpace(words, spaceSize); if (space) { // Double the allocation size for the next time if // we succeeded in allocating the whole space. taskData->allocCount++; if (spaceSize == requestSpace) taskData->allocSize = taskData->allocSize*2; taskData->allocLimit = space; taskData->allocPointer = space+spaceSize; // Actually allocate the object taskData->allocPointer -= words; #ifdef POLYML32IN64 ASSERT((uintptr_t)taskData->allocPointer & 4); // Must be odd-word aligned #endif return taskData->allocPointer; } } // It's possible that another thread has requested a GC in which case // we will have memory when that happens. We don't want to start // another GC. if (! singleThreaded) { PLocker locker(&schedLock); if (threadRequest != 0) { ThreadReleaseMLMemoryWithSchedLock(taskData); ThreadUseMLMemoryWithSchedLock(taskData); continue; // Try again } } // Try garbage-collecting. If this failed return 0. if (! QuickGC(taskData, words)) { extern FILE *polyStderr; if (! triedInterrupt) { triedInterrupt = true; fprintf(polyStderr,"Run out of store - interrupting threads\n"); if (debugOptions & DEBUG_THREADS) Log("THREAD: Run out of store, interrupting threads\n"); BroadcastInterrupt(); try { if (ProcessAsynchRequests(taskData)) return 0; // Has been interrupted. } catch(KillException &) { // The thread may have been killed. ThreadExit(taskData); } // Not interrupted: pause this thread to allow for other // interrupted threads to free something. #if defined(_WIN32) Sleep(5000); #else sleep(5); #endif // Try again. } else { // That didn't work. Exit. fprintf(polyStderr,"Failed to recover - exiting\n"); RequestProcessExit(1); // Begins the shutdown process ThreadExit(taskData); // And terminate this thread. } } // Try again. There should be space now. } } } #ifdef _MSC_VER // Don't tell me that exitThread has a non-void type. #pragma warning(disable:4646) #endif Handle exitThread(TaskData *taskData) /* A call to this is put on the stack of a new thread so when the thread function returns the thread goes away. */ { processesModule.ThreadExit(taskData); } // Terminate the current thread. Never returns. POLYUNSIGNED PolyThreadKillSelf(PolyObject *threadId) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); // Possibly not needed since we never return processesModule.ThreadExit(taskData); return 0; } /* Called when a thread is about to block, usually because of IO. If this is interruptable (currently only used for Posix functions) the process will be set to raise an exception if any signal is handled. It may also raise an exception if another thread has called broadcastInterrupt. */ void Processes::ThreadPauseForIO(TaskData *taskData, Waiter *pWait) { TestAnyEvents(taskData); // Consider this a blocking call that may raise Interrupt ThreadReleaseMLMemory(taskData); globalStats.incCount(PSC_THREADS_WAIT_IO); pWait->Wait(1000); // Wait up to a second globalStats.decCount(PSC_THREADS_WAIT_IO); ThreadUseMLMemory(taskData); TestAnyEvents(taskData); // Check if we've been interrupted. } // Default waiter: simply wait for the time. In Unix it may be woken // up by a signal. void Waiter::Wait(unsigned maxMillisecs) { // Since this is used only when we can't monitor the source directly // we set this to 10ms so that we're not waiting too long. if (maxMillisecs > 10) maxMillisecs = 10; #if (defined(_WIN32)) Sleep(maxMillisecs); #else // Unix fd_set read_fds, write_fds, except_fds; struct timeval toWait = { 0, 0 }; toWait.tv_sec = maxMillisecs / 1000; toWait.tv_usec = (maxMillisecs % 1000) * 1000; FD_ZERO(&read_fds); FD_ZERO(&write_fds); FD_ZERO(&except_fds); select(FD_SETSIZE, &read_fds, &write_fds, &except_fds, &toWait); #endif } static Waiter defWait; Waiter *Waiter::defaultWaiter = &defWait; #ifdef _WIN32 // Wait for the specified handle to be signalled. void WaitHandle::Wait(unsigned maxMillisecs) { // Wait until we get input or we're woken up. if (m_Handle == NULL) Sleep(maxMillisecs); else WaitForSingleObject(m_Handle, maxMillisecs); } #else // Unix and Cygwin: Wait for a file descriptor on input. void WaitInputFD::Wait(unsigned maxMillisecs) { fd_set read_fds, write_fds, except_fds; struct timeval toWait = { 0, 0 }; toWait.tv_sec = maxMillisecs / 1000; toWait.tv_usec = (maxMillisecs % 1000) * 1000; FD_ZERO(&read_fds); if (m_waitFD >= 0) FD_SET(m_waitFD, &read_fds); FD_ZERO(&write_fds); FD_ZERO(&except_fds); select(FD_SETSIZE, &read_fds, &write_fds, &except_fds, &toWait); } #endif // Get the task data for the current thread. This is held in // thread-local storage. Normally this is passed in taskData but // in a few cases this isn't available. TaskData *Processes::GetTaskDataForThread(void) { #if (!defined(_WIN32)) return (TaskData *)pthread_getspecific(tlsId); #else return (TaskData *)TlsGetValue(tlsId); #endif } // Called to create a task data object in the current thread. // This is currently only used if a thread created in foreign code calls // a callback. TaskData *Processes::CreateNewTaskData(Handle threadId, Handle threadFunction, Handle args, PolyWord flags) { TaskData *taskData = machineDependent->CreateTaskData(); #if defined(HAVE_WINDOWS_H) HANDLE thisProcess = GetCurrentProcess(); DuplicateHandle(thisProcess, GetCurrentThread(), thisProcess, &(taskData->threadHandle), THREAD_ALL_ACCESS, FALSE, 0); #endif unsigned thrdIndex; { PLocker lock(&schedLock); // See if there's a spare entry in the array. for (thrdIndex = 0; thrdIndex < taskArray.size() && taskArray[thrdIndex] != 0; thrdIndex++); if (thrdIndex == taskArray.size()) // Need to expand the array { try { taskArray.push_back(taskData); } catch (std::bad_alloc&) { delete(taskData); throw MemoryException(); } } else { taskArray[thrdIndex] = taskData; } } taskData->stack = gMem.NewStackSpace(machineDependent->InitialStackSize()); if (taskData->stack == 0) { delete(taskData); throw MemoryException(); } // TODO: Check that there isn't a problem if we try to allocate // memory here and result in a GC. taskData->InitStackFrame(taskData, threadFunction, args); ThreadUseMLMemory(taskData); // If the forking thread has created an ML thread object use that // otherwise create a new one in the current context. if (threadId != 0) taskData->threadObject = (ThreadObject*)threadId->WordP(); else { // Make a thread reference to point to this taskData object. Handle threadRef = MakeVolatileWord(taskData, taskData); // Make a thread object. Since it's in the thread table it can't be garbage collected. taskData->threadObject = (ThreadObject*)alloc(taskData, sizeof(ThreadObject)/sizeof(PolyWord), F_MUTABLE_BIT); taskData->threadObject->threadRef = threadRef->Word(); taskData->threadObject->flags = flags != TAGGED(0) ? TAGGED(PFLAG_SYNCH): flags; taskData->threadObject->threadLocal = TAGGED(0); // Empty thread-local store taskData->threadObject->requestCopy = TAGGED(0); // Cleared interrupt state taskData->threadObject->mlStackSize = TAGGED(0); // Unlimited stack size for (unsigned i = 0; i < sizeof(taskData->threadObject->debuggerSlots)/sizeof(PolyWord); i++) taskData->threadObject->debuggerSlots[i] = TAGGED(0); } #if (!defined(_WIN32)) initThreadSignals(taskData); pthread_setspecific(tlsId, taskData); #else TlsSetValue(tlsId, taskData); #endif globalStats.incCount(PSC_THREADS); return taskData; } // This function is run when a new thread has been forked. The // parameter is the taskData value for the new thread. This function // is also called directly for the main thread. #if (!defined(_WIN32)) static void *NewThreadFunction(void *parameter) { TaskData *taskData = (TaskData *)parameter; #ifdef HAVE_WINDOWS_H // Cygwin: Get the Windows thread handle in case it's needed for profiling. HANDLE thisProcess = GetCurrentProcess(); DuplicateHandle(thisProcess, GetCurrentThread(), thisProcess, &(taskData->threadHandle), THREAD_ALL_ACCESS, FALSE, 0); #endif initThreadSignals(taskData); pthread_setspecific(processesModule.tlsId, taskData); taskData->saveVec.init(); // Remove initial data globalStats.incCount(PSC_THREADS); processes->ThreadUseMLMemory(taskData); try { (void)taskData->EnterPolyCode(); // Will normally (always?) call ExitThread. } catch (KillException &) { processesModule.ThreadExit(taskData); } return 0; } #else static DWORD WINAPI NewThreadFunction(void *parameter) { TaskData *taskData = (TaskData *)parameter; TlsSetValue(processesModule.tlsId, taskData); taskData->saveVec.init(); // Removal initial data globalStats.incCount(PSC_THREADS); processes->ThreadUseMLMemory(taskData); try { (void)taskData->EnterPolyCode(); } catch (KillException &) { processesModule.ThreadExit(taskData); } return 0; } #endif // Sets up the initial thread from the root function. This is run on // the initial thread of the process so it will work if we don't // have pthreads. // When multithreading this thread also deals with all garbage-collection // and similar operations and the ML threads send it requests to deal with // that. These require all the threads to pause until the operation is complete // since they affect all memory but they are also sometimes highly recursive. // On Mac OS X and on Linux if the stack limit is set to unlimited only the // initial thread has a large stack and newly created threads have smaller // stacks. We need to make sure that any significant stack usage occurs only // on the inital thread. void Processes::BeginRootThread(PolyObject *rootFunction) { int exitLoopCount = 100; // Maximum 100 * 400 ms. if (taskArray.size() < 1) { try { taskArray.push_back(0); } catch (std::bad_alloc&) { ::Exit("Unable to create the initial thread - insufficient memory"); } } try { // We can't use ForkThread because we don't have a taskData object before we start TaskData *taskData = machineDependent->CreateTaskData(); Handle threadRef = MakeVolatileWord(taskData, taskData); taskData->threadObject = (ThreadObject*)alloc(taskData, sizeof(ThreadObject) / sizeof(PolyWord), F_MUTABLE_BIT); taskData->threadObject->threadRef = threadRef->Word(); // The initial thread is set to accept broadcast interrupt requests // and handle them synchronously. This is for backwards compatibility. taskData->threadObject->flags = TAGGED(PFLAG_BROADCAST|PFLAG_ASYNCH); // Flags taskData->threadObject->threadLocal = TAGGED(0); // Empty thread-local store taskData->threadObject->requestCopy = TAGGED(0); // Cleared interrupt state taskData->threadObject->mlStackSize = TAGGED(0); // Unlimited stack size for (unsigned i = 0; i < sizeof(taskData->threadObject->debuggerSlots)/sizeof(PolyWord); i++) taskData->threadObject->debuggerSlots[i] = TAGGED(0); #if defined(HAVE_WINDOWS_H) taskData->threadHandle = mainThreadHandle; #endif taskArray[0] = taskData; taskData->stack = gMem.NewStackSpace(machineDependent->InitialStackSize()); if (taskData->stack == 0) ::Exit("Unable to create the initial thread - insufficient memory"); taskData->InitStackFrame(taskData, taskData->saveVec.push(rootFunction), (Handle)0); // Create a packet for the Interrupt exception once so that we don't have to // allocate when we need to raise it. // We can only do this once the taskData object has been created. if (interrupt_exn == 0) interrupt_exn = makeExceptionPacket(taskData, EXC_interrupt); if (singleThreaded) { // If we don't have threading enter the code as if this were a new thread. // This will call finish so will never return. NewThreadFunction(taskData); } schedLock.Lock(); int errorCode = 0; #if (!defined(_WIN32)) if (pthread_create(&taskData->threadId, NULL, NewThreadFunction, taskData) != 0) errorCode = errno; #else taskData->threadHandle = CreateThread(NULL, 0, NewThreadFunction, taskData, 0, NULL); if (taskData->threadHandle == NULL) errorCode = GetLastError(); #endif if (errorCode != 0) { // Thread creation failed. taskArray[0] = 0; delete(taskData); ExitWithError("Unable to create initial thread:", errorCode); } if (debugOptions & DEBUG_THREADS) Log("THREAD: Forked initial root thread %p\n", taskData); } catch (std::bad_alloc &) { ::Exit("Unable to create the initial thread - insufficient memory"); } // Wait until the threads terminate or make a request. // We only release schedLock while waiting. while (1) { // Look at the threads to see if they are running. bool allStopped = true; bool noUserThreads = true; bool signalThreadRunning = false; for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; if (p) { if (p == sigTask) signalThreadRunning = true; else if (! p->threadExited) noUserThreads = false; if (p->inMLHeap) { allStopped = false; // It must be running - interrupt it if we are waiting. if (threadRequest != 0) p->InterruptCode(); } else if (p->threadExited) // Has the thread terminated? { // Wait for it to actually stop then delete the task data. #if (!defined(_WIN32)) pthread_join(p->threadId, NULL); #else WaitForSingleObject(p->threadHandle, INFINITE); #endif // The thread ref is no longer valid. *(TaskData**)(p->threadObject->threadRef.AsObjPtr()) = 0; delete(p); // Delete the task Data *i = 0; globalStats.decCount(PSC_THREADS); } } } if (noUserThreads) { // If all threads apart from the signal thread have exited then // we can finish but we must make sure that the signal thread has // exited before we finally finish and deallocate the memory. if (signalThreadRunning) exitRequest = true; else break; // Really no threads. } if (allStopped && threadRequest != 0) { mainThreadPhase = threadRequest->mtp; gcProgressBeginOtherGC(); // The default unless we're doing a GC. gMem.ProtectImmutable(false); // GC, sharing and export may all write to the immutable area threadRequest->Perform(); gMem.ProtectImmutable(true); mainThreadPhase = MTP_USER_CODE; gcProgressReturnToML(); threadRequest->completed = true; threadRequest = 0; // Allow a new request. mlThreadWait.Signal(); } // Have we had a request to stop? This may have happened while in the GC. if (exitRequest) { // Set this to kill the threads. for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *taskData = *i; if (taskData && taskData->requests != kRequestKill) MakeRequest(taskData, kRequestKill); } // Leave exitRequest set so that if we're in the process of // creating a new thread we will request it to stop when the // taskData object has been added to the table. } // Now release schedLock and wait for a thread // to wake us up or for the timer to expire to update the statistics. if (! initialThreadWait.WaitFor(&schedLock, 400)) { // We didn't receive a request in the last 400ms if (exitRequest) { if (--exitLoopCount < 0) { // The loop count has expired and there is at least one thread that hasn't exited. // Assume we've deadlocked. #if defined(HAVE_WINDOWS_H) ExitProcess(1); #else _exit(1); // Something is stuck. Get out without calling destructors. #endif } } } // Update the periodic stats. // Calculate the free memory. We have to be careful here because although // we have the schedLock we don't have any lock that prevents a thread // from allocating a new segment. Since these statistics are only // very rough it doesn't matter if there's a glitch. // One possibility would be see if the value of // gMem.GetFreeAllocSpace() has changed from what it was at the // start and recalculate if it has. // We also count the number of threads in ML code. Taking the // lock in EnterPolyCode on every RTS call turned out to be // expensive. uintptr_t freeSpace = 0; unsigned threadsInML = 0; for (std::vector::iterator j = taskArray.begin(); j != taskArray.end(); j++) { TaskData *taskData = *j; if (taskData) { // This gets the values last time it was in the RTS. PolyWord *limit = taskData->allocLimit, *ptr = taskData->allocPointer; if (limit < ptr && (uintptr_t)(ptr-limit) < taskData->allocSize) freeSpace += ptr-limit; if (taskData->inML) threadsInML++; } } // Add the space in the allocation areas after calculating the sizes for the // threads in case a thread has allocated some more. freeSpace += gMem.GetFreeAllocSpace(); globalStats.updatePeriodicStats(freeSpace, threadsInML); // Process the profile queue if necessary. processProfileQueue(); } schedLock.Unlock(); finish(exitResult); // Close everything down and exit. } // Create a new thread. Returns the ML thread identifier object if it succeeds. // May raise an exception. Handle Processes::ForkThread(TaskData *taskData, Handle threadFunction, Handle args, PolyWord flags, PolyWord stacksize) { if (singleThreaded) raise_exception_string(taskData, EXC_thread, "Threads not available"); try { // Create a taskData object for the new thread TaskData *newTaskData = machineDependent->CreateTaskData(); // We allocate the thread object in the PARENT's space Handle threadRef = MakeVolatileWord(taskData, newTaskData); Handle threadId = alloc_and_save(taskData, sizeof(ThreadObject) / sizeof(PolyWord), F_MUTABLE_BIT); newTaskData->threadObject = (ThreadObject*)DEREFHANDLE(threadId); newTaskData->threadObject->threadRef = threadRef->Word(); newTaskData->threadObject->flags = flags; // Flags newTaskData->threadObject->threadLocal = TAGGED(0); // Empty thread-local store newTaskData->threadObject->requestCopy = TAGGED(0); // Cleared interrupt state newTaskData->threadObject->mlStackSize = stacksize; for (unsigned i = 0; i < sizeof(newTaskData->threadObject->debuggerSlots)/sizeof(PolyWord); i++) newTaskData->threadObject->debuggerSlots[i] = TAGGED(0); unsigned thrdIndex; schedLock.Lock(); // Before forking a new thread check to see whether we have been asked // to exit. Processes::Exit sets the current set of threads to exit but won't // see a new thread. if (taskData->requests == kRequestKill) { schedLock.Unlock(); // Raise an exception although the thread may exit before we get there. raise_exception_string(taskData, EXC_thread, "Thread is exiting"); } // See if there's a spare entry in the array. for (thrdIndex = 0; thrdIndex < taskArray.size() && taskArray[thrdIndex] != 0; thrdIndex++); if (thrdIndex == taskArray.size()) // Need to expand the array { try { taskArray.push_back(newTaskData); } catch (std::bad_alloc&) { delete(newTaskData); schedLock.Unlock(); raise_exception_string(taskData, EXC_thread, "Too many threads"); } } else { taskArray[thrdIndex] = newTaskData; } schedLock.Unlock(); newTaskData->stack = gMem.NewStackSpace(machineDependent->InitialStackSize()); if (newTaskData->stack == 0) { delete(newTaskData); raise_exception_string(taskData, EXC_thread, "Unable to allocate thread stack"); } // Allocate anything needed for the new stack in the parent's heap. // The child still has inMLHeap set so mustn't GC. newTaskData->InitStackFrame(taskData, threadFunction, args); // Now actually fork the thread. bool success = false; schedLock.Lock(); #if (!defined(_WIN32)) success = pthread_create(&newTaskData->threadId, NULL, NewThreadFunction, newTaskData) == 0; #else newTaskData->threadHandle = CreateThread(NULL, 0, NewThreadFunction, newTaskData, 0, NULL); success = newTaskData->threadHandle != NULL; #endif if (success) { schedLock.Unlock(); if (debugOptions & DEBUG_THREADS) Log("THREAD: Forking new thread %p from thread %p\n", newTaskData, taskData); return threadId; } // Thread creation failed. taskArray[thrdIndex] = 0; delete(newTaskData); schedLock.Unlock(); if (debugOptions & DEBUG_THREADS) Log("THREAD: Fork from thread %p failed\n", taskData); raise_exception_string(taskData, EXC_thread, "Thread creation failed"); } catch (std::bad_alloc &) { raise_exception_string(taskData, EXC_thread, "Insufficient memory"); } } // ForkFromRTS. Creates a new thread from within the RTS. This is currently used // only to run a signal function. bool Processes::ForkFromRTS(TaskData *taskData, Handle proc, Handle arg) { try { (void)ForkThread(taskData, proc, arg, TAGGED(PFLAG_SYNCH), TAGGED(0)); return true; } catch (IOException &) { // If it failed return false; } } POLYUNSIGNED PolyThreadForkThread(PolyObject *threadId, PolyWord function, PolyWord attrs, PolyWord stack) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedFunction = taskData->saveVec.push(function); Handle result = 0; try { result = processesModule.ForkThread(taskData, pushedFunction, (Handle)0, attrs, stack); } catch (KillException &) { processes->ThreadExit(taskData); // TestSynchronousRequests may test for kill } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); taskData->PostRTSCall(); if (result == 0) return TAGGED(0).AsUnsigned(); else return result->Word().AsUnsigned(); } // Deal with any interrupt or kill requests. bool Processes::ProcessAsynchRequests(TaskData *taskData) { bool wasInterrupted = false; TaskData *ptaskData = taskData; schedLock.Lock(); switch (ptaskData->requests) { case kRequestNone: schedLock.Unlock(); break; case kRequestInterrupt: { // Handle asynchronous interrupts only. // We've been interrupted. POLYUNSIGNED attrs = ThreadAttrs(ptaskData); POLYUNSIGNED intBits = attrs & PFLAG_INTMASK; if (intBits == PFLAG_ASYNCH || intBits == PFLAG_ASYNCH_ONCE) { if (intBits == PFLAG_ASYNCH_ONCE) { // Set this so from now on it's synchronous. // This word is only ever set by the thread itself so // we don't need to synchronise. attrs = (attrs & (~PFLAG_INTMASK)) | PFLAG_SYNCH; ptaskData->threadObject->flags = TAGGED(attrs); } ptaskData->requests = kRequestNone; // Clear this ptaskData->threadObject->requestCopy = TAGGED(0); // And in the ML copy schedLock.Unlock(); // Don't actually throw the exception here. taskData->SetException(interrupt_exn); wasInterrupted = true; } else schedLock.Unlock(); } break; case kRequestKill: // The thread has been asked to stop. schedLock.Unlock(); throw KillException(); // Doesn't return. } #ifndef HAVE_WINDOWS_H // Start the profile timer if needed. if (profileMode == kProfileTime) { if (! ptaskData->runningProfileTimer) { ptaskData->runningProfileTimer = true; StartProfilingTimer(); } } else ptaskData->runningProfileTimer = false; // The timer will be stopped next time it goes off. #endif return wasInterrupted; } // If this thread is processing interrupts synchronously and has been // interrupted clear the interrupt and raise the exception. This is // called from IO routines which may block. void Processes::TestSynchronousRequests(TaskData *taskData) { TaskData *ptaskData = taskData; schedLock.Lock(); switch (ptaskData->requests) { case kRequestNone: schedLock.Unlock(); break; case kRequestInterrupt: { // Handle synchronous interrupts only. // We've been interrupted. POLYUNSIGNED attrs = ThreadAttrs(ptaskData); POLYUNSIGNED intBits = attrs & PFLAG_INTMASK; if (intBits == PFLAG_SYNCH) { ptaskData->requests = kRequestNone; // Clear this ptaskData->threadObject->requestCopy = TAGGED(0); schedLock.Unlock(); taskData->SetException(interrupt_exn); throw IOException(); } else schedLock.Unlock(); } break; case kRequestKill: // The thread has been asked to stop. schedLock.Unlock(); throw KillException(); // Doesn't return. } } // Check for asynchronous or synchronous events void Processes::TestAnyEvents(TaskData *taskData) { TestSynchronousRequests(taskData); if (ProcessAsynchRequests(taskData)) throw IOException(); } // Request that the process should exit. // This will usually be called from an ML thread as a result of // a call to OS.Process.exit but on Windows it can be called from the GUI thread. void Processes::RequestProcessExit(int n) { if (singleThreaded) finish(n); exitResult = n; exitRequest = true; PLocker lock(&schedLock); // Lock so we know the main thread is waiting initialThreadWait.Signal(); // Wake it if it's sleeping. } #if !defined(HAVE_WINDOWS_H) // N.B. This may be called either by an ML thread or by the main thread. // On the main thread taskData will be null. static void catchVTALRM(SIG_HANDLER_ARGS(sig, context)) { ASSERT(sig == SIGVTALRM); if (profileMode != kProfileTime) { // We stop the timer for this thread on the next signal after we end profile static struct itimerval stoptime = {{0, 0}, {0, 0}}; /* Stop the timer */ setitimer(ITIMER_VIRTUAL, & stoptime, NULL); } else { TaskData *taskData = processes->GetTaskDataForThread(); handleProfileTrap(taskData, (SIGNALCONTEXT*)context); } } #else /* Windows including Cygwin */ // This runs as a separate thread. Every millisecond it checks the CPU time used // by each ML thread and increments the count for each thread that has used a // millisecond of CPU time. static bool testCPUtime(HANDLE hThread, LONGLONG &lastCPUTime) { FILETIME cTime, eTime, kTime, uTime; // Try to get the thread CPU time if possible. This isn't supported // in Windows 95/98 so if it fails we just include this thread anyway. if (GetThreadTimes(hThread, &cTime, &eTime, &kTime, &uTime)) { LONGLONG totalTime = 0; LARGE_INTEGER li; li.LowPart = kTime.dwLowDateTime; li.HighPart = kTime.dwHighDateTime; totalTime += li.QuadPart; li.LowPart = uTime.dwLowDateTime; li.HighPart = uTime.dwHighDateTime; totalTime += li.QuadPart; if (totalTime - lastCPUTime >= 10000) { lastCPUTime = totalTime; return true; } return false; } else return true; // Failed to get thread time, maybe Win95. } void Processes::ProfileInterrupt(void) { // Wait for millisecond or until the stop event is signalled. while (WaitForSingleObject(hStopEvent, 1) == WAIT_TIMEOUT) { // We need to hold schedLock to examine the taskArray but // that is held during garbage collection. if (schedLock.Trylock()) { for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *p = *i; if (p && p->threadHandle) { if (testCPUtime(p->threadHandle, p->lastCPUTime)) { CONTEXT context; SuspendThread(p->threadHandle); context.ContextFlags = CONTEXT_CONTROL; /* Get Eip and Esp */ if (GetThreadContext(p->threadHandle, &context)) { handleProfileTrap(p, &context); } ResumeThread(p->threadHandle); } } } schedLock.Unlock(); } // Check the CPU time used by the main thread. This is used for GC // so we need to check that as well. if (testCPUtime(mainThreadHandle, lastCPUTime)) handleProfileTrap(NULL, NULL); } } DWORD WINAPI ProfilingTimer(LPVOID parm) { processesModule.ProfileInterrupt(); return 0; } #endif // Profiling control. Called by the root thread. void Processes::StartProfiling(void) { #ifdef HAVE_WINDOWS_H DWORD threadId; extern FILE *polyStdout; if (profilingHd) return; ResetEvent(hStopEvent); profilingHd = CreateThread(NULL, 0, ProfilingTimer, NULL, 0, &threadId); if (profilingHd == NULL) { fputs("Creating ProfilingTimer thread failed.\n", polyStdout); return; } /* Give this a higher than normal priority so it pre-empts the main thread. Without this it will tend only to be run when the main thread blocks for some reason. */ SetThreadPriority(profilingHd, THREAD_PRIORITY_ABOVE_NORMAL); #else // In Linux, at least, we need to run a timer in each thread. // We request each to enter the RTS so that it will start the timer. // Since this is being run by the main thread while all the ML threads // are paused this may not actually be necessary. for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { TaskData *taskData = *i; if (taskData) { taskData->InterruptCode(); } } StartProfilingTimer(); // Start the timer in the root thread. #endif } void Processes::StopProfiling(void) { #ifdef HAVE_WINDOWS_H if (hStopEvent) SetEvent(hStopEvent); // Wait for the thread to stop if (profilingHd) { WaitForSingleObject(profilingHd, 10000); CloseHandle(profilingHd); } profilingHd = NULL; #endif } // Called by the ML signal handling thread. It blocks until a signal // arrives. There should only be a single thread waiting here. bool Processes::WaitForSignal(TaskData *taskData, PLock *sigLock) { TaskData *ptaskData = taskData; // We need to hold the signal lock until we have acquired schedLock. PLocker lock(&schedLock); sigLock->Unlock(); if (sigTask != 0) { return false; } sigTask = ptaskData; if (ptaskData->requests == kRequestNone) { // Now release the ML memory. A GC can start. ThreadReleaseMLMemoryWithSchedLock(ptaskData); globalStats.incCount(PSC_THREADS_WAIT_SIGNAL); ptaskData->threadLock.Wait(&schedLock); globalStats.decCount(PSC_THREADS_WAIT_SIGNAL); // We want to use the memory again. ThreadUseMLMemoryWithSchedLock(ptaskData); } sigTask = 0; return true; } // Called by the signal detection thread to wake up the signal handler // thread. Must be called AFTER releasing sigLock. void Processes::SignalArrived(void) { PLocker locker(&schedLock); if (sigTask) sigTask->threadLock.Signal(); } #if (!defined(_WIN32)) // This is called when the thread exits in foreign code and // ThreadExit has not been called. static void threaddata_destructor(void *p) { TaskData *pt = (TaskData *)p; pt->threadExited = true; // This doesn't actually wake the main thread and relies on the // regular check to release the task data. } #endif void Processes::Init(void) { #if (!defined(_WIN32)) pthread_key_create(&tlsId, threaddata_destructor); #else tlsId = TlsAlloc(); #endif #if defined(HAVE_WINDOWS_H) /* Windows including Cygwin. */ // Create stop event for time profiling. hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Get the thread handle for this thread. HANDLE thisProcess = GetCurrentProcess(); DuplicateHandle(thisProcess, GetCurrentThread(), thisProcess, &mainThreadHandle, THREAD_ALL_ACCESS, FALSE, 0); #else // Set up a signal handler. This will be the same for all threads. markSignalInuse(SIGVTALRM); setSignalHandler(SIGVTALRM, catchVTALRM); #endif } #ifndef HAVE_WINDOWS_H // On Linux, at least, each thread needs to run this. void Processes::StartProfilingTimer(void) { // set virtual timer to go off every millisecond struct itimerval starttime; starttime.it_interval.tv_sec = starttime.it_value.tv_sec = 0; starttime.it_interval.tv_usec = starttime.it_value.tv_usec = 1000; setitimer(ITIMER_VIRTUAL,&starttime,NULL); } #endif void Processes::Stop(void) { #if (!defined(_WIN32)) pthread_key_delete(tlsId); #else TlsFree(tlsId); #endif #if defined(HAVE_WINDOWS_H) /* Stop the timer and profiling threads. */ if (hStopEvent) SetEvent(hStopEvent); if (profilingHd) { WaitForSingleObject(profilingHd, 10000); CloseHandle(profilingHd); profilingHd = NULL; } if (hStopEvent) CloseHandle(hStopEvent); hStopEvent = NULL; if (mainThreadHandle) CloseHandle(mainThreadHandle); mainThreadHandle = NULL; #else profileMode = kProfileOff; // Make sure the timer is not running struct itimerval stoptime; memset(&stoptime, 0, sizeof(stoptime)); setitimer(ITIMER_VIRTUAL, &stoptime, NULL); #endif } void Processes::GarbageCollect(ScanAddress *process) /* Ensures that all the objects are retained and their addresses updated. */ { /* The interrupt exn */ if (interrupt_exn != 0) { PolyObject *p = interrupt_exn; process->ScanRuntimeAddress(&p, ScanAddress::STRENGTH_STRONG); interrupt_exn = (PolyException*)p; } for (std::vector::iterator i = taskArray.begin(); i != taskArray.end(); i++) { if (*i) (*i)->GarbageCollect(process); } } void TaskData::GarbageCollect(ScanAddress *process) { saveVec.gcScan(process); if (threadObject != 0) { PolyObject *p = threadObject; process->ScanRuntimeAddress(&p, ScanAddress::STRENGTH_STRONG); threadObject = (ThreadObject*)p; } if (blockMutex != 0) process->ScanRuntimeAddress(&blockMutex, ScanAddress::STRENGTH_STRONG); // The allocation spaces are no longer valid. allocPointer = 0; allocLimit = 0; // Divide the allocation size by four. If we have made a single allocation // since the last GC the size will have been doubled after the allocation. // On average for each thread, apart from the one that ran out of space // and requested the GC, half of the space will be unused so reducing by // four should give a good estimate for next time. if (allocCount != 0) { // Do this only once for each GC. allocCount = 0; allocSize = allocSize/4; if (allocSize < MIN_HEAP_SIZE) allocSize = MIN_HEAP_SIZE; } } // Return the number of processors. extern unsigned NumberOfProcessors(void) { #if (defined(_WIN32)) SYSTEM_INFO info; memset(&info, 0, sizeof(info)); GetSystemInfo(&info); if (info.dwNumberOfProcessors == 0) // Just in case info.dwNumberOfProcessors = 1; return info.dwNumberOfProcessors; #elif(defined(_SC_NPROCESSORS_ONLN)) long res = sysconf(_SC_NPROCESSORS_ONLN); if (res <= 0) res = 1; return res; #elif(defined(HAVE_SYSCTL) && defined(CTL_HW) && defined(HW_NCPU)) static int mib[2] = { CTL_HW, HW_NCPU }; int nCPU = 1; size_t len = sizeof(nCPU); if (sysctl(mib, 2, &nCPU, &len, NULL, 0) == 0 && len == sizeof(nCPU)) return nCPU; else return 1; #else // Can't determine. return 1; #endif } // Return the number of physical processors. If hyperthreading is // enabled this returns less than NumberOfProcessors. Returns zero if // it cannot be determined. // This can be used in Cygwin as well as native Windows. #if (defined(HAVE_SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) typedef BOOL (WINAPI *GETP)(SYSTEM_LOGICAL_PROCESSOR_INFORMATION*, PDWORD); // Windows - use GetLogicalProcessorInformation if it's available. static unsigned WinNumPhysicalProcessors(void) { GETP getProcInfo = (GETP) GetProcAddress(GetModuleHandle(_T("kernel32")), "GetLogicalProcessorInformation"); if (getProcInfo == 0) return 0; // It's there - use it. SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buff = 0; DWORD space = 0; while (getProcInfo(buff, &space) == FALSE) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { free(buff); return 0; } free(buff); buff = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(space); if (buff == 0) return 0; } // Calculate the number of full entries in case it's truncated. unsigned nItems = space / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); unsigned numProcs = 0; for (unsigned i = 0; i < nItems; i++) { if (buff[i].Relationship == RelationProcessorCore) numProcs++; } free(buff); return numProcs; } #endif // Read and parse /proc/cpuinfo static unsigned LinuxNumPhysicalProcessors(void) { // Find out the total. This should be the maximum. unsigned nProcs = NumberOfProcessors(); // If there's only one we don't need to check further. if (nProcs <= 1) return nProcs; long *cpus = (long*)calloc(nProcs, sizeof(long)); if (cpus == 0) return 0; FILE *cpuInfo = fopen("/proc/cpuinfo", "r"); if (cpuInfo == NULL) { free(cpus); return 0; } char line[40]; unsigned count = 0; while (fgets(line, sizeof(line), cpuInfo) != NULL) { if (strncmp(line, "core id\t\t:", 10) == 0) { long n = strtol(line+10, NULL, 10); unsigned i = 0; // Skip this id if we've seen it already while (i < count && cpus[i] != n) i++; if (i == count) cpus[count++] = n; } if (strchr(line, '\n') == 0) { int ch; do { ch = getc(cpuInfo); } while (ch != '\n' && ch != EOF); } } fclose(cpuInfo); free(cpus); return count; } extern unsigned NumberOfPhysicalProcessors(void) { unsigned numProcs = 0; #if (defined(HAVE_SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) numProcs = WinNumPhysicalProcessors(); if (numProcs != 0) return numProcs; #endif #if (defined(HAVE_SYSCTLBYNAME) && defined(HAVE_SYS_SYSCTL_H)) // Mac OS X int nCores; size_t len = sizeof(nCores); if (sysctlbyname("hw.physicalcpu", &nCores, &len, NULL, 0) == 0) return (unsigned)nCores; #endif numProcs = LinuxNumPhysicalProcessors(); if (numProcs != 0) return numProcs; // Any other cases? return numProcs; } diff --git a/libpolyml/processes.h b/libpolyml/processes.h index f501101a..3bd565af 100644 --- a/libpolyml/processes.h +++ b/libpolyml/processes.h @@ -1,361 +1,358 @@ /* Title: Lightweight process library Author: David C.J. Matthews Copyright (c) 2007-8, 2012, 2015, 2017, 2019, 2020 David C.J. Matthews This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _PROCESSES_H_ #define _PROCESSES_H_ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_WIN32) #include "winconfig.h" #else #error "No configuration file" #endif #include "globals.h" #include "rts_module.h" #include "save_vec.h" #include "noreturn.h" #include "locking.h" class SaveVecEntry; typedef SaveVecEntry *Handle; class StackSpace; class PolyWord; class ScanAddress; class MDTaskData; class Exporter; class StackObject; #ifdef HAVE_WINDOWS_H typedef void *HANDLE; #endif #ifdef HAVE_SIGNAL_H #include #endif #ifdef HAVE_UCONTEXT_H #include #endif #ifdef HAVE_PTHREAD_H #include #endif // SIGNALCONTEXT is the argument type that is passed to GetPCandSPFromContext // to get the actual PC and SP in a profiling trap. #if defined(HAVE_WINDOWS_H) // First because it's used in both native Windows and Cygwin. #include #define SIGNALCONTEXT CONTEXT // This is the thread context. #elif defined(HAVE_UCONTEXT_T) #define SIGNALCONTEXT ucontext_t #elif defined(HAVE_STRUCT_SIGCONTEXT) #define SIGNALCONTEXT struct sigcontext #else #define SIGNALCONTEXT void #endif #define MIN_HEAP_SIZE 4096 // Minimum and initial heap segment size (words) // This is the ML "thread identifier" object. The fields // are read and set by the ML code. class ThreadObject: public PolyObject { public: PolyWord threadRef; // Weak ref containing the address of the thread data. Not used by ML PolyWord flags; // Tagged integer containing flags indicating how interrupts // are handled. Set by ML but only by the thread itself PolyWord threadLocal; // Head of a list of thread-local store items. // Handled entirely by ML but only by the thread. PolyWord requestCopy; // A tagged integer copy of the "requests" field. // This is provided so that ML can easily test if there // is an interrupt pending. PolyWord mlStackSize; // A tagged integer with the maximum ML stack size in bytes PolyWord debuggerSlots[4]; // These are used by the debugger. }; // Other threads may make requests to a thread. typedef enum { kRequestNone = 0, // Increasing severity kRequestInterrupt = 1, kRequestKill = 2 } ThreadRequests; // Per-thread data. This is subclassed for each architecture. class TaskData { public: TaskData(); virtual ~TaskData(); void FillUnusedSpace(void); virtual void GarbageCollect(ScanAddress *process); virtual Handle EnterPolyCode() = 0; // Start running ML virtual void InterruptCode() = 0; virtual bool AddTimeProfileCount(SIGNALCONTEXT *context) = 0; // Initialise the stack for a new thread. The parent task object is passed in because any // allocation that needs to be made must be made in the parent. virtual void InitStackFrame(TaskData *parentTask, Handle proc, Handle arg) = 0; virtual void SetException(poly_exn *exc) = 0; // If a foreign function calls back to ML we need to set up the call to the // ML callback function. virtual Handle EnterCallbackFunction(Handle func, Handle args) = 0; // The scheduler needs versions of atomic decrement and atomic reset that // work in exactly the same way as the code-generated versions (if any). // Atomic decrement isn't needed since it only ever releases a mutex. virtual Handle AtomicDecrement(Handle mutexp) = 0; // Reset a mutex to one. This needs to be atomic with respect to the // atomic increment and decrement instructions. virtual void AtomicReset(Handle mutexp) = 0; virtual void CopyStackFrame(StackObject *old_stack, uintptr_t old_length, StackObject *new_stack, uintptr_t new_length) = 0; virtual uintptr_t currentStackSpace(void) const = 0; // Add a count to the local function if we are using store profiling. virtual void addProfileCount(POLYUNSIGNED words) = 0; // Functions called before and after an RTS call. virtual void PreRTSCall(void) { inML = false; } virtual void PostRTSCall(void) { inML = true; } SaveVec saveVec; PolyWord *allocPointer; // Allocation pointer - decremented towards... PolyWord *allocLimit; // ... lower limit of allocation uintptr_t allocSize; // The preferred heap segment size unsigned allocCount; // The number of allocations since the last GC StackSpace *stack; ThreadObject *threadObject; // Pointer to the thread object. int lastError; // Last error from foreign code. void *signalStack; // Stack to handle interrupts (Unix only) bool inML; // True when this is in ML, false in the RTS // Get a TaskData pointer given the ML taskId. // This is called at the start of every RTS function that may allocate memory. // It is can be called safely to get the thread's own TaskData object without // a lock but any call to get the TaskData for another thread must take the // schedLock first in case the thread is exiting. static TaskData *FindTaskForId(PolyObject *taskId) { return *(TaskData**)(((ThreadObject*)taskId)->threadRef.AsObjPtr()); } private: // If a thread has to block it will block on this. PCondVar threadLock; // External requests made are stored here until they // can be actioned. ThreadRequests requests; // Pointer to the mutex when blocked. Set to NULL when it doesn't apply. PolyObject *blockMutex; // This is set to false when a thread blocks or enters foreign code, // While it is true the thread can manipulate ML memory so no other // thread can garbage collect. bool inMLHeap; // In Linux, at least, we need to run a separate timer in each thread bool runningProfileTimer; #ifdef HAVE_WINDOWS_H LONGLONG lastCPUTime; // Used for profiling #endif public: bool threadExited; private: #ifdef HAVE_PTHREAD_H pthread_t threadId; #endif #ifdef HAVE_WINDOWS_H public: // Because, on Cygwin, it's used in NewThreadFunction HANDLE threadHandle; private: #endif friend class Processes; }; NORETURNFN(extern Handle exitThread(TaskData *mdTaskData)); class ScanAddress; // Indicate what the main thread is doing if the profile // timer goes off. extern enum _mainThreadPhase { MTP_USER_CODE=0, MTP_GCPHASESHARING, MTP_GCPHASEMARK, MTP_GCPHASECOMPACT, MTP_GCPHASEUPDATE, MTP_GCQUICK, MTP_SHARING, MTP_EXPORTING, MTP_SAVESTATE, MTP_LOADSTATE, MTP_PROFILING, MTP_SIGHANDLER, MTP_CYGWINSPAWN, MTP_STOREMODULE, MTP_LOADMODULE, MTP_MAXENTRY } mainThreadPhase; // Data structure used for requests from a thread to the root // thread. These are GCs or similar. class MainThreadRequest { public: MainThreadRequest (enum _mainThreadPhase phase): mtp(phase), completed(false) {} virtual ~MainThreadRequest () {} // Suppress silly GCC warning const enum _mainThreadPhase mtp; bool completed; virtual void Perform() = 0; }; class PLock; // Class to wait for a given time or for an event, whichever comes first. // // A pointer to this class or a subclass is passed to ThreadPauseForIO. // Because a thread may be interrupted or killed by another ML thread we // don't allow any thread to block indefinitely. Instead whenever a // thread wants to do an operation that may block we have it enter a // loop that polls for the desired condition and if it is not ready it // calls ThreadPauseForIO. The default action is to block for a short // period and then return so that the caller can poll again. That can // limit performance when, for example, reading from a pipe so where possible // we use a sub-class that waits until either input is available or it times // out, whichever comes first, using "select" in Unix or MsgWaitForMultipleObjects // in Windows. // During a call to Waiter::Wait the thread is set as "not using ML memory" // so a GC can happen while this thread is blocked. class Waiter { public: Waiter() {} virtual ~Waiter() {} virtual void Wait(unsigned maxMillisecs); static Waiter *defaultWaiter; }; #ifdef _WIN32 class WaitHandle: public Waiter { public: WaitHandle(HANDLE h): m_Handle(h) {} virtual void Wait(unsigned maxMillisecs); private: HANDLE m_Handle; }; #else // Unix: Wait until a file descriptor is available for input class WaitInputFD: public Waiter { public: WaitInputFD(int fd): m_waitFD(fd) {} virtual void Wait(unsigned maxMillisecs); private: int m_waitFD; }; #endif // External interface to the Process module. These functions are all implemented // by the Processes class. class ProcessExternal { public: virtual ~ProcessExternal() {} // Defined to suppress a warning from GCC virtual TaskData *GetTaskDataForThread(void) = 0; virtual TaskData *CreateNewTaskData(Handle threadId, Handle threadFunction, Handle args, PolyWord flags) = 0; // Request all ML threads to exit and set the result code. Does not cause // the calling thread itself to exit since this may be called on the GUI thread. virtual void RequestProcessExit(int n) = 0; // Exit from this thread. virtual NORETURNFN(void ThreadExit(TaskData *taskData)) = 0; virtual void BroadcastInterrupt(void) = 0; virtual void BeginRootThread(PolyObject *rootFunction) = 0; // Called when a thread may block. Returns some time later when perhaps // the input is available. virtual void ThreadPauseForIO(TaskData *taskData, Waiter *pWait) = 0; // As ThreadPauseForIO but when there is no stream virtual void ThreadPause(TaskData *taskData) { ThreadPauseForIO(taskData, Waiter::defaultWaiter); } // If a thread is blocking for some time it should release its use // of the ML memory. That allows a GC. ThreadUseMLMemory returns true if // a GC was in progress. virtual void ThreadUseMLMemory(TaskData *taskData) = 0; virtual void ThreadReleaseMLMemory(TaskData *taskData) = 0; // Requests from the threads for actions that need to be performed by // the root thread. virtual void MakeRootRequest(TaskData *taskData, MainThreadRequest *request) = 0; // Deal with any interrupt or kill requests. virtual bool ProcessAsynchRequests(TaskData *taskData) = 0; // Process an interrupt request synchronously. virtual void TestSynchronousRequests(TaskData *taskData) = 0; // Process any events, synchronous or asynchronous. virtual void TestAnyEvents(TaskData *taskData) = 0; // ForkFromRTS. Creates a new thread from within the RTS. virtual bool ForkFromRTS(TaskData *taskData, Handle proc, Handle arg) = 0; // Profiling control. virtual void StartProfiling(void) = 0; virtual void StopProfiling(void) = 0; // Find space for an object. Returns a pointer to the start. "words" must include // the length word and the result points at where the length word will go. // If the allocation succeeds it may update the allocation values in the taskData object. // If the heap is exhausted it may set this thread (or other threads) to raise an exception. virtual PolyWord *FindAllocationSpace(TaskData *taskData, POLYUNSIGNED words, bool alwaysInSeg) = 0; // Signal handling support. The ML signal handler thread blocks until it is // woken up by the signal detection thread. virtual bool WaitForSignal(TaskData *taskData, PLock *sigLock) = 0; virtual void SignalArrived(void) = 0; - // After a Unix fork we only have a single thread in the new process. - virtual void SetSingleThreaded(void) = 0; - virtual poly_exn* GetInterrupt(void) = 0; }; // Return the number of processors. Used when configuring multi-threaded GC. extern unsigned NumberOfProcessors(void); extern unsigned NumberOfPhysicalProcessors(void); extern ProcessExternal *processes; extern struct _entrypts processesEPT[]; #endif diff --git a/libpolyml/rts_module.cpp b/libpolyml/rts_module.cpp index 2631548c..834d3a80 100644 --- a/libpolyml/rts_module.cpp +++ b/libpolyml/rts_module.cpp @@ -1,79 +1,86 @@ /* Title: rts_module.cpp - Base class for the run-time system modules. - Copyright (c) 2006 David C.J. Matthews + Copyright (c) 2006, 2020 David C.J. Matthews This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_WIN32) #include "winconfig.h" #else #error "No configuration file" #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_ASSERT_H #include #define ASSERT(x) assert(x) #else #define ASSERT(x) #endif #include "rts_module.h" #define MAX_MODULES 30 static RtsModule *module_table[MAX_MODULES]; static unsigned modCount = 0; // Add a module to the table. This will be done during the static // initialisation phase. void RtsModule::RegisterModule(void) { ASSERT(modCount < MAX_MODULES); module_table[modCount++] = this; } void InitModules(void) { for(unsigned i = 0; i < modCount; i++) module_table[i]->Init(); } void StartModules(void) { for(unsigned i = 0; i < modCount; i++) module_table[i]->Start(); } void StopModules(void) { for(unsigned i = 0; i < modCount; i++) module_table[i]->Stop(); } void GCModules(ScanAddress *process) { for(unsigned i = 0; i < modCount; i++) module_table[i]->GarbageCollect(process); } + +// Called on Unix in the child process. +void ForkChildModules(void) +{ + for (unsigned i = 0; i < modCount; i++) + module_table[i]->ForkChild(); +} diff --git a/libpolyml/rts_module.h b/libpolyml/rts_module.h index 56db43c5..9dcc1209 100644 --- a/libpolyml/rts_module.h +++ b/libpolyml/rts_module.h @@ -1,47 +1,49 @@ /* Title: rts_module.h - Base class for the run-time system modules. - Copyright (c) 2006, 2011, 2016 David C.J. Matthews + Copyright (c) 2006, 2011, 2016, 2020 David C.J. Matthews This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef RTS_MODULE_H_INCLUDED #define RTS_MODULE_H_INCLUDED class ScanAddress; class TaskData; class RtsModule { public: RtsModule() { RegisterModule(); } virtual ~RtsModule() {} // To keep GCC happy virtual void Init(void) {} virtual void Start(void) {} virtual void Stop(void) {} virtual void GarbageCollect(ScanAddress * /*process*/) {} + virtual void ForkChild(void) {} // Called in the child process after a Unix fork. private: void RegisterModule(void); }; void InitModules(void); void StartModules(void); void StopModules(void); void GCModules(ScanAddress *process); +void ForkChildModules(void); #endif diff --git a/libpolyml/unix_specific.cpp b/libpolyml/unix_specific.cpp index 8ea21d9d..afa3bff2 100644 --- a/libpolyml/unix_specific.cpp +++ b/libpolyml/unix_specific.cpp @@ -1,2029 +1,2026 @@ /* Title: Operating Specific functions: Unix version. - Copyright (c) 2000-8, 2016-17, 2019 David C. J. Matthews + Copyright (c) 2000-8, 2016-17, 2019, 2020 David C. J. Matthews Portions of this code are derived from the original stream io package copyright CUTS 1983-2000. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_WIN32) #include "winconfig.h" #else #error "No configuration file" #endif #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_ASSERT_H #include #define ASSERT(x) assert(x) #else #define ASSERT(x) #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_SYS_SIGNAL_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_TERMIOS_H #include #elif (defined(HAVE_TERMIOS_H)) #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_UTSNAME_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #include "globals.h" #include "arb.h" #include "run_time.h" #include "io_internal.h" #include "sys.h" #include "diagnostics.h" #include "machine_dep.h" #include "os_specific.h" #include "gc.h" #include "processes.h" #include "mpoly.h" #include "sighandler.h" #include "polystring.h" #include "save_vec.h" #include "rts_module.h" #include "rtsentry.h" extern "C" { POLYEXTERNALSYMBOL POLYUNSIGNED PolyOSSpecificGeneral(PolyObject *threadId, PolyWord code, PolyWord arg); POLYEXTERNALSYMBOL POLYUNSIGNED PolyGetOSType(); } #define SAVE(x) taskData->saveVec.push(x) #define ALLOC(n) alloc_and_save(taskData, n) #define SIZEOF(x) (sizeof(x)/sizeof(PolyWord)) /* Table of constants returned by call 4. */ // This is currently unsigned because that's necessary on the PowerPC for // NOFLUSH. Perhaps there should be separate tables for different kinds // of constants. static unsigned unixConstVec[] = { /* Error codes. */ E2BIG, /* 0 */ EACCES, EAGAIN, EBADF, #ifdef EBADMSG /* This is not defined in FreeBSD. */ EBADMSG, #else 0, #endif EBUSY, #ifdef ECANCELED /* This is not defined in Linux. Perhaps someone knows how to spell "cancelled". */ ECANCELED, #else 0, /* Perhaps some other value. */ #endif ECHILD, EDEADLK, EDOM, EEXIST, EFAULT, EFBIG, EINPROGRESS, EINTR, EINVAL, EIO, EISDIR, ELOOP, EMFILE, EMLINK, /* 20 */ EMSGSIZE, ENAMETOOLONG, ENFILE, ENODEV, ENOENT, ENOEXEC, ENOLCK, ENOMEM, ENOSPC, ENOSYS, ENOTDIR, ENOTEMPTY, #ifdef ENOTSUP /* Not defined in Linux. */ ENOTSUP, #else 0, #endif ENOTTY, ENXIO, EPERM, EPIPE, ERANGE, EROFS, ESPIPE, ESRCH, EXDEV, /* 42 */ /* Signals. */ SIGABRT, /* 43 */ SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, SIGUSR1, SIGUSR2, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, /* 62 */ /* Open flags. */ O_RDONLY, /* 63 */ O_WRONLY, O_RDWR, O_APPEND, O_EXCL, O_NOCTTY, O_NONBLOCK, #ifdef O_SYNC O_SYNC, /* Not defined in FreeBSD. */ #else 0, #endif O_TRUNC, /* 71 */ /* TTY: Special characters. */ VEOF, /* 72 */ VEOL, VERASE, VINTR, VKILL, VMIN, VQUIT, VSUSP, VTIME, VSTART, VSTOP, NCCS, /* 83 */ /* TTY: Input mode. */ BRKINT, /* 84 */ ICRNL, IGNBRK, IGNCR, IGNPAR, INLCR, INPCK, ISTRIP, IXOFF, IXON, PARMRK, /* 94 */ /* TTY: Output mode. */ OPOST, /* 95 */ /* TTY: Control modes. */ CLOCAL, /* 96 */ CREAD, CS5, CS6, CS7, CS8, CSIZE, CSTOPB, HUPCL, PARENB, PARODD, /* 106 */ /* TTY: Local modes. */ ECHO, /* 107 */ ECHOE, ECHOK, ECHONL, ICANON, IEXTEN, ISIG, (unsigned)NOFLSH, TOSTOP, /* 115 */ /* TTY: Speeds. */ B0, /* 116 */ B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, /* 131 */ /* FD flags. */ FD_CLOEXEC, /* 132 */ /* Wait flags. */ WUNTRACED, /* 133 */ WNOHANG, /* 134 */ /* tcsetattr flags. */ TCSANOW, /* 135 */ TCSADRAIN, TCSAFLUSH, /* tcflow flags. */ TCOOFF, /* 138 */ TCOON, TCIOFF, TCION, /* tcflush flags. */ TCIFLUSH, /* 142 */ TCOFLUSH, TCIOFLUSH, /* File permissions. */ S_IRUSR, /* 145 */ S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, S_ISUID, S_ISGID, /* 155 */ /* Bits for access function. */ R_OK, /* 156 */ W_OK, X_OK, F_OK, /* 159 */ /* Values for lseek. */ SEEK_SET, /* 160 */ SEEK_CUR, SEEK_END, /* 162 */ /* Values for lock types. */ F_RDLCK, /* 163 */ F_WRLCK, F_UNLCK, /* 165 */ /* Mask for file access. */ O_ACCMODE, /* 166 */ }; /* Auxiliary functions which implement the more complex cases. */ static Handle waitForProcess(TaskData *taskData, Handle args); static Handle makePasswordEntry(TaskData *taskData, struct passwd *pw); static Handle makeGroupEntry(TaskData *taskData, struct group *grp); static Handle getUname(TaskData *taskData); static Handle getSysConf(TaskData *taskData, Handle args); static Handle getTTYattrs(TaskData *taskData, Handle args); static Handle setTTYattrs(TaskData *taskData, Handle args); static Handle getStatInfo(TaskData *taskData, struct stat *buf); static Handle lockCommand(TaskData *taskData, int cmd, Handle args); static int findPathVar(TaskData *taskData, PolyWord ps); // Unmask all signals just before exec. static void restoreSignals(void) { sigset_t sigset; sigemptyset(&sigset); sigprocmask(SIG_SETMASK, &sigset, NULL); } Handle OS_spec_dispatch_c(TaskData *taskData, Handle args, Handle code) { unsigned lastSigCount = receivedSignalCount; // Have we received a signal? int c = get_C_long(taskData, code->Word()); switch (c) { case 0: /* Return our OS type. Not in any structure. */ return Make_fixed_precision(taskData, 0); /* 0 for Unix. */ case 4: /* Return a constant. */ { unsigned i = get_C_unsigned(taskData, args->Word()); if (i >= sizeof(unixConstVec)/sizeof(unixConstVec[0])) raise_syscall(taskData, "Invalid index", 0); return Make_sysword(taskData, unixConstVec[i]); } case 5: /* fork. */ { pid_t pid = fork(); if (pid < 0) raise_syscall(taskData, "fork failed", errno); + // Have to clean up the RTS in the child. It's single threaded among other things. if (pid == 0) - { - // In the child process the only thread is this one. - processes->SetSingleThreaded(); - GCSetSingleThreadAfterFork(); - } + ForkChildModules(); return Make_fixed_precision(taskData, pid); } case 6: /* kill */ { int pid = get_C_long(taskData, DEREFHANDLE(args)->Get(0)); int sig = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (kill(pid, sig) < 0) raise_syscall(taskData, "kill failed", errno); return Make_fixed_precision(taskData, 0); } case 7: /* get process id */ { pid_t pid = getpid(); if (pid < 0) raise_syscall(taskData, "getpid failed", errno); return Make_fixed_precision(taskData, pid); } case 8: /* get process id of parent */ { pid_t pid = getppid(); if (pid < 0) raise_syscall(taskData, "getppid failed", errno); return Make_fixed_precision(taskData, pid); } case 9: /* get real user id */ { uid_t uid = getuid(); // This is defined always to succeed return Make_fixed_precision(taskData, uid); } case 10: /* get effective user id */ { uid_t uid = geteuid(); // This is defined always to succeed return Make_fixed_precision(taskData, uid); } case 11: /* get real group id */ { gid_t gid = getgid(); // This is defined always to succeed return Make_fixed_precision(taskData, gid); } case 12: /* get effective group id */ { gid_t gid = getegid(); // This is defined always to succeed return Make_fixed_precision(taskData, gid); } case 13: /* Return process group */ { pid_t pid = getpgrp(); if (pid < 0) raise_syscall(taskData, "getpgrp failed", errno); return Make_fixed_precision(taskData, pid); } case 14: /* Wait for child process to terminate. */ return waitForProcess(taskData, args); case 15: /* Unpack a process result. */ { int resType, resVal; Handle result, typeHandle, resHandle; int status = get_C_long(taskData, args->Word()); if (WIFEXITED(status)) { resType = 1; resVal = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { resType = 2; resVal = WTERMSIG(status); } else if (WIFSTOPPED(status)) { resType = 3; resVal = WSTOPSIG(status); } else { /* ?? */ resType = 0; resVal = 0; } typeHandle = Make_fixed_precision(taskData, resType); resHandle = Make_fixed_precision(taskData, resVal); result = ALLOC(2); DEREFHANDLE(result)->Set(0, typeHandle->Word()); DEREFHANDLE(result)->Set(1, resHandle->Word()); return result; } case 16: /* Pack up a process result. The inverse of the previous call. */ { int resType = get_C_long(taskData, DEREFHANDLE(args)->Get(0)); int resVal = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int result = 0; switch (resType) { case 1: /* Exited */ result = resVal << 8; break; case 2: /* Signalled */ result = resVal; break; case 3: /* Stopped */ result = (resVal << 8) | 0177; } return Make_fixed_precision(taskData, result); } case 17: /* Run a new executable. */ { char *path = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); char **argl = stringListToVector(SAVE(DEREFHANDLE(args)->Get(1))); int err; restoreSignals(); execv(path, argl); err = errno; /* We only get here if there's been an error. */ free(path); freeStringVector(argl); raise_syscall(taskData, "execv failed", err); } case 18: /* Run a new executable with given environment. */ { char *path = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); char **argl = stringListToVector(SAVE(DEREFHANDLE(args)->Get(1))); char **envl = stringListToVector(SAVE(DEREFHANDLE(args)->Get(2))); int err; restoreSignals(); execve(path, argl, envl); err = errno; /* We only get here if there's been an error. */ free(path); freeStringVector(argl); freeStringVector(envl); raise_syscall(taskData, "execve failed", err); } case 19: /* Run a new executable using PATH environment variable. */ { char *path = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); char **argl = stringListToVector(SAVE(DEREFHANDLE(args)->Get(1))); int err; restoreSignals(); execvp(path, argl); err = errno; /* We only get here if there's been an error. */ free(path); freeStringVector(argl); raise_syscall(taskData, "execvp failed", err); } case 20: /* Sets an alarm and returns the current alarm time. A value of zero for the time cancels the timer. */ { /* We have a value in microseconds. We need to split it into seconds and microseconds. */ Handle hTime = args; Handle hMillion = Make_arbitrary_precision(taskData, 1000000); struct itimerval newTimer, oldTimer; newTimer.it_interval.tv_sec = 0; newTimer.it_interval.tv_usec = 0; newTimer.it_value.tv_sec = get_C_long(taskData, div_longc(taskData, hMillion, hTime)->Word()); newTimer.it_value.tv_usec = get_C_long(taskData, rem_longc(taskData, hMillion, hTime)->Word()); if (setitimer(ITIMER_REAL, &newTimer, &oldTimer) != 0) raise_syscall(taskData, "setitimer failed", errno); Handle result = /* Return the previous setting. */ Make_arb_from_pair_scaled(taskData, oldTimer.it_value.tv_sec, oldTimer.it_value.tv_usec, 1000000); return result; } case 21: /* Pause until signal. */ /* This never returns. When a signal is handled it will be interrupted. */ while (true) { processes->ThreadPause(taskData); if (lastSigCount != receivedSignalCount) raise_syscall(taskData, "Call interrupted by signal", EINTR); } case 22: /* Sleep until given time or until a signal. Note: this is called with an absolute time as an argument and returns a relative time as result. This RTS call is tried repeatedly until either the time has expired or a signal has occurred. */ while (true) { struct timeval tv; /* We have a value in microseconds. We need to split it into seconds and microseconds. */ Handle hSave = taskData->saveVec.mark(); Handle hTime = args; Handle hMillion = Make_arbitrary_precision(taskData, 1000000); unsigned long secs = get_C_ulong(taskData, div_longc(taskData, hMillion, hTime)->Word()); unsigned long usecs = get_C_ulong(taskData, rem_longc(taskData, hMillion, hTime)->Word()); taskData->saveVec.reset(hSave); /* Has the time expired? */ if (gettimeofday(&tv, NULL) != 0) raise_syscall(taskData, "gettimeofday failed", errno); /* If the timeout time is earlier than the current time we must return, otherwise we block. This can be interrupted by a signal. */ if ((unsigned long)tv.tv_sec < secs || ((unsigned long)tv.tv_sec == secs && (unsigned long)tv.tv_usec < usecs)) { processes->ThreadPause(taskData); if (lastSigCount != receivedSignalCount) raise_syscall(taskData, "Call interrupted by signal", EINTR); // And loop } else { processes->TestAnyEvents(taskData); // Check for interrupts anyway return Make_fixed_precision(taskData, 0); } } case 23: /* Set uid. */ { uid_t uid = get_C_long(taskData, args->Word()); if (setuid(uid) != 0) raise_syscall(taskData, "setuid failed", errno); return Make_fixed_precision(taskData, 0); } case 24: /* Set gid. */ { gid_t gid = get_C_long(taskData, args->Word()); if (setgid(gid) != 0) raise_syscall(taskData, "setgid failed", errno); return Make_fixed_precision(taskData, 0); } case 25: /* Get group list. */ { // This previously allocated gid_t[NGROUPS_MAX] on the stack but this // requires quite a bit of stack space. gid_t gid[1]; int ngroups = getgroups(0, gid); // Just get the number. if (ngroups < 0) raise_syscall(taskData, "getgroups failed", errno); if (ngroups == 0) return SAVE(ListNull); gid_t *groups = (gid_t*)calloc(sizeof(gid_t), ngroups); if (groups == 0) raise_syscall(taskData, "Unable to allocate memory", errno); if (getgroups(ngroups, groups) < 0) { int lasterr = errno; free(groups); raise_syscall(taskData, "getgroups failed", lasterr); } Handle saved = taskData->saveVec.mark(); Handle list = SAVE(ListNull); /* It's simplest to process the integers in reverse order */ while (--ngroups >= 0) { Handle value = Make_fixed_precision(taskData, groups[ngroups]); Handle next = ALLOC(SIZEOF(ML_Cons_Cell)); DEREFLISTHANDLE(next)->h = value->Word(); DEREFLISTHANDLE(next)->t = list->Word(); taskData->saveVec.reset(saved); list = SAVE(next->Word()); } free(groups); return list; } case 26: /* Get login name. */ { char *login = getlogin(); if (login == 0) raise_syscall(taskData, "getlogin failed", errno); return SAVE(C_string_to_Poly(taskData, login)); } case 27: /* Set sid */ { pid_t pid = setsid(); if (pid < 0) raise_syscall(taskData, "setsid failed", errno); return Make_fixed_precision(taskData, pid); } case 28: /* Set process group. */ { pid_t pid = get_C_long(taskData, DEREFHANDLE(args)->Get(0)); pid_t pgid = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (setpgid(pid, pgid) < 0 ) raise_syscall(taskData, "setpgid failed", errno); return Make_fixed_precision(taskData, 0); } case 29: /* uname */ return getUname(taskData); case 30: /* Get controlling terminal. */ #ifdef HAVE_CTERMID { char *term = ctermid(0); /* Can this generate an error? */ if (term == 0) raise_syscall(taskData, "ctermid failed", errno); return SAVE(C_string_to_Poly(taskData, term)); } #else raise_syscall(taskData, "ctermid is not implemented", 0); #endif case 31: /* Get terminal name for file descriptor. */ { char *term = ttyname(getStreamFileDescriptor(taskData, args->Word())); if (term == 0) raise_syscall(taskData, "ttyname failed", errno); return SAVE(C_string_to_Poly(taskData, term)); } case 32: /* Test if file descriptor is a terminal. Returns false if the stream is closed. */ { int descr = getStreamFileDescriptorWithoutCheck(args->Word()); if (descr != -1 && isatty(descr)) return Make_fixed_precision(taskData, 1); else return Make_fixed_precision(taskData, 0); } case 33: /* sysconf. */ return getSysConf(taskData, args); /* Filesys entries. */ case 50: /* Set the file creation mask and return the old one. */ { mode_t mode = get_C_ulong(taskData, args->Word()); return Make_fixed_precision(taskData, umask(mode)); } case 51: /* Create a hard link. */ { char *old = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); char *newp = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(1)); int err, res; res = link(old, newp); err = errno; /* Save the error result in case free changes it. */ free(old); free(newp); if (res < 0) raise_syscall(taskData, "link failed", err); return Make_fixed_precision(taskData, 0); } case 52: /* Create a directory. There is an OS-independent version in basicio which uses a default creation mode. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); mode_t mode = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int err, res; res = mkdir(name, mode); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "mkdir failed", err); return Make_fixed_precision(taskData, 0); } case 53: /* Create a fifo. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); mode_t mode = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int err, res; res = mkfifo(name, mode); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "mkfifo failed", err); return Make_fixed_precision(taskData, 0); } case 54: /* Create a symbolic link. */ { char *old = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); char *newp = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(1)); int err, res; res = symlink(old, newp); err = errno; /* Save the error result in case free changes it. */ free(old); free(newp); if (res < 0) raise_syscall(taskData, "link failed", err); return Make_fixed_precision(taskData, 0); } case 55: /* Get information about a file. */ { struct stat buf; int res, err; char *name = Poly_string_to_C_alloc(DEREFWORD(args)); res = stat(name, &buf); err = errno; free(name); if (res < 0) raise_syscall(taskData, "stat failed", err); return getStatInfo(taskData, &buf); } case 56: /* Get information about a symbolic link. */ { struct stat buf; int res, err; char *name = Poly_string_to_C_alloc(DEREFWORD(args)); res = lstat(name, &buf); err = errno; free(name); if (res < 0) raise_syscall(taskData, "lstat failed", err); return getStatInfo(taskData, &buf); } case 57: /* Get information about an open file. */ { struct stat buf; if (fstat(getStreamFileDescriptor(taskData, args->Word()), &buf) < 0) raise_syscall(taskData, "fstat failed", errno); return getStatInfo(taskData, &buf); } case 58: /* Test access rights to a file. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); int amode = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int res; res = access(name, amode); free(name); /* Return false if error, true if not. It's not clear that this is correct since there are several reasons why we might get -1 as the result. */ return Make_fixed_precision(taskData, res < 0 ? 0 : 1); } case 59: /* Change access rights. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); mode_t mode = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int err, res; res = chmod(name, mode); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "chmod failed", err); return Make_fixed_precision(taskData, 0); } case 60: /* Change access rights on open file. */ { mode_t mode = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (fchmod(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), mode) < 0) raise_syscall(taskData, "fchmod failed", errno); return Make_fixed_precision(taskData, 0); } case 61: /* Change owner and group. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); uid_t uid = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); gid_t gid = get_C_long(taskData, DEREFHANDLE(args)->Get(2)); int err, res; res = chown(name, uid, gid); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "chown failed", err); return Make_fixed_precision(taskData, 0); } case 62: /* Change owner and group on open file. */ { uid_t uid = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); gid_t gid = get_C_long(taskData, DEREFHANDLE(args)->Get(2)); if (fchown(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), uid, gid) < 0) raise_syscall(taskData, "fchown failed", errno); return Make_fixed_precision(taskData, 0); } case 63: /* Set access and modification times. We use utimes rather than utime since it allows us to be more accurate. There's a similar function in basicio which sets both the access and modification times to the same time. */ { char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); Handle hAccess = SAVE(DEREFHANDLE(args)->Get(1)); Handle hMod = SAVE(DEREFHANDLE(args)->Get(2)); struct timeval times[2]; /* We have a value in microseconds. We need to split it into seconds and microseconds. N.B. The arguments to div_longc and rem_longc are in reverse order. */ Handle hMillion = Make_arbitrary_precision(taskData, 1000000); unsigned secsAccess = get_C_ulong(taskData, div_longc(taskData, hMillion, hAccess)->Word()); unsigned usecsAccess = get_C_ulong(taskData, rem_longc(taskData, hMillion, hAccess)->Word()); unsigned secsMod = get_C_ulong(taskData, div_longc(taskData, hMillion, hMod)->Word()); unsigned usecsMod = get_C_ulong(taskData, rem_longc(taskData, hMillion, hMod)->Word()); int err, res; times[0].tv_sec = secsAccess; times[0].tv_usec = usecsAccess; times[1].tv_sec = secsMod; times[1].tv_usec = usecsMod; res = utimes(name, times); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "utimes failed", err); return Make_fixed_precision(taskData, 0); } case 64: /* Set access and modification times to the current time. This could be defined in terms of the previous call and Time.now but it could result in an error due to rounding. This is probably safer. */ { char *name = Poly_string_to_C_alloc(DEREFWORD(args)); int err, res; res = utimes(name, 0); err = errno; /* Save the error result in case free changes it. */ free(name); if (res < 0) raise_syscall(taskData, "utimes failed", err); return Make_fixed_precision(taskData, 0); } case 65: /* Truncate an open file. */ { int size = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (ftruncate(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), size) < 0) raise_syscall(taskData, "ftruncate failed", errno); return Make_fixed_precision(taskData, 0); } case 66: /* Get the configured limits for a file. */ { /* Look up the variable. May raise an exception. */ int nvar = findPathVar(taskData, DEREFHANDLE(args)->Get(1)); char *name = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); int err, res; /* Set errno to zero. If there is no limit pathconf returns -1 but does not change errno. */ errno = 0; res = pathconf(name, nvar); err = errno; /* Save the error result in case free changes it. */ free(name); /* We return -1 as a valid result indicating no limit. */ if (res < 0 && err != 0) raise_syscall(taskData, "pathconf failed", err); return Make_fixed_precision(taskData, res); } case 67: /* Get the configured limits for an open file. */ { /* Look up the variable. May raise an exception. */ int nvar = findPathVar(taskData, DEREFHANDLE(args)->Get(1)); errno = 0; /* Unchanged if there is no limit. */ int res = fpathconf(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), nvar); if (res < 0 && errno != 0) raise_syscall(taskData, "fpathconf failed", errno); return Make_fixed_precision(taskData, res); } /* Password and group entries. */ case 100: /* Get Password entry by name. */ { char pwName[200]; int length; struct passwd *pw; length = Poly_string_to_C(DEREFWORD(args), pwName, 200); if (length > 200) raise_syscall(taskData, "Password name too long", ENAMETOOLONG); pw = getpwnam(pwName); if (pw == NULL) raise_syscall(taskData, "Password entry not found", ENOENT); return makePasswordEntry(taskData, pw); } case 101: /* Get password entry by uid. */ { int uid = get_C_long(taskData, DEREFWORD(args)); struct passwd *pw = getpwuid(uid); if (pw == NULL) raise_syscall(taskData, "Password entry not found", ENOENT); return makePasswordEntry(taskData, pw); } case 102: /* Get group entry by name. */ { struct group *grp; char grpName[200]; int length; length = Poly_string_to_C(DEREFWORD(args), grpName, 200); if (length > 200) raise_syscall(taskData, "Group name too long", ENAMETOOLONG); grp = getgrnam(grpName); if (grp == NULL) raise_syscall(taskData, "Group entry not found", ENOENT); return makeGroupEntry(taskData, grp); } case 103: /* Get group entry by gid. */ { int gid = get_C_long(taskData, DEREFWORD(args)); struct group *grp = getgrgid(gid); if (grp == NULL) raise_syscall(taskData, "Group entry not found", ENOENT); return makeGroupEntry(taskData, grp); } /* IO Entries. */ case 110: /* Create a pipe. */ { int filedes[2]; if (pipe(filedes) < 0) raise_syscall(taskData, "pipe failed", errno); Handle strRead = wrapFileDescriptor(taskData, filedes[0]); Handle strWrite = wrapFileDescriptor(taskData, filedes[1]); Handle result = ALLOC(2); DEREFHANDLE(result)->Set(0, strRead->Word()); DEREFHANDLE(result)->Set(1, strWrite->Word()); return result; } case 111: /* Duplicate a file descriptor. */ { int srcFd = getStreamFileDescriptor(taskData, args->WordP()); int fd = dup(srcFd); if (fd < 0) raise_syscall(taskData, "dup failed", errno); return wrapFileDescriptor(taskData, fd); } case 112: /* Duplicate a file descriptor to a given entry. */ { int oldFd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)); int newFd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(1)); if (dup2(oldFd, newFd) < 0) raise_syscall(taskData, "dup2 failed", errno); return Make_fixed_precision(taskData, 0); } case 113: /* Duplicate a file descriptor to an entry equal to or greater than the given value. */ { int oldFd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)); int baseFd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(1)); int newFd = fcntl(oldFd, F_DUPFD, baseFd); return wrapFileDescriptor(taskData, newFd); } case 114: /* Get the file descriptor flags. */ { int res = fcntl(getStreamFileDescriptor(taskData, args->Word()), F_GETFD); if (res < 0) raise_syscall(taskData, "fcntl failed", errno); return Make_fixed_precision(taskData, res); } case 115: /* Set the file descriptor flags. */ { int flags = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (fcntl(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), F_SETFD, flags) < 0) raise_syscall(taskData, "fcntl failed", errno); return Make_fixed_precision(taskData, 0); } case 116: /* Get the file status and access flags. */ { int res = fcntl(getStreamFileDescriptor(taskData, args->Word()), F_GETFL); if (res < 0) raise_syscall(taskData, "fcntl failed", errno); return Make_fixed_precision(taskData, res); } case 117: /* Set the file status and access flags. */ { int flags = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (fcntl(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), F_SETFL, flags) < 0) raise_syscall(taskData, "fcntl failed", errno); return Make_fixed_precision(taskData, 0); } case 118: /* Seek to a position on the stream. */ { long position = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int whence = get_C_long(taskData, DEREFHANDLE(args)->Get(2)); long newpos = lseek(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), position, whence); if (newpos < 0) raise_syscall(taskData, "lseek failed", errno); return Make_arbitrary_precision(taskData, (POLYSIGNED)newpos); // Position.int } case 119: /* Synchronise file contents. */ { if (fsync(getStreamFileDescriptor(taskData, args->Word())) < 0) raise_syscall(taskData, "fsync failed", errno); return Make_fixed_precision(taskData, 0); } case 120: /* get lock */ return lockCommand(taskData, F_GETLK, args); case 121: /* set lock */ return lockCommand(taskData, F_SETLK, args); case 122: /* wait for lock */ /* TODO: This may well block the whole process. We should look at the result and retry if need be. */ return lockCommand(taskData, F_SETLKW, args); /* TTY entries. */ case 150: /* Get attributes. */ return getTTYattrs(taskData, args); case 151: /* Set attributes. */ return setTTYattrs(taskData, args); case 152: /* Send a break. */ { int duration = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (tcsendbreak(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), duration) < 0) raise_syscall(taskData, "tcsendbreak failed", errno); return Make_fixed_precision(taskData, 0); } case 153: /* Wait for output to drain. */ { /* TODO: This will block the process. It really needs to check whether the stream has drained and run another process until it has. */ #ifdef HAVE_TCDRAIN if (tcdrain(getStreamFileDescriptor(taskData, args->Word())) < 0) raise_syscall(taskData, "tcdrain failed", errno); #else raise_syscall(taskData, "tcdrain is not implemented", 0); #endif return Make_fixed_precision(taskData, 0); } case 154: /* Flush terminal stream. */ { int qs = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (tcflush(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), qs) < 0) raise_syscall(taskData, "tcflush failed", errno); return Make_fixed_precision(taskData, 0); } case 155: /* Flow control. */ { int action = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (tcflow(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), action) < 0) raise_syscall(taskData, "tcflow failed", errno); return Make_fixed_precision(taskData, 0); } case 156: /* Get process group. */ { pid_t pid = tcgetpgrp(getStreamFileDescriptor(taskData, args->Word())); if (pid < 0) raise_syscall(taskData, "tcgetpgrp failed", errno); return Make_fixed_precision(taskData, pid); } case 157: /* Set process group. */ { pid_t pid = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); if (tcsetpgrp(getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)), pid) < 0) raise_syscall(taskData, "tcsetpgrp failed", errno); return Make_fixed_precision(taskData, 0); } default: { char msg[100]; sprintf(msg, "Unknown unix-specific function: %d", c); raise_exception_string(taskData, EXC_Fail, msg); } } } // General interface to Unix OS-specific. Ideally the various cases will be made into // separate functions. POLYUNSIGNED PolyOSSpecificGeneral(PolyObject *threadId, PolyWord code, PolyWord arg) { TaskData *taskData = TaskData::FindTaskForId(threadId); ASSERT(taskData != 0); taskData->PreRTSCall(); Handle reset = taskData->saveVec.mark(); Handle pushedCode = taskData->saveVec.push(code); Handle pushedArg = taskData->saveVec.push(arg); Handle result = 0; try { result = OS_spec_dispatch_c(taskData, pushedArg, pushedCode); } catch (...) { } // If an ML exception is raised taskData->saveVec.reset(reset); // Ensure the save vec is reset taskData->PostRTSCall(); if (result == 0) return TAGGED(0).AsUnsigned(); else return result->Word().AsUnsigned(); } POLYUNSIGNED PolyGetOSType() { return TAGGED(0).AsUnsigned(); // Return 0 for Unix } Handle waitForProcess(TaskData *taskData, Handle args) /* Get result status of a child process. */ { TryAgain: // We should check for interrupts even if we're not going to block. processes->TestAnyEvents(taskData); int kind = get_C_long(taskData, DEREFHANDLE(args)->Get(0)); int pid = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); int callFlags = get_C_long(taskData, DEREFHANDLE(args)->Get(2)); int flags = callFlags | WNOHANG; // Add in WNOHANG so we never block. pid_t pres = 0; int status = 0; switch (kind) { case 0: /* Wait for any child. */ pres = waitpid(-1, &status, flags); break; case 1: /* Wait for specific process. */ pres = waitpid(pid, &status, flags); break; case 2: /* Wait for any in current process group. */ pres = waitpid(0, &status, flags); break; case 3: /* Wait for child in given process group */ pres = waitpid(-pid, &status, flags); break; } if (pres < 0) { if (errno == EINTR) goto TryAgain; else raise_syscall(taskData, "wait failed", errno); } /* If the caller did not specify WNOHANG but there wasn't a child process waiting we have to block and come back here later. */ if (pres == 0 && !(callFlags & WNOHANG)) { processes->ThreadPause(taskData); goto TryAgain; } /* Construct the result tuple. */ { Handle result, pidHandle, resHandle; pidHandle = Make_fixed_precision(taskData, pres); // If the pid is zero status may not be a valid value and may overflow. resHandle = Make_fixed_precision(taskData, pres == 0 ? 0: status); result = ALLOC(2); DEREFHANDLE(result)->Set(0, DEREFWORD(pidHandle)); DEREFHANDLE(result)->Set(1, DEREFWORD(resHandle)); return result; } } static Handle makePasswordEntry(TaskData *taskData, struct passwd *pw) /* Return a password entry. */ { Handle nameHandle, uidHandle, gidHandle, homeHandle, shellHandle, result; nameHandle = SAVE(C_string_to_Poly(taskData, pw->pw_name)); uidHandle = Make_fixed_precision(taskData, pw->pw_uid); gidHandle = Make_fixed_precision(taskData, pw->pw_gid); homeHandle = SAVE(C_string_to_Poly(taskData, pw->pw_dir)); shellHandle = SAVE(C_string_to_Poly(taskData, pw->pw_shell)); result = ALLOC(5); DEREFHANDLE(result)->Set(0, nameHandle->Word()); DEREFHANDLE(result)->Set(1, uidHandle->Word()); DEREFHANDLE(result)->Set(2, gidHandle->Word()); DEREFHANDLE(result)->Set(3, homeHandle->Word()); DEREFHANDLE(result)->Set(4, shellHandle->Word()); return result; } static Handle makeGroupEntry(TaskData *taskData, struct group *grp) { Handle nameHandle, gidHandle, membersHandle, result; int i; char **p; nameHandle = SAVE(C_string_to_Poly(taskData, grp->gr_name)); gidHandle = Make_fixed_precision(taskData, grp->gr_gid); /* Group members. */ for (i=0, p = grp->gr_mem; *p != NULL; p++, i++); membersHandle = convert_string_list(taskData, i, grp->gr_mem); result = ALLOC(3); DEREFHANDLE(result)->Set(0, nameHandle->Word()); DEREFHANDLE(result)->Set(1, gidHandle->Word()); DEREFHANDLE(result)->Set(2, membersHandle->Word()); return result; } /* Make a cons cell for a pair of strings. */ // Doesn't currently reset the save vec so it's only safe for a small number // of cells. static void makeStringPairList(TaskData *taskData, Handle &list, const char *s1, const char *s2) { Handle nameHandle, valueHandle, pairHandle, next; /* This has to be done carefully to ensure we don't throw anything away if we garbage-collect and also to ensure that each object is fully initialised before the next object is created. */ /* Make the strings. */ nameHandle = SAVE(C_string_to_Poly(taskData, s1)); valueHandle = SAVE(C_string_to_Poly(taskData, s2)); /* Make the pair. */ pairHandle = ALLOC(2); DEREFHANDLE(pairHandle)->Set(0, nameHandle->Word()); DEREFHANDLE(pairHandle)->Set(1, valueHandle->Word()); /* Make the cons cell. */ next = ALLOC(SIZEOF(ML_Cons_Cell)); DEREFLISTHANDLE(next)->h = pairHandle->Word(); DEREFLISTHANDLE(next)->t = list->Word(); list = SAVE(next->Word()); } /* Return the uname information. */ static Handle getUname(TaskData *taskData) { #ifdef HAVE_SYS_UTSNAME_H struct utsname name; Handle list = SAVE(ListNull); if (uname(&name) < 0) raise_syscall(taskData, "uname failed", errno); makeStringPairList(taskData, list, "sysname", name.sysname); makeStringPairList(taskData, list, "nodename", name.nodename); makeStringPairList(taskData, list, "release", name.release); makeStringPairList(taskData, list, "version", name.version); makeStringPairList(taskData, list, "machine", name.machine); return list; #else raise_syscall(taskData, "uname not available on this machine", errno); #endif } /* Return the contents of a stat buffer. */ static Handle getStatInfo(TaskData *taskData, struct stat *buf) { int kind; /* Get the protection mode, masking off the file type info. */ Handle modeHandle = Make_fixed_precision(taskData, buf->st_mode & (S_IRWXU|S_IRWXG|S_IRWXO|S_ISUID|S_ISGID)); if (S_ISDIR(buf->st_mode)) kind = 1; else if (S_ISCHR(buf->st_mode)) kind = 2; else if (S_ISBLK(buf->st_mode)) kind = 3; else if (S_ISFIFO(buf->st_mode)) kind = 4; else if ((buf->st_mode & S_IFMT) == S_IFLNK) kind = 5; else if ((buf->st_mode & S_IFMT) == S_IFSOCK) kind = 6; else /* Regular. */ kind = 0; Handle kindHandle = Make_fixed_precision(taskData, kind); Handle inoHandle = Make_arbitrary_precision(taskData, buf->st_ino); Handle devHandle = Make_arbitrary_precision(taskData, buf->st_dev); Handle linkHandle = Make_fixed_precision(taskData, buf->st_nlink); Handle uidHandle = Make_fixed_precision(taskData, buf->st_uid); Handle gidHandle = Make_fixed_precision(taskData, buf->st_gid); Handle sizeHandle = Make_arbitrary_precision(taskData, buf->st_size); // Position.int Handle atimeHandle = Make_arb_from_pair_scaled(taskData, STAT_SECS(buf,a), STAT_USECS(buf,a), 1000000); Handle mtimeHandle = Make_arb_from_pair_scaled(taskData, STAT_SECS(buf,m), STAT_USECS(buf,m), 1000000); Handle ctimeHandle = Make_arb_from_pair_scaled(taskData, STAT_SECS(buf,c), STAT_USECS(buf,c), 1000000); Handle result = ALLOC(11); DEREFHANDLE(result)->Set(0, modeHandle->Word()); DEREFHANDLE(result)->Set(1, kindHandle->Word()); DEREFHANDLE(result)->Set(2, inoHandle->Word()); DEREFHANDLE(result)->Set(3, devHandle->Word()); DEREFHANDLE(result)->Set(4, linkHandle->Word()); DEREFHANDLE(result)->Set(5, uidHandle->Word()); DEREFHANDLE(result)->Set(6, gidHandle->Word()); DEREFHANDLE(result)->Set(7, sizeHandle->Word()); DEREFHANDLE(result)->Set(8, atimeHandle->Word()); DEREFHANDLE(result)->Set(9, mtimeHandle->Word()); DEREFHANDLE(result)->Set(10, ctimeHandle->Word()); return result; } static Handle getTTYattrs(TaskData *taskData, Handle args) { int fd = getStreamFileDescriptor(taskData, args->Word()); struct termios tios; speed_t ispeed, ospeed; Handle ifHandle, ofHandle, cfHandle, lfHandle, ccHandle; Handle isHandle, osHandle, result; if (tcgetattr(fd, &tios) < 0) raise_syscall(taskData, "tcgetattr failed", errno); /* Extract the speed entries. */ ospeed = cfgetospeed(&tios); ispeed = cfgetispeed(&tios); /* Set the speed entries to zero. In Solaris, at least, the speed is encoded in the flags and we don't want any confusion. The order of these functions is significant. */ cfsetospeed(&tios, B0); cfsetispeed(&tios, B0); /* Convert the values to ML representation. */ ifHandle = Make_fixed_precision(taskData, tios.c_iflag); ofHandle = Make_fixed_precision(taskData, tios.c_oflag); cfHandle = Make_fixed_precision(taskData, tios.c_cflag); lfHandle = Make_fixed_precision(taskData, tios.c_lflag); /* The cc vector is treated as a string. */ ccHandle = SAVE(C_string_to_Poly(taskData, (const char *)tios.c_cc, NCCS)); isHandle = Make_fixed_precision(taskData, ispeed); osHandle = Make_fixed_precision(taskData, ospeed); /* We can now create the result tuple. */ result = ALLOC(7); DEREFHANDLE(result)->Set(0, ifHandle->Word()); DEREFHANDLE(result)->Set(1, ofHandle->Word()); DEREFHANDLE(result)->Set(2, cfHandle->Word()); DEREFHANDLE(result)->Set(3, lfHandle->Word()); DEREFHANDLE(result)->Set(4, ccHandle->Word()); DEREFHANDLE(result)->Set(5, isHandle->Word()); DEREFHANDLE(result)->Set(6, osHandle->Word()); return result; } /* Assemble the tios structure from the arguments and set the TTY attributes. */ static Handle setTTYattrs(TaskData *taskData, Handle args) { int fd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)); int actions = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); struct termios tios; speed_t ispeed, ospeed; /* Make sure anything unset is zero. It might be better to call tcgetattr instead. */ memset(&tios, 0, sizeof(tios)); tios.c_iflag = get_C_ulong(taskData, DEREFHANDLE(args)->Get(2)); tios.c_oflag = get_C_ulong(taskData, DEREFHANDLE(args)->Get(3)); tios.c_cflag = get_C_ulong(taskData, DEREFHANDLE(args)->Get(4)); tios.c_lflag = get_C_ulong(taskData, DEREFHANDLE(args)->Get(5)); /* The cc vector should be a string of exactly NCCS characters. It may well contain nulls so we can't use Poly_string_to_C to copy it. */ PolyWord ccv = DEREFHANDLE(args)->Get(6); if (ccv.IsTagged()) // Just to check. raise_syscall(taskData, "Incorrect cc vector", EINVAL); PolyStringObject * ccvs = (PolyStringObject *)ccv.AsObjPtr(); if (ccvs->length != NCCS) // Just to check. */ raise_syscall(taskData, "Incorrect cc vector", EINVAL); memcpy(tios.c_cc, ccvs->chars, NCCS); ispeed = get_C_ulong(taskData, DEREFHANDLE(args)->Get(7)); ospeed = get_C_ulong(taskData, DEREFHANDLE(args)->Get(8)); if (cfsetispeed(&tios, ispeed) < 0) raise_syscall(taskData, "cfsetispeed failed", errno); if (cfsetospeed(&tios, ospeed) < 0) raise_syscall(taskData, "cfsetospeed failed", errno); /* Now it's all set we can call tcsetattr to do the work. */ if (tcsetattr(fd, actions, &tios) < 0) raise_syscall(taskData, "tcsetattr failed", errno); return Make_fixed_precision(taskData, 0); } /* Lock/unlock/test file locks. Returns the, possibly modified, argument structure. */ static Handle lockCommand(TaskData *taskData, int cmd, Handle args) { int fd = getStreamFileDescriptor(taskData, DEREFHANDLE(args)->Get(0)); struct flock lock; memset(&lock, 0, sizeof(lock)); /* Make sure unused fields are zero. */ lock.l_type = get_C_long(taskData, DEREFHANDLE(args)->Get(1)); lock.l_whence = get_C_long(taskData, DEREFHANDLE(args)->Get(2)); lock.l_start = get_C_long(taskData, DEREFHANDLE(args)->Get(3)); lock.l_len = get_C_long(taskData, DEREFHANDLE(args)->Get(4)); lock.l_pid = get_C_long(taskData, DEREFHANDLE(args)->Get(5)); if (fcntl(fd, cmd, &lock) < 0) raise_syscall(taskData, "fcntl failed", errno); /* Construct the result. */ Handle typeHandle = Make_fixed_precision(taskData, lock.l_type); Handle whenceHandle = Make_fixed_precision(taskData, lock.l_whence); Handle startHandle = Make_arbitrary_precision(taskData, (POLYUNSIGNED)lock.l_start); // Position.int Handle lenHandle = Make_arbitrary_precision(taskData, (POLYUNSIGNED)lock.l_len); // Position.int Handle pidHandle = Make_fixed_precision(taskData, lock.l_pid); Handle result = ALLOC(5); DEREFHANDLE(result)->Set(0, typeHandle->Word()); DEREFHANDLE(result)->Set(1, whenceHandle->Word()); DEREFHANDLE(result)->Set(2, startHandle->Word()); DEREFHANDLE(result)->Set(3, lenHandle->Word()); DEREFHANDLE(result)->Set(4, pidHandle->Word()); return result; } /* This table maps string arguments for sysconf into the corresponding constants. */ /* These are highly OS dependent. It has been configured on Solaris 2.8, Linux Redhat 5.2 and FreeBSD 3.4. */ static struct { const char *saName; int saVal; } sysArgTable[] = { { "_SC_ARG_MAX", _SC_ARG_MAX }, { "_SC_CHILD_MAX", _SC_CHILD_MAX }, { "_SC_CLK_TCK", _SC_CLK_TCK }, { "_SC_NGROUPS_MAX", _SC_NGROUPS_MAX }, { "_SC_OPEN_MAX", _SC_OPEN_MAX }, { "_SC_JOB_CONTROL", _SC_JOB_CONTROL }, { "_SC_SAVED_IDS", _SC_SAVED_IDS }, { "_SC_VERSION", _SC_VERSION }, #ifdef _SC_PASS_MAX { "_SC_PASS_MAX", _SC_PASS_MAX }, #endif #ifdef _SC_LOGNAME_MAX { "_SC_LOGNAME_MAX", _SC_LOGNAME_MAX }, #endif #ifdef _SC_PAGESIZE { "_SC_PAGESIZE", _SC_PAGESIZE }, #endif #ifdef _SC_XOPEN_VERSION { "_SC_XOPEN_VERSION", _SC_XOPEN_VERSION }, #endif #ifdef _SC_NPROCESSORS_CONF { "_SC_NPROCESSORS_CONF", _SC_NPROCESSORS_CONF }, #endif #ifdef _SC_NPROCESSORS_ONLN { "_SC_NPROCESSORS_ONLN", _SC_NPROCESSORS_ONLN }, #endif #ifdef _SC_STREAM_MAX { "_SC_STREAM_MAX", _SC_STREAM_MAX }, #endif #ifdef _SC_TZNAME_MAX { "_SC_TZNAME_MAX", _SC_TZNAME_MAX }, #endif #ifdef _SC_AIO_LISTIO_MAX { "_SC_AIO_LISTIO_MAX", _SC_AIO_LISTIO_MAX }, #endif #ifdef _SC_AIO_MAX { "_SC_AIO_MAX", _SC_AIO_MAX }, #endif #ifdef _SC_AIO_PRIO_DELTA_MAX { "_SC_AIO_PRIO_DELTA_MAX", _SC_AIO_PRIO_DELTA_MAX }, #endif #ifdef _SC_ASYNCHRONOUS_IO { "_SC_ASYNCHRONOUS_IO", _SC_ASYNCHRONOUS_IO }, #endif #ifdef _SC_DELAYTIMER_MAX { "_SC_DELAYTIMER_MAX", _SC_DELAYTIMER_MAX }, #endif #ifdef _SC_FSYNC { "_SC_FSYNC", _SC_FSYNC }, #endif #ifdef _SC_MAPPED_FILES { "_SC_MAPPED_FILES", _SC_MAPPED_FILES }, #endif #ifdef _SC_MEMLOCK { "_SC_MEMLOCK", _SC_MEMLOCK }, #endif #ifdef _SC_MEMLOCK_RANGE { "_SC_MEMLOCK_RANGE", _SC_MEMLOCK_RANGE }, #endif #ifdef _SC_MEMORY_PROTECTION { "_SC_MEMORY_PROTECTION", _SC_MEMORY_PROTECTION }, #endif #ifdef _SC_MESSAGE_PASSING { "_SC_MESSAGE_PASSING", _SC_MESSAGE_PASSING }, #endif #ifdef _SC_MQ_OPEN_MAX { "_SC_MQ_OPEN_MAX", _SC_MQ_OPEN_MAX }, #endif #ifdef _SC_MQ_PRIO_MAX { "_SC_MQ_PRIO_MAX", _SC_MQ_PRIO_MAX }, #endif #ifdef _SC_PRIORITIZED_IO { "_SC_PRIORITIZED_IO", _SC_PRIORITIZED_IO }, #endif #ifdef _SC_PRIORITY_SCHEDULING { "_SC_PRIORITY_SCHEDULING", _SC_PRIORITY_SCHEDULING }, #endif #ifdef _SC_REALTIME_SIGNALS { "_SC_REALTIME_SIGNALS", _SC_REALTIME_SIGNALS }, #endif #ifdef _SC_RTSIG_MAX { "_SC_RTSIG_MAX", _SC_RTSIG_MAX }, #endif #ifdef _SC_SEMAPHORES { "_SC_SEMAPHORES", _SC_SEMAPHORES }, #endif #ifdef _SC_SEM_NSEMS_MAX { "_SC_SEM_NSEMS_MAX", _SC_SEM_NSEMS_MAX }, #endif #ifdef _SC_SEM_VALUE_MAX { "_SC_SEM_VALUE_MAX", _SC_SEM_VALUE_MAX }, #endif #ifdef _SC_SHARED_MEMORY_OBJECTS { "_SC_SHARED_MEMORY_OBJECTS", _SC_SHARED_MEMORY_OBJECTS }, #endif #ifdef _SC_SIGQUEUE_MAX { "_SC_SIGQUEUE_MAX", _SC_SIGQUEUE_MAX }, #endif #ifdef _SC_SIGRT_MIN { "_SC_SIGRT_MIN", _SC_SIGRT_MIN }, #endif #ifdef _SC_SIGRT_MAX { "_SC_SIGRT_MAX", _SC_SIGRT_MAX }, #endif #ifdef _SC_SYNCHRONIZED_IO { "_SC_SYNCHRONIZED_IO", _SC_SYNCHRONIZED_IO }, #endif #ifdef _SC_TIMERS { "_SC_TIMERS", _SC_TIMERS }, #endif #ifdef _SC_TIMER_MAX { "_SC_TIMER_MAX", _SC_TIMER_MAX }, #endif #ifdef _SC_2_C_BIND { "_SC_2_C_BIND", _SC_2_C_BIND }, #endif #ifdef _SC_2_C_DEV { "_SC_2_C_DEV", _SC_2_C_DEV }, #endif #ifdef _SC_2_C_VERSION { "_SC_2_C_VERSION", _SC_2_C_VERSION }, #endif #ifdef _SC_2_FORT_DEV { "_SC_2_FORT_DEV", _SC_2_FORT_DEV }, #endif #ifdef _SC_2_FORT_RUN { "_SC_2_FORT_RUN", _SC_2_FORT_RUN }, #endif #ifdef _SC_2_LOCALEDEF { "_SC_2_LOCALEDEF", _SC_2_LOCALEDEF }, #endif #ifdef _SC_2_SW_DEV { "_SC_2_SW_DEV", _SC_2_SW_DEV }, #endif #ifdef _SC_2_UPE { "_SC_2_UPE", _SC_2_UPE }, #endif #ifdef _SC_2_VERSION { "_SC_2_VERSION", _SC_2_VERSION }, #endif #ifdef _SC_BC_BASE_MAX { "_SC_BC_BASE_MAX", _SC_BC_BASE_MAX }, #endif #ifdef _SC_BC_DIM_MAX { "_SC_BC_DIM_MAX", _SC_BC_DIM_MAX }, #endif #ifdef _SC_BC_SCALE_MAX { "_SC_BC_SCALE_MAX", _SC_BC_SCALE_MAX }, #endif #ifdef _SC_BC_STRING_MAX { "_SC_BC_STRING_MAX", _SC_BC_STRING_MAX }, #endif #ifdef _SC_COLL_WEIGHTS_MAX { "_SC_COLL_WEIGHTS_MAX", _SC_COLL_WEIGHTS_MAX }, #endif #ifdef _SC_EXPR_NEST_MAX { "_SC_EXPR_NEST_MAX", _SC_EXPR_NEST_MAX }, #endif #ifdef _SC_LINE_MAX { "_SC_LINE_MAX", _SC_LINE_MAX }, #endif #ifdef _SC_RE_DUP_MAX { "_SC_RE_DUP_MAX", _SC_RE_DUP_MAX }, #endif #ifdef _SC_XOPEN_CRYPT { "_SC_XOPEN_CRYPT", _SC_XOPEN_CRYPT }, #endif #ifdef _SC_XOPEN_ENH_I18N { "_SC_XOPEN_ENH_I18N", _SC_XOPEN_ENH_I18N }, #endif #ifdef _SC_XOPEN_SHM { "_SC_XOPEN_SHM", _SC_XOPEN_SHM }, #endif #ifdef _SC_2_CHAR_TERM { "_SC_2_CHAR_TERM", _SC_2_CHAR_TERM }, #endif #ifdef _SC_XOPEN_XCU_VERSION { "_SC_XOPEN_XCU_VERSION", _SC_XOPEN_XCU_VERSION }, #endif #ifdef _SC_ATEXIT_MAX { "_SC_ATEXIT_MAX", _SC_ATEXIT_MAX }, #endif #ifdef _SC_IOV_MAX { "_SC_IOV_MAX", _SC_IOV_MAX }, #endif #ifdef _SC_XOPEN_UNIX { "_SC_XOPEN_UNIX", _SC_XOPEN_UNIX }, #endif #ifdef _SC_PAGE_SIZE { "_SC_PAGE_SIZE", _SC_PAGE_SIZE }, #endif #ifdef _SC_T_IOV_MAX { "_SC_T_IOV_MAX", _SC_T_IOV_MAX }, #endif #ifdef _SC_PHYS_PAGES { "_SC_PHYS_PAGES", _SC_PHYS_PAGES }, #endif #ifdef _SC_AVPHYS_PAGES { "_SC_AVPHYS_PAGES", _SC_AVPHYS_PAGES }, #endif #ifdef _SC_COHER_BLKSZ { "_SC_COHER_BLKSZ", _SC_COHER_BLKSZ }, #endif #ifdef _SC_SPLIT_CACHE { "_SC_SPLIT_CACHE", _SC_SPLIT_CACHE }, #endif #ifdef _SC_ICACHE_SZ { "_SC_ICACHE_SZ", _SC_ICACHE_SZ }, #endif #ifdef _SC_DCACHE_SZ { "_SC_DCACHE_SZ", _SC_DCACHE_SZ }, #endif #ifdef _SC_ICACHE_LINESZ { "_SC_ICACHE_LINESZ", _SC_ICACHE_LINESZ }, #endif #ifdef _SC_DCACHE_LINESZ { "_SC_DCACHE_LINESZ", _SC_DCACHE_LINESZ }, #endif #ifdef _SC_ICACHE_BLKSZ { "_SC_ICACHE_BLKSZ", _SC_ICACHE_BLKSZ }, #endif #ifdef _SC_DCACHE_BLKSZ { "_SC_DCACHE_BLKSZ", _SC_DCACHE_BLKSZ }, #endif #ifdef _SC_DCACHE_TBLKSZ { "_SC_DCACHE_TBLKSZ", _SC_DCACHE_TBLKSZ }, #endif #ifdef _SC_ICACHE_ASSOC { "_SC_ICACHE_ASSOC", _SC_ICACHE_ASSOC }, #endif #ifdef _SC_DCACHE_ASSOC { "_SC_DCACHE_ASSOC", _SC_DCACHE_ASSOC }, #endif #ifdef _SC_MAXPID { "_SC_MAXPID", _SC_MAXPID }, #endif #ifdef _SC_STACK_PROT { "_SC_STACK_PROT", _SC_STACK_PROT }, #endif #ifdef _SC_THREAD_DESTRUCTOR_ITERATIONS { "_SC_THREAD_DESTRUCTOR_ITERATIONS", _SC_THREAD_DESTRUCTOR_ITERATIONS }, #endif #ifdef _SC_GETGR_R_SIZE_MAX { "_SC_GETGR_R_SIZE_MAX", _SC_GETGR_R_SIZE_MAX }, #endif #ifdef _SC_GETPW_R_SIZE_MAX { "_SC_GETPW_R_SIZE_MAX", _SC_GETPW_R_SIZE_MAX }, #endif #ifdef _SC_LOGIN_NAME_MAX { "_SC_LOGIN_NAME_MAX", _SC_LOGIN_NAME_MAX }, #endif #ifdef _SC_THREAD_KEYS_MAX { "_SC_THREAD_KEYS_MAX", _SC_THREAD_KEYS_MAX }, #endif #ifdef _SC_THREAD_STACK_MI { "_SC_THREAD_STACK_MIN", _SC_THREAD_STACK_MIN }, #endif #ifdef _SC_THREAD_THREADS_MAX { "_SC_THREAD_THREADS_MAX", _SC_THREAD_THREADS_MAX }, #endif #ifdef _SC_THREAD_ATTR_STACKADDR { "_SC_THREAD_ATTR_STACKADDR", _SC_THREAD_ATTR_STACKADDR }, #endif #ifdef _SC_THREAD_ATTR_STACKSIZE { "_SC_THREAD_ATTR_STACKSIZE", _SC_THREAD_ATTR_STACKSIZE }, #endif #ifdef _SC_THREAD_PRIORITY_SCHEDULING { "_SC_THREAD_PRIORITY_SCHEDULING", _SC_THREAD_PRIORITY_SCHEDULING }, #endif #ifdef _SC_THREAD_PRIO_INHERIT { "_SC_THREAD_PRIO_INHERIT", _SC_THREAD_PRIO_INHERIT }, #endif #ifdef _SC_THREAD_PRIO_PROTECT { "_SC_THREAD_PRIO_PROTECT", _SC_THREAD_PRIO_PROTECT }, #endif #ifdef _SC_THREAD_PROCESS_SHARED { "_SC_THREAD_PROCESS_SHARED", _SC_THREAD_PROCESS_SHARED }, #endif #ifdef _SC_XOPEN_LEGACY { "_SC_XOPEN_LEGACY", _SC_XOPEN_LEGACY }, #endif #ifdef _SC_XOPEN_REALTIME { "_SC_XOPEN_REALTIME", _SC_XOPEN_REALTIME }, #endif #ifdef _SC_XOPEN_REALTIME_THREADS { "_SC_XOPEN_REALTIME_THREADS", _SC_XOPEN_REALTIME_THREADS }, #endif #ifdef _SC_XBS5_ILP32_OFF32 { "_SC_XBS5_ILP32_OFF32", _SC_XBS5_ILP32_OFF32 }, #endif #ifdef _SC_XBS5_ILP32_OFFBIG { "_SC_XBS5_ILP32_OFFBIG", _SC_XBS5_ILP32_OFFBIG }, #endif #ifdef _SC_XBS5_LP64_OFF64 { "_SC_XBS5_LP64_OFF64", _SC_XBS5_LP64_OFF64 }, #endif #ifdef _SC_XBS5_LPBIG_OFFBIG { "_SC_XBS5_LPBIG_OFFBIG", _SC_XBS5_LPBIG_OFFBIG }, #endif #ifdef _SC_EQUIV_CLASS_MAX { "_SC_EQUIV_CLASS_MAX", _SC_EQUIV_CLASS_MAX }, #endif #ifdef _SC_CHARCLASS_NAME_MAX { "_SC_CHARCLASS_NAME_MAX", _SC_CHARCLASS_NAME_MAX }, #endif #ifdef _SC_PII { "_SC_PII", _SC_PII }, #endif #ifdef _SC_PII_XTI { "_SC_PII_XTI", _SC_PII_XTI }, #endif #ifdef _SC_PII_SOCKET { "_SC_PII_SOCKET", _SC_PII_SOCKET }, #endif #ifdef _SC_PII_INTERNET { "_SC_PII_INTERNET", _SC_PII_INTERNET }, #endif #ifdef _SC_PII_OSI { "_SC_PII_OSI", _SC_PII_OSI }, #endif #ifdef _SC_POLL { "_SC_POLL", _SC_POLL }, #endif #ifdef _SC_SELECT { "_SC_SELECT", _SC_SELECT }, #endif #ifdef _SC_UIO_MAXIOV { "_SC_UIO_MAXIOV", _SC_UIO_MAXIOV }, #endif #ifdef _SC_PII_INTERNET_STREAM { "_SC_PII_INTERNET_STREAM", _SC_PII_INTERNET_STREAM }, #endif #ifdef _SC_PII_INTERNET_DGRAM { "_SC_PII_INTERNET_DGRAM", _SC_PII_INTERNET_DGRAM }, #endif #ifdef _SC_PII_OSI_COTS { "_SC_PII_OSI_COTS", _SC_PII_OSI_COTS }, #endif #ifdef _SC_PII_OSI_CLTS { "_SC_PII_OSI_CLTS", _SC_PII_OSI_CLTS }, #endif #ifdef _SC_PII_OSI_M { "_SC_PII_OSI_M", _SC_PII_OSI_M }, #endif #ifdef _SC_T_IOV_MAX { "_SC_T_IOV_MAX", _SC_T_IOV_MAX }, #endif #ifdef _SC_THREADS { "_SC_THREADS", _SC_THREADS }, #endif #ifdef _SC_THREAD_SAFE_FUNCTIONS { "_SC_THREAD_SAFE_FUNCTIONS", _SC_THREAD_SAFE_FUNCTIONS }, #endif #ifdef _SC_TTY_NAME_MAX { "_SC_TTY_NAME_MAX", _SC_TTY_NAME_MAX }, #endif #ifdef _SC_XOPEN_XPG2 { "_SC_XOPEN_XPG2", _SC_XOPEN_XPG2 }, #endif #ifdef _SC_XOPEN_XPG3 { "_SC_XOPEN_XPG3", _SC_XOPEN_XPG3 }, #endif #ifdef _SC_XOPEN_XPG4 { "_SC_XOPEN_XPG4", _SC_XOPEN_XPG4 }, #endif #ifdef _SC_CHAR_BIT { "_SC_CHAR_BIT", _SC_CHAR_BIT }, #endif #ifdef _SC_CHAR_MAX { "_SC_CHAR_MAX", _SC_CHAR_MAX }, #endif #ifdef _SC_CHAR_MIN { "_SC_CHAR_MIN", _SC_CHAR_MIN }, #endif #ifdef _SC_INT_MAX { "_SC_INT_MAX", _SC_INT_MAX }, #endif #ifdef _SC_INT_MIN { "_SC_INT_MIN", _SC_INT_MIN }, #endif #ifdef _SC_LONG_BIT { "_SC_LONG_BIT", _SC_LONG_BIT }, #endif #ifdef _SC_WORD_BIT { "_SC_WORD_BIT", _SC_WORD_BIT }, #endif #ifdef _SC_MB_LEN_MAX { "_SC_MB_LEN_MAX", _SC_MB_LEN_MAX }, #endif #ifdef _SC_NZERO { "_SC_NZERO", _SC_NZERO }, #endif #ifdef _SC_SSIZE_MAX { "_SC_SSIZE_MAX", _SC_SSIZE_MAX }, #endif #ifdef _SC_SCHAR_MAX { "_SC_SCHAR_MAX", _SC_SCHAR_MAX }, #endif #ifdef _SC_SCHAR_MIN { "_SC_SCHAR_MIN", _SC_SCHAR_MIN }, #endif #ifdef _SC_SHRT_MAX { "_SC_SHRT_MAX", _SC_SHRT_MAX }, #endif #ifdef _SC_SHRT_MIN { "_SC_SHRT_MIN", _SC_SHRT_MIN }, #endif #ifdef _SC_UCHAR_MAX { "_SC_UCHAR_MAX", _SC_UCHAR_MAX }, #endif #ifdef _SC_UINT_MAX { "_SC_UINT_MAX", _SC_UINT_MAX }, #endif #ifdef _SC_ULONG_MAX { "_SC_ULONG_MAX", _SC_ULONG_MAX }, #endif #ifdef _SC_USHRT_MAX { "_SC_USHRT_MAX", _SC_USHRT_MAX }, #endif #ifdef _SC_NL_ARGMAX { "_SC_NL_ARGMAX", _SC_NL_ARGMAX }, #endif #ifdef _SC_NL_LANGMAX { "_SC_NL_LANGMAX", _SC_NL_LANGMAX }, #endif #ifdef _SC_NL_MSGMAX { "_SC_NL_MSGMAX", _SC_NL_MSGMAX }, #endif #ifdef _SC_NL_NMAX { "_SC_NL_NMAX", _SC_NL_NMAX }, #endif #ifdef _SC_NL_SETMAX { "_SC_NL_SETMAX", _SC_NL_SETMAX }, #endif }; static Handle getSysConf(TaskData *taskData, Handle args) { char argName[200]; int length; unsigned i; long res; length = Poly_string_to_C(DEREFWORD(args), argName, 200); if (length > 200) raise_syscall(taskData, "Argument name too long", ENAMETOOLONG); for (i = 0; i < sizeof(sysArgTable)/sizeof(sysArgTable[0]); i++) { if (strcmp(argName, sysArgTable[i].saName) == 0) break; /* See if it matches without the _SC_ at the beginning. */ if (strcmp(argName, sysArgTable[i].saName+4) == 0) break; } if (i == sizeof(sysArgTable)/sizeof(sysArgTable[0])) raise_syscall(taskData, "sysconf argument not found", EINVAL); errno = 0; /* Sysconf may return -1 without updating errno. */ res = sysconf(sysArgTable[i].saVal); if (res < 0) raise_syscall(taskData, "sysconf failed", errno); return Make_fixed_precision(taskData, (POLYUNSIGNED)res); } static struct { const char *pcName; int pcVal; } pathConfTable[] = { { "_PC_LINK_MAX", _PC_LINK_MAX }, { "_PC_MAX_CANON", _PC_MAX_CANON }, { "_PC_MAX_INPUT", _PC_MAX_INPUT }, { "_PC_NAME_MAX", _PC_NAME_MAX }, { "_PC_PATH_MAX", _PC_PATH_MAX }, { "_PC_PIPE_BUF", _PC_PIPE_BUF }, { "_PC_NO_TRUNC", _PC_NO_TRUNC }, { "_PC_VDISABLE", _PC_VDISABLE }, { "_PC_CHOWN_RESTRICTED", _PC_CHOWN_RESTRICTED }, #ifdef _PC_ASYNC_IO { "_PC_ASYNC_IO", _PC_ASYNC_IO }, #endif #ifdef _PC_PRIO_IO { "_PC_PRIO_IO", _PC_PRIO_IO }, #endif #ifdef _PC_SYNC_IO { "_PC_SYNC_IO", _PC_SYNC_IO }, #endif #ifdef _PC_FILESIZEBITS { "_PC_FILESIZEBITS", _PC_FILESIZEBITS }, #endif #ifdef _PC_SOCK_MAXBUF { "_PC_SOCK_MAXBUF", _PC_SOCK_MAXBUF }, #endif }; /* Look up a path variable in the table. */ static int findPathVar(TaskData *taskData, PolyWord ps) { char argName[200]; int length; unsigned i; length = Poly_string_to_C(ps, argName, 200); if (length > 200) raise_syscall(taskData, "Argument name too long", ENAMETOOLONG); for (i = 0; i < sizeof(pathConfTable)/sizeof(pathConfTable[0]); i++) { if (strcmp(argName, pathConfTable[i].pcName) == 0) return pathConfTable[i].pcVal; /* See if it matches without the _PC_ at the beginning. */ if (strcmp(argName, pathConfTable[i].pcName+4) == 0) return pathConfTable[i].pcVal; } raise_syscall(taskData, "pathconf argument not found", EINVAL); } struct _entrypts osSpecificEPT[] = { { "PolyGetOSType", (polyRTSFunction)&PolyGetOSType}, { "PolyOSSpecificGeneral", (polyRTSFunction)&PolyOSSpecificGeneral}, { NULL, NULL} // End of list. }; class UnixSpecific: public RtsModule { public: virtual void Init(void); }; // Declare this. It will be automatically added to the table. static UnixSpecific unixModule; void UnixSpecific::Init(void) { struct sigaction sigcatch; /* Ignore SIGPIPE - return any errors as failure to write. */ memset(&sigcatch, 0, sizeof(sigcatch)); sigcatch.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sigcatch, NULL); }