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

[fuzzer] Add support for reference types and simd

Changes:
- Add reference types and simd to GetValueType().
- Generalize BlockScope to handle reference types. Add EmitValueType()
  to WasmFunctionBuilder.
- Constrain local_op and global_op to non-simd numeric types.
- Add GenerateOneOf() for functions that need a heap type. Add
  GenerateOptRef(). Add ref_null, get_local_opt_ref as options for
  GenerateOptRef().
- Remove the numeric conversion logic from ConsumeAndGenerate.

Bug: v8:11954
Change-Id: Idebae4a537326bdc03ac2f5e9c69a519f196938c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3009456
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarThibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75614}
parent 394c57a8
...@@ -120,6 +120,23 @@ void WasmFunctionBuilder::EmitWithU32V(WasmOpcode opcode, uint32_t immediate) { ...@@ -120,6 +120,23 @@ void WasmFunctionBuilder::EmitWithU32V(WasmOpcode opcode, uint32_t immediate) {
body_.write_u32v(immediate); body_.write_u32v(immediate);
} }
namespace {
void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
buffer->write_u8(type.value_type_code());
if (type.encoding_needs_heap_type()) {
buffer->write_i32v(type.heap_type().code());
}
if (type.is_rtt()) {
if (type.has_depth()) buffer->write_u32v(type.depth());
buffer->write_u32v(type.ref_index());
}
}
} // namespace
void WasmFunctionBuilder::EmitValueType(ValueType type) {
WriteValueType(&body_, type);
}
void WasmFunctionBuilder::EmitI32Const(int32_t value) { void WasmFunctionBuilder::EmitI32Const(int32_t value) {
EmitWithI32V(kExprI32Const, value); EmitWithI32V(kExprI32Const, value);
} }
...@@ -437,17 +454,6 @@ void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) { ...@@ -437,17 +454,6 @@ void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) {
void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; } void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; }
namespace { namespace {
void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
buffer->write_u8(type.value_type_code());
if (type.encoding_needs_heap_type()) {
buffer->write_i32v(type.heap_type().code());
}
if (type.is_rtt()) {
if (type.has_depth()) buffer->write_u32v(type.depth());
buffer->write_u32v(type.ref_index());
}
}
void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init, void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
ValueType type) { ValueType type) {
switch (init.kind()) { switch (init.kind()) {
...@@ -507,11 +513,16 @@ void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init, ...@@ -507,11 +513,16 @@ void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
break; break;
case kOptRef: case kOptRef:
buffer->write_u8(kExprRefNull); buffer->write_u8(kExprRefNull);
buffer->write_i32v(type.heap_type().code());
break;
case kS128:
buffer->write_u8(static_cast<byte>(kSimdPrefix));
buffer->write_u8(static_cast<byte>(kExprS128Const & 0xff));
for (int i = 0; i < kSimd128Size; i++) buffer->write_u8(0);
break; break;
case kI8: case kI8:
case kI16: case kI16:
case kVoid: case kVoid:
case kS128:
case kBottom: case kBottom:
case kRef: case kRef:
case kRtt: case kRtt:
......
...@@ -186,6 +186,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { ...@@ -186,6 +186,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject {
void EmitWithU8U8(WasmOpcode opcode, const byte imm1, const byte imm2); void EmitWithU8U8(WasmOpcode opcode, const byte imm1, const byte imm2);
void EmitWithI32V(WasmOpcode opcode, int32_t immediate); void EmitWithI32V(WasmOpcode opcode, int32_t immediate);
void EmitWithU32V(WasmOpcode opcode, uint32_t immediate); void EmitWithU32V(WasmOpcode opcode, uint32_t immediate);
void EmitValueType(ValueType type);
void EmitDirectCallIndex(uint32_t index); void EmitDirectCallIndex(uint32_t index);
void SetName(base::Vector<const char> name); void SetName(base::Vector<const char> name);
void AddAsmWasmOffset(size_t call_position, size_t to_number_position); void AddAsmWasmOffset(size_t call_position, size_t to_number_position);
......
...@@ -3153,6 +3153,9 @@ class WasmInterpreterInternals { ...@@ -3153,6 +3153,9 @@ class WasmInterpreterInternals {
switch (sig->GetParam(i).heap_representation()) { switch (sig->GetParam(i).heap_representation()) {
case HeapType::kExtern: case HeapType::kExtern:
case HeapType::kFunc: case HeapType::kFunc:
case HeapType::kEq:
case HeapType::kData:
case HeapType::kI31:
case HeapType::kAny: { case HeapType::kAny: {
Handle<Object> ref = value.to_ref(); Handle<Object> ref = value.to_ref();
encoded_values->set(encoded_index++, *ref); encoded_values->set(encoded_index++, *ref);
...@@ -3160,9 +3163,6 @@ class WasmInterpreterInternals { ...@@ -3160,9 +3163,6 @@ class WasmInterpreterInternals {
} }
case HeapType::kBottom: case HeapType::kBottom:
UNREACHABLE(); UNREACHABLE();
case HeapType::kEq:
case HeapType::kData:
case HeapType::kI31:
default: default:
// TODO(7748): Implement these. // TODO(7748): Implement these.
UNIMPLEMENTED(); UNIMPLEMENTED();
...@@ -3274,6 +3274,9 @@ class WasmInterpreterInternals { ...@@ -3274,6 +3274,9 @@ class WasmInterpreterInternals {
switch (sig->GetParam(i).heap_representation()) { switch (sig->GetParam(i).heap_representation()) {
case HeapType::kExtern: case HeapType::kExtern:
case HeapType::kFunc: case HeapType::kFunc:
case HeapType::kEq:
case HeapType::kData:
case HeapType::kI31:
case HeapType::kAny: { case HeapType::kAny: {
Handle<Object> ref(encoded_values->get(encoded_index++), Handle<Object> ref(encoded_values->get(encoded_index++),
isolate_); isolate_);
......
...@@ -90,20 +90,14 @@ class DataRange { ...@@ -90,20 +90,14 @@ class DataRange {
}; };
ValueType GetValueType(DataRange* data) { ValueType GetValueType(DataRange* data) {
// TODO(v8:8460): We do not add kWasmS128 here yet because this method is used constexpr ValueType types[] = {
// to generate globals, and since we do not have v128.const yet, there is no kWasmI32, kWasmI64,
// way to specify an initial value a global of this type. kWasmF32, kWasmF64,
switch (data->get<uint8_t>() % 4) { kWasmS128, kWasmExternRef,
case 0: kWasmFuncRef, kWasmEqRef,
return kWasmI32; kWasmAnyRef, ValueType::Ref(HeapType(HeapType::kData), kNullable)};
case 1:
return kWasmI64; return types[data->get<uint8_t>() % arraysize(types)];
case 2:
return kWasmF32;
case 3:
return kWasmF64;
}
UNREACHABLE();
} }
class WasmGenerator { class WasmGenerator {
...@@ -121,13 +115,14 @@ class WasmGenerator { ...@@ -121,13 +115,14 @@ class WasmGenerator {
base::Vector<const ValueType> br_types, bool emit_end = true) base::Vector<const ValueType> br_types, bool emit_end = true)
: gen_(gen), emit_end_(emit_end) { : gen_(gen), emit_end_(emit_end) {
gen->blocks_.emplace_back(br_types.begin(), br_types.end()); gen->blocks_.emplace_back(br_types.begin(), br_types.end());
gen->builder_->EmitByte(block_type);
if (param_types.size() == 0 && result_types.size() == 0) { if (param_types.size() == 0 && result_types.size() == 0) {
gen->builder_->EmitWithU8(block_type, kWasmVoid.value_type_code()); gen->builder_->EmitValueType(kWasmVoid);
return; return;
} }
if (param_types.size() == 0 && result_types.size() == 1) { if (param_types.size() == 0 && result_types.size() == 1) {
gen->builder_->EmitWithU8(block_type, gen->builder_->EmitValueType(result_types[0]);
result_types[0].value_type_code());
return; return;
} }
// Multi-value block. // Multi-value block.
...@@ -144,7 +139,7 @@ class WasmGenerator { ...@@ -144,7 +139,7 @@ class WasmGenerator {
} }
FunctionSig* sig = builder.Build(); FunctionSig* sig = builder.Build();
int sig_id = gen->builder_->builder()->AddSignature(sig); int sig_id = gen->builder_->builder()->AddSignature(sig);
gen->builder_->EmitWithI32V(block_type, sig_id); gen->builder_->EmitI32V(sig_id);
} }
~BlockScope() { ~BlockScope() {
...@@ -533,17 +528,6 @@ class WasmGenerator { ...@@ -533,17 +528,6 @@ class WasmGenerator {
builder_->Emit(kConvertOpcodes[arr_idx]); builder_->Emit(kConvertOpcodes[arr_idx]);
} }
void ConvertOrGenerate(ValueType src, ValueType dst, DataRange* data) {
if (src == dst) return;
if (src == kWasmVoid && dst != kWasmVoid) {
Generate(dst, data);
} else if (dst == kWasmVoid && src != kWasmVoid) {
builder_->Emit(kExprDrop);
} else {
Convert(src, dst);
}
}
void call(DataRange* data, ValueType wanted_kind, CallDirect call_direct) { void call(DataRange* data, ValueType wanted_kind, CallDirect call_direct) {
uint8_t random_byte = data->get<uint8_t>(); uint8_t random_byte = data->get<uint8_t>();
int func_index = random_byte % functions_.size(); int func_index = random_byte % functions_.size();
...@@ -616,12 +600,17 @@ class WasmGenerator { ...@@ -616,12 +600,17 @@ class WasmGenerator {
return {index, type}; return {index, type};
} }
constexpr static bool is_convertible_kind(ValueKind kind) {
return kind == kI32 || kind == kI64 || kind == kF32 || kind == kF64;
}
template <ValueKind wanted_kind> template <ValueKind wanted_kind>
void local_op(DataRange* data, WasmOpcode opcode) { void local_op(DataRange* data, WasmOpcode opcode) {
STATIC_ASSERT(wanted_kind == kVoid || is_convertible_kind(wanted_kind));
Var local = GetRandomLocal(data); Var local = GetRandomLocal(data);
// If there are no locals and no parameters, just generate any value (if a // If there are no locals and no parameters, just generate any value (if a
// value is needed), or do nothing. // value is needed), or do nothing.
if (!local.is_valid()) { if (!local.is_valid() || !is_convertible_kind(local.type.kind())) {
if (wanted_kind == kVoid) return; if (wanted_kind == kVoid) return;
return Generate<wanted_kind>(data); return Generate<wanted_kind>(data);
} }
...@@ -671,11 +660,12 @@ class WasmGenerator { ...@@ -671,11 +660,12 @@ class WasmGenerator {
template <ValueKind wanted_kind> template <ValueKind wanted_kind>
void global_op(DataRange* data) { void global_op(DataRange* data) {
STATIC_ASSERT(wanted_kind == kVoid || is_convertible_kind(wanted_kind));
constexpr bool is_set = wanted_kind == kVoid; constexpr bool is_set = wanted_kind == kVoid;
Var global = GetRandomGlobal(data, is_set); Var global = GetRandomGlobal(data, is_set);
// If there are no globals, just generate any value (if a value is needed), // If there are no globals, just generate any value (if a value is needed),
// or do nothing. // or do nothing.
if (!global.is_valid()) { if (!global.is_valid() || !is_convertible_kind(global.type.kind())) {
if (wanted_kind == kVoid) return; if (wanted_kind == kVoid) return;
return Generate<wanted_kind>(data); return Generate<wanted_kind>(data);
} }
...@@ -736,7 +726,23 @@ class WasmGenerator { ...@@ -736,7 +726,23 @@ class WasmGenerator {
void grow_memory(DataRange* data); void grow_memory(DataRange* data);
void ref_null(HeapType type, DataRange* data) {
builder_->EmitWithI32V(kExprRefNull, type.code());
}
void get_local_opt_ref(HeapType type, DataRange* data) {
Var local = GetRandomLocal(data);
// TODO(manoskouk): Ideally we would check for subtyping here over type
// equality, but we don't have a module.
if (local.is_valid() && local.type.is_object_reference() &&
type == local.type.heap_type()) {
builder_->EmitWithU32V(kExprLocalGet, local.index);
} else {
ref_null(type, data);
}
}
using GenerateFn = void (WasmGenerator::*const)(DataRange*); using GenerateFn = void (WasmGenerator::*const)(DataRange*);
using GenerateFnWithHeap = void (WasmGenerator::*const)(HeapType, DataRange*);
template <size_t N> template <size_t N>
void GenerateOneOf(GenerateFn (&alternatives)[N], DataRange* data) { void GenerateOneOf(GenerateFn (&alternatives)[N], DataRange* data) {
...@@ -748,6 +754,17 @@ class WasmGenerator { ...@@ -748,6 +754,17 @@ class WasmGenerator {
(this->*alternate)(data); (this->*alternate)(data);
} }
template <size_t N>
void GenerateOneOf(GenerateFnWithHeap (&alternatives)[N], HeapType type,
DataRange* data) {
static_assert(N < std::numeric_limits<uint8_t>::max(),
"Too many alternatives. Use a bigger type if needed.");
const auto which = data->get<uint8_t>();
GenerateFnWithHeap alternate = alternatives[which % N];
(this->*alternate)(type, data);
}
struct GeneratorRecursionScope { struct GeneratorRecursionScope {
explicit GeneratorRecursionScope(WasmGenerator* gen) : gen(gen) { explicit GeneratorRecursionScope(WasmGenerator* gen) : gen(gen) {
++gen->recursion_depth; ++gen->recursion_depth;
...@@ -795,6 +812,8 @@ class WasmGenerator { ...@@ -795,6 +812,8 @@ class WasmGenerator {
Generate<T2, Ts...>(data); Generate<T2, Ts...>(data);
} }
void GenerateOptRef(HeapType type, DataRange* data);
std::vector<ValueType> GenerateTypes(DataRange* data); std::vector<ValueType> GenerateTypes(DataRange* data);
void Generate(base::Vector<const ValueType> types, DataRange* data); void Generate(base::Vector<const ValueType> types, DataRange* data);
void ConsumeAndGenerate(base::Vector<const ValueType> parameter_types, void ConsumeAndGenerate(base::Vector<const ValueType> parameter_types,
...@@ -1524,11 +1543,20 @@ void WasmGenerator::Generate(ValueType type, DataRange* data) { ...@@ -1524,11 +1543,20 @@ void WasmGenerator::Generate(ValueType type, DataRange* data) {
return Generate<kF64>(data); return Generate<kF64>(data);
case kS128: case kS128:
return Generate<kS128>(data); return Generate<kS128>(data);
case kOptRef:
return GenerateOptRef(type.heap_type(), data);
default: default:
UNREACHABLE(); UNREACHABLE();
} }
} }
void WasmGenerator::GenerateOptRef(HeapType type, DataRange* data) {
constexpr GenerateFnWithHeap alternatives[] = {
&WasmGenerator::ref_null, &WasmGenerator::get_local_opt_ref};
GenerateOneOf(alternatives, type, data);
}
std::vector<ValueType> WasmGenerator::GenerateTypes(DataRange* data) { std::vector<ValueType> WasmGenerator::GenerateTypes(DataRange* data) {
std::vector<ValueType> types; std::vector<ValueType> types;
int num_params = int{data->get<uint8_t>()} % (kMaxParameters + 1); int num_params = int{data->get<uint8_t>()} % (kMaxParameters + 1);
...@@ -1575,33 +1603,14 @@ void WasmGenerator::Generate(base::Vector<const ValueType> types, ...@@ -1575,33 +1603,14 @@ void WasmGenerator::Generate(base::Vector<const ValueType> types,
} }
// Emit code to match an arbitrary signature. // Emit code to match an arbitrary signature.
// TODO(manoskouk): Do something which uses inputs instead of dropping them.
// Possibly generate function a function with the correct sig on the fly? Or
// generalize the {Convert} function.
void WasmGenerator::ConsumeAndGenerate( void WasmGenerator::ConsumeAndGenerate(
base::Vector<const ValueType> param_types, base::Vector<const ValueType> param_types,
base::Vector<const ValueType> return_types, DataRange* data) { base::Vector<const ValueType> return_types, DataRange* data) {
if (param_types.size() == 0) { for (unsigned i = 0; i < param_types.size(); i++) builder_->Emit(kExprDrop);
Generate(return_types, data); Generate(return_types, data);
return;
}
// Keep exactly one of the parameters on the stack with a combination of drops
// and selects, convert this value to the first return type, and generate the
// remaining types.
// TODO(thibaudm): Improve this strategy to potentially generate any sequence
// of instructions matching the given signature.
size_t return_index = data->get<uint8_t>() % param_types.size();
for (size_t i = param_types.size() - 1; i > return_index; --i) {
builder_->Emit(kExprDrop);
}
for (size_t i = return_index; i > 0; --i) {
Convert(param_types[i], param_types[i - 1]);
builder_->EmitI32Const(0);
builder_->Emit(kExprSelect);
}
if (return_types.empty()) {
builder_->Emit(kExprDrop);
} else {
Convert(param_types[0], return_types[0]);
Generate(return_types + 1, data);
}
} }
enum SigKind { kFunctionSig, kExceptionSig }; enum SigKind { kFunctionSig, kExceptionSig };
...@@ -1697,6 +1706,9 @@ class WasmCompileFuzzer : public WasmExecutionFuzzer { ...@@ -1697,6 +1706,9 @@ class WasmCompileFuzzer : public WasmExecutionFuzzer {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
constexpr bool require_valid = true; constexpr bool require_valid = true;
EXPERIMENTAL_FLAG_SCOPE(reftypes); EXPERIMENTAL_FLAG_SCOPE(reftypes);
EXPERIMENTAL_FLAG_SCOPE(typed_funcref);
EXPERIMENTAL_FLAG_SCOPE(gc);
EXPERIMENTAL_FLAG_SCOPE(simd);
EXPERIMENTAL_FLAG_SCOPE(eh); EXPERIMENTAL_FLAG_SCOPE(eh);
WasmCompileFuzzer().FuzzWasmModule({data, size}, require_valid); WasmCompileFuzzer().FuzzWasmModule({data, size}, require_valid);
return 0; return 0;
......
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