program.cpp 11.8 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>
Paul's avatar
Paul committed
6
#include <iostream>
Paul's avatar
Paul committed
7
#include <sstream>
Paul's avatar
Paul committed
8
9
#include <algorithm>

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

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

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

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

Paul's avatar
Paul committed
23
template <class F>
Paul's avatar
Paul committed
24
25
26
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
static void print_program(std::ostream& os, const program& p, F annonate)
{
    std::unordered_map<const instruction*, std::string> names;
    int count = 0;

    for(auto& ins : p)
    {
        std::string var_name = "@" + std::to_string(count);
        if(ins.op.name() == "@param")
        {
            var_name = any_cast<builtin::param>(ins.op).parameter;
        }

        os << var_name << " = ";

        os << ins.op;

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

        if(!ins.arguments.empty())
        {
            char delim = '(';
            for(auto&& arg : ins.arguments)
            {
                assert(p.has_instruction(arg) && "Instruction not found");
                os << delim << names.at(std::addressof(*arg));
                delim = ',';
            }
            os << ")";
        }

        os << " -> " << ins.result;

Paul's avatar
Paul committed
63
        annonate(ins, names);
Paul's avatar
Paul committed
64
65
66
67
68
69
70
71

        os << std::endl;

        names.emplace(std::addressof(ins), var_name);
        count++;
    }
}

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

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

Paul's avatar
Paul committed
78
instruction_ref program::add_instruction(operation op, std::vector<instruction_ref> args)
Paul's avatar
Paul committed
79
80
81
{
    return insert_instruction(impl->instructions.end(), std::move(op), std::move(args));
}
Paul's avatar
Paul committed
82
83
instruction_ref
program::insert_instruction(instruction_ref ins, operation op, std::vector<instruction_ref> args)
Paul's avatar
Paul committed
84
{
Paul's avatar
Paul committed
85
86
87
    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
88
    assert(not starts_with(op.name(), "@"));
Paul's avatar
Paul committed
89
90
    // TODO: Use move
    shape r     = compute_shape(op, args);
Paul's avatar
Paul committed
91
    auto result = impl->instructions.insert(ins, {op, r, args});
Paul's avatar
Paul committed
92
    backreference(result);
Paul's avatar
Paul committed
93
    assert(result->arguments == args);
Paul's avatar
Paul committed
94
    assert(result->valid(begin()));
Paul's avatar
Paul committed
95
    return result;
Paul's avatar
Paul committed
96
97
}

Paul's avatar
Paul committed
98
99
100
101
102
103
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
104
    assert(not starts_with(op.name(), "@"));
Paul's avatar
Paul committed
105

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

Paul's avatar
Paul committed
113
instruction_ref program::replace_instruction(instruction_ref ins, instruction_ref rep)
Paul's avatar
Paul committed
114
{
Paul's avatar
Paul committed
115
116
117
118
    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
119
    if(ins->output.empty())
Paul's avatar
Paul committed
120
121
122
    {
        return rep;
    }
Paul's avatar
Paul committed
123
    for(auto&& out : ins->output)
Paul's avatar
Paul committed
124
    {
Paul's avatar
Paul committed
125
126
        // TODO: Check for possible cycles
        if(out != rep)
Paul's avatar
Paul committed
127
        {
Paul's avatar
Paul committed
128
            replace_argument(out, ins, rep);
Paul's avatar
Paul committed
129
        }
Paul's avatar
Paul committed
130
        assert(out->valid(begin()));
Paul's avatar
Paul committed
131
    }
Paul's avatar
Paul committed
132
133
    // 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
134
    assert(ins->valid(begin()));
Paul's avatar
Paul committed
135
    assert(rep->valid(begin()));
Paul's avatar
Paul committed
136
137
138
    return rep;
}

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

147
148
instruction_ref program::remove_instructions(instruction_ref first, instruction_ref last)
{
Paul's avatar
Paul committed
149
150
    if(first == last)
        return first;
Paul's avatar
Paul committed
151
    // TODO: Check every element
152
    assert(has_instruction(first));
Paul's avatar
Paul committed
153
154
    std::for_each(first, last, [&](instruction& ins) { ins.clear_arguments(); });
    assert(std::all_of(first, last, [&](instruction& ins) { return ins.output.empty(); }));
155
156
157
158
159
160
161
162
163
    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
164
instruction_ref program::add_literal(literal l)
Paul's avatar
Paul committed
165
{
Paul's avatar
Paul committed
166
167
168
169
170
171
172
173
    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
174
175
}

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

Paul's avatar
Paul committed
182
shape program::get_parameter_shape(std::string name) const
Paul's avatar
Paul committed
183
184
{
    auto ins = std::find_if(
Paul's avatar
Paul committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
        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
199
200
}

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

Paul's avatar
Paul committed
215
bool program::has_instruction(instruction_ref ins) const
Paul's avatar
Paul committed
216
{
Paul's avatar
Paul committed
217
218
219
220
    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
221
222
}

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

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

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

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

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

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

Paul's avatar
Paul committed
317
318
319
320
321
322
323
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
324
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
325
    {
Paul's avatar
Paul committed
326
        total_acc += time<milliseconds>([&] { eval(params); });
Paul's avatar
Paul committed
327
328
329
    }
    std::unordered_map<const instruction*, double> ins_acc;
    // Fill the map
Paul's avatar
Paul committed
330
331
    generic_eval(
        *this, this->impl->ctx, params, [&](auto& ins, auto) { ins_acc[std::addressof(ins)] = 0; });
Paul's avatar
Paul committed
332
    // Run and time each instruction
Paul's avatar
Paul committed
333
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
334
    {
Paul's avatar
Paul committed
335
336
        generic_eval(*this, this->impl->ctx, params, [&](auto& ins, auto f) {
            ins_acc[std::addressof(ins)] += time<milliseconds>(f);
Paul's avatar
Paul committed
337
338
339
340
        });
    }
    // Run and time implicit overhead
    double overhead_acc = 0;
Paul's avatar
Paul committed
341
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
342
    {
Paul's avatar
Paul committed
343
344
        overhead_acc += time<milliseconds>(
            [&] { generic_eval(*this, this->impl->ctx, params, [](auto&&...) {}); });
Paul's avatar
Paul committed
345
346
    }

Paul's avatar
Paul committed
347
348
349
    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
350
    double total_instruction_time = 0.0;
Paul's avatar
Paul committed
351
    for(auto&& p : ins_acc)
Paul's avatar
Paul committed
352
        total_instruction_time += p.second / n;
Paul's avatar
Paul committed
353
354
    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
355
356
357
358
359
360
361

    print_program(os, *this, [&](auto& ins, auto&&) {
        os << ": " << ins_acc[std::addressof(ins)] / n << "ms";
    });

    os << "Total time: " << total_time << "ms" << std::endl;
    os << "Total instructions time: " << total_instruction_time << "ms" << std::endl;
Paul's avatar
Paul committed
362
363
364
365
    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
366
367
}

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

Paul's avatar
Paul committed
370
std::ostream& operator<<(std::ostream& os, const program& p)
Paul's avatar
Paul committed
371
{
Paul's avatar
Paul committed
372
    print_program(os, p, [](auto&&...) {});
Paul's avatar
Paul committed
373
    return os;
Paul's avatar
Paul committed
374
375
}

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