1 /*
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <cstdio>
8 #include <iterator>
9 #include "pio_assembler.h"
10 #include "parser.hpp"
11 
12 #ifdef _MSC_VER
13 #pragma warning(disable : 4996) // fopen
14 #endif
15 
16 using syntax_error = yy::parser::syntax_error;
17 
18 std::string output_format::default_name = "c-sdk";
19 
pio_assembler()20 pio_assembler::pio_assembler() {
21 }
22 
generate(std::shared_ptr<output_format> _format,const std::string & _source,const std::string & _dest,const std::vector<std::string> & _options)23 int pio_assembler::generate(std::shared_ptr<output_format> _format, const std::string &_source,
24                             const std::string &_dest, const std::vector<std::string> &_options) {
25     format = _format;
26     source = _source;
27     dest = _dest;
28     options = _options;
29     location.initialize(&source);
30     scan_begin();
31     yy::parser parse(*this);
32 //    parse.set_debug_level(false);
33     int res = parse();
34     scan_end();
35     return res;
36 }
37 
add_instruction(std::shared_ptr<instruction> inst)38 void program::add_instruction(std::shared_ptr<instruction> inst) {
39     uint limit = MAX_INSTRUCTIONS;
40     if (instructions.size() >= limit) {
41         // todo take offset into account
42         std::stringstream msg;
43         msg << "program instruction limit of " << limit << " instruction(s) exceeded";
44         throw syntax_error(inst->location, msg.str());
45     }
46     if (!sideset_opt && !inst->sideset) {
47         std::stringstream msg;
48         msg << "instruction requires 'side' to specify side set value for the instruction because non optional sideset was specified for the program at " << sideset.location;
49         throw syntax_error(inst->location, msg.str());
50     }
51     instructions.push_back(inst);
52 }
53 
54 using syntax_error = yy::parser::syntax_error;
55 
add_symbol(std::shared_ptr<symbol> symbol)56 void program::add_symbol(std::shared_ptr<symbol> symbol) {
57     const auto &existing = pioasm->get_symbol(symbol->name, this);
58     if (existing) {
59         std::stringstream msg;
60         if (symbol->is_label != existing->is_label) {
61             msg << "'" << symbol->name << "' was already defined as a " << (existing->is_label ? "label" : "value")
62                 << " at " << existing->location;
63         } else if (symbol->is_label) {
64             msg << "label '" << symbol->name << "' was already defined at " << existing->location;
65         } else {
66             msg << "'" << symbol->name << "' was already defined at " << existing->location;
67         }
68         throw syntax_error(symbol->location, msg.str());
69     }
70     symbols.insert(std::pair<std::string, std::shared_ptr<::symbol>>(symbol->name, symbol));
71     ordered_symbols.push_back(symbol);
72 }
73 
resolve(const program & program)74 int resolvable::resolve(const program &program) {
75     return resolve(program.pioasm, &program);
76 }
77 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)78 int unary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
79     int value = arg->resolve(pioasm, program, scope);
80     switch (op) {
81         case negate:
82             return -value;
83         case reverse: {
84             // slow is fine
85             uint result = 0;
86             for (uint i = 0; i < 32; i++) {
87                 result <<= 1u;
88                 if (value & 1u) {
89                     result |= 1u;
90                 }
91                 value >>= 1u;
92             }
93             return result;
94         }
95         default:
96             throw syntax_error(location, "internal error");
97     }
98 }
99 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)100 int binary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
101     int lvalue = left->resolve(pioasm, program, scope);
102     int rvalue = right->resolve(pioasm, program, scope);
103     switch (op) {
104         case add:
105             return lvalue + rvalue;
106         case subtract:
107             return lvalue - rvalue;
108         case multiply:
109             return lvalue * rvalue;
110         case divide:
111             return lvalue / rvalue;
112         case and_:
113             return lvalue & rvalue;
114         case or_:
115             return lvalue | rvalue;
116         case xor_:
117             return lvalue ^ rvalue;
118         default:
119             throw syntax_error(location, "internal error");
120     }
121 }
122 
set_wrap(const yy::location & l)123 void program::set_wrap(const yy::location &l) {
124     if (wrap) {
125         std::stringstream msg;
126         msg << ".wrap was already specified at " << wrap->location;
127         throw syntax_error(l, msg.str());
128     }
129     if (instructions.empty()) {
130         throw syntax_error(l, ".wrap cannot be placed before the first program instruction");
131     }
132     wrap = resolvable_int(l, instructions.size() - 1);
133 }
134 
set_wrap_target(const yy::location & l)135 void program::set_wrap_target(const yy::location &l) {
136     if (wrap_target) {
137         std::stringstream msg;
138         msg << ".wrap_target was already specified at " << wrap_target->location;
139         throw syntax_error(l, msg.str());
140     }
141     wrap_target = resolvable_int(l, instructions.size());
142 }
143 
add_code_block(const code_block & block)144 void program::add_code_block(const code_block &block) {
145     code_blocks[block.lang].push_back(block);
146 }
147 
add_lang_opt(std::string lang,std::string name,std::string value)148 void program::add_lang_opt(std::string lang, std::string name, std::string value) {
149     lang_opts[lang].emplace_back(name, value);
150 }
151 
finalize()152 void program::finalize() {
153     if (sideset.value) {
154         int bits = sideset.value->resolve(*this);
155         if (bits < 0) {
156             throw syntax_error(sideset.value->location, "number of side set bits must be positive");
157         }
158         sideset_max = (1u << bits) - 1;
159         if (sideset_opt) bits++;
160         sideset_bits_including_opt = bits;
161         if (bits > 5) {
162             if (sideset_opt)
163                 throw syntax_error(sideset.value->location, "maximum number of side set bits with optional is 4");
164             else
165                 throw syntax_error(sideset.value->location, "maximum number of side set bits is 5");
166         }
167         delay_max = (1u << (5 - bits)) - 1;
168     } else {
169         sideset_max = 0;
170         delay_max = 31;
171     }
172 }
173 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)174 int name_ref::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
175     auto symbol = pioasm->get_symbol(name, program);
176     if (symbol) {
177         if (symbol->resolve_started) {
178             std::stringstream msg;
179             msg << "circular dependency in definition of '" << name << "'; detected at " << location << ")";
180             throw syntax_error(scope.location, msg.str());
181         }
182         try {
183             symbol->resolve_started++;
184             int rc = symbol->value->resolve(pioasm, program, scope);
185             symbol->resolve_started--;
186             return rc;
187         } catch (syntax_error &e) {
188             symbol->resolve_started--;
189             throw e;
190         }
191     } else {
192         std::stringstream msg;
193         msg << "undefined symbol '" << name << "'";
194         throw syntax_error(location, msg.str());
195     }
196 }
197 
encode(const program & program)198 uint instruction::encode(const program &program) {
199     raw_encoding raw = raw_encode(program);
200     int _delay = delay->resolve(program);
201     if (_delay < 0) {
202         throw syntax_error(delay->location, "instruction delay must be positive");
203     }
204     if (_delay > program.delay_max) {
205         if (program.delay_max == 31) {
206             throw syntax_error(delay->location, "instruction delay must be <= 31");
207         } else {
208             std::stringstream msg;
209             msg << "the instruction delay limit is " << program.delay_max << " because of the side set specified at "
210                 << program.sideset.location;
211             throw syntax_error(delay->location, msg.str());
212         }
213     }
214     int _sideset = 0;
215     if (sideset) {
216         _sideset = sideset->resolve(program);
217         if (_sideset < 0) {
218             throw syntax_error(sideset->location, "side set value must be >=0");
219         }
220         if (_sideset > program.sideset_max) {
221             std::stringstream msg;
222             msg << "the maximum side set value is " << program.sideset_max << " based on the configuration specified at "
223                 << program.sideset.location;
224             throw syntax_error(sideset->location, msg.str());
225         }
226         _sideset <<= (5u - program.sideset_bits_including_opt);
227         if (program.sideset_opt) {
228             _sideset |= 0x10u;
229         }
230     }
231     return (((uint) raw.type) << 13u) | (((uint) _delay | (uint) _sideset) << 8u) | (raw.arg1 << 5u) | raw.arg2;
232 }
233 
raw_encode(const program & program)234 raw_encoding instruction::raw_encode(const program &program) {
235     throw syntax_error(location, "internal error");
236 }
237 
encode(const program & program)238 uint instr_word::encode(const program &program) {
239     uint value = encoding->resolve(program);
240     if (value > 0xffffu) {
241         throw syntax_error(location, ".word value must be a positive 16 bit value");
242     }
243     return value;
244 }
245 
raw_encode(const program & program)246 raw_encoding instr_jmp::raw_encode(const program &program) {
247     int dest = target->resolve(program);
248     if (dest < 0) {
249         throw syntax_error(target->location, "jmp target address must be positive");
250     } else if (dest >= (int)program.instructions.size()) {
251         std::stringstream msg;
252         msg << "jmp target address " << dest << " is beyond the end of the program";
253         throw syntax_error(target->location, msg.str());
254     }
255     return {inst_type::jmp, (uint) cond, (uint) dest};
256 }
257 
raw_encode(const program & program)258 raw_encoding instr_in::raw_encode(const program &program) {
259     int v = value->resolve(program);
260     if (v < 1 || v > 32) {
261         throw syntax_error(value->location, "'in' bit count must be >= 1 and <= 32");
262     }
263     return {inst_type::in, (uint) src, (uint) v & 0x1fu};
264 }
265 
raw_encode(const program & program)266 raw_encoding instr_out::raw_encode(const program &program) {
267     int v = value->resolve(program);
268     if (v < 1 || v > 32) {
269         throw syntax_error(value->location, "'out' bit count must be >= 1 and <= 32");
270     }
271     return {inst_type::out, (uint) dest, (uint) v & 0x1fu};
272 }
273 
raw_encode(const program & program)274 raw_encoding instr_set::raw_encode(const program &program) {
275     int v = value->resolve(program);
276     if (v < 0 || v > 31) {
277         throw syntax_error(value->location, "'set' bit count must be >= 0 and <= 31");
278     }
279     return {inst_type::set, (uint) dest, (uint) v};
280 }
281 
raw_encode(const program & program)282 raw_encoding instr_wait::raw_encode(const program &program) {
283     uint pol = polarity->resolve(program);
284     if (pol > 1) {
285         throw syntax_error(polarity->location, "'wait' polarity must be 0 or 1");
286     }
287     uint arg2 = source->param->resolve(program);
288     switch (source->target) {
289         case wait_source::irq:
290             if (arg2 > 7) throw syntax_error(source->param->location, "irq number must be must be >= 0 and <= 7");
291             break;
292         case wait_source::gpio:
293             if (arg2 > 31)
294                 throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 0 and <= 31");
295             break;
296         case wait_source::pin:
297             if (arg2 > 31) throw syntax_error(polarity->location, "pin number must be must be >= 0 and <= 31");
298             break;
299     }
300     return {inst_type::wait, (pol << 2u) | (uint) source->target, arg2 | (source->flag ? 0x10u : 0u)};
301 }
302 
raw_encode(const program & program)303 raw_encoding instr_irq::raw_encode(const program &program) {
304     uint arg2 = num->resolve(program);
305     if (arg2 > 7) throw syntax_error(num->location, "irq number must be must be >= 0 and <= 7");
306     if (relative) arg2 |= 0x10u;
307     return {inst_type::irq, (uint)modifiers, arg2};
308 }
309 
public_symbols(program & program)310 std::vector<compiled_source::symbol> pio_assembler::public_symbols(program &program) {
311     std::vector<std::shared_ptr<symbol>> public_symbols;
312     std::remove_copy_if(program.ordered_symbols.begin(), program.ordered_symbols.end(),
313                         std::inserter(public_symbols, public_symbols.end()),
314                         [](const std::shared_ptr<symbol> &s) { return !s->is_public; });
315 
316     std::vector<compiled_source::symbol> rc;
317     std::transform(public_symbols.begin(), public_symbols.end(), std::back_inserter(rc),
318                    [&](const std::shared_ptr<symbol> &s) {
319                        return compiled_source::symbol(s->name, s->value->resolve(program), s->is_label);
320                    });
321     return rc;
322 }
323 
write_output()324 int pio_assembler::write_output() {
325     std::set<std::string> known_output_formats;
326     std::transform(output_format::all().begin(), output_format::all().end(),
327                    std::inserter(known_output_formats, known_output_formats.begin()),
328                    [&](std::shared_ptr<output_format> &f) {
329                        return f->name;
330                    });
331 
332     compiled_source source;
333     source.global_symbols = public_symbols(get_dummy_global_program());
334     for (auto &program : programs) {
335         program.finalize();
336         source.programs.emplace_back(compiled_source::program(program.name));
337         auto &cprogram = source.programs[source.programs.size() - 1];
338         cprogram = compiled_source::program(program.name);
339 
340         // encode the instructions
341         std::transform(program.instructions.begin(), program.instructions.end(),
342                        std::back_inserter(cprogram.instructions), [&](std::shared_ptr<instruction> &inst) {
343                     return inst->encode(program);
344                 });
345 
346         for (const auto &e : program.code_blocks) {
347             bool ok = false;
348             for(const auto &o : known_output_formats) {
349                 if (o == e.first || 0 == e.first.find(o+"-")) {
350                     ok = true;
351                     break;
352                 }
353             }
354             if (!ok) {
355                 std::cerr << e.second[0].location << ": warning, unknown code block output type '" << e.first << "'\n";
356                 known_output_formats.insert(e.first);
357             }
358         }
359 
360         if (program.wrap) cprogram.wrap = program.wrap->resolve(program); else cprogram.wrap = std::max((int)program.instructions.size() - 1, 0);
361         if (program.wrap_target) cprogram.wrap_target = program.wrap_target->resolve(program); else cprogram.wrap_target = 0;
362         if (program.origin.value) cprogram.origin = program.origin.value->resolve(program);
363         if (program.sideset.value) {
364             cprogram.sideset_bits_including_opt = program.sideset_bits_including_opt;
365             cprogram.sideset_opt = program.sideset_opt;
366             cprogram.sideset_pindirs = program.sideset_pindirs;
367         }
368         std::transform(program.code_blocks.begin(), program.code_blocks.end(), std::inserter(cprogram.code_blocks, cprogram.code_blocks.begin()), [](const std::pair<std::string, std::vector<code_block>>&e) {
369             std::vector<std::string> blocks;
370             std::transform(e.second.begin(), e.second.end(), std::back_inserter(blocks), [&](const code_block& block) {
371                 return block.contents;
372             });
373             return std::pair<std::string, std::vector<std::string>>(e.first, blocks);
374         });
375         cprogram.lang_opts = program.lang_opts;
376         cprogram.symbols = public_symbols(program);
377     }
378     if (programs.empty()) {
379         std::cout << "warning: input contained no programs" << std::endl;
380     }
381     return format->output(dest, options, source);
382 }
383 
open_single_output(std::string destination)384 FILE *output_format::open_single_output(std::string destination) {
385     FILE *out = destination == "-" ? stdout : fopen(destination.c_str(), "w");
386     if (!out) {
387         std::cerr << "Can't open output file '" << destination << "'" << std::endl;
388     }
389     return out;
390 }
391