[WebAssembly][Modules] Prototype wasm import
[WebKit-https.git] / Source / JavaScriptCore / builtins / ModuleLoaderPrototype.js
1 /*
2  * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 // https://whatwg.github.io/loader/#loader-object
27 // Module Loader has several hooks that can be customized by the platform.
28 // For example, the [[Fetch]] hook can be provided by the JavaScriptCore shell
29 // as fetching the payload from the local file system.
30 // Currently, there are 4 hooks.
31 //    1. Loader.resolve
32 //    2. Loader.fetch
33
34 @globalPrivate
35 function setStateToMax(entry, newState)
36 {
37     // https://whatwg.github.io/loader/#set-state-to-max
38
39     "use strict";
40
41     if (entry.state < newState)
42         entry.state = newState;
43 }
44
45 @globalPrivate
46 function newRegistryEntry(key)
47 {
48     // https://whatwg.github.io/loader/#registry
49     //
50     // Each registry entry becomes one of the 5 states.
51     // 1. Fetch
52     //     Ready to fetch (or now fetching) the resource of this module.
53     //     Typically, we fetch the source code over the network or from the file system.
54     //     a. If the status is Fetch and there is no entry.fetch promise, the entry is ready to fetch.
55     //     b. If the status is Fetch and there is the entry.fetch promise, the entry is just fetching the resource.
56     //
57     // 2. Instantiate (AnalyzeModule)
58     //     Ready to instantiate (or now instantiating) the module record from the fetched
59     //     source code.
60     //     Typically, we parse the module code, extract the dependencies and binding information.
61     //     a. If the status is Instantiate and there is no entry.instantiate promise, the entry is ready to instantiate.
62     //     b. If the status is Instantiate and there is the entry.fetch promise, the entry is just instantiating
63     //        the module record.
64     //
65     // 3. Satisfy
66     //     Ready to request the dependent modules (or now requesting & resolving).
67     //     Without this state, the current draft causes infinite recursion when there is circular dependency.
68     //     a. If the status is Satisfy and there is no entry.satisfy promise, the entry is ready to resolve the dependencies.
69     //     b. If the status is Satisfy and there is the entry.satisfy promise, the entry is just resolving
70     //        the dependencies.
71     //
72     // 4. Link
73     //     Ready to link the module with the other modules.
74     //     Linking means that the module imports and exports the bindings from/to the other modules.
75     //
76     // 5. Ready
77     //     The module is linked, so the module is ready to be executed.
78     //
79     // Each registry entry has the 4 promises; "fetch", "instantiate" and "satisfy".
80     // They are assigned when starting the each phase. And they are fulfilled when the each phase is completed.
81     //
82     // In the current module draft, linking will be performed after the whole modules are instantiated and the dependencies are resolved.
83     // And execution is also done after the all modules are linked.
84     //
85     // TODO: We need to exploit the way to execute the module while fetching non-related modules.
86     // One solution; introducing the ready promise chain to execute the modules concurrently while keeping
87     // the execution order.
88
89     "use strict";
90
91     return {
92         key: key,
93         state: @ModuleFetch,
94         fetch: @undefined,
95         instantiate: @undefined,
96         satisfy: @undefined,
97         dependencies: [], // To keep the module order, we store the module keys in the array.
98         module: @undefined, // JSModuleRecord
99         linkError: @undefined,
100         linkSucceeded: true,
101         evaluated: false,
102     };
103 }
104
105 function ensureRegistered(key)
106 {
107     // https://whatwg.github.io/loader/#ensure-registered
108
109     "use strict";
110
111     var entry = this.registry.@get(key);
112     if (entry)
113         return entry;
114
115     entry = @newRegistryEntry(key);
116     this.registry.@set(key, entry);
117
118     return entry;
119 }
120
121 function forceFulfillPromise(promise, value)
122 {
123     "use strict";
124
125     if (@getByIdDirectPrivate(promise, "promiseState") === @promiseStatePending)
126         @fulfillPromise(promise, value);
127 }
128
129 function fulfillFetch(entry, source)
130 {
131     // https://whatwg.github.io/loader/#fulfill-fetch
132
133     "use strict";
134
135     if (!entry.fetch)
136         entry.fetch = @newPromiseCapability(@InternalPromise).@promise;
137     this.forceFulfillPromise(entry.fetch, source);
138     @setStateToMax(entry, @ModuleInstantiate);
139 }
140
141 // Loader.
142
143 function requestFetch(entry, parameters, fetcher)
144 {
145     // https://whatwg.github.io/loader/#request-fetch
146
147     "use strict";
148
149     if (entry.fetch) {
150         var currentAttempt = entry.fetch;
151         if (entry.state !== @ModuleFetch)
152             return currentAttempt;
153
154         return currentAttempt.catch((error) => {
155             // Even if the existing fetching request failed, this attempt may succeed.
156             // For example, previous attempt used invalid integrity="" value. But this
157             // request could have the correct integrity="" value. In that case, we should
158             // retry fetching for this request.
159             // https://html.spec.whatwg.org/#fetch-a-single-module-script
160             if (currentAttempt === entry.fetch)
161                 entry.fetch = @undefined;
162             return this.requestFetch(entry, parameters, fetcher);
163         });
164     }
165
166     // Hook point.
167     // 2. Loader.fetch
168     //     https://whatwg.github.io/loader/#browser-fetch
169     //     Take the key and fetch the resource actually.
170     //     For example, JavaScriptCore shell can provide the hook fetching the resource
171     //     from the local file system.
172     var fetchPromise = this.fetch(entry.key, parameters, fetcher).then((source) => {
173         @setStateToMax(entry, @ModuleInstantiate);
174         return source;
175     });
176     entry.fetch = fetchPromise;
177     return fetchPromise;
178 }
179
180 function requestInstantiate(entry, parameters, fetcher)
181 {
182     // https://whatwg.github.io/loader/#request-instantiate
183
184     "use strict";
185
186     // entry.instantiate is set if fetch succeeds.
187     if (entry.instantiate)
188         return entry.instantiate;
189
190     var instantiatePromise = this.requestFetch(entry, parameters, fetcher).then((source) => {
191         // https://html.spec.whatwg.org/#fetch-a-single-module-script
192         // Now fetching request succeeds. Then even if instantiation fails, we should cache it.
193         // Instantiation won't be retried.
194         if (entry.instantiate)
195             return entry.instantiate;
196         entry.instantiate = instantiatePromise;
197
198         var key = entry.key;
199         return this.parseModule(key, source).then((moduleRecord) => {
200             var dependenciesMap = moduleRecord.dependenciesMap;
201             var requestedModules = this.requestedModules(moduleRecord);
202             var dependencies = @newArrayWithSize(requestedModules.length);
203             for (var i = 0, length = requestedModules.length; i < length; ++i) {
204                 var depName = requestedModules[i];
205                 var depKey = this.resolveSync(depName, key, fetcher);
206                 var depEntry = this.ensureRegistered(depKey);
207                 @putByValDirect(dependencies, i, depEntry);
208                 dependenciesMap.@set(depName, depEntry);
209             }
210             entry.dependencies = dependencies;
211             entry.module = moduleRecord;
212             @setStateToMax(entry, @ModuleSatisfy);
213             return entry;
214         });
215     });
216     return instantiatePromise;
217 }
218
219 function requestSatisfy(entry, parameters, fetcher, visited)
220 {
221     // https://html.spec.whatwg.org/#internal-module-script-graph-fetching-procedure
222
223     "use strict";
224
225     if (entry.satisfy)
226         return entry.satisfy;
227
228     visited.@add(entry);
229     var satisfyPromise = this.requestInstantiate(entry, parameters, fetcher).then((entry) => {
230         if (entry.satisfy)
231             return entry.satisfy;
232
233         var depLoads = @newArrayWithSize(entry.dependencies.length);
234         for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
235             var depEntry = entry.dependencies[i];
236             var promise;
237
238             // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
239             // Stop tracing the circular dependencies.
240             // But to retrieve the instantiated module record correctly,
241             // we need to wait for the instantiation for the dependent module.
242             // For example, reaching here, the module is starting resolving the dependencies.
243             // But the module may or may not reach the instantiation phase in the loader's pipeline.
244             // If we wait for the Satisfy for this module, it construct the circular promise chain and
245             // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
246             // the Satisfy for this module, we just wait Instantiate for this.
247             if (visited.@has(depEntry))
248                 promise = this.requestInstantiate(depEntry, @undefined, fetcher);
249             else {
250                 // Currently, module loader do not pass any information for non-top-level module fetching.
251                 promise = this.requestSatisfy(depEntry, @undefined, fetcher, visited);
252             }
253             @putByValDirect(depLoads, i, promise);
254         }
255
256         return @InternalPromise.internalAll(depLoads).then((entries) => {
257             if (entry.satisfy)
258                 return entry;
259             @setStateToMax(entry, @ModuleLink);
260             entry.satisfy = satisfyPromise;
261             return entry;
262         });
263     });
264
265     return satisfyPromise;
266 }
267
268 // Linking semantics.
269
270 function link(entry, fetcher)
271 {
272     // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-instantiate-a-module-script
273
274     "use strict";
275
276     if (!entry.linkSucceeded)
277         throw entry.linkError;
278     if (entry.state === @ModuleReady)
279         return;
280     @setStateToMax(entry, @ModuleReady);
281
282     try {
283         // Since we already have the "dependencies" field,
284         // we can call moduleDeclarationInstantiation with the correct order
285         // without constructing the dependency graph by calling dependencyGraph.
286         var dependencies = entry.dependencies;
287         for (var i = 0, length = dependencies.length; i < length; ++i)
288             this.link(dependencies[i], fetcher);
289
290         this.moduleDeclarationInstantiation(entry.module, fetcher);
291     } catch (error) {
292         entry.linkSucceeded = false;
293         entry.linkError = error;
294         throw error;
295     }
296 }
297
298 // Module semantics.
299
300 function moduleEvaluation(entry, fetcher)
301 {
302     // http://www.ecma-international.org/ecma-262/6.0/#sec-moduleevaluation
303
304     "use strict";
305
306     if (entry.evaluated)
307         return;
308     entry.evaluated = true;
309
310     // The contents of the [[RequestedModules]] is cloned into entry.dependencies.
311     var dependencies = entry.dependencies;
312     for (var i = 0, length = dependencies.length; i < length; ++i)
313         this.moduleEvaluation(dependencies[i], fetcher);
314
315     this.evaluate(entry.key, entry.module, fetcher);
316 }
317
318 // APIs to control the module loader.
319
320 function provideFetch(key, value)
321 {
322     "use strict";
323
324     var entry = this.ensureRegistered(key);
325
326     if (entry.state > @ModuleFetch)
327         @throwTypeError("Requested module is already fetched.");
328     this.fulfillFetch(entry, value);
329 }
330
331 function loadModule(moduleName, parameters, fetcher)
332 {
333     "use strict";
334
335     // Loader.resolve hook point.
336     // resolve: moduleName => Promise(moduleKey)
337     // Take the name and resolve it to the unique identifier for the resource location.
338     // For example, take the "jquery" and return the URL for the resource.
339     return this.resolve(moduleName, @undefined, fetcher).then((key) => {
340         return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher, new @Set);
341     }).then((entry) => {
342         return entry.key;
343     });
344 }
345
346 function linkAndEvaluateModule(key, fetcher)
347 {
348     "use strict";
349
350     var entry = this.ensureRegistered(key);
351     if (entry.state < @ModuleLink)
352         @throwTypeError("Requested module is not instantiated yet.");
353
354     this.link(entry, fetcher);
355     return this.moduleEvaluation(entry, fetcher);
356 }
357
358 function loadAndEvaluateModule(moduleName, parameters, fetcher)
359 {
360     "use strict";
361
362     return this.loadModule(moduleName, parameters, fetcher).then((key) => {
363         return this.linkAndEvaluateModule(key, fetcher);
364     });
365 }
366
367 function requestImportModule(key, parameters, fetcher)
368 {
369     "use strict";
370
371     return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher, new @Set).then((entry) => {
372         this.linkAndEvaluateModule(entry.key, fetcher);
373         return this.getModuleNamespaceObject(entry.module);
374     });
375 }