--- nono/lib/mainapp.cpp 2026/04/29 17:05:39 1.1.1.20 +++ nono/lib/mainapp.cpp 2026/04/29 17:05:52 1.1.1.23 @@ -16,14 +16,14 @@ #include "monitor.h" #include "mystring.h" #include "mythread.h" -#include "sram.h" +#include "uimessage.h" #include "vm_luna.h" #include "vm_news.h" #include "vm_virt68k.h" #include "vm_x68k.h" -#include #include #include +#include #include #include #include @@ -32,6 +32,8 @@ #include #endif +static std::string maptocsv(const std::vector& list); + // グローバルインスタンス MainApp gMainApp; @@ -105,9 +107,13 @@ MainApp::ShowHelp(bool all) const if (all) { printf("\n(options for developers)\n"); - p("-b [,][,]"); + p("--b [,][,] (or -b)"); p(" set breakpoint. is either 'main'(default) or 'xp'"); + p("--bi [,][:][,]"); + p(" set instruction breakpoint."); p("--bi-exg set instruction breakpoint on 'exg sp,sp'"); + p("--bv [,][-][,]"); + p(" set vector breakpoint."); p("-C output log to console"); p("--console-log specify the console output log"); p("-d debugger prompt on startup"); @@ -126,9 +132,10 @@ MainApp::ShowHelp(bool all) const // 0x80 から始めておく。 enum { OPTstart = 0x80 - 1, + OPT_bi, OPT_bi_exg, + OPT_bv, OPT_console_log, - OPT_create_sram, OPT_fd0, OPT_fd1, OPT_fontsize, @@ -140,9 +147,11 @@ enum { OPT_show_hostnet, }; static struct option longopts[] = { + { "b", required_argument, NULL, 'b' }, + { "bi", required_argument, NULL, OPT_bi }, { "bi-exg", no_argument, NULL, OPT_bi_exg }, + { "bv", required_argument, NULL, OPT_bv }, { "console-log", required_argument, NULL, OPT_console_log }, - { "create-sram", no_argument, NULL, OPT_create_sram }, { "fd0", required_argument, NULL, OPT_fd0 }, { "fd1", required_argument, NULL, OPT_fd1 }, { "fontsize", required_argument, NULL, OPT_fontsize }, @@ -163,13 +172,13 @@ static struct option longopts[] = { // MainApp::PASS .. 実行を継続(通常パス) // EXIT_SUCCESS / EXIT_FAILURE .. この終了コードでアプリケーションを終了 int -MainApp::Init1(bool is_cli_, int ac, char *av[]) +MainApp::Init1(int ac, char *av[], std::string (*conv)(const std::string&)) { int rv; - is_cli = is_cli_; + is_cli = (conv == NULL); - rv = Init1a(ac, av); + rv = Init1a(ac, av, conv); if (rv != MainApp::PASS) { return rv; } @@ -181,7 +190,7 @@ MainApp::Init1(bool is_cli_, int ac, cha // VM の初期化、ステージ1 の前半。 // Config.Fix まで。 int -MainApp::Init1a(int ac, char *av[]) +MainApp::Init1a(int ac, char *av[], std::string (*conv)(const std::string&)) { int rv; @@ -196,16 +205,31 @@ MainApp::Init1a(int ac, char *av[]) return rv; } - // ログ機構は引数処理後なるはや - // CLI ならログは常に標準出力へ(も)出力。 - // GUI なら -C で指定。 - logger.reset(new Logger()); - logger->UseStdout(IsCLI() ? true : log_to_console); + // スレッドマネージャ (ログより前) + threadmanager.reset(new ThreadManager()); + // ここでメインスレッド関連の設定。 + PTHREAD_SETNAME("Main"); + threadmanager->RegistThread(NULL, "Main"); // モニタマネージャ (VM 作成より前、もしかしたら Config より前?) pMonitorManager.reset(new MonitorManager()); gMonitorManager = pMonitorManager.get(); + // ログ機構は引数処理後なるはや…にしたいがモニタマネージャは必要。 + // CLI ならログは常に標準出力へ(も)出力。 + // GUI なら -C で指定。 + logger.reset(new Logger()); + if (IsCLI()) { + logger->UseStdout(true); + } else { + logger->UseStdout(log_to_console); + logger->SetConverter(conv); + } + logger->StartThread(); + + // UIMessage は VM 起動前ならどこでもいいはず。 + uimessage.reset(new UIMessage()); + // 設定を作成。 // c0. コンストラクタで初期値を用意。 pConfig.reset(new Config()); @@ -263,13 +287,19 @@ MainApp::Init1a(int ac, char *av[]) gConfig->Update("ram-size=16", "-S"); gConfig->Update("luna-video-plane=1", "-S"); // ホスト側には干渉しない。 - gConfig->Update("hostcom-driver=none", "-S"); + gConfig->Update("hostcom0-driver=none", "-S"); + gConfig->Update("hostcom1-driver=none", "-S"); + gConfig->Update("hostcom2-driver=none", "-S"); gConfig->Update("hostnet-driver=none", "-S"); } // ログ表示のためだけのオブジェクト hostcpu.reset(new Object(OBJ_HOSTCPU)); + // この後の CheckCPU() で hostcpu のログを出したいので、 + // 先にログレベルを分かる分だけ処理する。 + ParseLogoptEarly(); + // ホスト CPU の機能チェック。 if (CheckCPU() == false) { return EXIT_FAILURE; @@ -300,16 +330,6 @@ MainApp::Init1a(int ac, char *av[]) return EXIT_FAILURE; } - // 0.5. VM が確定したところで SRAM 作成。 - if (create_sram) { - if (vmtype == VMType::X68030) { - return CreateSRAM(); - } else { - warnx("--create-sram is only for X68030 mode"); - return EXIT_FAILURE; - } - } - // 1. VM 作成 switch (vmtype) { case VMType::X68030: @@ -371,6 +391,16 @@ MainApp::Init1b() return EXIT_SUCCESS; } + // 古い隠しオプションは書式に互換性がないので捨てる。 + // --show-config の後にしないといけない。 + const ConfigItem& item_cpu_aff = gConfig->Find(".host-cpu-affinity"); + if (item_cpu_aff.GetFrom() != ConfigItem::FromInitial) { + item_cpu_aff.Err( + "Obsolete. Replace it with new syntax 'hostcpu-fastcore'. " + "(Please see document)"); + return false; + } + // 4.2 ログレベルを設定。コンストラクト後すぐに行う。 // -L help もここで処理。 if (!ParseLogopt()) { @@ -435,19 +465,11 @@ MainApp::Init2() return false; } - // メインスレッド名を設定 - PTHREAD_SETNAME("Main"); - // 8. スレッド開始 if (!pVM->StartThread()) { return false; } - // 9. 起動時設定の適用 - if (!pVM->Apply()) { - return false; - } - return true; } @@ -566,20 +588,24 @@ MainApp::ParseOpt(int ac, char *av[]) config_options.emplace_back(optarg, "-V"); break; + case OPT_bi: + debug_breakinst.push_back(optarg); + break; + case OPT_bi_exg: debug_breakinst_exg = true; break; + case OPT_bv: + debug_breakvec.push_back(optarg); + break; + case OPT_console_log: // この時点ではコンソールを持つ VM かどうかが分からないので // コンソールのない設定でもエラーを出さない(出せない)。 console_logfile = optarg; break; - case OPT_create_sram: - create_sram = true; - break; - case OPT_fontsize: { auto line = string_format("monitor-fontsize=%s", optarg); @@ -701,54 +727,16 @@ MainApp::AbsPath(const char *path) bool MainApp::CheckCPU() { - const ConfigItem& item_aff = gConfig->Find(".host-cpu-affinity"); - std::string cfg_aff = item_aff.AsString(); - - if (cfg_aff.empty() || cfg_aff == "auto") { - // 指定なし - // (auto も今の所指定なしと同じ) - cpu_affinity = CPUAffinity::None; - } else if (cfg_aff.find(':') == std::string::npos) { - item_aff.Err(); - } else { - // 書式は ":"。 - // は "low", "high", "perf" のいずれか。 - // は "0,3-5,7" みたいな感じ。 - std::vector vals = string_split(cfg_aff, ':'); - std::string key = string_trim(vals[0]); - std::string csv = vals[1]; - if (key == "low") { - cpu_affinity = CPUAffinity::Low; - } else if (key == "high") { - cpu_affinity = CPUAffinity::High; - } else if (key == "perf") { - cpu_affinity = CPUAffinity::Perf; - } else { - item_aff.Err("Invalid affinity keyword"); - } - cpu_list.resize(std::thread::hardware_concurrency()); - auto errmsg = ParseCPUList(cpu_list, csv); - if (errmsg.empty() == false) { - item_aff.Err(errmsg); - return false; - } - - // 全部 true/false なら、これは意味がないのでクリアする。 - uint num = std::count(cpu_list.begin(), cpu_list.end(), true); - if (num == 0) { - item_aff.Err("No valid cores specified (Ignoring it)"); - cpu_affinity = CPUAffinity::None; - } else if (num == cpu_list.size()) { - item_aff.Err("All cores specified (Ignoring it)"); - cpu_affinity = CPUAffinity::None; - } + // ヘテロ構成とか。 + if (CheckCPUAffinity() == false) { + return false; } #if defined(HAVE_AVX2) // AVX2 detect_avx2 = ::DetectAVX2(); - const ConfigItem& item_avx2 = gConfig->Find("host-avx2"); + const ConfigItem& item_avx2 = gConfig->Find("hostcpu-avx2"); std::string cfg_avx2 = item_avx2.AsString(); if (cfg_avx2 == "auto") { @@ -765,7 +753,7 @@ MainApp::CheckCPU() // NEON は aarch64 なら必ずある? detect_neon = true; - const ConfigItem& item_neon = gConfig->Find("host-neon"); + const ConfigItem& item_neon = gConfig->Find("hostcpu-neon"); std::string cfg_neon = item_neon.AsString(); if (cfg_neon == "auto") { @@ -782,16 +770,76 @@ MainApp::CheckCPU() return true; } -// CPU 番号リストの文字列からビット配列を作って返す。 -// cpulist は CPU コア数で初期化(resize)しておくこと。 +// CPU アフィニティを調べる。 +// 成功なら affinity_policy (と fastcore) をセットして true を返す。 +// 失敗ならエラーを表示して false を返す。 +bool +MainApp::CheckCPUAffinity() +{ + affinity_policy = AffinityPolicy::Neutral; + fastcore.resize(std::thread::hardware_concurrency()); + + const ConfigItem& item_aff = gConfig->Find("hostcpu-fastcore"); + std::string cfg_aff = item_aff.AsString(); + + if (cfg_aff == "none") { + // 何もしない。 + + } else if (cfg_aff == "auto") { +#if defined(__x86_64__) + if (ThreadManager::DetectCPUAffinity_x86(fastcore) == false) { + // 自動認識に失敗しただけなので、なかったことにして進む。 + std::fill(fastcore.begin(), fastcore.end(), false); + } + + // 区別出来た時だけ Binding にする。 + uint num = std::count(fastcore.begin(), fastcore.end(), true); + if (num != 0 && num != fastcore.size()) { + affinity_policy = AffinityPolicy::Binding; + } +#else + // none と同じ。 +#endif + + } else { + // 手動設定。 + // "0,3-5,7" みたいな感じで高性能側 CPU の番号を列挙。 + affinity_policy = AffinityPolicy::Binding; + auto errmsg = ParseCPUList(cfg_aff); + if (errmsg.empty() == false) { + item_aff.Err(errmsg); + return false; + } + + uint num = std::count(fastcore.begin(), fastcore.end(), true); + if (num == 0 || num == fastcore.size()) { + // 区別がないとたぶん割り当て出来ないスレッドが出来てしまうので + // エラーにする? + item_aff.Err("All cores have the same affinity?"); + return false; + } + } + + if (hostcpu->loglevel >= 1) { + if (affinity_policy == AffinityPolicy::Neutral) { + hostcpu->putmsgn("%s: affinity_policy=neutral", __func__); + } else { + hostcpu->putmsgn("%s: affinity_policy=binding", __func__); + hostcpu->putmsgn("%s: fast_cores=%s", __func__, + maptocsv(fastcore).c_str()); + } + } + + return true; +} + +// CPU 番号リストの文字列からビット配列を作って fastcore にセットする。 +// fastcore は事前にコア数分確保してあること。 // 成功すれば string.empty を返す。 // 失敗すればエラーメッセージを返す。 std::string -MainApp::ParseCPUList(std::vector& cpulist, const std::string& input) +MainApp::ParseCPUList(const std::string& input) { - if (input.empty()) { - return " must be specified"; - } // 設定は "0,3-5,7,10-" のような書式 std::vector csv = string_split(input, ','); @@ -821,7 +869,7 @@ MainApp::ParseCPUList(std::vector& return "syntax error"; } start = 0; - } else if (r[r.size() - 1] == '-') { + } else if (r.back() == '-') { // "a-" なら範囲 [a, MAX] str = &r[0]; errno = 0; @@ -829,7 +877,7 @@ MainApp::ParseCPUList(std::vector& if (endp == str || endp != &r[r.size() - 1] || errno == ERANGE) { return "syntax error"; } - last = cpulist.size(); + last = fastcore.size() - 1; } else { // "a-b" なら範囲 [a, b] str = &r[0]; @@ -852,22 +900,56 @@ MainApp::ParseCPUList(std::vector& } } - if (start >= cpulist.size()) { + if (start >= fastcore.size()) { return string_format("cpu number %u exceeds number of cores(%zu)", - start, cpulist.size()); + start, fastcore.size()); } - if (last >= cpulist.size()) { + if (last >= fastcore.size()) { return string_format("cpu number %u exceeds number of cores(%zu)", - start, cpulist.size()); + last, fastcore.size()); } for (uint n = start; n <= last; n++) { - cpulist[n] = true; + fastcore[n] = true; } } return ""; } +// srclist を "0-8,10" みたいな可読文字列にする。 +static std::string +maptocsv(const std::vector& srclist) +{ + std::string rv; + int start = -1; + + // 最後が true で終わっている場合の処理分けを減らすため、 + // 末尾に false を足しておく。 + std::vector list = srclist; + list.push_back(false); + + for (uint i = 0; i < list.size(); i++) { + if (start == -1) { + if (list[i] == true) { + if (rv.size() != 0) { + rv += ','; + } + rv += string_format("%u", i); + start = i; + } + } else { + if (list[i] == false) { + int end = i - 1; + if (start < end) { + rv += string_format("-%u", end); + } + start = -1; + } + } + } + return rv; +} + // バージョンを表示 void MainApp::ShowVersion() const @@ -903,10 +985,7 @@ MainApp::ParseLogopt() for (const auto& item : items) { if (item == "help") { std::vector list = GetLogNames(); - // less したいだろうから stderr ではなく stdout に出力する - for (const auto& name : list) { - printf(" %s\n", name.c_str()); - } + ShowHelpList(stdout, list); return false; } } @@ -921,6 +1000,65 @@ MainApp::ParseLogopt() return true; } +// list を一覧表示をする。-Lhelp, -Mhelp 用。 +/*static*/ void +MainApp::ShowHelpList(FILE *fp, const std::vector& list) +{ + int width = 0; + + // 端末なら桁数を取得。 + int fd = fileno(fp); + if (isatty(fd)) { + struct winsize ws; + if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { + width = ws.ws_col; + } + } + + if (width == 0) { + // パイプかリダイレクトなので垂れ流す。 + for (const auto& name : list) { + fprintf(fp, " %s\n", name.c_str()); + } + } else { + // 桁数が取得できたので ls(1) みたいに表示する。 + + uint maxlen = 0; + for (const auto& name : list) { + maxlen = std::max(maxlen, (uint)name.size()); + } + // 余白分。 + maxlen += 2; + + const int LeftPadding = 1; + int ncol = (width - LeftPadding) / maxlen; + int nrow = (list.size() + ncol - 1) / ncol; + for (int row = 0; row < nrow; row++) { + fprintf(fp, " "); + for (int col = 0; col < ncol; col++) { + int idx = col * nrow + row; + if (idx < list.size()) { + fprintf(fp, "%-*s", maxlen, list[idx].c_str()); + } + } + fprintf(fp, "\n"); + } + } +} + +// ログレベル指定文字列を処理する。早期処理用。 +// この上の ParseLogopt() と概ね同じだが、"help" を含めて知らないものは +// スルーし、エラーが起きてもエラーメッセージも出力しない。 +void +MainApp::ParseLogoptEarly() +{ + std::vector items; + items = string_split(logopt.c_str(), ','); + + std::string errmsg; + SetLogopt(items, &errmsg); +} + // ログレベルを設定する。 // ログレベル指定文字列を 1つずつに分解したリスト items を処理する。 // "help" があるケースはここに来るまでに処理してあるので、ここには来ない。 @@ -990,33 +1128,22 @@ MainApp::SetLogopt1(const std::string& i } return true; } - // "fdd" なら "fdd*" 全部にする - if (name == "fdd") { - for (auto obj : gMainApp.GetObjects()) { - if (strncasecmp(obj->GetName().c_str(), "fdd", 3) == 0) { - obj->SetLogLevel(level); - } - } - return true; - } - // "bankram" も "bankram[01]" 両方にする - if (name == "bankram") { - for (auto obj : gMainApp.GetObjects()) { - if (strncasecmp(obj->GetName().c_str(), "bankram", 7) == 0) { - obj->SetLogLevel(level); - } - } - return true; - } - // "gfpic" も "gfpic*" 全部にする - if (name == "gfpic") { - for (auto obj : gMainApp.GetObjects()) { - if (strncasecmp(obj->GetName().c_str(), "gfpic", 5) == 0) { - obj->SetLogLevel(level); - } - } - return true; - } + + // "" なら "*" 全部に展開する。 +#define EXPAND(key) do { \ + if (name == key) { \ + for (auto obj : gMainApp.GetObjects()) { \ + if (strncasecmp(obj->GetName().c_str(), key, strlen(key)) == 0) \ + obj->SetLogLevel(level); \ + } \ + return true; \ + } \ +} while (0) + EXPAND("bankram"); + EXPAND("fdd"); + EXPAND("gfpic"); + EXPAND("hostcom"); + EXPAND("hostnet"); // エイリアスリストを作る using aliaslist_t = std::vector>; @@ -1208,48 +1335,6 @@ MainApp::NormalizePath(const std::string return newpath; } -// 初回起動時用に SRAM.DAT を作成する。 -// 戻り値は EXIT_SUCCESS / EXIT_FAILURE。 -int -MainApp::CreateSRAM() -{ - char buf[16 * 1024]; - std::string filename; - autofd fd; - int r; - - filename = GetVMDir() + "SRAM.DAT"; - - // 存在確認のため一旦読み込み専用で開いてみる。 - // オープン出来たら何もしない。 - fd = open(filename.c_str(), O_RDONLY); - if (fd >= 0) { - warnx("%s: Already exists", filename.c_str()); - return EXIT_FAILURE; - } - - fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - warn("%s: open failed", filename.c_str()); - return EXIT_FAILURE; - } - - memset(&buf[0], 0, sizeof(buf)); - for (int i = 0; i < SRAMDevice::InitialData.size(); i++) { - buf[i] = SRAMDevice::InitialData[i]; - } - - r = write(fd, buf, sizeof(buf)); - if (r < 0) { - warn("%s: write failed", filename.c_str()); - return EXIT_FAILURE; - } - - fd.Close(); - warnx("created %s", filename.c_str()); - return EXIT_SUCCESS; -} - // 現在の VM が指定のケーパビリティを持っているか? bool MainApp::Has(VMCap cap) const