Skip to content

Commit 21f014f

Browse files
authored
wasm2js: support non-constant indexes for memory and table segments (#2055)
Mostly what we need for dynamic linking, at least on the binaryen side.
1 parent ef6020c commit 21f014f

4 files changed

Lines changed: 134 additions & 37 deletions

File tree

scripts/test/env.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ export function getTempRet0() {
88
return tempRet0;
99
}
1010

11+
export const memoryBase = 0;
12+
export const tableBase = 0;

src/wasm2js.h

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ void sequenceAppend(Ref& ast, Ref extra) {
7979
ast = ValueBuilder::makeSeq(ast, extra);
8080
}
8181

82+
IString stringToIString(std::string str) {
83+
return IString(str.c_str(), false);
84+
}
85+
8286
// Used when taking a wasm name and generating a JS identifier. Each scope here
8387
// is used to ensure that all names have a unique name but the same wasm name
8488
// within a scope always resolves to the same symbol.
@@ -89,16 +93,6 @@ enum class NameScope {
8993
Max,
9094
};
9195

92-
template<typename T>
93-
static uint64_t constOffset(const T& segment) {
94-
auto* c = segment.offset->template dynCast<Const>();
95-
if (!c) {
96-
Fatal() << "non-constant offsets aren't supported yet\n";
97-
abort();
98-
}
99-
return c->value.getInteger();
100-
}
101-
10296
//
10397
// Wasm2JSBuilder - converts a WebAssembly module's functions into JS
10498
//
@@ -196,7 +190,7 @@ class Wasm2JSBuilder {
196190
out << "_" << i;
197191
}
198192
auto mangled = asmangle(out.str());
199-
ret = IString(mangled.c_str(), false);
193+
ret = stringToIString(mangled);
200194
if (!allMangledNames.count(ret)) {
201195
break;
202196
}
@@ -219,10 +213,6 @@ class Wasm2JSBuilder {
219213
return ret;
220214
}
221215

222-
size_t getTableSize() {
223-
return tableSize;
224-
}
225-
226216
private:
227217
Flags flags;
228218
PassOptions options;
@@ -237,8 +227,6 @@ class Wasm2JSBuilder {
237227
std::unordered_map<const char*, IString> mangledNames[(int) NameScope::Max];
238228
std::unordered_set<IString> allMangledNames;
239229

240-
size_t tableSize;
241-
242230
// If a function is callable from outside, we'll need to cast the inputs
243231
// and our return value. Otherwise, internally, casts are only needed
244232
// on operations.
@@ -331,17 +319,6 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
331319
ModuleUtils::iterImportedGlobals(*wasm, [&](Global* import) {
332320
addGlobalImport(asmFunc[3], import);
333321
});
334-
// figure out the table size
335-
tableSize = std::accumulate(wasm->table.segments.begin(),
336-
wasm->table.segments.end(),
337-
0, [&](size_t size, Table::Segment seg) -> size_t {
338-
return size + seg.data.size() + constOffset(seg);
339-
});
340-
size_t pow2ed = 1;
341-
while (pow2ed < tableSize) {
342-
pow2ed <<= 1;
343-
}
344-
tableSize = pow2ed;
345322

346323
// make sure exports get their expected names
347324
for (auto& e : wasm->exports) {
@@ -509,8 +486,7 @@ void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) {
509486
// Emit a simple flat table as a JS array literal. Otherwise,
510487
// emit assignments separately for each index.
511488
FlatTable flat(wasm->table);
512-
assert(flat.valid); // TODO: non-flat tables
513-
if (!wasm->table.imported()) {
489+
if (flat.valid && !wasm->table.imported()) {
514490
Ref theVar = ValueBuilder::makeVar();
515491
ast->push_back(theVar);
516492
Ref theArray = ValueBuilder::makeArray();
@@ -525,16 +501,33 @@ void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) {
525501
ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name));
526502
}
527503
} else {
504+
if (!wasm->table.imported()) {
505+
Ref theVar = ValueBuilder::makeVar();
506+
ast->push_back(theVar);
507+
ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, ValueBuilder::makeArray());
508+
}
509+
528510
// TODO: optimize for size
529511
for (auto& segment : wasm->table.segments) {
530512
auto offset = segment.offset;
531-
Index start = offset->cast<Const>()->value.geti32();
532513
for (Index i = 0; i < segment.data.size(); i++) {
514+
Ref index;
515+
if (auto* c = offset->dynCast<Const>()) {
516+
index = ValueBuilder::makeInt(c->value.geti32() + i);
517+
} else if (auto* get = offset->dynCast<GetGlobal>()) {
518+
index = ValueBuilder::makeBinary(
519+
ValueBuilder::makeName(stringToIString(asmangle(get->name.str))),
520+
PLUS,
521+
ValueBuilder::makeNum(i)
522+
);
523+
} else {
524+
WASM_UNREACHABLE();
525+
}
533526
ast->push_back(ValueBuilder::makeStatement(
534527
ValueBuilder::makeBinary(
535528
ValueBuilder::makeSub(
536529
ValueBuilder::makeName(FUNCTION_TABLE),
537-
ValueBuilder::makeInt(start + i)
530+
index
538531
),
539532
SET,
540533
ValueBuilder::makeName(fromName(segment.data[i], NameScope::Top))
@@ -1794,7 +1787,7 @@ class Wasm2JSGlue {
17941787
void emitPostEmscripten();
17951788
void emitPostES6();
17961789

1797-
void emitMemory(std::string buffer, std::string segmentWriter);
1790+
void emitMemory(std::string buffer, std::string segmentWriter, std::function<std::string (std::string)> accessGlobal);
17981791
void emitScratchMemorySupport();
17991792
};
18001793

@@ -1857,7 +1850,9 @@ void Wasm2JSGlue::emitPost() {
18571850
}
18581851

18591852
void Wasm2JSGlue::emitPostEmscripten() {
1860-
emitMemory("wasmMemory.buffer", "writeSegment");
1853+
emitMemory("wasmMemory.buffer", "writeSegment", [](std::string globalName) {
1854+
return std::string("asmLibraryArg['") + asmangle(globalName) + "']";
1855+
});
18611856

18621857
out << "return asmFunc({\n"
18631858
<< " 'Int8Array': Int8Array,\n"
@@ -1895,7 +1890,8 @@ void Wasm2JSGlue::emitPostES6() {
18951890
}
18961891

18971892
emitMemory(std::string("mem") + moduleName.str,
1898-
std::string("assign") + moduleName.str);
1893+
std::string("assign") + moduleName.str,
1894+
[](std::string globalName) { return globalName; });
18991895

19001896
// Actually invoke the `asmFunc` generated function, passing in all global
19011897
// values followed by all imports
@@ -1958,7 +1954,7 @@ void Wasm2JSGlue::emitPostES6() {
19581954
}
19591955
}
19601956

1961-
void Wasm2JSGlue::emitMemory(std::string buffer, std::string segmentWriter) {
1957+
void Wasm2JSGlue::emitMemory(std::string buffer, std::string segmentWriter, std::function<std::string (std::string)> accessGlobal) {
19621958
if (wasm.memory.segments.empty()) return;
19631959

19641960
auto expr = R"(
@@ -1982,10 +1978,22 @@ void Wasm2JSGlue::emitMemory(std::string buffer, std::string segmentWriter) {
19821978
out << "var " << segmentWriter
19831979
<< " = (" << expr << ")(" << buffer << ");\n";
19841980

1981+
auto globalOffset = [&](const Memory::Segment& segment) {
1982+
if (auto* c = segment.offset->template dynCast<Const>()) {;
1983+
return std::to_string(c->value.getInteger());
1984+
}
1985+
if (auto* get = segment.offset->template dynCast<GetGlobal>()) {
1986+
auto internalName = get->name;
1987+
auto importedName = wasm.getGlobal(internalName)->base;
1988+
return accessGlobal(asmangle(importedName.str));
1989+
}
1990+
Fatal() << "non-constant offsets aren't supported yet\n";
1991+
};
1992+
19851993
for (auto& seg : wasm.memory.segments) {
19861994
assert(!seg.isPassive && "passive segments not implemented yet");
19871995
out << segmentWriter << "("
1988-
<< constOffset(seg)
1996+
<< globalOffset(seg)
19891997
<< ", \""
19901998
<< base64Encode(seg.data)
19911999
<< "\");\n";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { memoryBase } from 'env';
2+
import { tableBase } from 'env';
3+
4+
function asmFunc(global, env, buffer) {
5+
"almost asm";
6+
var memory = env.memory;
7+
var HEAP8 = new global.Int8Array(buffer);
8+
var HEAP16 = new global.Int16Array(buffer);
9+
var HEAP32 = new global.Int32Array(buffer);
10+
var HEAPU8 = new global.Uint8Array(buffer);
11+
var HEAPU16 = new global.Uint16Array(buffer);
12+
var HEAPU32 = new global.Uint32Array(buffer);
13+
var HEAPF32 = new global.Float32Array(buffer);
14+
var HEAPF64 = new global.Float64Array(buffer);
15+
var Math_imul = global.Math.imul;
16+
var Math_fround = global.Math.fround;
17+
var Math_abs = global.Math.abs;
18+
var Math_clz32 = global.Math.clz32;
19+
var Math_min = global.Math.min;
20+
var Math_max = global.Math.max;
21+
var Math_floor = global.Math.floor;
22+
var Math_ceil = global.Math.ceil;
23+
var Math_sqrt = global.Math.sqrt;
24+
var abort = env.abort;
25+
var nan = global.NaN;
26+
var infinity = global.Infinity;
27+
var import$memoryBase = env.memoryBase | 0;
28+
var import$tableBase = env.tableBase | 0;
29+
function foo() {
30+
31+
}
32+
33+
function bar() {
34+
35+
}
36+
37+
function baz() {
38+
39+
}
40+
41+
var FUNCTION_TABLE = [];
42+
FUNCTION_TABLE[import$tableBase + 0] = foo;
43+
FUNCTION_TABLE[import$tableBase + 1] = bar;
44+
return {
45+
"baz": baz
46+
};
47+
}
48+
49+
var memasmFunc = new ArrayBuffer(16777216);
50+
var assignasmFunc = (
51+
function(mem) {
52+
var _mem = new Uint8Array(mem);
53+
return function(offset, s) {
54+
if (typeof Buffer === 'undefined') {
55+
var bytes = atob(s);
56+
for (var i = 0; i < bytes.length; i++)
57+
_mem[offset + i] = bytes.charCodeAt(i);
58+
} else {
59+
var bytes = Buffer.from(s, 'base64');
60+
for (var i = 0; i < bytes.length; i++)
61+
_mem[offset + i] = bytes[i];
62+
}
63+
}
64+
}
65+
)(memasmFunc);
66+
assignasmFunc(memoryBase, "ZHluYW1pYyBkYXRh");
67+
var retasmFunc = asmFunc({Math,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,NaN,Infinity}, {abort:function() { throw new Error('abort'); }},memasmFunc);
68+
export var baz = retasmFunc.baz;

test/wasm2js/dynamicLibrary.wast

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(module
2+
(type $FUNCSIG$vi (func (param i32)))
3+
(type $FUNCSIG$ii (func (param i32) (result i32)))
4+
5+
(import "env" "memory" (memory $import$memory 256 256))
6+
(import "env" "memoryBase" (global $import$memoryBase i32))
7+
(data (global.get $import$memoryBase) "dynamic data")
8+
9+
(table 10 10 funcref)
10+
(import "env" "tableBase" (global $import$tableBase i32))
11+
(elem (global.get $import$tableBase) $foo $bar)
12+
13+
(export "baz" (func $baz))
14+
(export "tab" (table 0))
15+
16+
(func $foo)
17+
(func $bar)
18+
(func $baz)
19+
)

0 commit comments

Comments
 (0)