schedule_test.cpp 14.6 KB
Newer Older
Paul's avatar
Paul committed
1
2
3
4
5
#include <migraphx/schedule.hpp>
#include <migraphx/operators.hpp>
#include <migraphx/generate.hpp>
#include <migraphx/instruction.hpp>
#include <migraphx/iterator_for.hpp>
Paul's avatar
Paul committed
6
#include <migraphx/ranges.hpp>
Paul's avatar
Paul committed
7
#include <migraphx/dfor.hpp>
Paul's avatar
Paul committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <basic_ops.hpp>
#include <test.hpp>

struct unary_op
{
    std::string name() const { return "unary"; }
    migraphx::argument
    compute(migraphx::context&, const migraphx::shape&, std::vector<migraphx::argument> args) const
    {
        if(args.empty())
            return {};
        return args.front();
    }

    migraphx::shape compute_shape(std::vector<migraphx::shape> inputs) const
    {
        if(inputs.empty())
            return {};
        return inputs.front();
    }
    int output_alias(const std::vector<migraphx::shape>&) const { return 0; }
};

Paul's avatar
Paul committed
31
struct nary_op
Paul's avatar
Paul committed
32
{
Paul's avatar
Paul committed
33
    std::string name() const { return "nary"; }
Paul's avatar
Paul committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    migraphx::argument
    compute(migraphx::context&, const migraphx::shape&, std::vector<migraphx::argument> args) const
    {
        if(args.empty())
            return {};
        return args.front();
    }

    migraphx::shape compute_shape(std::vector<migraphx::shape> inputs) const
    {
        if(inputs.empty())
            return {};
        return inputs.front();
    }
};

Paul's avatar
Paul committed
50
51
struct wait_event
{
Paul's avatar
Paul committed
52
    std::shared_ptr<std::vector<std::size_t>> wait_for = std::make_shared<std::vector<std::size_t>>();
Paul's avatar
Paul committed
53
54
55
    template <class Self, class F>
    static auto reflect(Self& self, F f)
    {
Paul's avatar
Paul committed
56
        return migraphx::pack(f(*self.wait_for, "wait_for"));
Paul's avatar
Paul committed
57
58
59
60
    }
    std::string name() const { return "wait_event"; }
    migraphx::shape compute_shape(const std::vector<migraphx::shape>&) const { return {}; }

Paul's avatar
Paul committed
61
62
63
    migraphx::argument compute(migraphx::context&,
                               const migraphx::shape&,
                               const std::vector<migraphx::argument>&) const
Paul's avatar
Paul committed
64
    {
Paul's avatar
Paul committed
65
66
        assert(wait_for != nullptr);
        assert(not wait_for->empty());
Paul's avatar
Paul committed
67
68
69
70
        return {};
    }
};

Paul's avatar
Paul committed
71
using instruction_map = std::unordered_map<migraphx::instruction_ref, std::size_t>;
Paul's avatar
Paul committed
72
using wait_map = std::unordered_map<migraphx::instruction_ref, std::shared_ptr<std::vector<std::size_t>>>;
Paul's avatar
Paul committed
73
74
75

struct schedule_model_test
{
Paul's avatar
Paul committed
76
77
78
    std::shared_ptr<instruction_map> ins2stream = std::make_shared<instruction_map>();
    std::shared_ptr<std::unordered_map<std::size_t, std::size_t>> wait2stream = std::make_shared<std::unordered_map<std::size_t, std::size_t>>();
    std::shared_ptr<wait_map> ins2wait_for = std::make_shared<wait_map>();
Paul's avatar
Paul committed
79
80
    std::size_t concurrency() const { return 4; }
    void
Paul's avatar
Paul committed
81
    sched(migraphx::program&, migraphx::instruction_ref ins, std::size_t n) const
Paul's avatar
Paul committed
82
83
84
    {
        (*ins2stream)[ins] = n;
    }
Paul's avatar
Paul committed
85
    void wait(migraphx::program& p, migraphx::instruction_ref ins, std::size_t wait_id) const
Paul's avatar
Paul committed
86
    {
Paul's avatar
Paul committed
87
88
89
90
91
92
93
94
95
96
97
        if (ins2wait_for->count(ins) == 0)
        {
            auto event = wait_event{};
            p.insert_instruction(ins, event);
            (*ins2wait_for)[ins] = event.wait_for;
        }
        (*ins2wait_for)[ins]->push_back(wait2stream->at(wait_id));
    }
    void record(migraphx::program& p, migraphx::instruction_ref ins, std::size_t wait_id) const
    {
        (*wait2stream)[wait_id] = ins2stream->at(ins);
Paul's avatar
Paul committed
98
99
100
    }
    std::size_t weight(const migraphx::operation& op) const
    {
Paul's avatar
Paul committed
101
        if(op.name() == "binary" or op.name() == "unary")
Paul's avatar
Paul committed
102
103
104
105
106
107
108
109
            return 4;
        else
            return 1;
    }
};

