Skip to content

Commit 548b1c9

Browse files
committed
#75 wip: xboxビルドテスト v66
1 parent 688383b commit 548b1c9

4 files changed

Lines changed: 251 additions & 8 deletions

File tree

templates/xbox/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ if(MSVC)
137137
# Release でも PDB を生成する (クラッシュダンプ解析でスタックを名前解決するため)
138138
target_compile_options(Next2DXboxHost PRIVATE /Zi)
139139
target_link_options(Next2DXboxHost PRIVATE /DEBUG /OPT:REF /OPT:ICF)
140+
141+
# Release: リンク時コード生成 (LTCG)。翻訳単位を跨いだインライン化が効き、
142+
# 毎フレーム通る V8 バインディング層 (小さな関数の呼び出し連鎖) に有効。
143+
# (VS ジェネレータでは COMPILE_OPTIONS は C++ (ClCompile) にのみ渡り、
144+
# RC (assets.rc) には影響しない。/Zi と同じ流儀)
145+
target_compile_options(Next2DXboxHost PRIVATE $<$<CONFIG:Release>:/GL>)
146+
target_link_options(Next2DXboxHost PRIVATE $<$<CONFIG:Release>:/LTCG>)
147+
148+
# /arch:AVX2: Xbox Series X|S (Zen 2) と Haswell (2013) 以降の PC で動作。
149+
# ソフトラスタ (RasterCore) や pak 解析などホスト C++ の自動ベクトル化が進む。
150+
# Xbox One (Jaguar) は AVX2 非対応のため除外する。
151+
if(NOT CMAKE_GENERATOR_PLATFORM MATCHES "XboxOne")
152+
target_compile_options(Next2DXboxHost PRIVATE /arch:AVX2)
153+
endif()
140154
endif()
141155

142156
# webgpu_dawn は SHARED (DLL)。実行に必要なため exe の隣へステージする

