multi-return.cc 10.5 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2018 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 <cstddef>
#include <cstdint>

8 9
#include "src/codegen/machine-type.h"
#include "src/codegen/optimized-compilation-info.h"
10
#include "src/compiler/backend/instruction-selector.h"
11 12 13 14 15 16
#include "src/compiler/graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/raw-machine-assembler.h"
17
#include "src/compiler/wasm-compiler.h"
18 19 20
#include "src/objects-inl.h"
#include "src/objects.h"
#include "src/simulator.h"
21
#include "src/wasm/wasm-engine.h"
22
#include "src/wasm/wasm-features.h"
23
#include "src/wasm/wasm-limits.h"
24 25 26
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes.h"
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/fuzzer/fuzzer-support.h"

namespace v8 {
namespace internal {
namespace compiler {
namespace fuzzer {

constexpr MachineType kTypes[] = {
    // The first entry is just a placeholder, because '0' is a separator.
    MachineType(),
#if !V8_TARGET_ARCH_32_BIT
    MachineType::Int64(),
#endif
    MachineType::Int32(), MachineType::Float32(), MachineType::Float64()};

static constexpr int kNumTypes = arraysize(kTypes);

class InputProvider {
 public:
  InputProvider(const uint8_t* data, size_t size)
      : current_(data), end_(data + size) {}

  size_t NumNonZeroBytes(size_t offset, int limit) {
    DCHECK_LE(limit, std::numeric_limits<uint8_t>::max());
    DCHECK_GE(current_ + offset, current_);
    const uint8_t* p;
    for (p = current_ + offset; p < end_; ++p) {
      if (*p % limit == 0) break;
    }
    return p - current_ - offset;
  }

  int NextInt8(int limit) {
    DCHECK_LE(limit, std::numeric_limits<uint8_t>::max());
    if (current_ == end_) return 0;
    uint8_t result = *current_;
    current_++;
    return static_cast<int>(result) % limit;
  }

  int NextInt32(int limit) {
    if (current_ + sizeof(uint32_t) > end_) return 0;
71 72
    int result =
        ReadLittleEndianValue<int>(reinterpret_cast<Address>(current_));
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
    current_ += sizeof(uint32_t);
    return result % limit;
  }

 private:
  const uint8_t* current_;
  const uint8_t* end_;
};

MachineType RandomType(InputProvider* input) {
  return kTypes[input->NextInt8(kNumTypes)];
}

int index(MachineType type) { return static_cast<int>(type.representation()); }

Node* Constant(RawMachineAssembler& m, MachineType type, int value) {
  switch (type.representation()) {
    case MachineRepresentation::kWord32:
      return m.Int32Constant(static_cast<int32_t>(value));
    case MachineRepresentation::kWord64:
      return m.Int64Constant(static_cast<int64_t>(value));
    case MachineRepresentation::kFloat32:
      return m.Float32Constant(static_cast<float>(value));
    case MachineRepresentation::kFloat64:
      return m.Float64Constant(static_cast<double>(value));
    default:
      UNREACHABLE();
  }
}

Node* ToInt32(RawMachineAssembler& m, MachineType type, Node* a) {
  switch (type.representation()) {
    case MachineRepresentation::kWord32:
      return a;
    case MachineRepresentation::kWord64:
      return m.TruncateInt64ToInt32(a);
    case MachineRepresentation::kFloat32:
      return m.TruncateFloat32ToInt32(a);
    case MachineRepresentation::kFloat64:
      return m.RoundFloat64ToInt32(a);
    default:
      UNREACHABLE();
  }
}

CallDescriptor* CreateRandomCallDescriptor(Zone* zone, size_t return_count,
                                           size_t param_count,
                                           InputProvider* input) {
121
  wasm::FunctionSig::Builder builder(zone, return_count, param_count);
122 123
  for (size_t i = 0; i < param_count; i++) {
    MachineType type = RandomType(input);
124
    builder.AddParam(wasm::ValueTypes::ValueTypeFor(type));
125 126 127 128 129 130
  }
  // Read the end byte of the parameters.
  input->NextInt8(1);

  for (size_t i = 0; i < return_count; i++) {
    MachineType type = RandomType(input);
131
    builder.AddReturn(wasm::ValueTypes::ValueTypeFor(type));
132
  }
133 134 135 136

  return compiler::GetWasmCallDescriptor(zone, builder.Build());
}

137
std::shared_ptr<wasm::NativeModule> AllocateNativeModule(i::Isolate* isolate,
138
                                                         size_t code_size) {
139 140
  std::shared_ptr<wasm::WasmModule> module(new wasm::WasmModule);
  module->num_declared_functions = 1;
141

142 143 144
  // 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.
145
  return isolate->wasm_engine()->NewNativeModule(
146
      isolate, i::wasm::kAllWasmFeatures, code_size, false, std::move(module));
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();
  i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(support->GetContext());
  v8::TryCatch try_catch(isolate);
  v8::internal::AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);

  InputProvider input(data, size);
  // Create randomized descriptor.
  size_t param_count = input.NumNonZeroBytes(0, kNumTypes);
163 164
  if (param_count > Code::kMaxArguments) return 0;

165
  size_t return_count = input.NumNonZeroBytes(param_count + 1, kNumTypes);
166 167
  if (return_count > wasm::kV8MaxWasmFunctionMultiReturns) return 0;

168 169 170 171 172 173
  CallDescriptor* desc =
      CreateRandomCallDescriptor(&zone, return_count, param_count, &input);