struct schedule_target
{
Paul's avatar
Paul committed
110
    schedule_model_test model{};
Paul's avatar
Paul committed
111
112
113
    std::string name() const { return "schedule"; }
    std::vector<migraphx::pass> get_passes(migraphx::context&) const
    {
Paul's avatar
Paul committed
114
        return {migraphx::schedule{model}};
Paul's avatar
Paul committed
115
116
    }
    migraphx::context get_context() const { return {}; }
Paul's avatar
Paul committed
117
118
119
120
121
122
123
124
125
126

    std::size_t get_stream(migraphx::instruction_ref ins)
    {
        return model.ins2stream->at(ins);
    }

    bool has_stream(migraphx::instruction_ref ins)
    {
        return model.ins2stream->count(ins) > 0;
    }
Paul's avatar
Paul committed
127
128
129
130
};

bool check_conflicts(migraphx::program& p, migraphx::instruction_ref x, migraphx::instruction_ref y)
{
Paul's avatar
Paul committed
131
    for(auto ins : migraphx::iterator_for(p))
Paul's avatar
Paul committed
132
    {
Paul's avatar
Paul committed
133
        if(ins->name() != "identity")
Paul's avatar
Paul committed
134
            continue;
Paul's avatar
Paul committed
135
        if(not migraphx::contains(ins->inputs(), x))
Paul's avatar
Paul committed
136
            continue;
Paul's avatar
Paul committed
137
        if(not migraphx::contains(ins->inputs(), y))
Paul's avatar
Paul committed
138
139
            continue;
        return true;
Paul's avatar
Paul committed
140
141
142
143
    }
    return false;
}

Paul's avatar
Paul committed
144
void check_conflicts(migraphx::program& p,
Paul's avatar
Paul committed
145
146
                     std::vector<std::vector<migraphx::instruction_ref>> conflicts,
                     bool result = true)
Paul's avatar
Paul committed
147
148
{
    migraphx::dfor(conflicts.size(), conflicts.size())([&](auto i, auto j) {
Paul's avatar
Paul committed
149
        if(i == j)
Paul's avatar
Paul committed
150
            return;
Paul's avatar
Paul committed
151
152
        for(auto ins1 : conflicts[i])
            for(auto ins2 : conflicts[j])
Paul's avatar
Paul committed
153
                CHECK(check_conflicts(p, ins1, ins2) == result);
Paul's avatar
Paul committed
154
155
156
    });
}

Paul's avatar
Paul committed
157
template <class T>
Paul's avatar
Paul committed
158
159
160
161
162
163
std::vector<T> sorted(std::vector<T> x)
{
    std::sort(x.begin(), x.end());
    return x;
}

Paul's avatar
Paul committed
164
template <class T>
Paul's avatar
Paul committed
165
166
167
168
169
170
171
172
173
174
175
176
177
std::vector<T> unique(std::vector<T> x)
{
    std::sort(x.begin(), x.end());
    x.erase(std::unique(x.begin(), x.end()), x.end());
    return x;
}

std::vector<std::size_t> get_wait_for(std::vector<std::size_t> wait_for)
{
    std::sort(wait_for.begin(), wait_for.end());
    return wait_for;
}

