program.cpp 11.7 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
        if(enabled(MIGRAPH_TRACE_COMPILE{}))
248
            std::cout << *this << std::endl;
Paul's avatar
Paul committed
249
#ifndef NDEBUG
250
251
        if(enabled(MIGRAPH_TRACE_COMPILE{})) 
            std::cout << "Validate ..." << std::endl;
Paul's avatar
Paul committed
252
        auto invalid = this->validate();
Paul's avatar
Paul committed
253
254
        if(invalid != impl->instructions.end())
        {
Paul's avatar
Paul committed
255
            auto index = std::distance(impl->instructions.begin(), invalid);
Paul's avatar
Paul committed
256
            MIGRAPH_THROW(p.name() + " pass produces invalid program at instruction " +
Paul's avatar
Paul committed
257
                          std::to_string(index) + ": " + invalid->op.name());
Paul's avatar
Paul committed
258
        }
259
260
        if(enabled(MIGRAPH_TRACE_COMPILE{})) 
            std::cout << std::endl;
Paul's avatar
Paul committed
261
262
#endif
    }
Paul's avatar
Paul committed
263
    auto invalid = this->validate();
Paul's avatar
Paul committed
264
265
    if(invalid != impl->instructions.end())
    {
Paul's avatar
Paul committed
266
267
268
        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
269
270
}

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

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

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

    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
358
359
360
361
    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
362
363
}

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

Paul's avatar
Paul committed
366
std::ostream& operator<<(std::ostream& os, const program& p)
Paul's avatar
Paul committed
367
{
Paul's avatar
Paul committed
368
    print_program(os, p, [](auto&&...) {});
Paul's avatar
Paul committed
369
    return os;
Paul's avatar
Paul committed
370
371
}

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