| import Builder from '../Builder.js'; |
| import * as assert from '../assert.js'; |
| |
| const pageSize = 64 * 1024; |
| |
| const verbose = false; |
| |
| // https://github.com/WebAssembly/design/blob/master/Modules.md#imports |
| // Says: |
| // |
| // A linear memory import includes the same set of fields defined in the Linear |
| // Memory section: initial length and optional maximum length. The host |
| // environment must only allow imports of WebAssembly linear memories that have |
| // initial length greater-or-equal than the initial length declared in the |
| // import and that have maximum length less-or-equal than the maximum length |
| // declared in the import. This ensures that separate compilation can assume: |
| // memory accesses below the declared initial length are always in-bounds, |
| // accesses above the declared maximum length are always out-of-bounds and if |
| // initial equals maximum, the length is fixed. |
| |
| const instantiate = (builder, importObject = undefined) => { |
| return new WebAssembly.Instance( |
| new WebAssembly.Module( |
| builder.WebAssembly().get()), |
| importObject); |
| }; |
| |
| const test = (memoryToImport, importedMemoryDeclaration, growMemoryToImportBy = undefined, growImportedMemoryDeclarationBy = undefined, expectedFinal) => { |
| const builder0 = (new Builder()) |
| .Type().End() |
| .Function().End() |
| .Memory().InitialMaxPages(memoryToImport.initial, memoryToImport.maximum).End() |
| .Export() |
| .Function("current").Function("grow").Function("get") |
| .Memory("memory", 0) |
| .End() |
| .Code() |
| .Function("current", { params: [], ret: "i32" }).CurrentMemory(0).Return().End() |
| .Function("grow", { params: ["i32"], ret: "i32" }).GetLocal(0).GrowMemory(0).Return().End() |
| .Function("get", { params: ["i32"], ret: "i32" }).GetLocal(0).I32Load(2, 0).Return().End() |
| .End(); |
| |
| const builder1 = (new Builder()) |
| .Type().End() |
| .Import().Memory("imp", "memory", importedMemoryDeclaration).End() |
| .Function().End() |
| .Export() |
| .Function("current").Function("grow").Function("get") |
| .Memory("memory", 0) |
| .End() |
| .Code() |
| .Function("current", { params: [], ret: "i32" }).CurrentMemory(0).Return().End() |
| .Function("grow", { params: ["i32"], ret: "i32" }).GetLocal(0).GrowMemory(0).Return().End() |
| .Function("get", { params: ["i32"], ret: "i32" }).GetLocal(0).I32Load(2, 0).Return().End() |
| .End(); |
| |
| const i0 = instantiate(builder0); |
| const i1 = instantiate(builder1, { imp: { memory: i0.exports.memory } }); |
| assert.eq(i0.exports.current(), i1.exports.current()); |
| assert.eq(i0.exports.current(), memoryToImport.initial); |
| |
| if (growMemoryToImportBy !== undefined) { |
| const grow = i0.exports.grow(growMemoryToImportBy); |
| if (verbose) |
| print(`currents: ${i0.exports.current()} and ${i1.exports.current()} -- grow result: ${grow}`); |
| } |
| |
| if (growImportedMemoryDeclarationBy !== undefined) { |
| const grow = i1.exports.grow(growImportedMemoryDeclarationBy); |
| if (verbose) |
| print(`currents: ${i0.exports.current()} and ${i1.exports.current()} -- grow result: ${grow}`); |
| } |
| |
| assert.eq(i0.exports.current(), i1.exports.current()); |
| assert.eq(i0.exports.current(), expectedFinal); |
| }; |
| |
| const u = undefined; |
| |
| // Identical Just Works. |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 0, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 1, u, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 2, u, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, 3, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 0, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 1, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 2, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 2, maximum: 4 }, u, 3, 2); |
| |
| // Allowed: imported initial is greater than declared. |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 0, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 1, u, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 2, u, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, 3, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 0, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 1, u, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 2, u, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, 3, u, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 0, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 1, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 2, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 1, maximum: 4 }, u, 3, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 0, 2); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 1, 3); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 2, 4); |
| test({ initial: 2, maximum: 4 }, { initial: 0, maximum: 4 }, u, 3, 2); |
| |
| // Allowed: imported maximum is lesser than declared. |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 0, u, 2); |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 1, u, 3); |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, 2, u, 2); |
| test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, 0, u, 2); |
| test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, 1, u, 2); |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 0, 2); |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 1, 3); |
| test({ initial: 2, maximum: 3 }, { initial: 2, maximum: 4 }, u, 2, 2); |
| test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, u, 0, 2); |
| test({ initial: 2, maximum: 2 }, { initial: 2, maximum: 4 }, u, 1, 2); |
| |
| // Allowed: no declared maximum, same as above. |
| test({ initial: 2, maximum: 4 }, { initial: 2 }, 0, u, 2); |
| |
| // Disallowed: imported initial is lesser than declared. |
| assert.throws(() => test({ initial: 1, maximum: 4 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import imp:memory provided an 'initial' that is smaller than the module's declared 'initial' import memory size`); |
| assert.throws(() => test({ initial: 0, maximum: 4 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import imp:memory provided an 'initial' that is smaller than the module's declared 'initial' import memory size`); |
| |
| // Disallowed: imported maximum is greater than declared. |
| assert.throws(() => test({ initial: 2, maximum: 5 }, { initial: 2, maximum: 4 }, u, u, 2), WebAssembly.LinkError, `Memory import imp:memory provided a 'maximum' that is larger than the module's declared 'maximum' import memory size`); |
| |
| // Disallowed: no imported maximum, same as above. |
| assert.throws(() => test({ initial: 2 }, { initial: 2, maximum: 4 }, 0, u, 2), WebAssembly.LinkError, `Memory import imp:memory did not have a 'maximum' but the module requires that it does`); |