@auto_js/v8 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +4 -1
- package/_module.cc +1 -0
- package/evaluation/script.cc +3 -5
- package/evaluation/script.h.cc +2 -2
- package/handle/collected_handle.cc +1 -1
- package/handle/value_of.cc +7 -3
- package/isolated/agent.cc +41 -23
- package/isolated/agent.h.cc +14 -10
- package/isolated/cluster.cc +3 -2
- package/isolated/cluster.h.cc +8 -3
- package/isolated/executor.cc +31 -0
- package/isolated/executor.h.cc +34 -0
- package/isolated/memory_policy.cc +49 -0
- package/isolated/memory_policy.h.cc +48 -0
- package/package.json +3 -3
- package/platform/foreground_runner.cc +3 -22
- package/platform/foreground_runner.h.cc +0 -2
- package/platform/task_runner.cc +1 -1
- package/support/callback_storage.cc +1 -1
- package/support/error.h.cc +12 -7
- package/transfer/accept.h.cc +21 -0
- package/transfer/visit.cc +44 -1
- package/value/fixed_array.cc +1 -1
- package/value/primitive.cc +1 -1
- package/platform/allocator.h.cc +0 -22
package/CMakeLists.txt
CHANGED
|
@@ -63,6 +63,8 @@ if(v8_js STREQUAL isolated_v8_js)
|
|
|
63
63
|
isolated/agent.cc
|
|
64
64
|
isolated/clock.cc
|
|
65
65
|
isolated/cluster.cc
|
|
66
|
+
isolated/executor.cc
|
|
67
|
+
isolated/memory_policy.cc
|
|
66
68
|
isolated/platform.cc
|
|
67
69
|
platform/delegate.cc
|
|
68
70
|
platform/foreground_runner.cc
|
|
@@ -74,8 +76,9 @@ if(v8_js STREQUAL isolated_v8_js)
|
|
|
74
76
|
isolated/agent.h.cc
|
|
75
77
|
isolated/clock.h.cc
|
|
76
78
|
isolated/cluster.h.cc
|
|
79
|
+
isolated/executor.h.cc
|
|
80
|
+
isolated/memory_policy.h.cc
|
|
77
81
|
isolated/platform.h.cc
|
|
78
|
-
platform/allocator.h.cc
|
|
79
82
|
platform/delegate.h.cc
|
|
80
83
|
platform/foreground_runner.h.cc
|
|
81
84
|
platform/scheduler.cc
|
package/_module.cc
CHANGED
|
@@ -24,6 +24,7 @@ export import :evaluation.script;
|
|
|
24
24
|
export import :isolated.agent;
|
|
25
25
|
export import :isolated.clock;
|
|
26
26
|
export import :isolated.cluster;
|
|
27
|
+
export import :isolated.memory_policy;
|
|
27
28
|
export import :isolated.platform;
|
|
28
29
|
export import :platform.delegate;
|
|
29
30
|
export import :platform.foreground_runner;
|
package/evaluation/script.cc
CHANGED
|
@@ -20,11 +20,9 @@ auto script::compile(context_lock_witness lock, v8::Local<v8::String> code_strin
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
auto script::run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) ->
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return js::transfer_out<js::value_t>(unmaybe(maybe_result), lock);
|
|
27
|
-
});
|
|
23
|
+
auto script::run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) -> v8::Local<v8::Value> {
|
|
24
|
+
auto maybe_result = script->BindToCurrentContext()->Run(lock.context());
|
|
25
|
+
return unmaybe(maybe_result);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
} // namespace js::iv8
|
package/evaluation/script.h.cc
CHANGED
|
@@ -10,12 +10,12 @@ namespace js::iv8 {
|
|
|
10
10
|
export struct script {
|
|
11
11
|
public:
|
|
12
12
|
using expected_script_type = std::expected<v8::Local<v8::UnboundScript>, js::error_value>;
|
|
13
|
-
using expected_value_type = std::expected<js::value_t, js::error_value
|
|
13
|
+
using expected_value_type = std::optional<std::expected<js::value_t, js::error_value>>;
|
|
14
14
|
|
|
15
15
|
// nb: It is undocumented (and even mentions "context independent"), but the script compiler
|
|
16
16
|
// actually needs a context because it can throw an error and *that* would need a context.
|
|
17
17
|
static auto compile(context_lock_witness lock, auto code_string, source_origin source_origin) -> expected_script_type;
|
|
18
|
-
static auto run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) ->
|
|
18
|
+
static auto run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) -> v8::Local<v8::Value>;
|
|
19
19
|
|
|
20
20
|
private:
|
|
21
21
|
static auto compile(context_lock_witness lock, v8::Local<v8::String> code_string, source_origin source_origin) -> expected_script_type;
|
package/handle/value_of.cc
CHANGED
|
@@ -9,9 +9,9 @@ namespace js::iv8 {
|
|
|
9
9
|
template <class Tag>
|
|
10
10
|
struct value_specialization;
|
|
11
11
|
|
|
12
|
-
// nb: This corresponds to `js::napi::
|
|
13
|
-
// `js::napi::
|
|
14
|
-
template <class Tag>
|
|
12
|
+
// nb: This corresponds to `js::napi::value_of<T>`. `v8::Local<T>` is more like
|
|
13
|
+
// `js::napi::local_of<T>`.
|
|
14
|
+
export template <class Tag>
|
|
15
15
|
class value_of : public value_specialization<Tag>::type {
|
|
16
16
|
public:
|
|
17
17
|
using value_specialization<Tag>::type::type;
|
|
@@ -50,9 +50,13 @@ struct value_specialization<date_tag> : std::type_identity<class value_for_date>
|
|
|
50
50
|
template <>
|
|
51
51
|
struct value_specialization<external_tag> : std::type_identity<class value_for_external> {};
|
|
52
52
|
|
|
53
|
+
// TODO: value_for_record
|
|
53
54
|
template <>
|
|
54
55
|
struct value_specialization<object_tag> : std::type_identity<class value_for_object> {};
|
|
55
56
|
|
|
57
|
+
template <>
|
|
58
|
+
struct value_specialization<list_tag> : std::type_identity<class value_for_object> {};
|
|
59
|
+
|
|
56
60
|
template <>
|
|
57
61
|
struct value_specialization<array_buffer_tag> : std::type_identity<class value_for_array_buffer> {};
|
|
58
62
|
|
package/isolated/agent.cc
CHANGED
|
@@ -2,7 +2,6 @@ module;
|
|
|
2
2
|
#include <cassert>
|
|
3
3
|
module v8_js;
|
|
4
4
|
import :isolated.agent;
|
|
5
|
-
import :platform.allocator;
|
|
6
5
|
import std;
|
|
7
6
|
|
|
8
7
|
namespace js::iv8::isolated {
|
|
@@ -25,7 +24,7 @@ auto agent_storage::release(std::shared_ptr<agent_storage> storage) -> void {
|
|
|
25
24
|
agent_handle::agent_handle(std::shared_ptr<agent_host> host) :
|
|
26
25
|
host_{host} {
|
|
27
26
|
if (host && host->handle_count_.fetch_add(1, std::memory_order_relaxed) == 0) {
|
|
28
|
-
|
|
27
|
+
host->self_handle_.store(std::move(host), std::memory_order_relaxed);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -46,12 +45,29 @@ agent_handle::~agent_handle() {
|
|
|
46
45
|
host->handle_count_.load(std::memory_order_acquire) == 1 ||
|
|
47
46
|
host->handle_count_.fetch_sub(1, std::memory_order_acq_rel) == 1
|
|
48
47
|
) {
|
|
49
|
-
host->self_handle_.
|
|
48
|
+
host->self_handle_.store(nullptr, std::memory_order_relaxed);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
auto agent_handle::dispose(dispose_callback_type after_callback) -> void {
|
|
55
|
+
if (auto host = host_.lock()) {
|
|
56
|
+
assert(!host->dispose_callback_);
|
|
57
|
+
host->dispose_callback_ = std::move(after_callback);
|
|
58
|
+
host->executor_.terminate();
|
|
59
|
+
host->self_handle_.store(nullptr, std::memory_order_release);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
auto agent_handle::lock_host() const -> std::shared_ptr<agent_host> {
|
|
64
|
+
auto host = host_.lock();
|
|
65
|
+
if (host && host->executor_.is_terminated()) {
|
|
66
|
+
host.reset();
|
|
67
|
+
}
|
|
68
|
+
return host;
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
// `agent_host`
|
|
56
72
|
agent_host::agent_host(
|
|
57
73
|
destroy_callback_type destroy_callback,
|
|
@@ -59,27 +75,28 @@ agent_host::agent_host(
|
|
|
59
75
|
behavior_params params
|
|
60
76
|
) :
|
|
61
77
|
storage_{std::move(storage)},
|
|
62
|
-
|
|
63
|
-
// memory diagnostics though?
|
|
64
|
-
array_buffer_allocator_{std::make_shared<platform::data_block_allocator>()},
|
|
65
|
-
isolate_{v8::Isolate::Allocate()},
|
|
78
|
+
memory_policy_{std::move(params.memory_policy)},
|
|
66
79
|
clock_{params.clock},
|
|
67
80
|
destroy_callback_{destroy_callback},
|
|
68
81
|
reset_handle_callback_{reset_handle_type{util::fn<&agent_host::remote_expiration_callback>, *this}},
|
|
69
82
|
random_seed_{params.random_seed} {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
executor_.initialize([ & ](v8::Isolate* isolate) noexcept -> auto {
|
|
84
|
+
isolate->SetData(0, this);
|
|
85
|
+
v8::Isolate::CreateParams params;
|
|
86
|
+
auto memory_params = memory_policy_->make_create_params();
|
|
87
|
+
params.constraints = memory_params.constraints;
|
|
88
|
+
params.array_buffer_allocator_shared = std::move(memory_params.allocator);
|
|
89
|
+
return params;
|
|
90
|
+
});
|
|
91
|
+
auto* isolate = executor_.isolate();
|
|
92
|
+
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
93
|
+
memory_policy_->initialize(isolate);
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
agent_host::~agent_host() {
|
|
79
97
|
// Terminate foreground runner
|
|
80
98
|
auto& scheduler = storage_->foreground_runner().scheduler();
|
|
81
99
|
scheduler.terminate();
|
|
82
|
-
assert(!scheduler.is_this_thread());
|
|
83
100
|
if (!scheduler.is_this_thread()) {
|
|
84
101
|
auto thread = scheduler.invoke([](auto& controller) -> auto {
|
|
85
102
|
return controller.take();
|
|
@@ -88,19 +105,20 @@ agent_host::~agent_host() {
|
|
|
88
105
|
}
|
|
89
106
|
// Destroy isolate and remaining handles
|
|
90
107
|
{
|
|
91
|
-
auto lock = js::iv8::isolate_destructor_lock{
|
|
108
|
+
auto lock = js::iv8::isolate_destructor_lock{executor_.isolate()};
|
|
92
109
|
scratch_context_.Reset();
|
|
93
|
-
scheduler.mutate([ & ](platform::foreground_task_queue& queue) -> void {
|
|
94
|
-
queue.finalize();
|
|
95
|
-
});
|
|
96
110
|
remote_handle_list_.clear(util::slice(lock));
|
|
97
111
|
autorelease_pool_.clear();
|
|
98
112
|
destroy_callback_(util::slice(lock));
|
|
99
113
|
}
|
|
100
114
|
// Deallocate isolate (before `agent_storage`)
|
|
101
|
-
|
|
115
|
+
executor_.reset();
|
|
102
116
|
// Release storage
|
|
103
117
|
agent_storage::release(std::move(storage_));
|
|
118
|
+
// Notify client
|
|
119
|
+
if (dispose_callback_) {
|
|
120
|
+
dispose_callback_();
|
|
121
|
+
}
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
auto agent_host::make_context() -> v8::Local<v8::Context> {
|
|
@@ -109,7 +127,7 @@ auto agent_host::make_context() -> v8::Local<v8::Context> {
|
|
|
109
127
|
// the generator is initialized on context creation, so we just control the randomness in that one
|
|
110
128
|
// case.
|
|
111
129
|
should_give_seed_ = true;
|
|
112
|
-
auto context = v8::Context::New(
|
|
130
|
+
auto context = v8::Context::New(executor_.isolate());
|
|
113
131
|
should_give_seed_ = false;
|
|
114
132
|
return context;
|
|
115
133
|
}
|
|
@@ -122,7 +140,7 @@ auto agent_host::make_remote_handle_lock(isolate_lock_witness lock) -> remote_ha
|
|
|
122
140
|
auto agent_host::remote_expiration_callback(expired_remote_type remote) noexcept -> void {
|
|
123
141
|
storage_->foreground_runner().schedule_handle_task(
|
|
124
142
|
[ this, remote = std::move(remote) ](std::stop_token /*stop_token*/) -> void {
|
|
125
|
-
auto lock = isolate_execution_lock{isolate()};
|
|
143
|
+
auto lock = isolate_execution_lock{executor_.isolate()};
|
|
126
144
|
remote->reset(util::slice(lock));
|
|
127
145
|
remote_handle_list_.erase(*remote);
|
|
128
146
|
}
|
|
@@ -132,11 +150,11 @@ auto agent_host::remote_expiration_callback(expired_remote_type remote) noexcept
|
|
|
132
150
|
auto agent_host::scratch_context() -> v8::Local<v8::Context> {
|
|
133
151
|
if (scratch_context_.IsEmpty()) {
|
|
134
152
|
auto context = make_context();
|
|
135
|
-
scratch_context_.Reset(
|
|
153
|
+
scratch_context_.Reset(executor_.isolate(), context);
|
|
136
154
|
scratch_context_.SetWeak();
|
|
137
155
|
return context;
|
|
138
156
|
} else {
|
|
139
|
-
return scratch_context_.Get(
|
|
157
|
+
return scratch_context_.Get(executor_.isolate());
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
160
|
|
package/isolated/agent.h.cc
CHANGED
|
@@ -3,6 +3,8 @@ module;
|
|
|
3
3
|
export module v8_js:isolated.agent;
|
|
4
4
|
import :collected_handle;
|
|
5
5
|
import :isolated.clock;
|
|
6
|
+
import :isolated.executor;
|
|
7
|
+
import :isolated.memory_policy;
|
|
6
8
|
import :platform.foreground_runner;
|
|
7
9
|
import :remote;
|
|
8
10
|
import std;
|
|
@@ -17,6 +19,7 @@ class agent_host_of;
|
|
|
17
19
|
|
|
18
20
|
// `agent_host` behavior parameters
|
|
19
21
|
export struct behavior_params {
|
|
22
|
+
memory_policy::covariant memory_policy;
|
|
20
23
|
clock::any_clock clock;
|
|
21
24
|
std::optional<double> random_seed;
|
|
22
25
|
};
|
|
@@ -67,15 +70,18 @@ export class agent_storage {
|
|
|
67
70
|
export class agent_handle {
|
|
68
71
|
public:
|
|
69
72
|
using lock = agent_lock;
|
|
73
|
+
using dispose_callback_type = util::move_only_function<auto() noexcept -> void>;
|
|
70
74
|
explicit agent_handle(std::shared_ptr<agent_host> host);
|
|
71
|
-
agent_handle(const agent_handle&
|
|
75
|
+
agent_handle(const agent_handle& handle);
|
|
72
76
|
agent_handle(agent_handle&&) = default;
|
|
73
77
|
~agent_handle();
|
|
74
78
|
auto operator=(const agent_handle&) -> agent_handle& = delete;
|
|
75
79
|
auto operator=(agent_handle&&) -> agent_handle& = default;
|
|
76
80
|
|
|
81
|
+
auto dispose(dispose_callback_type after_callback) -> void;
|
|
82
|
+
|
|
77
83
|
protected:
|
|
78
|
-
[[nodiscard]] auto lock_host() const -> std::shared_ptr<agent_host
|
|
84
|
+
[[nodiscard]] auto lock_host() const -> std::shared_ptr<agent_host>;
|
|
79
85
|
|
|
80
86
|
private:
|
|
81
87
|
std::weak_ptr<agent_host> host_;
|
|
@@ -125,7 +131,7 @@ export class agent_host
|
|
|
125
131
|
|
|
126
132
|
auto autorelease_pool() -> util::autorelease_pool& { return autorelease_pool_; }
|
|
127
133
|
auto clock(this auto& self) -> auto& { return self.clock_.at(0); }
|
|
128
|
-
auto
|
|
134
|
+
auto executor() -> auto& { return executor_; }
|
|
129
135
|
auto make_context() -> v8::Local<v8::Context>;
|
|
130
136
|
auto make_remote_handle_lock(isolate_lock_witness lock) -> remote_handle_lock;
|
|
131
137
|
auto scratch_context() -> v8::Local<v8::Context>;
|
|
@@ -137,15 +143,12 @@ export class agent_host
|
|
|
137
143
|
|
|
138
144
|
private:
|
|
139
145
|
friend class agent_handle;
|
|
140
|
-
using lockable_handle_type = util::lockable_with<std::shared_ptr<agent_host>, {.shared = true}>;
|
|
141
|
-
|
|
142
146
|
auto remote_expiration_callback(expired_remote_type remote) noexcept -> void;
|
|
143
|
-
static auto dispose_isolate(v8::Isolate* isolate) -> void { isolate->Dispose(); }
|
|
144
147
|
|
|
145
148
|
// Order matters for these members
|
|
146
149
|
std::shared_ptr<agent_storage> storage_;
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
isolated::executor executor_;
|
|
151
|
+
isolated::memory_policy::covariant memory_policy_;
|
|
149
152
|
|
|
150
153
|
// Order doesn't really matter
|
|
151
154
|
bool should_give_seed_{};
|
|
@@ -157,8 +160,9 @@ export class agent_host
|
|
|
157
160
|
// /gcc-out/../gcc/gcc/cp/class.cc:9510
|
|
158
161
|
std::array<clock::any_clock, 1> clock_;
|
|
159
162
|
destroy_callback_type destroy_callback_;
|
|
160
|
-
|
|
163
|
+
util::atomic_shared_ptr<agent_host> self_handle_;
|
|
161
164
|
remote_handle_list remote_handle_list_;
|
|
165
|
+
agent_handle::dispose_callback_type dispose_callback_;
|
|
162
166
|
std::array<reset_handle_type, 1> reset_handle_callback_;
|
|
163
167
|
std::atomic<unsigned> handle_count_;
|
|
164
168
|
std::optional<double> random_seed_;
|
|
@@ -195,7 +199,7 @@ auto agent_handle_of<Type>::schedule(Task task, Args... args) const -> void {
|
|
|
195
199
|
auto& scheduler = host->storage()->foreground_runner();
|
|
196
200
|
scheduler.schedule_client_task(
|
|
197
201
|
[](std::stop_token /*stop_token*/, const auto& host, const auto& task, auto&&... args) -> void {
|
|
198
|
-
auto isolate_lock = isolate_execution_lock{host->isolate()};
|
|
202
|
+
auto isolate_lock = isolate_execution_lock{host->executor().isolate()};
|
|
199
203
|
std::visit([](auto& clock) -> void { clock.begin_tick(); }, host->clock());
|
|
200
204
|
task(lock{isolate_lock, static_cast<agent_host_of<Type>&>(*host)}, std::forward<decltype(args)>(args)...);
|
|
201
205
|
},
|
package/isolated/cluster.cc
CHANGED
|
@@ -14,8 +14,9 @@ auto cluster::acquire_agent_storage() -> std::shared_ptr<agent_storage> {
|
|
|
14
14
|
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
|
15
15
|
auto cluster::release_agent_storage(std::shared_ptr<agent_storage> storage) -> void {
|
|
16
16
|
agent_storage_.write()->erase(agent_storage_list_type::s_iterator_to(*storage));
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (storage->foreground_runner().scheduler().is_this_thread()) {
|
|
18
|
+
destroy_(std::any{std::move(storage)});
|
|
19
|
+
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
} // namespace js::iv8::isolated
|
package/isolated/cluster.h.cc
CHANGED
|
@@ -8,16 +8,21 @@ namespace js::iv8::isolated {
|
|
|
8
8
|
|
|
9
9
|
export class cluster {
|
|
10
10
|
public:
|
|
11
|
-
|
|
11
|
+
using synchronize_destroy = util::function_ref<auto(std::any)->void>;
|
|
12
|
+
|
|
13
|
+
explicit cluster(synchronize_destroy destroy) :
|
|
14
|
+
destroy_{destroy},
|
|
15
|
+
platform_{isolated_platform::acquire()} {}
|
|
16
|
+
|
|
12
17
|
auto make_agent(auto make_environment, behavior_params params, auto callback) -> void;
|
|
13
18
|
auto release_agent_storage(std::shared_ptr<agent_storage> storage) -> void;
|
|
14
19
|
|
|
15
20
|
private:
|
|
16
21
|
using intrusive_no_size = boost::intrusive::constant_time_size<false>;
|
|
17
22
|
using agent_storage_list_type = boost::intrusive::list<agent_storage, intrusive_no_size, agent_storage::intrusive_hook>;
|
|
18
|
-
|
|
19
23
|
auto acquire_agent_storage() -> std::shared_ptr<agent_storage>;
|
|
20
24
|
|
|
25
|
+
synchronize_destroy destroy_;
|
|
21
26
|
util::lockable<agent_storage_list_type> agent_storage_;
|
|
22
27
|
platform::platform_handle platform_;
|
|
23
28
|
};
|
|
@@ -36,7 +41,7 @@ auto cluster::make_agent(auto make_environment, behavior_params params, auto cal
|
|
|
36
41
|
auto callback
|
|
37
42
|
) -> auto {
|
|
38
43
|
auto host = std::make_shared<agent_host_of<environment_type>>(std::move(storage), params);
|
|
39
|
-
auto isolate_lock = isolate_execution_lock{host->isolate()};
|
|
44
|
+
auto isolate_lock = isolate_execution_lock{host->executor().isolate()};
|
|
40
45
|
host->emplace_environment(util::elide{[ & ]() -> environment_type {
|
|
41
46
|
return make_environment(agent_lock{isolate_lock, *host});
|
|
42
47
|
}});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module v8_js;
|
|
2
|
+
import :isolated.agent;
|
|
3
|
+
import :isolated.executor;
|
|
4
|
+
import std;
|
|
5
|
+
|
|
6
|
+
namespace js::iv8::isolated {
|
|
7
|
+
|
|
8
|
+
auto executor::reset() -> void {
|
|
9
|
+
isolate_.reset();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
auto executor::terminate() -> void {
|
|
13
|
+
terminated_.store(true, std::memory_order_release);
|
|
14
|
+
isolate_->TerminateExecution();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
auto executor::is_terminated() const -> bool {
|
|
18
|
+
return terminated_.load(std::memory_order_acquire);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
auto executor::on_before_call_entered(v8::Isolate* isolate) -> void {
|
|
22
|
+
// Checks for termination right after control has been passed to v8. This catches the case where a
|
|
23
|
+
// task is slow to start and `TerminateExecution()`, from `terminate()` is invoked on an idle
|
|
24
|
+
// isolate. In that case the termination is "handled" already.
|
|
25
|
+
auto& self = agent_host::get_current(isolate).executor();
|
|
26
|
+
if (self.is_terminated()) {
|
|
27
|
+
isolate->TerminateExecution();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
} // namespace js::iv8::isolated
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export module v8_js:isolated.executor;
|
|
2
|
+
import std;
|
|
3
|
+
import v8;
|
|
4
|
+
|
|
5
|
+
namespace js::iv8::isolated {
|
|
6
|
+
|
|
7
|
+
export class executor { // "En taro adun"
|
|
8
|
+
public:
|
|
9
|
+
auto initialize(std::invocable<v8::Isolate*> auto initialize) -> void;
|
|
10
|
+
auto reset() -> void;
|
|
11
|
+
auto terminate() -> void;
|
|
12
|
+
|
|
13
|
+
[[nodiscard]] auto is_terminated() const -> bool;
|
|
14
|
+
[[nodiscard]] auto isolate() const -> v8::Isolate* { return isolate_.get(); }
|
|
15
|
+
|
|
16
|
+
private:
|
|
17
|
+
constexpr static auto dispose_isolate = [](v8::Isolate* isolate) -> void { isolate->Dispose(); };
|
|
18
|
+
static auto on_before_call_entered(v8::Isolate* isolate) -> void;
|
|
19
|
+
|
|
20
|
+
std::unique_ptr<v8::Isolate, decltype(dispose_isolate)> isolate_;
|
|
21
|
+
std::atomic<bool> terminated_;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// ---
|
|
25
|
+
|
|
26
|
+
auto executor::initialize(std::invocable<v8::Isolate*> auto initialize) -> void {
|
|
27
|
+
static_assert(std::is_nothrow_invocable_v<decltype(initialize), v8::Isolate*>);
|
|
28
|
+
isolate_.reset(v8::Isolate::Allocate());
|
|
29
|
+
auto params = v8::Isolate::CreateParams{initialize(isolate_.get())};
|
|
30
|
+
v8::Isolate::Initialize(isolate_.get(), params);
|
|
31
|
+
isolate_->AddBeforeCallEnteredCallback(on_before_call_entered);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
} // namespace js::iv8::isolated
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module v8_js;
|
|
2
|
+
import :isolated.memory_policy;
|
|
3
|
+
|
|
4
|
+
namespace js::iv8::isolated {
|
|
5
|
+
|
|
6
|
+
// memory_policy::none
|
|
7
|
+
auto memory_policy::none::make_create_params() -> create_params {
|
|
8
|
+
return {
|
|
9
|
+
.allocator = std::shared_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator()),
|
|
10
|
+
.constraints = {}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// memory_policy::limited
|
|
15
|
+
auto memory_policy::limited::initialize(v8::Isolate* isolate) -> void {
|
|
16
|
+
isolate->AddNearHeapLimitCallback(
|
|
17
|
+
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
|
18
|
+
[](void* data, std::size_t current_heap_limit, std::size_t initial_heap_limit) -> std::size_t {
|
|
19
|
+
auto& self = *static_cast<memory_policy::limited*>(data);
|
|
20
|
+
return self.near_heap_limit_callback(current_heap_limit, initial_heap_limit);
|
|
21
|
+
},
|
|
22
|
+
this
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
auto memory_policy::limited::make_create_params() -> create_params {
|
|
27
|
+
v8::ResourceConstraints constraints{};
|
|
28
|
+
constraints.ConfigureDefaultsFromHeapSize(0, memory_limit_);
|
|
29
|
+
return {
|
|
30
|
+
.allocator = std::shared_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator()),
|
|
31
|
+
.constraints = constraints,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
|
36
|
+
auto memory_policy::limited::near_heap_limit_callback(std::size_t current_heap_limit, std::size_t /*initial_heap_limit*/) const -> std::size_t {
|
|
37
|
+
auto* isolate = v8::Isolate::GetCurrent();
|
|
38
|
+
v8::HeapStatistics heap;
|
|
39
|
+
isolate->GetHeapStatistics(&heap);
|
|
40
|
+
|
|
41
|
+
if (current_heap_limit > memory_limit_) {
|
|
42
|
+
auto& executor = agent_host::get_current(isolate).executor();
|
|
43
|
+
executor.terminate();
|
|
44
|
+
}
|
|
45
|
+
// Increase heap limit 64mb at a time
|
|
46
|
+
return current_heap_limit + (64 << 20);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
} // namespace js::iv8::isolated
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export module v8_js:isolated.memory_policy;
|
|
2
|
+
import std;
|
|
3
|
+
import util;
|
|
4
|
+
import v8;
|
|
5
|
+
|
|
6
|
+
namespace js::iv8::isolated {
|
|
7
|
+
|
|
8
|
+
export class memory_policy {
|
|
9
|
+
protected:
|
|
10
|
+
~memory_policy() = default;
|
|
11
|
+
|
|
12
|
+
public:
|
|
13
|
+
struct create_params;
|
|
14
|
+
|
|
15
|
+
// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
|
|
16
|
+
class none;
|
|
17
|
+
// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
|
|
18
|
+
class limited;
|
|
19
|
+
|
|
20
|
+
virtual auto initialize(v8::Isolate* isolate) -> void = 0;
|
|
21
|
+
virtual auto make_create_params() -> create_params = 0;
|
|
22
|
+
// virtual auto check_soft_limit(v8::Isolate* isolate) -> bool = 0;
|
|
23
|
+
using covariant = util::covariant_value<memory_policy, none, limited>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
struct memory_policy::create_params {
|
|
27
|
+
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator;
|
|
28
|
+
v8::ResourceConstraints constraints;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
class memory_policy::none final : public memory_policy {
|
|
32
|
+
public:
|
|
33
|
+
auto initialize(v8::Isolate* isolate) -> void override {};
|
|
34
|
+
auto make_create_params() -> create_params override;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
class memory_policy::limited final : public memory_policy {
|
|
38
|
+
public:
|
|
39
|
+
explicit limited(std::size_t memory_limit_bytes) : memory_limit_{memory_limit_bytes} {}
|
|
40
|
+
auto initialize(v8::Isolate* isolate) -> void override;
|
|
41
|
+
auto make_create_params() -> create_params override;
|
|
42
|
+
|
|
43
|
+
private:
|
|
44
|
+
std::size_t memory_limit_;
|
|
45
|
+
[[nodiscard]] auto near_heap_limit_callback(std::size_t current_heap_limit, std::size_t initial_heap_limit) const -> std::size_t;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
} // namespace js::iv8::isolated
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auto_js/v8",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"author": "https://github.com/laverdet/",
|
|
5
5
|
"homepage": "https://github.com/laverdet/isolated-vm/tree/experimental/packages/auto_js/v8",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@auto_js/js": "^0.0.
|
|
9
|
-
"@auto_js/v8_exports": "^0.0.
|
|
8
|
+
"@auto_js/js": "^0.0.9",
|
|
9
|
+
"@auto_js/v8_exports": "^0.0.7"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -7,10 +7,7 @@ namespace js::iv8::platform {
|
|
|
7
7
|
// `foreground_task_queue`
|
|
8
8
|
foreground_task_queue::foreground_task_queue(foreground_task_queue&& other) noexcept :
|
|
9
9
|
tasks_{std::move(other.tasks_)},
|
|
10
|
-
delayed_tasks_{std::move(other.delayed_tasks_)}
|
|
11
|
-
finalized_{other.finalized_} {
|
|
12
|
-
other.finalized_ = true;
|
|
13
|
-
}
|
|
10
|
+
delayed_tasks_{std::move(other.delayed_tasks_)} {}
|
|
14
11
|
|
|
15
12
|
auto foreground_task_queue::flush(clock_type::time_point now) -> clock_type::time_point {
|
|
16
13
|
// nb: As far as I can tell all delayed tasks are posted with `kUserVisible`. I'm not even sure
|
|
@@ -18,32 +15,16 @@ auto foreground_task_queue::flush(clock_type::time_point now) -> clock_type::tim
|
|
|
18
15
|
return delayed_tasks_.flush(tasks_.at(priority_numeric_from(v8::TaskPriority::kUserVisible)), now);
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
auto foreground_task_queue::finalize() -> void {
|
|
22
|
-
finalized_ = true;
|
|
23
|
-
auto& handle_tasks = tasks_.at(priority_numeric_from(v8::TaskPriority::kUserBlocking));
|
|
24
|
-
while (!handle_tasks.empty()) {
|
|
25
|
-
auto& task = tasks_.front();
|
|
26
|
-
task(std::stop_token{});
|
|
27
|
-
tasks_.pop();
|
|
28
|
-
};
|
|
29
|
-
while (!tasks_.empty()) tasks_.pop();
|
|
30
|
-
while (!delayed_tasks_.empty()) delayed_tasks_.pop();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
auto foreground_task_queue::front() -> task_entry_type& {
|
|
34
19
|
return std::ranges::find_if_not(tasks_.containers(), &queue_type::empty)->front();
|
|
35
20
|
}
|
|
36
21
|
|
|
37
22
|
auto foreground_task_queue::push_delayed(delayed_entry_type task) -> void {
|
|
38
|
-
|
|
39
|
-
delayed_tasks_.emplace(std::move(task));
|
|
40
|
-
}
|
|
23
|
+
delayed_tasks_.emplace(std::move(task));
|
|
41
24
|
}
|
|
42
25
|
|
|
43
26
|
auto foreground_task_queue::push(v8::TaskPriority priority, task_entry_type task) -> void {
|
|
44
|
-
|
|
45
|
-
tasks_.emplace(priority_numeric_from(priority), std::move(task));
|
|
46
|
-
}
|
|
27
|
+
tasks_.emplace(priority_numeric_from(priority), std::move(task));
|
|
47
28
|
}
|
|
48
29
|
|
|
49
30
|
// `foreground_runner`
|
|
@@ -25,7 +25,6 @@ class foreground_task_queue {
|
|
|
25
25
|
auto operator=(foreground_task_queue&&) -> foreground_task_queue& = delete;
|
|
26
26
|
|
|
27
27
|
[[nodiscard]] auto empty() const -> bool { return tasks_.empty(); }
|
|
28
|
-
auto finalize() -> void;
|
|
29
28
|
auto flush(clock_type::time_point now) -> clock_type::time_point;
|
|
30
29
|
auto front() -> task_entry_type&;
|
|
31
30
|
auto pop() -> void { tasks_.pop(); }
|
|
@@ -35,7 +34,6 @@ class foreground_task_queue {
|
|
|
35
34
|
private:
|
|
36
35
|
util::segmented_priority_queue<queue_type, priority_count> tasks_;
|
|
37
36
|
delayed_queue_type delayed_tasks_;
|
|
38
|
-
bool finalized_ = false;
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
// Foreground task runner. Combines scheduler and task queue.
|
package/platform/task_runner.cc
CHANGED
|
@@ -6,7 +6,7 @@ using std::chrono::steady_clock;
|
|
|
6
6
|
|
|
7
7
|
// NOTE:
|
|
8
8
|
// There is nothing interesting in this file. The main thing is that `task_runner_of` automatically
|
|
9
|
-
// defines `NonNestableTasksEnabled` (etc) for you based on whether or not `post_non_nestable` (etc)
|
|
9
|
+
// defines `NonNestableTasksEnabled` (etc) -> for you based on whether or not `post_non_nestable` (etc)
|
|
10
10
|
// is defined.
|
|
11
11
|
|
|
12
12
|
namespace js::iv8::platform {
|
package/support/error.h.cc
CHANGED
|
@@ -40,24 +40,29 @@ auto externalize_caught_error(context_lock_witness lock, v8::Local<v8::Value> va
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
// Catch pending js errors and externalize them as `js::error_value`.
|
|
44
|
-
// js::error_value
|
|
43
|
+
// Catch pending js errors and externalize them as `js::error_value`.
|
|
44
|
+
// `std::optional<std::expected<T, js::error_value>>` is returned. The outer optional is nullopt in
|
|
45
|
+
// the case of terminating execution.
|
|
45
46
|
export [[nodiscard]] auto invoke_externalized_error_scope(context_lock_witness lock, auto operation) {
|
|
46
47
|
using result_type = decltype(operation());
|
|
47
|
-
using value_type = std::expected<result_type, js::error_value
|
|
48
|
+
using value_type = std::optional<std::expected<result_type, js::error_value>>;
|
|
48
49
|
const auto try_catch = v8::TryCatch{lock.isolate()};
|
|
49
50
|
try {
|
|
50
51
|
if constexpr (type<result_type> == type<void>) {
|
|
51
52
|
operation();
|
|
52
|
-
return value_type{std::in_place};
|
|
53
|
+
return value_type{std::in_place, std::in_place};
|
|
53
54
|
} else {
|
|
54
|
-
return value_type{std::in_place, operation()};
|
|
55
|
+
return value_type{std::in_place, std::in_place, operation()};
|
|
55
56
|
}
|
|
56
57
|
} catch (const iv8::pending_error& /*error*/) {
|
|
57
58
|
assert(try_catch.HasCaught());
|
|
58
|
-
|
|
59
|
+
if (lock.isolate()->IsExecutionTerminating()) {
|
|
60
|
+
return value_type{std::nullopt};
|
|
61
|
+
} else {
|
|
62
|
+
return value_type{std::in_place, std::unexpect, externalize_caught_error(lock, try_catch)};
|
|
63
|
+
}
|
|
59
64
|
} catch (const js::error& error) {
|
|
60
|
-
return value_type{std::unexpect, error};
|
|
65
|
+
return value_type{std::in_place, std::unexpect, error};
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
package/transfer/accept.h.cc
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export module v8_js:accept;
|
|
2
2
|
import :callback_storage;
|
|
3
|
+
import :handle.value;
|
|
3
4
|
import :hash;
|
|
4
5
|
import :value;
|
|
5
6
|
import auto_js;
|
|
@@ -370,6 +371,20 @@ struct accept_v8_property_value {
|
|
|
370
371
|
accept_value<Meta, Type> second;
|
|
371
372
|
};
|
|
372
373
|
|
|
374
|
+
// Forward `value_of<T>`
|
|
375
|
+
template <class Tag>
|
|
376
|
+
struct accept_v8_value_of {
|
|
377
|
+
public:
|
|
378
|
+
explicit accept_v8_value_of(auto* /*transfer*/) {}
|
|
379
|
+
|
|
380
|
+
auto operator()(Tag /*tag*/, visit_holder /*visit*/, value_of<Tag> subject) const -> value_of<Tag> {
|
|
381
|
+
return subject;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
consteval static auto accept_tags_of() { return std::tuple{Tag{}}; }
|
|
385
|
+
consteval static auto types(auto /*recursive*/) { return util::type_pack{}; }
|
|
386
|
+
};
|
|
387
|
+
|
|
373
388
|
// `return_into(...)`
|
|
374
389
|
struct return_into_marker {};
|
|
375
390
|
|
|
@@ -475,6 +490,12 @@ struct accept<Meta, v8::Local<Type>> : iv8::accept_v8_value_with<typename Meta::
|
|
|
475
490
|
using iv8::accept_v8_value_with<typename Meta::accept_context_type>::accept_v8_value_with;
|
|
476
491
|
};
|
|
477
492
|
|
|
493
|
+
// forward `value_of<T>`
|
|
494
|
+
template <class Meta, class Tag>
|
|
495
|
+
struct accept<Meta, iv8::value_of<Tag>> : iv8::accept_v8_value_of<Tag> {
|
|
496
|
+
using iv8::accept_v8_value_of<Tag>::accept_v8_value_of;
|
|
497
|
+
};
|
|
498
|
+
|
|
478
499
|
// templates
|
|
479
500
|
template <class Meta, class Type>
|
|
480
501
|
requires std::is_convertible_v<Type, v8::Template>
|
package/transfer/visit.cc
CHANGED
|
@@ -380,7 +380,7 @@ struct visit_value : visit_flat_value<Target> {
|
|
|
380
380
|
template <class Accept>
|
|
381
381
|
auto immediate(v8::Local<v8::Array> subject, const Accept& accept) -> accept_target_t<Accept> {
|
|
382
382
|
auto visit_entry = visit_entry_pair<visit_property_name<visit_value>, visit_value&>{*this};
|
|
383
|
-
return accept(list_tag{}, visit_entry, value_of{witness(), subject.As<v8::
|
|
383
|
+
return accept(list_tag{}, visit_entry, value_of{witness(), subject.As<v8::Array>()});
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
// function template
|
|
@@ -433,6 +433,40 @@ struct visit_value : visit_flat_value<Target> {
|
|
|
433
433
|
v8::Local<v8::Context> context_;
|
|
434
434
|
};
|
|
435
435
|
|
|
436
|
+
// Forward `value_of<T>` back to acceptor
|
|
437
|
+
template <class Tag>
|
|
438
|
+
struct visit_v8_value_of {
|
|
439
|
+
private:
|
|
440
|
+
using visit_type = visit_value<void>;
|
|
441
|
+
|
|
442
|
+
public:
|
|
443
|
+
constexpr explicit visit_v8_value_of(auto* transfer, context_lock_witness lock) : visit_{transfer, lock} {}
|
|
444
|
+
|
|
445
|
+
template <class Accept>
|
|
446
|
+
constexpr auto operator()(value_of<Tag> subject, const Accept& accept) -> accept_target_t<Accept> {
|
|
447
|
+
return accept(Tag{}, visit_, subject);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
template <class Accept>
|
|
451
|
+
requires std::is_same_v<Tag, object_tag>
|
|
452
|
+
constexpr auto operator()(value_of<object_tag> subject, const Accept& accept) -> accept_target_t<Accept> {
|
|
453
|
+
auto visit_entry = visit_entry_pair<visit_property_name<visit_type>, visit_type&>{visit_};
|
|
454
|
+
return accept(object_tag{}, visit_entry, subject);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
template <class Accept>
|
|
458
|
+
requires std::is_same_v<Tag, list_tag>
|
|
459
|
+
constexpr auto operator()(value_of<list_tag> subject, const Accept& accept) -> accept_target_t<Accept> {
|
|
460
|
+
auto visit_entry = visit_entry_pair<visit_property_name<visit_type>, visit_type&>{visit_};
|
|
461
|
+
return accept(list_tag{}, visit_entry, subject);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
consteval static auto types(auto /*recursive*/) { return util::type_pack{}; }
|
|
465
|
+
|
|
466
|
+
private:
|
|
467
|
+
visit_type visit_;
|
|
468
|
+
};
|
|
469
|
+
|
|
436
470
|
// Template visitor needs no lock. The acceptor will instantiate values.
|
|
437
471
|
struct visit_template {
|
|
438
472
|
explicit visit_template(auto* /*transfer*/) {}
|
|
@@ -467,6 +501,15 @@ struct visit<Meta, v8::Local<Type>> : iv8::visit_value_with<Meta> {
|
|
|
467
501
|
using iv8::visit_value_with<Meta>::visit_value_with;
|
|
468
502
|
};
|
|
469
503
|
|
|
504
|
+
// value_of<T> visitor
|
|
505
|
+
template <class Meta, class Tag>
|
|
506
|
+
struct visit<Meta, iv8::value_of<Tag>> : iv8::visit_v8_value_of<Tag> {
|
|
507
|
+
using iv8::visit_v8_value_of<Tag>::visit_v8_value_of;
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
template <class Tag>
|
|
511
|
+
struct visit_subject_for<iv8::value_of<Tag>> : std::type_identity<v8::Local<v8::Value>> {};
|
|
512
|
+
|
|
470
513
|
// Template visitor
|
|
471
514
|
template <class Meta, class Type>
|
|
472
515
|
requires std::is_convertible_v<Type, v8::Template>
|
package/value/fixed_array.cc
CHANGED
package/value/primitive.cc
CHANGED
package/platform/allocator.h.cc
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export module v8_js:platform.allocator;
|
|
2
|
-
import std;
|
|
3
|
-
import v8;
|
|
4
|
-
|
|
5
|
-
namespace js::iv8::platform {
|
|
6
|
-
|
|
7
|
-
class data_block_allocator : public v8::ArrayBuffer::Allocator {
|
|
8
|
-
auto Allocate(std::size_t length) -> void* final {
|
|
9
|
-
auto* data = AllocateUninitialized(length);
|
|
10
|
-
std::memset(data, 0, length);
|
|
11
|
-
return data;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
auto AllocateUninitialized(std::size_t length) -> void* final { return new std::byte[ length ]; };
|
|
15
|
-
|
|
16
|
-
auto GetPageAllocator() -> v8::PageAllocator* final { return nullptr; }
|
|
17
|
-
void Free(void* data, std::size_t /*length*/) final { delete[] static_cast<std::byte*>(data); };
|
|
18
|
-
|
|
19
|
-
[[nodiscard]] auto MaxAllocationSize() const -> std::size_t final { return std::numeric_limits<std::uint32_t>::max(); }
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
} // namespace js::iv8::platform
|