diff --git a/src/bin/lfortran.cpp b/src/bin/lfortran.cpp index 5c2caae4aa..2f6cfb716e 100644 --- a/src/bin/lfortran.cpp +++ b/src/bin/lfortran.cpp @@ -614,197 +614,210 @@ int main(int argc, char *argv[]) #if defined(HAVE_LFORTRAN_STACKTRACE) LFortran::print_stack_on_segfault(); #endif - std::string runtime_library_dir = get_runtime_library_dir(); - Backend backend; - - bool arg_S = false; - bool arg_c = false; - bool arg_v = false; - bool arg_E = false; - std::string arg_o; - std::string arg_file; - bool arg_version = false; - bool show_tokens = false; - bool show_ast = false; - bool show_asr = false; - std::string arg_pass; - bool arg_no_color = false; - bool show_llvm = false; - bool show_cpp = false; - bool show_asm = false; - bool static_link = false; - std::string arg_backend = "llvm"; - std::string arg_kernel = ""; - - CLI::App app{"LFortran: modern interactive LLVM-based Fortran compiler"}; - // Standard options compatible with gfortran, gcc or clang - // We follow the established conventions - app.add_option("file", arg_file, "Source file"); - app.add_flag("-S", arg_S, "Emit assembly, do not assemble or link"); - app.add_flag("-c", arg_c, "Compile and assemble, do not link"); - app.add_option("-o", arg_o, "Specify the file to place the output into"); - app.add_flag("-v", arg_v, "Be more verbose"); - app.add_flag("-E", arg_E, "Preprocess only; do not compile, assemble or link"); - app.add_flag("--version", arg_version, "Display compiler version information"); - - // LFortran specific options - app.add_flag("--show-tokens", show_tokens, "Show tokens for the given file and exit"); - app.add_flag("--show-ast", show_ast, "Show AST for the given file and exit"); - app.add_flag("--show-asr", show_asr, "Show ASR for the given file and exit"); - app.add_flag("--no-color", arg_no_color, "Turn off colored AST/ASR"); - app.add_option("--pass", arg_pass, "Apply the ASR pass and show ASR (implies --show-asr)"); - app.add_flag("--show-llvm", show_llvm, "Show LLVM IR for the given file and exit"); - app.add_flag("--show-cpp", show_cpp, "Show C++ translation source for the given file and exit"); - app.add_flag("--show-asm", show_asm, "Show assembly for the given file and exit"); - app.add_flag("--static", static_link, "Create a static executable"); - app.add_option("--backend", arg_backend, "Select a backend (llvm, cpp)", true); - app.add_option("--kernel", arg_kernel, "Run in Jupyter kernel mode"); - - app.get_formatter()->column_width(25); - CLI11_PARSE(app, argc, argv); - - if (arg_version) { - std::string version = LFORTRAN_VERSION; - if (version == "0.1.1") version = "git"; - std::cout << "LFortran version: " << version << std::endl; - return 0; - } + try { + std::string runtime_library_dir = get_runtime_library_dir(); + Backend backend; + + bool arg_S = false; + bool arg_c = false; + bool arg_v = false; + bool arg_E = false; + std::string arg_o; + std::string arg_file; + bool arg_version = false; + bool show_tokens = false; + bool show_ast = false; + bool show_asr = false; + std::string arg_pass; + bool arg_no_color = false; + bool show_llvm = false; + bool show_cpp = false; + bool show_asm = false; + bool static_link = false; + std::string arg_backend = "llvm"; + std::string arg_kernel = ""; + + CLI::App app{"LFortran: modern interactive LLVM-based Fortran compiler"}; + // Standard options compatible with gfortran, gcc or clang + // We follow the established conventions + app.add_option("file", arg_file, "Source file"); + app.add_flag("-S", arg_S, "Emit assembly, do not assemble or link"); + app.add_flag("-c", arg_c, "Compile and assemble, do not link"); + app.add_option("-o", arg_o, "Specify the file to place the output into"); + app.add_flag("-v", arg_v, "Be more verbose"); + app.add_flag("-E", arg_E, "Preprocess only; do not compile, assemble or link"); + app.add_flag("--version", arg_version, "Display compiler version information"); + + // LFortran specific options + app.add_flag("--show-tokens", show_tokens, "Show tokens for the given file and exit"); + app.add_flag("--show-ast", show_ast, "Show AST for the given file and exit"); + app.add_flag("--show-asr", show_asr, "Show ASR for the given file and exit"); + app.add_flag("--no-color", arg_no_color, "Turn off colored AST/ASR"); + app.add_option("--pass", arg_pass, "Apply the ASR pass and show ASR (implies --show-asr)"); + app.add_flag("--show-llvm", show_llvm, "Show LLVM IR for the given file and exit"); + app.add_flag("--show-cpp", show_cpp, "Show C++ translation source for the given file and exit"); + app.add_flag("--show-asm", show_asm, "Show assembly for the given file and exit"); + app.add_flag("--static", static_link, "Create a static executable"); + app.add_option("--backend", arg_backend, "Select a backend (llvm, cpp)", true); + app.add_option("--kernel", arg_kernel, "Run in Jupyter kernel mode"); + + app.get_formatter()->column_width(25); + CLI11_PARSE(app, argc, argv); + + if (arg_version) { + std::string version = LFORTRAN_VERSION; + if (version == "0.1.1") version = "git"; + std::cout << "LFortran version: " << version << std::endl; + return 0; + } - if (arg_kernel != "") { + if (arg_kernel != "") { #ifdef HAVE_LFORTRAN_XEUS - return LFortran::run_kernel(arg_kernel); + return LFortran::run_kernel(arg_kernel); #else - std::cerr << "The --kernel option requires LFortran to be compiled with XEUS support. Recompile with `WITH_XEUS=yes`." << std::endl; - return 1; + std::cerr << "The --kernel option requires LFortran to be compiled with XEUS support. Recompile with `WITH_XEUS=yes`." << std::endl; + return 1; #endif - } + } - if (arg_E) { - return 1; - } + if (arg_E) { + return 1; + } - if (arg_backend == "llvm") { - backend = Backend::llvm; - } else if (arg_backend == "cpp") { - backend = Backend::cpp; - } else { - std::cerr << "The backend must be one of: llvm, cpp." << std::endl; - return 1; - } + if (arg_backend == "llvm") { + backend = Backend::llvm; + } else if (arg_backend == "cpp") { + backend = Backend::cpp; + } else { + std::cerr << "The backend must be one of: llvm, cpp." << std::endl; + return 1; + } - if (arg_file.size() == 0) { + if (arg_file.size() == 0) { #ifdef HAVE_LFORTRAN_LLVM - return prompt(arg_v); + return prompt(arg_v); #else - std::cerr << "Interactive prompt requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; - return 1; + std::cerr << "Interactive prompt requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; + return 1; #endif - } - - std::string outfile; - std::string basename; - basename = remove_extension(arg_file); - basename = remove_path(basename); - if (arg_o.size() > 0) { - outfile = arg_o; - } else if (arg_S) { - outfile = basename + ".s"; - } else if (arg_c) { - outfile = basename + ".o"; - } else if (show_tokens) { - outfile = basename + ".tokens"; - } else if (show_ast) { - outfile = basename + ".ast"; - } else if (show_asr) { - outfile = basename + ".asr"; - } else if (show_llvm) { - outfile = basename + ".ll"; - } else { - outfile = "a.out"; - } + } - if (show_tokens) { - return emit_tokens(arg_file); - } - if (show_ast) { - return emit_ast(arg_file, !arg_no_color); - } - std::vector passes; - if (arg_pass != "") { - if (arg_pass == "do_loops") { - passes.push_back(ASRPass::do_loops); - } else if (arg_pass == "global_stmts") { - passes.push_back(ASRPass::global_stmts); + std::string outfile; + std::string basename; + basename = remove_extension(arg_file); + basename = remove_path(basename); + if (arg_o.size() > 0) { + outfile = arg_o; + } else if (arg_S) { + outfile = basename + ".s"; + } else if (arg_c) { + outfile = basename + ".o"; + } else if (show_tokens) { + outfile = basename + ".tokens"; + } else if (show_ast) { + outfile = basename + ".ast"; + } else if (show_asr) { + outfile = basename + ".asr"; + } else if (show_llvm) { + outfile = basename + ".ll"; } else { - std::cerr << "Pass must be one of: do_loops, global_stmts" << std::endl; - return 1; + outfile = "a.out"; } - show_asr = true; - } - if (show_asr) { - return emit_asr(arg_file, !arg_no_color, passes); - } - if (show_llvm) { + + if (show_tokens) { + return emit_tokens(arg_file); + } + if (show_ast) { + return emit_ast(arg_file, !arg_no_color); + } + std::vector passes; + if (arg_pass != "") { + if (arg_pass == "do_loops") { + passes.push_back(ASRPass::do_loops); + } else if (arg_pass == "global_stmts") { + passes.push_back(ASRPass::global_stmts); + } else { + std::cerr << "Pass must be one of: do_loops, global_stmts" << std::endl; + return 1; + } + show_asr = true; + } + if (show_asr) { + return emit_asr(arg_file, !arg_no_color, passes); + } + if (show_llvm) { #ifdef HAVE_LFORTRAN_LLVM - return emit_llvm(arg_file); + return emit_llvm(arg_file); #else - std::cerr << "The --show-llvm option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; - return 1; + std::cerr << "The --show-llvm option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; + return 1; #endif - } - if (show_cpp) { - return emit_cpp(arg_file); - } - if (arg_S) { - if (backend == Backend::llvm) { + } + if (show_cpp) { + return emit_cpp(arg_file); + } + if (arg_S) { + if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM - return compile_to_assembly_file(arg_file, outfile); + return compile_to_assembly_file(arg_file, outfile); #else - std::cerr << "The -S option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; - return 1; + std::cerr << "The -S option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; + return 1; #endif - } else if (backend == Backend::cpp) { - std::cerr << "The C++ backend does not work with the -S option yet." << std::endl; - return 1; - } else { - LFORTRAN_ASSERT(false); + } else if (backend == Backend::cpp) { + std::cerr << "The C++ backend does not work with the -S option yet." << std::endl; + return 1; + } else { + LFORTRAN_ASSERT(false); + } } - } - if (arg_c) { - if (backend == Backend::llvm) { + if (arg_c) { + if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM - return compile_to_object_file(arg_file, outfile); + return compile_to_object_file(arg_file, outfile); #else - std::cerr << "The -c option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; - return 1; + std::cerr << "The -c option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; + return 1; #endif - } else if (backend == Backend::cpp) { - return compile_to_object_file_cpp(arg_file, outfile, false, true); - } else { - LFORTRAN_ASSERT(false); + } else if (backend == Backend::cpp) { + return compile_to_object_file_cpp(arg_file, outfile, false, true); + } else { + LFORTRAN_ASSERT(false); + } } - } - if (ends_with(arg_file, ".f90")) { - std::string tmp_o = outfile + ".tmp.o"; - int err; - if (backend == Backend::llvm) { + if (ends_with(arg_file, ".f90")) { + std::string tmp_o = outfile + ".tmp.o"; + int err; + if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM - err = compile_to_object_file(arg_file, tmp_o); + err = compile_to_object_file(arg_file, tmp_o); #else - std::cerr << "Compiling Fortran files to object files requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; - return 1; + std::cerr << "Compiling Fortran files to object files requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; + return 1; #endif - } else if (backend == Backend::cpp) { - err = compile_to_object_file_cpp(arg_file, tmp_o, false, true); + } else if (backend == Backend::cpp) { + err = compile_to_object_file_cpp(arg_file, tmp_o, false, true); + } else { + LFORTRAN_ASSERT(false); + } + if (err) return err; + return link_executable(tmp_o, outfile, runtime_library_dir, + backend, static_link, true); } else { - LFORTRAN_ASSERT(false); + return link_executable(arg_file, outfile, runtime_library_dir, + backend, static_link, true); } - if (err) return err; - return link_executable(tmp_o, outfile, runtime_library_dir, - backend, static_link, true); - } else { - return link_executable(arg_file, outfile, runtime_library_dir, - backend, static_link, true); + } catch(const std::runtime_error &e) { + std::cerr << "runtime_error: " << e.what() << std::endl; + return 1; + } catch(const LFortran::LFortranException &e) { + std::cerr << e.stacktrace(); + std::cerr << e.name() + ": " << e.msg() << std::endl; + return 1; + } catch(...) { + std::cerr << "Unknown Exception" << std::endl; + return 1; } + return 0; } diff --git a/src/lfortran/assert.h b/src/lfortran/assert.h index 8fd5530327..bf57259d07 100644 --- a/src/lfortran/assert.h +++ b/src/lfortran/assert.h @@ -4,6 +4,7 @@ // LFORTRAN_ASSERT uses internal functions to perform as assert // so that there is no effect with NDEBUG #include +#include #if defined(WITH_LFORTRAN_ASSERT) #include @@ -11,16 +12,25 @@ #if !defined(LFORTRAN_ASSERT) #define stringize(s) #s #define XSTR(s) stringize(s) -#define LFORTRAN_ASSERT(cond) \ +#if defined(HAVE_LFORTRAN_STACKTRACE) +#define LFORTRAN_ASSERT(cond) \ { \ if (!(cond)) { \ - std::cerr << "LFORTRAN_ASSERT failed: " << __FILE__ \ + throw LFortran::AssertFailed(XSTR(cond)); \ + } \ + } +#else +#define LFORTRAN_ASSERT(cond) \ + { \ + if (!(cond)) { \ + std::cerr << "LFORTRAN_ASSERT failed: " << __FILE__ \ << "\nfunction " << __func__ << "(), line number " \ << __LINE__ << " at \n" \ << XSTR(cond) << "\n"; \ abort(); \ } \ } +#endif // defined(HAVE_LFORTRAN_STACKTRACE) #endif // !defined(LFORTRAN_ASSERT) #if !defined(LFORTRAN_ASSERT_MSG) diff --git a/src/lfortran/exception.h b/src/lfortran/exception.h index dca1c60db9..6f57691561 100644 --- a/src/lfortran/exception.h +++ b/src/lfortran/exception.h @@ -6,12 +6,14 @@ extern "C" { #endif typedef enum { - LFORTRAN_NO_EXCEPTION = 0, - LFORTRAN_RUNTIME_ERROR = 1, - LFORTRAN_TOKENIZER_ERROR = 2, - LFORTRAN_PARSER_ERROR = 3, - LFORTRAN_SEMANTIC_ERROR = 4, - LFORTRAN_CODEGEN_ERROR = 5, + LFORTRAN_NO_EXCEPTION = 0, + LFORTRAN_RUNTIME_ERROR = 1, + LFORTRAN_EXCEPTION = 2, + LFORTRAN_TOKENIZER_ERROR = 3, + LFORTRAN_PARSER_ERROR = 4, + LFORTRAN_SEMANTIC_ERROR = 5, + LFORTRAN_CODEGEN_ERROR = 6, + LFORTRAN_ASSERT_FAILED = 7, } lfortran_exceptions_t; #ifdef __cplusplus @@ -24,6 +26,8 @@ typedef enum { #include #include #include +#include +#include namespace LFortran { @@ -32,14 +36,23 @@ class LFortranException : public std::exception { std::string m_msg; lfortran_exceptions_t ec; + std::string m_stacktrace; public: - LFortranException(const std::string &msg, lfortran_exceptions_t error) + LFortranException(const std::string &msg, lfortran_exceptions_t error, + int stacktrace_dept) : m_msg(msg), ec(error) { +#if defined(HAVE_LFORTRAN_STACKTRACE) + if (ec != lfortran_exceptions_t::LFORTRAN_TOKENIZER_ERROR && + ec != lfortran_exceptions_t::LFORTRAN_PARSER_ERROR && + ec != lfortran_exceptions_t::LFORTRAN_SEMANTIC_ERROR) { + m_stacktrace = LFortran::get_stacktrace(stacktrace_dept); + } +#endif } LFortranException(const std::string &msg) - : LFortranException(msg, LFORTRAN_RUNTIME_ERROR) + : LFortranException(msg, LFORTRAN_EXCEPTION, 2) { } const char *what() const throw() @@ -50,6 +63,28 @@ class LFortranException : public std::exception { return m_msg; } + std::string name() const + { + switch (ec) { + case (lfortran_exceptions_t::LFORTRAN_EXCEPTION) : + return "LFortranException"; + case (lfortran_exceptions_t::LFORTRAN_TOKENIZER_ERROR) : + return "TokenizerError"; + case (lfortran_exceptions_t::LFORTRAN_PARSER_ERROR) : + return "ParserError"; + case (lfortran_exceptions_t::LFORTRAN_SEMANTIC_ERROR) : + return "SemanticError"; + case (lfortran_exceptions_t::LFORTRAN_CODEGEN_ERROR) : + return "CodeGenError"; + case (lfortran_exceptions_t::LFORTRAN_ASSERT_FAILED) : + return "AssertFailed"; + default : return "Unknown Exception"; + } + } + std::string stacktrace() const + { + return m_stacktrace; + } lfortran_exceptions_t error_code() { return ec; @@ -64,7 +99,7 @@ class TokenizerError : public LFortranException public: TokenizerError(const std::string &msg, const Location &loc, const std::string &token) - : LFortranException(msg, LFORTRAN_TOKENIZER_ERROR), loc{loc}, + : LFortranException(msg, LFORTRAN_TOKENIZER_ERROR, 2), loc{loc}, token{token} { } @@ -77,7 +112,7 @@ class ParserError : public LFortranException int token; public: ParserError(const std::string &msg, const Location &loc, const int token) - : LFortranException(msg, LFORTRAN_PARSER_ERROR), loc{loc}, token{token} + : LFortranException(msg, LFORTRAN_PARSER_ERROR, 2), loc{loc}, token{token} { } }; @@ -88,7 +123,7 @@ class SemanticError : public LFortranException Location loc; public: SemanticError(const std::string &msg, const Location &loc) - : LFortranException(msg, LFORTRAN_SEMANTIC_ERROR), loc{loc} + : LFortranException(msg, LFORTRAN_SEMANTIC_ERROR, 2), loc{loc} { } }; @@ -97,7 +132,16 @@ class CodeGenError : public LFortranException { public: CodeGenError(const std::string &msg) - : LFortranException(msg, LFORTRAN_CODEGEN_ERROR) + : LFortranException(msg, LFORTRAN_CODEGEN_ERROR, 2) + { + } +}; + +class AssertFailed : public LFortranException +{ +public: + AssertFailed(const std::string &msg) + : LFortranException(msg, LFORTRAN_ASSERT_FAILED, 2) { } }; diff --git a/src/lfortran/stacktrace.cpp b/src/lfortran/stacktrace.cpp index a042315934..97ae73fa5f 100644 --- a/src/lfortran/stacktrace.cpp +++ b/src/lfortran/stacktrace.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -44,6 +45,10 @@ typedef long long unsigned bfd_vma; #endif +using LFortran::color; +using LFortran::style; +using LFortran::fg; + namespace { /* This struct is used to pass information between @@ -272,8 +277,11 @@ std::string addr2str(std::string file_name, bfd_vma addr) std::string name = demangle_function_name(data.function_name); if (data.filename.length() > 0) { // Nicely format the filename + function name + line - s << " File \"" << data.filename << "\", line " - << data.line << ", in " << name; + s << color(style::dim) << " File \"" << color(style::reset) + << color(style::bold) << color(fg::magenta) << data.filename + << color(fg::reset) << color(style::reset) + << color(style::dim) << "\", line " << data.line << ", in " << name + << color(style::reset); const std::string line_text = remove_leading_whitespace( read_line_from_file(data.filename, data.line)); if (line_text != "") { @@ -282,7 +290,9 @@ std::string addr2str(std::string file_name, bfd_vma addr) } else { // The file is unknown (and data.line == 0 in this case), so the // only meaningful thing to print is the function name: - s << " File unknown, in " << name; + s << color(style::dim) << " File " + color(style::reset) + << color(style::dim) << "unknown" + color(style::reset) + << color(style::dim) << ", in " << name << color(style::reset); } } s << "\n"; @@ -403,20 +413,16 @@ std::string stacktrace2str(const StacktraceAddresses &stacktrace_addresses) void loc_segfault_callback_print_stack(int sig_num) { - std::cout << "\nSegfault caught. Printing stacktrace:\n\n"; - LFortran::show_stacktrace(); - std::cout << "\nDone. Exiting the program.\n"; - // Deregister our abort callback: - signal(SIGABRT, SIG_DFL); - abort(); + std::cerr << LFortran::get_stacktrace(1); + std::cerr << "Segfault: Signal SIGSEGV (segmentation fault) received\n"; + exit(1); } void loc_abort_callback_print_stack(int sig_num) { - std::cout << "\nAbort caught. Printing stacktrace:\n\n"; - LFortran::show_stacktrace(); - std::cout << "\nDone.\n"; + std::cerr << LFortran::get_stacktrace(1); + std::cerr << "Abort: Signal SIGABRT (abort) received\n\n"; }