2 * Copyright 2015 WebAssembly Community Group participants
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 // WebAssembly-to-asm.js translator. Uses the Emscripten optimizer
22 #ifndef wasm_wasm2asm_h
23 #define wasm_wasm2asm_h
28 #include "asmjs/shared-constants.h"
30 #include "wasm-builder.h"
31 #include "emscripten-optimizer/optimizer.h"
32 #include "mixed_arena.h"
33 #include "asm_v_wasm.h"
35 #include "passes/passes.h"
39 using namespace cashew
;
41 IString
ASM_FUNC("asmFunc"),
43 FUNCTION_TABLE("FUNCTION_TABLE"),
44 NO_RESULT("wasm2asm$noresult"), // no result at all
45 EXPRESSION_RESULT("wasm2asm$expresult"); // result in an expression, no temp var
47 // Appends extra to block, flattening out if extra is a block as well
48 void flattenAppend(Ref ast
, Ref extra
) {
50 if (ast
[0] == BLOCK
|| ast
[0] == TOPLEVEL
) index
= 1;
51 else if (ast
[0] == DEFUN
) index
= 3;
53 if (extra
->isArray() && extra
[0] == BLOCK
) {
54 for (size_t i
= 0; i
< extra
[1]->size(); i
++) {
55 ast
[index
]->push_back(extra
[1][i
]);
58 ast
[index
]->push_back(extra
);
63 // Wasm2AsmBuilder - converts a WebAssembly module into asm.js
65 // In general, asm.js => wasm is very straightforward, as can
66 // be seen in asm2wasm.h. Just a single pass, plus a little
67 // state bookkeeping (breakStack, etc.), and a few after-the
68 // fact corrections for imports, etc. However, wasm => asm.js
69 // is tricky because wasm has statements == expressions, or in
70 // other words, things like `break` and `if` can show up
71 // in places where asm.js can't handle them, like inside an
72 // a loop's condition check.
74 // We therefore need the ability to lower an expression into
75 // a block of statements, and we keep statementizing until we
76 // reach a context in which we can emit those statments. This
77 // requires that we create temp variables to store values
78 // that would otherwise flow directly into their targets if
79 // we were an expression (e.g. if a loop's condition check
80 // is a bunch of statements, we execute those statements,
81 // then use the computed value in the loop's condition;
82 // we might also be able to avoid an assign to a temp var
83 // at the end of those statements, and put just that
84 // value in the loop's condition).
86 // It is possible to do this in a single pass, if we just
87 // allocate temp vars freely. However, pathological cases
88 // can easily show bad behavior here, with many unnecessary
89 // temp vars. We could rely on optimization passes like
90 // Emscripten's eliminate/registerize pair, but we want
91 // wasm2asm to be fairly fast to run, as it might run on
94 // The approach taken here therefore performs 2 passes on
95 // each function. First, it finds which expression will need to
96 // be statementized. It also sees which labels can receive a break
97 // with a value. Given that information, in the second pass we can
98 // allocate // temp vars in an efficient manner, as we know when we
99 // need them and when their use is finished. They are allocated
100 // using an RAII class, so that they are automatically freed
101 // when the scope ends. This means that a node cannot allocate
102 // its own temp var; instead, the parent - which knows the
103 // child will return a value in a temp var - allocates it,
104 // and tells the child what temp var to emit to. The child
105 // can then pass forward that temp var to its children,
106 // optimizing away unnecessary forwarding.
109 class Wasm2AsmBuilder
{
110 MixedArena allocator
;
115 bool pedantic
= false;
118 Wasm2AsmBuilder(Flags f
) : flags(f
) {}
120 Ref
processWasm(Module
* wasm
);
121 Ref
processFunction(Function
* func
);
123 // The first pass on an expression: scan it to see whether it will
124 // need to be statementized, and note spooky returns of values at
125 // a distance (aka break with a value).
126 void scanFunctionBody(Expression
* curr
);
128 // The second pass on an expression: process it fully, generating
130 // @param result Whether the context we are in receives a value,
131 // and its type, or if not, then we can drop our return,
133 Ref
processFunctionBody(Function
* func
, IString result
);
135 Ref
processAsserts(Element
& e
, SExpressionWasmBuilder
& sexpBuilder
);
138 IString
getTemp(WasmType type
, Function
* func
) {
140 if (frees
[type
].size() > 0) {
141 ret
= frees
[type
].back();
142 frees
[type
].pop_back();
144 size_t index
= temps
[type
]++;
145 ret
= IString((std::string("wasm2asm_") + printWasmType(type
) + "$" +
146 std::to_string(index
)).c_str(), false);
148 if (func
->localIndices
.find(ret
) == func
->localIndices
.end()) {
149 Builder::addVar(func
, ret
, type
);
155 void freeTemp(WasmType type
, IString temp
) {
156 frees
[type
].push_back(temp
);
159 static IString
fromName(Name name
) {
160 // TODO: more clever name fixing, including checking we do not collide
161 const char* str
= name
.str
;
162 // check the various issues, and recurse so we check the others
163 if (strchr(str
, '-')) {
164 char* mod
= strdup(str
);
167 if (*mod
== '-') *mod
= '_';
170 IString result
= fromName(IString(str
, false));
174 if (isdigit(str
[0]) || strcmp(str
, "if") == 0) {
175 std::string prefixed
= "$$";
176 prefixed
+= name
.str
;
177 return fromName(IString(prefixed
.c_str(), false));
182 void setStatement(Expression
* curr
) {
183 willBeStatement
.insert(curr
);
186 bool isStatement(Expression
* curr
) {
187 return curr
&& willBeStatement
.find(curr
) != willBeStatement
.end();
190 size_t getTableSize() {
197 // How many temp vars we need
198 std::vector
<size_t> temps
; // type => num temps
199 // Which are currently free to use
200 std::vector
<std::vector
<IString
>> frees
; // type => list of free names
202 // Expressions that will be a statement.
203 std::set
<Expression
*> willBeStatement
;
205 // All our function tables have the same size TODO: optimize?
208 void addBasics(Ref ast
);
209 void addImport(Ref ast
, Import
* import
);
210 void addTables(Ref ast
, Module
* wasm
);
211 void addExports(Ref ast
, Module
* wasm
);
212 void addWasmCompatibilityFuncs(Module
* wasm
);
213 bool isAssertHandled(Element
& e
);
214 Ref
makeAssertReturnFunc(SExpressionWasmBuilder
& sexpBuilder
,
215 Builder
& wasmBuilder
,
216 Element
& e
, Name testFuncName
);
217 Ref
makeAssertTrapFunc(SExpressionWasmBuilder
& sexpBuilder
,
218 Builder
& wasmBuilder
,
219 Element
& e
, Name testFuncName
);
220 Wasm2AsmBuilder() = delete;
221 Wasm2AsmBuilder(const Wasm2AsmBuilder
&) = delete;
222 Wasm2AsmBuilder
&operator=(const Wasm2AsmBuilder
&) = delete;
225 static Function
* makeCtzFunc(MixedArena
& allocator
, UnaryOp op
) {
226 assert(op
== CtzInt32
|| op
== CtzInt64
);
227 Builder
b(allocator
);
228 // if eqz(x) then 32 else (32 - clz(x ^ (x - 1)))
229 bool is32Bit
= (op
== CtzInt32
);
230 Name funcName
= is32Bit
? Name(WASM_CTZ32
) : Name(WASM_CTZ64
);
231 BinaryOp subOp
= is32Bit
? SubInt32
: SubInt64
;
232 BinaryOp xorOp
= is32Bit
? XorInt32
: XorInt64
;
233 UnaryOp clzOp
= is32Bit
? ClzInt32
: ClzInt64
;
234 UnaryOp eqzOp
= is32Bit
? EqZInt32
: EqZInt64
;
235 WasmType argType
= is32Bit
? i32
: i64
;
236 Binary
* xorExp
= b
.makeBinary(
238 b
.makeGetLocal(0, i32
),
241 b
.makeGetLocal(0, i32
),
242 b
.makeConst(is32Bit
? Literal(int32_t(1)) : Literal(int64_t(1)))
245 Binary
* subExp
= b
.makeBinary(
247 b
.makeConst(is32Bit
? Literal(int32_t(32 - 1)) : Literal(int64_t(64 - 1))),
248 b
.makeUnary(clzOp
, xorExp
)
253 b
.makeGetLocal(0, i32
)
255 b
.makeConst(is32Bit
? Literal(int32_t(32)) : Literal(int64_t(64))),
258 return b
.makeFunction(
260 std::vector
<NameType
>{NameType("x", argType
)},
262 std::vector
<NameType
>{},
267 static Function
* makePopcntFunc(MixedArena
& allocator
, UnaryOp op
) {
268 assert(op
== PopcntInt32
|| op
== PopcntInt64
);
269 Builder
b(allocator
);
270 // popcnt implemented as:
271 // int c; for (c = 0; x != 0; c++) { x = x & (x - 1) }; return c
272 bool is32Bit
= (op
== PopcntInt32
);
273 Name funcName
= is32Bit
? Name(WASM_POPCNT32
) : Name(WASM_POPCNT64
);
274 BinaryOp addOp
= is32Bit
? AddInt32
: AddInt64
;
275 BinaryOp subOp
= is32Bit
? SubInt32
: SubInt64
;
276 BinaryOp andOp
= is32Bit
? AndInt32
: AndInt64
;
277 UnaryOp eqzOp
= is32Bit
? EqZInt32
: EqZInt64
;
278 WasmType argType
= is32Bit
? i32
: i64
;
281 Break
* brIf
= b
.makeBreak(
283 b
.makeGetLocal(1, i32
),
286 b
.makeGetLocal(0, argType
)
289 SetLocal
* update
= b
.makeSetLocal(
293 b
.makeGetLocal(0, argType
),
296 b
.makeGetLocal(0, argType
),
297 b
.makeConst(is32Bit
? Literal(int32_t(1)) : Literal(int64_t(1)))
301 SetLocal
* inc
= b
.makeSetLocal(
305 b
.makeGetLocal(1, argType
),
306 b
.makeConst(Literal(1))
309 Break
* cont
= b
.makeBreak(loopName
);
310 Loop
* loop
= b
.makeLoop(loopName
, b
.blockify(brIf
, update
, inc
, cont
));
311 Block
* loopBlock
= b
.blockifyWithName(loop
, blockName
);
312 SetLocal
* initCount
= b
.makeSetLocal(1, b
.makeConst(Literal(0)));
313 return b
.makeFunction(
315 std::vector
<NameType
>{NameType("x", argType
)},
317 std::vector
<NameType
>{NameType("count", argType
)},
318 b
.blockify(initCount
, loopBlock
)
322 Function
* makeRotFunc(MixedArena
& allocator
, BinaryOp op
) {
323 assert(op
== RotLInt32
|| op
== RotRInt32
||
324 op
== RotLInt64
|| op
== RotRInt64
);
325 Builder
b(allocator
);
327 // (((((~0) >>> k) & x) << k) | ((((~0) << (w - k)) & x) >>> (w - k)))
328 // where k is shift modulo w. reverse shifts for right rotate
329 bool is32Bit
= (op
== RotLInt32
|| op
== RotRInt32
);
330 bool isLRot
= (op
== RotLInt32
|| op
== RotLInt64
);
331 static Name names
[2][2] = {{Name(WASM_ROTR64
), Name(WASM_ROTR32
)},
332 {Name(WASM_ROTL64
), Name(WASM_ROTL32
)}};
333 static BinaryOp shifters
[2][2] = {{ShrUInt64
, ShrUInt32
},
334 {ShlInt64
, ShlInt32
}};
335 Name funcName
= names
[isLRot
][is32Bit
];
336 BinaryOp lshift
= shifters
[isLRot
][is32Bit
];
337 BinaryOp rshift
= shifters
[!isLRot
][is32Bit
];
338 BinaryOp orOp
= is32Bit
? OrInt32
: OrInt64
;
339 BinaryOp andOp
= is32Bit
? AndInt32
: AndInt64
;
340 BinaryOp subOp
= is32Bit
? SubInt32
: SubInt64
;
341 WasmType argType
= is32Bit
? i32
: i64
;
343 is32Bit
? Literal(int32_t(32 - 1)) : Literal(int64_t(64 - 1));
345 is32Bit
? Literal(int32_t(32)) : Literal(int64_t(64));
346 auto shiftVal
= [&]() {
349 b
.makeGetLocal(1, argType
),
350 b
.makeConst(widthMask
)
353 auto widthSub
= [&]() {
354 return b
.makeBinary(subOp
, b
.makeConst(width
), shiftVal());
356 auto fullMask
= [&]() {
357 return b
.makeConst(is32Bit
? Literal(~int32_t(0)) : Literal(~int64_t(0)));
359 Binary
* maskRShift
= b
.makeBinary(rshift
, fullMask(), shiftVal());
360 Binary
* lowMask
= b
.makeBinary(andOp
, maskRShift
, b
.makeGetLocal(0, argType
));
361 Binary
* lowShift
= b
.makeBinary(lshift
, lowMask
, shiftVal());
362 Binary
* maskLShift
= b
.makeBinary(lshift
, fullMask(), widthSub());
364 b
.makeBinary(andOp
, maskLShift
, b
.makeGetLocal(0, argType
));
365 Binary
* highShift
= b
.makeBinary(rshift
, highMask
, widthSub());
366 Binary
* body
= b
.makeBinary(orOp
, lowShift
, highShift
);
367 return b
.makeFunction(
369 std::vector
<NameType
>{NameType("x", argType
),
370 NameType("k", argType
)},
372 std::vector
<NameType
>{},
377 void Wasm2AsmBuilder::addWasmCompatibilityFuncs(Module
* wasm
) {
378 wasm
->addFunction(makeCtzFunc(wasm
->allocator
, CtzInt32
));
379 wasm
->addFunction(makePopcntFunc(wasm
->allocator
, PopcntInt32
));
380 wasm
->addFunction(makeRotFunc(wasm
->allocator
, RotLInt32
));
381 wasm
->addFunction(makeRotFunc(wasm
->allocator
, RotRInt32
));
384 Ref
Wasm2AsmBuilder::processWasm(Module
* wasm
) {
385 addWasmCompatibilityFuncs(wasm
);
386 PassRunner
runner(wasm
);
387 runner
.add
<AutoDrop
>();
388 runner
.add("i64-to-i32-lowering");
389 runner
.add("flatten");
390 runner
.add("simplify-locals-notee-nostructure");
391 runner
.add("vacuum");
392 runner
.setDebug(flags
.debug
);
394 Ref ret
= ValueBuilder::makeToplevel();
395 Ref asmFunc
= ValueBuilder::makeFunction(ASM_FUNC
);
396 ret
[1]->push_back(asmFunc
);
397 ValueBuilder::appendArgumentToFunction(asmFunc
, GLOBAL
);
398 ValueBuilder::appendArgumentToFunction(asmFunc
, ENV
);
399 ValueBuilder::appendArgumentToFunction(asmFunc
, BUFFER
);
400 asmFunc
[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeString(USE_ASM
)));
402 addBasics(asmFunc
[3]);
403 for (auto& import
: wasm
->imports
) {
404 addImport(asmFunc
[3], import
.get());
406 // figure out the table size
407 tableSize
= std::accumulate(wasm
->table
.segments
.begin(),
408 wasm
->table
.segments
.end(),
409 0, [&](size_t size
, Table::Segment seg
) -> size_t {
410 return size
+ seg
.data
.size();
413 while (pow2ed
< tableSize
) {
418 for (auto& func
: wasm
->functions
) {
419 asmFunc
[3]->push_back(processFunction(func
.get()));
421 addTables(asmFunc
[3], wasm
);
423 addExports(asmFunc
[3], wasm
);
427 void Wasm2AsmBuilder::addBasics(Ref ast
) {
428 // heaps, var HEAP8 = new global.Int8Array(buffer); etc
429 auto addHeap
= [&](IString name
, IString view
) {
430 Ref theVar
= ValueBuilder::makeVar();
431 ast
->push_back(theVar
);
432 ValueBuilder::appendToVar(theVar
,
434 ValueBuilder::makeNew(
435 ValueBuilder::makeCall(
436 ValueBuilder::makeDot(
437 ValueBuilder::makeName(GLOBAL
),
440 ValueBuilder::makeName(BUFFER
)
445 addHeap(HEAP8
, INT8ARRAY
);
446 addHeap(HEAP16
, INT16ARRAY
);
447 addHeap(HEAP32
, INT32ARRAY
);
448 addHeap(HEAPU8
, UINT8ARRAY
);
449 addHeap(HEAPU16
, UINT16ARRAY
);
450 addHeap(HEAPU32
, UINT32ARRAY
);
451 addHeap(HEAPF32
, FLOAT32ARRAY
);
452 addHeap(HEAPF64
, FLOAT64ARRAY
);
453 // core asm.js imports
454 auto addMath
= [&](IString name
, IString base
) {
455 Ref theVar
= ValueBuilder::makeVar();
456 ast
->push_back(theVar
);
457 ValueBuilder::appendToVar(theVar
,
459 ValueBuilder::makeDot(
460 ValueBuilder::makeDot(
461 ValueBuilder::makeName(GLOBAL
),
468 addMath(MATH_IMUL
, IMUL
);
469 addMath(MATH_FROUND
, FROUND
);
470 addMath(MATH_ABS
, ABS
);
471 addMath(MATH_CLZ32
, CLZ32
);
474 void Wasm2AsmBuilder::addImport(Ref ast
, Import
* import
) {
475 Ref theVar
= ValueBuilder::makeVar();
476 ast
->push_back(theVar
);
477 Ref module
= ValueBuilder::makeName(ENV
); // TODO: handle nested module imports
478 ValueBuilder::appendToVar(theVar
,
479 fromName(import
->name
),
480 ValueBuilder::makeDot(
482 fromName(import
->base
)
487 void Wasm2AsmBuilder::addTables(Ref ast
, Module
* wasm
) {
488 std::map
<std::string
, std::vector
<IString
>> tables
; // asm.js tables, sig => contents of table
489 for (Table::Segment
& seg
: wasm
->table
.segments
) {
490 for (size_t i
= 0; i
< seg
.data
.size(); i
++) {
491 Name name
= seg
.data
[i
];
492 auto func
= wasm
->getFunction(name
);
493 std::string sig
= getSig(func
);
494 auto& table
= tables
[sig
];
495 if (table
.size() == 0) {
496 // fill it with the first of its type seen. we have to fill with something; and for asm2wasm output, the first is the null anyhow
497 table
.resize(tableSize
);
498 for (size_t j
= 0; j
< tableSize
; j
++) {
499 table
[j
] = fromName(name
);
502 table
[i
] = fromName(name
);
506 for (auto& pair
: tables
) {
507 auto& sig
= pair
.first
;
508 auto& table
= pair
.second
;
509 std::string stable
= std::string("FUNCTION_TABLE_") + sig
;
510 IString asmName
= IString(stable
.c_str(), false);
512 Ref theVar
= ValueBuilder::makeVar();
513 ast
->push_back(theVar
);
514 Ref theArray
= ValueBuilder::makeArray();
515 ValueBuilder::appendToVar(theVar
, asmName
, theArray
);
516 for (auto& name
: table
) {
517 ValueBuilder::appendToArray(theArray
, ValueBuilder::makeName(name
));
522 void Wasm2AsmBuilder::addExports(Ref ast
, Module
* wasm
) {
523 Ref exports
= ValueBuilder::makeObject();
524 for (auto& export_
: wasm
->exports
) {
525 ValueBuilder::appendToObject(
527 fromName(export_
->name
),
528 ValueBuilder::makeName(fromName(export_
->value
))
531 ast
->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports
)));
534 Ref
Wasm2AsmBuilder::processFunction(Function
* func
) {
537 std::cerr
<< "processFunction " << (fns
++) << " " << func
->name
540 Ref ret
= ValueBuilder::makeFunction(fromName(func
->name
));
542 frees
.resize(std::max(i32
, std::max(f32
, f64
)) + 1);
544 temps
.resize(std::max(i32
, std::max(f32
, f64
)) + 1);
545 temps
[i32
] = temps
[f32
] = temps
[f64
] = 0;
547 for (Index i
= 0; i
< func
->getNumParams(); i
++) {
548 IString name
= fromName(func
->getLocalNameOrGeneric(i
));
549 ValueBuilder::appendArgumentToFunction(ret
, name
);
551 ValueBuilder::makeStatement(
552 ValueBuilder::makeBinary(
553 ValueBuilder::makeName(name
), SET
,
555 ValueBuilder::makeName(name
),
556 wasmToAsmType(func
->getLocalType(i
))
562 Ref theVar
= ValueBuilder::makeVar();
563 size_t theVarIndex
= ret
[3]->size();
564 ret
[3]->push_back(theVar
);
566 auto appendFinalReturn
= [&] (Ref retVal
) {
569 ValueBuilder::makeReturn(
570 makeAsmCoercion(retVal
, wasmToAsmType(func
->result
))
574 scanFunctionBody(func
->body
);
575 bool isBodyBlock
= (func
->body
->_id
== Expression::BlockId
);
576 ExpressionList
* stats
= isBodyBlock
?
577 &static_cast<Block
*>(func
->body
)->list
: nullptr;
579 (isBodyBlock
&& ((*stats
)[stats
->size()-1]->_id
== Expression::ReturnId
));
581 // return already taken care of
582 flattenAppend(ret
, processFunctionBody(func
, NO_RESULT
));
583 } else if (isStatement(func
->body
)) {
584 // store result in variable then return it
586 func
->result
!= none
? getTemp(func
->result
, func
) : NO_RESULT
;
587 flattenAppend(ret
, processFunctionBody(func
, result
));
588 if (func
->result
!= none
) {
589 appendFinalReturn(ValueBuilder::makeName(result
));
590 freeTemp(func
->result
, result
);
592 } else if (func
->result
!= none
) {
593 // whole thing is an expression, just return body
594 appendFinalReturn(processFunctionBody(func
, EXPRESSION_RESULT
));
596 // func has no return
597 flattenAppend(ret
, processFunctionBody(func
, NO_RESULT
));
599 // vars, including new temp vars
600 for (Index i
= func
->getVarIndexBase(); i
< func
->getNumLocals(); i
++) {
601 ValueBuilder::appendToVar(
603 fromName(func
->getLocalNameOrGeneric(i
)),
604 makeAsmCoercedZero(wasmToAsmType(func
->getLocalType(i
)))
607 if (theVar
[1]->size() == 0) {
608 ret
[3]->splice(theVarIndex
, 1);
611 assert(frees
[i32
].size() == temps
[i32
]); // all temp vars should be free at the end
612 assert(frees
[f32
].size() == temps
[f32
]); // all temp vars should be free at the end
613 assert(frees
[f64
].size() == temps
[f64
]); // all temp vars should be free at the end
615 willBeStatement
.clear();
619 void Wasm2AsmBuilder::scanFunctionBody(Expression
* curr
) {
620 struct ExpressionScanner
: public PostWalker
<ExpressionScanner
> {
621 Wasm2AsmBuilder
* parent
;
623 ExpressionScanner(Wasm2AsmBuilder
* parent
) : parent(parent
) {}
627 void visitBlock(Block
* curr
) {
628 parent
->setStatement(curr
);
630 void visitIf(If
* curr
) {
631 parent
->setStatement(curr
);
633 void visitLoop(Loop
* curr
) {
634 parent
->setStatement(curr
);
636 void visitBreak(Break
* curr
) {
637 parent
->setStatement(curr
);
639 void visitSwitch(Switch
* curr
) {
640 parent
->setStatement(curr
);
642 void visitCall(Call
* curr
) {
643 for (auto item
: curr
->operands
) {
644 if (parent
->isStatement(item
)) {
645 parent
->setStatement(curr
);
650 void visitCallImport(CallImport
* curr
) {
651 for (auto item
: curr
->operands
) {
652 if (parent
->isStatement(item
)) {
653 parent
->setStatement(curr
);
658 void visitCallIndirect(CallIndirect
* curr
) {
659 if (parent
->isStatement(curr
->target
)) {
660 parent
->setStatement(curr
);
663 for (auto item
: curr
->operands
) {
664 if (parent
->isStatement(item
)) {
665 parent
->setStatement(curr
);
670 void visitSetLocal(SetLocal
* curr
) {
671 if (parent
->isStatement(curr
->value
)) {
672 parent
->setStatement(curr
);
675 void visitLoad(Load
* curr
) {
676 if (parent
->isStatement(curr
->ptr
)) {
677 parent
->setStatement(curr
);
680 void visitStore(Store
* curr
) {
681 if (parent
->isStatement(curr
->ptr
) || parent
->isStatement(curr
->value
)) {
682 parent
->setStatement(curr
);
685 void visitUnary(Unary
* curr
) {
686 if (parent
->isStatement(curr
->value
)) {
687 parent
->setStatement(curr
);
690 void visitBinary(Binary
* curr
) {
691 if (parent
->isStatement(curr
->left
) || parent
->isStatement(curr
->right
)) {
692 parent
->setStatement(curr
);
695 void visitSelect(Select
* curr
) {
696 if (parent
->isStatement(curr
->ifTrue
) || parent
->isStatement(curr
->ifFalse
) || parent
->isStatement(curr
->condition
)) {
697 parent
->setStatement(curr
);
700 void visitReturn(Return
* curr
) {
701 parent
->setStatement(curr
);
703 void visitHost(Host
* curr
) {
704 for (auto item
: curr
->operands
) {
705 if (parent
->isStatement(item
)) {
706 parent
->setStatement(curr
);
712 ExpressionScanner(this).walk(curr
);
715 Ref
Wasm2AsmBuilder::processFunctionBody(Function
* func
, IString result
) {
716 struct ExpressionProcessor
: public Visitor
<ExpressionProcessor
, Ref
> {
717 Wasm2AsmBuilder
* parent
;
720 MixedArena allocator
;
721 ExpressionProcessor(Wasm2AsmBuilder
* parent
, Function
* func
) : parent(parent
), func(func
) {}
723 // A scoped temporary variable.
725 Wasm2AsmBuilder
* parent
;
729 // @param possible if provided, this is a variable we can use as our temp. it has already been
730 // allocated in a higher scope, and we can just assign to it as our result is
731 // going there anyhow.
732 ScopedTemp(WasmType type
, Wasm2AsmBuilder
* parent
, Function
* func
,
733 IString possible
= NO_RESULT
) : parent(parent
), type(type
) {
734 assert(possible
!= EXPRESSION_RESULT
);
735 if (possible
== NO_RESULT
) {
736 temp
= parent
->getTemp(type
, func
);
745 parent
->freeTemp(type
, temp
);
753 return ValueBuilder::makeName(temp
);
757 Ref
visit(Expression
* curr
, IString nextResult
) {
758 IString old
= result
;
760 Ref ret
= Visitor::visit(curr
);
761 result
= old
; // keep it consistent for the rest of this frame, which may call visit on multiple children
765 Ref
visit(Expression
* curr
, ScopedTemp
& temp
) {
766 return visit(curr
, temp
.temp
);
769 // this result is for an asm expression slot, but it might be a statement
770 Ref
visitForExpression(Expression
* curr
, WasmType type
, IString
& tempName
) {
771 if (isStatement(curr
)) {
772 ScopedTemp
temp(type
, parent
, func
);
773 tempName
= temp
.temp
;
774 return visit(curr
, temp
);
776 return visit(curr
, EXPRESSION_RESULT
);
780 Ref
visitAndAssign(Expression
* curr
, IString result
) {
781 Ref ret
= visit(curr
, result
);
782 // if it's not already a statement, then it's an expression, and we need to assign it
783 // (if it is a statement, it already assigns to the result var)
784 if (!isStatement(curr
) && result
!= NO_RESULT
) {
785 ret
= ValueBuilder::makeStatement(
786 ValueBuilder::makeBinary(ValueBuilder::makeName(result
), SET
, ret
));
791 Ref
visitAndAssign(Expression
* curr
, ScopedTemp
& temp
) {
792 return visitAndAssign(curr
, temp
.getName());
795 bool isStatement(Expression
* curr
) {
796 return parent
->isStatement(curr
);
799 // Expressions with control flow turn into a block, which we must
800 // then handle, even if we are an expression.
801 bool isBlock(Ref ast
) {
802 return !!ast
&& ast
->isArray() && ast
[0] == BLOCK
;
805 Ref
blockify(Ref ast
) {
806 if (isBlock(ast
)) return ast
;
807 Ref ret
= ValueBuilder::makeBlock();
808 ret
[1]->push_back(ValueBuilder::makeStatement(ast
));
812 // For spooky return-at-a-distance/break-with-result, this tells us
813 // what the result var is for a specific label.
814 std::map
<Name
, IString
> breakResults
;
816 // Breaks to the top of a loop should be emitted as continues, to that loop's main label
817 std::unordered_set
<Name
> continueLabels
;
819 IString
fromName(Name name
) {
820 return parent
->fromName(name
);
825 Ref
visitBlock(Block
* curr
) {
826 breakResults
[curr
->name
] = result
;
827 Ref ret
= ValueBuilder::makeBlock();
828 size_t size
= curr
->list
.size();
829 auto noResults
= result
== NO_RESULT
? size
: size
-1;
830 for (size_t i
= 0; i
< noResults
; i
++) {
831 flattenAppend(ret
, ValueBuilder::makeStatement(visit(curr
->list
[i
], NO_RESULT
)));
833 if (result
!= NO_RESULT
) {
834 flattenAppend(ret
, visitAndAssign(curr
->list
[size
-1], result
));
836 if (curr
->name
.is()) {
837 ret
= ValueBuilder::makeLabel(fromName(curr
->name
), ret
);
842 Ref
visitIf(If
* curr
) {
844 Ref condition
= visitForExpression(curr
->condition
, i32
, temp
);
845 Ref ifTrue
= ValueBuilder::makeStatement(visitAndAssign(curr
->ifTrue
, result
));
848 ifFalse
= ValueBuilder::makeStatement(visitAndAssign(curr
->ifFalse
, result
));
851 return ValueBuilder::makeIf(condition
, ifTrue
, ifFalse
); // simple if
853 condition
= blockify(condition
);
854 // just add an if to the block
855 condition
[1]->push_back(ValueBuilder::makeIf(ValueBuilder::makeName(temp
), ifTrue
, ifFalse
));
859 Ref
visitLoop(Loop
* curr
) {
860 Name asmLabel
= curr
->name
;
861 continueLabels
.insert(asmLabel
);
862 Ref body
= blockify(visit(curr
->body
, result
));
863 flattenAppend(body
, ValueBuilder::makeBreak(fromName(asmLabel
)));
864 Ref ret
= ValueBuilder::makeDo(body
, ValueBuilder::makeInt(1));
865 return ValueBuilder::makeLabel(fromName(asmLabel
), ret
);
868 Ref
visitBreak(Break
* curr
) {
869 if (curr
->condition
) {
870 // we need an equivalent to an if here, so use that code
871 Break fakeBreak
= *curr
;
872 fakeBreak
.condition
= nullptr;
873 If
fakeIf(allocator
);
874 fakeIf
.condition
= curr
->condition
;
875 fakeIf
.ifTrue
= &fakeBreak
;
876 return visit(&fakeIf
, result
);
879 auto iter
= continueLabels
.find(curr
->name
);
880 if (iter
== continueLabels
.end()) {
881 theBreak
= ValueBuilder::makeBreak(fromName(curr
->name
));
883 theBreak
= ValueBuilder::makeContinue(fromName(curr
->name
));
885 if (!curr
->value
) return theBreak
;
886 // generate the value, including assigning to the result, and then do the break
887 Ref ret
= visitAndAssign(curr
->value
, breakResults
[curr
->name
]);
889 ret
[1]->push_back(theBreak
);
893 Expression
* defaultBody
= nullptr; // default must be last in asm.js
895 Ref
visitSwitch(Switch
* curr
) {
896 assert(!curr
->value
);
897 Ref ret
= ValueBuilder::makeBlock();
899 if (isStatement(curr
->condition
)) {
900 ScopedTemp
temp(i32
, parent
, func
);
901 flattenAppend(ret
[2], visit(curr
->condition
, temp
));
902 condition
= temp
.getAstName();
904 condition
= visit(curr
->condition
, EXPRESSION_RESULT
);
907 ValueBuilder::makeSwitch(makeAsmCoercion(condition
, ASM_INT
));
908 ret
[1]->push_back(theSwitch
);
909 for (size_t i
= 0; i
< curr
->targets
.size(); i
++) {
910 ValueBuilder::appendCaseToSwitch(theSwitch
, ValueBuilder::makeNum(i
));
911 ValueBuilder::appendCodeToSwitch(theSwitch
, blockify(ValueBuilder::makeBreak(fromName(curr
->targets
[i
]))), false);
913 ValueBuilder::appendDefaultToSwitch(theSwitch
);
914 ValueBuilder::appendCodeToSwitch(theSwitch
, blockify(ValueBuilder::makeBreak(fromName(curr
->default_
))), false);
918 Ref
makeStatementizedCall(ExpressionList
& operands
, Ref ret
, Ref theCall
, IString result
, WasmType type
) {
919 std::vector
<ScopedTemp
*> temps
; // TODO: utility class, with destructor?
920 for (auto& operand
: operands
) {
921 temps
.push_back(new ScopedTemp(operand
->type
, parent
, func
));
922 IString temp
= temps
.back()->temp
;
923 flattenAppend(ret
, visitAndAssign(operand
, temp
));
924 theCall
[2]->push_back(makeAsmCoercion(ValueBuilder::makeName(temp
), wasmToAsmType(operand
->type
)));
926 theCall
= makeAsmCoercion(theCall
, wasmToAsmType(type
));
927 if (result
!= NO_RESULT
) {
928 theCall
= ValueBuilder::makeStatement(
929 ValueBuilder::makeBinary(
930 ValueBuilder::makeName(result
), SET
, theCall
));
932 flattenAppend(ret
, theCall
);
933 for (auto temp
: temps
) {
939 Ref
visitGenericCall(Expression
* curr
, Name target
,
940 ExpressionList
& operands
) {
941 Ref theCall
= ValueBuilder::makeCall(fromName(target
));
942 if (!isStatement(curr
)) {
943 // none of our operands is a statement; go right ahead and create a
945 for (auto operand
: operands
) {
946 theCall
[2]->push_back(
947 makeAsmCoercion(visit(operand
, EXPRESSION_RESULT
),
948 wasmToAsmType(operand
->type
)));
950 return makeAsmCoercion(theCall
, wasmToAsmType(curr
->type
));
952 // we must statementize them all
953 return makeStatementizedCall(operands
, ValueBuilder::makeBlock(), theCall
,
957 Ref
visitCall(Call
* curr
) {
958 return visitGenericCall(curr
, curr
->target
, curr
->operands
);
961 Ref
visitCallImport(CallImport
* curr
) {
962 return visitGenericCall(curr
, curr
->target
, curr
->operands
);
965 Ref
visitCallIndirect(CallIndirect
* curr
) {
966 std::string stable
= std::string("FUNCTION_TABLE_") + curr
->fullType
.c_str();
967 IString table
= IString(stable
.c_str(), false);
968 auto makeTableCall
= [&](Ref target
) {
969 return ValueBuilder::makeCall(ValueBuilder::makeSub(
970 ValueBuilder::makeName(table
),
971 ValueBuilder::makeBinary(target
, AND
, ValueBuilder::makeInt(parent
->getTableSize()-1))
974 if (!isStatement(curr
)) {
975 // none of our operands is a statement; go right ahead and create a simple expression
976 Ref theCall
= makeTableCall(visit(curr
->target
, EXPRESSION_RESULT
));
977 for (auto operand
: curr
->operands
) {
978 theCall
[2]->push_back(makeAsmCoercion(visit(operand
, EXPRESSION_RESULT
), wasmToAsmType(operand
->type
)));
980 return makeAsmCoercion(theCall
, wasmToAsmType(curr
->type
));
982 // we must statementize them all
983 Ref ret
= ValueBuilder::makeBlock();
984 ScopedTemp
temp(i32
, parent
, func
);
985 flattenAppend(ret
, visit(curr
->target
, temp
));
986 Ref theCall
= makeTableCall(temp
.getAstName());
987 return makeStatementizedCall(curr
->operands
, ret
, theCall
, result
, curr
->type
);
990 Ref
makeSetVar(Expression
* curr
, Expression
* value
, Name name
) {
991 if (!isStatement(curr
)) {
992 return ValueBuilder::makeBinary(
993 ValueBuilder::makeName(fromName(name
)), SET
,
994 visit(value
, EXPRESSION_RESULT
)
997 // if result was provided, our child can just assign there.
998 // Otherwise, allocate a temp for it to assign to.
999 ScopedTemp
temp(value
->type
, parent
, func
, result
);
1000 Ref ret
= blockify(visit(value
, temp
));
1001 // the output was assigned to result, so we can just assign it to our target
1003 ValueBuilder::makeStatement(
1004 ValueBuilder::makeBinary(
1005 ValueBuilder::makeName(fromName(name
)), SET
,
1013 Ref
visitGetLocal(GetLocal
* curr
) {
1014 return ValueBuilder::makeName(
1015 fromName(func
->getLocalNameOrGeneric(curr
->index
))
1019 Ref
visitSetLocal(SetLocal
* curr
) {
1020 return makeSetVar(curr
, curr
->value
, func
->getLocalNameOrGeneric(curr
->index
));
1023 Ref
visitGetGlobal(GetGlobal
* curr
) {
1024 return ValueBuilder::makeName(fromName(curr
->name
));
1027 Ref
visitSetGlobal(SetGlobal
* curr
) {
1028 return makeSetVar(curr
, curr
->value
, curr
->name
);
1031 Ref
visitLoad(Load
* curr
) {
1032 if (isStatement(curr
)) {
1033 ScopedTemp
temp(i32
, parent
, func
);
1034 GetLocal
fakeLocal(allocator
);
1035 fakeLocal
.index
= func
->getLocalIndex(temp
.getName());
1036 Load fakeLoad
= *curr
;
1037 fakeLoad
.ptr
= &fakeLocal
;
1038 Ref ret
= blockify(visitAndAssign(curr
->ptr
, temp
));
1039 flattenAppend(ret
, visitAndAssign(&fakeLoad
, result
));
1042 if (curr
->align
!= 0 && curr
->align
< curr
->bytes
) {
1043 // set the pointer to a local
1044 ScopedTemp
temp(i32
, parent
, func
);
1045 SetLocal
set(allocator
);
1046 set
.index
= func
->getLocalIndex(temp
.getName());
1047 set
.value
= curr
->ptr
;
1048 Ref ptrSet
= visit(&set
, NO_RESULT
);
1049 GetLocal
get(allocator
);
1050 get
.index
= func
->getLocalIndex(temp
.getName());
1054 load
.bytes
= 1; // do the worst
1056 switch (curr
->type
) {
1058 rest
= makeAsmCoercion(visit(&load
, EXPRESSION_RESULT
), ASM_INT
);
1059 for (size_t i
= 1; i
< curr
->bytes
; i
++) {
1061 Ref add
= makeAsmCoercion(visit(&load
, EXPRESSION_RESULT
), ASM_INT
);
1062 add
= ValueBuilder::makeBinary(add
, LSHIFT
, ValueBuilder::makeNum(8*i
));
1063 rest
= ValueBuilder::makeBinary(rest
, OR
, add
);
1068 std::cerr
<< "Unhandled type in load: " << curr
->type
<< std::endl
;
1072 return ValueBuilder::makeSeq(ptrSet
, rest
);
1075 Ref ptr
= visit(curr
->ptr
, EXPRESSION_RESULT
);
1077 ptr
= makeAsmCoercion(
1078 ValueBuilder::makeBinary(ptr
, PLUS
, ValueBuilder::makeNum(curr
->offset
)),
1082 switch (curr
->type
) {
1084 switch (curr
->bytes
) {
1086 ret
= ValueBuilder::makeSub(
1087 ValueBuilder::makeName(curr
->signed_
? HEAP8
: HEAPU8
),
1088 ValueBuilder::makePtrShift(ptr
, 0));
1091 ret
= ValueBuilder::makeSub(
1092 ValueBuilder::makeName(curr
->signed_
? HEAP16
: HEAPU16
),
1093 ValueBuilder::makePtrShift(ptr
, 1));
1096 ret
= ValueBuilder::makeSub(
1097 ValueBuilder::makeName(curr
->signed_
? HEAP32
: HEAPU32
),
1098 ValueBuilder::makePtrShift(ptr
, 2));
1101 std::cerr
<< "Unhandled number of bytes in i32 load: "
1102 << curr
->bytes
<< std::endl
;
1109 ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32
),
1110 ValueBuilder::makePtrShift(ptr
, 2));
1113 ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64
),
1114 ValueBuilder::makePtrShift(ptr
, 3));
1117 std::cerr
<< "Unhandled type in load: " << curr
->type
<< std::endl
;
1121 return makeAsmCoercion(ret
, wasmToAsmType(curr
->type
));
1124 Ref
visitStore(Store
* curr
) {
1125 if (isStatement(curr
)) {
1126 ScopedTemp
tempPtr(i32
, parent
, func
);
1127 ScopedTemp
tempValue(curr
->valueType
, parent
, func
);
1128 GetLocal
fakeLocalPtr(allocator
);
1129 fakeLocalPtr
.index
= func
->getLocalIndex(tempPtr
.getName());
1130 GetLocal
fakeLocalValue(allocator
);
1131 fakeLocalValue
.index
= func
->getLocalIndex(tempValue
.getName());
1132 Store fakeStore
= *curr
;
1133 fakeStore
.ptr
= &fakeLocalPtr
;
1134 fakeStore
.value
= &fakeLocalValue
;
1135 Ref ret
= blockify(visitAndAssign(curr
->ptr
, tempPtr
));
1136 flattenAppend(ret
, visitAndAssign(curr
->value
, tempValue
));
1137 flattenAppend(ret
, visitAndAssign(&fakeStore
, result
));
1140 if (curr
->align
!= 0 && curr
->align
< curr
->bytes
) {
1141 // set the pointer to a local
1142 ScopedTemp
temp(i32
, parent
, func
);
1143 SetLocal
set(allocator
);
1144 set
.index
= func
->getLocalIndex(temp
.getName());
1145 set
.value
= curr
->ptr
;
1146 Ref ptrSet
= visit(&set
, NO_RESULT
);
1147 GetLocal
get(allocator
);
1148 get
.index
= func
->getLocalIndex(temp
.getName());
1149 // set the value to a local
1150 ScopedTemp
tempValue(curr
->value
->type
, parent
, func
);
1151 SetLocal
setValue(allocator
);
1152 setValue
.index
= func
->getLocalIndex(tempValue
.getName());
1153 setValue
.value
= curr
->value
;
1154 Ref valueSet
= visit(&setValue
, NO_RESULT
);
1155 GetLocal
getValue(allocator
);
1156 getValue
.index
= func
->getLocalIndex(tempValue
.getName());
1158 Store store
= *curr
;
1160 store
.bytes
= 1; // do the worst
1162 switch (curr
->valueType
) {
1164 Const
_255(allocator
);
1165 _255
.value
= Literal(int32_t(255));
1167 for (size_t i
= 0; i
< curr
->bytes
; i
++) {
1168 Const
shift(allocator
);
1169 shift
.value
= Literal(int32_t(8*i
));
1171 Binary
shifted(allocator
);
1172 shifted
.op
= ShrUInt32
;
1173 shifted
.left
= &getValue
;
1174 shifted
.right
= &shift
;
1176 Binary
anded(allocator
);
1177 anded
.op
= AndInt32
;
1178 anded
.left
= i
> 0 ? static_cast<Expression
*>(&shifted
) : static_cast<Expression
*>(&getValue
);
1179 anded
.right
= &_255
;
1181 store
.value
= &anded
;
1182 Ref part
= visit(&store
, NO_RESULT
);
1186 rest
= ValueBuilder::makeSeq(rest
, part
);
1193 std::cerr
<< "Unhandled type in store: " << curr
->valueType
1198 return ValueBuilder::makeSeq(ValueBuilder::makeSeq(ptrSet
, valueSet
), rest
);
1201 Ref ptr
= visit(curr
->ptr
, EXPRESSION_RESULT
);
1203 ptr
= makeAsmCoercion(ValueBuilder::makeBinary(ptr
, PLUS
, ValueBuilder::makeNum(curr
->offset
)), ASM_INT
);
1205 Ref value
= visit(curr
->value
, EXPRESSION_RESULT
);
1207 switch (curr
->valueType
) {
1209 switch (curr
->bytes
) {
1210 case 1: ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAP8
), ValueBuilder::makePtrShift(ptr
, 0)); break;
1211 case 2: ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAP16
), ValueBuilder::makePtrShift(ptr
, 1)); break;
1212 case 4: ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32
), ValueBuilder::makePtrShift(ptr
, 2)); break;
1217 case f32
: ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32
), ValueBuilder::makePtrShift(ptr
, 2)); break;
1218 case f64
: ret
= ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64
), ValueBuilder::makePtrShift(ptr
, 3)); break;
1220 std::cerr
<< "Unhandled type in store: " << curr
->valueType
1225 return ValueBuilder::makeBinary(ret
, SET
, value
);
1228 Ref
visitDrop(Drop
* curr
) {
1229 assert(!isStatement(curr
));
1230 return visitAndAssign(curr
->value
, result
);
1233 Ref
visitConst(Const
* curr
) {
1234 switch (curr
->type
) {
1235 case i32
: return ValueBuilder::makeInt(curr
->value
.geti32());
1237 Ref ret
= ValueBuilder::makeCall(MATH_FROUND
);
1238 Const
fake(allocator
);
1239 fake
.value
= Literal(double(curr
->value
.getf32()));
1241 ret
[2]->push_back(visitConst(&fake
));
1245 double d
= curr
->value
.getf64();
1246 if (d
== 0 && std::signbit(d
)) { // negative zero
1247 return ValueBuilder::makeUnary(PLUS
, ValueBuilder::makeUnary(MINUS
, ValueBuilder::makeDouble(0)));
1249 return ValueBuilder::makeUnary(PLUS
, ValueBuilder::makeDouble(curr
->value
.getf64()));
1255 Ref
visitUnary(Unary
* curr
) {
1256 if (isStatement(curr
)) {
1257 ScopedTemp
temp(curr
->value
->type
, parent
, func
);
1258 GetLocal
fakeLocal(allocator
);
1259 fakeLocal
.index
= func
->getLocalIndex(temp
.getName());
1260 Unary fakeUnary
= *curr
;
1261 fakeUnary
.value
= &fakeLocal
;
1262 Ref ret
= blockify(visitAndAssign(curr
->value
, temp
));
1263 flattenAppend(ret
, visitAndAssign(&fakeUnary
, result
));
1267 switch (curr
->type
) {
1271 return ValueBuilder::makeCall(
1273 visit(curr
->value
, EXPRESSION_RESULT
)
1277 ValueBuilder::makeCall(
1279 visit(curr
->value
, EXPRESSION_RESULT
)
1285 ValueBuilder::makeCall(
1287 visit(curr
->value
, EXPRESSION_RESULT
)
1292 return ValueBuilder::makeBinary(
1293 makeAsmCoercion(visit(curr
->value
,
1294 EXPRESSION_RESULT
), ASM_INT
), EQ
,
1295 makeAsmCoercion(ValueBuilder::makeInt(0), ASM_INT
));
1297 std::cerr
<< "Unhandled unary i32 operator: " << curr
1309 ret
= ValueBuilder::makeUnary(
1311 visit(curr
->value
, EXPRESSION_RESULT
)
1316 ret
= ValueBuilder::makeCall(
1318 visit(curr
->value
, EXPRESSION_RESULT
)
1323 ret
= ValueBuilder::makeCall(
1325 visit(curr
->value
, EXPRESSION_RESULT
)
1330 ret
= ValueBuilder::makeCall(
1332 visit(curr
->value
, EXPRESSION_RESULT
)
1337 ret
= ValueBuilder::makeCall(
1339 visit(curr
->value
, EXPRESSION_RESULT
)
1342 case NearestFloat32
:
1343 case NearestFloat64
:
1344 ret
= ValueBuilder::makeCall(
1346 visit(curr
->value
,EXPRESSION_RESULT
)
1351 ret
= ValueBuilder::makeCall(
1353 visit(curr
->value
, EXPRESSION_RESULT
)
1356 // TODO: more complex unary conversions
1358 std::cerr
<< "Unhandled unary float operator: " << curr
1362 if (curr
->type
== f32
) { // doubles need much less coercing
1363 return makeAsmCoercion(ret
, ASM_FLOAT
);
1368 std::cerr
<< "Unhandled type in unary: " << curr
<< std::endl
;
1374 Ref
visitBinary(Binary
* curr
) {
1375 if (isStatement(curr
)) {
1376 ScopedTemp
tempLeft(curr
->left
->type
, parent
, func
);
1377 GetLocal
fakeLocalLeft(allocator
);
1378 fakeLocalLeft
.index
= func
->getLocalIndex(tempLeft
.getName());
1379 ScopedTemp
tempRight(curr
->right
->type
, parent
, func
);
1380 GetLocal
fakeLocalRight(allocator
);
1381 fakeLocalRight
.index
= func
->getLocalIndex(tempRight
.getName());
1382 Binary fakeBinary
= *curr
;
1383 fakeBinary
.left
= &fakeLocalLeft
;
1384 fakeBinary
.right
= &fakeLocalRight
;
1385 Ref ret
= blockify(visitAndAssign(curr
->left
, tempLeft
));
1386 flattenAppend(ret
, visitAndAssign(curr
->right
, tempRight
));
1387 flattenAppend(ret
, visitAndAssign(&fakeBinary
, result
));
1391 Ref left
= visit(curr
->left
, EXPRESSION_RESULT
);
1392 Ref right
= visit(curr
->right
, EXPRESSION_RESULT
);
1396 ret
= ValueBuilder::makeBinary(left
, PLUS
, right
);
1399 ret
= ValueBuilder::makeBinary(left
, MINUS
, right
);
1402 if (curr
->type
== i32
) {
1403 // TODO: when one operand is a small int, emit a multiply
1404 return ValueBuilder::makeCall(MATH_IMUL
, left
, right
);
1406 return ValueBuilder::makeBinary(left
, MUL
, right
);
1410 ret
= ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), DIV
,
1411 makeSigning(right
, ASM_SIGNED
));
1414 ret
= ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), DIV
,
1415 makeSigning(right
, ASM_UNSIGNED
));
1418 ret
= ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), MOD
,
1419 makeSigning(right
, ASM_SIGNED
));
1422 ret
= ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), MOD
,
1423 makeSigning(right
, ASM_UNSIGNED
));
1426 ret
= ValueBuilder::makeBinary(left
, AND
, right
);
1429 ret
= ValueBuilder::makeBinary(left
, OR
, right
);
1432 ret
= ValueBuilder::makeBinary(left
, XOR
, right
);
1435 ret
= ValueBuilder::makeBinary(left
, LSHIFT
, right
);
1438 ret
= ValueBuilder::makeBinary(left
, TRSHIFT
, right
);
1441 ret
= ValueBuilder::makeBinary(left
, RSHIFT
, right
);
1444 ret
= ValueBuilder::makeCall(MATH_MIN
, left
, right
);
1447 ret
= ValueBuilder::makeCall(MATH_MAX
, left
, right
);
1450 // TODO: check if this condition is still valid/necessary
1451 if (curr
->left
->type
== i32
) {
1452 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), EQ
,
1453 makeSigning(right
, ASM_SIGNED
));
1455 return ValueBuilder::makeBinary(left
, EQ
, right
);
1459 if (curr
->left
->type
== i32
) {
1460 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), NE
,
1461 makeSigning(right
, ASM_SIGNED
));
1463 return ValueBuilder::makeBinary(left
, NE
, right
);
1467 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), LT
,
1468 makeSigning(right
, ASM_SIGNED
));
1470 return ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), LT
,
1471 makeSigning(right
, ASM_UNSIGNED
));
1473 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), LE
,
1474 makeSigning(right
, ASM_SIGNED
));
1476 return ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), LE
,
1477 makeSigning(right
, ASM_UNSIGNED
));
1479 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), GT
,
1480 makeSigning(right
, ASM_SIGNED
));
1482 return ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), GT
,
1483 makeSigning(right
, ASM_UNSIGNED
));
1485 return ValueBuilder::makeBinary(makeSigning(left
, ASM_SIGNED
), GE
,
1486 makeSigning(right
, ASM_SIGNED
));
1488 return ValueBuilder::makeBinary(makeSigning(left
, ASM_UNSIGNED
), GE
,
1489 makeSigning(right
, ASM_UNSIGNED
));
1491 return makeSigning(ValueBuilder::makeCall(WASM_ROTL32
, left
, right
),
1494 return makeSigning(ValueBuilder::makeCall(WASM_ROTR32
, left
, right
),
1497 std::cerr
<< "Unhandled binary operator: " << curr
<< std::endl
;
1501 return makeAsmCoercion(ret
, wasmToAsmType(curr
->type
));
1504 Ref
visitSelect(Select
* curr
) {
1505 if (isStatement(curr
)) {
1506 ScopedTemp
tempIfTrue(curr
->ifTrue
->type
, parent
, func
);
1507 GetLocal
fakeLocalIfTrue(allocator
);
1508 fakeLocalIfTrue
.index
= func
->getLocalIndex(tempIfTrue
.getName());
1509 ScopedTemp
tempIfFalse(curr
->ifFalse
->type
, parent
, func
);
1510 GetLocal
fakeLocalIfFalse(allocator
);
1511 fakeLocalIfFalse
.index
= func
->getLocalIndex(tempIfFalse
.getName());
1512 ScopedTemp
tempCondition(i32
, parent
, func
);
1513 GetLocal
fakeCondition(allocator
);
1514 fakeCondition
.index
= func
->getLocalIndex(tempCondition
.getName());
1515 Select fakeSelect
= *curr
;
1516 fakeSelect
.ifTrue
= &fakeLocalIfTrue
;
1517 fakeSelect
.ifFalse
= &fakeLocalIfFalse
;
1518 fakeSelect
.condition
= &fakeCondition
;
1519 Ref ret
= blockify(visitAndAssign(curr
->ifTrue
, tempIfTrue
));
1520 flattenAppend(ret
, visitAndAssign(curr
->ifFalse
, tempIfFalse
));
1521 flattenAppend(ret
, visitAndAssign(curr
->condition
, tempCondition
));
1522 flattenAppend(ret
, visitAndAssign(&fakeSelect
, result
));
1526 Ref ifTrue
= visit(curr
->ifTrue
, EXPRESSION_RESULT
);
1527 Ref ifFalse
= visit(curr
->ifFalse
, EXPRESSION_RESULT
);
1528 Ref condition
= visit(curr
->condition
, EXPRESSION_RESULT
);
1529 ScopedTemp
tempIfTrue(curr
->type
, parent
, func
),
1530 tempIfFalse(curr
->type
, parent
, func
),
1531 tempCondition(i32
, parent
, func
);
1533 ValueBuilder::makeSeq(
1534 ValueBuilder::makeBinary(tempCondition
.getAstName(), SET
, condition
),
1535 ValueBuilder::makeSeq(
1536 ValueBuilder::makeBinary(tempIfTrue
.getAstName(), SET
, ifTrue
),
1537 ValueBuilder::makeSeq(
1538 ValueBuilder::makeBinary(tempIfFalse
.getAstName(), SET
, ifFalse
),
1539 ValueBuilder::makeConditional(
1540 tempCondition
.getAstName(),
1541 tempIfTrue
.getAstName(),
1542 tempIfFalse
.getAstName()
1549 Ref
visitReturn(Return
* curr
) {
1550 Ref val
= (curr
->value
== nullptr) ?
1553 visit(curr
->value
, NO_RESULT
),
1554 wasmToAsmType(curr
->value
->type
)
1556 return ValueBuilder::makeReturn(val
);
1559 Ref
visitHost(Host
* curr
) {
1563 Ref
visitNop(Nop
* curr
) {
1564 return ValueBuilder::makeToplevel();
1567 Ref
visitUnreachable(Unreachable
* curr
) {
1568 return ValueBuilder::makeCall(ABORT_FUNC
);
1571 return ExpressionProcessor(this, func
).visit(func
->body
, result
);
1574 static Ref
makeInstantiation() {
1575 Ref lib
= ValueBuilder::makeObject();
1576 auto insertItem
= [&](IString item
) {
1577 ValueBuilder::appendToObject(lib
, item
, ValueBuilder::makeName(item
));
1580 insertItem(INT8ARRAY
);
1581 insertItem(INT16ARRAY
);
1582 insertItem(INT32ARRAY
);
1583 insertItem(UINT8ARRAY
);
1584 insertItem(UINT16ARRAY
);
1585 insertItem(UINT32ARRAY
);
1586 insertItem(FLOAT32ARRAY
);
1587 insertItem(FLOAT64ARRAY
);
1588 Ref env
= ValueBuilder::makeObject();
1589 Ref mem
= ValueBuilder::makeNew(
1590 ValueBuilder::makeCall(ARRAY_BUFFER
, ValueBuilder::makeInt(0x10000)));
1591 Ref call
= ValueBuilder::makeCall(IString(ASM_FUNC
), lib
, env
, mem
);
1592 Ref ret
= ValueBuilder::makeVar();
1593 ValueBuilder::appendToVar(ret
, ASM_MODULE
, call
);
1597 static void prefixCalls(Ref asmjs
) {
1598 if (asmjs
->isArray()) {
1599 ArrayStorage
& arr
= asmjs
->getArray();
1600 for (Ref
& r
: arr
) {
1603 if (arr
.size() > 0 && arr
[0]->isString() && arr
[0]->getIString() == CALL
) {
1604 assert(arr
.size() >= 2);
1605 Ref prefixed
= ValueBuilder::makeDot(ValueBuilder::makeName(ASM_MODULE
),
1606 arr
[1]->getIString());
1607 arr
[1]->setArray(prefixed
->getArray());
1612 Ref
Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder
& sexpBuilder
,
1613 Builder
& wasmBuilder
,
1614 Element
& e
, Name testFuncName
) {
1615 Expression
* actual
= sexpBuilder
.parseExpression(e
[1]);
1616 Expression
* body
= nullptr;
1617 if (e
.size() == 2) {
1618 if (actual
->type
== none
) {
1619 body
= wasmBuilder
.blockify(
1621 wasmBuilder
.makeConst(Literal(uint32_t(1)))
1626 } else if (e
.size() == 3) {
1627 Expression
* expected
= sexpBuilder
.parseExpression(e
[2]);
1628 WasmType resType
= expected
->type
;
1629 actual
->type
= resType
;
1632 case i32
: eqOp
= EqInt32
; break;
1633 case i64
: eqOp
= EqInt64
; break;
1634 case f32
: eqOp
= EqFloat32
; break;
1635 case f64
: eqOp
= EqFloat64
; break;
1637 std::cerr
<< "Unhandled type in assert: " << resType
<< std::endl
;
1641 body
= wasmBuilder
.makeBinary(eqOp
, actual
, expected
);
1643 assert(false && "Unexpected number of parameters in assert_return");
1645 std::unique_ptr
<Function
> testFunc(
1646 wasmBuilder
.makeFunction(
1648 std::vector
<NameType
>{},
1650 std::vector
<NameType
>{},
1654 Ref jsFunc
= processFunction(testFunc
.get());
1655 prefixCalls(jsFunc
);
1659 Ref
Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder
& sexpBuilder
,
1660 Builder
& wasmBuilder
,
1661 Element
& e
, Name testFuncName
) {
1662 Name
innerFuncName("f");
1663 Expression
* expr
= sexpBuilder
.parseExpression(e
[1]);
1664 std::unique_ptr
<Function
> exprFunc(
1665 wasmBuilder
.makeFunction(innerFuncName
,
1666 std::vector
<NameType
>{},
1668 std::vector
<NameType
>{},
1671 IString expectedErr
= e
[2]->str();
1672 Ref innerFunc
= processFunction(exprFunc
.get());
1673 Ref outerFunc
= ValueBuilder::makeFunction(testFuncName
);
1674 outerFunc
[3]->push_back(innerFunc
);
1675 Ref tryBlock
= ValueBuilder::makeBlock();
1676 ValueBuilder::appendToBlock(tryBlock
, ValueBuilder::makeCall(innerFuncName
));
1677 Ref catchBlock
= ValueBuilder::makeBlock();
1678 ValueBuilder::appendToBlock(
1680 ValueBuilder::makeReturn(
1681 ValueBuilder::makeCall(
1682 ValueBuilder::makeDot(
1683 ValueBuilder::makeName(IString("e")),
1684 ValueBuilder::makeName(IString("message")),
1685 ValueBuilder::makeName(IString("includes"))
1687 ValueBuilder::makeString(expectedErr
)
1691 outerFunc
[3]->push_back(ValueBuilder::makeTry(
1693 ValueBuilder::makeName((IString("e"))),
1695 outerFunc
[3]->push_back(ValueBuilder::makeReturn(ValueBuilder::makeInt(0)));
1699 bool Wasm2AsmBuilder::isAssertHandled(Element
& e
) {
1700 return e
.isList() && e
.size() >= 2 && e
[0]->isStr()
1701 && (e
[0]->str() == Name("assert_return") ||
1702 (flags
.pedantic
&& e
[0]->str() == Name("assert_trap")))
1703 && e
[1]->isList() && e
[1]->size() >= 2 && (*e
[1])[0]->isStr()
1704 && (*e
[1])[0]->str() == Name("invoke");
1707 Ref
Wasm2AsmBuilder::processAsserts(Element
& root
,
1708 SExpressionWasmBuilder
& sexpBuilder
) {
1709 Builder
wasmBuilder(sexpBuilder
.getAllocator());
1710 Ref ret
= ValueBuilder::makeBlock();
1711 flattenAppend(ret
, makeInstantiation());
1712 for (size_t i
= 1; i
< root
.size(); ++i
) {
1713 Element
& e
= *root
[i
];
1714 if (!isAssertHandled(e
)) {
1715 std::cerr
<< "skipping " << e
<< std::endl
;
1718 Name
testFuncName(IString(("check" + std::to_string(i
)).c_str(), false));
1719 bool isReturn
= (e
[0]->str() == Name("assert_return"));
1720 Element
& testOp
= *e
[1];
1721 // Replace "invoke" with "call"
1722 testOp
[0]->setString(IString("call"), false, false);
1723 // Need to claim dollared to get string as function target
1724 testOp
[1]->setString(testOp
[1]->str(), /*dollared=*/true, false);
1726 Ref testFunc
= isReturn
?
1727 makeAssertReturnFunc(sexpBuilder
, wasmBuilder
, e
, testFuncName
) :
1728 makeAssertTrapFunc(sexpBuilder
, wasmBuilder
, e
, testFuncName
);
1730 flattenAppend(ret
, testFunc
);
1731 std::stringstream failFuncName
;
1732 failFuncName
<< "fail" << std::to_string(i
);
1735 ValueBuilder::makeIf(
1736 ValueBuilder::makeUnary(L_NOT
, ValueBuilder::makeCall(testFuncName
)),
1737 ValueBuilder::makeCall(IString(failFuncName
.str().c_str(), false)),
1748 #endif // wasm_wasm2asm_h