| Index: test/cctest/compiler/test-structured-ifbuilder-fuzzer.cc
|
| diff --git a/test/cctest/compiler/test-structured-ifbuilder-fuzzer.cc b/test/cctest/compiler/test-structured-ifbuilder-fuzzer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1eef8620faef3fa1412cf8945dd42b0461bb4afd
|
| --- /dev/null
|
| +++ b/test/cctest/compiler/test-structured-ifbuilder-fuzzer.cc
|
| @@ -0,0 +1,677 @@
|
| +// Copyright 2014 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 <string>
|
| +
|
| +#include "src/v8.h"
|
| +#include "test/cctest/cctest.h"
|
| +
|
| +#include "src/base/utils/random-number-generator.h"
|
| +#include "test/cctest/compiler/codegen-tester.h"
|
| +
|
| +#if V8_TURBOFAN_TARGET
|
| +
|
| +using namespace v8::internal;
|
| +using namespace v8::internal::compiler;
|
| +
|
| +typedef StructuredMachineAssembler::IfBuilder IfBuilder;
|
| +typedef StructuredMachineAssembler::LoopBuilder Loop;
|
| +
|
| +static const int32_t kUninitializedVariableOffset = -1;
|
| +static const int32_t kUninitializedOutput = -1;
|
| +static const int32_t kVerifiedOutput = -2;
|
| +
|
| +static const int32_t kInitalVar = 1013;
|
| +static const int32_t kConjunctionInc = 1069;
|
| +static const int32_t kDisjunctionInc = 1151;
|
| +static const int32_t kThenInc = 1223;
|
| +static const int32_t kElseInc = 1291;
|
| +static const int32_t kIfInc = 1373;
|
| +
|
| +class IfBuilderModel {
|
| + public:
|
| + explicit IfBuilderModel(Zone* zone)
|
| + : zone_(zone),
|
| + variable_offset_(0),
|
| + root_(new(zone_) Node(NULL)),
|
| + current_node_(root_),
|
| + current_expression_(NULL) {}
|
| +
|
| + void If() {
|
| + if (current_node_->else_node != NULL) {
|
| + current_node_ = current_node_->else_node;
|
| + } else if (current_node_->then_node != NULL) {
|
| + current_node_ = current_node_->then_node;
|
| + }
|
| + ASSERT(current_expression_ == NULL);
|
| + current_expression_ = new(zone_) Expression(zone_, NULL);
|
| + current_node_->condition = current_expression_;
|
| + }
|
| + void IfNode() { LastChild()->variable_offset = variable_offset_++; }
|
| +
|
| + void OpenParen() { current_expression_ = LastChild(); }
|
| + void CloseParen() { current_expression_ = current_expression_->parent; }
|
| +
|
| + void And() { NewChild()->conjunction = true; }
|
| + void Or() { NewChild()->disjunction = true; }
|
| +
|
| + void Then() {
|
| + ASSERT(current_expression_ == NULL || current_expression_->parent == NULL);
|
| + current_expression_ = NULL;
|
| + ASSERT(current_node_->then_node == NULL);
|
| + current_node_->then_node = new(zone_) Node(current_node_);
|
| + }
|
| + void Else() {
|
| + ASSERT(current_expression_ == NULL || current_expression_->parent == NULL);
|
| + current_expression_ = NULL;
|
| + ASSERT(current_node_->else_node == NULL);
|
| + current_node_->else_node = new(zone_) Node(current_node_);
|
| + }
|
| + void Return() {
|
| + if (current_node_->else_node != NULL) {
|
| + current_node_->else_node->returns = true;
|
| + } else if (current_node_->then_node != NULL) {
|
| + current_node_->then_node->returns = true;
|
| + } else {
|
| + CHECK(false);
|
| + }
|
| + }
|
| + void End() {}
|
| +
|
| + void Print(std::vector<char>* v) { PrintRecursive(v, root_); }
|
| +
|
| + struct VerificationState {
|
| + int32_t* inputs;
|
| + int32_t* outputs;
|
| + int32_t var;
|
| + };
|
| +
|
| + int32_t Verify(int length, int32_t* inputs, int32_t* outputs) {
|
| + CHECK_EQ(variable_offset_, length);
|
| + // Input/Output verification.
|
| + for (int i = 0; i < length; ++i) {
|
| + CHECK(inputs[i] == 0 || inputs[i] == 1);
|
| + CHECK(outputs[i] == kUninitializedOutput || outputs[i] >= 0);
|
| + }
|
| + // Do verification.
|
| + VerificationState state;
|
| + state.inputs = inputs;
|
| + state.outputs = outputs;
|
| + state.var = kInitalVar;
|
| + VerifyRecursive(root_, &state);
|
| + // Verify all outputs marked.
|
| + for (int i = 0; i < length; ++i) {
|
| + CHECK(outputs[i] == kUninitializedOutput ||
|
| + outputs[i] == kVerifiedOutput);
|
| + }
|
| + return state.var;
|
| + }
|
| +
|
| + private:
|
| + struct Expression;
|
| + typedef std::vector<Expression*, zone_allocator<Expression*> > Expressions;
|
| +
|
| + struct Expression : public ZoneObject {
|
| + Expression(Zone* zone, Expression* p)
|
| + : variable_offset(kUninitializedVariableOffset),
|
| + disjunction(false),
|
| + conjunction(false),
|
| + parent(p),
|
| + children(Expressions::allocator_type(zone)) {}
|
| + int variable_offset;
|
| + bool disjunction;
|
| + bool conjunction;
|
| + Expression* parent;
|
| + Expressions children;
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(Expression);
|
| + };
|
| +
|
| + struct Node : public ZoneObject {
|
| + explicit Node(Node* p)
|
| + : parent(p),
|
| + condition(NULL),
|
| + then_node(NULL),
|
| + else_node(NULL),
|
| + returns(false) {}
|
| + Node* parent;
|
| + Expression* condition;
|
| + Node* then_node;
|
| + Node* else_node;
|
| + bool returns;
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(Node);
|
| + };
|
| +
|
| + Expression* LastChild() {
|
| + if (current_expression_->children.empty()) {
|
| + current_expression_->children.push_back(
|
| + new(zone_) Expression(zone_, current_expression_));
|
| + }
|
| + return current_expression_->children.back();
|
| + }
|
| +
|
| + Expression* NewChild() {
|
| + Expression* child = new(zone_) Expression(zone_, current_expression_);
|
| + current_expression_->children.push_back(child);
|
| + return child;
|
| + }
|
| +
|
| + static void PrintRecursive(std::vector<char>* v, Expression* expression) {
|
| + CHECK(expression != NULL);
|
| + if (expression->conjunction) {
|
| + ASSERT(!expression->disjunction);
|
| + v->push_back('&');
|
| + } else if (expression->disjunction) {
|
| + v->push_back('|');
|
| + }
|
| + if (expression->variable_offset != kUninitializedVariableOffset) {
|
| + v->push_back('v');
|
| + }
|
| + Expressions& children = expression->children;
|
| + if (children.empty()) return;
|
| + v->push_back('(');
|
| + for (Expressions::iterator i = children.begin();
|
| + i != children.end(); ++i) {
|
| + PrintRecursive(v, *i);
|
| + }
|
| + v->push_back(')');
|
| + }
|
| +
|
| + static void PrintRecursive(std::vector<char>* v, Node* node) {
|
| + // Termination condition.
|
| + if (node->condition == NULL) {
|
| + CHECK(node->then_node == NULL &&
|
| + node->else_node == NULL);
|
| + if (node->returns) v->push_back('r');
|
| + return;
|
| + }
|
| + CHECK(!node->returns);
|
| + v->push_back('i');
|
| + PrintRecursive(v, node->condition);
|
| + if (node->then_node != NULL) {
|
| + v->push_back('t');
|
| + PrintRecursive(v, node->then_node);
|
| + }
|
| + if (node->else_node != NULL) {
|
| + v->push_back('e');
|
| + PrintRecursive(v, node->else_node);
|
| + }
|
| + }
|
| +
|
| + static bool VerifyRecursive(Expression* expression,
|
| + VerificationState* state) {
|
| + bool result = false;
|
| + bool first_iteration = true;
|
| + Expressions& children = expression->children;
|
| + CHECK(!children.empty());
|
| + for (Expressions::iterator i = children.begin();
|
| + i != children.end(); ++i) {
|
| + Expression* child = *i;
|
| + // Short circuit evaluation,
|
| + // but mixes of &&s and ||s have weird semantics.
|
| + if ((child->conjunction && !result) ||
|
| + (child->disjunction && result)) {
|
| + continue;
|
| + }
|
| + if (child->conjunction) state->var += kConjunctionInc;
|
| + if (child->disjunction) state->var += kDisjunctionInc;
|
| + bool child_result;
|
| + if (child->variable_offset != kUninitializedVariableOffset) {
|
| + // Verify output
|
| + CHECK_EQ(state->var, state->outputs[child->variable_offset]);
|
| + state->outputs[child->variable_offset] = kVerifiedOutput; // Mark seen.
|
| + child_result = state->inputs[child->variable_offset];
|
| + CHECK(child->children.empty());
|
| + state->var += kIfInc;
|
| + } else {
|
| + child_result = VerifyRecursive(child, state);
|
| + }
|
| + if (child->conjunction) {
|
| + result &= child_result;
|
| + } else if (child->disjunction) {
|
| + result |= child_result;
|
| + } else {
|
| + CHECK(first_iteration);
|
| + result = child_result;
|
| + }
|
| + first_iteration = false;
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + static void VerifyRecursive(Node* node, VerificationState* state) {
|
| + if (node->condition == NULL) return;
|
| + bool result = VerifyRecursive(node->condition, state);
|
| + if (result) {
|
| + if (node->then_node) {
|
| + state->var += kThenInc;
|
| + return VerifyRecursive(node->then_node, state);
|
| + }
|
| + } else {
|
| + if (node->else_node) {
|
| + state->var += kElseInc;
|
| + return VerifyRecursive(node->else_node, state);
|
| + }
|
| + }
|
| + }
|
| +
|
| + Zone* zone_;
|
| + int variable_offset_;
|
| + Node* root_;
|
| + Node* current_node_;
|
| + Expression* current_expression_;
|
| + DISALLOW_COPY_AND_ASSIGN(IfBuilderModel);
|
| +};
|
| +
|
| +
|
| +class IfBuilderGenerator : public StructuredMachineAssemblerTester<int32_t> {
|
| + public:
|
| + IfBuilderGenerator()
|
| + : StructuredMachineAssemblerTester(MachineOperatorBuilder::pointer_rep(),
|
| + MachineOperatorBuilder::pointer_rep()),
|
| + var_(NewVariable(Int32Constant(kInitalVar))),
|
| + c_(this),
|
| + m_(this->zone()),
|
| + one_(Int32Constant(1)),
|
| + offset_(0) {}
|
| +
|
| + static void GenerateExpression(v8::base::RandomNumberGenerator* rng,
|
| + std::vector<char>* v,
|
| + int n_vars) {
|
| + int depth = 1;
|
| + v->push_back('(');
|
| + bool need_if = true;
|
| + bool populated = false;
|
| + while (n_vars != 0) {
|
| + if (need_if) {
|
| + // can nest a paren or do a variable
|
| + if (rng->NextBool()) {
|
| + v->push_back('v');
|
| + n_vars--;
|
| + need_if = false;
|
| + populated = true;
|
| + } else {
|
| + v->push_back('(');
|
| + depth++;
|
| + populated = false;
|
| + }
|
| + } else {
|
| + // can pop, do && or do ||
|
| + int options = 3;
|
| + if (depth == 1 || !populated) {
|
| + options--;
|
| + }
|
| + switch (rng->NextInt(options)) {
|
| + case 0:
|
| + v->push_back('&');
|
| + need_if = true;
|
| + break;
|
| + case 1:
|
| + v->push_back('|');
|
| + need_if = true;
|
| + break;
|
| + case 2:
|
| + v->push_back(')');
|
| + depth--;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + CHECK(!need_if);
|
| + while (depth != 0) {
|
| + v->push_back(')');
|
| + depth--;
|
| + }
|
| + }
|
| +
|
| + static void GenerateIfThenElse(v8::base::RandomNumberGenerator* rng,
|
| + std::vector<char>* v,
|
| + int n_ifs,
|
| + int max_exp_length) {
|
| + CHECK_GT(n_ifs, 0);
|
| + CHECK_GT(max_exp_length, 0);
|
| + bool have_env = true;
|
| + bool then_done = false;
|
| + bool else_done = false;
|
| + bool first_iteration = true;
|
| + while (n_ifs != 0) {
|
| + if (have_env) {
|
| + int options = 3;
|
| + if (else_done || first_iteration) { // Don't do else or return
|
| + options -= 2;
|
| + first_iteration = false;
|
| + }
|
| + switch (rng->NextInt(options)) {
|
| + case 0:
|
| + v->push_back('i');
|
| + n_ifs--;
|
| + have_env = false;
|
| + GenerateExpression(rng, v, rng->NextInt(max_exp_length) + 1);
|
| + break;
|
| + case 1:
|
| + v->push_back('r');
|
| + have_env = false;
|
| + break;
|
| + case 2:
|
| + v->push_back('e');
|
| + else_done = true;
|
| + then_done = false;
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + } else { // Can only do then or else
|
| + int options = 2;
|
| + if (then_done) options--;
|
| + switch (rng->NextInt(options)) {
|
| + case 0:
|
| + v->push_back('e');
|
| + else_done = true;
|
| + then_done = false;
|
| + break;
|
| + case 1:
|
| + v->push_back('t');
|
| + then_done = true;
|
| + else_done = false;
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + have_env = true;
|
| + }
|
| + }
|
| + // Last instruction must have been an if, can complete it in several ways.
|
| + int options = 2;
|
| + if (then_done && !else_done) options++;
|
| + switch (rng->NextInt(3)) {
|
| + case 0:
|
| + // Do nothing.
|
| + break;
|
| + case 1:
|
| + v->push_back('t');
|
| + switch (rng->NextInt(3)) {
|
| + case 0:
|
| + v->push_back('r');
|
| + break;
|
| + case 1:
|
| + v->push_back('e');
|
| + break;
|
| + case 2:
|
| + v->push_back('e');
|
| + v->push_back('r');
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + break;
|
| + case 2:
|
| + v->push_back('e');
|
| + if (rng->NextBool()) v->push_back('r');
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + }
|
| +
|
| + std::string::const_iterator ParseExpression(std::string::const_iterator it,
|
| + std::string::const_iterator end) {
|
| + // Prepare for expression.
|
| + m_.If();
|
| + c_.If();
|
| + int depth = 0;
|
| + for (; it != end; ++it) {
|
| + switch (*it) {
|
| + case 'v':
|
| + m_.IfNode();
|
| + {
|
| + Node* offset = Int32Constant(offset_ * 4);
|
| + Store(kMachineWord32, Parameter(1), offset, var_.Get());
|
| + var_.Set(Int32Add(var_.Get(), Int32Constant(kIfInc)));
|
| + c_.If(Load(kMachineWord32, Parameter(0), offset));
|
| + offset_++;
|
| + }
|
| + break;
|
| + case '&':
|
| + m_.And();
|
| + c_.And();
|
| + var_.Set(Int32Add(var_.Get(), Int32Constant(kConjunctionInc)));
|
| + break;
|
| + case '|':
|
| + m_.Or();
|
| + c_.Or();
|
| + var_.Set(Int32Add(var_.Get(), Int32Constant(kDisjunctionInc)));
|
| + break;
|
| + case '(':
|
| + if (depth != 0) {
|
| + m_.OpenParen();
|
| + c_.OpenParen();
|
| + }
|
| + depth++;
|
| + break;
|
| + case ')':
|
| + depth--;
|
| + if (depth == 0) return it;
|
| + m_.CloseParen();
|
| + c_.CloseParen();
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + }
|
| + CHECK(false);
|
| + return it;
|
| + }
|
| +
|
| + void ParseIfThenElse(const std::string& str) {
|
| + int n_vars = 0;
|
| + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) {
|
| + if (*it == 'v') n_vars++;
|
| + }
|
| + InitializeConstants(n_vars);
|
| + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) {
|
| + switch (*it) {
|
| + case 'i':
|
| + {
|
| + it++;
|
| + CHECK(it != str.end());
|
| + CHECK_EQ('(', *it);
|
| + it = ParseExpression(it, str.end());
|
| + CHECK_EQ(')', *it);
|
| + break;
|
| + }
|
| + case 't':
|
| + m_.Then();
|
| + c_.Then();
|
| + var_.Set(Int32Add(var_.Get(), Int32Constant(kThenInc)));
|
| + break;
|
| + case 'e':
|
| + m_.Else();
|
| + c_.Else();
|
| + var_.Set(Int32Add(var_.Get(), Int32Constant(kElseInc)));
|
| + break;
|
| + case 'r':
|
| + m_.Return();
|
| + Return(var_.Get());
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + }
|
| + m_.End();
|
| + c_.End();
|
| + Return(var_.Get());
|
| + // Compare generated model to parsed version.
|
| + {
|
| + std::vector<char> v;
|
| + m_.Print(&v);
|
| + std::string m_str(v.begin(), v.end());
|
| + CHECK(m_str == str);
|
| + }
|
| + }
|
| +
|
| + void ParseExpression(const std::string& str) {
|
| + CHECK(inputs_.is_empty());
|
| + std::string wrapped = "i(" + str + ")te";
|
| + ParseIfThenElse(wrapped);
|
| + }
|
| +
|
| + void ParseRandomIfThenElse(v8::base::RandomNumberGenerator* rng,
|
| + int n_ifs,
|
| + int n_vars) {
|
| + std::vector<char> v;
|
| + GenerateIfThenElse(rng, &v, n_ifs, n_vars);
|
| + std::string str(v.begin(), v.end());
|
| + ParseIfThenElse(str);
|
| + }
|
| +
|
| + void RunRandom(v8::base::RandomNumberGenerator* rng) {
|
| + // TODO(dcarney): permute inputs via model.
|
| + // TODO(dcarney): compute test_cases from n_ifs and n_vars.
|
| + int test_cases = 100;
|
| + for (int test = 0; test < test_cases; test++) {
|
| + Initialize();
|
| + for (int i = 0; i < offset_; i++) {
|
| + inputs_[i] = rng->NextBool();
|
| + }
|
| + DoCall();
|
| + }
|
| + }
|
| +
|
| + void Run(const std::string& str, int32_t expected) {
|
| + Initialize();
|
| + int offset = 0;
|
| + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) {
|
| + switch (*it) {
|
| + case 't':
|
| + inputs_[offset++] = 1;
|
| + break;
|
| + case 'f':
|
| + inputs_[offset++] = 0;
|
| + break;
|
| + default:
|
| + CHECK(false);
|
| + }
|
| + }
|
| + CHECK_EQ(offset_, offset);
|
| + // Call.
|
| + int32_t result = DoCall();
|
| + CHECK_EQ(result, expected);
|
| + }
|
| +
|
| + private:
|
| + typedef std::vector<int32_t, zone_allocator<int32_t> > IOVector;
|
| +
|
| + void InitializeConstants(int n_vars) {
|
| + CHECK(inputs_.is_empty());
|
| + inputs_.Reset(new int32_t[n_vars]);
|
| + outputs_.Reset(new int32_t[n_vars]);
|
| + }
|
| +
|
| + void Initialize() {
|
| + for (int i = 0; i < offset_; i++) {
|
| + inputs_[i] = 0;
|
| + outputs_[i] = kUninitializedOutput;
|
| + }
|
| + }
|
| +
|
| + int32_t DoCall() {
|
| + int32_t result = Call(inputs_.get(), outputs_.get());
|
| + int32_t expected = m_.Verify(offset_, inputs_.get(), outputs_.get());
|
| + CHECK_EQ(result, expected);
|
| + return result;
|
| + }
|
| +
|
| + const v8::internal::compiler::Variable var_;
|
| + IfBuilder c_;
|
| + IfBuilderModel m_;
|
| + Node* one_;
|
| + int32_t offset_;
|
| + SmartArrayPointer<int32_t> inputs_;
|
| + SmartArrayPointer<int32_t> outputs_;
|
| +};
|
| +
|
| +
|
| +TEST(RunExpressionString) {
|
| + IfBuilderGenerator m;
|
| + m.ParseExpression("((v|v)|v)");
|
| + m.Run("ttt", kInitalVar + 1 * kIfInc + kThenInc);
|
| + m.Run("ftt", kInitalVar + 2 * kIfInc + kDisjunctionInc + kThenInc);
|
| + m.Run("fft", kInitalVar + 3 * kIfInc + 2 * kDisjunctionInc + kThenInc);
|
| + m.Run("fff", kInitalVar + 3 * kIfInc + 2 * kDisjunctionInc + kElseInc);
|
| +}
|
| +
|
| +
|
| +TEST(RunExpressionStrings) {
|
| + const char* strings[] = {
|
| + "v", "(v)", "((v))",
|
| + "v|v", "(v|v)", "((v|v))",
|
| + "v&v", "(v&v)", "((v&v))",
|
| + "v&(v)", "v&(v|v)", "v&(v|v)&v",
|
| + "v|(v)", "v|(v&v)", "v|(v&v)|v",
|
| + "v|(((v)|(v&v)|(v)|v)&(v))|v",
|
| + };
|
| + v8::base::RandomNumberGenerator rng;
|
| + for (size_t i = 0; i < ARRAY_SIZE(strings); i++) {
|
| + IfBuilderGenerator m;
|
| + m.ParseExpression(strings[i]);
|
| + m.RunRandom(&rng);
|
| + }
|
| +}
|
| +
|
| +
|
| +TEST(RunSimpleIfElseTester) {
|
| + const char* tests[] = {
|
| + "i(v)", "i(v)t", "i(v)te", "i(v)er",
|
| + "i(v)ter", "i(v)ti(v)trei(v)ei(v)ei(v)ei(v)ei(v)ei(v)ei(v)e"
|
| + };
|
| + v8::base::RandomNumberGenerator rng;
|
| + for (size_t i = 0; i < ARRAY_SIZE(tests); ++i) {
|
| + IfBuilderGenerator m;
|
| + m.ParseIfThenElse(tests[i]);
|
| + m.RunRandom(&rng);
|
| + }
|
| +}
|
| +
|
| +
|
| +TEST(RunRandomExpressions) {
|
| + v8::base::RandomNumberGenerator rng;
|
| + for (int n_vars = 1; n_vars < 12; n_vars++) {
|
| + for (int i = 0; i < n_vars * n_vars + 10; i++) {
|
| + IfBuilderGenerator m;
|
| + m.ParseRandomIfThenElse(&rng, 1, n_vars);
|
| + m.RunRandom(&rng);
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +TEST(RunRandomIfElse) {
|
| + v8::base::RandomNumberGenerator rng;
|
| + for (int n_ifs = 1; n_ifs < 12; n_ifs++) {
|
| + for (int i = 0; i < n_ifs * n_ifs + 10; i++) {
|
| + IfBuilderGenerator m;
|
| + m.ParseRandomIfThenElse(&rng, n_ifs, 1);
|
| + m.RunRandom(&rng);
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +TEST(RunRandomIfElseExpressions) {
|
| + v8::base::RandomNumberGenerator rng;
|
| + for (int n_vars = 2; n_vars < 6; n_vars++) {
|
| + for (int n_ifs = 2; n_ifs < 7; n_ifs++) {
|
| + for (int i = 0; i < n_ifs * n_vars + 10; i++) {
|
| + IfBuilderGenerator m;
|
| + m.ParseRandomIfThenElse(&rng, n_ifs, n_vars);
|
| + m.RunRandom(&rng);
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +#endif
|
|
|