diff options
author | Alon Zakai <alonzakai@gmail.com> | 2016-11-19 12:13:58 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2016-12-07 16:50:04 -1000 |
commit | 0a498b945e6fe51db9fd3e5b76997e3f40b735a7 (patch) | |
tree | d9f3bf67eabb7a465dc0a16cc9fac81302f47a25 | |
parent | 420f28ff46322b1d9eddd20ce4bd4f30b42fa311 (diff) | |
download | binaryen-0a498b945e6fe51db9fd3e5b76997e3f40b735a7.tar.gz binaryen-0a498b945e6fe51db9fd3e5b76997e3f40b735a7.tar.bz2 binaryen-0a498b945e6fe51db9fd3e5b76997e3f40b735a7.zip |
make legalizeJSInterface handle f32s as well, which are not valid in asm.js ffis
-rw-r--r-- | src/asm2wasm.h | 5 | ||||
-rw-r--r-- | src/passes/LegalizeJSInterface.cpp | 20 | ||||
-rw-r--r-- | test/min.fromasm | 31 | ||||
-rw-r--r-- | test/min.fromasm.imprecise | 31 | ||||
-rw-r--r-- | test/min.fromasm.imprecise.no-opts | 31 | ||||
-rw-r--r-- | test/min.fromasm.no-opts | 31 | ||||
-rw-r--r-- | test/unit.asm.js | 9 | ||||
-rw-r--r-- | test/unit.fromasm | 44 | ||||
-rw-r--r-- | test/unit.fromasm.imprecise | 44 | ||||
-rw-r--r-- | test/unit.fromasm.imprecise.no-opts | 46 | ||||
-rw-r--r-- | test/unit.fromasm.no-opts | 46 |
11 files changed, 307 insertions, 31 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 9ac9ef2c4..853cb84df 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -1003,10 +1003,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { passRunner.add<FinalizeCalls>(this); passRunner.add<ReFinalize>(); // FinalizeCalls changes call types, need to percolate passRunner.add<AutoDrop>(); // FinalizeCalls may cause us to require additional drops - if (wasmOnly) { - // we didn't legalize i64s in fastcomp, and so must legalize the interface to the outside - passRunner.add("legalize-js-interface"); - } + passRunner.add("legalize-js-interface"); if (runOptimizationPasses) { // autodrop can add some garbage passRunner.add("vacuum"); diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 33bba2112..1c2685b10 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -22,6 +22,9 @@ // stub methods added in this pass, that thunk i64s into i32, i32 and // vice versa as necessary. // +// This pass also legalizes according to asm.js FFI rules, which +// disallow f32s. TODO: an option to not do that, if it matters? +// #include <wasm.h> #include <pass.h> @@ -94,9 +97,9 @@ private: template<typename T> bool isIllegal(T* t) { for (auto param : t->params) { - if (param == i64) return true; + if (param == i64 || param == f32) return true; } - if (t->result == i64) return true; + if (t->result == i64 || t->result == f32) return true; return false; } @@ -115,6 +118,9 @@ private: call->operands.push_back(I64Utilities::recreateI64(builder, legal->params.size(), legal->params.size() + 1)); legal->params.push_back(i32); legal->params.push_back(i32); + } else if (param == f32) { + call->operands.push_back(builder.makeUnary(DemoteFloat64, builder.makeGetLocal(legal->params.size(), f64))); + legal->params.push_back(f64); } else { call->operands.push_back(builder.makeGetLocal(legal->params.size(), param)); legal->params.push_back(param); @@ -134,6 +140,9 @@ private: block->list.push_back(I64Utilities::getI64Low(builder, index)); block->finalize(); legal->body = block; + } else if (func->result == f32) { + legal->result = f64; + legal->body = builder.makeUnary(PromoteFloat32, call); } else { legal->result = func->result; legal->body = call; @@ -170,6 +179,9 @@ private: call->operands.push_back(I64Utilities::getI64High(builder, func->params.size())); type->params.push_back(i32); type->params.push_back(i32); + } else if (param == f32) { + call->operands.push_back(builder.makeUnary(PromoteFloat32, builder.makeGetLocal(func->params.size(), f64))); + type->params.push_back(f64); } else { call->operands.push_back(builder.makeGetLocal(func->params.size(), param)); type->params.push_back(param); @@ -184,6 +196,10 @@ private: get = builder.makeGetGlobal(TEMP_RET_0, i32); func->body = I64Utilities::recreateI64(builder, call, get); type->result = i32; + } else if (im->functionType->result == f32) { + call->type = f64; + func->body = builder.makeUnary(PromoteFloat32, call); + type->result = f64; } else { call->type = im->functionType->result; func->body = call; diff --git a/test/min.fromasm b/test/min.fromasm index 76d071ef6..4287f6259 100644 --- a/test/min.fromasm +++ b/test/min.fromasm @@ -5,10 +5,10 @@ (import "env" "tableBase" (global $tableBase i32)) (data (get_global $memoryBase) "min.asm.js") (global $M (mut i32) (i32.const 0)) - (export "floats" (func $floats)) + (export "floats" (func $legalstub$floats)) (export "getTempRet0" (func $ub)) - (export "neg" (func $neg)) - (export "bitcasts" (func $bitcasts)) + (export "neg" (func $legalstub$neg)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "ctzzzz" (func $ctzzzz)) (func $floats (param $0 f32) (result f32) (local $1 f32) @@ -40,4 +40,29 @@ ) (get_global $M) ) + (func $legalstub$floats (param $0 f64) (result f64) + (f64.promote/f32 + (call $floats + (f32.demote/f64 + (get_local $0) + ) + ) + ) + ) + (func $legalstub$neg (param $0 i32) (param $1 i32) (result f64) + (f64.promote/f32 + (call $neg + (get_local $0) + (get_local $1) + ) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) ) diff --git a/test/min.fromasm.imprecise b/test/min.fromasm.imprecise index 9b63300b5..302f64c53 100644 --- a/test/min.fromasm.imprecise +++ b/test/min.fromasm.imprecise @@ -4,10 +4,10 @@ (import "env" "memoryBase" (global $memoryBase i32)) (import "env" "tableBase" (global $tableBase i32)) (global $M (mut i32) (i32.const 0)) - (export "floats" (func $floats)) + (export "floats" (func $legalstub$floats)) (export "getTempRet0" (func $ub)) - (export "neg" (func $neg)) - (export "bitcasts" (func $bitcasts)) + (export "neg" (func $legalstub$neg)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "ctzzzz" (func $ctzzzz)) (func $floats (param $0 f32) (result f32) (local $1 f32) @@ -39,4 +39,29 @@ ) (get_global $M) ) + (func $legalstub$floats (param $0 f64) (result f64) + (f64.promote/f32 + (call $floats + (f32.demote/f64 + (get_local $0) + ) + ) + ) + ) + (func $legalstub$neg (param $0 i32) (param $1 i32) (result f64) + (f64.promote/f32 + (call $neg + (get_local $0) + (get_local $1) + ) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) ) diff --git a/test/min.fromasm.imprecise.no-opts b/test/min.fromasm.imprecise.no-opts index 4bbdeff65..6e8a1c9ea 100644 --- a/test/min.fromasm.imprecise.no-opts +++ b/test/min.fromasm.imprecise.no-opts @@ -6,10 +6,10 @@ (import "env" "tableBase" (global $tableBase i32)) (global $tDP (mut i32) (get_global $tDP$asm2wasm$import)) (global $M (mut i32) (i32.const 0)) - (export "floats" (func $floats)) + (export "floats" (func $legalstub$floats)) (export "getTempRet0" (func $ub)) - (export "neg" (func $neg)) - (export "bitcasts" (func $bitcasts)) + (export "neg" (func $legalstub$neg)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "ctzzzz" (func $ctzzzz)) (func $floats (param $f f32) (result f32) (local $t f32) @@ -73,4 +73,29 @@ (get_global $M) ) ) + (func $legalstub$floats (param $0 f64) (result f64) + (f64.promote/f32 + (call $floats + (f32.demote/f64 + (get_local $0) + ) + ) + ) + ) + (func $legalstub$neg (param $0 i32) (param $1 i32) (result f64) + (f64.promote/f32 + (call $neg + (get_local $0) + (get_local $1) + ) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) ) diff --git a/test/min.fromasm.no-opts b/test/min.fromasm.no-opts index 4bbdeff65..6e8a1c9ea 100644 --- a/test/min.fromasm.no-opts +++ b/test/min.fromasm.no-opts @@ -6,10 +6,10 @@ (import "env" "tableBase" (global $tableBase i32)) (global $tDP (mut i32) (get_global $tDP$asm2wasm$import)) (global $M (mut i32) (i32.const 0)) - (export "floats" (func $floats)) + (export "floats" (func $legalstub$floats)) (export "getTempRet0" (func $ub)) - (export "neg" (func $neg)) - (export "bitcasts" (func $bitcasts)) + (export "neg" (func $legalstub$neg)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "ctzzzz" (func $ctzzzz)) (func $floats (param $f f32) (result f32) (local $t f32) @@ -73,4 +73,29 @@ (get_global $M) ) ) + (func $legalstub$floats (param $0 f64) (result f64) + (f64.promote/f32 + (call $floats + (f32.demote/f64 + (get_local $0) + ) + ) + ) + ) + (func $legalstub$neg (param $0 i32) (param $1 i32) (result f64) + (f64.promote/f32 + (call $neg + (get_local $0) + (get_local $1) + ) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) ) diff --git a/test/unit.asm.js b/test/unit.asm.js index 4ab43b737..385db859c 100644 --- a/test/unit.asm.js +++ b/test/unit.asm.js @@ -658,6 +658,13 @@ function asm(global, env, buffer) { return ftCall_idi(30, 1.5, 200) | 0; // with args } + function exported_f32_user(x, y, z) { + x = x | 0; + y = Math_fround(y); + z = +z; + return Math_fround(y); + } + function v() { } function vi(x) { @@ -669,6 +676,6 @@ function asm(global, env, buffer) { var FUNCTION_TABLE_c = [ z, cneg, z, z, z, z, z, z ]; var FUNCTION_TABLE_vi = [ vi, vi, vi, vi, vi, vi, vi, vi ]; - return { big_negative: big_negative, pick: forgetMe, pick: exportMe, doubleCompares: doubleCompares, intOps: intOps, conversions: conversions, switcher: switcher, frem: frem, big_uint_div_u: big_uint_div_u, fr: fr, negZero: negZero, neg: neg, smallCompare: smallCompare, cneg_nosemicolon: cneg_nosemicolon, forLoop: forLoop, ceiling_32_64: ceiling_32_64, aborts: aborts, continues: continues, bitcasts: bitcasts, recursiveBlockMerging: recursiveBlockMerging, lb: lb, zeroInit: zeroInit, phi: phi, smallIf: smallIf, dropCall: dropCall, useSetGlobal: useSetGlobal, usesSetGlobal2: usesSetGlobal2, breakThroughMany: breakThroughMany, ifChainEmpty: ifChainEmpty, heap8NoShift: heap8NoShift, conditionalTypeFun: conditionalTypeFun, loadSigned: loadSigned, globalOpts: globalOpts, dropCallImport: dropCallImport, loophi: loophi, loophi2: loophi2, relooperJumpThreading: relooperJumpThreading, relooperJumpThreading__ZN4game14preloadweaponsEv: relooperJumpThreading__ZN4game14preloadweaponsEv, __Z12multi_varargiz: __Z12multi_varargiz, jumpThreadDrop: jumpThreadDrop, dropIgnoredImportInIf: dropIgnoredImportInIf, dropIgnoredImportsInIf: dropIgnoredImportsInIf, relooperJumpThreading_irreducible: relooperJumpThreading_irreducible, store_fround: store_fround, exportedNumber: 42, relocatableAndModules: relocatableAndModules }; + return { big_negative: big_negative, pick: forgetMe, pick: exportMe, doubleCompares: doubleCompares, intOps: intOps, conversions: conversions, switcher: switcher, frem: frem, big_uint_div_u: big_uint_div_u, fr: fr, negZero: negZero, neg: neg, smallCompare: smallCompare, cneg_nosemicolon: cneg_nosemicolon, forLoop: forLoop, ceiling_32_64: ceiling_32_64, aborts: aborts, continues: continues, bitcasts: bitcasts, recursiveBlockMerging: recursiveBlockMerging, lb: lb, zeroInit: zeroInit, phi: phi, smallIf: smallIf, dropCall: dropCall, useSetGlobal: useSetGlobal, usesSetGlobal2: usesSetGlobal2, breakThroughMany: breakThroughMany, ifChainEmpty: ifChainEmpty, heap8NoShift: heap8NoShift, conditionalTypeFun: conditionalTypeFun, loadSigned: loadSigned, globalOpts: globalOpts, dropCallImport: dropCallImport, loophi: loophi, loophi2: loophi2, relooperJumpThreading: relooperJumpThreading, relooperJumpThreading__ZN4game14preloadweaponsEv: relooperJumpThreading__ZN4game14preloadweaponsEv, __Z12multi_varargiz: __Z12multi_varargiz, jumpThreadDrop: jumpThreadDrop, dropIgnoredImportInIf: dropIgnoredImportInIf, dropIgnoredImportsInIf: dropIgnoredImportsInIf, relooperJumpThreading_irreducible: relooperJumpThreading_irreducible, store_fround: store_fround, exportedNumber: 42, relocatableAndModules: relocatableAndModules, exported_f32_user: exported_f32_user }; } diff --git a/test/unit.fromasm b/test/unit.fromasm index d887aedac..e18bfd16a 100644 --- a/test/unit.fromasm +++ b/test/unit.fromasm @@ -36,16 +36,16 @@ (export "switcher" (func $switcher)) (export "frem" (func $frem)) (export "big_uint_div_u" (func $big_uint_div_u)) - (export "fr" (func $fr)) + (export "fr" (func $legalstub$fr)) (export "negZero" (func $negZero)) (export "neg" (func $neg)) (export "smallCompare" (func $smallCompare)) (export "cneg_nosemicolon" (func $cneg_nosemicolon)) (export "forLoop" (func $forLoop)) - (export "ceiling_32_64" (func $ceiling_32_64)) + (export "ceiling_32_64" (func $legalstub$ceiling_32_64)) (export "aborts" (func $aborts)) (export "continues" (func $continues)) - (export "bitcasts" (func $bitcasts)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "recursiveBlockMerging" (func $recursiveBlockMerging)) (export "lb" (func $lb)) (export "zeroInit" (func $zeroInit)) @@ -73,6 +73,7 @@ (export "store_fround" (func $store_fround)) (export "exportedNumber" (global $exportedNumber)) (export "relocatableAndModules" (func $relocatableAndModules)) + (export "exported_f32_user" (func $legalstub$exported_f32_user)) (func $big_negative (nop) ) @@ -1146,7 +1147,44 @@ (i32.const 30) ) ) + (func $exported_f32_user (param $0 i32) (param $1 f32) (param $2 f64) (result f32) + (get_local $1) + ) (func $vi (param $0 i32) (nop) ) + (func $legalstub$fr (param $0 f64) + (call $fr + (f32.demote/f64 + (get_local $0) + ) + ) + ) + (func $legalstub$ceiling_32_64 (param $0 f64) (param $1 f64) + (call $ceiling_32_64 + (f32.demote/f64 + (get_local $0) + ) + (get_local $1) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) + (func $legalstub$exported_f32_user (param $0 i32) (param $1 f64) (param $2 f64) (result f64) + (f64.promote/f32 + (call $exported_f32_user + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + (get_local $2) + ) + ) + ) ) diff --git a/test/unit.fromasm.imprecise b/test/unit.fromasm.imprecise index 10ea5a01f..2ec8b82ff 100644 --- a/test/unit.fromasm.imprecise +++ b/test/unit.fromasm.imprecise @@ -31,16 +31,16 @@ (export "switcher" (func $switcher)) (export "frem" (func $frem)) (export "big_uint_div_u" (func $big_uint_div_u)) - (export "fr" (func $fr)) + (export "fr" (func $legalstub$fr)) (export "negZero" (func $negZero)) (export "neg" (func $neg)) (export "smallCompare" (func $smallCompare)) (export "cneg_nosemicolon" (func $cneg_nosemicolon)) (export "forLoop" (func $forLoop)) - (export "ceiling_32_64" (func $ceiling_32_64)) + (export "ceiling_32_64" (func $legalstub$ceiling_32_64)) (export "aborts" (func $aborts)) (export "continues" (func $continues)) - (export "bitcasts" (func $bitcasts)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "recursiveBlockMerging" (func $recursiveBlockMerging)) (export "lb" (func $lb)) (export "zeroInit" (func $zeroInit)) @@ -68,6 +68,7 @@ (export "store_fround" (func $store_fround)) (export "exportedNumber" (global $exportedNumber)) (export "relocatableAndModules" (func $relocatableAndModules)) + (export "exported_f32_user" (func $legalstub$exported_f32_user)) (func $big_negative (nop) ) @@ -1122,7 +1123,44 @@ (i32.const 30) ) ) + (func $exported_f32_user (param $0 i32) (param $1 f32) (param $2 f64) (result f32) + (get_local $1) + ) (func $vi (param $0 i32) (nop) ) + (func $legalstub$fr (param $0 f64) + (call $fr + (f32.demote/f64 + (get_local $0) + ) + ) + ) + (func $legalstub$ceiling_32_64 (param $0 f64) (param $1 f64) + (call $ceiling_32_64 + (f32.demote/f64 + (get_local $0) + ) + (get_local $1) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) + (func $legalstub$exported_f32_user (param $0 i32) (param $1 f64) (param $2 f64) (result f64) + (f64.promote/f32 + (call $exported_f32_user + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + (get_local $2) + ) + ) + ) ) diff --git a/test/unit.fromasm.imprecise.no-opts b/test/unit.fromasm.imprecise.no-opts index 7831656f7..b79ff2389 100644 --- a/test/unit.fromasm.imprecise.no-opts +++ b/test/unit.fromasm.imprecise.no-opts @@ -39,16 +39,16 @@ (export "switcher" (func $switcher)) (export "frem" (func $frem)) (export "big_uint_div_u" (func $big_uint_div_u)) - (export "fr" (func $fr)) + (export "fr" (func $legalstub$fr)) (export "negZero" (func $negZero)) (export "neg" (func $neg)) (export "smallCompare" (func $smallCompare)) (export "cneg_nosemicolon" (func $cneg_nosemicolon)) (export "forLoop" (func $forLoop)) - (export "ceiling_32_64" (func $ceiling_32_64)) + (export "ceiling_32_64" (func $legalstub$ceiling_32_64)) (export "aborts" (func $aborts)) (export "continues" (func $continues)) - (export "bitcasts" (func $bitcasts)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "recursiveBlockMerging" (func $recursiveBlockMerging)) (export "lb" (func $lb)) (export "zeroInit" (func $zeroInit)) @@ -76,6 +76,7 @@ (export "store_fround" (func $store_fround)) (export "exportedNumber" (global $exportedNumber)) (export "relocatableAndModules" (func $relocatableAndModules)) + (export "exported_f32_user" (func $legalstub$exported_f32_user)) (func $big_negative (local $temp f64) (set_local $temp @@ -1818,10 +1819,49 @@ ) ) ) + (func $exported_f32_user (param $x i32) (param $y f32) (param $z f64) (result f32) + (return + (get_local $y) + ) + ) (func $v (nop) ) (func $vi (param $x i32) (nop) ) + (func $legalstub$fr (param $0 f64) + (call $fr + (f32.demote/f64 + (get_local $0) + ) + ) + ) + (func $legalstub$ceiling_32_64 (param $0 f64) (param $1 f64) + (call $ceiling_32_64 + (f32.demote/f64 + (get_local $0) + ) + (get_local $1) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) + (func $legalstub$exported_f32_user (param $0 i32) (param $1 f64) (param $2 f64) (result f64) + (f64.promote/f32 + (call $exported_f32_user + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + (get_local $2) + ) + ) + ) ) diff --git a/test/unit.fromasm.no-opts b/test/unit.fromasm.no-opts index dfcf18538..717b6a4c4 100644 --- a/test/unit.fromasm.no-opts +++ b/test/unit.fromasm.no-opts @@ -43,16 +43,16 @@ (export "switcher" (func $switcher)) (export "frem" (func $frem)) (export "big_uint_div_u" (func $big_uint_div_u)) - (export "fr" (func $fr)) + (export "fr" (func $legalstub$fr)) (export "negZero" (func $negZero)) (export "neg" (func $neg)) (export "smallCompare" (func $smallCompare)) (export "cneg_nosemicolon" (func $cneg_nosemicolon)) (export "forLoop" (func $forLoop)) - (export "ceiling_32_64" (func $ceiling_32_64)) + (export "ceiling_32_64" (func $legalstub$ceiling_32_64)) (export "aborts" (func $aborts)) (export "continues" (func $continues)) - (export "bitcasts" (func $bitcasts)) + (export "bitcasts" (func $legalstub$bitcasts)) (export "recursiveBlockMerging" (func $recursiveBlockMerging)) (export "lb" (func $lb)) (export "zeroInit" (func $zeroInit)) @@ -80,6 +80,7 @@ (export "store_fround" (func $store_fround)) (export "exportedNumber" (global $exportedNumber)) (export "relocatableAndModules" (func $relocatableAndModules)) + (export "exported_f32_user" (func $legalstub$exported_f32_user)) (func $big_negative (local $temp f64) (set_local $temp @@ -1824,10 +1825,49 @@ ) ) ) + (func $exported_f32_user (param $x i32) (param $y f32) (param $z f64) (result f32) + (return + (get_local $y) + ) + ) (func $v (nop) ) (func $vi (param $x i32) (nop) ) + (func $legalstub$fr (param $0 f64) + (call $fr + (f32.demote/f64 + (get_local $0) + ) + ) + ) + (func $legalstub$ceiling_32_64 (param $0 f64) (param $1 f64) + (call $ceiling_32_64 + (f32.demote/f64 + (get_local $0) + ) + (get_local $1) + ) + ) + (func $legalstub$bitcasts (param $0 i32) (param $1 f64) + (call $bitcasts + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + ) + ) + (func $legalstub$exported_f32_user (param $0 i32) (param $1 f64) (param $2 f64) (result f64) + (f64.promote/f32 + (call $exported_f32_user + (get_local $0) + (f32.demote/f64 + (get_local $1) + ) + (get_local $2) + ) + ) + ) ) |