Skip to content

Commit b99d668

Browse files
authored
wasm2js: use scratch memory properly (#2033)
This replaces all uses of __tempMemory__, the old scratch space location, with calls to function imports for scratch memory access. This lets us then implement those in a way that does not use the same heap as main memory. This avoids possible bugs with scratch memory overwriting something, or just in general that it has observable side effects, which can confuse fuzzing etc. The intrinsics are currently implemented in the glue. We could perhaps emit them inline instead (but that might limit asm.js optimizations, so I wanted to keep our options open for now - easy to change later). Also fixes some places where we used 0 as the scratch space address.
1 parent db14d04 commit b99d668

21 files changed

Lines changed: 2677 additions & 2241 deletions

scripts/fuzz_opt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
# parameters
2828

29-
NANS = False
29+
NANS = True
3030

3131
FUZZ_OPTS = [] # '--all-features' etc
3232

scripts/test/env.js

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

11-
export const __tempMemory__ = 0;
12-

src/abi/js.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define wasm_abi_abi_h
1919

2020
#include "wasm.h"
21+
#include "asmjs/shared-constants.h"
2122

2223
namespace wasm {
2324

@@ -36,6 +37,57 @@ inline std::string getLegalizationPass(LegalizationLevel level) {
3637
}
3738
}
3839

40+
namespace wasm2js {
41+
42+
extern cashew::IString SCRATCH_LOAD_I32,
43+
SCRATCH_STORE_I32,
44+
SCRATCH_LOAD_I64,
45+
SCRATCH_STORE_I64,
46+
SCRATCH_LOAD_F32,
47+
SCRATCH_STORE_F32,
48+
SCRATCH_LOAD_F64,
49+
SCRATCH_STORE_F64;
50+
51+
// The wasm2js scratch memory helpers let us read and write to scratch memory
52+
// for purposes of implementing things like reinterpret, etc.
53+
// The optional "specific" parameter is a specific function we want. If not
54+
// provided, we create them all.
55+
inline void ensureScratchMemoryHelpers(Module* wasm, cashew::IString specific = cashew::IString()) {
56+
auto ensureImport = [&](Name name, const std::vector<Type> params, Type result) {
57+
if (wasm->getFunctionOrNull(name)) return;
58+
if (specific.is() && name != specific) return;
59+
auto func = make_unique<Function>();
60+
func->name = name;
61+
func->params = params;
62+
func->result = result;
63+
func->module = ENV;
64+
func->base = name;
65+
wasm->addFunction(std::move(func));
66+
};
67+
68+
ensureImport(SCRATCH_LOAD_I32, { i32 }, i32);
69+
ensureImport(SCRATCH_STORE_I32, { i32, i32 }, none);
70+
ensureImport(SCRATCH_LOAD_I64, {}, i64);
71+
ensureImport(SCRATCH_STORE_I64, { i64 }, none);
72+
ensureImport(SCRATCH_LOAD_F32, {}, f32);
73+
ensureImport(SCRATCH_STORE_F32, { f32 }, none);
74+
ensureImport(SCRATCH_LOAD_F64, {}, f64);
75+
ensureImport(SCRATCH_STORE_F64, { f64 }, none);
76+
}
77+
78+
inline bool isScratchMemoryHelper(cashew::IString name) {
79+
return name == SCRATCH_LOAD_I32 ||
80+
name == SCRATCH_STORE_I32 ||
81+
name == SCRATCH_LOAD_I64 ||
82+
name == SCRATCH_STORE_I64 ||
83+
name == SCRATCH_LOAD_F32 ||
84+
name == SCRATCH_STORE_F32 ||
85+
name == SCRATCH_LOAD_F64 ||
86+
name == SCRATCH_STORE_F64;
87+
}
88+
89+
} // namespace wasm2js
90+
3991
} // namespace ABI
4092

4193
} // namespace wasm

src/asmjs/shared-constants.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,20 @@ cashew::IString GLOBAL("global"),
102102
WASM_I64_UDIV("__wasm_i64_udiv"),
103103
WASM_I64_SREM("__wasm_i64_srem"),
104104
WASM_I64_UREM("__wasm_i64_urem");
105+
106+
namespace ABI {
107+
namespace wasm2js {
108+
109+
cashew::IString SCRATCH_LOAD_I32("wasm2js_scratch_load_i32"),
110+
SCRATCH_STORE_I32("wasm2js_scratch_store_i32"),
111+
SCRATCH_LOAD_I64("wasm2js_scratch_load_i64"),
112+
SCRATCH_STORE_I64("wasm2js_scratch_store_i64"),
113+
SCRATCH_LOAD_F32("wasm2js_scratch_load_f32"),
114+
SCRATCH_STORE_F32("wasm2js_scratch_store_f32"),
115+
SCRATCH_LOAD_F64("wasm2js_scratch_load_f64"),
116+
SCRATCH_STORE_F64("wasm2js_scratch_store_f64");
117+
118+
} // namespace wasm2js
119+
} // namespace ABI
120+
105121
}

