-
Notifications
You must be signed in to change notification settings - Fork 0
/
asmfmt_fwd.hpp
402 lines (373 loc) · 16.9 KB
/
asmfmt_fwd.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
#ifndef ASMFMT_FWD_HPP
#define ASMFMT_FWD_HPP
/* Copyright (c) 2019 by NEC Corporation
* This file is part of ve-jit */
#include "jitpage.h"
#include <iosfwd>
#include <string>
//#include <stack>
#include <vector> // now can match #defines with regex
#include <regex>
#include <list>
#if ASMFMTREMOVE < 2
/* terminal .S --> .bin or throw */
std::string fname_bin( std::string const& fname_S );
/** asm2bin : .S --> .bin . \post .bin file exists and has >0 bytes.
* Makefile bin.mk should exist.
* \pre You want a VE binary blob (so we use ncc/nas/nobjcopy/nobjdump)
* \ret size of binary blob file created.
* \throw on error. */
size_t asm2bin( std::string const& fname_S, int const verbose=1 );
/** read a binary blob file into a new code page */
struct ExecutablePage {
ExecutablePage( std::string const& fname_bin );
~ExecutablePage();
void *addr() const {return page.mem;}
JitPage const page;
static const int verbosity=0;
};
#endif //CODEREMOVE < 2
/* assemble a .S file to a .bin file and load it into an ExecutablePage */
//ExecutablePage asm2page( std::string const& fname_S );
#if ASMFMTREMOVE < 1
/** Simple assembly line formatting. -std=c++11.
* An \c ostringstream wrapper for nice-looking assembler output.
*
* Keeping adding labels, instructions and comments to build up
* an output string. When destroyed [or with \c write] the assembler
* code string is output to \c cout [or a file, depending on constructor].
*
* Being a wrapper around std::ostringstream, you can also get
* a copy of the internal string with \c str(). This way you can
* pretty-format canned coded chunks and assemble them with greater
* control.
*
* \c scope is meant to support a stack of #define #undef things,
* (or maybe they can be implemented as asm macros?)
*
* Should flush() and write() return the formatter to empty state,
* via a->clear(), a->set("") ? Write now it is dead, unusable, inactive!
*
* \ref loops/fuse2.cpp for a demo of how to <em>wire in sub-kernel asm code to
* preselected parent locations</em>. This technique replaces the arbitrariy-deep
* nesting and node search mechanisms of cblock.hpp. Register alloc is done
* <em>by hand</em>, just like in regular assembly programming.
*
* - The trick is to create \e separate AsmFmtCols for each section
* that a sub-kernel might want to add code to,
* - and then at the end \e stitch together the AsmFmtCols outputs in
* correct order for the final program.
*
* - \ref cblock.hpp (to output C code) uses a tree-of-nodes with path-name
* lookups, a slightly different approach (allows nicely indented code output)
*
* \todo remove output file support (and 'cout' support too) since keeping
* everything as string is really the best default policy. Get rid of
* 'write' and 'flush' and simplify the counterproductive 'written' state logic.
*/
class AsmFmtCols {
public: // utility
AsmFmtCols();
~AsmFmtCols(); ///< warn if content or scopes remain
/** default is to allow any same-named variables to alias.
* this is a "root" property, so is we have a parent, we
* propagate this setting to everyone.
* \todo this is a root property that is \e inherited by
* children (but we do not have a proper tree set up yet).
*/
bool allow_alias() const { return this->_allow_alias; }
bool allow_alias(bool const b) { return this->_allow_alias = b; }
/** return copy of internal ostringstream.
* \note This returns a temporary so that directly calling \c c_str()
* on the return value results in a \b dangling pointer. */
std::string str() const;
/** Forcibly empty the output buffer */
void clear();
/** Same as str(), with an implied clear() */
std::string flush(){ std::string ret=str(); clear(); return ret; }
/** Flush any code, \b and also output the \c stack_undefs.
* \return a temporary string */
std::string flush_all() { pop_scopes(); return flush(); }
//
/** output #define text for these string pairs, and push a corresponding
* string of #undef onto a scope-stack.
* \return number of #defines in this scope (which could be zero)
* \c pop_scope() emits the last bunch of #undef text.
*
* It is an error to destruct or early-destruct [via \c write() or \c flush()]
* without having popped all scopes. Or maybe we will auto-pop any remaining?
*
* Note: you may \c def [and \e deprecated: \c undef] symbols one-by-one.
* Such definitions behave as though operating on the global
* scope; i.e. they add to stack_defs[0], stack_undefs[0].
*
* NEW: you can pattern-match the symbol substitutions. The intent is to
* allow searching for "all used registers matching...
* Ex. std::vector<std::string> vs = asm.match("^%v[:digit:]+.*"
*/
template<typename PAIRCONTAINER>
std::size_t scope( PAIRCONTAINER const& pairs, std::string const& block_name="" );
/** emit last set of #undefs. \return number of remaining stack-scopes
* never errs (no-op if scope-stack is empty) */
std::vector<std::string>::size_type pop_scope();
/** pop all scopes (append all active undefs to our internal buffer) */
void pop_scopes();
/** Search active macro \e substitutions that begin \c with.
* Matches stop at the first [ \n\t#/;] character, so if a match with
* "%s1\/\*foo\*\/" would return just plain "%s1".
* We assume that '\#define' is only used to introduce macro substitutions.
* \post returned strings all begin with \c with.
*/
std::vector<std::string> def_words_starting(std::string with) const;
/** like \c def_words_starting, but return cpp macros {name,word}s
* instead of just words (words begin \c with). */
std::vector<std::pair<std::string,std::string>> def_macs_starting(std::string with) const;
/** all defined macros (this and parent[s]) */
std::vector<std::pair<std::string,std::string>> def_macs() const;
/** return macro mapping of \c macname, or empty string (no throw) */
std::string mac_lookup(std::string macname) const;
/** Set scoping parent, returning old pointer [default=NULL].
* You can arrange AsmFmtCols as a nesting of scopes, so we can
* consult \c parent to know about other in-scope \c def macros.
* \return old AsmFmtCols \c parent (usually nullptr).
*/
AsmFmtCols *setParent( AsmFmtCols* p );
/// \defgroup simple formatting
///{
struct AsmLine {
std::string label;
std::string op;
std::string args;
std::string comment;
std::string remain; ///< allow multi-statement, ';' as separator
};
static AsmLine parts(std::string const& instruction); ///< split into op and args (only?)
/** \#define symbol subst (into \b global scope) */
AsmFmtCols& def(std::string const& symbol, std::string const& subst, std::string const name="");
/** undef something explicitly. XXX not robust:
* 1) cannot \em uncover a previous definition?
* 2) might spit out duplicate undef lines?
* Is this fn necesary? Can \c push_scope, \c pop_scope do everything you need?
* POSSIBLY useful for non-register macros.
*/
AsmFmtCols& undef(std::string const& symbol, std::string const& name="");
//@}
AsmFmtCols& raw(std::string const& anything); ///< use this for stuff like cpp macros or output from other AsmFmtCols 'kernels'
AsmFmtCols& lcom(std::string const& comment); ///< left <// comment>
AsmFmtCols& com(std::string const& comment); ///< mid <// comment>
AsmFmtCols& rcom(std::string const& comment); ///< right <# comment>
AsmFmtCols& lab(std::string const& label, std::string const& comment=""); ///< <label:>
AsmFmtCols& ins(); ///< blank line
AsmFmtCols& ins(std::string const& instruction); ///< [ ][op ][args]
/** < ><op >[<args>]< # <asmcomment> */
AsmFmtCols& ins(std::string const& instruction, std::string const& comment);
///}
/// \defgroup comment strings can also be a list of comment lines
///{
template< typename... LComments >
AsmFmtCols& lcom(std::string const& comment, LComments... lcomments){
lcom(comment);
lcom(lcomments...);
return *this;
}
template< typename... Comments >
AsmFmtCols& com(std::string const& comment, Comments... comments){
com(comment);
com(comments...);
return *this;
}
template< typename... RComments >
AsmFmtCols& rcom(std::string const& comment, RComments... rcomments){
rcom(comment);
rcom(rcomments...);
return *this;
}
/** ```
* < ><op >[<args>]< # <asmcomment>
* < // <comment2>i
* ...
* ``` */
template< typename... RComments >
AsmFmtCols& ins(std::string const& instruction, std::string const& comment,
std::string const& comment2, RComments... rcomments){
ins(instruction,comment);
rcom(comment2, rcomments...);
return *this;
}
///}
protected:
std::string fmt_def(std::string const& symbol, std::string const& subst, std::string const& name="");
std::string fmt_undef(std::string const& symbol, std::string const& name);
private:
std::ostringstream *a; ///< [cpp +] assembler code
static char const* const ws; ///< =" \t\r" within-statement whitespace (newline separates statement)
static char const* const indent; ///< = " ";
static int const inwidth; ///< indent width, 4
static int const opwidth; ///< = 12-1;
//static std::streampos const argwidth; ///< = 24-1;
static int const argwidth;
/** push with \c scope, pop with \c pop_scopes or pop_scope.
* Each element is a multiline-string (one or more '\#undef') */
std::vector<std::string> stack_undefs;
/** Each scope has a vector of macro --> substitution strings */
struct StringPairs
: public std::vector<std::pair<std::string,std::string>>
{
/// Convenient \c push_back trimmed versions of \c name and \c subst.
void push_trimmed(std::string name, std::string subst);
};
/** Now that we maintain the original \c stack_defs mapping, we can
* hide the undef string creation details. */
std::string defs2undefs( StringPairs const& macs, std::string block_name );
/** push with \c scope, pop with \c pop_scopes or pop_scope.
* Each element represents one or macro definitions, delimited
* by '\#define'. This was added so the that the set of substitutions
* could be \c match'ed to search for 'all defined regs'.
*
* - Invariant after all calls:
* - \c stack_defs.size()==stack_undefs.size()
* - Every '\#define' in stack_defs[1,2,...] has a matching '\#undef',
* but not for stack_defs[0], the "global" scope, where you can
* manually add/remove defintions with \c def and \c undef.
*
* Hmmm. Rather than parsing lines, it is more convenient to store
* the raw strings (as a reverse mapping of \e subst --> \e macro name.
*/
std::vector<StringPairs> stack_defs;
AsmFmtCols *parent; ///< optional scope parent to support \c def_words_starting
bool _allow_alias; ///< probably should end up as a tree Root property?
};
/** add VE-specific optimizations */
struct AsmFmtVe : public AsmFmtCols
{
static unsigned const MVL=256;
/** LVL as 1 or 2 ops */
AsmFmtVe& set_vector_length(uint64_t const vl);
/** LVL as 1 op if vlen is register, o/w try vlen as u64 string */
AsmFmtVe& set_vector_length(std::string vlen_reg);
};
/** remove asm comments (after '#'),
* accepting ';'- or newline-separated multiline \c asmcode.
* The returned string may be uglified, so you might need to
* pass it through an \c AsmFmtCols::ins again.
* Could be a static member fn? */
std::string uncomment_asm( std::string asmcode );
/// \defgroup VE assembler helpers
//@{
/** \c out = replicated MSB (sign bit) of \c in.
* \return code setting \c out to 0 or -1. */
std::string ve_signum64(std::string out, std::string in);
/** \c out = absolute value of int64_t \c in */
std::string ve_abs64(std::string out, std::string in);
/** This helper is unlike a kernel, because a base pointer remains valid
* forever onward, perhaps even into data stored after the code area.
* \p a assembler output container.
* \p bp assembler register (Ex. %s33)
* \p name of func (Ex. foo)*/
void ve_set_base_pointer( AsmFmtCols & a, std::string bp="%s34", std::string name="foo" );
/** \defgroup VE load scalar register with constant */
//@{
struct OpLoadregStrings{
std::string lea;
std::string log;
std::string shl;
std::string ari;
std::string lea2; ///< 2-op lea
bool one_op() const;
std::string choose() const;
};
/** return all types of found loadreg strings, according to instruction type */
OpLoadregStrings opLoadregStrings( uint64_t const parm );
/** use just one choice with some default instruction-type preference.
* - register usage: the string uses scalar registers
* - OUT (#define as %s3 for \ref veli_loadreg.cpp tests)
* - T0 (tmp register, %s40 in \c veli_loadreg.cpp tests)
*
* so you could output a chunk to load 77 into a scalar register with:
* ```
* std::string load77;
* {
* AsmFmtCols prog;
* prog.def("OUT",[name of your scalar output register]);
* prog.def("T0",[name of your scalar temporary register]);
* prog.ins(choose(opLoadregStrings(77))
* load77 = prog.flush_all();
* }
* ```
* (oh. T0 is no longer required)
* Desired: comment as OUT = hexdec(77), stripping off the opLoadregStrings "debug" comments
*
* \p context is for future use (ex. supply an instruction types of surrounding
* instruction[s] so we can better overlap execution units)
*
* TODO: util for optimal load constant to 'C' variable with inline asm
* (use string replacement of OUT and T0?)
*/
std::string choose(OpLoadregStrings const& ops, void* context=nullptr);
inline std::string OpLoadregStrings::choose() const {
return ::choose(*this);
}
inline bool OpLoadregStrings::one_op() const {
return !lea.empty()
|| !log.empty()
|| !shl.empty()
|| !ari.empty() ;
}
/** \b NOT optimized -- reduce dependence on other headers!
* \c s is a VE register, \c v is the constant to load. */
std::string ve_load64_opt0(std::string s, uint64_t v);
/** load reg \c s with value \c v, optimized (but w/o context).
*
* - Worst case: \b lea/lea (2 ops)
* - This is better that clang/ncc which still seem to be
* clearing the hi bits before the second lea.
*
* - but also checks for 1-op loads via \e logical, \e shift
* and \e arithmetic ops (as well as 1-op \e lea).
*/
std::string ve_load64(std::string s, uint64_t v);
#if 0
/** set vector length (local \c tmp register used iff immN out of 0-127 range). */
std::string ve_set_vector_length(uint64_t immN, std::string tmp);
#endif
/** A common pattern is to pre-select any required registers into an \c AsmScope block,
* and then shove all the assignments into AsmFmtCols::scope(block,name) */
typedef std::list<std::pair<std::string,std::string>> AsmScope;
enum RegSearch { SCALAR, SCALAR_TMP, VECTOR, VECTOR_TMP };
/** add a scalar register to \c block that does conflicts with neither \c block
* nor \c asmfmt scopes (or parent scopes).
*
* - \c search_pattern SCALAR searches %s{{0-7},{34,63},{18,33},
* - VECTOR searches %v{{0-63}}
* - and *_TMP search in reverse order.
*/
void ve_propose_reg( std::string variable,
AsmScope& block, // add {variable,register} to block
AsmFmtCols const& a, // excluding registers known to block or a
enum RegSearch const how );
/** A customized register search order. */
void ve_propose_reg( std::string variable,
AsmScope& block,
AsmFmtCols const& a,
std::string prefix,
std::vector<std::pair<int,int>> const search_order
);
//@}
/* dispatch table based on 0 <= %s0 < nCases.
* - Register Usage example:
* - CASE %s0
* - CASE_ERR %s1
* - %s0 out-of-range returns CASE_ERR = +/- 1; normally CASE_ERR is 0
*
* TODO return a vector of code entry points, where client can inject arbitrary
* assembler implementations.
*
* We need a ProgNode tree structure to make this easier!
*
* - i.e. something like a regs/progTree.hpp,
* - or its path-based 'C' version in cblock.hpp)
*/
//@}
#endif //CODEREMOVE < 1
// vim: ts=4 sw=4 et cindent cino=^=l0,\:.5s,=-.5s,N-s,g.5s,b1 cinkeys=0{,0},0),\:,0#,!^F,o,O,e,0=break
#endif // ASMFMT_FWD_HPP