  if (FLAG_wasm_fuzzer_gen_test) {
    // Print some debugging output which describes the produced signature.
    printf("[");
174 175 176 177
    for (size_t j = 0; j < param_count; ++j) {
      // Parameter 0 is the WasmContext.
      printf(" %s", MachineReprToString(
                        desc->GetParameterType(j + 1).representation()));
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
    }
    printf(" ] -> [");
    for (size_t j = 0; j < desc->ReturnCount(); ++j) {
      printf(" %s",
             MachineReprToString(desc->GetReturnType(j).representation()));
    }
    printf(" ]\n\n");
  }

  // Count parameters of each type.
  constexpr size_t kNumMachineRepresentations =
      static_cast<size_t>(MachineRepresentation::kLastRepresentation) + 1;

  // 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};
194 195 196
  for (size_t i = 0; i < param_count; ++i) {
    // Parameter 0 is the WasmContext.
    ++counts[index(desc->GetParameterType(i + 1))];
197 198 199
  }

  // Generate random inputs.
200
  std::unique_ptr<int[]> inputs(new int[param_count]);
201
  std::unique_ptr<int[]> outputs(new int[desc->ReturnCount()]);
202
  for (size_t i = 0; i < param_count; ++i) {
203 204 205 206 207 208 209 210 211
    inputs[i] = input.NextInt32(10000);
  }

  RawMachineAssembler callee(
      i_isolate, new (&zone) Graph(&zone), desc,
      MachineType::PointerRepresentation(),
      InstructionSelector::SupportedMachineOperatorFlags());

  // Generate callee, returning random picks of its parameters.
212 213 214 215 216 217 218
  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);
219
  }
220

221 222 223 224 225 226 227
  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.
    if (counts[index(type)] == 0) {
      returns[i] = Constant(callee, type, 42);
      outputs[i] = 42;
    } else {
228
      int n = input.NextInt32(counts[index(type)]);
229
      int k = 0;
230 231
      while (desc->GetParameterType(k + 1) != desc->GetReturnType(i) ||
             --n > 0) {
232 233 234 235 236 237 238 239
        ++k;
      }
      returns[i] = params[k];
      outputs[i] = inputs[k];
    }
  }
  callee.Return(static_cast<int>(desc->ReturnCount()), returns.get());

240
  OptimizedCompilationInfo info(ArrayVector("testing"), &zone, Code::STUB);
241 242 243 244
  Handle<Code> code = Pipeline::GenerateCodeForTesting(
                          &info, i_isolate, desc, callee.graph(),
                          AssemblerOptions::Default(i_isolate), callee.Export())
                          .ToHandleChecked();
245

246
  std::shared_ptr<wasm::NativeModule> module =
247
      AllocateNativeModule(i_isolate, code->raw_instruction_size());
248
  wasm::WasmCodeRefScope wasm_code_ref_scope;
249
  byte* code_start = module->AddCodeForTesting(code)->instructions().begin();
250 251 252 253 254 255 256 257 258 259 260 261 262
  // Generate wrapper.
  int expect = 0;

  MachineSignature::Builder sig_builder(&zone, 1, 0);
  sig_builder.AddReturn(MachineType::Int32());

  CallDescriptor* wrapper_desc =
      Linkage::GetSimplifiedCDescriptor(&zone, sig_builder.Build());
  RawMachineAssembler caller(
      i_isolate, new (&zone) Graph(&zone), wrapper_desc,
      MachineType::PointerRepresentation(),
      InstructionSelector::SupportedMachineOperatorFlags());

263 264 265 266 267
  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]);
268 269
  }
  Node* call = caller.AddNode(caller.common()->Call(desc),
270
                              static_cast<int>(param_count + 2), params.get());
271 272 273 274 275 276 277 278 279 280 281 282 283
  Node* ret = Constant(caller, MachineType::Int32(), 0);
  for (size_t i = 0; i < desc->ReturnCount(); ++i) {
    // Skip roughly one third of the outputs.
    if (input.NextInt8(3) == 0) continue;
    Node* ret_i = (desc->ReturnCount() == 1)
                      ? call
                      : caller.AddNode(caller.common()->Projection(i), call);
    ret = caller.Int32Add(ret, ToInt32(caller, desc->GetReturnType(i), ret_i));
    expect += outputs[i];
  }
  caller.Return(ret);

  // Call the wrapper.
284
  OptimizedCompilationInfo wrapper_info(ArrayVector("wrapper"), &zone,
285
                                        Code::STUB);
286
  Handle<Code> wrapper_code =
287 288 289
      Pipeline::GenerateCodeForTesting(
          &wrapper_info, i_isolate, wrapper_desc, caller.graph(),
          AssemblerOptions::Default(i_isolate), caller.Export())
290
          .ToHandleChecked();
291

292 293 294 295 296 297 298 299 300 301 302
  auto fn = GeneratedCode<int32_t>::FromCode(*wrapper_code);
  int result = fn.Call();

  CHECK_EQ(expect, result);
  return 0;
}

}  // namespace fuzzer
}  // namespace compiler
}  // namespace internal
}  // namespace v8