1- #include " avm_simulate_napi.hpp"
1+ #include " barretenberg/nodejs_module/avm_simulate/ avm_simulate_napi.hpp"
22
3+ #include < array>
34#include < memory>
45#include < vector>
56
1314#include " barretenberg/vm2/simulation/lib/cancellation_token.hpp"
1415
1516namespace bb ::nodejs {
16-
1717namespace {
18+
1819// Log levels from TS foundation/src/log/log-levels.ts: ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug',
1920// 'trace'] Map: 0=silent, 1=fatal, 2=error, 3=warn, 4=info, 5=verbose, 6=debug, 7=trace
2021constexpr int LOG_LEVEL_VERBOSE = 5 ;
@@ -29,6 +30,46 @@ inline void set_logging_from_level(int log_level)
2930 debug_logging = (log_level >= LOG_LEVEL_TRACE);
3031}
3132
33+ // Map C++ LogLevel enum to TypeScript log level string
34+ // C++ LogLevel: DEBUG=0, INFO=1, VERBOSE=2, IMPORTANT=3
35+ // TS LogLevels: ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace']
36+ inline const char * cpp_log_level_to_ts (LogLevel level)
37+ {
38+ switch (level) {
39+ case LogLevel::DEBUG:
40+ return " debug" ;
41+ case LogLevel::INFO:
42+ return " info" ;
43+ case LogLevel::VERBOSE:
44+ return " verbose" ;
45+ case LogLevel::IMPORTANT:
46+ return " warn" ;
47+ default :
48+ return " info" ;
49+ }
50+ }
51+
52+ // Helper to create a LogFunction wrapper from a ThreadSafeFunction
53+ // This allows C++ logging to call back to TypeScript logger from worker threads
54+ LogFunction create_log_function_from_tsfn (const std::shared_ptr<Napi::ThreadSafeFunction>& logger_tsfn)
55+ {
56+ return [logger_tsfn](LogLevel level, const char * msg) {
57+ // Convert C++ LogLevel to TS log level string
58+ const char * ts_level = cpp_log_level_to_ts (level);
59+ std::string msg_str (msg);
60+
61+ // Call TypeScript logger function on the JS main thread
62+ // Using BlockingCall to ensure synchronous execution
63+ // Ignore errors - logging failures shouldn't crash the simulation
64+ logger_tsfn->BlockingCall ([ts_level, msg_str](Napi::Env env, Napi::Function js_logger) {
65+ // Create arguments: (level: string, msg: string)
66+ auto level_js = Napi::String::New (env, ts_level);
67+ auto msg_js = Napi::String::New (env, msg_str);
68+ js_logger.Call ({ level_js, msg_js });
69+ });
70+ };
71+ }
72+
3273// Callback method names
3374constexpr const char * CALLBACK_GET_CONTRACT_INSTANCE = " getContractInstance" ;
3475constexpr const char * CALLBACK_GET_CONTRACT_CLASS = " getContractClass" ;
@@ -117,63 +158,42 @@ Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info)
117158{
118159 Napi::Env env = cb_info.Env ();
119160
120- // Validate arguments - expects 4-5 arguments
161+ // Validate arguments - expects 3-6 arguments
121162 // arg[0]: inputs Buffer (required)
122163 // arg[1]: contractProvider object (required)
123164 // arg[2]: worldStateHandle external (required)
124- // arg[3]: logLevel number (required) - index into TS LogLevels array
125- // arg[4]: cancellationToken external (optional)
126- if (cb_info.Length () < 4 ) {
127- throw Napi::TypeError::New (env,
128- " Wrong number of arguments. Expected 4-5 arguments: inputs Buffer, contractProvider "
129- " object, worldStateHandle, logLevel, and optional cancellationToken." );
165+ // arg[3]: logLevel number (optional) - index into TS LogLevels array, -1 if omitted
166+ // arg[4]: loggerFunction (optional) - can be null/undefined
167+ // arg[5]: cancellationToken external (optional)
168+ if (cb_info.Length () < 3 ) {
169+ throw Napi::TypeError::New (
170+ env,
171+ " Wrong number of arguments. Expected 3-6 arguments: inputs Buffer, contractProvider "
172+ " object, worldStateHandle, optional logLevel, optional loggerFunction, and optional cancellationToken." );
130173 }
131174
175+ /* ******************************
176+ *** AvmFastSimulationInputs ***
177+ *******************************/
132178 if (!cb_info[0 ].IsBuffer ()) {
133179 throw Napi::TypeError::New (env,
134180 " First argument must be a Buffer containing serialized AvmFastSimulationInputs" );
135181 }
136-
137- if (!cb_info[1 ].IsObject ()) {
138- throw Napi::TypeError::New (env, " Second argument must be a contractProvider object" );
139- }
140-
141- if (!cb_info[2 ].IsExternal ()) {
142- throw Napi::TypeError::New (env, " Third argument must be a WorldState handle (External)" );
143- }
144-
145- if (!cb_info[3 ].IsNumber ()) {
146- throw Napi::TypeError::New (env, " Fourth argument must be a log level number (0-7)" );
147- }
148-
149- // Extract optional cancellation token (5th argument)
150- avm2::simulation::CancellationTokenPtr cancellation_token = nullptr ;
151- if (cb_info.Length () > 4 && cb_info[4 ].IsExternal ()) {
152- auto token_external = cb_info[4 ].As <Napi::External<avm2::simulation::CancellationToken>>();
153- // Wrap the raw pointer in a shared_ptr that does NOT delete (since the External owns it)
154- cancellation_token = std::shared_ptr<avm2::simulation::CancellationToken>(
155- token_external.Data (), [](avm2::simulation::CancellationToken*) {
156- // No-op deleter: the External
157- // (via shared_ptr destructor
158- // callback) owns the token
159- });
160- }
161-
162- // Extract log level and set logging flags
163- int log_level = cb_info[3 ].As <Napi::Number>().Int32Value ();
164- set_logging_from_level (log_level);
165-
166182 // Extract the inputs buffer
167183 auto inputs_buffer = cb_info[0 ].As <Napi::Buffer<uint8_t >>();
168184 size_t length = inputs_buffer.Length ();
169-
170185 // Copy the buffer data into C++ memory (we can't access Napi objects from worker thread)
171186 auto data = std::make_shared<std::vector<uint8_t >>(inputs_buffer.Data (), inputs_buffer.Data () + length);
172187
188+ /* **********************************
189+ *** ContractProvider (required) ***
190+ ***********************************/
191+ if (!cb_info[1 ].IsObject ()) {
192+ throw Napi::TypeError::New (env, " Second argument must be a contractProvider object" );
193+ }
173194 // Extract and validate contract provider callbacks
174195 auto contract_provider = cb_info[1 ].As <Napi::Object>();
175196 ContractCallbacks::validate (env, contract_provider);
176-
177197 // Create thread-safe function wrappers for callbacks
178198 // These allow us to call TypeScript from the C++ worker thread
179199 ContractTsfns tsfns{
@@ -196,18 +216,68 @@ Napi::Value AvmSimulateNapi::simulate(const Napi::CallbackInfo& cb_info)
196216 env, ContractCallbacks::get (contract_provider, CALLBACK_REVERT_CHECKPOINT), CALLBACK_REVERT_CHECKPOINT),
197217 };
198218
219+ /* ****************************
220+ *** WorldState (required) ***
221+ *****************************/
222+ if (!cb_info[2 ].IsExternal ()) {
223+ throw Napi::TypeError::New (env, " Third argument must be a WorldState handle (External)" );
224+ }
199225 // Extract WorldState handle (3rd argument)
200226 auto external = cb_info[2 ].As <Napi::External<world_state::WorldState>>();
201227 world_state::WorldState* ws_ptr = external.Data ();
202228
203- // Create a deferred promise
204- auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
229+ /* **************************
230+ *** LogLevel (optional) ***
231+ ***************************/
232+ int log_level = -1 ;
233+ if (cb_info.Length () > 3 && cb_info[3 ].IsNumber ()) {
234+ log_level = cb_info[3 ].As <Napi::Number>().Int32Value ();
235+ set_logging_from_level (log_level);
236+ }
237+
238+ /* ********************************
239+ *** LoggerFunction (optional) ***
240+ *********************************/
241+ std::shared_ptr<Napi::ThreadSafeFunction> logger_tsfn = nullptr ;
242+ if (cb_info.Length () > 4 && !cb_info[4 ].IsNull () && !cb_info[4 ].IsUndefined ()) {
243+ if (cb_info[4 ].IsFunction ()) {
244+ // Logger function provided - create thread-safe wrapper
245+ auto logger_function = cb_info[4 ].As <Napi::Function>();
246+ logger_tsfn = make_tsfn (env, logger_function, " LoggerCallback" );
247+ // Create LogFunction wrapper and set it as the global log function
248+ // This will be used by C++ logging macros (info, debug, vinfo, important)
249+ set_log_function (create_log_function_from_tsfn (logger_tsfn));
250+ } else {
251+ throw Napi::TypeError::New (env, " Fifth argument must be a logger function, null, or undefined" );
252+ }
253+ }
254+
255+ /* ************************************
256+ *** Cancellation Token (optional) ***
257+ *************************************/
258+ avm2::simulation::CancellationTokenPtr cancellation_token = nullptr ;
259+ if (cb_info.Length () > 5 && cb_info[5 ].IsExternal ()) {
260+ auto token_external = cb_info[5 ].As <Napi::External<avm2::simulation::CancellationToken>>();
261+ // Wrap the raw pointer in a shared_ptr that does NOT delete (since the External owns it)
262+ cancellation_token = std::shared_ptr<avm2::simulation::CancellationToken>(
263+ token_external.Data (), [](avm2::simulation::CancellationToken*) {
264+ // No-op deleter: the External (via shared_ptr destructor callback) owns the token
265+ });
266+ }
267+
268+ /* *********************************************************
269+ *** Create Deferred Promise and launch async operation ***
270+ **********************************************************/
205271
272+ auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
206273 // Create async operation that will run on a worker thread
207- auto * op =
208- new AsyncOperation (env, deferred, [data, tsfns, ws_ptr, cancellation_token](msgpack::sbuffer& result_buffer) {
274+ auto * op = new AsyncOperation (
275+ env, deferred, [data, tsfns, logger_tsfn, ws_ptr, cancellation_token](msgpack::sbuffer& result_buffer) {
276+ // Collect all thread-safe functions including logger for cleanup
277+ auto all_tsfns = tsfns.to_vector ();
278+ all_tsfns.push_back (logger_tsfn);
209279 // Ensure all thread-safe functions are released in all code paths
210- TsfnReleaser releaser = TsfnReleaser (tsfns. to_vector ( ));
280+ TsfnReleaser releaser = TsfnReleaser (std::move (all_tsfns ));
211281
212282 try {
213283 // Deserialize inputs from msgpack
0 commit comments