[wasm] Make multi-return tests wasm-specific

The multi-return tests and fuzzer used a custom call descriptor which
was based on the default RegisterConfiguration. This meant that for the
tests, all available registers could be used to pass parameters and to
return values. This caused a problem, because in some cases we need a
scratch register in the frame deconstruction.

With this CL I change both the tests and the fuzzer to use the
WebAssembly call descriptor. Thereby we only use 2 registers for
returns, and one of the other registers can be used as scratch
register.

WebAssembly is the only use case at the moment which wants to return
values not only through registers but also over the stack. Therefore
I think it's acceptable to only test the WebAssembly usecase.

R=mstarzinger@chromium.org

Bug: chromium:813288
Change-Id: I31bed757af5f3e8589d2b3dfb6f0112ddecd1a20
Reviewed-on: https://chromium-review.googlesource.com/970656
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52099}
This commit is contained in:
Andreas Haas 2018-03-20 15:19:33 +01:00 committed by Commit Bot
parent 3ba29d5590
commit ba5409a6fd
2 changed files with 135 additions and 187 deletions

View File

@ -12,9 +12,13 @@
#include "src/codegen.h"
#include "src/compiler.h"
#include "src/compiler/linkage.h"
#include "src/compiler/wasm-compiler.h"
#include "src/machine-type.h"
#include "src/macro-assembler.h"
#include "src/objects-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/cctest/compiler/value-helper.h"
@ -25,89 +29,18 @@ namespace compiler {
namespace {
int size(MachineType type) {
return 1 << ElementSizeLog2Of(type.representation());
}
CallDescriptor* CreateCallDescriptor(Zone* zone, int return_count,
int param_count, MachineType type) {
wasm::FunctionSig::Builder builder(zone, return_count, param_count);
int num_registers(MachineType type) {
const RegisterConfiguration* config = RegisterConfiguration::Default();
switch (type.representation()) {
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
return config->num_allocatable_general_registers();
case MachineRepresentation::kFloat32:
return config->num_allocatable_float_registers();
case MachineRepresentation::kFloat64:
return config->num_allocatable_double_registers();
default:
UNREACHABLE();
}
}
const int* codes(MachineType type) {
const RegisterConfiguration* config = RegisterConfiguration::Default();
switch (type.representation()) {
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
return config->allocatable_general_codes();
case MachineRepresentation::kFloat32:
return config->allocatable_float_codes();
case MachineRepresentation::kFloat64:
return config->allocatable_double_codes();
default:
UNREACHABLE();
}
}
CallDescriptor* CreateMonoCallDescriptor(Zone* zone, int return_count,
int param_count, MachineType type) {
LocationSignature::Builder locations(zone, return_count, param_count);
int span = std::max(1, size(type) / kPointerSize);
int stack_params = 0;
for (int i = 0; i < param_count; i++) {
LinkageLocation location = LinkageLocation::ForAnyRegister();
if (i < num_registers(type)) {
location = LinkageLocation::ForRegister(codes(type)[i], type);
} else {
int slot = span * (i - param_count);
location = LinkageLocation::ForCallerFrameSlot(slot, type);
stack_params += span;
}
locations.AddParam(location);
builder.AddParam(type.representation());
}
int stack_returns = 0;
for (int i = 0; i < return_count; i++) {
LinkageLocation location = LinkageLocation::ForAnyRegister();
if (i < num_registers(type)) {
location = LinkageLocation::ForRegister(codes(type)[i], type);
} else {
int slot = span * (num_registers(type) - i) - stack_params - 1;
location = LinkageLocation::ForCallerFrameSlot(slot, type);
stack_returns += span;
}
locations.AddReturn(location);
builder.AddReturn(type.representation());
}
const RegList kCalleeSaveRegisters = 0;
const RegList kCalleeSaveFPRegisters = 0;
MachineType target_type = MachineType::AnyTagged();
LinkageLocation target_loc = LinkageLocation::ForAnyRegister(target_type);
return new (zone) CallDescriptor( // --
CallDescriptor::kCallCodeObject, // kind
target_type, // target MachineType
target_loc, // target location
locations.Build(), // location_sig
stack_params, // on-stack parameter count
compiler::Operator::kNoProperties, // properties
kCalleeSaveRegisters, // callee-saved registers
kCalleeSaveFPRegisters, // callee-saved fp regs
CallDescriptor::kNoFlags, // flags
"c-call", // debug name
0, // allocatable registers
stack_returns); // on-stack return count
return compiler::GetWasmCallDescriptor(zone, builder.Build());
}
} // namespace
@ -187,6 +120,26 @@ Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) {
}
}
std::unique_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate,
size_t code_size) {
// We have to add the code object to a NativeModule, because the
// WasmCallDescriptor assumes that code is on the native heap and not
// within a code object.
std::unique_ptr<wasm::NativeModule> module =
isolate->wasm_engine()->code_manager()->NewNativeModule(code_size, 1, 0,
false);
// TODO(mstarzinger): Remove the WasmCompiledModule here as soon as source
// positions are stored in the WasmCode directly.
Handle<WasmCompiledModule> compiled_module = Handle<WasmCompiledModule>::cast(
isolate->factory()->NewStruct(WASM_COMPILED_MODULE_TYPE, TENURED));
Handle<FixedArray> source_positions =
isolate->factory()->NewFixedArray(1, TENURED);
compiled_module->set_source_positions(*source_positions);
module->SetCompiledModule(compiled_module);
return module;
}
void TestReturnMultipleValues(MachineType type) {
const int kMaxCount = 20;
for (int count = 0; count < kMaxCount; ++count) {
@ -194,15 +147,16 @@ void TestReturnMultipleValues(MachineType type) {
MachineReprToString(type.representation()), count);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
CallDescriptor* desc = CreateMonoCallDescriptor(&zone, count, 2, type);
CallDescriptor* desc = CreateCallDescriptor(&zone, count, 2, type);
HandleAndZoneScope handles;
RawMachineAssembler m(handles.main_isolate(),
new (handles.main_zone()) Graph(handles.main_zone()),
desc, MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
// m.Parameter(0) is the WasmContext.
Node* p0 = m.Parameter(1);
Node* p1 = m.Parameter(2);
typedef Node* Node_ptr;
std::unique_ptr<Node_ptr[]> returns(new Node_ptr[count]);
for (int i = 0; i < count; ++i) {
@ -232,11 +186,21 @@ void TestReturnMultipleValues(MachineType type) {
if (i % 4 == 0) sign = -sign;
}
std::unique_ptr<wasm::NativeModule> module =
AllocateNativeModule(handles.main_isolate(), code->instruction_size());
byte* code_start = module->AddCodeCopy(code, wasm::WasmCode::kFunction, 0)
->instructions()
.start();
RawMachineAssemblerTester<int32_t> mt;
Node* na = Constant(mt, type, a);
Node* nb = Constant(mt, type, b);
Node* ret_multi =
mt.AddNode(mt.common()->Call(desc), mt.HeapConstant(code), na, nb);
Node* call_inputs[] = {mt.PointerConstant(code_start),
// WasmContext dummy
mt.PointerConstant(nullptr),
// Inputs
Constant(mt, type, a), Constant(mt, type, b)};
Node* ret_multi = mt.AddNode(mt.common()->Call(desc),
arraysize(call_inputs), call_inputs);
Node* ret = Constant(mt, type, 0);
bool sign = false;
for (int i = 0; i < count; ++i) {
@ -275,10 +239,11 @@ void ReturnLastValue(MachineType type) {
for (auto slot_count : slot_counts) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
const int return_count = num_registers(type) + slot_count;
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + slot_count;
CallDescriptor* desc =
CreateMonoCallDescriptor(&zone, return_count, 0, type);
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles;
RawMachineAssembler m(handles.main_isolate(),
@ -299,12 +264,20 @@ void ReturnLastValue(MachineType type) {
Handle<Code> code = Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(), m.Export());
std::unique_ptr<wasm::NativeModule> module =
AllocateNativeModule(handles.main_isolate(), code->instruction_size());
byte* code_start = module->AddCodeCopy(code, wasm::WasmCode::kFunction, 0)
->instructions()
.start();
// Generate caller.
int expect = return_count - 1;
RawMachineAssemblerTester<int32_t> mt;
Node* code_node = mt.HeapConstant(code);
Node* inputs[] = {mt.PointerConstant(code_start),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 1, &code_node);
Node* call = mt.AddNode(mt.common()->Call(desc), 2, inputs);
mt.Return(ToInt32(
mt, type, mt.AddNode(mt.common()->Projection(return_count - 1), call)));
@ -326,10 +299,11 @@ void ReturnSumOfReturns(MachineType type) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Let {unused_stack_slots + 1} returns be on the stack.
const int return_count = num_registers(type) + unused_stack_slots + 1;
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + unused_stack_slots + 1;
CallDescriptor* desc =
CreateMonoCallDescriptor(&zone, return_count, 0, type);
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles;
RawMachineAssembler m(handles.main_isolate(),
@ -350,11 +324,19 @@ void ReturnSumOfReturns(MachineType type) {
Handle<Code> code = Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(), m.Export());
std::unique_ptr<wasm::NativeModule> module =
AllocateNativeModule(handles.main_isolate(), code->instruction_size());
byte* code_start = module->AddCodeCopy(code, wasm::WasmCode::kFunction, 0)
->instructions()
.start();
// Generate caller.
RawMachineAssemblerTester<int32_t> mt;
Node* code_node = mt.HeapConstant(code);
Node* call_inputs[] = {mt.PointerConstant(code_start),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 1, &code_node);
Node* call = mt.AddNode(mt.common()->Call(desc), 2, call_inputs);
uint32_t expect = 0;
Node* result = mt.Int32Constant(0);

View File

@ -13,11 +13,16 @@
#include "src/compiler/operator.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/raw-machine-assembler.h"
#include "src/compiler/wasm-compiler.h"
#include "src/machine-type.h"
#include "src/objects-inl.h"
#include "src/objects.h"
#include "src/simulator.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/fuzzer/fuzzer-support.h"
@ -91,49 +96,8 @@ int num_registers(MachineType type) {
}
}
int size(MachineType type) {
return 1 << ElementSizeLog2Of(type.representation());
}
int index(MachineType type) { return static_cast<int>(type.representation()); }
const int* codes(MachineType type) {
const RegisterConfiguration* config = RegisterConfiguration::Default();
switch (type.representation()) {
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
return config->allocatable_general_codes();
case MachineRepresentation::kFloat32:
return config->allocatable_float_codes();
case MachineRepresentation::kFloat64:
return config->allocatable_double_codes();
default:
UNREACHABLE();
}
}
LinkageLocation AllocateLocation(MachineType type, int* int_count,
int* float_count, int* stack_slots) {
int* count = IsFloatingPoint(type.representation()) ? float_count : int_count;
int reg_code = *count;
#if V8_TARGET_ARCH_ARM
// Allocate floats using a double register, but modify the code to
// reflect how ARM FP registers alias.
if (type == MachineType::Float32()) {
reg_code *= 2;
}
#endif
LinkageLocation location = LinkageLocation::ForAnyRegister(); // Dummy.
if (reg_code < num_registers(type)) {
location = LinkageLocation::ForRegister(codes(type)[reg_code], type);
} else {
location = LinkageLocation::ForCallerFrameSlot(-*stack_slots - 1, type);
*stack_slots += std::max(1, size(type) / kPointerSize);
}
++*count;
return location;
}
Node* Constant(RawMachineAssembler& m, MachineType type, int value) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
@ -167,51 +131,40 @@ Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) {
CallDescriptor* CreateRandomCallDescriptor(Zone* zone, size_t return_count,
size_t param_count,
InputProvider* input) {
LocationSignature::Builder locations(zone, return_count, param_count);
int stack_slots = 0;
int int_params = 0;
int float_params = 0;
wasm::FunctionSig::Builder builder(zone, return_count, param_count);
for (size_t i = 0; i < param_count; i++) {
MachineType type = RandomType(input);
LinkageLocation location =
AllocateLocation(type, &int_params, &float_params, &stack_slots);
locations.AddParam(location);
builder.AddParam(type.representation());
}
// Read the end byte of the parameters.
input->NextInt8(1);
int stack_params = stack_slots;
#if V8_TARGET_ARCH_ARM64
// Align the stack slots.
stack_slots = stack_slots + (stack_slots % 2);
#endif
int aligned_stack_params = stack_slots;
int int_returns = 0;
int float_returns = 0;
for (size_t i = 0; i < return_count; i++) {
MachineType type = RandomType(input);
LinkageLocation location =
AllocateLocation(type, &int_returns, &float_returns, &stack_slots);
locations.AddReturn(location);
builder.AddReturn(type.representation());
}
int stack_returns = stack_slots - aligned_stack_params;
MachineType target_type = MachineType::AnyTagged();
LinkageLocation target_loc = LinkageLocation::ForAnyRegister(target_type);
return new (zone) CallDescriptor( // --
CallDescriptor::kCallCodeObject, // kind
target_type, // target MachineType
target_loc, // target location
locations.Build(), // location_sig
stack_params, // on-stack parameter count
compiler::Operator::kNoProperties, // properties
0, // callee-saved registers
0, // callee-saved fp regs
CallDescriptor::kNoFlags, // flags
"c-call", // debug name
0, // allocatable registers
stack_returns); // on-stack return count
return compiler::GetWasmCallDescriptor(zone, builder.Build());
}
std::unique_ptr<wasm::NativeModule> AllocateNativeModule(i::Isolate* isolate,
size_t code_size) {
// We have to add the code object to a NativeModule, because the
// WasmCallDescriptor assumes that code is on the native heap and not
// within a code object.
std::unique_ptr<wasm::NativeModule> module =
isolate->wasm_engine()->code_manager()->NewNativeModule(code_size, 1, 0,
false);
// TODO(mstarzinger): Remove the WasmCompiledModule here as soon as source
// positions are stored in the WasmCode directly.
Handle<WasmCompiledModule> compiled_module = Handle<WasmCompiledModule>::cast(
isolate->factory()->NewStruct(WASM_COMPILED_MODULE_TYPE, TENURED));
Handle<FixedArray> source_positions =
isolate->factory()->NewFixedArray(1, TENURED);
compiled_module->set_source_positions(*source_positions);
module->SetCompiledModule(compiled_module);
return module;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
@ -239,9 +192,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (FLAG_wasm_fuzzer_gen_test) {
// Print some debugging output which describes the produced signature.
printf("[");
for (size_t j = 0; j < desc->ParameterCount(); ++j) {
printf(" %s",
MachineReprToString(desc->GetParameterType(j).representation()));
for (size_t j = 0; j < param_count; ++j) {
// Parameter 0 is the WasmContext.
printf(" %s", MachineReprToString(
desc->GetParameterType(j + 1).representation()));
}
printf(" ] -> [");
for (size_t j = 0; j < desc->ReturnCount(); ++j) {
@ -258,14 +212,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Trivial hash table for the number of occurrences of parameter types. The
// MachineRepresentation of the parameter types is used as hash code.
int counts[kNumMachineRepresentations] = {0};
for (size_t i = 0; i < desc->ParameterCount(); ++i) {
++counts[index(desc->GetParameterType(i))];
for (size_t i = 0; i < param_count; ++i) {
// Parameter 0 is the WasmContext.
++counts[index(desc->GetParameterType(i + 1))];
}
// Generate random inputs.
std::unique_ptr<int[]> inputs(new int[desc->ParameterCount()]);
std::unique_ptr<int[]> inputs(new int[param_count]);
std::unique_ptr<int[]> outputs(new int[desc->ReturnCount()]);
for (size_t i = 0; i < desc->ParameterCount(); ++i) {
for (size_t i = 0; i < param_count; ++i) {
inputs[i] = input.NextInt32(10000);
}
@ -275,11 +230,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
InstructionSelector::SupportedMachineOperatorFlags());
// Generate callee, returning random picks of its parameters.
std::unique_ptr<Node* []> params(new Node*[desc->ParameterCount() + 1]);
std::unique_ptr<Node* []> returns(new Node*[desc->ReturnCount()]);
for (size_t i = 0; i < desc->ParameterCount(); ++i) {
params[i] = callee.Parameter(i);
std::unique_ptr<Node* []> params(new Node*[desc->ParameterCount() + 2]);
// The first input of a return is the number of stack slots that should be
// popped before returning.
std::unique_ptr<Node* []> returns(new Node*[desc->ReturnCount() + 1]);
for (size_t i = 0; i < param_count; ++i) {
// Parameter(0) is the WasmContext.
params[i] = callee.Parameter(i + 1);
}
for (size_t i = 0; i < desc->ReturnCount(); ++i) {
MachineType type = desc->GetReturnType(i);
// Find a random same-type parameter to return. Use a constant if none.
@ -289,7 +248,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
} else {
int n = input.NextInt32(counts[index(type)]);
int k = 0;
while (desc->GetParameterType(k) != desc->GetReturnType(i) || --n > 0) {
while (desc->GetParameterType(k + 1) != desc->GetReturnType(i) ||
--n > 0) {
++k;
}
returns[i] = params[k];
@ -302,6 +262,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
Handle<Code> code = Pipeline::GenerateCodeForTesting(
&info, i_isolate, desc, callee.graph(), callee.Export());
std::unique_ptr<wasm::NativeModule> module =
AllocateNativeModule(i_isolate, code->instruction_size());
byte* code_start = module->AddCodeCopy(code, wasm::WasmCode::kFunction, 0)
->instructions()
.start();
// Generate wrapper.
int expect = 0;
@ -315,13 +280,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
params[0] = caller.HeapConstant(code);
for (size_t i = 0; i < desc->ParameterCount(); ++i) {
params[i + 1] = Constant(caller, desc->GetParameterType(i), inputs[i]);
params[0] = caller.PointerConstant(code_start);
// WasmContext dummy.
params[1] = caller.PointerConstant(nullptr);
for (size_t i = 0; i < param_count; ++i) {
params[i + 2] = Constant(caller, desc->GetParameterType(i + 1), inputs[i]);
}
Node* call = caller.AddNode(caller.common()->Call(desc),
static_cast<int>(desc->ParameterCount() + 1),
params.get());
static_cast<int>(param_count + 2), params.get());
Node* ret = Constant(caller, MachineType::Int32(), 0);
for (size_t i = 0; i < desc->ReturnCount(); ++i) {
// Skip roughly one third of the outputs.