Paul's avatar
Paul committed
178
179
180
181
182
183
184
185
186
187
std::vector<std::size_t> get_wait_for(std::size_t wait_on, std::vector<std::size_t> wait_for)
{
    wait_for.erase(std::find(wait_for.begin(), wait_for.end(), wait_on));
    std::sort(wait_for.begin(), wait_for.end());
    return wait_for;
}

std::vector<std::size_t> get_wait_for(migraphx::instruction_ref ins)
{
    auto wait_ins = std::prev(ins);
Paul's avatar
Paul committed
188
    if(wait_ins->name() != "wait_event")
Paul's avatar
Paul committed
189
        return {};
Paul's avatar
Paul committed
190
    auto wf = *migraphx::any_cast<wait_event>(wait_ins->get_operator()).wait_for;
Paul's avatar
Paul committed
191
192
193
194
    std::sort(wf.begin(), wf.end());
    return wf;
}

Paul's avatar
Paul committed
195
196
197
template <class T>
std::vector<migraphx::instruction_ref>
chain(migraphx::program& p, std::size_t n, T x, migraphx::instruction_ref input)
Paul's avatar
Paul committed
198
199
{
    std::vector<migraphx::instruction_ref> result;
Paul's avatar
Paul committed
200
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
201
202
203
204
205
206
    {
        result.push_back(p.add_instruction(x, input));
        input = result.back();
    }
    return result;
}
Paul's avatar
Paul committed
207
208
TEST_CASE(single_entry)
{
Paul's avatar
Paul committed
209
    schedule_target t{};
Paul's avatar
Paul committed
210
211
    migraphx::program p;
    auto one    = p.add_literal(1);
Paul's avatar
Paul committed
212
213
    auto onep1  = p.add_instruction(unary_op{}, one);
    auto onep2  = p.add_instruction(unary_op{}, one);
Paul's avatar
Paul committed
214
    auto binary = p.add_instruction(nary_op{}, onep1, onep2);
Paul's avatar
Paul committed
215
216
217
218
219
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(onep1) != t.get_stream(onep2));
    EXPECT(t.get_stream(binary) == 0);
    EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(onep1), t.get_stream(onep2)}));
Paul's avatar
Paul committed
220
221
222
    EXPECT(check_conflicts(p, onep1, onep2));
}

Paul's avatar
Paul committed
223
224
TEST_CASE(zero_merge1)
{
Paul's avatar
Paul committed
225
    schedule_target t{};
Paul's avatar
Paul committed
226
227
228
229
230
    migraphx::program p;
    auto one    = p.add_literal(1);
    auto onep1  = p.add_instruction(unary_op{}, one);
    auto onep2  = p.add_instruction(unary_op{}, one);
    auto binary = p.add_instruction(migraphx::op::identity{}, onep1, onep2);
Paul's avatar
Paul committed
231
232
233
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(onep1) != t.get_stream(onep2));
Paul's avatar
Paul committed
234
    // No stream assignment
Paul's avatar
Paul committed
235
    EXPECT(not t.has_stream(binary));
Paul's avatar
Paul committed
236
237
238
239
240
241
242
    // There is no wait
    EXPECT(get_wait_for(binary).empty());
    EXPECT(check_conflicts(p, onep1, onep2));
}

TEST_CASE(zero_merge2)
{
Paul's avatar
Paul committed
243
    schedule_target t{};
Paul's avatar
Paul committed
244
245
246
247
    migraphx::program p;
    auto one    = p.add_literal(1);
    auto onep1  = p.add_instruction(unary_op{}, one);
    auto onep2  = p.add_instruction(unary_op{}, one);
Paul's avatar
Paul committed
248
249
250
    auto binary = p.add_instruction(migraphx::op::identity{},
                                    p.add_instruction(migraphx::op::identity{}, onep1),
                                    p.add_instruction(migraphx::op::identity{}, onep2));
Paul's avatar
Paul committed
251
252
253
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(onep1) != t.get_stream(onep2));
Paul's avatar
Paul committed
254
    // No stream assignment
Paul's avatar
Paul committed
255
    EXPECT(not t.has_stream(binary));
