1/*
2    Title:      Main program
3
4    Copyright (c) 2000
5        Cambridge University Technical Services Limited
6
7    Further development copyright David C.J. Matthews 2001-12, 2015, 2017-19
8
9    This library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Lesser General Public
11    License version 2.1 as published by the Free Software Foundation.
12
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21
22*/
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#elif defined(_WIN32)
27#include "winconfig.h"
28#else
29#error "No configuration file"
30#endif
31
32#ifdef HAVE_STDIO_H
33#include <stdio.h>
34#endif
35
36#ifdef HAVE_STDLIB_H
37#include <stdlib.h>
38#endif
39
40#ifdef HAVE_STDARG_H
41#include <stdarg.h>
42#endif
43
44#ifdef HAVE_STRING_H
45#include <string.h>
46#endif
47
48#ifdef HAVE_ASSERT_H
49#include <assert.h>
50#define ASSERT(x) assert(x)
51#else
52#define ASSERT(x) 0
53#endif
54
55#if (defined(_WIN32))
56#include <tchar.h>
57#else
58#define _T(x) x
59#define _tcslen strlen
60#define _tcstol strtol
61#define _tcsncmp strncmp
62#define _tcschr strchr
63#endif
64
65#include "globals.h"
66#include "sys.h"
67#include "gc.h"
68#include "heapsizing.h"
69#include "run_time.h"
70#include "machine_dep.h"
71#include "version.h"
72#include "diagnostics.h"
73#include "processes.h"
74#include "mpoly.h"
75#include "scanaddrs.h"
76#include "save_vec.h"
77#include "../polyexports.h"
78#include "memmgr.h"
79#include "pexport.h"
80#include "polystring.h"
81#include "statistics.h"
82#include "noreturn.h"
83#include "savestate.h"
84
85#if (defined(_WIN32))
86#include "winstartup.h"
87#include "winguiconsole.h"
88
89static const TCHAR *lpszServiceName = 0; // DDE service name
90#endif
91
92FILE *polyStdout, *polyStderr; // Redirected in the Windows GUI
93
94NORETURNFN(static void Usage(const char *message, ...));
95
96
97struct _userOptions userOptions;
98
99time_t exportTimeStamp;
100
101enum {
102    OPT_HEAPMIN,
103    OPT_HEAPMAX,
104    OPT_HEAPINIT,
105    OPT_GCPERCENT,
106    OPT_RESERVE,
107    OPT_GCTHREADS,
108    OPT_DEBUGOPTS,
109    OPT_DEBUGFILE,
110    OPT_DDESERVICE,
111    OPT_CODEPAGE,
112    OPT_REMOTESTATS
113};
114
115static struct __argtab {
116    const TCHAR *argName;
117    const char *argHelp;
118    unsigned argKey;
119} argTable[] =
120{
121    { _T("-H"),             "Initial heap size (MB)",                               OPT_HEAPINIT },
122    { _T("--minheap"),      "Minimum heap size (MB)",                               OPT_HEAPMIN },
123    { _T("--maxheap"),      "Maximum heap size (MB)",                               OPT_HEAPMAX },
124    { _T("--gcpercent"),    "Target percentage time in GC (1-99)",                  OPT_GCPERCENT },
125    { _T("--stackspace"),   "Space to reserve for thread stacks and C++ heap(MB)",  OPT_RESERVE },
126    { _T("--gcthreads"),    "Number of threads to use for garbage collection",      OPT_GCTHREADS },
127    { _T("--debug"),        "Debug options: checkmem, gc, x",                       OPT_DEBUGOPTS },
128    { _T("--logfile"),      "Logging file (default is to log to stdout)",           OPT_DEBUGFILE },
129#if (defined(_WIN32))
130#ifdef UNICODE
131    { _T("--codepage"),     "Code-page to use for file-names etc in Windows",       OPT_CODEPAGE },
132#endif
133    { _T("-pServiceName"),  "DDE service name for remote interrupt in Windows",     OPT_DDESERVICE }
134#else
135    { _T("--exportstats"),  "Enable another process to read the statistics",        OPT_REMOTESTATS }
136#endif
137};
138
139static struct __debugOpts {
140    const TCHAR *optName;
141    const char *optHelp;
142    unsigned optKey;
143} debugOptTable[] =
144{
145    { _T("checkmem"),           "Perform additional debugging checks on memory",    DEBUG_CHECK_OBJECTS },
146    { _T("gc"),                 "Log summary garbage-collector information",        DEBUG_GC },
147    { _T("gcenhanced"),         "Log enhanced garbage-collector information",       DEBUG_GC_ENHANCED },
148    { _T("gcdetail"),           "Log detailed garbage-collector information",       DEBUG_GC_DETAIL },
149    { _T("memmgr"),             "Memory manager information",                       DEBUG_MEMMGR },
150    { _T("threads"),            "Thread related information",                       DEBUG_THREADS },
151    { _T("gctasks"),            "Log multi-thread GC information",                  DEBUG_GCTASKS },
152    { _T("heapsize"),           "Log heap resizing data",                           DEBUG_HEAPSIZE },
153    { _T("x"),                  "Log X-windows information",                        DEBUG_X},
154    { _T("sharing"),            "Information from PolyML.shareCommonData",          DEBUG_SHARING},
155    { _T("locks"),              "Information about contended locks",                DEBUG_CONTENTION},
156    { _T("rts"),                "General run-time system calls",                    DEBUG_RTSCALLS},
157    { _T("saving"),             "Saving and loading state; exporting",              DEBUG_SAVING }
158};
159
160// Parse a parameter that is meant to be a size.  Returns the value as a number
161// of kilobytes.
162POLYUNSIGNED parseSize(const TCHAR *p, const TCHAR *arg)
163{
164    POLYUNSIGNED result = 0;
165    if (*p < '0' || *p > '9')
166        // There must be at least one digit
167        Usage("Incomplete %s option\n", arg);
168    while (true)
169    {
170        result = result*10 + *p++ - '0';
171        if (*p == 0)
172        {
173            // The default is megabytes
174            result *= 1024;
175            break;
176        }
177        if (*p == 'G' || *p == 'g')
178        {
179            result *= 1024 * 1024;
180            p++;
181            break;
182        }
183        if (*p == 'M' || *p == 'm')
184        {
185            result *= 1024;
186            p++;
187            break;
188        }
189        if (*p == 'K' || *p == 'k')
190        {
191            p++;
192            break;
193        }
194        if (*p < '0' || *p > '9')
195            break;
196    }
197    if (*p != 0)
198        Usage("Malformed %s option\n", arg);
199    // The sizes must not exceed the possible heap size.
200#ifdef POLYML32IN64
201    if (result > 16 * 1024 * 1024)
202        Usage("Value of %s option must not exceeed 16Gbytes\n", arg);
203#elif (SIZEOF_VOIDP == 4)
204    if (result > 4 * 1024 * 1024)
205        Usage("Value of %s option must not exceeed 4Gbytes\n", arg);
206#else
207    // For completion only!
208    if (result > (POLYUNSIGNED)8 * 1024 * 1024 * 1024 * 1024 * 1024)
209        Usage("Value of %s option must not exceeed 8Ebytes\n", arg);
210#endif
211    return result;
212}
213
214/* In the Windows version this is called from WinMain in Console.c */
215int polymain(int argc, TCHAR **argv, exportDescription *exports)
216{
217    POLYUNSIGNED minsize=0, maxsize=0, initsize=0;
218    unsigned gcpercent=0;
219    /* Get arguments. */
220    memset(&userOptions, 0, sizeof(userOptions)); /* Reset it */
221    userOptions.gcthreads = 0; // Default multi-threaded
222
223    if (polyStdout == 0) polyStdout = stdout;
224    if (polyStderr == 0) polyStderr = stderr;
225
226    // Get the program name for CommandLine.name.  This is allowed to be a full path or
227    // just the last component so we return whatever the system provides.
228    if (argc > 0)
229        userOptions.programName = argv[0];
230    else
231        userOptions.programName = _T(""); // Set it to a valid empty string
232
233    TCHAR *importFileName = 0;
234    debugOptions       = 0;
235
236    userOptions.user_arg_count   = 0;
237    userOptions.user_arg_strings = (TCHAR**)malloc(argc * sizeof(TCHAR*)); // Enough room for all of them
238
239    // Process the argument list removing those recognised by the RTS and adding the
240    // remainder to the user argument list.
241    for (int i = 1; i < argc; i++)
242    {
243        if (argv[i][0] == '-')
244        {
245            bool argUsed = false;
246            for (unsigned j = 0; j < sizeof(argTable)/sizeof(argTable[0]); j++)
247            {
248                size_t argl = _tcslen(argTable[j].argName);
249                if (_tcsncmp(argv[i], argTable[j].argName, argl) == 0)
250                {
251                    const TCHAR *p = 0;
252                    TCHAR *endp = 0;
253                    if (argTable[j].argKey != OPT_REMOTESTATS)
254                    {
255                        if (_tcslen(argv[i]) == argl)
256                        { // If it has used all the argument pick the next
257                            i++;
258                            p = argv[i];
259                        }
260                        else
261                        {
262                            p = argv[i]+argl;
263                             if (*p == '=') p++; // Skip an equals sign
264                        }
265                        if (i >= argc)
266                            Usage("Incomplete %s option\n", argTable[j].argName);
267                    }
268                    switch (argTable[j].argKey)
269                    {
270                    case OPT_HEAPMIN:
271                        minsize = parseSize(p, argTable[j].argName);
272                        break;
273                    case OPT_HEAPMAX:
274                        maxsize = parseSize(p, argTable[j].argName);
275                        break;
276                    case OPT_HEAPINIT:
277                        initsize = parseSize(p, argTable[j].argName);
278                        break;
279                    case OPT_GCPERCENT:
280                        gcpercent = _tcstol(p, &endp, 10);
281                        if (*endp != '\0')
282                            Usage("Malformed %s option\n", argTable[j].argName);
283                        if (gcpercent < 1 || gcpercent > 99)
284                        {
285                            Usage("%s argument must be between 1 and 99\n", argTable[j].argName);
286                            gcpercent = 0;
287                        }
288                        break;
289                    case OPT_RESERVE:
290                        {
291                            POLYUNSIGNED reserve = parseSize(p, argTable[j].argName);
292                            if (reserve != 0)
293                                gHeapSizeParameters.SetReservation(reserve);
294                            break;
295                        }
296                    case OPT_GCTHREADS:
297                        userOptions.gcthreads = _tcstol(p, &endp, 10);
298                        if (*endp != '\0')
299                            Usage("Incomplete %s option\n", argTable[j].argName);
300                        break;
301                    case OPT_DEBUGOPTS:
302                        while (*p != '\0')
303                        {
304                            // Debug options are separated by commas
305                            bool optFound = false;
306                            const TCHAR *q = _tcschr(p, ',');
307                            if (q == NULL) q = p+_tcslen(p);
308                            for (unsigned k = 0; k < sizeof(debugOptTable)/sizeof(debugOptTable[0]); k++)
309                            {
310                                if (_tcslen(debugOptTable[k].optName) == (size_t)(q-p) &&
311                                        _tcsncmp(p, debugOptTable[k].optName, q-p) == 0)
312                                {
313                                    debugOptions |= debugOptTable[k].optKey;
314                                    optFound = true;
315                                }
316                            }
317                            if (! optFound)
318                                Usage("Unknown argument to --debug\n");
319                            if (*q == ',') p = q+1; else p = q;
320                        }
321                        if (debugOptions & DEBUG_GC_DETAIL) debugOptions |= DEBUG_GC_ENHANCED;
322                        if (debugOptions & DEBUG_GC_ENHANCED) debugOptions |= DEBUG_GC;
323                        break;
324                    case OPT_DEBUGFILE:
325                        SetLogFile(p);
326                        break;
327#if (defined(_WIN32))
328                    case OPT_DDESERVICE:
329                        // Set the name for the DDE service.  This allows the caller to specify the
330                        // service name to be used to send Interrupt "signals".
331                        lpszServiceName = p;
332                        break;
333#if (defined(UNICODE))
334                    case OPT_CODEPAGE:
335                        if (! setWindowsCodePage(p))
336                            Usage("Unknown argument to --codepage. Use code page number or CP_ACP, CP_UTF8.\n");
337                        break;
338#endif
339#endif
340                    case OPT_REMOTESTATS:
341                        // If set we export the statistics on Unix.
342                        globalStats.exportStats = true;
343                        break;
344                    }
345                    argUsed = true;
346                    break;
347                }
348            }
349            if (! argUsed) // Add it to the user args.
350                userOptions.user_arg_strings[userOptions.user_arg_count++] = argv[i];
351        }
352        else if (exports == 0 && importFileName == 0)
353            importFileName = argv[i];
354        else
355            userOptions.user_arg_strings[userOptions.user_arg_count++] = argv[i];
356    }
357
358    if (!gMem.Initialise())
359        Usage("Unable to initialise memory allocator\n");
360
361    if (exports == 0 && importFileName == 0)
362        Usage("Missing import file name\n");
363
364    // If the maximum is provided it must be not less than the minimum.
365    if (maxsize != 0 && maxsize < minsize)
366        Usage("Minimum heap size must not be more than maximum size\n");
367    // The initial size must be not more than the maximum
368    if (maxsize != 0 && maxsize < initsize)
369        Usage("Initial heap size must not be more than maximum size\n");
370    // The initial size must be not less than the minimum
371    if (initsize != 0 && initsize < minsize)
372        Usage("Initial heap size must not be less than minimum size\n");
373
374    if (userOptions.gcthreads == 0)
375    {
376        // If the gcthreads option is missing or zero the default is to try to
377        // use as many threads as there are physical processors.  The result may
378        // be zero in which case we use the number of processors.  Because memory
379        // bandwidth is a limiting factor we want to avoid muliple GC threads on
380        // hyperthreaded "processors".
381        userOptions.gcthreads = NumberOfPhysicalProcessors();
382        if (userOptions.gcthreads == 0)
383            userOptions.gcthreads = NumberOfProcessors();
384    }
385
386    // Set the heap size if it has been provided otherwise use the default.
387    gHeapSizeParameters.SetHeapParameters(minsize, maxsize, initsize, gcpercent);
388
389#if (defined(_WIN32))
390    SetupDDEHandler(lpszServiceName); // Windows: Start the DDE handler now we processed any service name.
391#endif
392
393    // Initialise the run-time system before creating the heap.
394    InitModules();
395    CreateHeap();
396
397    PolyObject *rootFunction = 0;
398
399    if (exports != 0)
400        rootFunction = InitHeaderFromExport(exports);
401    else
402    {
403        if (importFileName != 0)
404            rootFunction = ImportPortable(importFileName);
405        if (rootFunction == 0)
406            exit(1);
407    }
408
409    StartModules();
410
411    // Set up the initial process to run the root function.
412    processes->BeginRootThread(rootFunction);
413
414    finish(0);
415
416    /*NOTREACHED*/
417    return 0; /* just to keep lint happy */
418}
419
420void Uninitialise(void)
421// Close down everything and free all resources.  Stop any threads or timers.
422{
423    StopModules();
424}
425
426void finish (int n)
427{
428    // Make sure we don't get any interrupts once the destructors are
429    // applied to globals or statics.
430    Uninitialise();
431#if (defined(_WIN32))
432    ExitThread(n);
433#else
434    exit (n);
435#endif
436}
437
438// Print a message and exit if an argument is malformed.
439void Usage(const char *message, ...)
440{
441    va_list vl;
442    fprintf(polyStdout, "\n");
443    va_start(vl, message);
444    vfprintf(polyStdout, message, vl);
445    va_end(vl);
446
447    for (unsigned j = 0; j < sizeof(argTable)/sizeof(argTable[0]); j++)
448    {
449#if (defined(_WIN32) && defined(UNICODE))
450        fprintf(polyStdout, "%S <%s>\n", argTable[j].argName, argTable[j].argHelp);
451#else
452        fprintf(polyStdout, "%s <%s>\n", argTable[j].argName, argTable[j].argHelp);
453#endif
454    }
455    fprintf(polyStdout, "Debug options:\n");
456    for (unsigned k = 0; k < sizeof(debugOptTable)/sizeof(debugOptTable[0]); k++)
457    {
458#if (defined(_WIN32) && defined(UNICODE))
459        fprintf(polyStdout, "%S <%s>\n", debugOptTable[k].optName, debugOptTable[k].optHelp);
460#else
461        fprintf(polyStdout, "%s <%s>\n", debugOptTable[k].optName, debugOptTable[k].optHelp);
462#endif
463    }
464    fflush(polyStdout);
465
466#if (defined(_WIN32))
467    if (useConsole)
468    {
469        MessageBox(hMainWindow, _T("Poly/ML has exited"), _T("Poly/ML"), MB_OK);
470    }
471#endif
472    exit (1);
473}
474
475// Return a string containing the argument names.  Can be printed out in response
476// to a --help argument.  It is up to the ML application to do that since it may well
477// want to produce information about any arguments it chooses to process.
478char *RTSArgHelp(void)
479{
480    static char buff[2000];
481    char *p = buff;
482    for (unsigned j = 0; j < sizeof(argTable)/sizeof(argTable[0]); j++)
483    {
484#if (defined(_WIN32) && defined(UNICODE))
485        int spaces = sprintf(p, "%S <%s>\n", argTable[j].argName, argTable[j].argHelp);
486#else
487        int spaces = sprintf(p, "%s <%s>\n", argTable[j].argName, argTable[j].argHelp);
488#endif
489        p += spaces;
490    }
491    {
492        int spaces = sprintf(p, "Debug options:\n");
493        p += spaces;
494    }
495    for (unsigned k = 0; k < sizeof(debugOptTable)/sizeof(debugOptTable[0]); k++)
496    {
497#if (defined(_WIN32) && defined(UNICODE))
498        int spaces = sprintf(p, "%S <%s>\n", debugOptTable[k].optName, debugOptTable[k].optHelp);
499#else
500        int spaces = sprintf(p, "%s <%s>\n", debugOptTable[k].optName, debugOptTable[k].optHelp);
501#endif
502        p += spaces;
503    }
504    ASSERT((unsigned)(p - buff) < (unsigned)sizeof(buff));
505    return buff;
506}
507