Chromium Code Reviews| Index: src/wasm/ast-decoder.cc | 
| diff --git a/src/wasm/ast-decoder.cc b/src/wasm/ast-decoder.cc | 
| index 3721ff1cfaf4d07f46d171ddd8369314ad622bce..a03bf28cd80f95324943076d991677bccf350456 100644 | 
| --- a/src/wasm/ast-decoder.cc | 
| +++ b/src/wasm/ast-decoder.cc | 
| @@ -5,6 +5,7 @@ | 
| #include "src/base/platform/elapsed-timer.h" | 
| #include "src/signature.h" | 
| +#include "src/bit-vector.h" | 
| #include "src/flags.h" | 
| #include "src/handles.h" | 
| #include "src/zone-containers.h" | 
| @@ -97,13 +98,111 @@ struct IfEnv { | 
| #define BUILD0(func) (build() ? builder_->func() : nullptr) | 
| +// Generic Wasm bytecode decoder with utilities for decoding operands, | 
| +// lengths, etc. | 
| +class WasmDecoder : public Decoder { | 
| + public: | 
| + WasmDecoder() : Decoder(nullptr, nullptr), function_env_(nullptr) {} | 
| 
 
bradnelson
2016/01/22 06:28:33
Are you planning to pull this out into it's own fi
 
 | 
| + | 
| + protected: | 
| + FunctionEnv* function_env_; | 
| + | 
| + void Reset(FunctionEnv* function_env, const byte* start, const byte* end) { | 
| + Decoder::Reset(start, end); | 
| + function_env_ = function_env; | 
| + } | 
| + | 
| + // Load an operand at [pc + 1]. | 
| + template <typename V> | 
| + V Operand(const byte* pc) { | 
| + if ((limit_ - pc) < static_cast<int>(1 + sizeof(V))) { | 
| + const char* msg = "Expected operand following opcode"; | 
| + switch (sizeof(V)) { | 
| + case 1: | 
| + msg = "Expected 1-byte operand following opcode"; | 
| + break; | 
| + case 2: | 
| + msg = "Expected 2-byte operand following opcode"; | 
| + break; | 
| + case 4: | 
| + msg = "Expected 4-byte operand following opcode"; | 
| + break; | 
| + default: | 
| + break; | 
| + } | 
| + error(pc, msg); | 
| + return -1; | 
| + } | 
| + return *reinterpret_cast<const V*>(pc + 1); | 
| + } | 
| + | 
| + LocalType LocalOperand(const byte* pc, uint32_t* index, int* length) { | 
| + *index = UnsignedLEB128Operand(pc, length); | 
| + if (function_env_->IsValidLocal(*index)) { | 
| + return function_env_->GetLocalType(*index); | 
| + } | 
| + error(pc, "invalid local variable index"); | 
| + return kAstStmt; | 
| + } | 
| + | 
| + LocalType GlobalOperand(const byte* pc, uint32_t* index, int* length) { | 
| + *index = UnsignedLEB128Operand(pc, length); | 
| + if (function_env_->module->IsValidGlobal(*index)) { | 
| + return WasmOpcodes::LocalTypeFor( | 
| + function_env_->module->GetGlobalType(*index)); | 
| + } | 
| + error(pc, "invalid global variable index"); | 
| + return kAstStmt; | 
| + } | 
| + | 
| + FunctionSig* FunctionSigOperand(const byte* pc, uint32_t* index, | 
| + int* length) { | 
| + *index = UnsignedLEB128Operand(pc, length); | 
| + if (function_env_->module->IsValidFunction(*index)) { | 
| + return function_env_->module->GetFunctionSignature(*index); | 
| + } | 
| + error(pc, "invalid function index"); | 
| + return nullptr; | 
| + } | 
| + | 
| + FunctionSig* SigOperand(const byte* pc, uint32_t* index, int* length) { | 
| + *index = UnsignedLEB128Operand(pc, length); | 
| + if (function_env_->module->IsValidSignature(*index)) { | 
| + return function_env_->module->GetSignature(*index); | 
| + } | 
| + error(pc, "invalid signature index"); | 
| + return nullptr; | 
| + } | 
| + | 
| + uint32_t UnsignedLEB128Operand(const byte* pc, int* length) { | 
| + uint32_t result = 0; | 
| + ReadUnsignedLEB128ErrorCode error_code = | 
| + ReadUnsignedLEB128Operand(pc + 1, limit_, length, &result); | 
| + if (error_code == kInvalidLEB128) error(pc, "invalid LEB128 varint"); | 
| + if (error_code == kMissingLEB128) error(pc, "expected LEB128 varint"); | 
| + (*length)++; | 
| + return result; | 
| + } | 
| + | 
| + void MemoryAccessOperand(const byte* pc, int* length, uint32_t* offset) { | 
| + byte bitfield = Operand<uint8_t>(pc); | 
| + if (MemoryAccess::OffsetField::decode(bitfield)) { | 
| + *offset = UnsignedLEB128Operand(pc + 1, length); | 
| + (*length)++; // to account for the memory access byte | 
| + } else { | 
| + *offset = 0; | 
| + *length = 2; | 
| + } | 
| + } | 
| +}; | 
| + | 
| + | 
| // A shift-reduce-parser strategy for decoding Wasm code that uses an explicit | 
| // shift-reduce strategy with multiple internal stacks. | 
| -class LR_WasmDecoder : public Decoder { | 
| +class LR_WasmDecoder : public WasmDecoder { | 
| public: | 
| LR_WasmDecoder(Zone* zone, TFBuilder* builder) | 
| - : Decoder(nullptr, nullptr), | 
| - zone_(zone), | 
| + : zone_(zone), | 
| builder_(builder), | 
| trees_(zone), | 
| stack_(zone), | 
| @@ -127,8 +226,7 @@ class LR_WasmDecoder : public Decoder { | 
| } | 
| base_ = base; | 
| - Reset(pc, end); | 
| - function_env_ = function_env; | 
| + Reset(function_env, pc, end); | 
| InitSsaEnv(); | 
| DecodeFunctionBody(); | 
| @@ -177,7 +275,6 @@ class LR_WasmDecoder : public Decoder { | 
| TreeResult result_; | 
| SsaEnv* ssa_env_; | 
| - FunctionEnv* function_env_; | 
| ZoneVector<Tree*> trees_; | 
| ZoneVector<Production> stack_; | 
| @@ -1291,94 +1388,11 @@ class LR_WasmDecoder : public Decoder { | 
| return result; | 
| } | 
| - // Load an operand at [pc + 1]. | 
| - template <typename V> | 
| - V Operand(const byte* pc) { | 
| - if ((limit_ - pc) < static_cast<int>(1 + sizeof(V))) { | 
| - const char* msg = "Expected operand following opcode"; | 
| - switch (sizeof(V)) { | 
| - case 1: | 
| - msg = "Expected 1-byte operand following opcode"; | 
| - break; | 
| - case 2: | 
| - msg = "Expected 2-byte operand following opcode"; | 
| - break; | 
| - case 4: | 
| - msg = "Expected 4-byte operand following opcode"; | 
| - break; | 
| - default: | 
| - break; | 
| - } | 
| - error(pc, msg); | 
| - return -1; | 
| - } | 
| - return *reinterpret_cast<const V*>(pc + 1); | 
| - } | 
| - | 
| int EnvironmentCount() { | 
| if (builder_) return static_cast<int>(function_env_->GetLocalCount()); | 
| return 0; // if we aren't building a graph, don't bother with SSA renaming. | 
| } | 
| - LocalType LocalOperand(const byte* pc, uint32_t* index, int* length) { | 
| - *index = UnsignedLEB128Operand(pc, length); | 
| - if (function_env_->IsValidLocal(*index)) { | 
| - return function_env_->GetLocalType(*index); | 
| - } | 
| - error(pc, "invalid local variable index"); | 
| - return kAstStmt; | 
| - } | 
| - | 
| - LocalType GlobalOperand(const byte* pc, uint32_t* index, int* length) { | 
| - *index = UnsignedLEB128Operand(pc, length); | 
| - if (function_env_->module->IsValidGlobal(*index)) { | 
| - return WasmOpcodes::LocalTypeFor( | 
| - function_env_->module->GetGlobalType(*index)); | 
| - } | 
| - error(pc, "invalid global variable index"); | 
| - return kAstStmt; | 
| - } | 
| - | 
| - FunctionSig* FunctionSigOperand(const byte* pc, uint32_t* index, | 
| - int* length) { | 
| - *index = UnsignedLEB128Operand(pc, length); | 
| - if (function_env_->module->IsValidFunction(*index)) { | 
| - return function_env_->module->GetFunctionSignature(*index); | 
| - } | 
| - error(pc, "invalid function index"); | 
| - return nullptr; | 
| - } | 
| - | 
| - FunctionSig* SigOperand(const byte* pc, uint32_t* index, int* length) { | 
| - *index = UnsignedLEB128Operand(pc, length); | 
| - if (function_env_->module->IsValidSignature(*index)) { | 
| - return function_env_->module->GetSignature(*index); | 
| - } | 
| - error(pc, "invalid signature index"); | 
| - return nullptr; | 
| - } | 
| - | 
| - uint32_t UnsignedLEB128Operand(const byte* pc, int* length) { | 
| - uint32_t result = 0; | 
| - ReadUnsignedLEB128ErrorCode error_code = | 
| - ReadUnsignedLEB128Operand(pc + 1, limit_, length, &result); | 
| - if (error_code == kInvalidLEB128) error(pc, "invalid LEB128 varint"); | 
| - if (error_code == kMissingLEB128) error(pc, "expected LEB128 varint"); | 
| - (*length)++; | 
| - return result; | 
| - } | 
| - | 
| - void MemoryAccessOperand(const byte* pc, int* length, uint32_t* offset) { | 
| - byte bitfield = Operand<uint8_t>(pc); | 
| - if (MemoryAccess::OffsetField::decode(bitfield)) { | 
| - *offset = UnsignedLEB128Operand(pc + 1, length); | 
| - (*length)++; // to account for the memory access byte | 
| - } else { | 
| - *offset = 0; | 
| - *length = 2; | 
| - } | 
| - } | 
| - | 
| virtual void onFirstError() { | 
| limit_ = start_; // Terminate decoding loop. | 
| builder_ = nullptr; // Don't build any more nodes. | 
| @@ -1476,6 +1490,7 @@ ReadUnsignedLEB128ErrorCode ReadUnsignedLEB128Operand(const byte* pc, | 
| } | 
| +// TODO(titzer): move this into WasmDecoder and bounds check accesses. | 
| int OpcodeLength(const byte* pc) { | 
| switch (static_cast<WasmOpcode>(*pc)) { | 
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: | 
| @@ -1527,6 +1542,7 @@ int OpcodeLength(const byte* pc) { | 
| } | 
| +// TODO(titzer): move this into WasmDecoder and bounds check accesses. | 
| int OpcodeArity(FunctionEnv* env, const byte* pc) { | 
| #define DECLARE_ARITY(name, ...) \ | 
| static const LocalType kTypes_##name[] = {__VA_ARGS__}; \ | 
| @@ -1595,6 +1611,7 @@ int OpcodeArity(FunctionEnv* env, const byte* pc) { | 
| } | 
| + | 
| void PrintAst(FunctionEnv* env, const byte* start, const byte* end) { | 
| const byte* pc = start; | 
| std::vector<int> arity_stack; | 
| @@ -1624,6 +1641,65 @@ void PrintAst(FunctionEnv* env, const byte* start, const byte* end) { | 
| } | 
| } | 
| } | 
| + | 
| + | 
| +// Analyzes loop bodies for static assignments to locals, which helps in | 
| +// reducing the number of phis introduced at loop headers. | 
| +class LoopAssignmentAnalyzer : public WasmDecoder { | 
| + public: | 
| + LoopAssignmentAnalyzer(Zone* zone, FunctionEnv* function_env) : zone_(zone) { | 
| + function_env_ = function_env; | 
| + } | 
| + | 
| + BitVector* Analyze(const byte* pc, const byte* limit) { | 
| + Decoder::Reset(pc, limit); | 
| + if (pc_ >= limit_) return nullptr; | 
| + if (*pc_ != kExprLoop) return nullptr; | 
| + | 
| + BitVector* assigned = | 
| + new (zone_) BitVector(function_env_->total_locals, zone_); | 
| + // Keep a stack to model the nesting of expressions. | 
| + std::vector<int> arity_stack; | 
| + arity_stack.push_back(OpcodeArity(function_env_, pc_)); | 
| + pc_ += OpcodeLength(pc_); | 
| + | 
| + // Iteratively process all AST nodes nested inside the loop. | 
| + while (pc_ < limit_) { | 
| + WasmOpcode opcode = static_cast<WasmOpcode>(*pc_); | 
| + int arity = 0; | 
| + int length = 1; | 
| + if (opcode == kExprSetLocal) { | 
| + uint32_t index; | 
| + LocalOperand(pc_, &index, &length); | 
| + if (index < assigned->length()) assigned->Add(index); | 
| 
 
ahaas
2016/01/22 06:53:26
How could index >= assigned-length() even be possi
 
titzer
2016/01/23 16:20:37
This is because I plan to use it while decoding, w
 
 | 
| + arity = 1; | 
| 
 
bradnelson
2016/01/22 06:28:33
Why bake in the arity again?
Pull the else block o
 
titzer
2016/01/23 16:20:37
Ah, just a micro-optimization.
 
 | 
| + } else { | 
| + arity = OpcodeArity(function_env_, pc_); | 
| + length = OpcodeLength(pc_); | 
| + } | 
| + | 
| + pc_ += length; | 
| + arity_stack.push_back(arity); | 
| + while (arity_stack.back() == 0) { | 
| + arity_stack.pop_back(); | 
| + if (arity_stack.empty()) return assigned; // reached end of loop | 
| + arity_stack.back()--; | 
| + } | 
| + } | 
| + return assigned; | 
| 
 
bradnelson
2016/01/22 06:28:33
In a well formed program, shouldn't we always hit
 
titzer
2016/01/23 16:20:37
As mentioned above, I'm assuming not assuming the
 
 | 
| + } | 
| + | 
| + private: | 
| + Zone* zone_; | 
| +}; | 
| + | 
| + | 
| +BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, FunctionEnv* env, | 
| + const byte* start, const byte* end) { | 
| + LoopAssignmentAnalyzer analyzer(zone, env); | 
| + return analyzer.Analyze(start, end); | 
| +} | 
| + | 
| } // namespace wasm | 
| } // namespace internal | 
| } // namespace v8 |