Paul's avatar
Paul committed
256
257
258
259
260
    // There is no wait
    EXPECT(get_wait_for(binary).empty());
    EXPECT(check_conflicts(p, onep1, onep2));
}

Paul's avatar
Paul committed
261
TEST_CASE(double_entry)
Paul's avatar
Paul committed
262
{
Paul's avatar
Paul committed
263
    schedule_target t{};
Paul's avatar
Paul committed
264
    migraphx::program p;
Paul's avatar
Paul committed
265
266
267
268
    auto one    = p.add_literal(1);
    auto two    = p.add_literal(2);
    auto onep   = p.add_instruction(unary_op{}, one);
    auto twop   = p.add_instruction(unary_op{}, two);
Paul's avatar
Paul committed
269
    auto binary = p.add_instruction(nary_op{}, onep, twop);
Paul's avatar
Paul committed
270
271
272
273
274
275
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(not t.has_stream(two));
    EXPECT(t.get_stream(onep) != t.get_stream(twop));
    EXPECT(t.get_stream(binary) == 0);
    EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(onep), t.get_stream(twop)}));
Paul's avatar
Paul committed
276
277
278
    // EXPECT(check_conflicts(p, onep, twop));
}

Paul's avatar
Paul committed
279
TEST_CASE(two_branches)
Paul's avatar
Paul committed
280
{
Paul's avatar
Paul committed
281
    schedule_target t{};
Paul's avatar
Paul committed
282
283
    migraphx::program p;
    auto one    = p.add_literal(1);
Paul's avatar
Paul committed
284
285
    auto c1     = chain(p, 2, unary_op{}, one);
    auto i1     = p.add_instruction(unary_op{}, one);
Paul's avatar
Paul committed
286
    auto binary = p.add_instruction(nary_op{}, i1, c1.back());
Paul's avatar
Paul committed
287
288
289
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(i1) == 1);
Paul's avatar
Paul committed
290
    for(auto ins : c1)
Paul's avatar
Paul committed
291
292
293
        EXPECT(t.get_stream(ins) == 0);
    EXPECT(t.get_stream(binary) == 0);
    EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(c1.back()), t.get_stream(i1)}));
Paul's avatar
Paul committed
294
295
296
    check_conflicts(p, {c1, {i1}});
}

Paul's avatar
Paul committed
297
TEST_CASE(four_branches)
Paul's avatar
Paul committed
298
{
Paul's avatar
Paul committed
299
    schedule_target t{};
Paul's avatar
Paul committed
300
301
    migraphx::program p;
    auto one    = p.add_literal(1);
Paul's avatar
Paul committed
302
303
304
305
    auto c1     = chain(p, 4, unary_op{}, one);
    auto c2     = chain(p, 3, unary_op{}, one);
    auto c3     = chain(p, 2, unary_op{}, one);
    auto i1     = p.add_instruction(unary_op{}, one);
Paul's avatar
Paul committed
306
    auto binary = p.add_instruction(nary_op{}, i1, c1.back(), c2.back(), c3.back());
Paul's avatar
Paul committed
307
308
309
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(i1) == 3);
Paul's avatar
Paul committed
310
    for(auto ins : c1)
Paul's avatar
Paul committed
311
        EXPECT(t.get_stream(ins) == 0);
Paul's avatar
Paul committed
312
    for(auto ins : c2)
Paul's avatar
Paul committed
313
        EXPECT(t.get_stream(ins) == 1);
Paul's avatar
Paul committed
314
    for(auto ins : c3)
Paul's avatar
Paul committed
315
316
        EXPECT(t.get_stream(ins) == 2);
    EXPECT(t.get_stream(binary) == 0);
Paul's avatar
Paul committed
317
    EXPECT(get_wait_for(binary) ==
Paul's avatar
Paul committed
318
319
           get_wait_for(t.get_stream(binary),
                        {t.get_stream(c1.back()), t.get_stream(c2.back()), t.get_stream(c3.back()), t.get_stream(i1)}));
Paul's avatar
Paul committed
320
    check_conflicts(p, {c1, c2, c3, {i1}});
