|
3 | 3 | #include "node_internals.h" |
4 | 4 |
|
5 | 5 | namespace node { |
| 6 | + |
| 7 | +namespace per_process { |
| 8 | +native_module::NativeModuleLoader native_module_loader; |
| 9 | +} // namespace per_process |
| 10 | + |
6 | 11 | namespace native_module { |
7 | 12 |
|
8 | 13 | using v8::Array; |
@@ -78,13 +83,14 @@ void NativeModuleLoader::GetCacheUsage( |
78 | 83 | void NativeModuleLoader::SourceObjectGetter( |
79 | 84 | Local<Name> property, const PropertyCallbackInfo<Value>& info) { |
80 | 85 | Local<Context> context = info.GetIsolate()->GetCurrentContext(); |
81 | | - info.GetReturnValue().Set(per_process_loader.GetSourceObject(context)); |
| 86 | + info.GetReturnValue().Set( |
| 87 | + per_process::native_module_loader.GetSourceObject(context)); |
82 | 88 | } |
83 | 89 |
|
84 | 90 | void NativeModuleLoader::ConfigStringGetter( |
85 | 91 | Local<Name> property, const PropertyCallbackInfo<Value>& info) { |
86 | 92 | info.GetReturnValue().Set( |
87 | | - per_process_loader.GetConfigString(info.GetIsolate())); |
| 93 | + per_process::native_module_loader.GetConfigString(info.GetIsolate())); |
88 | 94 | } |
89 | 95 |
|
90 | 96 | Local<Object> NativeModuleLoader::GetSourceObject( |
@@ -96,41 +102,62 @@ Local<String> NativeModuleLoader::GetConfigString(Isolate* isolate) const { |
96 | 102 | return config_.ToStringChecked(isolate); |
97 | 103 | } |
98 | 104 |
|
99 | | -Local<String> NativeModuleLoader::GetSource(Isolate* isolate, |
100 | | - const char* id) const { |
101 | | - const auto it = source_.find(id); |
102 | | - CHECK_NE(it, source_.end()); |
103 | | - return it->second.ToStringChecked(isolate); |
104 | | -} |
105 | | - |
106 | 105 | NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) { |
107 | 106 | LoadJavaScriptSource(); |
108 | 107 | LoadCodeCache(); |
109 | 108 | } |
110 | 109 |
|
111 | | -void NativeModuleLoader::CompileCodeCache( |
112 | | - const FunctionCallbackInfo<Value>& args) { |
| 110 | +// This is supposed to be run only by the main thread in |
| 111 | +// tools/generate_code_cache.js |
| 112 | +void NativeModuleLoader::GetCodeCache(const FunctionCallbackInfo<Value>& args) { |
113 | 113 | Environment* env = Environment::GetCurrent(args); |
| 114 | + Isolate* isolate = env->isolate(); |
| 115 | + CHECK(env->is_main_thread()); |
| 116 | + |
114 | 117 | CHECK(args[0]->IsString()); |
115 | | - node::Utf8Value id(env->isolate(), args[0].As<String>()); |
| 118 | + node::Utf8Value id_v(isolate, args[0].As<String>()); |
| 119 | + const char* id = *id_v; |
116 | 120 |
|
117 | | - // TODO(joyeecheung): allow compiling cache for bootstrapper by |
118 | | - // switching on id |
119 | | - MaybeLocal<Value> result = |
120 | | - CompileAsModule(env, *id, CompilationResultType::kCodeCache); |
121 | | - if (!result.IsEmpty()) { |
122 | | - args.GetReturnValue().Set(result.ToLocalChecked()); |
| 121 | + const NativeModuleLoader& loader = per_process::native_module_loader; |
| 122 | + MaybeLocal<Uint8Array> ret = loader.GetCodeCache(isolate, id); |
| 123 | + if (!ret.IsEmpty()) { |
| 124 | + args.GetReturnValue().Set(ret.ToLocalChecked()); |
123 | 125 | } |
124 | 126 | } |
125 | 127 |
|
| 128 | +// This is supposed to be run only by the main thread in |
| 129 | +// tools/generate_code_cache.js |
| 130 | +MaybeLocal<Uint8Array> NativeModuleLoader::GetCodeCache(Isolate* isolate, |
| 131 | + const char* id) const { |
| 132 | + EscapableHandleScope scope(isolate); |
| 133 | + Mutex::ScopedLock lock(code_cache_mutex_); |
| 134 | + |
| 135 | + ScriptCompiler::CachedData* cached_data = nullptr; |
| 136 | + const auto it = code_cache_.find(id); |
| 137 | + if (it == code_cache_.end()) { |
| 138 | + // The module has not been compiled before. |
| 139 | + return MaybeLocal<Uint8Array>(); |
| 140 | + } |
| 141 | + |
| 142 | + cached_data = it->second.get(); |
| 143 | + |
| 144 | + MallocedBuffer<uint8_t> copied(cached_data->length); |
| 145 | + memcpy(copied.data, cached_data->data, cached_data->length); |
| 146 | + Local<ArrayBuffer> buf = |
| 147 | + ArrayBuffer::New(isolate, |
| 148 | + copied.release(), |
| 149 | + cached_data->length, |
| 150 | + ArrayBufferCreationMode::kInternalized); |
| 151 | + return scope.Escape(Uint8Array::New(buf, 0, cached_data->length)); |
| 152 | +} |
| 153 | + |
126 | 154 | void NativeModuleLoader::CompileFunction( |
127 | 155 | const FunctionCallbackInfo<Value>& args) { |
128 | 156 | Environment* env = Environment::GetCurrent(args); |
129 | 157 | CHECK(args[0]->IsString()); |
130 | 158 | node::Utf8Value id(env->isolate(), args[0].As<String>()); |
131 | 159 |
|
132 | | - MaybeLocal<Value> result = |
133 | | - CompileAsModule(env, *id, CompilationResultType::kFunction); |
| 160 | + MaybeLocal<Function> result = CompileAsModule(env, *id); |
134 | 161 | if (!result.IsEmpty()) { |
135 | 162 | args.GetReturnValue().Set(result.ToLocalChecked()); |
136 | 163 | } |
@@ -145,57 +172,43 @@ MaybeLocal<Value> NativeModuleLoader::CompileAndCall( |
145 | 172 | std::vector<Local<Value>>* arguments, |
146 | 173 | Environment* optional_env) { |
147 | 174 | Isolate* isolate = context->GetIsolate(); |
148 | | - MaybeLocal<Value> compiled = per_process_loader.LookupAndCompile( |
149 | | - context, id, parameters, CompilationResultType::kFunction, nullptr); |
| 175 | + MaybeLocal<Function> compiled = |
| 176 | + per_process::native_module_loader.LookupAndCompile( |
| 177 | + context, id, parameters, nullptr); |
150 | 178 | if (compiled.IsEmpty()) { |
151 | | - return compiled; |
| 179 | + return MaybeLocal<Value>(); |
152 | 180 | } |
153 | 181 | Local<Function> fn = compiled.ToLocalChecked().As<Function>(); |
154 | 182 | return fn->Call( |
155 | 183 | context, v8::Null(isolate), arguments->size(), arguments->data()); |
156 | 184 | } |
157 | 185 |
|
158 | | -MaybeLocal<Value> NativeModuleLoader::CompileAsModule( |
159 | | - Environment* env, const char* id, CompilationResultType result) { |
| 186 | +MaybeLocal<Function> NativeModuleLoader::CompileAsModule(Environment* env, |
| 187 | + const char* id) { |
160 | 188 | std::vector<Local<String>> parameters = {env->exports_string(), |
161 | 189 | env->require_string(), |
162 | 190 | env->module_string(), |
163 | 191 | env->process_string(), |
164 | 192 | env->internal_binding_string()}; |
165 | | - return per_process_loader.LookupAndCompile( |
166 | | - env->context(), id, ¶meters, result, env); |
167 | | -} |
168 | | - |
169 | | -// Returns nullptr if there is no code cache corresponding to the id |
170 | | -ScriptCompiler::CachedData* NativeModuleLoader::GetCachedData( |
171 | | - const char* id) const { |
172 | | - const auto it = per_process_loader.code_cache_.find(id); |
173 | | - // This could be false if the module cannot be cached somehow. |
174 | | - // See lib/internal/bootstrap/cache.js on the modules that cannot be cached |
175 | | - if (it == per_process_loader.code_cache_.end()) { |
176 | | - return nullptr; |
177 | | - } |
178 | | - |
179 | | - const uint8_t* code_cache_value = it->second.one_bytes_data(); |
180 | | - size_t code_cache_length = it->second.length(); |
181 | | - |
182 | | - return new ScriptCompiler::CachedData(code_cache_value, code_cache_length); |
| 193 | + return per_process::native_module_loader.LookupAndCompile( |
| 194 | + env->context(), id, ¶meters, env); |
183 | 195 | } |
184 | 196 |
|
185 | 197 | // Returns Local<Function> of the compiled module if return_code_cache |
186 | 198 | // is false (we are only compiling the function). |
187 | 199 | // Otherwise return a Local<Object> containing the cache. |
188 | | -MaybeLocal<Value> NativeModuleLoader::LookupAndCompile( |
| 200 | +MaybeLocal<Function> NativeModuleLoader::LookupAndCompile( |
189 | 201 | Local<Context> context, |
190 | 202 | const char* id, |
191 | 203 | std::vector<Local<String>>* parameters, |
192 | | - CompilationResultType result_type, |
193 | 204 | Environment* optional_env) { |
194 | 205 | Isolate* isolate = context->GetIsolate(); |
195 | 206 | EscapableHandleScope scope(isolate); |
196 | 207 | Local<Value> ret; // Used to convert to MaybeLocal before return |
197 | 208 |
|
198 | | - Local<String> source = GetSource(isolate, id); |
| 209 | + const auto source_it = source_.find(id); |
| 210 | + CHECK_NE(source_it, source_.end()); |
| 211 | + Local<String> source = source_it->second.ToStringChecked(isolate); |
199 | 212 |
|
200 | 213 | std::string filename_s = id + std::string(".js"); |
201 | 214 | Local<String> filename = |
@@ -204,31 +217,24 @@ MaybeLocal<Value> NativeModuleLoader::LookupAndCompile( |
204 | 217 | Local<Integer> column_offset = Integer::New(isolate, 0); |
205 | 218 | ScriptOrigin origin(filename, line_offset, column_offset); |
206 | 219 |
|
207 | | - bool use_cache = false; |
208 | | - ScriptCompiler::CachedData* cached_data = nullptr; |
| 220 | + Mutex::ScopedLock lock(code_cache_mutex_); |
209 | 221 |
|
210 | | - // 1. We won't even check the existence of the cache if the binary is not |
211 | | - // built with them. |
212 | | - // 2. If we are generating code cache for tools/general_code_cache.js, we |
213 | | - // are not going to use any cache ourselves. |
214 | | - if (has_code_cache_ && result_type == CompilationResultType::kFunction) { |
215 | | - cached_data = GetCachedData(id); |
216 | | - if (cached_data != nullptr) { |
217 | | - use_cache = true; |
| 222 | + ScriptCompiler::CachedData* cached_data = nullptr; |
| 223 | + { |
| 224 | + auto cache_it = code_cache_.find(id); |
| 225 | + if (cache_it != code_cache_.end()) { |
| 226 | + // Transfer ownership to ScriptCompiler::Source later. |
| 227 | + cached_data = cache_it->second.release(); |
| 228 | + code_cache_.erase(cache_it); |
218 | 229 | } |
219 | 230 | } |
220 | 231 |
|
| 232 | + const bool use_cache = cached_data != nullptr; |
| 233 | + ScriptCompiler::CompileOptions options = |
| 234 | + use_cache ? ScriptCompiler::kConsumeCodeCache |
| 235 | + : ScriptCompiler::kEagerCompile; |
221 | 236 | ScriptCompiler::Source script_source(source, origin, cached_data); |
222 | 237 |
|
223 | | - ScriptCompiler::CompileOptions options; |
224 | | - if (result_type == CompilationResultType::kCodeCache) { |
225 | | - options = ScriptCompiler::kEagerCompile; |
226 | | - } else if (use_cache) { |
227 | | - options = ScriptCompiler::kConsumeCodeCache; |
228 | | - } else { |
229 | | - options = ScriptCompiler::kNoCompileOptions; |
230 | | - } |
231 | | - |
232 | 238 | MaybeLocal<Function> maybe_fun = |
233 | 239 | ScriptCompiler::CompileFunctionInContext(context, |
234 | 240 | &script_source, |
@@ -244,10 +250,14 @@ MaybeLocal<Value> NativeModuleLoader::LookupAndCompile( |
244 | 250 | // In the case of early errors, v8 is already capable of |
245 | 251 | // decorating the stack for us - note that we use CompileFunctionInContext |
246 | 252 | // so there is no need to worry about wrappers. |
247 | | - return MaybeLocal<Value>(); |
| 253 | + return MaybeLocal<Function>(); |
248 | 254 | } |
249 | 255 |
|
250 | 256 | Local<Function> fun = maybe_fun.ToLocalChecked(); |
| 257 | + // XXX(joyeecheung): this bookkeeping is not exactly accurate because |
| 258 | + // it only starts after the Environment is created, so the per_context.js |
| 259 | + // will never be in any of these two sets, but the two sets are only for |
| 260 | + // testing anyway. |
251 | 261 | if (use_cache) { |
252 | 262 | if (optional_env != nullptr) { |
253 | 263 | // This could happen when Node is run with any v8 flag, but |
@@ -264,29 +274,15 @@ MaybeLocal<Value> NativeModuleLoader::LookupAndCompile( |
264 | 274 | } |
265 | 275 | } |
266 | 276 |
|
267 | | - if (result_type == CompilationResultType::kCodeCache) { |
268 | | - std::unique_ptr<ScriptCompiler::CachedData> cached_data( |
269 | | - ScriptCompiler::CreateCodeCacheForFunction(fun)); |
270 | | - CHECK_NE(cached_data, nullptr); |
271 | | - size_t cached_data_length = cached_data->length; |
272 | | - // Since we have no special allocator to create an ArrayBuffer |
273 | | - // from a new'ed pointer, we will need to copy it - but this |
274 | | - // code path is only run by the tooling that generates the code |
275 | | - // cache to be bundled in the binary |
276 | | - // so it should be fine. |
277 | | - MallocedBuffer<uint8_t> copied(cached_data->length); |
278 | | - memcpy(copied.data, cached_data->data, cached_data_length); |
279 | | - Local<ArrayBuffer> buf = |
280 | | - ArrayBuffer::New(isolate, |
281 | | - copied.release(), |
282 | | - cached_data_length, |
283 | | - ArrayBufferCreationMode::kInternalized); |
284 | | - ret = Uint8Array::New(buf, 0, cached_data_length); |
285 | | - } else { |
286 | | - ret = fun; |
287 | | - } |
| 277 | + // Generate new cache for next compilation |
| 278 | + std::unique_ptr<ScriptCompiler::CachedData> new_cached_data( |
| 279 | + ScriptCompiler::CreateCodeCacheForFunction(fun)); |
| 280 | + CHECK_NE(new_cached_data, nullptr); |
288 | 281 |
|
289 | | - return scope.Escape(ret); |
| 282 | + // The old entry should've been erased by now so we can just emplace |
| 283 | + code_cache_.emplace(id, std::move(new_cached_data)); |
| 284 | + |
| 285 | + return scope.Escape(fun); |
290 | 286 | } |
291 | 287 |
|
292 | 288 | void NativeModuleLoader::Initialize(Local<Object> target, |
@@ -320,8 +316,7 @@ void NativeModuleLoader::Initialize(Local<Object> target, |
320 | 316 | target, "getCacheUsage", NativeModuleLoader::GetCacheUsage); |
321 | 317 | env->SetMethod( |
322 | 318 | target, "compileFunction", NativeModuleLoader::CompileFunction); |
323 | | - env->SetMethod( |
324 | | - target, "compileCodeCache", NativeModuleLoader::CompileCodeCache); |
| 319 | + env->SetMethod(target, "getCodeCache", NativeModuleLoader::GetCodeCache); |
325 | 320 | // internalBinding('native_module') should be frozen |
326 | 321 | target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust(); |
327 | 322 | } |
|