templates/xbox/src/v8/CodeCache.h

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// V8 バイトコードキャッシュ。
2+
//
3+
// player+framework のバンドル (数 MB) は毎起動フルパースがかかり、jitless
4+
// (Ignition) では起動時間の主要因になる。初回起動時に eager コンパイルして
5+
// バイトコードをディスクへ保存し、2 回目以降はそれを消費してパースを省略する。
6+
// 生成物はデータであり動的コード生成ではないため、コンソール (JIT 禁止) でも使える。
7+
//
8+
// - 保存先: %LOCALAPPDATA%\Next2D\codecache\<hash>.v8bc
9+
// (DomShims の localStorage バックエンドと同じ配置方針。環境変数が無ければ無効化)
10+
// - キー: スクリプト名 + ソース内容の FNV-1a。ソースが変われば別ファイルになる。
11+
// - V8 のバージョン/フラグ不一致は V8 自身が検出して reject する (その場で作り直す)。
12+
// - すべてのエラーは graceful degrade (キャッシュ無しの通常コンパイルに落ちる)。
13+
// - 小さいスクリプトはパースが安く I/O の方が高いため対象外 (kMinSourceSize)。
14+
#pragma once
15+
16+
#include <v8.h>
17+
18+
#include <chrono>
19+
#include <cstdint>
20+
#include <cstdio>
21+
#include <cstdlib>
22+
#include <cstring>
23+
#include <filesystem>
24+
#include <fstream>
25+
#include <memory>
26+
#include <string>
27+
#include <vector>
28+
29+
namespace next2d::codecache {
30+
31+
// キャッシュ対象の最小ソースサイズ。bootstrap.js (十数 KB) は対象外、
32+
// ゲーム/player バンドル (数百 KB〜数 MB) が対象になる境界。
33+
inline constexpr size_t kMinSourceSize = 64 * 1024;
34+
35+
inline const std::filesystem::path& CacheDir()
36+
{
37+
static const std::filesystem::path dir = []() -> std::filesystem::path {
38+
const char* base = std::getenv("LOCALAPPDATA");
39+
if (!base || !*base) {
40+
return {};
41+
}
42+
std::error_code ec;
43+
std::filesystem::path d = std::filesystem::path(base) / "Next2D" / "codecache";
44+
std::filesystem::create_directories(d, ec);
45+
if (ec) {
46+
return {};
47+
}
48+
// ゲーム更新でソースが変わるとキャッシュはファイル名ごと変わり、旧ファイルは
49+
// 参照されない孤児になる。30 日アクセスの無い .v8bc を起動時に掃除する
50+
// (Load が使用時に write time を touch するため実質 LRU)。
51+
const auto now = std::filesystem::file_time_type::clock::now();
52+
std::error_code iter_ec;
53+
for (const auto& entry : std::filesystem::directory_iterator(d, iter_ec)) {
54+
std::error_code e2;
55+
if (entry.path().extension() != ".v8bc") {
56+
continue;
57+
}
58+
const auto t = std::filesystem::last_write_time(entry.path(), e2);
59+
if (!e2 && now - t > std::chrono::hours(24 * 30)) {
60+
std::filesystem::remove(entry.path(), e2);
61+
}
62+
}
63+
return d;
64+
}();
65+
return dir;
66+
}
67+
68+
inline uint64_t Fnv1a(const void* data, size_t size, uint64_t hash = 1469598103934665603ull)
69+
{
70+
const auto* p = static_cast<const unsigned char*>(data);
71+
for (size_t i = 0; i < size; ++i) {
72+
hash ^= p[i];
73+
hash *= 1099511628211ull;
74+
}
75+
return hash;
76+
}
77+
78+
inline std::filesystem::path CacheFileFor(const std::string& name, const std::string& source)
79+
{
80+
const std::filesystem::path& dir = CacheDir();
81+
if (dir.empty()) {
82+
return {};
83+
}
84+
uint64_t h = Fnv1a(name.data(), name.size());
85+
h = Fnv1a(source.data(), source.size(), h);
86+
char buf[17] = {};
87+
std::snprintf(buf, sizeof(buf), "%016llx", static_cast<unsigned long long>(h));
88+
return dir / (std::string(buf) + ".v8bc");
89+
}
90+
91+
inline bool Eligible(const std::string& source)
92+
{
93+
return source.size() >= kMinSourceSize && !CacheDir().empty();
94+
}
95+
96+
// キャッシュファイルを読む。戻り値の所有権は ScriptCompiler::Source が取る
97+
// (BufferOwned なので CachedData 破棄時に delete[] される)。無ければ nullptr。
98+
inline v8::ScriptCompiler::CachedData* Load(const std::string& name, const std::string& source)
99+
{
100+
const std::filesystem::path file = CacheFileFor(name, source);
101+
if (file.empty()) {
102+
return nullptr;
103+
}
104+
std::ifstream ifs(file, std::ios::binary);
105+
if (!ifs) {
106+
return nullptr;
107+
}
108+
std::vector<char> buf(
109+
(std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
110+
if (buf.empty()) {
111+
return nullptr;
112+
}
113+
// 使用時に write time を更新し、CacheDir の 30 日掃除を LRU として機能させる
114+
std::error_code ec;
115+
std::filesystem::last_write_time(
116+
file, std::filesystem::file_time_type::clock::now(), ec);
117+
118+
auto* data = new uint8_t[buf.size()];
119+
std::memcpy(data, buf.data(), buf.size());
120+
return new v8::ScriptCompiler::CachedData(
121+
data, static_cast<int>(buf.size()),
122+
v8::ScriptCompiler::CachedData::BufferOwned);
123+
}
124+
125+
inline void Store(const std::string& name, const std::string& source,
126+
const v8::ScriptCompiler::CachedData* data)
127+
{
128+
if (!data || !data->data || data->length <= 0) {
129+
return;
130+
}
131+
const std::filesystem::path file = CacheFileFor(name, source);
132+
if (file.empty()) {
133+
return;
134+
}
135+
std::ofstream ofs(file, std::ios::binary | std::ios::trunc);
136+
if (ofs) {
137+
ofs.write(reinterpret_cast<const char*>(data->data), data->length);
138+
}
139+
}
140+
141+
// classic script をキャッシュ込みでコンパイルする。
142+
// - キャッシュ有り: kConsumeCodeCache で消費 (V8 が reject したら作り直して保存)
143+
// - キャッシュ無し: kEagerCompile で全関数を一括コンパイルして保存
144+
// (初回はその分遅いが、以降の起動は lazy compile も含めて丸ごと省ける)
145+
// - 対象外サイズ: 従来どおりの lazy コンパイル
146+
inline v8::MaybeLocal<v8::Script> Compile(v8::Local<v8::Context> ctx,
147+
v8::Local<v8::String> src,
148+
v8::ScriptOrigin& origin,
149+
const std::string& name,
150+
const std::string& source_str)
151+
{
152+
if (!Eligible(source_str)) {
153+
v8::ScriptCompiler::Source source(src, origin);
154+
return v8::ScriptCompiler::Compile(ctx, &source);
155+
}
156+
157+
if (v8::ScriptCompiler::CachedData* cached = Load(name, source_str)) {
158+
v8::ScriptCompiler::Source source(src, origin, cached);
159+
v8::Local<v8::Script> script;
160+
if (!v8::ScriptCompiler::Compile(
161+
ctx, &source, v8::ScriptCompiler::kConsumeCodeCache).ToLocal(&script)) {
162+
return {};
163+
}
164+
if (source.GetCachedData() && source.GetCachedData()->rejected) {
165+
std::unique_ptr<v8::ScriptCompiler::CachedData> fresh(
166+
v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
167+
Store(name, source_str, fresh.get());
168+
}
169+
return script;
170+
}
171+
172+
v8::ScriptCompiler::Source source(src, origin);
173+
v8::Local<v8::Script> script;
174+
if (!v8::ScriptCompiler::Compile(
175+
ctx, &source, v8::ScriptCompiler::kEagerCompile).ToLocal(&script)) {
176+
return {};
177+
}
178+
std::unique_ptr<v8::ScriptCompiler::CachedData> fresh(
179+
v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
180+
Store(name, source_str, fresh.get());
181+
return script;
182+
}
183+
184+
// ES module 版。規約は Compile と同じ。
185+
inline v8::MaybeLocal<v8::Module> CompileModule(v8::Isolate* isolate,
186+
v8::Local<v8::String> src,
187+
v8::ScriptOrigin& origin,
188+
const std::string& name,
189+
const std::string& source_str)
190+
{
191+
if (!Eligible(source_str)) {
192+
v8::ScriptCompiler::Source source(src, origin);
193+
return v8::ScriptCompiler::CompileModule(isolate, &source);
194+
}
195+
196+
if (v8::ScriptCompiler::CachedData* cached = Load(name, source_str)) {
197+
v8::ScriptCompiler::Source source(src, origin, cached);
198+
v8::Local<v8::Module> module;
199+
if (!v8::ScriptCompiler::CompileModule(
200+
isolate, &source, v8::ScriptCompiler::kConsumeCodeCache).ToLocal(&module)) {
201+
return {};
202+
}
203+
if (source.GetCachedData() && source.GetCachedData()->rejected) {
204+
std::unique_ptr<v8::ScriptCompiler::CachedData> fresh(
205+
v8::ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript()));
206+
Store(name, source_str, fresh.get());
207+
}
208+
return module;
209+
}
210+
211+
v8::ScriptCompiler::Source source(src, origin);
212+
v8::Local<v8::Module> module;
213+
if (!v8::ScriptCompiler::CompileModule(
214+
isolate, &source, v8::ScriptCompiler::kEagerCompile).ToLocal(&module)) {
215+
return {};
216+
}
217+
std::unique_ptr<v8::ScriptCompiler::CachedData> fresh(
218+
v8::ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript()));
219+
Store(name, source_str, fresh.get());
220+
return module;
221+
}
222+
223+
} // namespace next2d::codecache

templates/xbox/src/v8/V8Runtime.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "V8Runtime.h"
22

33
#include "V8Util.h"
4+
#include "CodeCache.h"
45
#include "HostContext.h"
56
#include "EmbeddedAssets.h"
67
#include "bindings/Bindings.h"
@@ -175,8 +176,9 @@ bool V8Runtime::RunScript(const std::string& source, const std::string& name)
175176
v8::ScriptOrigin origin(v8util::Str(isolate_, name));
176177
v8::Local<v8::String> src = v8util::Str(isolate_, source);
177178

179+
// バイトコードキャッシュ込みでコンパイル (2回目以降の起動でパースを省略)
178180
v8::Local<v8::Script> script;
179-
if (!v8::Script::Compile(ctx, src, &origin).ToLocal(&script)) {
181+
if (!codecache::Compile(ctx, src, origin, name, source).ToLocal(&script)) {
180182
ReportException(&try_catch);
181183
return false;
182184
}
@@ -222,9 +224,10 @@ v8::MaybeLocal<v8::Module> V8Runtime::LoadModule(const std::string& path,
222224
false, false, /*is_module*/ true
223225
);
224226

225-
v8::ScriptCompiler::Source compiler_source(src, origin);
227+
// バイトコードキャッシュ込みでコンパイル。アプリ本体 (数 MB の ESM バンドル) の
228+
// 毎起動フルパースを 2 回目以降省略する (起動時間の主要因)。
226229
v8::Local<v8::Module> module;
227-
if (!v8::ScriptCompiler::CompileModule(isolate_, &compiler_source).ToLocal(&module)) {
230+
if (!codecache::CompileModule(isolate_, src, origin, abs, source).ToLocal(&module)) {
228231
return v8::MaybeLocal<v8::Module>();
229232
}
230233

templates/xbox/src/worker/WorkerRuntime.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "bindings/ImageSource.h"
88
#include "platform/ImageTypes.h"
99
#include "v8/V8Util.h"
10+
#include "v8/CodeCache.h"
1011

1112
#include <cstdlib>
1213
#include <cstring>
@@ -535,8 +536,10 @@ bool WorkerInstance::Start()
535536
v8::TryCatch tc(isolate_);
536537
v8::ScriptOrigin origin(Str(isolate_, url_));
537538
v8::Local<v8::String> code = Str(isolate_, source);
539+
// バイトコードキャッシュ込みでコンパイル。レンダラ worker (player の描画バンドル)
540+
// が最大のパース対象。キーは自己修復パッチ適用後のソースなので整合する。
538541
v8::Local<v8::Script> script;
539-
if (!v8::Script::Compile(context, code, &origin).ToLocal(&script) ||
542+
if (!codecache::Compile(context, code, origin, url_, source).ToLocal(&script) ||
540543
script->Run(context).IsEmpty()) {
541544
std::cerr << "[Worker] script eval failed: " << ShortUrl(url_) << std::endl;
542545
if (tc.HasCaught()) {
@@ -573,9 +576,9 @@ v8::MaybeLocal<v8::Module> WorkerInstance::LoadModule(const std::string& url)
573576

574577
v8::ScriptOrigin origin(Str(isolate_, url), 0, 0, false, -1,
575578
v8::Local<v8::Value>(), false, false, /*is_module*/ true);
576-
v8::ScriptCompiler::Source source(Str(isolate_, *src), origin);
577579
v8::Local<v8::Module> module;
578-
if (!v8::ScriptCompiler::CompileModule(isolate_, &source).ToLocal(&module)) {
580+
if (!codecache::CompileModule(isolate_, Str(isolate_, *src), origin, url, *src)
581+
.ToLocal(&module)) {
579582
std::cerr << "[Worker] module compile failed: " << ShortUrl(url) << std::endl;
580583
return v8::MaybeLocal<v8::Module>();
581584
}
@@ -622,9 +625,9 @@ bool WorkerInstance::EvaluateModule(v8::Local<v8::Context> context, const std::s
622625
// 直接コンパイルする(assets 経由の LoadModule は依存モジュール用)。
623626
v8::ScriptOrigin origin(Str(isolate_, url_), 0, 0, false, -1,
624627
v8::Local<v8::Value>(), false, false, /*is_module*/ true);
625-
v8::ScriptCompiler::Source src(Str(isolate_, source), origin);
626628
v8::Local<v8::Module> module;
627-
if (!v8::ScriptCompiler::CompileModule(isolate_, &src).ToLocal(&module)) {
629+
if (!codecache::CompileModule(isolate_, Str(isolate_, source), origin, url_, source)
630+
.ToLocal(&module)) {
628631
std::cerr << "[Worker] module compile failed: " << ShortUrl(url_) << std::endl;
629632
return false;
630633
}

0 commit comments

Comments
 (0)