Paul's avatar
Paul committed
321
322
}

Paul's avatar
Paul committed
323
TEST_CASE(five_branches)
Paul's avatar
Paul committed
324
{
Paul's avatar
Paul committed
325
    schedule_target t{};
Paul's avatar
Paul committed
326
327
328
329
330
331
332
333
    migraphx::program p;
    auto one    = p.add_literal(1);
    auto c1     = chain(p, 5, unary_op{}, one);
    auto c2     = chain(p, 4, unary_op{}, one);
    auto c3     = chain(p, 3, unary_op{}, one);
    auto c4     = chain(p, 2, unary_op{}, one);
    auto i1     = p.add_instruction(unary_op{}, one);
    auto binary = p.add_instruction(nary_op{}, i1, c1.back(), c2.back(), c3.back(), c4.back());
Paul's avatar
Paul committed
334
335
336
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(i1) == 3);
Paul's avatar
Paul committed
337
    for(auto ins : c1)
Paul's avatar
Paul committed
338
        EXPECT(t.get_stream(ins) == 0);
Paul's avatar
Paul committed
339
    for(auto ins : c2)
Paul's avatar
Paul committed
340
        EXPECT(t.get_stream(ins) == 1);
Paul's avatar
Paul committed
341
    for(auto ins : c3)
Paul's avatar
Paul committed
342
        EXPECT(t.get_stream(ins) == 2);
Paul's avatar
Paul committed
343
    for(auto ins : c4)
Paul's avatar
Paul committed
344
345
        EXPECT(t.get_stream(ins) == 3);
    EXPECT(t.get_stream(binary) == 0);
Paul's avatar
Paul committed
346
    EXPECT(get_wait_for(binary) ==
Paul's avatar
Paul committed
347
348
           get_wait_for(t.get_stream(binary),
                        {t.get_stream(c1.back()), t.get_stream(c2.back()), t.get_stream(c3.back()), t.get_stream(i1)}));
Paul's avatar
Paul committed
349
350
351
352
    check_conflicts(p, {c1, c2, c3, c4});
    check_conflicts(p, {c1, c2, c3, {i1}});
}

Paul's avatar
Paul committed
353
354
TEST_CASE(four_branches_eq)
{
Paul's avatar
Paul committed
355
    schedule_target t{};
Paul's avatar
Paul committed
356
357
358
359
360
361
362
    migraphx::program p;
    auto one    = p.add_literal(1);
    auto onep1  = p.add_instruction(unary_op{}, one);
    auto onep2  = p.add_instruction(unary_op{}, one);
    auto onep3  = p.add_instruction(unary_op{}, one);
    auto onep4  = p.add_instruction(unary_op{}, one);
    auto binary = p.add_instruction(nary_op{}, onep1, onep2, onep3, onep4);
Paul's avatar
Paul committed
363
364
    p.compile(t);
    EXPECT(not t.has_stream(one));
Paul's avatar
Paul committed
365
    EXPECT(sorted<std::size_t>(
Paul's avatar
Paul committed
366
               {t.get_stream(onep1), t.get_stream(onep2), t.get_stream(onep3), t.get_stream(onep4)}) ==
Paul's avatar
Paul committed
367
           unique<std::size_t>(
Paul's avatar
Paul committed
368
369
               {t.get_stream(onep1), t.get_stream(onep2), t.get_stream(onep3), t.get_stream(onep4)}));
    EXPECT(t.get_stream(binary) == 0);
Paul's avatar
Paul committed
370
371
    EXPECT(
        get_wait_for(binary) ==
Paul's avatar
Paul committed
372
        get_wait_for(t.get_stream(binary), {t.get_stream(onep1), t.get_stream(onep2), t.get_stream(onep3), t.get_stream(onep4)}));
Paul's avatar
Paul committed
373
374
375
    check_conflicts(p, {{onep1}, {onep2}, {onep3}, {onep4}});
}

