[MLton] Cygwin->Mingw32: patch + future

Wesley W. Terpstra terpstra@gkec.tu-darmstadt.de
Wed, 17 Nov 2004 17:54:17 +0100

Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I built a cygwin->mingw32 mlton cross-compiler last night, and it works!
However, there were a couple changes needed in main/main.fun, I've attached
the (small) patch to 20041109 that I used.

There were only two issues:
1. With gcc-3.4, the '-b <arch>' must be the first argument.
2. When using cygwin to compile native windows applications via their
included mingw compiler, the option to use is '-mno-cygwin' instead.
(I don't know why they did this, but there you have it)

In general,  I think most of the CC-controlling options should be moved
into a config file somewhere. Some people might want to use 'icc' for
example, or under windows one of those other non-free compilers. Also, the
gcc command-line api is always changing (and recompiling MLton is brutal!). 
I'm not planning on doing this.

There remains an outstanding problem where a win32 gui application (ie: not
a console application that always opens a terminal) quits immediately. The
console version opens a window via mGTK with no trouble, but the moment I
add -link-opt "-mwindows", the problem appears.

Has anyone else seen this behaviour?

As to future work, I am going to add kill() and pipe() functions ti
MLton under mingw. I'll mail it here once they're written and tested.

Brent Fulgham mentioned that pipe()s are "not the same". CreatePipe appears
to give two HANDLEs. Similarly, CreateProcess takes three HANDLEs. However,
after scouring the net I have discovered _open_osfhandle and _get_osfhandle
which allow conversion between normal file descriptors and HANDLEs. (Win98+)

However, I've seen previous posts about how a full posix-emulation is
probably more trouble than it is worth. I agree. Therefore, I propose to
implement the Unix.* methods. Those are more portable and almost ok.

I see that MLton.Process.spawn already exists.

Unfortunately, this is not enough to write Unix.* since there is no
provision for connecting std{in,out}. Furthermore, I believe
that the SML Basis Library's {Windows,Unix}.execute is buggy.

There are two problems:
1. There is no provision to connect stderr
2. There is no provision to NOT connect std{in,out}
  a. This leads to deadlock when used with child processes that stream
     data (eg: grep, gzip, ...) -- especially when there is buffering
  b. Remember: no poll/select on pipes --> you can't avoid deadlock on win32
     (unless you emulate it with threads -- like gtk+ does (!!!) )
  c. it would be safer to attach some of the {in,out}put to a file

I propose to add in MLTON_PROCESS (or maybe MLTON_CHILD?):

exception RedirectionFailure of OS.syserror
exception ProcessAlreadyReaped

datatype iosource = NULL | INHERIT | PIPE | FILE of string
type ('a, 'b, 'c) proc

(* when no env is supplied, defaults to parent environment
 * when no stdio is supplied, defaults to parent stdio
 *  i.e. (INHERIT, INHERIT, INHERIT) --> beware of stdin races
val create: {
    path: string, args: string list, searchPath: bool,
    env: string list option, stdio: iosource option 
  } -> ('a, 'b, 'c) proc

(* wait for the process to complete and return the exit status
 * -- beware of deadlock if the child-process excepts piped input
 * will raise ProcessAlreadyReaped if the process is reap/kill'd twice
val reap : ('a, 'b, 'c) proc -> OS.Process.status
(* forcibly terminate the process and 'reap' it as quickly as possible *)
val kill : ('a, 'b, 'c) proc -> unit

(* Retrieves the input/output from a process if the corresponding component
 * was constructed with PIPE. 
val textStdinOf:  (TextIO.instream option, 'b, 'c) proc -> TextIO.instream option
val textStdoutOf: ('a, TextIO.outstream option, 'c) proc -> TextIO.outstream option
val textStderrOf: ('a, 'b, TextIO.outstream option) proc -> TextIO.outstream option
val binStdinOf:  (BinIO.instream option, 'b, 'c) proc -> BinIO.instream option
val binStdoutOf: ('a, BinIO.outstream option, 'c) proc -> BinIO.outstream option
val binStderrOf: ('a, 'b, BinIO.outstream option) proc -> BinIO.outstream option

The underlying syscall will be called mlton_create_process. 
It will accept file descriptors to attach to std{in,out,err}.
The return value will be the new 'pid' (under windows a HANDLE---those
things seem to be used for everything: files, processes, windows, ...)

Kill will accept a 'pid' and use real kill under linux as it does now.
Inside cygwin.c the die-stub will be replaced by TerminateProcess.

This interface could then be used to implement Unix.* and the corresponding
Windows.* methods. Also, by using the mlton_create_process syscall, there
would no longer be a need for the spawn syscall. I would remove it in favour
of the more powerful syscall and change the spawn code to use it instead.

The MLTON_PROCESS/CHILD implementation would be conditioned on Platform.OS
like spawn is now. (ie: use mlton_create_process or fork/exec)

To wrap up with my questions:

Is anyone working on providing Windows : WINDOWS? (should i?)
Has the "path handling to get CM files to build properly" been fixed?
Does anyone have suggestions for a better approach to Unix.*?
 -- in particular, what's the best way to pick a file in 'iosource' ?
Should I put these new methods in their own new signature or MLTON_PROCESS?

(please reply soon if you think my approach is bad b/c I am going to write
it over the next couple of days and it would save me rewritig time)

Wesley W. Terpstra

Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cross-compile.patch"

--- src/mlton/mlton-20041109/mlton/main/main.fun	2004-11-04 18:15:45.000000000 +0100
+++ main.fun	2004-11-17 11:23:18.000000000 +0100
@@ -616,6 +616,14 @@
 		   | SOME lib => [lib]
 	  | _ => []
+      val gccTargetOpts =
+         case target of
+           Self => []
+         | Cross s =>
+             if MLton.Platform.OS.host = Cygwin andalso
+                s = "i686-pc-mingw32"
+	     then ["-mno-cygwin"]
+	     else ["-b", s]
       val linkOpts =
 	 List.concat [[concat ["-L", !libTargetDir],
 		       if !debug then "-lmlton-gdb" else "-lmlton"],
@@ -785,10 +793,8 @@
-				[["-o", output],
-				 (case target of
-				     Cross s => ["-b", s]
-				   | Self => []),
+				[gccTargetOpts,
+				 ["-o", output],
 				 if !debug then gccDebug else [],
@@ -842,11 +848,9 @@
 					 if !debug
 					    then debugSwitches @ switches
 					 else switches
-				      val switches =
-					 case target of
-					    Cross s => "-b" :: s :: switches
-					  | Self => switches
 				      val switches = "-c" :: switches
+				      (* target opts (-b ...) must be first *)
+				      val switches = gccTargetOpts @ switches
 				      val output =
 					 if stop = Place.O orelse !keepO