@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 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;
@@ -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) -> expected_value_type {
24
- return invoke_externalized_error_scope(lock, [ & ]() -> js::value_t {
25
- auto maybe_result = script->BindToCurrentContext()->Run(lock.context());
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
@@ -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) -> expected_value_type;
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;
@@ -1,5 +1,5 @@
1
1
  module;
2
- #include <v8/version.h>
2
+ #include <v8_js/version.h>
3
3
  export module v8_js:collected_handle;
4
4
  import :lock;
5
5
  import std;
@@ -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::bound_value_of<T>`. `v8::Local<T>` is more like
13
- // `js::napi::value_of<T>`.
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
- *host->self_handle_.write() = std::move(host);
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_.write()->reset();
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
- // TODO: v8 holds its own reference to this. I will probably need it later for per-isolate
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
- isolate_->SetData(0, this);
71
- auto create_params = v8::Isolate::CreateParams{};
72
- create_params.array_buffer_allocator_shared = array_buffer_allocator_;
73
- v8::Isolate::Initialize(isolate_.get(), create_params);
74
-
75
- isolate_->SetCaptureStackTraceForUncaughtExceptions(true);
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{isolate_.get()};
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
- isolate_.reset();
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(isolate_.get());
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(isolate_.get(), context);
153
+ scratch_context_.Reset(executor_.isolate(), context);
136
154
  scratch_context_.SetWeak();
137
155
  return context;
138
156
  } else {
139
- return scratch_context_.Get(isolate_.get());
157
+ return scratch_context_.Get(executor_.isolate());
140
158
  }
141
159
  }
142
160
 
@@ -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& /*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> { return host_.lock(); }
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 isolate() -> v8::Isolate* { return isolate_.get(); }
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
- std::shared_ptr<v8::ArrayBuffer::Allocator> array_buffer_allocator_;
148
- std::unique_ptr<v8::Isolate, util::function_constant<dispose_isolate>> isolate_;
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
- lockable_handle_type self_handle_;
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
  },
@@ -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
- // I haven't seen this case yet.
18
- assert(!storage->foreground_runner().scheduler().is_this_thread());
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
@@ -8,16 +8,21 @@ namespace js::iv8::isolated {
8
8
 
9
9
  export class cluster {
10
10
  public:
11
- cluster() : platform_{isolated_platform::acquire()} {}
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.7",
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.7",
9
- "@auto_js/v8_exports": "^0.0.5"
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
- if (!finalized_) {
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
- if (!finalized_) {
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.
@@ -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 {
@@ -1,5 +1,5 @@
1
1
  module;
2
- #include <v8/version.h>
2
+ #include <v8_js/version.h>
3
3
  export module v8_js:callback_storage;
4
4
  import :collected_handle;
5
5
  import :unmaybe;
@@ -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`. `std::expected<T,
44
- // js::error_value>` is returned.
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
- return value_type{std::unexpect, externalize_caught_error(lock, try_catch)};
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
 
@@ -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::Object>()});
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>
@@ -1,5 +1,5 @@
1
1
  module;
2
- #include <v8/version.h>
2
+ #include <v8_js/version.h>
3
3
  module v8_js;
4
4
  import v8;
5
5
 
@@ -1,5 +1,5 @@
1
1
  module;
2
- #include <v8/version.h>
2
+ #include <v8_js/version.h>
3
3
  module v8_js;
4
4
  import :primitive;
5
5
  import std;
@@ -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