Paul's avatar
Paul committed
376
377
TEST_CASE(seq_merge)
{
Paul's avatar
Paul committed
378
    schedule_target t{};
Paul's avatar
Paul committed
379
    migraphx::program p;
Paul's avatar
Paul committed
380
381
382
    auto one     = p.add_literal(1);
    auto c1      = chain(p, 2, unary_op{}, one);
    auto i1      = p.add_instruction(unary_op{}, one);
Paul's avatar
Paul committed
383
384
    auto binary1 = p.add_instruction(nary_op{}, i1, c1.back());

Paul's avatar
Paul committed
385
386
    auto c2      = chain(p, 2, unary_op{}, binary1);
    auto i2      = p.add_instruction(unary_op{}, binary1);
Paul's avatar
Paul committed
387
388
    auto binary2 = p.add_instruction(nary_op{}, i2, c2.back());

Paul's avatar
Paul committed
389
390
    p.compile(t);
    EXPECT(not t.has_stream(one));
Paul's avatar
Paul committed
391

Paul's avatar
Paul committed
392
    EXPECT(t.get_stream(i1) == 2);
Paul's avatar
Paul committed
393
    for(auto ins : c1)
Paul's avatar
Paul committed
394
395
396
        EXPECT(t.get_stream(ins) == 3);
    EXPECT(t.get_stream(binary1) == 3);
    EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)}));
Paul's avatar
Paul committed
397
398
    check_conflicts(p, {c1, {i1}});

Paul's avatar
Paul committed
399
    EXPECT(t.get_stream(i2) == 3);
Paul's avatar
Paul committed
400
    for(auto ins : c2)
Paul's avatar
Paul committed
401
402
403
        EXPECT(t.get_stream(ins) == 0);
    EXPECT(t.get_stream(binary2) == 0);
    EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)}));
Paul's avatar
Paul committed
404
405
406
407
408
    check_conflicts(p, {c2, {i2}});
}

TEST_CASE(par_merge)
{
Paul's avatar
Paul committed
409
    schedule_target t{};
Paul's avatar
Paul committed
410
    migraphx::program p;
Paul's avatar
Paul committed
411
412
413
414
    auto one     = p.add_literal(1);
    auto start1  = p.add_instruction(unary_op{}, one);
    auto c1      = chain(p, 3, unary_op{}, start1);
    auto i1      = p.add_instruction(unary_op{}, start1);
Paul's avatar
Paul committed
415
416
    auto binary1 = p.add_instruction(nary_op{}, i1, c1.back());

Paul's avatar
Paul committed
417
418
419
    auto start2  = p.add_instruction(unary_op{}, one);
    auto c2      = chain(p, 2, unary_op{}, start2);
    auto i2      = p.add_instruction(unary_op{}, start2);
Paul's avatar
Paul committed
420
421
422
423
    auto binary2 = p.add_instruction(nary_op{}, i2, c2.back());

    auto binary3 = p.add_instruction(nary_op{}, binary1, binary2);

Paul's avatar
Paul committed
424
425
426
    p.compile(t);
    EXPECT(not t.has_stream(one));
    EXPECT(t.get_stream(binary3) == 0);
Paul's avatar
Paul committed
427

Paul's avatar
Paul committed
428
    EXPECT(t.get_stream(i1) == 2);
Paul's avatar
Paul committed
429
    for(auto ins : c1)
Paul's avatar
Paul committed
430
431
432
        EXPECT(t.get_stream(ins) == 0);
    EXPECT(t.get_stream(binary1) == 0);
    EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)}));
Paul's avatar
Paul committed
433
434
    check_conflicts(p, {c1, {i1}});

Paul's avatar
Paul committed
435
    EXPECT(t.get_stream(i2) == 1);
Paul's avatar
Paul committed
436
    for(auto ins : c2)
Paul's avatar
Paul committed
437
438
439
        EXPECT(t.get_stream(ins) == 3);
    EXPECT(t.get_stream(binary2) == 3);
    EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)}));
Paul's avatar
Paul committed
440
441
442
443
    check_conflicts(p, {c2, {i2}});

    EXPECT(check_conflicts(p, binary1, binary2));
}
Paul's avatar
Paul committed
444
int main(int argc, const char* argv[]) { test::run(argc, argv); }