/*! * @file CompilerControl.cpp * Compiler implementation for forms which actually control the compiler. */ #include #include "goalc/compiler/Compiler.h" #include "goalc/compiler/IR.h" #include "common/util/Timer.h" #include "common/util/DgoWriter.h" #include "common/util/FileUtil.h" #include "goalc/data_compiler/game_text.h" #include "goalc/data_compiler/game_count.h" #include "common/goos/ReplHistory.h" /*! * Exit the compiler. Disconnects the listener and tells the target to reset itself. * Will actually exit the next time the REPL runs. */ Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {}, {}); if (m_debugger.is_attached()) { m_debugger.detach(); } if (m_listener.is_connected()) { m_listener.send_reset(false); } // flag for the REPL. m_want_exit = true; ReplHistory::repl_save_history(); return get_none(); } /*! * Evaluate GOOS code. It's not possible to get the result, so this is really only useful to get * a side effect. Used to bootstrap the GOAL/GOOS macro system. */ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; try { for_each_in_list(rest, [&](const goos::Object& o) { m_goos.eval_with_rewind(o, m_goos.global_environment.as_env()); }); } catch (std::runtime_error& e) { throw_compiler_error(form, "Error while evaluating GOOS: ", e.what()); } return get_none(); } /*! * Compile a "data file" */ Val* Compiler::compile_asm_data_file(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {goos::ObjectType::SYMBOL, goos::ObjectType::STRING}, {}); auto kind = symbol_string(args.unnamed.at(0)); if (kind == "game-text") { compile_game_text(as_string(args.unnamed.at(1))); } else if (kind == "game-count") { compile_game_count(as_string(args.unnamed.at(1))); } else { throw_compiler_error(form, "The option {} was not recognized for asm-data-file.", kind); } return get_none(); } /*! * Compile a file, and optionally color, save, or load. * This should only be used for v3 "code object" files. */ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; int i = 0; std::string filename; bool load = false; bool color = false; bool write = false; bool no_code = false; bool disassemble = false; std::vector> timing; Timer total_timer; // parse arguments for_each_in_list(rest, [&](const goos::Object& o) { if (i == 0) { filename = as_string(o); } else { auto setting = symbol_string(o); if (setting == ":load") { load = true; } else if (setting == ":color") { color = true; } else if (setting == ":write") { write = true; } else if (setting == ":no-code") { no_code = true; } else if (setting == ":disassemble") { disassemble = true; } else { throw_compiler_error(form, "The option {} was not recognized for asm-file.", setting); } } i++; }); // READ Timer reader_timer; auto code = m_goos.reader.read_from_file({filename}); timing.emplace_back("read", reader_timer.getMs()); Timer compile_timer; std::string obj_file_name = filename; // Extract object name from file name. for (int idx = int(filename.size()) - 1; idx-- > 0;) { if (filename.at(idx) == '\\' || filename.at(idx) == '/') { obj_file_name = filename.substr(idx + 1); break; } } obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.')); // COMPILE auto obj_file = compile_object_file(obj_file_name, code, !no_code); timing.emplace_back("compile", compile_timer.getMs()); if (color) { // register allocation Timer color_timer; color_object_file(obj_file); timing.emplace_back("color", color_timer.getMs()); // code/object file generation Timer codegen_timer; std::vector data; std::string disasm; if (disassemble) { codegen_and_disassemble_object_file(obj_file, &data, &disasm); printf("%s\n", disasm.c_str()); } else { data = codegen_object_file(obj_file); } timing.emplace_back("codegen", codegen_timer.getMs()); // send to target if (load) { if (m_listener.is_connected()) { m_listener.send_code(data); } else { printf("WARNING - couldn't load because listener isn't connected\n"); // todo log warn } } // save file if (write) { file_util::create_dir_if_needed(file_util::get_file_path({"out", "obj"})); file_util::write_binary_file(file_util::get_file_path({"out", "obj", obj_file_name + ".o"}), (void*)data.data(), data.size()); } } else { if (load) { printf("WARNING - couldn't load because coloring is not enabled\n"); } if (write) { printf("WARNING - couldn't write because coloring is not enabled\n"); } if (disassemble) { printf("WARNING - couldn't disassemble because coloring is not enabled\n"); } } if (m_settings.print_timing) { printf("F: %36s ", obj_file_name.c_str()); timing.emplace_back("total", total_timer.getMs()); for (auto& e : timing) { printf(" %12s %4.0f", e.first.c_str(), e.second); } printf("\n"); } else { auto total_time = total_timer.getMs(); if (total_time > 10.0) { fmt::print("[ASM-FILE] {} took {:.2f} ms\n", obj_file_name, total_time); } } return get_none(); } /*! * Simple help / documentation command */ Val* Compiler::compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env) { fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n"); fmt::print(" - Exit the compiler once the current REPL command is finished\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt [ip-address] [port-number])\n"); fmt::print( " - Connect the listener to a running target. The IP address defaults to `127.0.0.1` and the " "port to `8112`\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(r [ip-address] [port-number])\n"); fmt::print( " - Attempt to reset the target and reconnect. After this, the target will have nothing " "loaded.\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:status)\n"); fmt::print(" - Send a ping-like message to the target. Requires the target to be connected\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(shutdown-target)\n"); fmt::print(" - If the target is connected, make it exit\n"); fmt::print(fmt::emphasis::bold, "\nCompiling & Building:\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(m \"filename\")\n"); fmt::print(" - Compile an OpenGOAL source file\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(ml \"filename\")\n"); fmt::print(" - Compile and Load an OpenGOAL source file\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-game)\n"); fmt::print(" - Loads and builds all game files and rebuilds DGOs\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-kernel)\n"); fmt::print(" - Similar to (build-game) but only the kernel files\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(blg)\n"); fmt::print(" - Performs a (build-game) and then loads all CGOs\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-dgos \"path/to/dgos/description/file\")\n"); fmt::print( " - Builds all the DGO files described in the DGO description file. See " "`goal_src/builds/dgos.txt` for an example.\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(asm-data-file tool-name \"file-name\")\n"); fmt::print( " - Build a data file. The `tool-name` refers to which data building tool should be used.\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-data)\n"); fmt::print(" - Macro for rebuilding all data files\n"); fmt::print(fmt::emphasis::bold, "\nOther:\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), "(gs)\n"); fmt::print(" - Enter a GOOS REPL\n"); fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), "(set-config! config-name config-value)\n"); fmt::print(" - Used to set compiler configuration\n"); return get_none(); } /*! * Connect the compiler to a target. Takes an optional IP address / port, defaults to * 127.0.0.1 and 8112, which is the local computer and the default port for the DECI2 over IP * implementation. */ Val* Compiler::compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; std::string ip = "127.0.0.1"; int port = DECI2_PORT; bool got_port = false, got_ip = false; for_each_in_list(rest, [&](const goos::Object& o) { if (o.is_string()) { if (got_ip) { throw_compiler_error(form, "listen-to-target can only use 1 IP address"); } got_ip = true; ip = o.as_string()->data; } else if (o.is_int()) { if (got_port) { throw_compiler_error(form, "listen-to-target can only use 1 port number"); } got_port = true; port = o.integer_obj.value; } else { throw_compiler_error(form, "invalid argument to listen-to-target: \"{}\"", o.print()); } }); m_listener.connect_to_target(30, ip, port); return get_none(); } /*! * Send the target a command to reset, which totally resets the state of the target. * Optionally takes a :shutdown command which causes the exec_runtime function of the target * to return after MachineShutdown. */ Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; bool shutdown = false; for_each_in_list(rest, [&](const goos::Object& o) { if (o.is_symbol() && symbol_string(o) == ":shutdown") { shutdown = true; } else { throw_compiler_error(form, "invalid argument to reset-target: \"{}\"", o.print()); } }); m_listener.send_reset(shutdown); return get_none(); } /*! * Send a "poke" message to the target. This can be used to check if the target is still alive and * acknowledges commands, and also tells that target that somebody is connected so it will flush * its outgoing buffers that have been storing data from startup. */ Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {}, {}); m_listener.send_poke(); return get_none(); } /*! * Enter a goos REPL. */ Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {}, {}); m_goos.execute_repl(); return get_none(); } /*! * Set a compiler setting by name. */ Val* Compiler::compile_set_config(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {}); m_settings.set(symbol_string(args.unnamed.at(0)), args.unnamed.at(1)); return get_none(); } /*! * Ignore the "in-package" statement and anything it contains at the top of GOAL files. */ Val* Compiler::compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env) { (void)form; (void)rest; (void)env; return get_none(); } /*! * Build dgo files. Takes a string argument pointing to the DGO description file, which is read * and parsed here. */ Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); va_check(form, args, {goos::ObjectType::STRING}, {}); auto dgo_desc = pair_cdr(m_goos.reader.read_from_file({args.unnamed.at(0).as_string()->data})); for_each_in_list(dgo_desc, [&](const goos::Object& dgo) { DgoDescription desc; auto first = pair_car(dgo); desc.dgo_name = as_string(first); auto dgo_rest = pair_cdr(dgo); for_each_in_list(dgo_rest, [&](const goos::Object& entry) { auto e_arg = get_va(dgo, entry); va_check(dgo, e_arg, {goos::ObjectType::STRING, goos::ObjectType::STRING}, {}); DgoDescription::DgoEntry o; o.file_name = as_string(e_arg.unnamed.at(0)); o.name_in_dgo = as_string(e_arg.unnamed.at(1)); if (o.file_name.substr(o.file_name.length() - 3) != ".go") { desc.entries.push_back(o); } else { // allow data objects to be missing. if (std::filesystem::exists(file_util::get_file_path({"out", "obj", o.file_name}))) { desc.entries.push_back(o); } } }); build_dgo(desc); }); return get_none(); }