program.cpp 12.1 KB
Newer Older
Paul's avatar
Paul committed
1
2
3
#include <migraph/program.hpp>
#include <migraph/stringutils.hpp>
#include <migraph/instruction.hpp>
Paul's avatar
Paul committed
4
#include <migraph/env.hpp>
Paul's avatar
Paul committed
5
#include <migraph/time.hpp>
6
#include <migraph/iterator_for.hpp>
Paul's avatar
Paul committed
7
#include <iostream>
Paul's avatar
Paul committed
8
#include <sstream>
Paul's avatar
Paul committed
9
10
#include <algorithm>

Paul's avatar
Paul committed
11
namespace migraph {
Paul's avatar
Paul committed
12

Paul's avatar
Paul committed
13
14
MIGRAPH_DECLARE_ENV_VAR(MIGRAPH_TRACE_COMPILE)

Paul's avatar
Paul committed
15
16
17
18
struct program_impl
{
    // A list is used to keep references to an instruction stable
    std::list<instruction> instructions;
Paul's avatar
Paul committed
19
    context ctx;
Paul's avatar
Paul committed
20
21
};

Paul's avatar
Paul committed
22
const operation& get_operation(instruction_ref ins) { return ins->op; }
Paul's avatar
Paul committed
23

Paul's avatar
Paul committed
24
template <class F>
Paul's avatar
Paul committed
25
26
static void print_program(std::ostream& os, const program& p, F annonate)
{
27
    std::unordered_map<instruction_ref, std::string> names;
Paul's avatar
Paul committed
28
29
    int count = 0;

30
    for(auto ins : iterator_for(p))
Paul's avatar
Paul committed
31
32
    {
        std::string var_name = "@" + std::to_string(count);
33
        if(ins->op.name() == "@param")
Paul's avatar
Paul committed
34
        {
35
            var_name = any_cast<builtin::param>(ins->op).parameter;
Paul's avatar
Paul committed
36
37
38
39
        }

        os << var_name << " = ";

40
        os << ins->op;
Paul's avatar
Paul committed
41

42
        if(ins->op.name() == "@literal")
Paul's avatar
Paul committed
43
        {
44
            if(ins->lit.get_shape().elements() > 10)
Paul's avatar
Paul committed
45
46
                os << "{ ... }";
            else
47
                os << "{" << ins->lit << "}";
Paul's avatar
Paul committed
48
49
        }

50
        if(!ins->arguments.empty())
Paul's avatar
Paul committed
51
52
        {
            char delim = '(';
53
            for(auto&& arg : ins->arguments)
Paul's avatar
Paul committed
54
55
            {
                assert(p.has_instruction(arg) && "Instruction not found");
56
                os << delim << names.at(arg);
Paul's avatar
Paul committed
57
58
59
60
61
                delim = ',';
            }
            os << ")";
        }

62
        os << " -> " << ins->result;
Paul's avatar
Paul committed
63

Paul's avatar
Paul committed
64
        annonate(ins, names);
Paul's avatar
Paul committed
65
66
67

        os << std::endl;

68
        names.emplace(ins, var_name);
Paul's avatar
Paul committed
69
70
71
72
        count++;
    }
}

Paul's avatar
Paul committed
73
program::program() : impl(std::make_unique<program_impl>()) {}
Paul's avatar
Paul committed
74

Paul's avatar
Paul committed
75
program::program(program&&) noexcept = default;
Paul's avatar
Paul committed
76
77
program& program::operator=(program&&) noexcept = default;
program::~program() noexcept                    = default;
Paul's avatar
Paul committed
78

Paul's avatar
Paul committed
79
instruction_ref program::add_instruction(operation op, std::vector<instruction_ref> args)
Paul's avatar
Paul committed
80
81
82
{
    return insert_instruction(impl->instructions.end(), std::move(op), std::move(args));
}
Paul's avatar
Paul committed
83
84
instruction_ref
program::insert_instruction(instruction_ref ins, operation op, std::vector<instruction_ref> args)
Paul's avatar
Paul committed
85
{
Paul's avatar
Paul committed
86
87
88
    assert(std::all_of(
               args.begin(), args.end(), [&](instruction_ref x) { return has_instruction(x); }) &&
           "Argument is not an exisiting instruction");
Paul's avatar
Paul committed
89
    assert(not starts_with(op.name(), "@"));
Paul's avatar
Paul committed
90
91
    // TODO: Use move
    shape r     = compute_shape(op, args);
Paul's avatar
Paul committed
92
    auto result = impl->instructions.insert(ins, {op, r, args});
Paul's avatar
Paul committed
93
    backreference(result);
Paul's avatar
Paul committed
94
    assert(result->arguments == args);
Paul's avatar
Paul committed
95
    assert(result->valid(begin()));
Paul's avatar
Paul committed
96
    return result;
Paul's avatar
Paul committed
97
98
}

Paul's avatar
Paul committed
99
100
101
102
103
104
instruction_ref
program::replace_instruction(instruction_ref ins, operation op, std::vector<instruction_ref> args)
{
    assert(std::all_of(
               args.begin(), args.end(), [&](instruction_ref x) { return has_instruction(x); }) &&
           "Argument is not an exisiting instruction");
Paul's avatar
Paul committed
105
    assert(not starts_with(op.name(), "@"));
Paul's avatar
Paul committed
106

Paul's avatar
Paul committed
107
    shape r = compute_shape(op, args);
Paul's avatar
Paul committed
108
109
    ins->replace(op, r, args);
    backreference(ins);
Paul's avatar
Paul committed
110
    assert(ins->valid(begin()));
Paul's avatar
Paul committed
111
112
113
    return ins;
}

Paul's avatar
Paul committed
114
instruction_ref program::replace_instruction(instruction_ref ins, instruction_ref rep)
Paul's avatar
Paul committed
115
{
Paul's avatar
Paul committed
116
117
118
119
    assert(has_instruction(ins));
    assert(has_instruction(rep));
    assert(ins != rep);
    // TODO: Should it be an error if the output is empty?
Paul's avatar
Paul committed
120
    if(ins->output.empty())
Paul's avatar
Paul committed
121
122
123
    {
        return rep;
    }
Paul's avatar
Paul committed
124
    for(auto&& out : ins->output)
Paul's avatar
Paul committed
125
    {
Paul's avatar
Paul committed
126
127
        // TODO: Check for possible cycles
        if(out != rep)
Paul's avatar
Paul committed
128
        {
Paul's avatar
Paul committed
129
            replace_argument(out, ins, rep);
Paul's avatar
Paul committed
130
        }
Paul's avatar
Paul committed
131
        assert(out->valid(begin()));
Paul's avatar
Paul committed
132
    }
Paul's avatar
Paul committed
133
134
    // Replacement should not be dead code unless its the last instruction
    assert(!rep->output.empty() or rep == std::prev(end()));
Paul's avatar
Paul committed
135
    assert(ins->valid(begin()));
Paul's avatar
Paul committed
136
    assert(rep->valid(begin()));
Paul's avatar
Paul committed
137
138
139
    return rep;
}

Paul's avatar
Paul committed
140
instruction_ref program::remove_instruction(instruction_ref ins)
Paul's avatar
Paul committed
141
142
143
144
145
146
147
{
    assert(has_instruction(ins));
    assert(ins->output.empty());
    ins->clear_arguments();
    return impl->instructions.erase(ins);
}

148
149
instruction_ref program::remove_instructions(instruction_ref first, instruction_ref last)
{
Paul's avatar
Paul committed
150
151
    if(first == last)
        return first;
Paul's avatar
Paul committed
152
    // TODO: Check every element
153
    assert(has_instruction(first));
Paul's avatar
Paul committed
154
155
    std::for_each(first, last, [&](instruction& ins) { ins.clear_arguments(); });
    assert(std::all_of(first, last, [&](instruction& ins) { return ins.output.empty(); }));
156
157
158
159
160
161
162
163
164
    return impl->instructions.erase(first, last);
}

instruction_ref program::move_instruction(instruction_ref src, instruction_ref dst)
{
    impl->instructions.splice(dst, impl->instructions, src);
    return src;
}

Paul's avatar
Paul committed
165
instruction_ref program::add_literal(literal l)
Paul's avatar
Paul committed
166
{
Paul's avatar
Paul committed
167
168
169
170
171
172
173
174
    impl->instructions.emplace_front(std::move(l));
    return impl->instructions.begin();
}

instruction_ref program::add_outline(shape s)
{
    impl->instructions.push_front({builtin::outline{s}, s, {}});
    return impl->instructions.begin();
Paul's avatar
Paul committed
175
176
}

Paul's avatar
Paul committed
177
instruction_ref program::add_parameter(std::string name, shape s)
Paul's avatar
Paul committed
178
{
Paul's avatar
Paul committed
179
180
181
182
    impl->instructions.push_front({builtin::param{std::move(name)}, s, {}});
    return impl->instructions.begin();
}

Paul's avatar
Paul committed
183
shape program::get_parameter_shape(std::string name) const
Paul's avatar
Paul committed
184
185
{
    auto ins = std::find_if(
Paul's avatar
Paul committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
        impl->instructions.begin(), impl->instructions.end(), [&](const instruction& x) {
            if(x.op.name() == "@param")
            {
                return any_cast<builtin::param>(x.op).parameter == name;
            }
            else
            {
                return false;
            }
        });
    if(ins != this->end())
        return ins->result;
    else
        return {};
Paul's avatar
Paul committed
200
201
}

Paul's avatar
Paul committed
202
203
204
std::unordered_map<std::string, shape> program::get_parameter_shapes() const
{
    std::unordered_map<std::string, shape> result;
Paul's avatar
Paul committed
205
    for(auto&& ins : impl->instructions)
Paul's avatar
Paul committed
206
207
208
    {
        if(ins.op.name() == "@param")
        {
Paul's avatar
Paul committed
209
            auto&& name  = any_cast<builtin::param>(ins.op).parameter;
Paul's avatar
Paul committed
210
211
212
213
214
215
            result[name] = ins.result;
        }
    }
    return result;
}

Paul's avatar
Paul committed
216
bool program::has_instruction(instruction_ref ins) const
Paul's avatar
Paul committed
217
{
Paul's avatar
Paul committed
218
219
220
221
    return std::find_if(
               impl->instructions.begin(), impl->instructions.end(), [&](const instruction& x) {
                   return std::addressof(*ins) == std::addressof(x);
               }) != impl->instructions.end();
Paul's avatar
Paul committed
222
223
}

Paul's avatar
Paul committed
224
std::size_t program::size() const { return impl->instructions.size(); }
Paul's avatar
Paul committed
225
226
instruction_ref program::begin() const { return impl->instructions.begin(); }
instruction_ref program::end() const { return impl->instructions.end(); }
227

Paul's avatar
Paul committed
228
shape program::get_shape() const { return impl->instructions.back().result; }
Paul's avatar
Paul committed
229

Paul's avatar
Paul committed
230
231
instruction_ref program::validate() const
{
Paul's avatar
Paul committed
232
233
    return std::find_if(impl->instructions.begin(),
                        impl->instructions.end(),
Paul's avatar
Paul committed
234
                        [&](const instruction& i) { return !i.valid(impl->instructions.begin()); });
Paul's avatar
Paul committed
235
236
}

Paul's avatar
Paul committed
237
238
void program::compile(const target& t)
{
Paul's avatar
Paul committed
239
    assert(this->validate() == impl->instructions.end());
Paul's avatar
Paul committed
240
    this->impl->ctx = t.get_context();
Paul's avatar
Paul committed
241
    if(enabled(MIGRAPH_TRACE_COMPILE{}))
Paul's avatar
Paul committed
242
243
        std::cout << *this << std::endl << std::endl;
    ;
Paul's avatar
Paul committed
244
    for(auto&& p : t.get_passes(this->impl->ctx))
Paul's avatar
Paul committed
245
    {
Paul's avatar
Paul committed
246
247
        if(enabled(MIGRAPH_TRACE_COMPILE{}))
            std::cout << "Pass: " << p.name() << std::endl;
Paul's avatar
Paul committed
248
        p.apply(*this);
Paul's avatar
Paul committed
249
        if(enabled(MIGRAPH_TRACE_COMPILE{}))
250
            std::cout << *this << std::endl;
Paul's avatar
Paul committed
251
#ifndef NDEBUG
Paul's avatar
Paul committed
252
        if(enabled(MIGRAPH_TRACE_COMPILE{}))
253
            std::cout << "Validate ..." << std::endl;
Paul's avatar
Paul committed
254
        auto invalid = this->validate();
Paul's avatar
Paul committed
255
256
        if(invalid != impl->instructions.end())
        {
Paul's avatar
Paul committed
257
            auto index = std::distance(impl->instructions.begin(), invalid);
Paul's avatar
Paul committed
258
            MIGRAPH_THROW(p.name() + " pass produces invalid program at instruction " +
Paul's avatar
Paul committed
259
                          std::to_string(index) + ": " + invalid->op.name());
Paul's avatar
Paul committed
260
        }
Paul's avatar
Paul committed
261
        if(enabled(MIGRAPH_TRACE_COMPILE{}))
262
            std::cout << std::endl;
Paul's avatar
Paul committed
263
264
#endif
    }
Paul's avatar
Paul committed
265
    auto invalid = this->validate();
Paul's avatar
Paul committed
266
267
    if(invalid != impl->instructions.end())
    {
Paul's avatar
Paul committed
268
269
270
        auto index = std::distance(impl->instructions.begin(), invalid);
        MIGRAPH_THROW("Invalid program from compilation at instruction " + std::to_string(index));
    }
Paul's avatar
Paul committed
271
272
}

Paul's avatar
Paul committed
273
274
275
276
277
template <class F>
argument generic_eval(const program& p,
                      context& ctx,
                      std::unordered_map<std::string, argument> params,
                      F trace)
Paul's avatar
Paul committed
278
{
Paul's avatar
Paul committed
279
    assert(p.validate() == p.end());
280
281
    std::unordered_map<instruction_ref, argument> results;
    results.reserve(p.size()*2);
Paul's avatar
Paul committed
282
283
    std::vector<argument> values;
    values.reserve(16);
284
    for(auto ins : iterator_for(p))
Paul's avatar
Paul committed
285
    {
286
        if(ins->op.name() == "@literal")
Paul's avatar
Paul committed
287
        {
288
            results.emplace(ins, trace(ins, [&] { return ins->lit.get_argument(); }));
Paul's avatar
Paul committed
289
        }
290
        else if(ins->op.name() == "@param")
Paul's avatar
Paul committed
291
        {
292
            results.emplace(ins, trace(ins, [&] { return params.at(any_cast<builtin::param>(ins->op).parameter); }));
Paul's avatar
Paul committed
293
        }
294
        else if(ins->op.name() == "@outline")
Paul's avatar
Paul committed
295
        {
296
            results.emplace(ins, trace(ins, [&] { return argument{ins->result, nullptr}; }));
Paul's avatar
Paul committed
297
        }
Paul's avatar
Paul committed
298
299
        else
        {
300
301
302
            values.resize(ins->arguments.size());
            std::transform(ins->arguments.begin(),
                           ins->arguments.end(),
Paul's avatar
Paul committed
303
                           values.begin(),
304
305
306
307
308
                           [&](instruction_ref i) {
                                assert(results.find(i) != results.end());
                                return results[i]; 
                            });
            results.emplace(ins, trace(ins, [&] { return ins->op.compute(ctx, ins->result, values); }));
Paul's avatar
Paul committed
309
        }
310
        assert(results.find(ins) != results.end());
Paul's avatar
Paul committed
311
    }
312
    return results.at(std::prev(p.end()));
Paul's avatar
Paul committed
313
314
}

Paul's avatar
Paul committed
315
316
argument program::eval(std::unordered_map<std::string, argument> params) const
{
317
    return generic_eval(*this, this->impl->ctx, params, [](auto&, auto f) { return f(); });
Paul's avatar
Paul committed
318
319
}

Paul's avatar
Paul committed
320
321
322
323
324
325
326
void program::perf_report(std::ostream& os, std::size_t n, parameter_map params) const
{
    using milliseconds = std::chrono::duration<double, std::milli>;
    // Run once by itself
    eval(params);
    // Run and time entire program
    double total_acc = 0;
Paul's avatar
Paul committed
327
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
328
    {
Paul's avatar
Paul committed
329
        total_acc += time<milliseconds>([&] { eval(params); });
Paul's avatar
Paul committed
330
    }
331
    std::unordered_map<instruction_ref, double> ins_acc;
Paul's avatar
Paul committed
332
    // Fill the map
Paul's avatar
Paul committed
333
    generic_eval(
334
        *this, this->impl->ctx, params, [&](auto ins, auto) { ins_acc[ins] = 0; return argument{}; });
Paul's avatar
Paul committed
335
    // Run and time each instruction
Paul's avatar
Paul committed
336
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
337
    {
338
339
340
341
342
343
        generic_eval(*this, this->impl->ctx, params, [&](auto ins, auto f) {
            argument result;
            ins_acc[ins] += time<milliseconds>([&]{
                result = f();
            });
            return result;
Paul's avatar
Paul committed
344
345
346
347
        });
    }
    // Run and time implicit overhead
    double overhead_acc = 0;
Paul's avatar
Paul committed
348
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
349
    {
Paul's avatar
Paul committed
350
        overhead_acc += time<milliseconds>(
351
            [&] { generic_eval(*this, this->impl->ctx, params, [](auto...) { return argument{}; }); });
Paul's avatar
Paul committed
352
353
    }

Paul's avatar
Paul committed
354
355
356
    double total_time             = total_acc / n;
    double overhead_time          = overhead_acc / n;
    double overhead_percent       = overhead_time * 100.0 / total_time;
Paul's avatar
Paul committed
357
    double total_instruction_time = 0.0;
Paul's avatar
Paul committed
358
    for(auto&& p : ins_acc)
Paul's avatar
Paul committed
359
        total_instruction_time += p.second / n;
Paul's avatar
Paul committed
360
361
    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
362

363
364
    print_program(os, *this, [&](auto ins, auto&&) {
        os << ": " << ins_acc[ins] / n << "ms";
Paul's avatar
Paul committed
365
366
367
368
    });

    os << "Total time: " << total_time << "ms" << std::endl;
    os << "Total instructions time: " << total_instruction_time << "ms" << std::endl;
Paul's avatar
Paul committed
369
370
371
372
    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
373
374
}

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

Paul's avatar
Paul committed
377
std::ostream& operator<<(std::ostream& os, const program& p)
Paul's avatar
Paul committed
378
{
Paul's avatar
Paul committed
379
    print_program(os, p, [](auto&&...) {});
Paul's avatar
Paul committed
380
    return os;
Paul's avatar
Paul committed
381
382
}

Paul's avatar
Paul committed
383
} // namespace migraph