program.cpp 15.8 KB
Newer Older
Paul's avatar
Paul committed
1
2
3
#include <migraphx/program.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/instruction.hpp>
4
#include <migraphx/op/identity.hpp>
Paul's avatar
Paul committed
5
#include <migraphx/target.hpp>
Paul's avatar
Paul committed
6
7
8
#include <migraphx/env.hpp>
#include <migraphx/ranges.hpp>
#include <migraphx/time.hpp>
9
#include <migraphx/pass_manager.hpp>
10
#include <migraphx/register_target.hpp>
Shucai Xiao's avatar
Shucai Xiao committed
11
#include <migraphx/iterator_for.hpp>
Paul's avatar
Paul committed
12
#include <iostream>
Paul's avatar
Paul committed
13
#include <sstream>
Paul's avatar
Paul committed
14
#include <algorithm>
15
#include <set>
Paul's avatar
Paul committed
16
#include <utility>
17

18
#include <unordered_set>
Shucai Xiao's avatar
Shucai Xiao committed
19
20
#include <map>
#include <cassert>
Paul's avatar
Paul committed
21

Paul's avatar
Paul committed
22
namespace migraphx {
Paul's avatar
Paul committed
23
inline namespace MIGRAPHX_INLINE_NS {
Paul's avatar
Paul committed
24

Paul's avatar
Paul committed
25
26
struct program_impl
{
Shucai Xiao's avatar
Shucai Xiao committed
27
28
    // A map is used to keep references to modules of the program
    std::map<std::string, module> modules;
Paul's avatar
Paul committed
29
    context ctx;
30
    std::string target_name;
Paul's avatar
Paul committed
31
32
};

Paul's avatar
Paul committed
33
34
35
static void print_instruction(std::ostream& os,
                              instruction_ref ins,
                              const std::unordered_map<instruction_ref, std::string>& names)
Paul's avatar
Paul committed
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
    os << names.at(ins) << " = ";

    os << ins->get_operator();

    if(ins->name() == "@literal")
    {
        if(ins->get_literal().get_shape().elements() > 10)
            os << "{ ... }";
        else
            os << "{" << ins->get_literal() << "}";
    }

    if(!ins->inputs().empty())
    {
        char delim = '(';
        for(auto&& arg : ins->inputs())
        {
            os << delim << names.at(arg);
            delim = ',';
        }
        os << ")";
    }
Paul's avatar
Paul committed
59

60
61
62
    // skip return instruction shape
    if(ins->name() != "@return")
        os << " -> " << ins->get_shape();
Paul's avatar
Paul committed
63
64
}

Shucai Xiao's avatar
Shucai Xiao committed
65
program::program() : impl(std::make_unique<program_impl>()) { impl->modules["main"] = {}; }
Paul's avatar
Paul committed
66

Paul's avatar
Paul committed
67
program::program(program&&) noexcept = default;
Shucai Xiao's avatar
Shucai Xiao committed
68
program::~program() noexcept         = default;
Paul's avatar
Paul committed
69

70
// copy constructor
Shucai Xiao's avatar
Shucai Xiao committed
71
program::program(const program& p) { assign(p); }
72
73

// copy assignment operator
Shucai Xiao's avatar
Shucai Xiao committed
74
program& program::operator=(program p)
75
{
Shucai Xiao's avatar
Shucai Xiao committed
76
    std::swap(p.impl, this->impl);
77
78
79
    return *this;
}

Shucai Xiao's avatar
Shucai Xiao committed
80
void program::assign(const program& p)
81
{
Shucai Xiao's avatar
Shucai Xiao committed
82
    if(!impl)
83
84
85
    {
        impl = std::make_unique<program_impl>();
    }
Shucai Xiao's avatar
Shucai Xiao committed
86
    else if(!impl->modules.empty())
87
    {
Shucai Xiao's avatar
Shucai Xiao committed
88
        impl->modules.clear();
89
    }
90
    impl->ctx         = p.impl->ctx;
Shucai Xiao's avatar
Shucai Xiao committed
91
92
    impl->target_name = p.impl->target_name;
    impl->modules     = p.impl->modules;
93
94
}

Paul's avatar
Paul committed
95
shape program::get_parameter_shape(std::string name) const
Paul's avatar
Paul committed
96
{
Shucai Xiao's avatar
Shucai Xiao committed
97
98
    const auto* mm = this->get_main_module();
    return mm->get_parameter_shape(std::move(name));
Paul's avatar
Paul committed
99
100
}

101
102
std::vector<std::string> program::get_parameter_names() const
{
Shucai Xiao's avatar
Shucai Xiao committed
103
104
    const auto* mm = this->get_main_module();
    return mm->get_parameter_names();
105
106
}

mei-ye's avatar
mei-ye committed
107
108
instruction_ref program::get_parameter(std::string name) const
{
Shucai Xiao's avatar
Shucai Xiao committed
109
110
    const auto* mm = this->get_main_module();
    return mm->get_parameter(std::move(name));
mei-ye's avatar
mei-ye committed
111
112
}

Paul's avatar
Paul committed
113
114
std::unordered_map<std::string, shape> program::get_parameter_shapes() const
{
Shucai Xiao's avatar
Shucai Xiao committed
115
116
    const auto* mm = this->get_main_module();
    return mm->get_parameter_shapes();
Paul's avatar
Paul committed
117
118
}

Shucai Xiao's avatar
Shucai Xiao committed
119
std::size_t program::size() const { return impl->modules.size(); }
120

121
122
std::vector<shape> program::get_output_shapes() const
{
Shucai Xiao's avatar
Shucai Xiao committed
123
124
    const auto* mm = this->get_main_module();
    return mm->get_output_shapes();
125
}
Paul's avatar
Paul committed
126

Paul's avatar
Paul committed
127
128
context& program::get_context() const { return impl->ctx; }

Paul's avatar
Paul committed
129
130
instruction_ref program::validate() const
{
Shucai Xiao's avatar
Shucai Xiao committed
131
132
    const auto* mm = this->get_main_module();
    return mm->validate();
Paul's avatar
Paul committed
133
134
}

135
136
bool program::is_compiled() const { return not this->impl->target_name.empty(); }

137
void program::compile(const target& t, compile_options options)
Paul's avatar
Paul committed
138
{
139
140
141
    assert(not this->is_compiled());
    this->impl->target_name = t.name();
    this->impl->ctx         = t.get_context();
Paul's avatar
Paul committed
142
    if(enabled(MIGRAPHX_TRACE_COMPILE{}))
143
        options.trace = tracer{std::cout};
Shucai Xiao's avatar
Shucai Xiao committed
144

145
146
    options.trace(*this);
    options.trace();
Shucai Xiao's avatar
Shucai Xiao committed
147
148
149
    auto&& passes = t.get_passes(this->impl->ctx, options);

    for(auto& mp : impl->modules)
Paul's avatar
Paul committed
150
    {
Shucai Xiao's avatar
Shucai Xiao committed
151
152
153
154
155
156
157
158
159
160
161
        auto& modl = mp.second;
        assert(modl.validate() == modl.end());
        run_passes(modl, passes, options.trace);
        auto invalid = this->validate();
        if(invalid != modl.end())
        {
            auto index = std::distance(modl.begin(), invalid);
            MIGRAPHX_THROW("Invalid module " + mp.first + " from compilation at instruction " +
                           std::to_string(index));
        }
        modl.finalize(this->impl->ctx);
Paul's avatar
Paul committed
162
    }
Paul's avatar
Paul committed
163
164
165
166
}

void program::finalize()
{
Shucai Xiao's avatar
Shucai Xiao committed
167
    for(auto& mp : this->impl->modules)
Paul's avatar
Paul committed
168
    {
Shucai Xiao's avatar
Shucai Xiao committed
169
        mp.second.finalize(this->impl->ctx);
Paul's avatar
Paul committed
170
    }
Paul's avatar
Paul committed
171
172
}

Paul's avatar
Paul committed
173
template <class F>
Shucai Xiao's avatar
Shucai Xiao committed
174
std::vector<argument> generic_eval(const module& p,
175
176
177
                                   context& ctx,
                                   std::unordered_map<std::string, argument> params,
                                   F trace)
Paul's avatar
Paul committed
178
{
Paul's avatar
Paul committed
179
    assert(p.validate() == p.end());
180
    std::unordered_map<instruction_ref, argument> results;
Paul's avatar
Paul committed
181
    results.reserve(p.size() * 2);
Paul's avatar
Paul committed
182
183
    std::vector<argument> values;
    values.reserve(16);
184
    for(auto ins : iterator_for(p))
Paul's avatar
Paul committed
185
    {
186
187
        const auto& name = ins->name();
        if(name == "@literal")
Paul's avatar
Paul committed
188
        {
Paul's avatar
Paul committed
189
            results.emplace(ins, trace(ins, [&] { return ins->get_literal().get_argument(); }));
Paul's avatar
Paul committed
190
        }
191
        else if(name == "@param")
Paul's avatar
Paul committed
192
        {
Paul's avatar
Paul committed
193
194
195
196
197
            results.emplace(
                ins, trace(ins, [&] {
                    auto param_name = any_cast<builtin::param>(ins->get_operator()).parameter;
                    if(not contains(params, param_name))
                        MIGRAPHX_THROW("Parameter not found: " + param_name);
198
                    auto param = params[param_name];
Paul's avatar
Paul committed
199
200
201
202
203
                    if(param.get_shape() != ins->get_shape())
                        MIGRAPHX_THROW("Incorrect shape {" + to_string(param.get_shape()) +
                                       "} for parameter: " + param_name);
                    return param;
                }));
Paul's avatar
Paul committed
204
        }
205
        else if(name == "@outline")
Paul's avatar
Paul committed
206
        {
Paul's avatar
Paul committed
207
            results.emplace(ins, trace(ins, [&] { return argument{ins->get_shape(), nullptr}; }));
Paul's avatar
Paul committed
208
        }
209
210
211
212
213
214
215
216
217
218
219
220
221
        else if(name == "@return")
        {
            std::vector<argument> prog_outputs;
            std::transform(ins->inputs().begin(),
                           ins->inputs().end(),
                           std::back_inserter(prog_outputs),
                           [&](instruction_ref i) {
                               assert(results.find(i) != results.end());
                               return results[i];
                           });

            return prog_outputs;
        }
Paul's avatar
Paul committed
222
223
        else
        {
Paul's avatar
Paul committed
224
            values.resize(ins->inputs().size());
Paul's avatar
Paul committed
225
226
227
228
229
            std::transform(
                ins->inputs().begin(), ins->inputs().end(), values.begin(), [&](instruction_ref i) {
                    assert(results.find(i) != results.end());
                    return results[i];
                });
Paul's avatar
Paul committed
230
231
232
            results.emplace(ins, trace(ins, [&] {
                                return ins->get_operator().compute(ctx, ins->get_shape(), values);
                            }));
Paul's avatar
Paul committed
233
        }
234
        assert(results.find(ins) != results.end());
Paul's avatar
Paul committed
235
    }
236

237
    return {results.at(std::prev(p.end()))};
Paul's avatar
Paul committed
238
239
}

Shucai Xiao's avatar
Shucai Xiao committed
240
241
242
243
244
245
246
247
248
249
template <class F>
std::vector<argument> generic_eval(const program& p,
                                   context& ctx,
                                   std::unordered_map<std::string, argument> params,
                                   F trace)
{
    const auto* mm = p.get_main_module();
    return generic_eval(*mm, ctx, params, trace);
}

250
std::vector<argument> program::eval(parameter_map params) const
Paul's avatar
Paul committed
251
{
Paul's avatar
Paul committed
252
253
    auto& ctx = this->impl->ctx;
#ifndef NDEBUG
Paul's avatar
Paul committed
254
    auto sctx          = ctx;
Paul's avatar
Paul committed
255
256
257
    auto check_context = [&](auto f) {
        assert(is_shared(ctx, sctx));
        auto x = f();
Paul's avatar
Paul committed
258
        sctx   = ctx;
Paul's avatar
Paul committed
259
260
261
        return x;
    };
#else
Paul's avatar
Paul committed
262
    auto check_context = [](auto f) { return f(); };
Paul's avatar
Paul committed
263
#endif
Paul's avatar
Paul committed
264
265
266
267

    auto trace_level = value_of(MIGRAPHX_TRACE_EVAL{});

    if(trace_level > 0)
Paul's avatar
Paul committed
268
    {
Paul's avatar
Paul committed
269
        return generic_eval(*this, ctx, std::move(params), [&](auto& ins, auto f) {
Paul's avatar
Paul committed
270
            ctx.finish();
Paul's avatar
Paul committed
271
272
            std::cout << "Run instruction: ";
            this->debug_print(ins);
Paul's avatar
Paul committed
273
274
            auto result = check_context(f);
            ctx.finish();
Paul's avatar
Paul committed
275
            if(trace_level > 1 and ins->name().front() != '@' and ins->name() != "load")
Paul's avatar
Paul committed
276
277
                std::cout << "Ouput: " << result << std::endl;
            return result;
Paul's avatar
Paul committed
278
        });
Paul's avatar
Paul committed
279
280
281
282
    }
    else
    {
        return generic_eval(
Paul's avatar
Paul committed
283
            *this, ctx, std::move(params), [&](auto&, auto f) { return check_context(f); });
Paul's avatar
Paul committed
284
    }
Paul's avatar
Paul committed
285
286
}

287
const int program_file_version = 3;
288
289
290
291
292
293
294
295

value program::to_value() const
{
    value result;
    result["version"] = program_file_version;
    result["target"]  = this->impl->target_name;
    if(not this->impl->target_name.empty())
        result["context"] = this->impl->ctx.to_value();
Shucai Xiao's avatar
Shucai Xiao committed
296
297
298
299
300
301
302

    result["modules"] = value::object{};
    auto& module_val  = result.at("modules");
    for(auto& m : impl->modules)
    {
        module_val[m.first] = m.second.to_value();
    }
303
304
    return result;
}
Shucai Xiao's avatar
Shucai Xiao committed
305

306
307
308
309
void program::from_value(const value& v)
{
    auto version = v.at("version").to<int>();
    if(version != program_file_version)
Shucai Xiao's avatar
Shucai Xiao committed
310
311
312
313
    {
        MIGRAPHX_THROW("Warning: Program version mismatch");
    }

314
315
316
317
318
319
320
321
    this->impl->target_name = v.at("target").to<std::string>();
    if(not this->impl->target_name.empty())
    {
        target t        = make_target(this->impl->target_name);
        this->impl->ctx = t.get_context();
        this->impl->ctx.from_value(v.at("context"));
    }

Shucai Xiao's avatar
Shucai Xiao committed
322
323
    auto val_modules = v.at("modules");
    for(const auto& vv : val_modules)
324
    {
Shucai Xiao's avatar
Shucai Xiao committed
325
326
327
328
329
        const auto& key = vv.get_key();
        auto val        = vv.without_key();
        module modl;
        modl.from_value(val);
        impl->modules[key] = modl;
330
331
332
333
    }
    this->finalize();
}

Paul's avatar
Paul committed
334
335
336
double common_average(const std::vector<double>& v)
{
    std::size_t n = v.size() / 4;
Paul's avatar
Paul committed
337
338
    double total  = std::accumulate(v.begin() + n, v.end() - n, 0.0);
    return total / std::distance(v.begin() + n, v.end() - n);
Paul's avatar
Paul committed
339
340
}

Paul's avatar
Paul committed
341
342
343
void program::perf_report(std::ostream& os, std::size_t n, parameter_map params) const
{
    using milliseconds = std::chrono::duration<double, std::milli>;
Paul's avatar
Paul committed
344
    auto& ctx          = this->impl->ctx;
Paul's avatar
Paul committed
345
346
    // Run once by itself
    eval(params);
Paul's avatar
Paul committed
347
    ctx.finish();
Paul's avatar
Paul committed
348
    // Run and time entire program
Paul's avatar
Paul committed
349
350
    std::vector<double> total_vec;
    total_vec.reserve(n);
Paul's avatar
Paul committed
351
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
352
    {
Paul's avatar
Paul committed
353
354
355
356
        total_vec.push_back(time<milliseconds>([&] {
            eval(params);
            ctx.finish();
        }));
Paul's avatar
Paul committed
357
    }
Paul's avatar
Paul committed
358
359
    std::sort(total_vec.begin(), total_vec.end());
    std::unordered_map<instruction_ref, std::vector<double>> ins_vec;
Paul's avatar
Paul committed
360
    // Fill the map
Paul's avatar
Paul committed
361
    generic_eval(*this, ctx, params, [&](auto ins, auto) {
Paul's avatar
Paul committed
362
        ins_vec[ins].reserve(n);
Paul's avatar
Paul committed
363
364
        return argument{};
    });
Paul's avatar
Paul committed
365
    // Run and time each instruction
Paul's avatar
Paul committed
366
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
367
    {
Paul's avatar
Paul committed
368
        generic_eval(*this, ctx, params, [&](auto ins, auto f) {
369
            argument result;
Paul's avatar
Paul committed
370
371
372
373
            ins_vec[ins].push_back(time<milliseconds>([&] {
                result = f();
                ctx.finish();
            }));
374
            return result;
Paul's avatar
Paul committed
375
376
        });
    }
Paul's avatar
Paul committed
377
378
    for(auto&& p : ins_vec)
        std::sort(p.second.begin(), p.second.end());
Paul's avatar
Paul committed
379
    // Run and time implicit overhead
Paul's avatar
Paul committed
380
381
    std::vector<double> overhead_vec;
    overhead_vec.reserve(n);
Paul's avatar
Paul committed
382
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
383
    {
Paul's avatar
Paul committed
384
        overhead_vec.push_back(time<milliseconds>([&] { dry_run(params); }));
Paul's avatar
Paul committed
385
386
    }

Paul's avatar
Paul committed
387
    double total_time             = common_average(total_vec);
Paul's avatar
Paul committed
388
    double rate                   = 1000.0 / total_time;
Paul's avatar
Paul committed
389
    double overhead_time          = common_average(overhead_vec);
Paul's avatar
Paul committed
390
    double overhead_percent       = overhead_time * 100.0 / total_time;
Paul's avatar
Paul committed
391
    double total_instruction_time = 0.0;
Paul's avatar
Paul committed
392
    std::unordered_map<std::string, double> op_times;
Paul's avatar
Paul committed
393
    for(auto&& p : ins_vec)
Paul's avatar
Paul committed
394
395
    {
        double avg = common_average(p.second);
Paul's avatar
Paul committed
396
        op_times[p.first->name()] += avg;
Paul's avatar
Paul committed
397
398
        total_instruction_time += avg;
    }
Paul's avatar
Paul committed
399
400
    double calculate_overhead_time    = total_time - total_instruction_time;
    double calculate_overhead_percent = calculate_overhead_time * 100.0 / total_time;
Paul's avatar
Paul committed
401

Shucai Xiao's avatar
Shucai Xiao committed
402
    this->print([&](auto ins, auto names) {
Khalique's avatar
Khalique committed
403
        print_instruction(std::cout, ins, names);
404
405
406
407
408

        // skip return instruction
        if(ins->name() == "@return")
            return;

Paul's avatar
Paul committed
409
410
411
        double avg     = common_average(ins_vec[ins]);
        double percent = std::ceil(100.0 * avg / total_instruction_time);
        os << ": " << avg << "ms, " << percent << "%";
412
        os << std::endl;
Paul's avatar
Paul committed
413
    });
Paul's avatar
Paul committed
414
415
416

    os << std::endl;
    os << "Summary:" << std::endl;
417
418
419
420
421
422
423
    std::vector<std::pair<double, std::string>> op_times_sorted;
    std::transform(op_times.begin(),
                   op_times.end(),
                   std::back_inserter(op_times_sorted),
                   [](auto p) { return std::make_pair(p.second, p.first); });
    std::sort(op_times_sorted.begin(), op_times_sorted.end(), std::greater<>{});
    for(auto&& p : op_times_sorted)
Paul's avatar
Paul committed
424
    {
425
426
        auto&& name    = p.second;
        double avg     = p.first;
Paul's avatar
Paul committed
427
428
429
430
431
        double percent = std::ceil(100.0 * avg / total_instruction_time);
        os << name << ": " << avg << "ms, " << percent << "%" << std::endl;
    }

    os << std::endl;
Paul's avatar
Paul committed
432

Paul's avatar
Paul committed
433
    os << "Rate: " << rate << "/sec" << std::endl;
Paul's avatar
Paul committed
434
435
    os << "Total time: " << total_time << "ms" << std::endl;
    os << "Total instructions time: " << total_instruction_time << "ms" << std::endl;
Paul's avatar
Paul committed
436
437
438
439
    os << "Overhead time: " << overhead_time << "ms"
       << ", " << calculate_overhead_time << "ms" << std::endl;
    os << "Overhead: " << std::round(overhead_percent) << "%"
       << ", " << std::round(calculate_overhead_percent) << "%" << std::endl;
Paul's avatar
Paul committed
440
441
}

Paul's avatar
Paul committed
442
443
void program::debug_print() const { std::cout << *this << std::endl; }
void program::debug_print(instruction_ref ins) const
Paul's avatar
Paul committed
444
{
Shucai Xiao's avatar
Shucai Xiao committed
445
446
447
    if(std::any_of(this->impl->modules.begin(), this->impl->modules.end(), [&](auto it) {
           return (it.second.end() == ins);
       }))
Paul's avatar
Paul committed
448
449
450
451
    {
        std::cout << "End instruction" << std::endl;
        return;
    }
Shucai Xiao's avatar
Shucai Xiao committed
452
453
454
    else if(not std::any_of(this->impl->modules.begin(), this->impl->modules.end(), [&](auto it) {
                return it.second.has_instruction(ins);
            }))
Paul's avatar
Paul committed
455
456
457
458
    {
        std::cout << "Instruction not part of program" << std::endl;
        return;
    }
Shucai Xiao's avatar
Shucai Xiao committed
459

Paul's avatar
Paul committed
460
    std::stringstream ss;
Shucai Xiao's avatar
Shucai Xiao committed
461
    this->print([&](auto x, const auto& names) {
Paul's avatar
Paul committed
462
        if(x == ins)
Paul's avatar
Paul committed
463
464
465
466
467
468
469
        {
            print_instruction(std::cout, x, names);
            std::cout << std::endl;
        }
    });
}

Shucai Xiao's avatar
Shucai Xiao committed
470
471
472
void program::print(const std::function<
                    void(instruction_ref, const std::unordered_map<instruction_ref, std::string>&)>&
                        print_func) const
473
{
Shucai Xiao's avatar
Shucai Xiao committed
474
    for(const auto& mdl : this->impl->modules)
475
    {
Shucai Xiao's avatar
Shucai Xiao committed
476
        mdl.second.print(print_func);
477
478
479
    }
}

Shucai Xiao's avatar
Shucai Xiao committed
480
void program::print_graph(std::ostream& os, bool brief) const
481
{
Shucai Xiao's avatar
Shucai Xiao committed
482
483
    const auto* mm = this->get_main_module();
    mm->print_graph(os, brief);
484
485
486
487
488
}

void program::print_cpp(std::ostream& os) const
{
    os << "migraphx::program p;" << std::endl;
Shucai Xiao's avatar
Shucai Xiao committed
489
490
    const auto* mm = this->get_main_module();
    mm->print_cpp(os);
491
492
}

Paul's avatar
Paul committed
493
494
void program::dry_run(std::unordered_map<std::string, argument> params) const
{
Paul's avatar
Paul committed
495
    auto& ctx = this->impl->ctx;
Paul's avatar
Paul committed
496
    generic_eval(*this, ctx, std::move(params), [](auto&&...) { return argument{}; });
Paul's avatar
Paul committed
497
498
}

Shucai Xiao's avatar
Shucai Xiao committed
499
void program::annotate(std::ostream& os, const std::function<void(instruction_ref)>& a) const
Paul's avatar
Paul committed
500
{
Shucai Xiao's avatar
Shucai Xiao committed
501
502
503
504
505
    for(auto& modl : this->impl->modules)
    {
        std::cout << modl.first << ":" << std::endl;
        modl.second.annotate(os, a);
    }
Paul's avatar
Paul committed
506
507
}

Shucai Xiao's avatar
Shucai Xiao committed
508
509
510
511
module* program::get_main_module() { return &impl->modules["main"]; }

const module* program::get_main_module() const { return &impl->modules["main"]; }

512
513
program& program::sort()
{
Shucai Xiao's avatar
Shucai Xiao committed
514
515
516
517
518
    for(auto& modl : this->impl->modules)
    {
        modl.second.sort();
    }

519
520
521
    return *this;
}

Paul's avatar
Paul committed
522
bool operator==(const program& x, const program& y) { return to_string(x) == to_string(y); }
Paul's avatar
Paul committed
523

Paul's avatar
Paul committed
524
std::ostream& operator<<(std::ostream& os, const program& p)
Paul's avatar
Paul committed
525
{
Shucai Xiao's avatar
Shucai Xiao committed
526
527
528
529
    for(auto& mp : p.impl->modules)
    {
        os << "Module " << mp.first << ": " << std::endl;
        os << mp.second;
530
        os << std::endl;
Shucai Xiao's avatar
Shucai Xiao committed
531
532
    }

Paul's avatar
Paul committed
533
    return os;
Paul's avatar
Paul committed
534
}
Paul's avatar
Paul committed
535

Paul's avatar
Paul committed
536
} // namespace MIGRAPHX_INLINE_NS
Paul's avatar
Paul committed
537
} // namespace migraphx