TODO.Win32 Introduction. one thing that I thought of today that would be helpful irrespective of what else happens, though, is something of a TODO.win32 Haven't seen much feedback yet... * salex is both busy and ignorant of things windows, so can't help :( That might be good. listing firstly things that don't work and your ideas for how to deal with them * l_a_m_ (n=a_a@che33-2-82-225-176-169.fbx.proxad.net) has joined #lisp and secondly points of friction between the unix POV and the win32 POV, and how one might either abstract them or deal with them individually (I appreciate that just documenting this is an unbounded amount of work, but some kind of guide would almost certainly help the ravening hordes of potential win32 programmers) What doesn't work? 1.) [fixed] FFI. We have an alien-funcall mechanism which can call both stdcall and cdecl functions, and dynamic symbol resolution (not the best on the latter, but servicable). 2.) [fixed] Contribs have been tested. They don't all work. 3.) [moderate] Sb-bsd-sockets definately won't work. To fix this we need working FFI. Using either Winsock or the "native" NT socket APIs will provide us with HANDLEs. To turn these into fds for fd-streams / serve-event, use _open_osfhandle(). 4.) [split] This was "signal" handling. Some work has been done, the rest is outlined below (4a - 4e). 4a.) [easy] Exception handling is minimally stubbed out at this point. Unhandled exceptions are forwarded to a lisp function which is expected to turn the exception into a Lisp error. That it does, but the errors aren't very useful right now, and should probably be broken down by exception type and tied into the existing sigfpe handler if appropriate. 4b.) [moderate] Stack unwinding in the presence of foreign code with exception handlers is broken. The semantics of win32 stack unwinding are actually fairly well specified, so the problem is more one of interfacing to that. 4c.) [moderate] The semantics of win32 stack unwinding require that foreign code be able to ask for lisp stack frames be unwound. We don't even check for the request right now. 4d.) [insane] If a lisp-side handler declines to handle an exception, we should have the runtime re-signal it somehow so that foreign exception handlers can take a crack at it. For more fun, nested lisp and foreign signal handlers should take turns deciding if they want to handle the exception. Making this work may require changing the method used to reflect various exceptions into lisp. 4e.) [moderate] "Blockable" signals (specifically SIGINT, aka SetConsoleCtrlHandler) are presently unhandled. The handler routine for SetConsoleCtrlHandler typically executes in a different thread context than the main thread (unless it doesn't, which we may want to check for at runtime), so we will need to emulate blockable signals by means of the windows threading API. This is a prerequisite for working lisp threads, as the same mechansim should be used to emulate SIG_STOP_FOR_GC and friends. 5.) [insane] Threading doesn't work. The Win32 threading model is very different from the linux model, particularly when it comes to what we do for TLS slots. Pre-allocating TLS slots in Win32 is possible in the .exe format, but we need an extra indirection for our own TLS arrays. Or we could do what some other lisps do and dedicate EDI to point to our TLS array. For more ideas on how to attack this, see the next section. 6.) [insane] It is literally impossible to reserve a specific memory range on a per-executable basis. There are various mechanisms which can and will be used to swipe our memory spaces. We will get bitten by this eventualy. The only real fix is to use relocatable cores. 7.) [fixed] This was filesystem stuff. There may be more things to do here, such as UNC filepaths, but they're not critical. 8.) [fixed] This was backtrace support. The patch may not be in CVS yet. 9.) [moderate] A lot of the local-environment functionality (hostname, init files, etc.) is disabled. Much of this will be a matter of figuring out what the right thing is and making it happen. 10.) [moderate] Some of the transcendental / math functions are unimplemented. I don't know what's involved in fixing them. 11.) [moderate] FFI again. Alien-funcall-fastcall. Up to three function arguments get passed in registers. Making alien- funcall-stdcall work was hard enough, and that was just changing who pops function arguments. The compiler gurus tell me that this is [moderate], so I downrated it from [insane]. Not many APIs use fastcall, so this isn't urgent. 12.) [moderate] The select() implementation is a bit of a hack. Most uses of select() only operate on one fd, and should probably be changed to use WaitForSingleObject(). The main use of select() is from serve-event. We probably should tie serve-event into a windows message loop so that we can have GUI apps running while the REPL still works. 13.) [moderate] Window procedures will require stdcall callbacks. Both stdcall and cdecl callbacks will require some careful work to support threading and exception handling properly. 14.) [moderate] The TIME macro causes a type error by receiving a NIL value where it expected some sort of number. Looking at the source, the NIL value is coming from the function sb-sys:get-system-info, which is in win32-os.lisp. This function normally uses sb-unix:unix-fast-getrusage, but is stubbed out because unix-fast-getrusage is unimplemented. Other parts of the machinery behind TIME also use sb-unix:unix-fast-getrusage, so that's probably the right place for a fix. 15.) [moderate] Many windows APIs either take or return UTF-16 strings (or their slightly more messed-up cousin, BSTRs). This will be useful as the FFI starts becoming usable, and required once people start wanting to use COM APIs. One place to start looking for ideas on how to implement this would be %naturalize-utf8-string and friends in src/code/target-c-call.lisp. How does Win32 differ from POSIX systems, and Linux in particular? You've all heard the scary warnings about how different Win32 is. I'm going to tell you that it is both not as bad as you've heard and far, far worse. I'm only going to cover the lower levels of the system here; the requirements for GUI programming support are another matter entirely. First of all, MSVCRT hides a lot of differences. It provides an fd-based interface to files and other HANDLE-based objects. It contains a number of wrappers to provide semi-posix interfaces for things like stat(), and the main changes for filesystem type things are the lack of symlinks, the addition of drives, that backslashes are the default directory separator (although you can use slashes, most of the information functions will return backslashes), and so on. So that's the good news. The bad news is that MapViewOfFile() doesn't have nearly the granularity of control that mmap() does, nor can you mix its memory areas with those managed with the Virtual* family of memory management functions. The good news about VirtualAlloc() and friends is that they let you reserve a large address space without actually committing memory to it. No more heinous memory overcommit. The bad news about this, however, is that some functions will check to make sure that pointers they are passed point to committed memory instead of just accessing it and taking a fault (which would allow our fault handler to come in and commit a memory page to that space). Just something to keep in mind. I mentioned far, far worse earlier. That would be both the complete inability to reserve memory spaces before your program is loaded and the fact that the windows approach to exception handling is completely at odds with posix signal handling. Windows, historically, has considered any memory space loaded from an executable or DLL file to be relocatable, and not without reason. Originally, Windows was a 16-bit system, and relocating a 16-bit segment is fairly simple; you move the bits around and you update the segment base in the descriptor table. With Win32 they moved to a flat memory model, and declared that any executable or DLL file segment, code or otherwise, without relocation information deserved to lose. This is especially evident when it comes to DLLs, as requiring everyone to pick non-conflicting base addresses for their DLLs all across the board is a non-starter, but it also holds true for executables as well, as was tested when Win95 was released and used the address space that earlier NT executables were mapped to for the Win16 memory space. The upshot of the philosophy of relocatable memory is that one literally cannot reserve a given memory range for oneself. Even without the moral equivalent of LD_PRELOAD (which exists somewhere), you still have the ability to start a program as if you were a debugger, load your own DLLs into the memory space, or just use the Virtual* family of functions for a bit, and then set the program going. This will bite us at some point unless we figure out some way around it. Symbol resolution for dynamic linking requires a handle to the DLL being linked to. This is in contrast to the posix dlsym(), which will search all loaded libraries. Win32, like VMS before it, uses an exception handling system based on a linked list of records describing an exception handler. When an exception occurs, the system begins walking the list of handlers looking for one which can handle the exception. While the system searches, it bounds-checks each link in the list against the allocated stack space for the current thread (this is important later). If an exception handler is not found, the system uses a default "kill the program and let the user sort the bodies" exception handler. Many of the things which posix considers a singal (USR1, for example) have no mapping to Win32 exceptions. When an exception occurs within an exception handler, the system merely searches the exception list again. One upshot of the bounds-check when searching the list of exception handlers is that it is impossible to safely switch stacks without using the NT-only Fiber functions, which allocate their own stacks to switch to. This is because when you make an API call, the API will tend to push a new exception frame on the front of the list while they verify their parameters. If there is a reserved-but-not-commited memory page at the other end of that buffer pointer, things go boom. Finally, Win32 is slightly thread-happy. Not so bad as BeOS was, but you will find all sorts of callbacks being called from random threads. For example, right now if you hit C-c when SBCL is running in a console window, SBCL dies. There is a function called SetConsoleCtrlHandler() that will trap the C-c and invoke a callback when it occurs. That callback is invoked in another thread, apparently one created specifically for the callback, although for all I know the system could have a pool of such threads. This sort of thing happens all over the place. And, while we're on the subject of threads, SBCL threading uses the FS register to point to a TLS (thread local storage) area. This requires the ability to manipulate descriptors in the local or global descriptor table. Windows doesn't provide read or write access to either descriptor table. Windows also uses the FS register to point to its own TLS area. This area has something like 64 TLS slots for application use which are allocated with the TlsAlloc() function. The upside here is that, unlike memory spaces, the initial allocation map for TLS slots is in the executable file. So, to support threading, we can reserve a small handful of windows TLS slots for things like allocation pointers and a pointer to the Lisp TLS slots (for symbol values). We wrap the GC with an EnterCriticalSection() / LeaveCriticalSection() pair, use SuspendThread() on all of the Lisp threads, then we can use the debugging API call GetThreadContext() to read out their register set... We don't even need SIG_STOP_FOR_GC. This shouldn't take a sufficiently motivated wizard more than a month or so to do, even with the compiler hacking involved. What Do We Need to Know in order to Make GUI Apps? When we actually get to trying to write GUI apps on Win32 things aren't nearly so bad. The basic gotchas are that windows have thread affinity, and that you have to use MsgWaitFor{Single, Multiple}Object[s]() instead of the non-Msg-prefix versions if you want to keep handling window messages in your thread. Unlike CLX, the Win32 windowing system relies on callbacks for event notification, but the main message loop is typically under program control. We probably should build it into serve-event at some point. There's far more to making robust GUI apps than just this, however. The sort of stunts used in frameworks like MFC to get their hooks into the message loop for dialog boxes are beyond the scope of this document. EOF