program.cpp 11.5 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
224
instruction_ref program::begin() const { return impl->instructions.begin(); }
instruction_ref program::end() const { return impl->instructions.end(); }
225

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

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

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

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

Paul's avatar
Paul committed
304
305
306
307
308
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
309
310
311
312
313
314
315
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
316
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
317
    {
Paul's avatar
Paul committed
318
        total_acc += time<milliseconds>([&] { eval(params); });
Paul's avatar
Paul committed
319
320
321
    }
    std::unordered_map<const instruction*, double> ins_acc;
    // Fill the map
Paul's avatar
Paul committed
322
323
    generic_eval(
        *this, this->impl->ctx, params, [&](auto& ins, auto) { ins_acc[std::addressof(ins)] = 0; });
Paul's avatar
Paul committed
324
    // Run and time each instruction
Paul's avatar
Paul committed
325
    for(std::size_t i = 0; i < n; i++)
Paul's avatar
Paul committed
326
    {
Paul's avatar
Paul committed
327
328
        generic_eval(*this, this->impl->ctx, params, [&](auto& ins, auto f) {
            ins_acc[std::addressof(ins)] += time<milliseconds>(f);
Paul's avatar
Paul committed
329
330
331
332
        });
    }
    // Run and time implicit overhead
    double overhead_acc = 0;
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
        overhead_acc += time<milliseconds>(
            [&] { generic_eval(*this, this->impl->ctx, params, [](auto&&...) {}); });
Paul's avatar
Paul committed
337
338
    }

Paul's avatar
Paul committed
339
340
341
    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
342
    double total_instruction_time = 0.0;
Paul's avatar
Paul committed
343
    for(auto&& p : ins_acc)
Paul's avatar
Paul committed
344
        total_instruction_time += p.second / n;
Paul's avatar
Paul committed
345
346
    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
347
348
349
350
351
352
353

    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
354
355
356
357
    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
358
359
}

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

Paul's avatar
Paul committed
362
std::ostream& operator<<(std::ostream& os, const program& p)
Paul's avatar
Paul committed
363
{
Paul's avatar
Paul committed
364
    print_program(os, p, [](auto&&...) {});
Paul's avatar
Paul committed
365
    return os;
Paul's avatar
Paul committed
366
367
}

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