src/passes/I64ToI32Lowering.cpp

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "emscripten-optimizer/istring.h"
2828
#include "support/name.h"
2929
#include "wasm-builder.h"
30+
#include "abi/js.h"
3031
#include "ir/flat.h"
3132
#include "ir/iteration.h"
3233
#include "ir/memory-utils.h"
@@ -337,25 +338,30 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
337338
template<typename T>
338339
using BuilderFunc = std::function<T*(std::vector<Expression*>&, Type)>;
339340

341+
// Fixes up a call. If we performed fixups, returns the call; otherwise returns nullptr;
340342
template<typename T>
341-
void visitGenericCall(T* curr, BuilderFunc<T> callBuilder) {
343+
T* visitGenericCall(T* curr, BuilderFunc<T> callBuilder) {
344+
bool fixed = false;
342345
std::vector<Expression*> args;
343346
for (auto* e : curr->operands) {
344347
args.push_back(e);
345348
if (hasOutParam(e)) {
346349
TempVar argHighBits = fetchOutParam(e);
347350
args.push_back(builder->makeGetLocal(argHighBits, i32));
351+
fixed = true;
348352
}
349353
}
350354
if (curr->type != i64) {
351-
replaceCurrent(callBuilder(args, curr->type));
352-
return;
355+
auto* ret = callBuilder(args, curr->type);
356+
replaceCurrent(ret);
357+
return fixed ? ret : nullptr;
353358
}
354359
TempVar lowBits = getTemp();
355360
TempVar highBits = getTemp();
361+
auto* call = callBuilder(args, i32);
356362
SetLocal* doCall = builder->makeSetLocal(
357363
lowBits,
358-
callBuilder(args, i32)
364+
call
359365
);
360366
SetLocal* setHigh = builder->makeSetLocal(
361367
highBits,
@@ -365,14 +371,21 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
365371
Block* result = builder->blockify(doCall, setHigh, getLow);
366372
setOutParam(result, std::move(highBits));
367373
replaceCurrent(result);
374+
return call;
368375
}
369376
void visitCall(Call* curr) {
370-
visitGenericCall<Call>(
377+
auto* fixedCall = visitGenericCall<Call>(
371378
curr,
372379
[&](std::vector<Expression*>& args, Type ty) {
373380
return builder->makeCall(curr->target, args, ty);
374381
}
375382
);
383+
// If this was to an import, we need to call the legal version. This assumes
384+
// that legalize-js-interface has been run before.
385+
if (fixedCall && getModule()->getFunction(fixedCall->target)->imported()) {
386+
fixedCall->target = std::string("legalfunc$") + fixedCall->target.str;
387+
return;
388+
}
376389
}
377390

378391
void visitCallIndirect(CallIndirect* curr) {
@@ -635,51 +648,31 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
635648
// our f64 through memory at address 0
636649
TempVar highBits = getTemp();
637650
Block *result = builder->blockify(
638-
builder->makeStore(8, 0, 8, makeGetTempMemory(), curr->value, f64),
651+
builder->makeCall(ABI::wasm2js::SCRATCH_STORE_F64, { curr->value }, none),
639652
builder->makeSetLocal(
640653
highBits,
641-
builder->makeLoad(4, true, 4, 4, makeGetTempMemory(), i32)
654+
builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, { builder->makeConst(Literal(int32_t(1))) }, i32)
642655
),
643-
builder->makeLoad(4, true, 0, 4, makeGetTempMemory(), i32)
656+
builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, { builder->makeConst(Literal(int32_t(0))) }, i32)
644657
);
645658
setOutParam(result, std::move(highBits));
646659
replaceCurrent(result);
647660
MemoryUtils::ensureExists(getModule()->memory);
648-
ensureTempMemoryGlobal();
661+
ABI::wasm2js::ensureScratchMemoryHelpers(getModule());
649662
}
650663

651664
void lowerReinterpretInt64(Unary* curr) {
652665
// Assume that the wasm file assumes the address 0 is invalid and roundtrip
653666
// our i64 through memory at address 0
654667
TempVar highBits = fetchOutParam(curr->value);
655668
Block *result = builder->blockify(
656-
builder->makeStore(4, 0, 4, makeGetTempMemory(), curr->value, i32),
657-
builder->makeStore(4, 4, 4, makeGetTempMemory(), builder->makeGetLocal(highBits, i32), i32),
658-
builder->makeLoad(8, true, 0, 8, makeGetTempMemory(), f64)
669+
builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, { builder->makeConst(Literal(int32_t(0))), curr->value }, none),
670+
builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, { builder->makeConst(Literal(int32_t(1))), builder->makeGetLocal(highBits, i32) }, none),
671+
builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_F64, {}, f64)
659672
);
660673
replaceCurrent(result);
661674
MemoryUtils::ensureExists(getModule()->memory);
662-
ensureTempMemoryGlobal();
663-
}
664-
665-
Name tempMemory = "__tempMemory__";
666-
667-
void ensureTempMemoryGlobal() {
668-
// Ensure the existence of an imported global, __tempMemory__, which points to 8
669-
// bytes of scratch memory we can use for roundtrip purposes.
670-
if (!getModule()->getGlobalOrNull(tempMemory)) {
671-
auto global = make_unique<Global>();
672-
global->name = tempMemory;
673-
global->type = i32;
674-
global->mutable_ = false;
675-
global->module = ENV;
676-
global->base = tempMemory;
677-
getModule()->addGlobal(global.release());
678-
}
679-
}
680-
681-
Expression* makeGetTempMemory() {
682-
return builder->makeGetGlobal(tempMemory, i32);
675+
ABI::wasm2js::ensureScratchMemoryHelpers(getModule());
683676
}
684677

