From 6660ba7942324a10cecda7f223a6fc4f110db6fb Mon Sep 17 00:00:00 2001 From: Adam Laszlo Kulcsar Date: Mon, 8 Jan 2024 12:39:06 +0100 Subject: [PATCH] [WIP] Implement live variable analysis Signed-off-by: Adam Laszlo Kulcsar --- src/interpreter/ByteCode.h | 19 +- src/parser/WASMParser.cpp | 401 +++++++++++++++++++++++++++++++ test/basic/local_livelyness.wast | 94 ++++++++ test/basic/local_sets.wast | 109 +++++++++ test/basic/useless_locals.wast | 8 + 5 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 test/basic/local_livelyness.wast create mode 100644 test/basic/local_sets.wast create mode 100644 test/basic/useless_locals.wast diff --git a/src/interpreter/ByteCode.h b/src/interpreter/ByteCode.h index c18cbe9bf..72b3e9fca 100644 --- a/src/interpreter/ByteCode.h +++ b/src/interpreter/ByteCode.h @@ -663,6 +663,7 @@ class BinaryOperation : public ByteCode { const ByteCodeStackOffset* srcOffset() const { return m_srcOffset; } ByteCodeStackOffset dstOffset() const { return m_dstOffset; } void setDstOffset(ByteCodeStackOffset o) { m_dstOffset = o; } + void setSrcOffset(ByteCodeStackOffset o, size_t index) { m_srcOffset[index] = o; } #if !defined(NDEBUG) void dump(size_t pos) { @@ -705,6 +706,14 @@ class UnaryOperation : public ByteCode { } ByteCodeStackOffset srcOffset() const { return m_srcOffset; } ByteCodeStackOffset dstOffset() const { return m_dstOffset; } + void setDstOffset(ByteCodeStackOffset newOffset) + { + m_dstOffset = newOffset; + } + void setSrcOffset(ByteCodeStackOffset newOffset) + { + m_srcOffset = newOffset; + } #if !defined(NDEBUG) void dump(size_t pos) { @@ -874,6 +883,14 @@ class Move32 : public ByteCode { ByteCodeStackOffset srcOffset() const { return m_srcOffset; } ByteCodeStackOffset dstOffset() const { return m_dstOffset; } + void setDstOffset(ByteCodeStackOffset newOffset) + { + m_dstOffset = newOffset; + } + void setSrcOffset(ByteCodeStackOffset newOffset) + { + m_srcOffset = newOffset; + } #if !defined(NDEBUG) void dump(size_t pos) @@ -2255,7 +2272,7 @@ class End : public ByteCode { { } - ByteCodeStackOffset* resultOffsets() const + ByteCodeStackOffset* resultOffsets() { return reinterpret_cast(reinterpret_cast(this) + sizeof(End)); } diff --git a/src/parser/WASMParser.cpp b/src/parser/WASMParser.cpp index 048ed5f3f..ebd968983 100644 --- a/src/parser/WASMParser.cpp +++ b/src/parser/WASMParser.cpp @@ -20,10 +20,46 @@ #include "runtime/Store.h" #include "runtime/Module.h" +#include "runtime/Value.h" #include "wabt/walrus/binary-reader-walrus.h" +#include +#include namespace wabt { +bool isWalrusBinaryOperation(Walrus::ByteCode::Opcode opcode) +{ + switch (opcode) { +#define GENERATE_BINARY_CODE_CASE(name, ...) \ + case Walrus::ByteCode::name##Opcode: + FOR_EACH_BYTECODE_BINARY_OP(GENERATE_BINARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_BINARY_OP(GENERATE_BINARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_BINARY_SHIFT_OP(GENERATE_BINARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_BINARY_OTHER(GENERATE_BINARY_CODE_CASE) +#undef GENERATE_BINARY_CODE_CASE + return true; + default: + return false; + } +} + +bool isWalrusUnaryOperation(Walrus::ByteCode::Opcode opcode) +{ + switch (opcode) { +#define GENERATE_UNARY_CODE_CASE(name, ...) \ + case Walrus::ByteCode::name##Opcode: + FOR_EACH_BYTECODE_UNARY_OP(GENERATE_UNARY_CODE_CASE) + FOR_EACH_BYTECODE_UNARY_OP_2(GENERATE_UNARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_UNARY_OP(GENERATE_UNARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_UNARY_CONVERT_OP(GENERATE_UNARY_CODE_CASE) + FOR_EACH_BYTECODE_SIMD_UNARY_OTHER(GENERATE_UNARY_CODE_CASE) +#undef GENERATE_UNARY_CODE_CASE + return true; + default: + return false; + } +} + enum class WASMOpcode : size_t { #define WABT_OPCODE(rtype, type1, type2, type3, memSize, prefix, code, name, \ text, decomp) \ @@ -2200,6 +2236,371 @@ class WASMBinaryReader : public wabt::WASMBinaryReaderDelegate { } } else { generateEndCode(true); + + if (!m_preprocessData.m_inPreprocess) { + // variable life analysis start + std::vector> variableRange; + std::vector> blocks; + Walrus::End* end = nullptr; + + variableRange.reserve(m_localInfo.size()); + + for (unsigned i = 0; i != variableRange.capacity(); i++) { + variableRange.push_back({ UINT16_MAX, 0 }); + } + + Walrus::Vector params; + size_t offset = 0; + + for (auto& type : m_currentFunctionType->param()) { + params.push_back(LocalInfo{ type, offset }); + offset += Walrus::valueStackAllocatedSize(type); + } + + for (size_t i = 0; i < params.size(); i++) { + variableRange[i].first = 0; + } + + for (size_t i = 0; i < m_currentFunction->currentByteCodeSize();) { + Walrus::ByteCode* byteCode = reinterpret_cast(const_cast(m_currentFunction->byteCode() + i)); + + int32_t jumpOffset = INT32_MAX; + Walrus::ByteCodeStackOffset opOffset = UINT16_MAX; + Walrus::ByteCodeStackOffset srcOffset1 = UINT16_MAX; + Walrus::ByteCodeStackOffset srcOffset2 = UINT16_MAX; + if (isWalrusBinaryOperation(byteCode->opcode())) { + opOffset = reinterpret_cast(byteCode)->dstOffset(); + srcOffset1 = reinterpret_cast(byteCode)->srcOffset()[0]; + srcOffset2 = reinterpret_cast(byteCode)->srcOffset()[1]; + } else if (isWalrusUnaryOperation(byteCode->opcode())) { + opOffset = reinterpret_cast(byteCode)->dstOffset(); + srcOffset1 = reinterpret_cast(byteCode)->srcOffset(); + } else if (byteCode->opcode() == Walrus::ByteCode::Move32Opcode + || byteCode->opcode() == Walrus::ByteCode::Move64Opcode + || byteCode->opcode() == Walrus::ByteCode::Move128Opcode) { + opOffset = reinterpret_cast(byteCode)->dstOffset(); + srcOffset1 = reinterpret_cast(byteCode)->srcOffset(); + } else if (byteCode->opcode() == Walrus::ByteCode::JumpOpcode) { + jumpOffset = reinterpret_cast(byteCode)->offset(); + } else if (byteCode->opcode() == Walrus::ByteCode::JumpIfTrueOpcode + || byteCode->opcode() == Walrus::ByteCode::JumpIfFalseOpcode) { + jumpOffset = reinterpret_cast(byteCode)->offset(); + } else { + i += byteCode->getSize(); + continue; + } + + for (size_t j = 0; j < m_localInfo.size(); j++) { + if (m_localInfo[j].m_position == opOffset + || m_localInfo[j].m_position == srcOffset1 + || m_localInfo[j].m_position == srcOffset2) { + if (variableRange[j].first > i) { + variableRange[j].first = i; + } + if (variableRange[j].second < i) { + variableRange[j].second = i; + } + } + } + + if (jumpOffset != INT32_MAX) { + blocks.push_back( + std::pair( + std::min(i + byteCode->getSize(), i + byteCode->getSize() + jumpOffset), + std::max(i + byteCode->getSize(), i + byteCode->getSize() + jumpOffset))); + } + + i += byteCode->getSize(); + } + + std::sort(blocks.begin(), blocks.end(), [](const std::pair& left, const std::pair& right) { + return left.first < right.first; + }); + + + for (auto& elem : variableRange) { + for (size_t i = elem.second; i < m_currentFunction->currentByteCodeSize();) { + Walrus::ByteCode* byteCode = reinterpret_cast(const_cast(m_currentFunction->byteCode() + i)); + + bool skip = false; + for (auto& block : blocks) { + if (block.first == i) { + if (elem.second < block.second) { + elem.second = block.second; + skip = true; + } + } + } + + + i -= byteCode->getSize(); + if (skip) { + i = m_currentFunction->currentByteCodeSize(); + } + } + } + + unsigned overlapping32 = 0; + unsigned overlapping64 = 0; + unsigned overlapping128 = 0; + + std::vector containingLives(variableRange.size()); + for (auto& num : containingLives) { + num = 1; + } + + for (size_t j = 0; j < variableRange.size(); j++) { + for (size_t k = 0; k < variableRange.size(); k++) { + if (variableRange[j] == variableRange[k] || m_localInfo[j].m_valueType != m_localInfo[k].m_valueType) { + continue; + } + if (variableRange[j].first < variableRange[k].first && variableRange[j].second >= variableRange[k].second) { + containingLives[j]++; + } + } + } + + for (size_t j = 0; j < containingLives.capacity(); j++) { + switch (m_localInfo[j].m_valueType) { + case Walrus::Value::I32: + case Walrus::Value::F32: { + if (overlapping32 < containingLives[j]) { + overlapping32 = containingLives[j]; + } + break; + } + case Walrus::Value::F64: + case Walrus::Value::I64: { + if (overlapping64 < containingLives[j]) { + overlapping64 = containingLives[j]; + } + break; + } + case Walrus::Value::V128: { + if (overlapping128 < containingLives[j]) { + overlapping128 = containingLives[j]; + } + break; + } + + default: + break; + } + } + + unsigned numberOf32 = 0; + unsigned numberOf64 = 0; + unsigned numberOf128 = 0; + for (auto& elem : m_localInfo) { + switch (elem.m_valueType) { + case Walrus::Value::F32: + case Walrus::Value::I32: { + numberOf32++; + break; + } + case Walrus::Value::F64: + case Walrus::Value::I64: { + numberOf64++; + break; + } + case Walrus::Value::V128: { + numberOf128++; + break; + } + default: + break; + } + } + + struct variableInfo { + Walrus::Value::Type type; + Walrus::ByteCodeStackOffset pos; + Walrus::ByteCodeStackOffset originalPos; + Walrus::ByteCodeStackOffset end; + bool free; + }; + + Walrus::Vector infos; + for (int i = overlapping32 > 0 ? overlapping32 : numberOf32; i > 0; i--) { + variableInfo var; + var.free = true; + var.type = Walrus::Value::I32; + var.pos = offset; + var.originalPos = UINT16_MAX; + infos.push_back(var); + offset += Walrus::valueStackAllocatedSize(var.type); + } + + for (int i = overlapping64 > 0 ? overlapping64 : numberOf64; i > 0; i--) { + variableInfo var; + var.free = true; + var.type = Walrus::Value::I64; + var.pos = offset; + var.originalPos = UINT16_MAX; + infos.push_back(var); + offset += Walrus::valueStackAllocatedSize(var.type); + } + + for (int i = overlapping128 > 0 ? overlapping128 : numberOf128; i > 0; i--) { + variableInfo var; + var.free = true; + var.type = Walrus::Value::V128; + var.pos = offset; + var.originalPos = UINT16_MAX; + infos.push_back(var); + offset += Walrus::valueStackAllocatedSize(var.type); + } + + Walrus::Vector constants; + for (auto& constant : m_preprocessData.m_constantData) { + constants.push_back(LocalInfo{ constant.first.type(), constant.second }); + offset += Walrus::valueStackAllocatedSize(constant.first.type()); + } + + + for (size_t i = 0; i < m_currentFunction->currentByteCodeSize();) { + Walrus::ByteCode* byteCode = reinterpret_cast(const_cast(m_currentFunction->byteCode() + i)); + + for (auto& info : infos) { + if (info.end <= i && !info.free) { + info.free = true; + info.originalPos = UINT16_MAX; + info.end = 0; + } + } + + Walrus::ByteCodeStackOffset srcOffset1 = UINT16_MAX; + Walrus::ByteCodeStackOffset srcOffset2 = UINT16_MAX; + Walrus::ByteCodeStackOffset dstOffset = UINT16_MAX; + std::vector resultOffsets; + if (byteCode->opcode() == Walrus::ByteCode::Move32Opcode + || byteCode->opcode() == Walrus::ByteCode::Move64Opcode + || byteCode->opcode() == Walrus::ByteCode::Move128Opcode) { + Walrus::Move32* move = reinterpret_cast(byteCode); + dstOffset = move->dstOffset(); + srcOffset1 = move->srcOffset(); + } + if (isWalrusBinaryOperation(byteCode->opcode())) { + Walrus::BinaryOperation* binOp = reinterpret_cast(byteCode); + dstOffset = binOp->dstOffset(); + srcOffset1 = binOp->srcOffset()[0]; + srcOffset2 = binOp->srcOffset()[1]; + } + if (isWalrusUnaryOperation(byteCode->opcode())) { + Walrus::UnaryOperation* unOp = reinterpret_cast(byteCode); + dstOffset = unOp->dstOffset(); + srcOffset1 = unOp->srcOffset(); + } + if (byteCode->opcode() == Walrus::ByteCode::EndOpcode) { + end = reinterpret_cast(byteCode); + } + + if (dstOffset == UINT16_MAX && srcOffset1 == UINT16_MAX && srcOffset2 == UINT16_MAX) { + i += byteCode->getSize(); + continue; + } + + bool isLocal = false; + for (auto& localInfo : m_localInfo) { + if (dstOffset == localInfo.m_position) { + isLocal = true; + } + } + + if (!isLocal) { + i += byteCode->getSize(); + continue; + } + + for (auto& param : params) { + if (dstOffset == param.m_position) { + dstOffset = UINT16_MAX; + } + if (srcOffset1 == param.m_position) { + srcOffset1 = UINT16_MAX; + } + if (srcOffset2 == param.m_position) { + srcOffset2 = UINT16_MAX; + } + } + + for (auto& range : variableRange) { + if (range.first <= i && range.second >= i) { + bool skip = false; + for (auto& info : infos) { + if (info.originalPos == dstOffset) { + skip = true; + } + } + + for (size_t k = 0; k < infos.size() && !skip; k++) { + if (infos[k].free) { + infos[k].free = false; + infos[k].originalPos = dstOffset; + infos[k].end = range.second; + k = infos.size(); + } + } + } + } + + for (size_t j = 0; j < infos.size(); j++) { + if (infos[j].originalPos == dstOffset && dstOffset != UINT16_MAX) { + if (byteCode->opcode() == Walrus::ByteCode::Move32Opcode + || byteCode->opcode() == Walrus::ByteCode::Move64Opcode + || byteCode->opcode() == Walrus::ByteCode::Move128Opcode) { + Walrus::Move32* move = reinterpret_cast(byteCode); + move->setDstOffset(infos[j].pos); + } + if (isWalrusBinaryOperation(byteCode->opcode())) { + Walrus::BinaryOperation* binOp = reinterpret_cast(byteCode); + binOp->setDstOffset(infos[j].pos); + } + if (isWalrusUnaryOperation(byteCode->opcode())) { + Walrus::UnaryOperation* unOp = reinterpret_cast(byteCode); + unOp->setDstOffset(infos[j].pos); + } + } + + if (infos[j].originalPos == srcOffset1 && srcOffset1 != UINT16_MAX) { + if (byteCode->opcode() == Walrus::ByteCode::Move32Opcode + || byteCode->opcode() == Walrus::ByteCode::Move64Opcode + || byteCode->opcode() == Walrus::ByteCode::Move128Opcode) { + Walrus::Move32* move = reinterpret_cast(byteCode); + move->setSrcOffset(infos[j].pos); + } + if (isWalrusBinaryOperation(byteCode->opcode())) { + Walrus::BinaryOperation* binOp = reinterpret_cast(byteCode); + binOp->setSrcOffset(infos[j].pos, 0); + } + if (isWalrusUnaryOperation(byteCode->opcode())) { + Walrus::UnaryOperation* unOp = reinterpret_cast(byteCode); + unOp->setSrcOffset(infos[j].pos); + } + } + + if (infos[j].originalPos == srcOffset2 && srcOffset2 != UINT16_MAX) { + if (isWalrusBinaryOperation(byteCode->opcode())) { + Walrus::BinaryOperation* binOp = reinterpret_cast(byteCode); + binOp->setSrcOffset(infos[j].pos, 1); + } + } + } + + i += byteCode->getSize(); + } + + // for (auto& info : infos) { + // if (info.originalPos == UINT16_MAX) { + // offset -= Walrus::valueStackAllocatedSize(info.type); + // } + // } + + m_currentFunction->m_requiredStackSize = offset; + + // variable life analysis end + } } } diff --git a/test/basic/local_livelyness.wast b/test/basic/local_livelyness.wast new file mode 100644 index 000000000..6f656ed8a --- /dev/null +++ b/test/basic/local_livelyness.wast @@ -0,0 +1,94 @@ +(module + (func $local_zero (export "local_zero")(result i32) + (local i32) + local.get 0 + ) + + (func $local_loop1 (export "local_loop1")(result i32) + (local i32 i32 i32) + i32.const 10 + local.set 0 ;;start of 0 + + ;;start of 1 + (loop $loop + i32.const 1 + local.set 1 ;;start of 1, but inside loop + + local.get 0 + i32.const 1 + i32.sub + local.tee 0 + i32.eqz + br_if $loop + ) + + local.get 1 ;;end of 1 + ) + + (func $local_blocks (export "local_block1")(result i32) + (local i32 i32 i32 i32 i64) + + ;;end of 2 + + local.get 4 ;; start of 4 + local.get 3 ;; start of 3 + drop + drop + + i32.const 0 + local.set 0 ;; start of 0 + + + (block $block1 + i32.const 1 + local.get 0 + i32.add + local.set 0 + + (loop $block2 + local.get 1 ;; start of 1 + i32.const 3 + i32.eq + br_if $block2 + ) + + i32.const 0 + local.get 1 + i32.add + drop + ) ;; end of 1 + + ;; end of 3, 4 + i32.const 0 + ) + + (func $local_blocks2 (export "local_block2")(param i32)(result i32) + (local i32) + + i32.const 1 + i32.const 1 + i32.sub + drop + + local.get 0 + local.tee 1 + ) + + (func $params (export "params")(param i32 i64 i32 v128)(result i32) + (local i32 i64 v128) + i32.const 0 + ) + + (func $params2 (export "params2")(param v128 i32 v128)(result i32) + i32.const 0 + ) + + +) + +(assert_return (invoke "local_zero") (i32.const 0)) +(assert_return (invoke "local_loop1") (i32.const 1)) +(assert_return (invoke "local_block1") (i32.const 0)) +(assert_return (invoke "local_block2" (i32.const 42)) (i32.const 42)) +(assert_return (invoke "params" (i32.const 1) (i64.const 2) (i32.const 3) (v128.const i64x2 4 5)) (i32.const 0)) +(assert_return (invoke "params2" (v128.const i64x2 1 2) (i32.const 3) (v128.const i64x2 4 5)) (i32.const 0)) diff --git a/test/basic/local_sets.wast b/test/basic/local_sets.wast new file mode 100644 index 000000000..88669ac99 --- /dev/null +++ b/test/basic/local_sets.wast @@ -0,0 +1,109 @@ +(module + (func $test (export "test")(param i32)(result i32) + (local i32 i32 i32 i64) + + local.get 0 ;; start of 0 + local.get 1 ;; start of 1 + drop + drop + + i32.const 32 + local.set 0 + + i32.const 33 + local.set 1 + + local.get 0 + local.get 1 ;; end of 1 + drop + drop + + i32.const 34 + local.set 0 ;; end of 0 + + + i32.const 1 + local.set 2 ;; start of 2 + local.get 2 ;; end of 2 + drop + + i64.const 23 + local.set 4 + local.get 4 + drop + + i32.const 0 + ) + + + (func $test2 (export "test2")(result i32) + (local i32 i32 i32 i32 i32) + + i32.const 10 + local.set 0 + (loop $outer ;; runs 10 times + + i32.const 5 + local.set 1 + (loop $inner1 ;; runs 5 times + i32.const 42 + local.set 2 + local.get 2 + drop + + local.get 1 + i32.const 1 + i32.sub + local.tee 1 + + i32.const 0 + i32.eq + br_if $inner1 + ) + + i32.const 8 + local.set 3 + (loop $inner2 ;; runs 8 times + local.get 3 + i32.const 1 + i32.sub + local.tee 3 + + i32.const 0 + i32.eq + br_if $inner2 + ) + + local.get 0 + i32.const 1 + i32.sub + local.tee 0 + + i32.const 0 + i32.eq + br_if $outer + ) + + (block $block + i32.const 99999 + local.set 4 + + i32.const 0 + i32.eqz + br_if $block + + ;;junk + i32.const 1 + i32.const 0 + i32.add + drop + ) + + i32.const 0 + ) +) + + + +(assert_return (invoke "test" (i32.const 12))(i32.const 0)) +(assert_return (invoke "test2")(i32.const 0)) diff --git a/test/basic/useless_locals.wast b/test/basic/useless_locals.wast new file mode 100644 index 000000000..a96b64083 --- /dev/null +++ b/test/basic/useless_locals.wast @@ -0,0 +1,8 @@ +(module + (func $useless_locals (export "useless_locals")(param i32)(result i32) + (local i32 i32) + i32.const 42 + ) +) + +(assert_return (invoke "useless_locals" (i32.const 222)) (i32.const 42))