See More

#include #include #include #include #define CLI11_HAS_FILESYSTEM 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using LFortran::endswith; using LFortran::CompilerOptions; using LFortran::Python::parse_python_file; enum Backend { llvm, cpp, x86 }; enum ASRPass { do_loops, global_stmts, implied_do_loops, array_op, arr_slice, print_arr, class_constructor, unused_functions, }; std::string remove_extension(const std::string& filename) { size_t lastdot = filename.find_last_of("."); if (lastdot == std::string::npos) return filename; return filename.substr(0, lastdot); } std::string remove_path(const std::string& filename) { size_t lastslash = filename.find_last_of("/"); if (lastslash == std::string::npos) return filename; return filename.substr(lastslash+1); } std::string read_file(const std::string &filename) { std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); std::ifstream::pos_type filesize = ifs.tellg(); if (filesize < 0) return std::string(); ifs.seekg(0, std::ios::beg); std::vector bytes(filesize); ifs.read(&bytes[0], filesize); return std::string(&bytes[0], filesize); } std::string get_kokkos_dir() { char *env_p = std::getenv("LFORTRAN_KOKKOS_DIR"); if (env_p) return env_p; std::cerr << "The code C++ generated by the C++ LFortran backend uses the Kokkos library" << std::endl; std::cerr << "(https://github.com/kokkos/kokkos). Please define the LFORTRAN_KOKKOS_DIR" << std::endl; std::cerr << "environment variable to point to the Kokkos installation." << std::endl; throw LFortran::LFortranException("LFORTRAN_KOKKOS_DIR is not defined"); } #ifdef HAVE_LFORTRAN_LLVM void section(const std::string &s) { std::cout << color(LFortran::style::bold) << color(LFortran::fg::blue) << s << color(LFortran::style::reset) << color(LFortran::fg::reset) << std::endl; } int emit_tokens(const std::string &input, std::vector<:string> &tok_strings, std::vector &toks, std::vector<:yystype> &stypes) { // Overload for the case where we want all the token information to use // elsewhere // Src -> Tokens Allocator al(64*1024*1024); //std::vector toks; //std::vector<:yystype> stypes; LFortran::diag::Diagnostics diagnostics; auto res = LFortran::tokens(al, input, diagnostics, &stypes); LFortran::LocationManager lm; lm.in_filename = "input"; lm.init_simple(input); LFortran::CompilerOptions cu; std::cerr << diagnostics.render(input, lm, cu); if (res.ok) { toks = res.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } for (size_t i=0; i < toks.size(); i++) { tok_strings.push_back(LFortran::pickle(toks[i], stypes[i])); //std::cout << LFortran::pickle(toks[i], stypes[i]) << std::endl; } return 0; } bool determine_completeness(std::string command) { // Determine if the statement is complete // Get the tokens bool complete; std::vector toks; std::vector<:yystype> stypes; std::vector<:string> token_strings; int tok_ret = emit_tokens(command, token_strings, toks, stypes); // The token enumerators are in parser.tab.hh int do_blnc = 0; if (std::find(toks.begin(), toks.end(), KW_DO)!=toks.end() || std::find(toks.begin(), toks.end(), KW_DOWHILE)!=toks.end()) { // Statement contains do loop for (size_t i = 0; i < toks.size(); i++) { if (toks[i] == KW_DO || toks[i] == KW_DOWHILE) { do_blnc++; } else if (toks[i] == KW_END_DO || toks[i] == KW_ENDDO) { do_blnc--; } } } int sr_blnc = 0; if (std::find(toks.begin(), toks.end(), KW_SUBROUTINE)!=toks.end()) { // Statement contains subroutine for (size_t i = 0; i < toks.size(); i++) { if (toks[i] == KW_SUBROUTINE) { sr_blnc++; } else if (toks[i] == KW_END_SUBROUTINE || toks[i] == KW_ENDSUBROUTINE) { sr_blnc--; } } } int fn_blnc = 0; if (std::find(toks.begin(), toks.end(), KW_FUNCTION)!=toks.end()) { // Statement contains function for (size_t i = 0; i < toks.size(); i++) { if (toks[i] == KW_FUNCTION) { fn_blnc++; } else if (toks[i] == KW_END_FUNCTION || toks[i] == KW_ENDFUNCTION) { fn_blnc--; } } } int if_blnc = 0; size_t endif_loc = toks.size() - 1; // If statements need more involved checks due to the potential for deep // nesting of block and logical if's. if (std::find(toks.begin(), toks.end(), KW_IF)!=toks.end()) { // Statement contains if for (size_t i = 0; i < toks.size(); i++) { if (toks[i] == KW_IF) { if_blnc++; // Determine if this is a logical or block if by checking for // then token. // Block if balance is decremented on an end if token. // An apparent logical if is decremented arbitrarily since we // aren't checking syntax here. if (std::find(toks.begin() + i, toks.begin() + endif_loc, KW_THEN)!=toks.begin() + endif_loc) { // The statement contains block ifs, check for end if for (size_t j = i+1; j < endif_loc; j++) { if (toks[j] == KW_THEN) { bool then_found = true; for (size_t k = endif_loc; k > j; k--) { if (toks[k] == KW_ENDIF || toks[k] == KW_END_IF) { if_blnc--; // Make sure to not double count this end // if by not looping to this far again endif_loc = k - 1; } } if (then_found) { // We hit a then and no end if so this must be // incomplete break; } } else if (toks[j] == KW_IF) { // We've encountered another if before a then, so // this appears to be a logical if in a statement // with other block ifs. if_blnc--; break; } } } else { // No associated then, assume logical if if_blnc--; } } } } if (do_blnc > 0 || sr_blnc > 0 || fn_blnc > 0 || if_blnc > 0) { complete = false; } else { // If there are excess end statements just return error eventually complete = true; } if (tok_ret == 1) { // Tokenizer error, assume complete and return error eventually complete = true; } return complete; } int prompt(bool verbose) { Terminal term(true, false); std::cout << "Interactive Fortran. Experimental prototype, not ready for end users." << std::endl; std::cout << " * Use Ctrl-D to exit" << std::endl; std::cout << " * Use Enter to submit" << std::endl; std::cout << " * Use Alt-Enter or Ctrl-N to make a new line" << std::endl; std::cout << " - Editing (Keys: Left, Right, Home, End, Backspace, Delete)" << std::endl; std::cout << " - History (Keys: Up, Down)" << std::endl; Allocator al(64*1024*1024); CompilerOptions cu; LFortran::FortranEvaluator e(cu); std::vector<:string> history; std::function iscomplete = determine_completeness; while (true) { std::string input = prompt0(term, ">>> ", history, iscomplete); if (input.size() == 1 && input[0] == CTRL_KEY('d')) { std::cout << std::endl; std::cout << "Exiting." << std::endl; return 0; } if (verbose) { section("Input:"); std::cout << input << std::endl; } LFortran::FortranEvaluator::EvalResult r; LFortran::diag::Diagnostics diagnostics; try { LFortran::LocationManager lm; lm.in_filename = "input"; LFortran::Result<:fortranevaluator::evalresult> res = e.evaluate(input, verbose, lm, diagnostics); std::cerr << diagnostics.render(input, lm, cu); if (res.ok) { r = res.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) continue; } } catch (const LFortran::LFortranException &e) { std::cout << "Other LFortran exception: " << e.msg() << std::endl; continue; } if (verbose) { section("AST:"); std::cout << r.ast << std::endl; section("ASR:"); std::cout << r.asr << std::endl; section("LLVM IR:"); std::cout << r.llvm_ir << std::endl; } switch (r.type) { case (LFortran::FortranEvaluator::EvalResult::integer4) : { if (verbose) std::cout << "Return type: integer" << std::endl; if (verbose) section("Result:"); std::cout << r.i32 << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::integer8) : { if (verbose) std::cout << "Return type: integer(8)" << std::endl; if (verbose) section("Result:"); std::cout << r.i64 << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::real4) : { if (verbose) std::cout << "Return type: real" << std::endl; if (verbose) section("Result:"); std::cout << std::setprecision(8) << r.f32 << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::real8) : { if (verbose) std::cout << "Return type: real(8)" << std::endl; if (verbose) section("Result:"); std::cout << std::setprecision(17) << r.f64 << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::complex4) : { if (verbose) std::cout << "Return type: complex" << std::endl; if (verbose) section("Result:"); std::cout << std::setprecision(8) << "(" << r.c32.re << ", " << r.c32.im << ")" << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::complex8) : { if (verbose) std::cout << "Return type: complex(8)" << std::endl; if (verbose) section("Result:"); std::cout << std::setprecision(17) << "(" << r.c64.re << ", " << r.c64.im << ")" << std::endl; break; } case (LFortran::FortranEvaluator::EvalResult::statement) : { if (verbose) { std::cout << "Return type: none" << std::endl; section("Result:"); std::cout << "(statement)" << std::endl; } break; } case (LFortran::FortranEvaluator::EvalResult::none) : { if (verbose) { std::cout << "Return type: none" << std::endl; section("Result:"); std::cout << "(nothing to execute)" << std::endl; } break; } default : throw LFortran::LFortranException("Return type not supported"); } } return 0; } #endif int emit_prescan(const std::string &infile, CompilerOptions &compiler_options) { std::string input = read_file(infile); LFortran::LocationManager lm; lm.in_filename = infile; std::string prescan = LFortran::fix_continuation(input, lm, compiler_options.fixed_form); std::cout << prescan << std::endl; return 0; } int emit_tokens(const std::string &infile, bool line_numbers, const CompilerOptions &compiler_options) { std::string input = read_file(infile); // Src -> Tokens Allocator al(64*1024*1024); std::vector toks; std::vector<:yystype> stypes; std::vector<:location> locations; LFortran::diag::Diagnostics diagnostics; auto res = LFortran::tokens(al, input, diagnostics, &stypes, &locations); LFortran::LocationManager lm; lm.in_filename = infile; lm.init_simple(input); std::cerr << diagnostics.render(input, lm, compiler_options); if (res.ok) { toks = res.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } for (size_t i=0; i < toks.size(); i++) { std::cout << LFortran::pickle(toks[i], stypes[i]); if (line_numbers) { std::cout << " " << locations[i].first << ":" << locations[i].last; } std::cout << std::endl; } return 0; } int emit_ast_f90(const std::string &infile, CompilerOptions &compiler_options) { std::string input = read_file(infile); LFortran::FortranEvaluator fe(compiler_options); LFortran::LocationManager lm; LFortran::diag::Diagnostics diagnostics; lm.in_filename = infile; LFortran::Result<:ast::translationunit_t> r = fe.get_ast2(input, lm, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (r.ok) { std::cout << LFortran::ast_to_src(*r.result, compiler_options.use_colors); return 0; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } } // int format(const std::string &infile, bool inplace, bool color, int indent, // bool indent_unit, CompilerOptions &compiler_options) // { // std::string input = read_file(infile); // // LFortran::FortranEvaluator fe(compiler_options); // LFortran::LocationManager lm; // LFortran::diag::Diagnostics diagnostics; // lm.in_filename = infile; // LFortran::Result<:ast::translationunit_t> // r = fe.get_ast2(input, lm, diagnostics); // std::cerr << diagnostics.render(input, lm, compiler_options); // if (!r.ok) { // LFORTRAN_ASSERT(diagnostics.has_error()) // return 2; // } // LFortran::AST::TranslationUnit_t* ast = r.result; // // // AST -> Source // if (inplace) color = false; // std::string source = LFortran::ast_to_src(*ast, color, // indent, indent_unit); // // if (inplace) { // std::ofstream out; // out.open(infile); // out << source; // } else { // std::cout << source; // } // // return 0; // } int python_wrapper(const std::string &infile, std::string array_order, CompilerOptions &compiler_options) { bool c_order = (0==array_order.compare("c")); std::string input = read_file(infile); LFortran::FortranEvaluator fe(compiler_options); LFortran::ASR::TranslationUnit_t* asr; // Src -> AST -> ASR LFortran::LocationManager lm; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> result = fe.get_asr2(input, lm, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { asr = result.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } // figure out pyx and pxd filenames auto prefix = infile.substr(0,infile.rfind('.')); auto chdr_fname = prefix + ".h"; auto pxd_fname = prefix + "_pxd.pxd"; // the "_pxd" is an ugly hack, see comment in asr_to_py.cpp auto pyx_fname = prefix + ".pyx"; // The ASR to Python converter needs to know the name of the .h file that will be written, // but needs all path information stripped off - just the filename. auto chdr_fname_forcodegen = chdr_fname; { // Find last ocurrence of \ or /, and delete everything up to that point. auto pos_windows = chdr_fname_forcodegen.rfind('\\'); auto pos_other = chdr_fname_forcodegen.rfind('/'); auto lastpos = std::max( (pos_windows == std::string::npos ? 0 : pos_windows) , (pos_other == std::string::npos ? 0 : pos_other) ); if (lastpos > 0UL) chdr_fname_forcodegen.erase(0,lastpos+1); } // ASR -> (C header file, Cython pxd file, Cython pyx file) std::string c_h, pxd, pyx; std::tie(c_h, pxd, pyx) = LFortran::asr_to_py(*asr, c_order, chdr_fname_forcodegen); // save generated outputs to files. std::ofstream(chdr_fname) << c_h; std::ofstream(pxd_fname) << pxd; std::ofstream(pyx_fname) << pyx; return 0; } int emit_ast(const std::string &infile, const std::string &runtime_library_dir, CompilerOptions &compiler_options) { Allocator al(4*1024); LFortran::Result<:python::ast::ast_t> r = parse_python_file( al, runtime_library_dir, infile); if (!r.ok) { return 1; } LFortran::Python::AST::ast_t* ast = r.result; std::cout << LFortran::Python::pickle_python(*ast, compiler_options.use_colors, compiler_options.indent) << std::endl; return 0; } int emit_asr(const std::string &infile, const std::string &runtime_library_dir, bool with_intrinsic_modules, CompilerOptions &compiler_options) { Allocator al(4*1024); LFortran::Result<:python::ast::ast_t> r1 = parse_python_file( al, runtime_library_dir, infile); if (!r1.ok) { return 1; } LFortran::Python::AST::ast_t* ast = r1.result; LFortran::LocationManager lm; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> r = LFortran::Python::python_ast_to_asr(al, *ast, diagnostics, true); std::string input = read_file(infile); std::cerr << diagnostics.render(input, lm, compiler_options); if (!r.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } LFortran::ASR::TranslationUnit_t* asr = r.result; std::cout << LFortran::pickle(*asr, compiler_options.use_colors, compiler_options.indent, with_intrinsic_modules) << std::endl; return 0; } int emit_cpp(const std::string &infile, const std::string &runtime_library_dir, CompilerOptions &compiler_options) { Allocator al(4*1024); LFortran::Result<:python::ast::ast_t> r = parse_python_file( al, runtime_library_dir, infile); if (!r.ok) { return 1; } LFortran::Python::AST::ast_t* ast = r.result; LFortran::LocationManager lm; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> r1 = LFortran::Python::python_ast_to_asr(al, *ast, diagnostics, true); std::string input = read_file(infile); std::cerr << diagnostics.render(input, lm, compiler_options); if (!r1.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } LFortran::ASR::TranslationUnit_t* asr = r1.result; diagnostics.diagnostics.clear(); auto res = LFortran::asr_to_cpp(al, *asr, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (!res.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 3; } std::cout << res.result; return 0; } int save_mod_files(const LFortran::ASR::TranslationUnit_t &u) { for (auto &item : u.m_global_scope->scope) { if (LFortran::ASR::is_a<:asr::module_t>(*item.second)) { LFortran::ASR::Module_t *m = LFortran::ASR::down_cast<:asr::module_t>(item.second); // Do not save modfiles for modules that were already loaded // from modfiles (as full ASR) if (m->m_loaded_from_mod) continue; Allocator al(4*1024); LFortran::SymbolTable *symtab = al.make_new<:symboltable>(nullptr); symtab->scope[std::string(m->m_name)] = item.second; LFortran::SymbolTable *orig_symtab = m->m_symtab->parent; m->m_symtab->parent = symtab; LFortran::Location loc; LFortran::ASR::asr_t *asr = LFortran::ASR::make_TranslationUnit_t(al, loc, symtab, nullptr, 0); LFortran::ASR::TranslationUnit_t *tu = LFortran::ASR::down_cast2<:asr::translationunit_t>(asr); LFORTRAN_ASSERT(LFortran::asr_verify(*tu)); std::string modfile_binary = LFortran::save_modfile(*tu); m->m_symtab->parent = orig_symtab; LFORTRAN_ASSERT(LFortran::asr_verify(u)); std::string modfile = std::string(m->m_name) + ".mod"; { std::ofstream out; out.open(modfile, std::ofstream::out | std::ofstream::binary); out << modfile_binary; } } } return 0; } #ifdef HAVE_LFORTRAN_LLVM int emit_llvm(const std::string &infile, const std::string &runtime_library_dir, CompilerOptions &compiler_options) { Allocator al(4*1024); LFortran::Result<:python::ast::ast_t> r = parse_python_file( al, runtime_library_dir, infile); if (!r.ok) { return 1; } LFortran::LocationManager lm; // Src -> AST -> ASR LFortran::Python::AST::ast_t* ast = r.result; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> r1 = LFortran::Python::python_ast_to_asr(al, *ast, diagnostics, true); std::string input = read_file(infile); std::cerr << diagnostics.render(input, lm, compiler_options); if (!r1.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } LFortran::ASR::TranslationUnit_t* asr = r1.result; diagnostics.diagnostics.clear(); // ASR -> LLVM LFortran::FortranEvaluator fe(compiler_options); LFortran::Result<:unique_ptr>> res = fe.get_llvm3(*asr, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (!res.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 3; } std::cout << (res.result)->str(); return 0; } int emit_asm(const std::string &infile, CompilerOptions &compiler_options) { std::string input = read_file(infile); LFortran::FortranEvaluator fe(compiler_options); LFortran::LocationManager lm; LFortran::diag::Diagnostics diagnostics; lm.in_filename = infile; LFortran::Result<:string> r = fe.get_asm(input, lm, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (r.ok) { std::cout << r.result; return 0; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } } int compile_python_to_object_file( const std::string &infile, const std::string &outfile, const std::string &runtime_library_dir, CompilerOptions &compiler_options) { Allocator al(4*1024); LFortran::Result<:python::ast::ast_t> r = parse_python_file( al, runtime_library_dir, infile); if (!r.ok) { return 1; } LFortran::LocationManager lm; // Src -> AST -> ASR LFortran::Python::AST::ast_t* ast = r.result; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> r1 = LFortran::Python::python_ast_to_asr(al, *ast, diagnostics, true); std::string input = read_file(infile); std::cerr << diagnostics.render(input, lm, compiler_options); if (!r1.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } LFortran::ASR::TranslationUnit_t* asr = r1.result; diagnostics.diagnostics.clear(); // ASR -> LLVM LFortran::FortranEvaluator fe(compiler_options); LFortran::LLVMEvaluator e(compiler_options.target); std::unique_ptr<:llvmmodule> m; LFortran::Result<:unique_ptr>> res = fe.get_llvm3(*asr, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (!res.ok) { LFORTRAN_ASSERT(diagnostics.has_error()) return 3; } m = std::move(res.result); e.save_object_file(*(m->m_m), outfile); return 0; } int compile_to_object_file(const std::string &infile, const std::string &outfile, bool assembly, CompilerOptions &compiler_options) { std::string input = read_file(infile); LFortran::FortranEvaluator fe(compiler_options); LFortran::ASR::TranslationUnit_t* asr; // Src -> AST -> ASR LFortran::LocationManager lm; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> result = fe.get_asr2(input, lm, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { asr = result.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } // Save .mod files { int err = save_mod_files(*asr); if (err) return err; } // ASR -> LLVM LFortran::LLVMEvaluator e(compiler_options.target); if (!LFortran::ASRUtils::main_program_present(*asr)) { // Create an empty object file (things will be actually // compiled and linked when the main program is present): { e.create_empty_object_file(outfile); } return 0; } std::unique_ptr<:llvmmodule> m; diagnostics.diagnostics.clear(); LFortran::Result<:unique_ptr>> res = fe.get_llvm3(*asr, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (res.ok) { m = std::move(res.result); } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 5; } if (compiler_options.fast) { e.opt(*m->m_m); } // LLVM -> Machine code (saves to an object file) if (assembly) { e.save_asm_file(*(m->m_m), outfile); } else { e.save_object_file(*(m->m_m), outfile); } return 0; } int compile_to_assembly_file(const std::string &infile, const std::string &outfile, CompilerOptions &compiler_options) { return compile_to_object_file(infile, outfile, true, compiler_options); } #endif int compile_to_binary_x86(const std::string &infile, const std::string &outfile, bool time_report, CompilerOptions &compiler_options) { int time_file_read=0; int time_src_to_ast=0; int time_ast_to_asr=0; int time_asr_to_x86=0; std::string input; LFortran::diag::Diagnostics diagnostics; LFortran::FortranEvaluator fe(compiler_options); Allocator al(64*1024*1024); // Allocate 64 MB LFortran::AST::TranslationUnit_t* ast; LFortran::ASR::TranslationUnit_t* asr; { auto t1 = std::chrono::high_resolution_clock::now(); input = read_file(infile); auto t2 = std::chrono::high_resolution_clock::now(); time_file_read = std::chrono::duration_cast<:chrono::milliseconds>(t2 - t1).count(); } // Src -> AST LFortran::LocationManager lm; lm.in_filename = infile; { auto t1 = std::chrono::high_resolution_clock::now(); LFortran::Result<:ast::translationunit_t> result = fe.get_ast2(input, lm, diagnostics); auto t2 = std::chrono::high_resolution_clock::now(); time_src_to_ast = std::chrono::duration_cast<:chrono::milliseconds>(t2 - t1).count(); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { ast = result.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } } // AST -> ASR { diagnostics.diagnostics.clear(); auto t1 = std::chrono::high_resolution_clock::now(); LFortran::Result<:asr::translationunit_t> result = fe.get_asr3(*ast, diagnostics); auto t2 = std::chrono::high_resolution_clock::now(); time_ast_to_asr = std::chrono::duration_cast<:chrono::milliseconds>(t2 - t1).count(); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { asr = result.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 2; } } // ASR -> x86 machine code { diagnostics.diagnostics.clear(); auto t1 = std::chrono::high_resolution_clock::now(); LFortran::Result result = LFortran::asr_to_x86(*asr, al, outfile, time_report); auto t2 = std::chrono::high_resolution_clock::now(); time_asr_to_x86 = std::chrono::duration_cast<:chrono::milliseconds>(t2 - t1).count(); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { // pass } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 3; } } if (time_report) { std::cout << "Allocator usage of last chunk (MB): " << al.size_current() / (1024. * 1024) << std::endl; std::cout << "Allocator chunks: " << al.num_chunks() << std::endl; std::cout << std::endl; std::cout << "Time report:" << std::endl; std::cout << "File reading:" << std::setw(5) << time_file_read << std::endl; std::cout << "Src -> AST: " << std::setw(5) << time_src_to_ast << std::endl; std::cout << "AST -> ASR: " << std::setw(5) << time_ast_to_asr << std::endl; std::cout << "ASR -> x86: " << std::setw(5) << time_asr_to_x86 << std::endl; int total = time_file_read + time_src_to_ast + time_ast_to_asr + time_asr_to_x86; std::cout << "Total: " << std::setw(5) << total << std::endl; } return 0; } int compile_to_object_file_cpp(const std::string &infile, const std::string &outfile, bool assembly, bool kokkos, const std::string &rtlib_header_dir, CompilerOptions &compiler_options) { std::string input = read_file(infile); LFortran::FortranEvaluator fe(compiler_options); LFortran::ASR::TranslationUnit_t* asr; // Src -> AST -> ASR LFortran::LocationManager lm; lm.in_filename = infile; LFortran::diag::Diagnostics diagnostics; LFortran::Result<:asr::translationunit_t> result = fe.get_asr2(input, lm, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (result.ok) { asr = result.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 1; } // Save .mod files { int err = save_mod_files(*asr); if (err) return err; } if (!LFortran::ASRUtils::main_program_present(*asr)) { // Create an empty object file (things will be actually // compiled and linked when the main program is present): if (compiler_options.platform == LFortran::Platform::Windows) { { std::ofstream out; out.open(outfile); out << " "; } } else { std::string outfile_empty = outfile + ".empty.c"; { std::ofstream out; out.open(outfile_empty); out << " "; } std::string CC = "cc"; char *env_CC = std::getenv("LFORTRAN_CC"); if (env_CC) CC = env_CC; std::string cmd = CC + " -c " + outfile_empty + " -o " + outfile; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 11; } } return 0; } // ASR -> C++ std::string src; diagnostics.diagnostics.clear(); LFortran::Result<:string> res = fe.get_cpp2(*asr, diagnostics); std::cerr << diagnostics.render(input, lm, compiler_options); if (res.ok) { src = res.result; } else { LFORTRAN_ASSERT(diagnostics.has_error()) return 5; } // C++ -> Machine code (saves to an object file) if (assembly) { throw LFortran::LFortranException("Not implemented"); } else { std::string cppfile = outfile + ".tmp.cpp"; { std::ofstream out; out.open(cppfile); out << src; } std::string CXX = "g++"; std::string options; if (compiler_options.openmp) { options += "-fopenmp "; } if (kokkos) { std::string kokkos_dir = get_kokkos_dir(); options += "-std=c++17 -I" + kokkos_dir + "/include"; } options += " -I" + rtlib_header_dir; std::string cmd = CXX + " " + options + " -o " + outfile + " -c " + cppfile; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 11; } } return 0; } // infile is an object file // outfile will become the executable int link_executable(const std::vector<:string> &infiles, const std::string &outfile, const std::string &runtime_library_dir, Backend backend, bool static_executable, bool kokkos, CompilerOptions &compiler_options) { /* The `gcc` line for dynamic linking that is constructed below: gcc -o $outfile $infile \ -Lsrc/runtime -Wl,-rpath=src/runtime -llfortran_runtime is equivalent to the following: ld -o $outfile $infile \ -Lsrc/runtime -rpath=src/runtime -llfortran_runtime \ -dynamic-linker /lib64/ld-linux-x86-64.so.2 \ /usr/lib/x86_64-linux-gnu/Scrt1.o /usr/lib/x86_64-linux-gnu/libc.so and this for static linking: gcc -static -o $outfile $infile \ -Lsrc/runtime -Wl,-rpath=src/runtime -llfortran_runtime_static is equivalent to: ld -o $outfile $infile \ -Lsrc/runtime -rpath=src/runtime -llfortran_runtime_static \ /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o \ /usr/lib/x86_64-linux-gnu/libc.a \ /usr/lib/gcc/x86_64-linux-gnu/7/libgcc_eh.a \ /usr/lib/x86_64-linux-gnu/libc.a \ /usr/lib/gcc/x86_64-linux-gnu/7/libgcc.a \ /usr/lib/x86_64-linux-gnu/crtn.o This was tested on Ubuntu 18.04. The `gcc` and `ld` approaches are equivalent except: 1. The `gcc` command knows how to find and link the `libc` library, while in `ld` we must do that manually 2. For dynamic linking, we must also specify the dynamic linker for `ld` Notes: * We can use `lld` to do the linking via the `ld` approach, so `ld` is preferable if we can mitigate the issues 1. and 2. * If we ship our own libc (such as musl), then we know how to find it and link it, which mitigates the issue 1. * If we link `musl` statically, then issue 2. does not apply. * If we link `musl` dynamically, then we have to find the dynamic linker (doable), which mitigates the issue 2. One way to find the default dynamic linker is by: $ readelf -e /bin/bash | grep ld-linux [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] There are probably simpler ways. */ #ifdef HAVE_LFORTRAN_LLVM std::string t = (compiler_options.target == "") ? LFortran::LLVMEvaluator::get_default_target_triple() : compiler_options.target; #else std::string t = (compiler_options.platform == LFortran::Platform::Windows) ? "x86_64-pc-windows-msvc" : compiler_options.target; #endif if (backend == Backend::llvm) { if (t == "x86_64-pc-windows-msvc") { std::string cmd = "link /NOLOGO /OUT:" + outfile + " "; for (auto &s : infiles) { cmd += s + " "; } cmd += runtime_library_dir + "\\lfortran_runtime_static.lib"; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 10; } } else { std::string CC = "cc"; char *env_CC = std::getenv("LFORTRAN_CC"); if (env_CC) CC = env_CC; std::string base_path = "\"" + runtime_library_dir + "\""; std::string options; std::string runtime_lib = "lfortran_runtime"; if (static_executable) { if (compiler_options.platform != LFortran::Platform::macOS_Intel && compiler_options.platform != LFortran::Platform::macOS_ARM) { options += " -static "; } runtime_lib = "lfortran_runtime_static"; } std::string cmd = CC + options + " -o " + outfile + " "; for (auto &s : infiles) { cmd += s + " "; } cmd += + " -L" + base_path + " -Wl,-rpath," + base_path + " -l" + runtime_lib + " -lm"; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 10; } } return 0; } else if (backend == Backend::cpp) { std::string CXX = "g++"; std::string options, post_options; if (static_executable) { options += " -static "; } if (compiler_options.openmp) { options += " -fopenmp "; } if (kokkos) { std::string kokkos_dir = get_kokkos_dir(); post_options += kokkos_dir + "/lib/libkokkoscontainers.a " + kokkos_dir + "/lib/libkokkoscore.a -ldl"; } std::string cmd = CXX + options + " -o " + outfile + " "; for (auto &s : infiles) { cmd += s + " "; } cmd += + " -L"; cmd += " " + post_options + " -lm"; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 10; } return 0; } else if (backend == Backend::x86) { std::string cmd = "cp " + infiles[0] + " " + outfile; int err = system(cmd.c_str()); if (err) { std::cout << "The command '" + cmd + "' failed." << std::endl; return 10; } return 0; } else { LFORTRAN_ASSERT(false); return 1; } } // int emit_c_preprocessor(const std::string &infile, CompilerOptions &compiler_options) // { // std::string input = read_file(infile); // // LFortran::CPreprocessor cpp(compiler_options); // LFortran::LocationManager lm; // lm.in_filename = infile; // std::string s = cpp.run(input, lm, cpp.macro_definitions); // std::cout << s; // return 0; // } } // anonymous namespace int main(int argc, char *argv[]) { LFortran::initialize(); #if defined(HAVE_LFORTRAN_STACKTRACE) LFortran::print_stack_on_segfault(); #endif try { int dirname_length; LFortran::get_executable_path(LFortran::binary_executable_path, dirname_length); std::string runtime_library_dir = LFortran::get_runtime_library_dir(); std::string rtlib_header_dir = LFortran::get_runtime_library_header_dir(); Backend backend; bool arg_S = false; bool arg_c = false; bool arg_v = false; // bool arg_E = false; // bool arg_g = false; // std::string arg_J; // std::vector<:string> arg_I; // std::vector<:string> arg_l; // std::vector<:string> arg_L; std::string arg_o; std::vector<:string> arg_files; bool arg_version = false; bool show_prescan = false; bool show_tokens = false; bool show_ast = false; bool show_asr = false; bool show_cpp = false; bool with_intrinsic_modules = false; bool show_ast_f90 = false; std::string arg_pass; bool arg_no_color = false; bool show_llvm = false; bool show_asm = false; bool time_report = false; bool static_link = false; std::string arg_backend = "llvm"; std::string arg_kernel_f; bool print_targets = false; std::string arg_fmt_file; // int arg_fmt_indent = 4; // bool arg_fmt_indent_unit = false; // bool arg_fmt_inplace = false; // bool arg_fmt_no_color = false; std::string arg_mod_file; // bool arg_mod_show_asr = false; // bool arg_mod_no_color = false; std::string arg_pywrap_file; std::string arg_pywrap_array_order="f"; CompilerOptions compiler_options; CLI::App app{"LPython: modern interactive LLVM-based Python compiler"}; // Standard options compatible with gfortran, gcc or clang // We follow the established conventions app.add_option("files", arg_files, "Source files"); // Should the following Options required for LPython?? // Instead we need support all the options from Python 3 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_option("-l", arg_l, "Link library option"); // app.add_option("-L", arg_L, "Library path option"); // app.add_option("-I", arg_I, "Include path")->allow_extra_args(false); // app.add_option("-J", arg_J, "Where to save mod files"); // app.add_flag("-g", arg_g, "Compile with debugging information"); // app.add_option("-D", compiler_options.c_preprocessor_defines, "Define = (or 1 if omitted)")->allow_extra_args(false); app.add_flag("--version", arg_version, "Display compiler version information"); // LPython specific options app.add_flag("--cpp", compiler_options.c_preprocessor, "Enable C preprocessing"); app.add_flag("--show-prescan", show_prescan, "Show tokens for the given file and exit"); 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 python file and exit"); app.add_flag("--show-asr", show_asr, "Show ASR for the given python file and exit"); app.add_flag("--show-ast-f90", show_ast_f90, "Show Fortran from AST for the given file and exit"); 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 python file and exit"); app.add_flag("--show-asm", show_asm, "Show assembly for the given file and exit"); app.add_flag("--show-stacktrace", compiler_options.show_stacktrace, "Show internal stacktrace on compiler errors"); app.add_flag("--with-intrinsic-mods", with_intrinsic_modules, "Show intrinsic modules in ASR"); app.add_flag("--no-color", arg_no_color, "Turn off colored AST/ASR"); app.add_flag("--indent", compiler_options.indent, "Indented print ASR/AST"); app.add_option("--pass", arg_pass, "Apply the ASR pass and show ASR (implies --show-asr)"); app.add_flag("--symtab-only", compiler_options.symtab_only, "Only create symbol tables in ASR (skip executable stmt)"); app.add_flag("--time-report", time_report, "Show compilation time report"); app.add_flag("--static", static_link, "Create a static executable"); app.add_flag("--no-warnings", compiler_options.no_warnings, "Turn off all warnings"); app.add_flag("--no-error-banner", compiler_options.no_error_banner, "Turn off error banner"); app.add_option("--backend", arg_backend, "Select a backend (llvm, cpp, x86)")->capture_default_str(); app.add_flag("--openmp", compiler_options.openmp, "Enable openmp"); app.add_flag("--fast", compiler_options.fast, "Best performance (disable strict standard compliance)"); app.add_option("--target", compiler_options.target, "Generate code for the given target")->capture_default_str(); app.add_flag("--print-targets", print_targets, "Print the registered targets"); /* * Subcommands: */ // fmt: Should LPython support `fmt` subcommand?? // CLI::App &fmt = *app.add_subcommand("fmt", "Format Fortran source files."); // fmt.add_option("file", arg_fmt_file, "Fortran source file to format")->required(); // fmt.add_flag("-i", arg_fmt_inplace, "Modify in-place (instead of writing to stdout)"); // fmt.add_option("--spaces", arg_fmt_indent, "Number of spaces to use for indentation")->capture_default_str(); // fmt.add_flag("--indent-unit", arg_fmt_indent_unit, "Indent contents of sub / fn / prog / mod"); // fmt.add_flag("--no-color", arg_fmt_no_color, "Turn off color when writing to stdout"); // kernel CLI::App &kernel = *app.add_subcommand("kernel", "Run in Jupyter kernel mode."); kernel.add_option("-f", arg_kernel_f, "The kernel connection file")->required(); // mod // CLI::App &mod = *app.add_subcommand("mod", "Fortran mod file utilities."); // mod.add_option("file", arg_mod_file, "Mod file (*.mod)")->required(); // mod.add_flag("--show-asr", arg_mod_show_asr, "Show ASR for the module"); // mod.add_flag("--no-color", arg_mod_no_color, "Turn off colored ASR"); // pywrap CLI::App &pywrap = *app.add_subcommand("pywrap", "Python wrapper generator"); pywrap.add_option("file", arg_pywrap_file, "Fortran source file (*.f90)")->required(); pywrap.add_option("--array-order", arg_pywrap_array_order, "Select array order (c, f)")->capture_default_str(); app.get_formatter()->column_width(25); app.require_subcommand(0, 1); CLI11_PARSE(app, argc, argv); if (arg_version) { std::string version = LFORTRAN_VERSION; std::cout << "LFortran version: " << version << std::endl; std::cout << "Platform: "; switch (compiler_options.platform) { case (LFortran::Platform::Linux) : std::cout << "Linux"; break; case (LFortran::Platform::macOS_Intel) : std::cout << "macOS Intel"; break; case (LFortran::Platform::macOS_ARM) : std::cout << "macOS ARM"; break; case (LFortran::Platform::Windows) : std::cout << "Windows"; break; case (LFortran::Platform::FreeBSD) : std::cout << "FreeBSD"; break; } std::cout << std::endl; #ifdef HAVE_LFORTRAN_LLVM std::cout << "Default target: " << LFortran::LLVMEvaluator::get_default_target_triple() << std::endl; #endif return 0; } if (print_targets) { #ifdef HAVE_LFORTRAN_LLVM LFortran::LLVMEvaluator::print_targets(); return 0; #else std::cerr << "The --print-targets option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; return 1; #endif } compiler_options.use_colors = !arg_no_color; // if (fmt) { // return format(arg_fmt_file, arg_fmt_inplace, !arg_fmt_no_color, // arg_fmt_indent, arg_fmt_indent_unit, compiler_options); // } if (kernel) { #ifdef HAVE_LFORTRAN_XEUS return LFortran::run_kernel(arg_kernel_f); #else std::cerr << "The kernel subcommand requires LFortran to be compiled with XEUS support. Recompile with `WITH_XEUS=yes`." << std::endl; return 1; #endif } // if (mod) { // if (arg_mod_show_asr) { // Allocator al(1024*1024); // LFortran::ASR::TranslationUnit_t *asr; // asr = LFortran::mod_to_asr(al, arg_mod_file); // std::cout << LFortran::pickle(*asr, !arg_mod_no_color) << std::endl; // return 0; // } // return 0; // } if (pywrap) { return python_wrapper(arg_pywrap_file, arg_pywrap_array_order, compiler_options); } if (arg_backend == "llvm") { backend = Backend::llvm; } else if (arg_backend == "cpp") { backend = Backend::cpp; } else if (arg_backend == "x86") { backend = Backend::x86; } else { std::cerr << "The backend must be one of: llvm, cpp, x86." << std::endl; return 1; } if (arg_files.size() == 0) { #ifdef HAVE_LFORTRAN_LLVM 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; #endif } // TODO: for now we ignore the other filenames, only handle // the first: std::string arg_file = arg_files[0]; 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_prescan) { outfile = basename + ".prescan"; } 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 (arg_E) { // return emit_c_preprocessor(arg_file, compiler_options); // } if (show_prescan) { return emit_prescan(arg_file, compiler_options); } if (show_tokens) { return emit_tokens(arg_file, false, compiler_options); } if (show_ast_f90) { return emit_ast_f90(arg_file, compiler_options); } 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 if (arg_pass == "implied_do_loops") { passes.push_back(ASRPass::implied_do_loops); } else if (arg_pass == "array_op") { passes.push_back(ASRPass::array_op); } else if (arg_pass == "class_constructor") { passes.push_back(ASRPass::class_constructor); } else if (arg_pass == "print_arr") { passes.push_back(ASRPass::print_arr); } else if (arg_pass == "arr_slice") { passes.push_back(ASRPass::arr_slice); } else if (arg_pass == "unused_functions") { passes.push_back(ASRPass::unused_functions); } else { std::cerr << "Pass must be one of: do_loops, global_stmts, implied_do_loops, array_op, class_constructor, print_arr, arr_slice, unused_functions" << std::endl; return 1; } show_asr = true; } if (show_ast) { return emit_ast(arg_file, runtime_library_dir, compiler_options); } if (show_asr) { return emit_asr(arg_file, runtime_library_dir, with_intrinsic_modules, compiler_options); } if (show_cpp) { return emit_cpp(arg_file, runtime_library_dir, compiler_options); } if (show_llvm) { #ifdef HAVE_LFORTRAN_LLVM return emit_llvm(arg_file, runtime_library_dir, compiler_options); #else 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_asm) { #ifdef HAVE_LFORTRAN_LLVM return emit_asm(arg_file, compiler_options); #else std::cerr << "The --show-asm option requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; return 1; #endif } if (arg_S) { if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM return compile_to_assembly_file(arg_file, outfile, compiler_options); #else 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); } } if (arg_c) { if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM return compile_python_to_object_file(arg_file, outfile, runtime_library_dir, compiler_options); #else 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, rtlib_header_dir, compiler_options); } else if (backend == Backend::x86) { return compile_to_binary_x86(arg_file, outfile, time_report, compiler_options); } else { throw LFortran::LFortranException("Unsupported backend."); } } if (endswith(arg_file, ".py")) { std::string tmp_o = outfile + ".tmp.o"; int err; if (backend == Backend::llvm) { #ifdef HAVE_LFORTRAN_LLVM err = compile_python_to_object_file(arg_file, tmp_o, runtime_library_dir, compiler_options); #else std::cerr << "Compiling Python files to object files requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; return 1; #endif } else { throw LFortran::LFortranException("Unsupported backend."); } if (err) return err; return link_executable({tmp_o}, outfile, runtime_library_dir, backend, static_link, true, compiler_options); } else { return link_executable(arg_files, outfile, runtime_library_dir, backend, static_link, true, compiler_options); } } catch(const LFortran::LFortranException &e) { std::cerr << "Internal Compiler Error: Unhandled exception" << std::endl; std::vector<:stacktraceitem> d = e.stacktrace_addresses(); get_local_addresses(d); get_local_info(d); std::cerr << stacktrace2str(d, LFortran::stacktrace_depth); std::cerr << e.name() + ": " << e.msg() << std::endl; return 1; } catch(const std::runtime_error &e) { std::cerr << "runtime_error: " << e.what() << std::endl; return 1; } catch(const std::exception &e) { std::cerr << "std::exception: " << e.what() << std::endl; return 1; } catch(...) { std::cerr << "Unknown Exception" << std::endl; return 1; } return 0; }