685678
void lowerTruncFloatToInt(Unary *curr) {

src/passes/RemoveNonJSOps.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "asmjs/shared-constants.h"
3434
#include "wasm-builder.h"
3535
#include "wasm-s-parser.h"
36+
#include "abi/js.h"
3637
#include "ir/memory-utils.h"
3738
#include "ir/module-utils.h"
3839
#include "ir/find_all.h"
@@ -50,6 +51,9 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> {
5051
Pass* create() override { return new RemoveNonJSOpsPass; }
5152

5253
void doWalkModule(Module* module) {
54+
// Intrinsics may use scratch memory, ensure it.
55+
ABI::wasm2js::ensureScratchMemoryHelpers(module);
56+
5357
// Discover all of the intrinsics that we need to inject, lowering all
5458
// operations to intrinsic calls while we're at it.
5559
if (!builder) builder = make_unique<Builder>(*module);

src/passes/wasm-intrinsics.wast

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
;;
88
;; LOCAL MODS done by hand afterwards:
99
;; * Remove hardcoded address 1024 (apparently a free memory location rustc
10-
;; thinks is ok to use?); add a global __tempMemory__ which is used for that
11-
;; purpose.
10+
;; thinks is ok to use?); add intrinsic functions, which load/store to
11+
;; special scratch space, wasm2js_scratch_load_i32 etc.
1212
;; * Fix function type of __wasm_ctz_i64, which was wrong somehow,
1313
;; i32, i32 => i32 instead of i64 => i64
1414
;;
@@ -22,7 +22,8 @@
2222
(type $4 (func (param i32 i32) (result i32)))
2323
(type $5 (func (param i64) (result i64)))
2424
(import "env" "memory" (memory $0 17))
25-
(import "env" "__tempMemory__" (global $__tempMemory__ i32))
25+
(import "env" "wasm2js_scratch_load_i64" (func $wasm2js_scratch_load_i64 (result i64)))
26+
(import "env" "wasm2js_scratch_store_i64" (func $wasm2js_scratch_store_i64 (param i64)))
2627
(export "__wasm_i64_sdiv" (func $__wasm_i64_sdiv))
2728
(export "__wasm_i64_udiv" (func $__wasm_i64_udiv))
2829
(export "__wasm_i64_srem" (func $__wasm_i64_srem))
@@ -136,9 +137,7 @@
136137
(local.get $var$1)
137138
)
138139
)
139-
(i64.load
140-
(global.get $__tempMemory__)
141-
)
140+
(call $wasm2js_scratch_load_i64)
142141
)
143142
;; lowering of the i64.mul instruction, return $var0 * $var$1
144143
(func $__wasm_i64_mul (; 4 ;) (type $0) (param $var$0 i64) (param $var$1 i64) (result i64)
@@ -579,8 +578,7 @@
579578
(i64.const 4294967296)
580579
)
581580
)
582-
(i64.store
583-
(global.get $__tempMemory__)
581+
(call $wasm2js_scratch_store_i64
584582
(i64.extend_i32_u
585583
(i32.sub
586584
(local.tee $var$2
@@ -641,8 +639,7 @@
641639
(local.get $var$3)
642640
)
643641
)
644-
(i64.store
645-
(global.get $__tempMemory__)
642+
(call $wasm2js_scratch_store_i64
646643
(i64.or
647644
(i64.shl
648645
(i64.extend_i32_u
@@ -722,8 +719,7 @@
722719
)
723720
(br $label$3)
724721
)
725-
(i64.store
726-
(global.get $__tempMemory__)
722+
(call $wasm2js_scratch_store_i64
727723
(i64.shl
728724
(i64.extend_i32_u
729725
(i32.sub
@@ -765,8 +761,7 @@
765761
)
766762
(br $label$2)
767763
)
768-
(i64.store
769-
(global.get $__tempMemory__)
764+
(call $wasm2js_scratch_store_i64
770765
(i64.extend_i32_u
771766
(i32.and
772767
(local.get $var$4)
@@ -897,8 +892,7 @@
897892
)
898893
)
899894
)
900-
(i64.store
901-
(global.get $__tempMemory__)
895+
(call $wasm2js_scratch_store_i64
902896
(local.get $var$5)
903897
)
904898
(return
@@ -911,8 +905,7 @@
911905
)
912906
)
913907
)
914-
(i64.store
915-
(global.get $__tempMemory__)
908+
(call $wasm2js_scratch_store_i64
916909
(local.get $var$0)
917910
)
918911
(local.set $var$0

0 commit comments

Comments
 (0)