/* Copyright (c) 2013 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/timer.h" #include "util/macros.h" #include "util/io.h" #include "util/file_lock.h" #include "util/options.h" #include "util/option_declarations.h" #include "kernel/environment.h" #include "kernel/kernel_exception.h" #include "library/formatter.h" #include "library/module.h" #include "library/time_task.h" #include "library/compiler/ir.h" #include "library/trace.h" #include "library/print.h" #include "initialize/init.h" #include "library/compiler/ir_interpreter.h" #include "util/path.h" #ifdef _MSC_VER #include #define STDOUT_FILENO 1 #else #include #include #endif #if defined(LEAN_EMSCRIPTEN) #include #endif #include "githash.h" // NOLINT #ifdef LEAN_WINDOWS #include #undef ERROR // thanks, wingdi.h #else #include #endif #if defined(LEAN_LLVM) #include #endif #ifdef _MSC_VER // extremely simple implementation of getopt.h enum arg_opt { no_argument, required_argument, optional_argument }; struct option { const char name[12]; arg_opt has_arg; int *flag; char val; }; static char *optarg; static int optind = 1; int getopt_long(int argc, char *in_argv[], const char *optstring, const option *opts, int *index) { optarg = nullptr; if (optind >= argc) return -1; char *argv = in_argv[optind]; if (argv[0] != '-') { // find first -opt if any int i = optind; bool found = false; for (; i < argc; ++i) { if (in_argv[i][0] == '-') { found = true; break; } } if (!found) return -1; auto next = in_argv[i]; // FIXME: this doesn't account for long options with arguments like --foo arg memmove(&in_argv[optind + 1], &in_argv[optind], (i - optind) * sizeof(argv)); argv = in_argv[optind] = next; } ++optind; // long option if (argv[1] == '-') { auto eq = strchr(argv, '='); size_t sz = (eq ? (eq - argv) : strlen(argv)) - 2; for (auto I = opts; *I->name; ++I) { if (!strncmp(I->name, argv + 2, sz) && I->name[sz] == '\0') { assert(!I->flag); switch (I->has_arg) { case no_argument: if (eq) { std::cerr << in_argv[0] << ": option doesn't take an argument -- " << I->name << std::endl; return '?'; } break; case required_argument: if (eq) { optarg = eq + 1; } else { if (optind >= argc) { std::cerr << in_argv[0] << ": option requires an argument -- " << I->name << std::endl; return '?'; } optarg = in_argv[optind++]; } break; case optional_argument: if (eq) { optarg = eq + 1; } break; } if (index) *index = I - opts; return I->val; } } return '?'; } else { auto opt = strchr(optstring, argv[1]); if (!opt) return '?'; if (opt[1] == ':') { if (argv[2] == '\0') { if (optind < argc) { optarg = in_argv[optind++]; } else { std::cerr << in_argv[0] << ": option requires an argument -- " << *opt << std::endl; return '?'; } } else { optarg = argv + 2; } } return *opt; } } #endif using namespace lean; // NOLINT #ifndef LEAN_SERVER_DEFAULT_MAX_MEMORY #define LEAN_SERVER_DEFAULT_MAX_MEMORY 1024 #endif #ifndef LEAN_DEFAULT_MAX_MEMORY #define LEAN_DEFAULT_MAX_MEMORY 0 #endif #ifndef LEAN_DEFAULT_MAX_HEARTBEAT #define LEAN_DEFAULT_MAX_HEARTBEAT 0 #endif #ifndef LEAN_SERVER_DEFAULT_MAX_HEARTBEAT #define LEAN_SERVER_DEFAULT_MAX_HEARTBEAT 100000 #endif static void display_header(std::ostream & out) { out << "Lean (version " << get_version_string() << ", " << LEAN_STR(LEAN_BUILD_TYPE) << ")\n"; } static void display_help(std::ostream & out) { display_header(out); std::cout << "Miscellaneous:\n"; std::cout << " --help -h display this message\n"; std::cout << " --version -v display version number\n"; std::cout << " --githash display the git commit hash number used to build this binary\n"; std::cout << " --run call the 'main' definition in a file with the remaining arguments\n"; std::cout << " --o=oname -o create olean file\n"; std::cout << " --c=fname -c name of the C output file\n"; std::cout << " --stdin take input from stdin\n"; std::cout << " --root=dir set package root directory from which the module name of the input file is calculated\n" << " (default: current working directory)\n"; std::cout << " --trust=num -t trust level (default: max) 0 means do not trust any macro,\n" << " and type check all imported modules\n"; std::cout << " --quiet -q do not print verbose messages\n"; std::cout << " --memory=num -M maximum amount of memory that should be used by Lean\n"; std::cout << " (in megabytes)\n"; std::cout << " --timeout=num -T maximum number of memory allocations per task\n"; std::cout << " this is a deterministic way of interrupting long running tasks\n"; #if defined(LEAN_MULTI_THREAD) std::cout << " --threads=num -j number of threads used to process lean files\n"; std::cout << " --tstack=num -s thread stack size in Kb\n"; std::cout << " --server start lean in server mode\n"; std::cout << " --worker start lean in server-worker mode\n"; #endif std::cout << " --plugin=file load and initialize shared library for registering linters etc.\n"; std::cout << " --deps just print dependencies of a Lean input\n"; std::cout << " --profile display elaboration/type checking time for each definition/theorem\n"; std::cout << " --stats display environment statistics\n"; DEBUG_CODE( std::cout << " --debug=tag enable assertions with the given tag\n"; ) std::cout << " -D name=value set a configuration option (see set_option command)\n"; } static struct option g_long_options[] = { {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"githash", no_argument, 0, 'g'}, {"run", no_argument, 0, 'r'}, {"o", optional_argument, 0, 'o'}, {"stdin", no_argument, 0, 'i'}, {"root", required_argument, 0, 'R'}, {"memory", required_argument, 0, 'M'}, {"trust", required_argument, 0, 't'}, {"profile", no_argument, 0, 'P'}, {"stats", no_argument, 0, 'a'}, {"quiet", no_argument, 0, 'q'}, {"deps", no_argument, 0, 'd'}, {"timeout", optional_argument, 0, 'T'}, {"c", optional_argument, 0, 'c'}, {"exitOnPanic", no_argument, 0, 'e'}, #if defined(LEAN_MULTI_THREAD) {"threads", required_argument, 0, 'j'}, {"tstack", required_argument, 0, 's'}, {"server", no_argument, 0, 'S'}, {"worker", no_argument, 0, 'W'}, #endif {"plugin", required_argument, 0, 'p'}, #ifdef LEAN_DEBUG {"debug", required_argument, 0, 'B'}, #endif {0, 0, 0, 0} }; static char const * g_opt_str = "PdD:o:c:C:qgvht:012j:012rR:M:012T:012ap:e" #if defined(LEAN_MULTI_THREAD) "s:012" #endif ; // NOLINT options set_config_option(options const & opts, char const * in) { if (!in) return opts; while (*in && std::isspace(*in)) ++in; std::string in_str(in); auto pos = in_str.find('='); if (pos == std::string::npos) throw lean::exception("invalid -D parameter, argument must contain '='"); lean::name opt = lean::string_to_name(in_str.substr(0, pos)); std::string val = in_str.substr(pos+1); auto decls = lean::get_option_declarations(); auto it = decls.find(opt); if (it) { switch (it->kind()) { case lean::data_value_kind::Bool: if (val == "true") return opts.update(opt, true); else if (val == "false") return opts.update(opt, false); else throw lean::exception(lean::sstream() << "invalid -D parameter, invalid configuration option '" << opt << "' value, it must be true/false"); case lean::data_value_kind::Nat: return opts.update(opt, static_cast(atoi(val.c_str()))); case lean::data_value_kind::String: return opts.update(opt, val.c_str()); default: throw lean::exception(lean::sstream() << "invalid -D parameter, configuration option '" << opt << "' cannot be set in the command line, use set_option command"); } } else { throw lean::exception(lean::sstream() << "invalid -D parameter, unknown configuration option '" << opt << "'"); } } void load_plugin(std::string path) { void * init; // we never want to look up plugins using the system library search path = lrealpath(path); std::string pkg = stem(path); std::string sym = "initialize_" + pkg; #ifdef LEAN_WINDOWS HMODULE h = LoadLibrary(path.c_str()); if (!h) { throw exception(sstream() << "error loading plugin " << path); } init = reinterpret_cast(GetProcAddress(h, sym.c_str())); #else void *handle = dlopen(path.c_str(), RTLD_LAZY); if (!handle) { throw exception(sstream() << "error loading plugin, " << dlerror()); } init = dlsym(handle, sym.c_str()); #endif if (!init) { throw exception(sstream() << "error, plugin " << path << " does not seem to contain a module '" << pkg << ".Default'"); } auto init_fn = reinterpret_cast(init); object *r = init_fn(io_mk_world()); consume_io_result(r); // NOTE: we never unload plugins } class initializer { private: lean::initializer m_init; public: initializer() { } ~initializer() { } }; namespace lean { extern "C" object * lean_run_frontend(object * input, object * opts, object * filename, object * main_module_name, object * w); pair_ref run_new_frontend(std::string const & input, options const & opts, std::string const & file_name, name const & main_module_name) { return get_io_result>( lean_run_frontend(mk_string(input), opts.to_obj_arg(), mk_string(file_name), main_module_name.to_obj_arg(), io_mk_world())); } /* def workerMain : IO UInt32 */ extern "C" object* lean_server_worker_main(object* w); uint32_t run_server_worker() { return get_io_scalar_result(lean_server_worker_main(io_mk_world())); } /* def watchdogMain (args : List String) : IO Uint32 */ extern "C" object* lean_server_watchdog_main(object* args, object* w); uint32_t run_server_watchdog(buffer const & args) { list_ref arglist = to_list_ref(args); return get_io_scalar_result(lean_server_watchdog_main(arglist.to_obj_arg(), io_mk_world())); } extern "C" object* lean_init_search_path(object* opt_path, object* w); void init_search_path() { get_io_scalar_result(lean_init_search_path(mk_option_none(), io_mk_world())); } extern "C" object* lean_module_name_of_file(object* fname, object * root_dir, object* w); optional module_name_of_file(std::string const & fname, optional const & root_dir, bool optional) { object * oroot_dir = mk_option_none(); if (root_dir) { oroot_dir = mk_option_some(mk_string(*root_dir)); } object * o = lean_module_name_of_file(mk_string(fname), oroot_dir, io_mk_world()); if (io_result_is_error(o) && optional) { return lean::optional(); } else { return some(get_io_result(o)); } } /* def printImports (input : String) (fileName : Option String := none) : IO Unit */ extern "C" object* lean_print_imports(object* input, object* file_name, object* w); void print_imports(std::string const & input, std::string const & fname) { consume_io_result(lean_print_imports(mk_string(input), mk_option_some(mk_string(fname)), io_mk_world())); } extern "C" object* lean_environment_free_regions(object * env, object * w); void environment_free_regions(environment && env) { consume_io_result(lean_environment_free_regions(env.steal(), io_mk_world())); } } void check_optarg(char const * option_name) { if (!optarg) { std::cerr << "error: argument missing for option '-" << option_name << "'" << std::endl; std::exit(1); } } int main(int argc, char ** argv) { #ifdef LEAN_EMSCRIPTEN // When running in command-line mode under Node.js, we make system directories available in the virtual filesystem. // This mode is used to compile 32-bit oleans. EM_ASM( if ((typeof process === "undefined") || (process.release.name !== "node")) { throw new Error("The Lean command-line driver can only run under Node.js. For the embeddable WASM library, see lean_wasm.cpp."); } // We cannot mount /, see https://github.com/emscripten-core/emscripten/issues/2040 FS.mount(NODEFS, { root: "/home" }, "/home"); FS.mount(NODEFS, { root: "/tmp" }, "/tmp"); FS.chdir(process.cwd()); ); #elif defined(LEAN_WINDOWS) // "best practice" according to https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode SetErrorMode(SEM_FAILCRITICALERRORS); #endif auto init_start = std::chrono::steady_clock::now(); ::initializer init; second_duration init_time = std::chrono::steady_clock::now() - init_start; bool run = false; optional olean_fn; bool use_stdin = false; unsigned trust_lvl = LEAN_BELIEVER_TRUST_LEVEL + 1; bool only_deps = false; bool stats = false; // 0 = don't run server, 1 = watchdog, 2 = worker int run_server = 0; unsigned num_threads = 0; #if defined(LEAN_MULTI_THREAD) num_threads = hardware_concurrency(); #endif #if defined(LEAN_LLVM) // Initialize LLVM backends for native code generation. llvm::InitializeNativeTarget(); #endif try { init_search_path(); } catch (lean::throwable & ex) { std::cerr << "error: " << ex.what() << std::endl; return 1; } options opts; optional server_in; std::string native_output; optional c_output; optional root_dir; buffer forwarded_args; while (true) { int c = getopt_long(argc, argv, g_opt_str, g_long_options, NULL); if (c == -1) break; // end of command line switch (c) { case 'e': lean_set_exit_on_panic(true); break; case 'j': num_threads = static_cast(atoi(optarg)); forwarded_args.push_back(string_ref("-j" + std::string(optarg))); break; case 'v': display_header(std::cout); return 0; case 'g': std::cout << LEAN_GITHASH << "\n"; return 0; case 'h': display_help(std::cout); return 0; case 'c': check_optarg("c"); c_output = optarg; break; case 's': lean::lthread::set_thread_stack_size( static_cast((atoi(optarg) / 4) * 4) * static_cast(1024)); break; case 'i': use_stdin = true; break; case 'r': run = true; break; case 'o': olean_fn = optarg; break; case 'R': root_dir = optarg; break; case 'M': check_optarg("M"); opts = opts.update(get_max_memory_opt_name(), static_cast(atoi(optarg))); break; case 'T': check_optarg("T"); forwarded_args.push_back(string_ref("-T" + std::string(optarg))); opts = opts.update(get_timeout_opt_name(), static_cast(atoi(optarg))); break; case 't': check_optarg("t"); trust_lvl = atoi(optarg); break; case 'q': opts = opts.update(lean::get_verbose_opt_name(), false); break; case 'd': only_deps = true; break; case 'a': stats = true; break; case 'D': try { check_optarg("D"); opts = set_config_option(opts, optarg); } catch (lean::exception & ex) { std::cerr << ex.what() << std::endl; return 1; } break; case 'S': run_server = 1; break; case 'W': run_server = 2; break; case 'P': opts = opts.update("profiler", true); break; #if defined(LEAN_DEBUG) case 'B': check_optarg("B"); lean::enable_debug(optarg); break; #endif case 'p': check_optarg("p"); load_plugin(optarg); break; default: std::cerr << "Unknown command line option\n"; display_help(std::cerr); return 1; } } if (auto max_memory = opts.get_unsigned(get_max_memory_opt_name(), opts.get_bool("server") ? LEAN_SERVER_DEFAULT_MAX_MEMORY : LEAN_DEFAULT_MAX_MEMORY)) { set_max_memory_megabyte(max_memory); } if (auto timeout = opts.get_unsigned(get_timeout_opt_name(), opts.get_bool("server") ? LEAN_SERVER_DEFAULT_MAX_HEARTBEAT : LEAN_DEFAULT_MAX_HEARTBEAT)) { set_max_heartbeat_thousands(timeout); } if (get_profiler(opts)) { report_profiling_time("initialization", init_time); } environment env(trust_lvl); scoped_task_manager scope_task_man(num_threads); optional main_module_name; std::string mod_fn = ""; std::string contents; try { if (run_server == 1) return run_server_watchdog(forwarded_args); else if (run_server == 2) return run_server_worker(); if (use_stdin) { if (argc - optind != 0) { mod_fn = argv[optind++]; } else { mod_fn = ""; } std::stringstream buf; buf << std::cin.rdbuf(); contents = buf.str(); } else { if ((!run && argc - optind != 1) || (run && argc - optind == 0)) { std::cerr << "Expected exactly one file name\n"; display_help(std::cerr); return 1; } mod_fn = argv[optind++]; contents = read_file(mod_fn); main_module_name = module_name_of_file(mod_fn, root_dir, /* optional */ !olean_fn && !c_output); } if (only_deps) { print_imports(contents, mod_fn); return 0; } // Quick and dirty `#lang` support // TODO: make it extensible, and add `lean4md` if (contents.compare(0, 5, "#lang") == 0) { auto end_line_pos = contents.find("\n"); // TODO: trim auto lang_id = contents.substr(6, end_line_pos - 6); if (lang_id == "lean4") { // do nothing for now } else { std::cerr << "unknown language '" << lang_id << "'\n"; return 1; } // Remove up to `\n` contents.erase(0, end_line_pos); } if (!main_module_name) main_module_name = name("_stdin"); pair_ref r = run_new_frontend(contents, opts, mod_fn, *main_module_name); env = r.fst(); bool ok = unbox(r.snd().raw()); if (stats) { env.display_stats(); } if (run && ok) { uint32 ret = ir::run_main(env, opts, argc - optind, argv + optind); // environment_free_regions(std::move(env)); return ret; } if (olean_fn && ok) { time_task t(".olean serialization", opts); write_module(env, *olean_fn); } if (c_output && ok) { std::ofstream out(*c_output); if (out.fail()) { std::cerr << "failed to create '" << *c_output << "'\n"; return 1; } time_task _("C code generation", opts); out << lean::ir::emit_c(env, *main_module_name).data(); out.close(); } display_cumulative_profiling_times(std::cerr); return ok ? 0 : 1; } catch (lean::throwable & ex) { std::cerr << ex.what() << "\n"; } catch (std::bad_alloc & ex) { std::cerr << "out of memory" << std::endl; } return 1; }