Is there a way to compile a R7RS program that uses a R7RS library into a standalone executable using the latest Gambit from git?
The sample implementation for draft SRFI 175 works fine with gsi. gsc produces executables without complaining, but when trying to run those executables, they exit with code 70 and don't write anything to stdout or stderr. Using -:debug= flags to redirect the output does not cause any messages to be shown either.
Here's how to reproduce the problem:
$ git clone -q https://github.com/lassik/srfi-175.git ~/tmp/srfi-175 --branch gambit $ cd ~/tmp/srfi-175 $ srfi/compile-gambit-r7rs.sh Entering directory '/Users/lassi/tmp/srfi-175' + gsc-script -:r7rs -exe . srfi/examples.scm + gsc-script -:r7rs -exe . srfi/tests.scm $ git ls-files --others srfi/examples srfi/tests $ srfi/examples; echo "exited with code $?" exited with code 70 $ srfi/tests; echo "exited with code $?" exited with code 70
The two programs are srfi/examples.scm and srfi/tests.scm; both depend on the R7RS library srfi/175.sld. Note that the programs (.scm files) are not wrapped within `define-library` forms.
Afficher les réponses par date
On Nov 11, 2019, at 11:03 AM, Lassi Kortela lassi@lassi.io wrote:
Is there a way to compile a R7RS program that uses a R7RS library into a standalone executable using the latest Gambit from git?
The sample implementation for draft SRFI 175 works fine with gsi. gsc produces executables without complaining, but when trying to run those executables, they exit with code 70 and don't write anything to stdout or stderr. Using -:debug= flags to redirect the output does not cause any messages to be shown either.
Here's how to reproduce the problem:
$ git clone -q https://github.com/lassik/srfi-175.git ~/tmp/srfi-175 --branch gambit $ cd ~/tmp/srfi-175 $ srfi/compile-gambit-r7rs.sh Entering directory '/Users/lassi/tmp/srfi-175'
- gsc-script -:r7rs -exe . srfi/examples.scm
- gsc-script -:r7rs -exe . srfi/tests.scm
$ git ls-files --others srfi/examples srfi/tests $ srfi/examples; echo "exited with code $?" exited with code 70 $ srfi/tests; echo "exited with code $?" exited with code 70
The two programs are srfi/examples.scm and srfi/tests.scm; both depend on the R7RS library srfi/175.sld. Note that the programs (.scm files) are not wrapped within `define-library` forms.
The issue you are encountering is related to Gambit’s module loader and the fact that it hasn’t yet been upgraded to seamlessly support multi module programs. In the not too distant future your usage of gsc-script should just work as-is, but for now you need to help the “old-style” module loader locate the modules. Here are some explanations.
The Gambit compiler currently supports linking multiple modules into a single executable. The gsi and gsc executables are in fact composed of multiple modules. You can see this by looking at the table of registered modules:
% gsi Gambit v4.9.3
##registered-modules
(#(_gsi 2 #(#(_gsi) #() () 1 #<procedure #2 _gsi#> #<foreign #3 0x10172eda0>)) #(_gsilib 2 #(#(_gsilib) #() () 1 #<procedure #4 _gsilib#> #<foreign #5 0x10199f600>)) #(_repl 2 #(#(_repl) #() () 1 #<procedure #6 _repl#> #<foreign #7 0x10188f200>)) #(_thread 2 #(#(_thread) #() () 1 #<procedure #8 _thread#> #<foreign #9 0x10187f920>)) #(_nonstd 2 #(#(_nonstd) #() () 1 #<procedure #10 _nonstd#> #<foreign #11 0x10186aa00>)) #(_io 2 #(#(_io) #() () 1 #<procedure #12 _io#> #<foreign #13 0x101830de0>)) #(_module 2 #(#(_module) #() () 1 #<procedure #14 _module#> #<foreign #15 0x10180bfc0>)) #(_eval 2 #(#(_eval) #() () 1 #<procedure #16 _eval#> #<foreign #17 0x1017ed560>)) #(_std 2 #(#(_std) #() () 1 #<procedure #18 _std#> #<foreign #19 0x101795880>)) #(_num 2 #(#(_num) #() () 1 #<procedure #20 _num#> #<foreign #21 0x1017589c0>)) #(_system 2 #(#(_system) #() () 1 #<procedure #22 _system#> #<foreign #23 0x1017448c0>)) #(_kernel 2 #(#(_kernel) #() () 1 #<procedure #24 _kernel#> #<foreign #25 0x1017367c0>)))
You can see that gsi is a collection of 12 modules. The _gsi module is gsi's “main” module, _module is the filesystem module getter/loader, _eval is the Scheme interpreter, _num is the numerical library, etc
The order in which these modules are “loaded” is in the reverse order, i.e. _kernel is loaded first, then _system, etc and finally _gsi which (usually) starts gsi's REPL. “loading” a module essentially means executing its top-level forms (expressions and definitions).
The link file (in this case gsi/_gsi_.c generated by the Gambit compiler) contains an ordered list of the modules to be initialized and each module has a descriptor to store some meta-information about the module, in particular:
- a “preload” flag - the list of modules this module depends on, as indicated in the (##demand-module <module-ref>) forms contained in the source file
The preload flag is “on” by default, and indicates that this module must be loaded even if no other module depends on it. This flag can be controlled with the -preload/-nopreload Gambit compiler options. The main module is always loaded, even if its preload flag is “off”.
When the preload flag is off, the module is linked into the executable but it is not loaded. If at some point during the execution a (##demand-module <the-module>) is evaluated, then the module will be loaded (once) by executing its top-level forms. There is no need to search the filesystem for the module, so this is quick and it avoids problems with the module search order being incorrectly set for the modules installed in the filesystem.
Up until recently, the module descriptors did not specify the module dependencies, and the order of the modules on the command-line of the Gambit compiler’s linking invocation (combined with the -preload/-nopreload options) was the only way to specify the load order.
But now, the primitive module loader in the _kernel module has been extended to also take the module dependencies into account to determine the module load order. Now the topological sort of the module dependencies determines the module load order.
It is the primitive module loader in the _kernel module that determines the module load order, after it has loaded (in a sense the Gambit runtime system only loads the _kernel module, and it is then the responsability of the _kernel module to load the other modules).
In the case of your:
% gsc-script -:r7rs -exe . srfi/examples.scm
The module srfi/examples.scm contains a dependency on the module srfi/175. However, the executable does not contain that module and when the executable is run it needs to get srfi/175 from the filesystem to know the dependencies of srfi/175 (which could require getting other modules from the filesystem before any of the modules are actually “loaded”). But the filesystem module getter/loader is in module _module, that hasn’t been loaded yet!
This can be solved by turning off the preload flag of the main program. In that case, the modules of Gambit's runtime system (including _module) will be loaded before the main program and its dependencies are loaded. So all you need to do is:
% gsc-script -:r7rs -exe . -nopreload srfi/examples.scm
If you then try to run your program, you will get this:
% srfi/examples *** ERROR IN ##collect-modules -- Module not found (##get-module 'srfi/175)
That error is because _module is trying to fing the module srfi/175 but the search order does not contain the current directory. So you need to explicitly add that to the runtime system’s search order:
% srfi/examples -:search=. (hex-digit #\a) => 10 (hex-digit #\0) => 0 (hex-digit #\9) => 9 (hex-digit #\A) => 10 (hex-digit #\F) => 15 (hex-digit #\G) => #f (ascii-nth-upper-case 0) => #\A (ascii-nth-upper-case -1) => #\Z (ascii-nth-lower-case 15) => #\p (caesar -55 (caesar 55 "hello world")) => "hello world" (call-with-port (open-binary-input-file "/bin/ls") strings) => __PAGEZERO __TEXT ...
Sorry for the long explanation. The situation will improve when the compiler can analyze the module dependencies at compile time and include in the executable all of the required modules.
Marc
On Nov 11, 2019, at 5:58 PM, Marc Feeley feeley@iro.umontreal.ca wrote:
Sorry for the long explanation. The situation will improve when the compiler can analyze the module dependencies at compile time and include in the executable all of the required modules.
I forgot to mention that you can combine all of the modules in a single executable, but here too the steps are a little contrived currently (because the module-ref has to be specified separately for each compiled module for no good reason):
% gsc-script -c -module-ref srfi/175 srfi/175.sld % gsc-script -c -module-ref srfi/examples . srfi/examples.scm % gsc-script -exe -nopreload srfi/175.c srfi/examples.c % srfi/examples (hex-digit #\a) => 10 (hex-digit #\0) => 0 (hex-digit #\9) => 9 ...
The executable will contain the following registered modules (obtained by adding a trace at the end of srfi/examples.scm):
(#(srfi/examples 2 #(#(srfi/examples) #(srfi/175) () 0 #<procedure #2 srfi/examples#> #<foreign #3 0x10400c3b0>)) #(srfi/175 2 #(#(srfi/175) #() () 0 #<procedure #4 srfi/175#> #<foreign #5 0x104008bf0>)) #(_repl 2 #(#(_repl) #() () 1 #<procedure #6 _repl#> #<foreign #7 0x10416a740>)) #(_thread 2 #(#(_thread) #() () 1 #<procedure #8 _thread#> #<foreign #9 0x10415ae60>)) #(_nonstd 2 #(#(_nonstd) #() () 1 #<procedure #10 _nonstd#> #<foreign #11 0x104145f40>)) #(_io 2 #(#(_io) #() () 1 #<procedure #12 _io#> #<foreign #13 0x10410c320>)) #(_module 2 #(#(_module) #() () 1 #<procedure #14 _module#> #<foreign #15 0x1040e7500>)) #(_eval 2 #(#(_eval) #() () 1 #<procedure #16 _eval#> #<foreign #17 0x1040c8aa0>)) #(_std 2 #(#(_std) #() () 1 #<procedure #18 _std#> #<foreign #19 0x104070dc0>)) #(_num 2 #(#(_num) #() () 1 #<procedure #20 _num#> #<foreign #21 0x104033f00>)) #(_system 2 #(#(_system) #() () 1 #<procedure #22 _system#> #<foreign #23 0x10401fe00>)) #(_kernel 2 #(#(_kernel) #() () 1 #<procedure #24 _kernel#> #<foreign #25 0x104011d00>)))
The next step in the evolution of the compiler is to allow this simpler invocation to build the executable:
% gsc-script -exe . srfi/examples
The compiler will first get the source code of the srfi/examples module, then it sees that it does an (import (srfi 175)) so it then gets the source code of module srfi/175, which does not contain dependencies on any other module. So the compiler needs the files srfi/175.c and srfi/examples.c, which it can generate from the Scheme source code, or reuse previously generated .c files if they are not stale. In general figuring out when a .c file is stale is not easy (when module dependencies don’t use explicit versioning). To simplify, no staleness check could be performed in normal use and the compiler could have an option to force rebuilding all the required .c files from the .scm files.
Marc
Sorry for the long explanation. The situation will improve when the compiler can analyze the module dependencies at compile time and include in the executable all of the required modules.
Thank you very much. On the contrary, your explanation is incredibly thorough and clear. Sorry for taking a day to get back to it.
I updated the SRFI 175 implementation to use the command lines you suggested; works great. Will be in the next draft.
Is there a way that we users can help with the upcoming module system improvements?