blob: 57ee78f91cf0d518bc923023422f7efc990040d7 [file] [log] [blame]
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/signature.h"
#include "src/base/platform/elapsed-timer.h"
#include "src/flags.h"
#include "src/handles.h"
#include "src/objects-inl.h"
#include "src/zone/zone-containers.h"
#include "src/wasm/decoder.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/ostreams.h"
#include "src/compiler/wasm-compiler.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
// An SsaEnv environment carries the current local variable renaming
// as well as the current effect and control dependency in the TF graph.
// It maintains a control state that tracks whether the environment
// is reachable, has reached a control end, or has been merged.
struct SsaEnv {
enum State { kControlEnd, kUnreachable, kReached, kMerged };
State state;
TFNode* control;
TFNode* effect;
compiler::WasmContextCacheNodes context_cache;
TFNode** locals;
bool go() { return state >= kReached; }
void Kill(State new_state = kControlEnd) {
state = new_state;
locals = nullptr;
control = nullptr;
effect = nullptr;
context_cache = {0};
}
void SetNotMerged() {
if (state == kMerged) state = kReached;
}
};
#define BUILD(func, ...) \
([&] { \
DCHECK(ssa_env_->go()); \
DCHECK(decoder->ok()); \
return CheckForException(decoder, builder_->func(__VA_ARGS__)); \
})()
constexpr uint32_t kNullCatch = static_cast<uint32_t>(-1);
class WasmGraphBuildingInterface {
public:
static constexpr wasm::Decoder::ValidateFlag validate =
wasm::Decoder::kValidate;
using Decoder = WasmFullDecoder<validate, WasmGraphBuildingInterface>;
struct Value : public ValueWithNamedConstructors<Value> {
TFNode* node;
};
struct TryInfo : public ZoneObject {
SsaEnv* catch_env;
TFNode* exception;
explicit TryInfo(SsaEnv* c) : catch_env(c), exception(nullptr) {}
};
struct Control : public ControlWithNamedConstructors<Control, Value> {
SsaEnv* end_env; // end environment for the construct.
SsaEnv* false_env; // false environment (only for if).
TryInfo* try_info; // information used for compiling try statements.
int32_t previous_catch; // previous Control (on the stack) with a catch.
};
explicit WasmGraphBuildingInterface(TFBuilder* builder) : builder_(builder) {}
void StartFunction(Decoder* decoder) {
SsaEnv* ssa_env =
reinterpret_cast<SsaEnv*>(decoder->zone()->New(sizeof(SsaEnv)));
uint32_t num_locals = decoder->NumLocals();
uint32_t env_count = num_locals;
size_t size = sizeof(TFNode*) * env_count;
ssa_env->state = SsaEnv::kReached;
ssa_env->locals =
size > 0 ? reinterpret_cast<TFNode**>(decoder->zone()->New(size))
: nullptr;
// The first '+ 1' is needed by TF Start node, the second '+ 1' is for the
// wasm_context parameter.
TFNode* start = builder_->Start(
static_cast<int>(decoder->sig_->parameter_count() + 1 + 1));
// Initialize the wasm_context (the paramater at index 0).
builder_->set_wasm_context(
builder_->Param(compiler::kWasmContextParameterIndex));
// Initialize local variables. Parameters are shifted by 1 because of the
// the wasm_context.
uint32_t index = 0;
for (; index < decoder->sig_->parameter_count(); ++index) {
ssa_env->locals[index] = builder_->Param(index + 1);
}
while (index < num_locals) {
ValueType type = decoder->GetLocalType(index);
TFNode* node = DefaultValue(type);
while (index < num_locals && decoder->GetLocalType(index) == type) {
// Do a whole run of like-typed locals at a time.
ssa_env->locals[index++] = node;
}
}
ssa_env->effect = start;
ssa_env->control = start;
// Initialize effect and control before loading the context.
builder_->set_effect_ptr(&ssa_env->effect);
builder_->set_control_ptr(&ssa_env->control);
LoadContextIntoSsa(ssa_env);
SetEnv(ssa_env);
}
// Reload the wasm context variables from the WasmContext structure attached
// to the memory object into the Ssa Environment.
void LoadContextIntoSsa(SsaEnv* ssa_env) {
if (!ssa_env || !ssa_env->go()) return;
builder_->InitContextCache(&ssa_env->context_cache);
}
void StartFunctionBody(Decoder* decoder, Control* block) {
SsaEnv* break_env = ssa_env_;
SetEnv(Steal(decoder->zone(), break_env));
block->end_env = break_env;
}
void FinishFunction(Decoder*) { builder_->PatchInStackCheckIfNeeded(); }
void OnFirstError(Decoder*) {}
void NextInstruction(Decoder*, WasmOpcode) {}
void Block(Decoder* decoder, Control* block) {
// The break environment is the outer environment.
block->end_env = ssa_env_;
SetEnv(Steal(decoder->zone(), ssa_env_));
}
void Loop(Decoder* decoder, Control* block) {
SsaEnv* finish_try_env = Steal(decoder->zone(), ssa_env_);
block->end_env = finish_try_env;
// The continue environment is the inner environment.
SetEnv(PrepareForLoop(decoder, finish_try_env));
ssa_env_->SetNotMerged();
if (!decoder->ok()) return;
// Wrap input merge into phis.
for (unsigned i = 0; i < block->start_merge.arity; ++i) {
Value& val = block->start_merge[i];
val.node = builder_->Phi(val.type, 1, &val.node, block->end_env->control);
}
}
void Try(Decoder* decoder, Control* block) {
SsaEnv* outer_env = ssa_env_;
SsaEnv* catch_env = Split(decoder, outer_env);
// Mark catch environment as unreachable, since only accessable
// through catch unwinding (i.e. landing pads).
catch_env->state = SsaEnv::kUnreachable;
SsaEnv* try_env = Steal(decoder->zone(), outer_env);
SetEnv(try_env);
TryInfo* try_info = new (decoder->zone()) TryInfo(catch_env);
block->end_env = outer_env;
block->try_info = try_info;
block->previous_catch = current_catch_;
current_catch_ = static_cast<int32_t>(decoder->control_depth() - 1);
}
void If(Decoder* decoder, const Value& cond, Control* if_block) {
TFNode* if_true = nullptr;
TFNode* if_false = nullptr;
if (ssa_env_->go()) BUILD(BranchNoHint, cond.node, &if_true, &if_false);
SsaEnv* end_env = ssa_env_;
SsaEnv* false_env = Split(decoder, ssa_env_);
false_env->control = if_false;
SsaEnv* true_env = Steal(decoder->zone(), ssa_env_);
true_env->control = if_true;
if_block->end_env = end_env;
if_block->false_env = false_env;
SetEnv(true_env);
}
void FallThruTo(Decoder* decoder, Control* c) {
DCHECK(!c->is_loop());
MergeValuesInto(decoder, c, &c->end_merge);
}
void PopControl(Decoder* decoder, Control* block) {
if (!block->is_loop()) SetEnv(block->end_env);
}
void EndControl(Decoder* decoder, Control* block) { ssa_env_->Kill(); }
void UnOp(Decoder* decoder, WasmOpcode opcode, FunctionSig* sig,
const Value& value, Value* result) {
result->node = BUILD(Unop, opcode, value.node, decoder->position());
}
void BinOp(Decoder* decoder, WasmOpcode opcode, FunctionSig* sig,
const Value& lhs, const Value& rhs, Value* result) {
auto node = BUILD(Binop, opcode, lhs.node, rhs.node, decoder->position());
if (result) result->node = node;
}
void I32Const(Decoder* decoder, Value* result, int32_t value) {
result->node = builder_->Int32Constant(value);
}
void I64Const(Decoder* decoder, Value* result, int64_t value) {
result->node = builder_->Int64Constant(value);
}
void F32Const(Decoder* decoder, Value* result, float value) {
result->node = builder_->Float32Constant(value);
}
void F64Const(Decoder* decoder, Value* result, double value) {
result->node = builder_->Float64Constant(value);
}
void Drop(Decoder* decoder, const Value& value) {}
void DoReturn(Decoder* decoder, Vector<Value> values, bool implicit) {
if (implicit) {
DCHECK_EQ(1, decoder->control_depth());
SetEnv(decoder->control_at(0)->end_env);
}
size_t num_values = values.size();
TFNode** buffer = GetNodes(values);
for (size_t i = 0; i < num_values; ++i) {
buffer[i] = values[i].node;
}
BUILD(Return, static_cast<unsigned>(values.size()), buffer);
}
void GetLocal(Decoder* decoder, Value* result,
const LocalIndexOperand<validate>& operand) {
if (!ssa_env_->locals) return; // unreachable
result->node = ssa_env_->locals[operand.index];
}
void SetLocal(Decoder* decoder, const Value& value,
const LocalIndexOperand<validate>& operand) {
if (!ssa_env_->locals) return; // unreachable
ssa_env_->locals[operand.index] = value.node;
}
void TeeLocal(Decoder* decoder, const Value& value, Value* result,
const LocalIndexOperand<validate>& operand) {
result->node = value.node;
if (!ssa_env_->locals) return; // unreachable
ssa_env_->locals[operand.index] = value.node;
}
void GetGlobal(Decoder* decoder, Value* result,
const GlobalIndexOperand<validate>& operand) {
result->node = BUILD(GetGlobal, operand.index);
}
void SetGlobal(Decoder* decoder, const Value& value,
const GlobalIndexOperand<validate>& operand) {
BUILD(SetGlobal, operand.index, value.node);
}
void Unreachable(Decoder* decoder) {
BUILD(Unreachable, decoder->position());
}
void Select(Decoder* decoder, const Value& cond, const Value& fval,
const Value& tval, Value* result) {
TFNode* controls[2];
BUILD(BranchNoHint, cond.node, &controls[0], &controls[1]);
TFNode* merge = BUILD(Merge, 2, controls);
TFNode* vals[2] = {tval.node, fval.node};
TFNode* phi = BUILD(Phi, tval.type, 2, vals, merge);
result->node = phi;
ssa_env_->control = merge;
}
void Br(Decoder* decoder, Control* target) {
MergeValuesInto(decoder, target, target->br_merge());
}
void BrIf(Decoder* decoder, const Value& cond, Control* target) {
SsaEnv* fenv = ssa_env_;
SsaEnv* tenv = Split(decoder, fenv);
fenv->SetNotMerged();
BUILD(BranchNoHint, cond.node, &tenv->control, &fenv->control);
ssa_env_ = tenv;
Br(decoder, target);
ssa_env_ = fenv;
}
void BrTable(Decoder* decoder, const BranchTableOperand<validate>& operand,
const Value& key) {
if (operand.table_count == 0) {
// Only a default target. Do the equivalent of br.
uint32_t target = BranchTableIterator<validate>(decoder, operand).next();
Br(decoder, decoder->control_at(target));
return;
}
SsaEnv* break_env = ssa_env_;
// Build branches to the various blocks based on the table.
TFNode* sw = BUILD(Switch, operand.table_count + 1, key.node);
SsaEnv* copy = Steal(decoder->zone(), break_env);
ssa_env_ = copy;
BranchTableIterator<validate> iterator(decoder, operand);
while (iterator.has_next()) {
uint32_t i = iterator.cur_index();
uint32_t target = iterator.next();
ssa_env_ = Split(decoder, copy);
ssa_env_->control = (i == operand.table_count) ? BUILD(IfDefault, sw)
: BUILD(IfValue, i, sw);
Br(decoder, decoder->control_at(target));
}
DCHECK(decoder->ok());
ssa_env_ = break_env;
}
void Else(Decoder* decoder, Control* if_block) {
SetEnv(if_block->false_env);
}
void LoadMem(Decoder* decoder, LoadType type,
const MemoryAccessOperand<validate>& operand, const Value& index,
Value* result) {
result->node =
BUILD(LoadMem, type.value_type(), type.mem_type(), index.node,
operand.offset, operand.alignment, decoder->position());
}
void StoreMem(Decoder* decoder, StoreType type,
const MemoryAccessOperand<validate>& operand,
const Value& index, const Value& value) {
BUILD(StoreMem, type.mem_rep(), index.node, operand.offset,
operand.alignment, value.node, decoder->position(),
type.value_type());
}
void CurrentMemoryPages(Decoder* decoder, Value* result) {
result->node = BUILD(CurrentMemoryPages);
}
void GrowMemory(Decoder* decoder, const Value& value, Value* result) {
result->node = BUILD(GrowMemory, value.node);
// Always reload the context cache after growing memory.
LoadContextIntoSsa(ssa_env_);
}
void CallDirect(Decoder* decoder,
const CallFunctionOperand<validate>& operand,
const Value args[], Value returns[]) {
DoCall(decoder, nullptr, operand, args, returns, false);
}
void CallIndirect(Decoder* decoder, const Value& index,
const CallIndirectOperand<validate>& operand,
const Value args[], Value returns[]) {
DoCall(decoder, index.node, operand, args, returns, true);
}
void SimdOp(Decoder* decoder, WasmOpcode opcode, Vector<Value> args,
Value* result) {
TFNode** inputs = GetNodes(args);
TFNode* node = BUILD(SimdOp, opcode, inputs);
if (result) result->node = node;
}
void SimdLaneOp(Decoder* decoder, WasmOpcode opcode,
const SimdLaneOperand<validate> operand, Vector<Value> inputs,
Value* result) {
TFNode** nodes = GetNodes(inputs);
result->node = BUILD(SimdLaneOp, opcode, operand.lane, nodes);
}
void SimdShiftOp(Decoder* decoder, WasmOpcode opcode,
const SimdShiftOperand<validate> operand, const Value& input,
Value* result) {
TFNode* inputs[] = {input.node};
result->node = BUILD(SimdShiftOp, opcode, operand.shift, inputs);
}
void Simd8x16ShuffleOp(Decoder* decoder,
const Simd8x16ShuffleOperand<validate>& operand,
const Value& input0, const Value& input1,
Value* result) {
TFNode* input_nodes[] = {input0.node, input1.node};
result->node = BUILD(Simd8x16ShuffleOp, operand.shuffle, input_nodes);
}
TFNode* GetExceptionTag(Decoder* decoder,
const ExceptionIndexOperand<validate>& operand) {
// TODO(kschimpf): Need to get runtime exception tag values. This
// code only handles non-imported/exported exceptions.
return BUILD(Int32Constant, operand.index);
}
void Throw(Decoder* decoder, const ExceptionIndexOperand<validate>& operand,
Control* block, const Vector<Value>& value_args) {
int count = value_args.length();
ZoneVector<TFNode*> args(count, decoder->zone());
for (int i = 0; i < count; ++i) {
args[i] = value_args[i].node;
}
BUILD(Throw, operand.index, operand.exception, vec2vec(args));
Unreachable(decoder);
EndControl(decoder, block);
}
void CatchException(Decoder* decoder,
const ExceptionIndexOperand<validate>& operand,
Control* block, Vector<Value> values) {
DCHECK(block->is_try_catch());
current_catch_ = block->previous_catch;
SsaEnv* catch_env = block->try_info->catch_env;
SetEnv(catch_env);
TFNode* compare_i32 = nullptr;
if (block->try_info->exception == nullptr) {
// Catch not applicable, no possible throws in the try
// block. Create dummy code so that body of catch still
// compiles. Note: This only happens because the current
// implementation only builds a landing pad if some node in the
// try block can (possibly) throw.
//
// TODO(kschimpf): Always generate a landing pad for a try block.
compare_i32 = BUILD(Int32Constant, 0);
} else {
// Get the exception and see if wanted exception.
TFNode* caught_tag = BUILD(GetExceptionRuntimeId);
TFNode* exception_tag =
BUILD(ConvertExceptionTagToRuntimeId, operand.index);
compare_i32 = BUILD(Binop, kExprI32Eq, caught_tag, exception_tag);
}
TFNode* if_catch = nullptr;
TFNode* if_no_catch = nullptr;
BUILD(BranchNoHint, compare_i32, &if_catch, &if_no_catch);
SsaEnv* if_no_catch_env = Split(decoder, ssa_env_);
if_no_catch_env->control = if_no_catch;
SsaEnv* if_catch_env = Steal(decoder->zone(), ssa_env_);
if_catch_env->control = if_catch;
// TODO(kschimpf): Generalize to allow more catches. Will force
// moving no_catch code to END opcode.
SetEnv(if_no_catch_env);
BUILD(Rethrow);
Unreachable(decoder);
EndControl(decoder, block);
SetEnv(if_catch_env);
if (block->try_info->exception == nullptr) {
// No caught value, make up filler nodes so that catch block still
// compiles.
for (Value& value : values) {
value.node = DefaultValue(value.type);
}
} else {
// TODO(kschimpf): Can't use BUILD() here, GetExceptionValues() returns
// TFNode** rather than TFNode*. Fix to add landing pads.
TFNode** caught_values = builder_->GetExceptionValues(operand.exception);
for (size_t i = 0, e = values.size(); i < e; ++i) {
values[i].node = caught_values[i];
}
}
}
void AtomicOp(Decoder* decoder, WasmOpcode opcode, Vector<Value> args,
const MemoryAccessOperand<validate>& operand, Value* result) {
TFNode** inputs = GetNodes(args);
TFNode* node = BUILD(AtomicOp, opcode, inputs, operand.alignment,
operand.offset, decoder->position());
if (result) result->node = node;
}
private:
SsaEnv* ssa_env_;
TFBuilder* builder_;
uint32_t current_catch_ = kNullCatch;
TryInfo* current_try_info(Decoder* decoder) {
return decoder->control_at(decoder->control_depth() - 1 - current_catch_)
->try_info;
}
TFNode** GetNodes(Value* values, size_t count) {
TFNode** nodes = builder_->Buffer(count);
for (size_t i = 0; i < count; ++i) {
nodes[i] = values[i].node;
}
return nodes;
}
TFNode** GetNodes(Vector<Value> values) {
return GetNodes(values.start(), values.size());
}
void SetEnv(SsaEnv* env) {
#if DEBUG
if (FLAG_trace_wasm_decoder) {
char state = 'X';
if (env) {
switch (env->state) {
case SsaEnv::kReached:
state = 'R';
break;
case SsaEnv::kUnreachable:
state = 'U';
break;
case SsaEnv::kMerged:
state = 'M';
break;
case SsaEnv::kControlEnd:
state = 'E';
break;
}
}
PrintF("{set_env = %p, state = %c", static_cast<void*>(env), state);
if (env && env->control) {
PrintF(", control = ");
compiler::WasmGraphBuilder::PrintDebugName(env->control);
}
PrintF("}\n");
}
#endif
ssa_env_ = env;
// TODO(wasm): combine the control and effect pointers with context cache.
builder_->set_control_ptr(&env->control);
builder_->set_effect_ptr(&env->effect);
builder_->set_context_cache(&env->context_cache);
}
TFNode* CheckForException(Decoder* decoder, TFNode* node) {
if (node == nullptr) return nullptr;
const bool inside_try_scope = current_catch_ != kNullCatch;
if (!inside_try_scope) return node;
TFNode* if_success = nullptr;
TFNode* if_exception = nullptr;
if (!builder_->ThrowsException(node, &if_success, &if_exception)) {
return node;
}
SsaEnv* success_env = Steal(decoder->zone(), ssa_env_);
success_env->control = if_success;
SsaEnv* exception_env = Split(decoder, success_env);
exception_env->control = if_exception;
TryInfo* try_info = current_try_info(decoder);
Goto(decoder, exception_env, try_info->catch_env);
TFNode* exception = try_info->exception;
if (exception == nullptr) {
DCHECK_EQ(SsaEnv::kReached, try_info->catch_env->state);
try_info->exception = if_exception;
} else {
DCHECK_EQ(SsaEnv::kMerged, try_info->catch_env->state);
try_info->exception =
builder_->CreateOrMergeIntoPhi(kWasmI32, try_info->catch_env->control,
try_info->exception, if_exception);
}
SetEnv(success_env);
return node;
}
TFNode* DefaultValue(ValueType type) {
switch (type) {
case kWasmI32:
return builder_->Int32Constant(0);
case kWasmI64:
return builder_->Int64Constant(0);
case kWasmF32:
return builder_->Float32Constant(0);
case kWasmF64:
return builder_->Float64Constant(0);
case kWasmS128:
return builder_->S128Zero();
default:
UNREACHABLE();
}
}
void MergeValuesInto(Decoder* decoder, Control* c, Merge<Value>* merge) {
DCHECK(merge == &c->start_merge || merge == &c->end_merge);
if (!ssa_env_->go()) return;
SsaEnv* target = c->end_env;
const bool first = target->state == SsaEnv::kUnreachable;
Goto(decoder, ssa_env_, target);
uint32_t avail =
decoder->stack_size() - decoder->control_at(0)->stack_depth;
uint32_t start = avail >= merge->arity ? 0 : merge->arity - avail;
for (uint32_t i = start; i < merge->arity; ++i) {
auto& val = decoder->GetMergeValueFromStack(c, merge, i);
auto& old = (*merge)[i];
DCHECK_NOT_NULL(val.node);
DCHECK(val.type == old.type || val.type == kWasmVar);
old.node = first ? val.node
: builder_->CreateOrMergeIntoPhi(
old.type, target->control, old.node, val.node);
}
}
void Goto(Decoder* decoder, SsaEnv* from, SsaEnv* to) {
DCHECK_NOT_NULL(to);
if (!from->go()) return;
switch (to->state) {
case SsaEnv::kUnreachable: { // Overwrite destination.
to->state = SsaEnv::kReached;
to->locals = from->locals;
to->control = from->control;
to->effect = from->effect;
to->context_cache = from->context_cache;
break;
}
case SsaEnv::kReached: { // Create a new merge.
to->state = SsaEnv::kMerged;
// Merge control.
TFNode* controls[] = {to->control, from->control};
TFNode* merge = builder_->Merge(2, controls);
to->control = merge;
// Merge effects.
if (from->effect != to->effect) {
TFNode* effects[] = {to->effect, from->effect, merge};
to->effect = builder_->EffectPhi(2, effects, merge);
}
// Merge SSA values.
for (int i = decoder->NumLocals() - 1; i >= 0; i--) {
TFNode* a = to->locals[i];
TFNode* b = from->locals[i];
if (a != b) {
TFNode* vals[] = {a, b};
to->locals[i] =
builder_->Phi(decoder->GetLocalType(i), 2, vals, merge);
}
}
// Start a new merge from the context cache.
builder_->NewContextCacheMerge(&to->context_cache, &from->context_cache,
merge);
break;
}
case SsaEnv::kMerged: {
TFNode* merge = to->control;
// Extend the existing merge control node.
builder_->AppendToMerge(merge, from->control);
// Merge effects.
to->effect = builder_->CreateOrMergeIntoEffectPhi(merge, to->effect,
from->effect);
// Merge locals.
for (int i = decoder->NumLocals() - 1; i >= 0; i--) {
to->locals[i] = builder_->CreateOrMergeIntoPhi(
decoder->GetLocalType(i), merge, to->locals[i], from->locals[i]);
}
// Merge the context caches.
builder_->MergeContextCacheInto(&to->context_cache,
&from->context_cache, merge);
break;
}
default:
UNREACHABLE();
}
return from->Kill();
}
SsaEnv* PrepareForLoop(Decoder* decoder, SsaEnv* env) {
if (!env->go()) return Split(decoder, env);
env->state = SsaEnv::kMerged;
env->control = builder_->Loop(env->control);
env->effect = builder_->EffectPhi(1, &env->effect, env->control);
builder_->Terminate(env->effect, env->control);
// The '+ 1' here is to be able to set the context cache as assigned.
BitVector* assigned = WasmDecoder<validate>::AnalyzeLoopAssignment(
decoder, decoder->pc(), decoder->total_locals() + 1, decoder->zone());
if (decoder->failed()) return env;
if (assigned != nullptr) {
// Only introduce phis for variables assigned in this loop.
int context_cache_index = decoder->total_locals();
for (int i = decoder->NumLocals() - 1; i >= 0; i--) {
if (!assigned->Contains(i)) continue;
env->locals[i] = builder_->Phi(decoder->GetLocalType(i), 1,
&env->locals[i], env->control);
}
// Introduce phis for context cache pointers if necessary.
if (assigned->Contains(context_cache_index)) {
builder_->PrepareContextCacheForLoop(&env->context_cache, env->control);
}
SsaEnv* loop_body_env = Split(decoder, env);
builder_->StackCheck(decoder->position(), &(loop_body_env->effect),
&(loop_body_env->control));
return loop_body_env;
}
// Conservatively introduce phis for all local variables.
for (int i = decoder->NumLocals() - 1; i >= 0; i--) {
env->locals[i] = builder_->Phi(decoder->GetLocalType(i), 1,
&env->locals[i], env->control);
}
// Conservatively introduce phis for context cache.
builder_->PrepareContextCacheForLoop(&env->context_cache, env->control);
SsaEnv* loop_body_env = Split(decoder, env);
builder_->StackCheck(decoder->position(), &loop_body_env->effect,
&loop_body_env->control);
return loop_body_env;
}
// Create a complete copy of {from}.
SsaEnv* Split(Decoder* decoder, SsaEnv* from) {
DCHECK_NOT_NULL(from);
SsaEnv* result =
reinterpret_cast<SsaEnv*>(decoder->zone()->New(sizeof(SsaEnv)));
size_t size = sizeof(TFNode*) * decoder->NumLocals();
result->control = from->control;
result->effect = from->effect;
if (from->go()) {
result->state = SsaEnv::kReached;
result->locals =
size > 0 ? reinterpret_cast<TFNode**>(decoder->zone()->New(size))
: nullptr;
memcpy(result->locals, from->locals, size);
result->context_cache = from->context_cache;
} else {
result->state = SsaEnv::kUnreachable;
result->locals = nullptr;
result->context_cache = {0};
}
return result;
}
// Create a copy of {from} that steals its state and leaves {from}
// unreachable.
SsaEnv* Steal(Zone* zone, SsaEnv* from) {
DCHECK_NOT_NULL(from);
if (!from->go()) return UnreachableEnv(zone);
SsaEnv* result = reinterpret_cast<SsaEnv*>(zone->New(sizeof(SsaEnv)));
result->state = SsaEnv::kReached;
result->locals = from->locals;
result->control = from->control;
result->effect = from->effect;
result->context_cache = from->context_cache;
from->Kill(SsaEnv::kUnreachable);
return result;
}
// Create an unreachable environment.
SsaEnv* UnreachableEnv(Zone* zone) {
SsaEnv* result = reinterpret_cast<SsaEnv*>(zone->New(sizeof(SsaEnv)));
result->state = SsaEnv::kUnreachable;
result->control = nullptr;
result->effect = nullptr;
result->locals = nullptr;
result->context_cache = {0};
return result;
}
template <typename Operand>
void DoCall(WasmFullDecoder<validate, WasmGraphBuildingInterface>* decoder,
TFNode* index_node, const Operand& operand, const Value args[],
Value returns[], bool is_indirect) {
int param_count = static_cast<int>(operand.sig->parameter_count());
TFNode** arg_nodes = builder_->Buffer(param_count + 1);
TFNode** return_nodes = nullptr;
arg_nodes[0] = index_node;
for (int i = 0; i < param_count; ++i) {
arg_nodes[i + 1] = args[i].node;
}
if (is_indirect) {
builder_->CallIndirect(operand.index, arg_nodes, &return_nodes,
decoder->position());
} else {
builder_->CallDirect(operand.index, arg_nodes, &return_nodes,
decoder->position());
}
int return_count = static_cast<int>(operand.sig->return_count());
for (int i = 0; i < return_count; ++i) {
returns[i].node = return_nodes[i];
}
// The invoked function could have used grow_memory, so we need to
// reload mem_size and mem_start
LoadContextIntoSsa(ssa_env_);
}
};
} // namespace
bool DecodeLocalDecls(BodyLocalDecls* decls, const byte* start,
const byte* end) {
Decoder decoder(start, end);
if (WasmDecoder<Decoder::kValidate>::DecodeLocals(&decoder, nullptr,
&decls->type_list)) {
DCHECK(decoder.ok());
decls->encoded_size = decoder.pc_offset();
return true;
}
return false;
}
BytecodeIterator::BytecodeIterator(const byte* start, const byte* end,
BodyLocalDecls* decls)
: Decoder(start, end) {
if (decls != nullptr) {
if (DecodeLocalDecls(decls, start, end)) {
pc_ += decls->encoded_size;
if (pc_ > end_) pc_ = end_;
}
}
}
DecodeResult VerifyWasmCode(AccountingAllocator* allocator,
const wasm::WasmModule* module,
FunctionBody& body) {
Zone zone(allocator, ZONE_NAME);
WasmFullDecoder<Decoder::kValidate, EmptyInterface> decoder(&zone, module,
body);
decoder.Decode();
return decoder.toResult(nullptr);
}
DecodeResult VerifyWasmCodeWithStats(AccountingAllocator* allocator,
const wasm::WasmModule* module,
FunctionBody& body, bool is_wasm,
Counters* counters) {
CHECK_LE(0, body.end - body.start);
auto time_counter = is_wasm ? counters->wasm_decode_wasm_function_time()
: counters->wasm_decode_asm_function_time();
TimedHistogramScope wasm_decode_function_time_scope(time_counter);
return VerifyWasmCode(allocator, module, body);
}
DecodeResult BuildTFGraph(AccountingAllocator* allocator, TFBuilder* builder,
FunctionBody& body) {
Zone zone(allocator, ZONE_NAME);
WasmFullDecoder<Decoder::kValidate, WasmGraphBuildingInterface> decoder(
&zone, builder->module(), body, builder);
decoder.Decode();
return decoder.toResult(nullptr);
}
unsigned OpcodeLength(const byte* pc, const byte* end) {
Decoder decoder(pc, end);
return WasmDecoder<Decoder::kNoValidate>::OpcodeLength(&decoder, pc);
}
std::pair<uint32_t, uint32_t> StackEffect(const WasmModule* module,
FunctionSig* sig, const byte* pc,
const byte* end) {
WasmDecoder<Decoder::kNoValidate> decoder(module, sig, pc, end);
return decoder.StackEffect(pc);
}
void PrintRawWasmCode(const byte* start, const byte* end) {
AccountingAllocator allocator;
PrintRawWasmCode(&allocator, FunctionBody{nullptr, 0, start, end}, nullptr,
kPrintLocals);
}
namespace {
const char* RawOpcodeName(WasmOpcode opcode) {
switch (opcode) {
#define DECLARE_NAME_CASE(name, opcode, sig) \
case kExpr##name: \
return "kExpr" #name;
FOREACH_OPCODE(DECLARE_NAME_CASE)
#undef DECLARE_NAME_CASE
default:
break;
}
return "Unknown";
}
} // namespace
bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body,
const wasm::WasmModule* module,
PrintLocals print_locals) {
OFStream os(stdout);
Zone zone(allocator, ZONE_NAME);
WasmDecoder<Decoder::kNoValidate> decoder(module, body.sig, body.start,
body.end);
int line_nr = 0;
// Print the function signature.
if (body.sig) {
os << "// signature: " << *body.sig << std::endl;
++line_nr;
}
// Print the local declarations.
BodyLocalDecls decls(&zone);
BytecodeIterator i(body.start, body.end, &decls);
if (body.start != i.pc() && print_locals == kPrintLocals) {
os << "// locals: ";
if (!decls.type_list.empty()) {
ValueType type = decls.type_list[0];
uint32_t count = 0;
for (size_t pos = 0; pos < decls.type_list.size(); ++pos) {
if (decls.type_list[pos] == type) {
++count;
} else {
os << " " << count << " " << WasmOpcodes::TypeName(type);
type = decls.type_list[pos];
count = 1;
}
}
}
os << std::endl;
++line_nr;
for (const byte* locals = body.start; locals < i.pc(); locals++) {
os << (locals == body.start ? "0x" : " 0x") << AsHex(*locals, 2) << ",";
}
os << std::endl;
++line_nr;
}
os << "// body: " << std::endl;
++line_nr;
unsigned control_depth = 0;
for (; i.has_next(); i.next()) {
unsigned length =
WasmDecoder<Decoder::kNoValidate>::OpcodeLength(&decoder, i.pc());
WasmOpcode opcode = i.current();
if (opcode == kExprElse) control_depth--;
int num_whitespaces = control_depth < 32 ? 2 * control_depth : 64;
// 64 whitespaces
const char* padding =
" ";
os.write(padding, num_whitespaces);
os << RawOpcodeName(opcode) << ",";
for (unsigned j = 1; j < length; ++j) {
os << " 0x" << AsHex(i.pc()[j], 2) << ",";
}
switch (opcode) {
case kExprElse:
os << " // @" << i.pc_offset();
control_depth++;
break;
case kExprLoop:
case kExprIf:
case kExprBlock:
case kExprTry: {
BlockTypeOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // @" << i.pc_offset();
for (unsigned i = 0; i < operand.out_arity(); i++) {
os << " " << WasmOpcodes::TypeName(operand.out_type(i));
}
control_depth++;
break;
}
case kExprEnd:
os << " // @" << i.pc_offset();
control_depth--;
break;
case kExprBr: {
BreakDepthOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // depth=" << operand.depth;
break;
}
case kExprBrIf: {
BreakDepthOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // depth=" << operand.depth;
break;
}
case kExprBrTable: {
BranchTableOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // entries=" << operand.table_count;
break;
}
case kExprCallIndirect: {
CallIndirectOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // sig #" << operand.index;
if (decoder.Complete(i.pc(), operand)) {
os << ": " << *operand.sig;
}
break;
}
case kExprCallFunction: {
CallFunctionOperand<Decoder::kNoValidate> operand(&i, i.pc());
os << " // function #" << operand.index;
if (decoder.Complete(i.pc(), operand)) {
os << ": " << *operand.sig;
}
break;
}
default:
break;
}
os << std::endl;
++line_nr;
}
return decoder.ok();
}
BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, size_t num_locals,
const byte* start, const byte* end) {
Decoder decoder(start, end);
return WasmDecoder<Decoder::kValidate>::AnalyzeLoopAssignment(
&decoder, start, static_cast<uint32_t>(num_locals), zone);
}
#undef BUILD
} // namespace wasm
} // namespace internal
} // namespace v8