Commit e557383c authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm] Only decode most frequent constant expressions once

We introduce {ConstantExpression}, which represents the most frequent
constant expression types directly, and falls back to a {WireBytesRef}
for the rest. During module decoding, we decode the most common
expressions separately and store them as {ConstantExpression}, so we do
not have to decode them again during module instantiation.

Bug: chromium:1284557
Change-Id: Ie411bbe9811d0d9f6e750ba202bb0ccff801dfee
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3378347Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78576}
parent 5ab55557
......@@ -865,7 +865,7 @@ class ModuleDecoderImpl : public Decoder {
ValueType type = consume_value_type();
bool mutability = consume_mutability();
if (failed()) break;
WireBytesRef init = consume_init_expr(module_.get(), type);
ConstantExpression init = consume_init_expr(module_.get(), type);
module_->globals.push_back({type, mutability, init, {0}, false, false});
}
if (ok()) CalculateGlobalOffsets(module_.get());
......@@ -998,11 +998,11 @@ class ModuleDecoderImpl : public Decoder {
consume_count("number of elements", max_table_init_entries());
for (uint32_t j = 0; j < num_elem; j++) {
using Entry = WasmElemSegment::Entry;
Entry entry =
ConstantExpression entry =
segment.element_type == WasmElemSegment::kExpressionElements
? Entry(consume_init_expr(module_.get(), segment.type))
: Entry(consume_element_func_index(segment.type));
? consume_init_expr(module_.get(), segment.type)
: ConstantExpression::RefFunc(
consume_element_func_index(segment.type));
if (failed()) return;
segment.entries.push_back(entry);
}
......@@ -1085,7 +1085,7 @@ class ModuleDecoderImpl : public Decoder {
bool is_active;
uint32_t memory_index;
WireBytesRef dest_addr;
ConstantExpression dest_addr;
consume_data_segment_header(&is_active, &memory_index, &dest_addr);
if (failed()) break;
......@@ -1457,7 +1457,7 @@ class ModuleDecoderImpl : public Decoder {
return ok() ? result : nullptr;
}
WireBytesRef DecodeInitExprForTesting(ValueType expected) {
ConstantExpression DecodeInitExprForTesting(ValueType expected) {
return consume_init_expr(module_.get(), expected);
}
......@@ -1748,9 +1748,79 @@ class ModuleDecoderImpl : public Decoder {
return true;
}
WireBytesRef consume_init_expr(WasmModule* module, ValueType expected) {
FunctionBody body(FunctionSig::Build(&init_expr_zone_, {expected}, {}),
buffer_offset_, pc_, end_);
ConstantExpression consume_init_expr(WasmModule* module, ValueType expected) {
uint32_t length;
// The error message mimics the one generated by the {WasmFullDecoder}.
#define TYPE_CHECK(found) \
if (V8_UNLIKELY(!IsSubtypeOf(found, expected, module_.get()))) { \
errorf(pc() + 1, \
"type error in init. expression[0] (expected %s, got %s)", \
expected.name().c_str(), found.name().c_str()); \
return {}; \
}
// To avoid initializing a {WasmFullDecoder} for the most common
// expressions, we replicate their decoding and validation here. The
// manually handled cases correspond to {ConstantExpression}'s kinds.
// We need to make sure to check that the expression ends in {kExprEnd};
// otherwise, it is just the first operand of a composite expression, and we
// fall back to the default case.
if (!more()) {
error("Beyond end of code");
return {};
}
switch (static_cast<WasmOpcode>(*pc())) {
case kExprI32Const: {
int32_t value =
read_i32v<kFullValidation>(pc() + 1, &length, "i32.const");
if (V8_UNLIKELY(failed())) return {};
if (V8_LIKELY(lookahead(1 + length, kExprEnd))) {
TYPE_CHECK(kWasmI32)
consume_bytes(length + 2);
return ConstantExpression::I32Const(value);
}
break;
}
case kExprRefFunc: {
uint32_t index =
read_u32v<kFullValidation>(pc() + 1, &length, "ref.func");
if (V8_UNLIKELY(failed())) return {};
if (V8_LIKELY(lookahead(1 + length, kExprEnd))) {
if (V8_UNLIKELY(index >= module_->functions.size())) {
errorf(pc() + 1, "function index %u out of bounds", index);
return {};
}
ValueType type =
enabled_features_.has_typed_funcref()
? ValueType::Ref(module_->functions[index].sig_index,
kNonNullable)
: kWasmFuncRef;
TYPE_CHECK(type)
module_->functions[index].declared = true;
consume_bytes(length + 2);
return ConstantExpression::RefFunc(index);
}
break;
}
case kExprRefNull: {
HeapType type = value_type_reader::read_heap_type<kFullValidation>(
this, pc() + 1, &length, module_.get(), enabled_features_);
if (V8_UNLIKELY(failed())) return {};
if (V8_LIKELY(lookahead(1 + length, kExprEnd))) {
TYPE_CHECK(ValueType::Ref(type, kNullable))
consume_bytes(length + 2);
return ConstantExpression::RefNull(type.representation());
}
break;
}
default:
break;
}
#undef TYPE_CHECK
auto sig = FixedSizeSignature<ValueType>::Returns(expected);
FunctionBody body(&sig, buffer_offset_, pc_, end_);
WasmFeatures detected;
WasmFullDecoder<Decoder::kFullValidation, InitExprInterface,
kInitExpression>
......@@ -1773,7 +1843,8 @@ class ModuleDecoderImpl : public Decoder {
return {};
}
return {offset, static_cast<uint32_t>(decoder.end() - decoder.start())};
return ConstantExpression::WireBytes(
offset, static_cast<uint32_t>(decoder.end() - decoder.start()));
}
// Read a mutability flag
......@@ -1931,7 +2002,7 @@ class ModuleDecoderImpl : public Decoder {
ValueType table_type =
is_active ? module_->tables[table_index].type : kWasmBottom;
WireBytesRef offset;
ConstantExpression offset;
if (is_active) {
offset = consume_init_expr(module_.get(), kWasmI32);
// Failed to parse offset initializer, return early.
......@@ -1992,7 +2063,7 @@ class ModuleDecoderImpl : public Decoder {
}
void consume_data_segment_header(bool* is_active, uint32_t* index,
WireBytesRef* offset) {
ConstantExpression* offset) {
const byte* pos = pc();
uint32_t flag = consume_u32v("flag");
......@@ -2163,8 +2234,9 @@ const FunctionSig* DecodeWasmSignatureForTesting(const WasmFeatures& enabled,
return decoder.DecodeFunctionSignature(zone, start);
}
WireBytesRef DecodeWasmInitExprForTesting(const WasmFeatures& enabled,
const byte* start, const byte* end,
ConstantExpression DecodeWasmInitExprForTesting(const WasmFeatures& enabled,
const byte* start,
const byte* end,
ValueType expected) {
ModuleDecoderImpl decoder(enabled, start, end, kWasmOrigin);
AccountingAllocator allocator;
......
......@@ -171,7 +171,7 @@ V8_EXPORT_PRIVATE FunctionResult DecodeWasmFunctionForTesting(
const WasmModule* module, const byte* function_start,
const byte* function_end, Counters* counters);
V8_EXPORT_PRIVATE WireBytesRef
V8_EXPORT_PRIVATE ConstantExpression
DecodeWasmInitExprForTesting(const WasmFeatures& enabled, const byte* start,
const byte* end, ValueType expected);
......
......@@ -937,46 +937,20 @@ bool HasDefaultToNumberBehaviour(Isolate* isolate,
}
WasmValue EvaluateInitExpression(
Zone* zone, WireBytesRef init, ValueType expected, Isolate* isolate,
Zone* zone, ConstantExpression expr, ValueType expected, Isolate* isolate,
Handle<WasmInstanceObject> instance,
InitExprInterface::FunctionStrictness function_strictness =
InitExprInterface::kStrictFunctions) {
base::Vector<const byte> module_bytes =
instance->module_object().native_module()->wire_bytes();
const byte* start = module_bytes.begin() + init.offset();
const byte* end = module_bytes.begin() + init.end_offset();
// We implement a fast path for the most common expressions, so we do not have
// to allocate a WasmFullDecoder. It is possible that the first expression we
// encounter is only an operand of a more complex expression. Therefore we
// have to check we reached the end of the expression in each case.
switch (static_cast<WasmOpcode>(*start)) {
case kExprI32Const: {
Decoder decoder(start + 1, end);
int32_t value = decoder.consume_i32v();
if (*decoder.pc() == kExprEnd) return WasmValue(value);
break;
}
case kExprI64Const: {
Decoder decoder(start + 1, end);
int64_t value = decoder.consume_i64v();
if (*decoder.pc() == kExprEnd) return WasmValue(value);
break;
}
case kExprGlobalGet: {
Decoder decoder(start + 1, end);
uint32_t index = decoder.consume_u32v();
if (*decoder.pc() == kExprEnd) {
return WasmInstanceObject::GetGlobalValue(
instance, instance->module()->globals[index]);
}
break;
}
case kExprRefFunc: {
Decoder decoder(start + 1, end);
uint32_t index = decoder.consume_u32v();
if (*decoder.pc() == kExprEnd) {
switch (expr.kind()) {
case ConstantExpression::kEmpty:
UNREACHABLE();
case ConstantExpression::kI32Const:
return WasmValue(expr.i32_value());
case ConstantExpression::kRefNull:
return WasmValue(isolate->factory()->null_value(),
ValueType::Ref(expr.repr(), kNullable));
case ConstantExpression::kRefFunc: {
uint32_t index = expr.index();
Handle<Object> value =
function_strictness == InitExprInterface::kLazyFunctions
? Handle<Object>(Smi::FromInt(index), isolate)
......@@ -985,33 +959,32 @@ WasmValue EvaluateInitExpression(
isolate, instance, index));
return WasmValue(value, expected);
}
break;
}
case kExprRefNull: {
Decoder decoder(start + 1, end);
value_type_reader::consume_heap_type(&decoder, instance->module(),
WasmFeatures::All());
if (*decoder.pc() == kExprEnd) {
return WasmValue(isolate->factory()->null_value(), expected);
}
break;
}
default:
break;
}
case ConstantExpression::kWireBytesRef: {
WireBytesRef ref = expr.wire_bytes_ref();
base::Vector<const byte> module_bytes =
instance->module_object().native_module()->wire_bytes();
const byte* start = module_bytes.begin() + ref.offset();
const byte* end = module_bytes.begin() + ref.end_offset();
auto sig = FixedSizeSignature<ValueType>::Returns(expected);
FunctionBody body(&sig, init.offset(), start, end);
FunctionBody body(&sig, ref.offset(), start, end);
WasmFeatures detected;
// We use kFullValidation so we do not have to create another template
// instance of WasmFullDecoder, which would cost us >50Kb binary code size.
WasmFullDecoder<Decoder::kFullValidation, InitExprInterface, kInitExpression>
decoder(zone, instance->module(), WasmFeatures::All(), &detected, body,
instance->module(), isolate, instance, function_strictness);
// instance of WasmFullDecoder, which would cost us >50Kb binary code
// size.
WasmFullDecoder<Decoder::kFullValidation, InitExprInterface,
kInitExpression>
decoder(zone, instance->module(), WasmFeatures::All(), &detected,
body, instance->module(), isolate, instance,
function_strictness);
decoder.DecodeFunctionBody();
return decoder.interface().result();
}
}
}
} // namespace
......@@ -2053,16 +2026,14 @@ bool LoadElemSegmentImpl(Zone* zone, Isolate* isolate,
IsSubtypeOf(table_object->type(), kWasmFuncRef, instance->module());
for (size_t i = 0; i < count; ++i) {
WasmElemSegment::Entry entry = elem_segment.entries[src + i];
ConstantExpression entry = elem_segment.entries[src + i];
int entry_index = static_cast<int>(dst + i);
Handle<Object> value =
elem_segment.element_type == WasmElemSegment::kExpressionElements
? EvaluateInitExpression(
zone, entry.ref, elem_segment.type, isolate, instance,
EvaluateInitExpression(
zone, entry, elem_segment.type, isolate, instance,
is_function_table ? InitExprInterface::kLazyFunctions
: InitExprInterface::kStrictFunctions)
.to_ref()
: handle(Smi::FromInt(entry.index), isolate);
.to_ref();
SetTableEntry(isolate, instance, table_object, table_index, entry_index,
value);
}
......
......@@ -21,7 +21,9 @@ namespace wasm {
struct WasmModule;
class WasmFeatures;
// Representation of an initializer expression.
// Representation of an initializer expression. Unlike {ConstantExpression} in
// wasm-module.h, this does not use {WireBytesRef}, i.e., it does not depend on
// a wasm module's bytecode representation.
class WasmInitExpr : public ZoneObject {
public:
enum Operator {
......
......@@ -22,6 +22,7 @@
#include "src/wasm/struct-types.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-init-expr.h"
#include "src/wasm/wasm-limits.h"
namespace v8 {
......@@ -72,11 +73,104 @@ struct WasmFunction {
bool declared;
};
// A representation of a constant expression. The most common expression types
// are hard-coded, while the rest are represented as a {WireBytesRef}.
class ConstantExpression {
public:
enum Kind {
kEmpty,
kI32Const,
kRefNull,
kRefFunc,
kWireBytesRef,
kLastKind = kWireBytesRef
};
union Value {
int32_t i32_value;
uint32_t index_or_offset;
HeapType::Representation repr;
};
ConstantExpression() : bit_field_(KindField::encode(kEmpty)) {}
static ConstantExpression I32Const(int32_t value) {
return ConstantExpression(ValueField::encode(value) |
KindField::encode(kI32Const));
}
static ConstantExpression RefFunc(uint32_t index) {
return ConstantExpression(ValueField::encode(index) |
KindField::encode(kRefFunc));
}
static ConstantExpression RefNull(HeapType::Representation repr) {
return ConstantExpression(ValueField::encode(repr) |
KindField::encode(kRefNull));
}
static ConstantExpression WireBytes(uint32_t offset, uint32_t length) {
return ConstantExpression(OffsetField::encode(offset) |
LengthField::encode(length) |
KindField::encode(kWireBytesRef));
}
Kind kind() const { return KindField::decode(bit_field_); }
bool is_set() const { return kind() != kEmpty; }
uint32_t index() const {
DCHECK_EQ(kind(), kRefFunc);
return ValueField::decode(bit_field_);
}
HeapType::Representation repr() const {
DCHECK_EQ(kind(), kRefNull);
return static_cast<HeapType::Representation>(
ValueField::decode(bit_field_));
}
int32_t i32_value() const {
DCHECK_EQ(kind(), kI32Const);
return ValueField::decode(bit_field_);
}
WireBytesRef wire_bytes_ref() const {
DCHECK_EQ(kind(), kWireBytesRef);
return WireBytesRef(OffsetField::decode(bit_field_),
LengthField::decode(bit_field_));
}
private:
static constexpr int kValueBits = 32;
static constexpr int kLengthBits = 30;
static constexpr int kOffsetBits = 30;
static constexpr int kKindBits = 3;
// There are two possible combinations of fields: offset + length + kind if
// kind = kWireBytesRef, or value + kind for anything else.
using ValueField = base::BitField<uint32_t, 0, kValueBits, uint64_t>;
using OffsetField = base::BitField<uint32_t, 0, kOffsetBits, uint64_t>;
using LengthField = OffsetField::Next<uint32_t, kLengthBits>;
using KindField = LengthField::Next<Kind, kKindBits>;
// Make sure we reserve enough bits for a {WireBytesRef}'s length and offset.
STATIC_ASSERT(kV8MaxWasmModuleSize <= LengthField::kMax + 1);
STATIC_ASSERT(kV8MaxWasmModuleSize <= OffsetField::kMax + 1);
// Make sure kind fits in kKindBits.
STATIC_ASSERT(kLastKind <= KindField::kMax + 1);
explicit ConstantExpression(uint64_t bit_field) : bit_field_(bit_field) {}
uint64_t bit_field_;
};
// We want to keep {ConstantExpression} small to reduce memory usage during
// compilation/instantiation.
STATIC_ASSERT(sizeof(ConstantExpression) <= 8);
// Static representation of a wasm global variable.
struct WasmGlobal {
ValueType type; // type of the global.
bool mutability; // {true} if mutable.
WireBytesRef init; // the initialization expression of the global.
ConstantExpression init; // the initialization expression of the global.
union {
// Index of imported mutable global.
uint32_t index;
......@@ -103,13 +197,13 @@ struct WasmTag {
// Static representation of a wasm data segment.
struct WasmDataSegment {
// Construct an active segment.
explicit WasmDataSegment(WireBytesRef dest_addr)
: dest_addr(std::move(dest_addr)), active(true) {}
explicit WasmDataSegment(ConstantExpression dest_addr)
: dest_addr(dest_addr), active(true) {}
// Construct a passive segment, which has no dest_addr.
WasmDataSegment() : active(false) {}
WireBytesRef dest_addr; // destination memory address of the data.
ConstantExpression dest_addr; // destination memory address of the data.
WireBytesRef source; // start offset in the module bytes.
bool active = true; // true if copied automatically during instantiation.
};
......@@ -122,20 +216,10 @@ struct WasmElemSegment {
kStatusDeclarative // purely declarative and never copied.
};
enum ElementType { kFunctionIndexElements, kExpressionElements };
// An element segment entry. If {element_type == kExpressionElements}, it
// refers to an initializer expression (via a {WireBytesRef}); otherwise, it
// represents a function index.
union Entry {
WireBytesRef ref;
uint32_t index;
explicit Entry(uint32_t index) : index(index) {}
explicit Entry(WireBytesRef ref) : ref(ref) {}
};
// Construct an active segment.
WasmElemSegment(ValueType type, uint32_t table_index, WireBytesRef offset,
ElementType element_type)
WasmElemSegment(ValueType type, uint32_t table_index,
ConstantExpression offset, ElementType element_type)
: status(kStatusActive),
type(type),
table_index(table_index),
......@@ -164,9 +248,9 @@ struct WasmElemSegment {
Status status;
ValueType type;
uint32_t table_index;
WireBytesRef offset;
ConstantExpression offset;
ElementType element_type;
std::vector<Entry> entries;
std::vector<ConstantExpression> entries;
};
// Static representation of a wasm import.
......@@ -439,7 +523,7 @@ struct WasmTable {
bool has_maximum_size = false; // true if there is a maximum size.
bool imported = false; // true if imported.
bool exported = false; // true if exported.
WireBytesRef initial_value;
ConstantExpression initial_value;
};
inline bool is_asmjs_module(const WasmModule* module) {
......
......@@ -331,7 +331,7 @@ uint32_t TestingModuleBuilder::AddPassiveElementSegment(
WasmElemSegment::kFunctionIndexElements);
auto& elem_segment = test_module_->elem_segments.back();
for (uint32_t entry : entries) {
elem_segment.entries.emplace_back(entry);
elem_segment.entries.emplace_back(ConstantExpression::RefFunc(entry));
}
// The vector pointers may have moved, so update the instance object.
......
......@@ -519,18 +519,36 @@ void AppendInitExpr(std::ostream& os, const WasmInitExpr& expr) {
void DecodeAndAppendInitExpr(StdoutStream& os, Zone* zone,
const WasmModule* module,
ModuleWireBytes module_bytes, WireBytesRef init,
ValueType expected) {
FunctionBody body(FunctionSig::Build(zone, {expected}, {}), init.offset(),
module_bytes.start() + init.offset(),
module_bytes.start() + init.end_offset());
ModuleWireBytes module_bytes,
ConstantExpression init, ValueType expected) {
switch (init.kind()) {
case ConstantExpression::kEmpty:
UNREACHABLE();
case ConstantExpression::kI32Const:
AppendInitExpr(os, WasmInitExpr(init.i32_value()));
break;
case ConstantExpression::kRefNull:
AppendInitExpr(os, WasmInitExpr::RefNullConst(init.repr()));
break;
case ConstantExpression::kRefFunc:
AppendInitExpr(os, WasmInitExpr::RefFuncConst(init.index()));
break;
case ConstantExpression::kWireBytesRef: {
WireBytesRef ref = init.wire_bytes_ref();
auto sig = FixedSizeSignature<ValueType>::Returns(expected);
FunctionBody body(&sig, ref.offset(), module_bytes.start() + ref.offset(),
module_bytes.start() + ref.end_offset());
WasmFeatures detected;
WasmFullDecoder<Decoder::kFullValidation, InitExprInterface, kInitExpression>
WasmFullDecoder<Decoder::kFullValidation, InitExprInterface,
kInitExpression>
decoder(zone, module, WasmFeatures::All(), &detected, body, zone);
decoder.DecodeFunctionBody();
AppendInitExpr(os, decoder.interface().result());
break;
}
}
}
} // namespace
......@@ -656,9 +674,9 @@ void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
for (uint32_t i = 0; i < elem_segment.entries.size(); i++) {
if (elem_segment.element_type == WasmElemSegment::kExpressionElements) {
DecodeAndAppendInitExpr(os, &zone, module, wire_bytes,
elem_segment.entries[i].ref, elem_segment.type);
elem_segment.entries[i], elem_segment.type);
} else {
os << elem_segment.entries[i].index;
os << elem_segment.entries[i].index();
}
if (i < elem_segment.entries.size() - 1) os << ", ";
}
......
......@@ -495,8 +495,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
kI32Code, // type
1) // mutable
};
EXPECT_FAILURE_WITH_MSG(no_initializer_no_end,
"Initializer expression is missing 'end'");
EXPECT_FAILURE_WITH_MSG(no_initializer_no_end, "Beyond end of code");
static const byte no_initializer[] = {
SECTION(Global, //--
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment