In scope of supporting cavatools, one of crucial pieces is instruction decoder for Power ISA. This task can re-use some of existing infrastructure of the project, relying on nMigen and insndb. It is critically important to provide the code which can be used as a standalone entity: we can and should generate some bits for this code, but it should not rely on external components other than cavatools itself.
In an ideal world I think we should not even depend on cavatools (in fact, the dependency should be opposite). However, I relaxed this requirement for this iteration due to the time constraints and the understanding that the world is not ideal.
With new visitors (https://bugs.libre-soc.org/show_bug.cgi?id=1094) we should (mostly?) be ready to generate the huge major opcode table like binutils do. Approximately the following steps: 1. Generate a huge table with instructions sorted by opcodes, along with the operands. 2. Provide a way to extend this table with additional data we currently lack a bit (e.g. type of operand, various flags, etc.). 3. Supply an additional manually written code which performs the lookup based on the instruction. 4. Everything should be in C. I suggest we compile this code into standalone library so that it can be reused by anything which needs instruction decoding. I assume we'll need this functionality later for other activities as well.
After some thoughts, I decided to postpone raising subtasks here, because the overall description above already perfectly summarizes the task. I suggest that we limit this exact task to the library, and leave the actual adoption for later milestones. I will, however, check that the library works; if time permits, I'll even might provide some tests.
I started working on this task, marked as "in progress"
OK, it looks like I have something already. Still working on that, but the result looks promising. Here is the sketch; I've deliberately dropped "parts" of the code (8K+ lines). static const struct svp64_operand svp64_operands[] = { /* snip */ [29] = /* SVxd */ { .asm = svp64_operand_nonzero_asm, .dis = svp64_operand_nonzero_dis, .fields = {6, 7, 8, 9, 10}, }, /* snip */ [64] = /* FRT */ { .asm = svp64_operand_fpr_asm, .dis = svp64_operand_fpr_dis, .fields = {6, 7, 8, 9, 10}, }, /* snip */ [70] = /* FRA */ { .asm = svp64_operand_fpr_asm, .dis = svp64_operand_fpr_dis, .fields = {11, 12, 13, 14, 15}, }, /* snip */ }; static const struct svp64_instruction svp64_instructions[] = { /* snip */ { .name = "fpow.", .opcode = { .value = 0xfc0007db, .mask = 0xfc0007ff, }, .operands = { /* FRT */ [0] = &svp64_operands[64], /* FRA */ [1] = &svp64_operands[70], /* FRB */ [2] = &svp64_operands[71], }, }, }; As you can see, the overall code structure is somewhat familiar... :-) This generation needs two visitors, where the first one creates a compact table of operands and the second one creates the instructions. Perhaps there will be more visitors, we have to output more entries.
Ah yes, I forgot that asm is a keyword. Sigh. I'll use assemble/disassemble.
(In reply to Dmitry Selyutin from comment #6) > Ah yes, I forgot that asm is a keyword. i did tell you! > Sigh. I'll use assemble/disassemble. i like "ass" and "disass" but these maaay not be well-received by the wider binutils community :)
(In reply to Dmitry Selyutin from comment #5) > OK, it looks like I have something already. Still working on that, but the > result looks promising. awesome, that was quick. > As you can see, the overall code structure is somewhat familiar... :-) indeed, i suspect it will end up near-identical to binutils obj/asm except perhaps with a stand-alone, more self-contained API? btw watch out for the size of the decoder table, and consider (as a *second* phase of optimisation) using that quine algorithm https://git.libre-soc.org/?p=openpower-isa.git;a=blob;f=src/openpower/decoder/quine.py;hb=HEAD it should then be easy to "group" by the largest number of matching bits (by masked-value up to a maximum set array size, then branch out in a tree: opcode 0b--011111 opcode 0b---11110 results in a "first level" matcher of "0b---1111-". the idea there being to reduce decode time by not having massive numbers of if-elif-elif-elif conditions but instead a cascading sequence of jump-tables. hell, even arrays of functions to call that decode the next part. anything to avoid the massive switch/case mess. i would expect this to create a three-level jump-table, corresponding to MAJOR PO, then 1/2 of the XO (bits 26-31), and finally bits 21-25 although ironically you might as well do that by hand, sigh. a fun option to explore would be hashes, but not just any hash: *actually dynamically and randomly find one* that happens to produce 100% unique hits (remember "illegal opcode" has to be also detected) and if you *really* want some fun, and to not have L1 Cache end up being obliterated due to the size of decode being so large it cannot fit in L1 Cache, look up "MROB RIES", which is a FORTH engine, and adapt it to find an *equation* that produces a unique index for each opcode (!!! yes really! i have seen this done with UCS-2 to DOS Codepage tables, in samba) but... all still way into the future as an optimisation.
(In reply to Luke Kenneth Casson Leighton from comment #7) > (In reply to Dmitry Selyutin from comment #6) > > Ah yes, I forgot that asm is a keyword. I tend to use x_ when x is a keyword. So, asm_
sounds like you want https://en.wikipedia.org/wiki/Perfect_hash_function#Minimal_perfect_hash_function
TL;DR: at this stage, this will likely heavily follow binutils. IIRC they hash PO. Detailed answer. At this stage, I will most likely avoid optimizing it. I want to make something that works, and care about optimizations at the later stage. First, they are hard; second, I have not so much time; third, but not the least, if I dive into optimizing it now, this will go outside of budget bounds.
Also, I insist we will limit this to Power ISA which we support, and likely minus SVP64 disassembly. I don't want to copy manually written bits of binutils code, and would rather generate parts of it (that is, extend fields with visitors, and the ini-like or similar format). But these deserve standalone tasks. Any objections?
So, to summarize: 0. Only disassembly. I reserve the right to prepare some ground for assembly, you already saw, but I don't promise assembly (it's even outside of the tasks description). 1. Vanilla 32-bit instructions, but only those we have in openpower-isa. I assume all operands, all names, etc. 2. No SVP64 yet. No sooner than we have some format for fields. I'm tired of editing it in two places, and the third one will likely lead me to a suicide. 3. No nifty extensions whatsoever. 4. Code will follow binutils logic mostly. All optimizations beyond are likely to be done later.
(In reply to Dmitry Selyutin from comment #11) > TL;DR: at this stage, this will likely heavily follow binutils. IIRC they > hash PO. makes sense. > Detailed answer. At this stage, I will most likely avoid optimizing it. gooood. (In reply to Dmitry Selyutin from comment #13) > So, to summarize: > 0. Only disassembly. gooood. > I don't promise assembly (it's even outside of the tasks description). ohh yes very much so. i'm slightly surprised you mention it, i can't see what use there would be: i believe cavatools might actually use objdump/disasm in its console? > 1. Vanilla 32-bit instructions, but only those we have in openpower-isa. yes absolutely. this is a crucial first step (even in the openpower-isa PowerDecoder / PowerDecoder2) the augmentation from SVP64 is entirely separate. i absolutely insisted that there be NO mixing or changing of SVP64-Prefixed instruction encoding, precisely so that the 32-bit decoder may be used as-is, later. > 2. No SVP64 yet. agreed. it is firmly out of scope but would be covered by the separate task anyway. (at which point, using c++ classes and operator-overloads (on registers in particular) will make the transformation of RA-as-5-bit into RA-as-7-bit much MUCH easier, but again this is definitely NOT part of this task).
I think you remember how binutils did these tricks, eh? #define TO TBR + 1 #define DUI TO #define SVme TO #define SVG TO #define TO_MASK (0x1f << 21) { 0x1f, 21, NULL, NULL, 0 }, /* The UI field in a D form instruction. */ #define UI TO + 1 { 0xffff, 0, NULL, NULL, 0 }, I've updated the generation so that it's clear that the same entry may refer to multiple possible operands. [0] = /* BO, SVG, SVme, TO */ { .assemble = svp64_operand_assemble, .disassemble = svp64_operand_disassemble, .fields = {6, 7, 8, 9, 10}, }, The decision on whether two different operands are the same depends on two key factors: 1. Is operand class (cf. insndb/core.py) the same? 2. If so, are it's fields the same? We don't care about names, at all, other than for documentation purposes.
You know what? I thought about it more. Who needs these fields, we can create assemble/disassemble functions dealing with fields immediately, instead of passing the fields into the generic function. This way, the whole assemble/disassemble process becomes just a simple jump to the corresponding function.
Yep, like this: [0] = /* BO, SVG, SVme, TO */ { .assemble = svp64_operand_0_assemble, .disassemble = svp64_operand_0_disassemble, }, [1] = /* RA */ { .assemble = svp64_operand_1_assemble, .disassemble = svp64_operand_1_disassemble, }, And, once svp64_operand_##_assemble and svp64_operand_##_disassemble are generated, we have just a jump.
(In reply to Dmitry Selyutin from comment #15) > I think you remember how binutils did these tricks, eh? > > #define TO TBR + 1 > #define DUI TO > #define SVme TO > #define SVG TO oh dear... > We don't care about names, at all, other than for documentation purposes. internally, no. externally, yes. i.e. "as an API user", it should be bloody obvious, when someone looks at the spec, that "operand N is under Form M which oh look it's really obvious that that links this header file line Y to section A.B.C of the spec" therefore can you please make sure that the header-file and/or structs contain the Form as part of the "unique identification"? something like: X_FORM_L0 X_FORM_L1 ... i have very specifically ensured that "L" (which is a nightmare candidate that i have been warning IBM about for 3 years) as an example is unique... *only* if you prefix it with the Form, by renaming conflicting occurrences of "L" to "L0 L1 L2 L3". i really want this to work with c, and not to critically depend on c++ namespaces.
(In reply to Dmitry Selyutin from comment #16) > You know what? I thought about it more. Who needs these fields, we can > create assemble/disassemble functions dealing with fields immediately, > instead of passing the fields into the generic function. > This way, the whole assemble/disassemble process becomes just a simple jump > to the corresponding function. you have to be very careful with higher-order-functions because they are very expensive (unless static-inlined, and even then) this has to be a *high performance* API, with a budget of only 50 CPU cycles per emulated instruction. each function call blows a whopping *ten percent* of that available budget.
(In reply to Luke Kenneth Casson Leighton from comment #18) > (In reply to Dmitry Selyutin from comment #15) > > I think you remember how binutils did these tricks, eh? > > > > #define TO TBR + 1 > > #define DUI TO > > #define SVme TO > > #define SVG TO > > oh dear... I knew you'd like it! They even had to drop parentheses in constructs like (TBR + 1), since some compilers bark at those. > > We don't care about names, at all, other than for documentation purposes. > > internally, no. externally, yes. i.e. "as an API user", it should > be bloody obvious, when someone looks at the spec, that "operand N > is under Form M which oh look it's really obvious that that links > this header file line Y to section A.B.C of the spec" > > therefore can you please make sure that the header-file and/or structs > contain > the Form as part of the "unique identification"? something like: Sure thing! Since it's not needed for either assembly or dissasembly yet, I'll handle it later, OK? I'm planning to add even more stuff. I eventually want the library to cover everything which we have in insndb. I think that, regardless of cavatools, this will be useful per se. So, short answer: yes, there will be many enums eventually, not that many in scope of this task though. > X_FORM_L0 > X_FORM_L1 > ... > > i have very specifically ensured that "L" (which is a nightmare > candidate that i have been warning IBM about for 3 years) as an > example is unique... *only* if you prefix it with the Form, by > renaming conflicting occurrences of "L" to "L0 L1 L2 L3". > > i really want this to work with c, and not to critically depend > on c++ namespaces. This all will be C-based. The only use case for C++ I'm thinking of is register remapping. That said, even there I'll likely avoid C++. First (and yes, this is a damn good reason!), I don't like it. Second, it will complicate things. Third... do we even need third here? Wrapping C API is trivial, especially for C++; if some weirdo needs classes and templates (no idea why), they can do it themselves. (In reply to Luke Kenneth Casson Leighton from comment #19) > you have to be very careful with higher-order-functions because they are > very expensive (unless static-inlined, and even then) > > this has to be a *high performance* API, with a budget of only 50 CPU cycles > per emulated instruction. > > each function call blows a whopping *ten percent* of that available budget. Yes, binutils optimize the case when callback is empty, and deal with it internally. I'm thinking of another way to handle it AND at the same time still generate it. Stay tuned.
OK, here's a better idea. No functions at all other than one huge function which performs the jumps. static inline int64_t svp64_operand_disassemble_value(size_t id, uint64_t insn) { { switch (id) { case 0: /* BO, SVG, SVme, TO */ return (int64_t)( (((insn >> UINT64_C(21)) & UINT64_C(1)) << UINT64_C(0)) | (((insn >> UINT64_C(22)) & UINT64_C(1)) << UINT64_C(1)) | (((insn >> UINT64_C(23)) & UINT64_C(1)) << UINT64_C(2)) | (((insn >> UINT64_C(24)) & UINT64_C(1)) << UINT64_C(3)) | UINT64_C(0) ); case 1: /* RA */ return (int64_t)( (((insn >> UINT64_C(16)) & UINT64_C(1)) << UINT64_C(0)) | (((insn >> UINT64_C(17)) & UINT64_C(1)) << UINT64_C(1)) | (((insn >> UINT64_C(18)) & UINT64_C(1)) << UINT64_C(2)) | (((insn >> UINT64_C(19)) & UINT64_C(1)) << UINT64_C(3)) | UINT64_C(0) ); } That's obviously is a sketch, but should illustrate the idea.
(In reply to Dmitry Selyutin from comment #21) > static > case 0: /* BO, SVG, SVme, TO */ > return (int64_t)( > (((insn >> UINT64_C(21)) & UINT64_C(1)) << UINT64_C(0)) | compilers will merge all these together as static bitshifts. should do fine.
(In reply to Luke Kenneth Casson Leighton from comment #19) > each function call blows a whopping *ten percent* of that available budget. with tail-call optimization, you can get even better performance than a giant switch if you do it right... also modern cpus usually correctly predict exactly where call and return insns go leaving them as very low cost (maybe 1 cycle each, or less if they overlap other computation), so, no, calls aren't always expensive. (In reply to Dmitry Selyutin from comment #21) > OK, here's a better idea. No functions at all other than one huge function > which performs the jumps. i think having separate functions is easier to understand, and with __attribute__((always_inline)) produces essentially identical code.
(In reply to Jacob Lifshay from comment #23) > (In reply to Luke Kenneth Casson Leighton from comment #19) > > each function call blows a whopping *ten percent* of that available budget. > > with tail-call optimization, you can get even better performance than a > giant switch if you do it right... > > also modern cpus usually correctly predict exactly where call and return > insns go leaving them as very low cost (maybe 1 cycle each, or less if they > overlap other computation), so, no, calls aren't always expensive. I wouldn't rely too much on optimizations in the generated code. When it's short and manually-written, it's already difficult enough to perform such optimizations. > (In reply to Dmitry Selyutin from comment #21) > > OK, here's a better idea. No functions at all other than one huge function > > which performs the jumps. > > i think having separate functions is easier to understand, and with > __attribute__((always_inline)) produces essentially identical code. On the generation _product_ -- yes. I'd argue it's easier when we're talking about the generator _itself_. :-) Also, I'd like to avoid non-portable extensions whenever possible. We do the same thing as binutils except that we don't have the distinction between "usual" and "special" operands (those with and without callbacks). The function with the switch is static and is a simple (C99) inline. Sufficient for me, I'm moving on.
(In reply to Dmitry Selyutin from comment #24) > On the generation _product_ -- yes. I'd argue it's easier when we're talking > about the generator _itself_. :-) Also, I'd like to avoid non-portable > extensions whenever possible. if you're worried about portability, just use any of the macros that expand to always inline when available and nothing otherwise: https://github.com/bminor/binutils-gdb/blob/41770089162edd23ea7ae33b7952585316accde5/gnulib/config.in#L1642
(In reply to Jacob Lifshay from comment #23) > also modern cpus usually correctly predict exactly where call and return > insns go leaving them as very low cost (maybe 1 cycle each, or less if they > overlap other computation), so, no, calls aren't always expensive. e.g. here, the call/return pair costs 0.13 ns/call on average when I tested it, which is less than one clock cycle: https://gcc.godbolt.org/z/vfME5dbjG
(In reply to Dmitry Selyutin from comment #24) > We do the same thing as binutils except that we don't have the distinction > between "usual" and "special" operands gooood. the non-portable extensions (callbacks) were (hypothesis) because of a lack of thoroughness in insndb [?] > (those with and without callbacks). > The function with the switch is static and is a simple (C99) inline. > Sufficient for me, I'm moving on. does-the-job. gets you to the next hoop. makes more money for you because it works and was quicker to implement. am totally behind that.
(In reply to Luke Kenneth Casson Leighton from comment #27) > (In reply to Dmitry Selyutin from comment #24) > > gooood. the non-portable extensions (callbacks) were (hypothesis) because > of a lack of thoroughness in insndb [?] Naaah, that was just an attempt to make their special case a common one. They call callbacks when they have it; my first idea was just to call it unconditionally. Then I revisited this place and thought that there's no real need to call something unconditionally (yet creating one or two additional tables) when this can be converted to direct jumps to code. That is, why bother jumping to data and hope it'd be optimized when we can just jump to the code directly? :-) Any approach can be done, but jumping to the code location makes it even simpler. Speaking of non-portable extensions, we could've even have this: { static void *table[] = { &&operand_0, &&operand_1, &&operand_3 }; goto *table[id]; operand_0: /* code 0 */ operand_1: /* code 1 */ } But, in the end of the day, it's just the same as switch, just highly non-portable one. I'm happy with the switch for now. :-)
That said, to support Jacob: I agree that generally jumping to function by its pointer via index is cleaner and more readable. It's just that I can ignore this for now, because in the generated code it really wouldn't help: navigating in switch or navigating in array for generated code is kinda the same for large amount of entries.
Hi folks! Good news, I've already implemented a very trivial generated library based on visitors. This library is intended to keep both assembler and disassembler; the code looks like this (obviously the code is truncated): $ cat svp64.h enum svp64_state { SVP64_SUCCESS, SVP64_ERROR_LOOKUP, SVP64_ERROR_OPERAND_0, SVP64_ERROR_OPERAND_1, SVP64_ERROR_OPERAND_2, SVP64_ERROR_OPERAND_3, SVP64_ERROR_OPERAND_4, SVP64_ERROR_OPERAND_5, SVP64_ERROR_OPERAND_6, SVP64_ERROR_OPERAND_7, }; struct svp64_opcode { uint32_t value; uint32_t mask; }; struct svp64_record { struct svp64_opcode opcode; uint8_t operands[8]; char name[16]; }; struct svp64_ctx { struct svp64_record const *record; int64_t operands[8]; }; enum svp64_state svp64_disassemble(struct svp64_ctx *ctx, uint32_t insn); struct svp64_record const * svp64_lookup_insn(uint32_t insn); $ cat svp64-dis-gen.c static inline enum svp64_state svp64_disassemble_operand(struct svp64_ctx *ctx, uint32_t insn, size_t id) { int64_t value; switch (ctx->record->operands[id]) { case 0x02: /* RA */ value = (int64_t)( /* 11 */ (((insn >> UINT64_C(20)) & UINT64_C(1)) << UINT64_C(4)) | /* 12 */ (((insn >> UINT64_C(19)) & UINT64_C(1)) << UINT64_C(3)) | /* 13 */ (((insn >> UINT64_C(18)) & UINT64_C(1)) << UINT64_C(2)) | /* 14 */ (((insn >> UINT64_C(17)) & UINT64_C(1)) << UINT64_C(1)) | /* 15 */ (((insn >> UINT64_C(16)) & UINT64_C(1)) << UINT64_C(0)) | UINT64_C(0) ); break; case 0x03: /* SI */ value = (int64_t)( ( ( /* 16 */ (((insn >> UINT64_C(15)) & UINT64_C(1)) << UINT64_C(15)) | /* 17 */ (((insn >> UINT64_C(14)) & UINT64_C(1)) << UINT64_C(14)) | /* 18 */ (((insn >> UINT64_C(13)) & UINT64_C(1)) << UINT64_C(13)) | /* 19 */ (((insn >> UINT64_C(12)) & UINT64_C(1)) << UINT64_C(12)) | /* 20 */ (((insn >> UINT64_C(11)) & UINT64_C(1)) << UINT64_C(11)) | /* 21 */ (((insn >> UINT64_C(10)) & UINT64_C(1)) << UINT64_C(10)) | /* 22 */ (((insn >> UINT64_C(9)) & UINT64_C(1)) << UINT64_C(9)) | /* 23 */ (((insn >> UINT64_C(8)) & UINT64_C(1)) << UINT64_C(8)) | /* 24 */ (((insn >> UINT64_C(7)) & UINT64_C(1)) << UINT64_C(7)) | /* 25 */ (((insn >> UINT64_C(6)) & UINT64_C(1)) << UINT64_C(6)) | /* 26 */ (((insn >> UINT64_C(5)) & UINT64_C(1)) << UINT64_C(5)) | /* 27 */ (((insn >> UINT64_C(4)) & UINT64_C(1)) << UINT64_C(4)) | /* 28 */ (((insn >> UINT64_C(3)) & UINT64_C(1)) << UINT64_C(3)) | /* 29 */ (((insn >> UINT64_C(2)) & UINT64_C(1)) << UINT64_C(2)) | /* 30 */ (((insn >> UINT64_C(1)) & UINT64_C(1)) << UINT64_C(1)) | /* 31 */ (((insn >> UINT64_C(0)) & UINT64_C(1)) << UINT64_C(0)) | UINT64_C(0) ) ^ (UINT64_C(1) << (UINT64_C(16) - 1)) ) - (UINT64_C(1) << (UINT64_C(16) - 1)) ); break; case 0x04: /* RS, RT */ value = (int64_t)( /* 6 */ (((insn >> UINT64_C(25)) & UINT64_C(1)) << UINT64_C(4)) | /* 7 */ (((insn >> UINT64_C(24)) & UINT64_C(1)) << UINT64_C(3)) | /* 8 */ (((insn >> UINT64_C(23)) & UINT64_C(1)) << UINT64_C(2)) | /* 9 */ (((insn >> UINT64_C(22)) & UINT64_C(1)) << UINT64_C(1)) | /* 10 */ (((insn >> UINT64_C(21)) & UINT64_C(1)) << UINT64_C(0)) | UINT64_C(0) ); break; case 0x05: /* RB */ value = (int64_t)( /* 16 */ (((insn >> UINT64_C(15)) & UINT64_C(1)) << UINT64_C(4)) | /* 17 */ (((insn >> UINT64_C(14)) & UINT64_C(1)) << UINT64_C(3)) | /* 18 */ (((insn >> UINT64_C(13)) & UINT64_C(1)) << UINT64_C(2)) | /* 19 */ (((insn >> UINT64_C(12)) & UINT64_C(1)) << UINT64_C(1)) | /* 20 */ (((insn >> UINT64_C(11)) & UINT64_C(1)) << UINT64_C(0)) | UINT64_C(0) ); break; } $ cat svp64-opc-gen.c [29] = { .name = "addi", .opcode = { .value = UINT64_C(0x0000000038000000), .mask = UINT64_C(0x00000000fc000000), }, .operands = { [0] = UINT8_C(0x04), /* RT */ [1] = UINT8_C(0x02), /* RA */ [2] = UINT8_C(0x03), /* SI */ [3] = UINT8_C(0x00), /* NIL */ }, }, [232] = { /* * OE=0 [21] * Rc=0 [31] */ .name = "add", .opcode = { .value = UINT64_C(0x000000007c000214), .mask = UINT64_C(0x00000000fc0007ff), }, .operands = { [0] = UINT8_C(0x04), /* RT */ [1] = UINT8_C(0x02), /* RA */ [2] = UINT8_C(0x05), /* RB */ [3] = UINT8_C(0x00), /* NIL */ }, },
I implemented a hash lookup by PO as well. With this lookup, here's what we have: $ echo -n > /tmp/test.s; for i in $(seq 4194304); do echo "addi 2,1,-1" >> /tmp/test.s; done $ powerpc64le-linux-gnu-as -mlibresoc /tmp/test.s -o /tmp/test.o && powerpc64le-linux-gnu-objcopy -O binary /tmp/test.o /tmp/bin.o $ time powerpc64le-linux-gnu-objdump -dr -mpowerpc -D -b binary -mpowerpc -EL /tmp/bin.o > /dev/null real 0m3.033s user 0m1.922s sys 0m1.082s $ cat test.c #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include "svp64.h" int main(int argc, char *argv[]) { FILE *fp; struct svp64_ctx ctx; fp = fopen(argv[1], "rb"); for (size_t index = 0; index < 4194304UL; ++index) { uint32_t insn; enum svp64_state state; fread(&insn, sizeof(uint32_t), 1, fp); state = svp64_disassemble(&ctx, insn); if (state != SVP64_SUCCESS) abort(); printf("%s ", ctx.record->name); for (size_t id = 0; ctx.record->operands[id]; ++id) printf("%ld ", ctx.operands[id]); printf("\n"); } return 0; } $ ./test /tmp/bin.o > /dev/null real 0m0.854s user 0m0.828s sys 0m0.023s I for sure realize that binutils does more work than we do, but still, I think this is a good start.
The commits list: 1. Updates to insndb (mostly minor, in a way which makes it possible to e.g. walk over different entries): https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=7222af4f4d12235a67b266321c9513aafe92c3ec https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=35a4c7293e48b9a35245b6e159b13913b9e9197a https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=5a6cdbcee899209dbdfe17d10365e6f07cc10838 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=64a5a5140b4709e3d7a6880c105ff00c6f078505 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=399b101e80e8d07a0fc999bae9736a45f23bf4ff 2. libsvp64 build and static files: https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=2fc1852e1c6cb13839d5fa0c9bfe3147f4913ff8 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=b26ec8839992ade8363674f767c74f4a1d93e949 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=aef4872edb2b99572067caa6ccfa23beab00732f 3. The code generator: https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=ba7703509ec242d5ef3a54da7ceca42171ca0485 The overall project can be found at libsvp64 branch: https://git.libre-soc.org/?p=openpower-isa.git;a=shortlog;h=refs/heads/libsvp64
The next plans are to handle other operand types (e.g. generating the correct masks; for now we support only usual DynamicOperand and SignedOperand classes). I plan to handle other operand types and, when that's done, mark this task as closed, because that's fairly sufficient for this revision. First of all, I'm sorry about the code quality. Luke, I'm sorry, this includes line breaks; I have so little time that I really wanted to make something that works and paid little attention to anything besides making it work. Feel free to refactor it, or you can take my promise that I'll do it eventually. I'm really, really sorry, but I wanted to have a result to show. You can format it (just, please, use a single indent, OK?). Or just leave it to me. Besides operands, as I stated in comment #13 (https://bugs.libre-soc.org/show_bug.cgi?id=979#c13), I have no further plans in scope of this task. Everything else is to be handled later. This "everything else" includes: 1. SVP64 support. This one depends on the following: 1.a. Introducing some text format for fields. 1.b. Introducing a generated jump table based on these fields. 2. Tests, including stress tests and tricky cases (like svhape2 vs svshape). 3. Assembler. Yes, we'll eventually need it, because at some stage I'd like to re-use bits of this generated code in binutils and possibly clang and whatever. 4. Possibly further optimizations (though I should tell I was surprized with the quality even now; I expected much worse outcome). 5. An opportunity for caller to adjust the output based on entity type (like colored output in binutils). Each of these items deserves a standalone task.
(In reply to Dmitry Selyutin from comment #33) > First of all, I'm sorry about the code quality. Luke, I'm sorry, this > includes line breaks; if you're referring to the generated C output, you can just run it through clang-format, it has a GNU style option.
(In reply to Jacob Lifshay from comment #34) > (In reply to Dmitry Selyutin from comment #33) > > First of all, I'm sorry about the code quality. Luke, I'm sorry, this > > includes line breaks; > > if you're referring to the generated C output, you can just run it through > clang-format, it has a GNU style option. Nah, unfortunately I meant codegen.py, which takes care of the generator. As for the generated code, I'm not that strict; but the generator itself, I wish it could be better. That said, despite the code quality... This idea with visitor was REALLY impressive. It'd have taken more time to achieve even this degree without visitors.
Good news everyone! I've updated the code generation so that it also includes the details about the operand flags: struct svp64_operand { uint32_t value; uint32_t flags; }; #define SVP64_OPERAND_SIGNED (UINT32_C(1) << UINT32_C(0)) #define SVP64_OPERAND_GPR (UINT32_C(1) << UINT32_C(1)) #define SVP64_OPERAND_FPR (UINT32_C(1) << UINT32_C(2)) #define SVP64_OPERAND_PAIR (UINT32_C(1) << UINT32_C(3)) #define SVP64_OPERAND_CR3 (UINT32_C(1) << UINT32_C(4)) #define SVP64_OPERAND_CR5 (UINT32_C(1) << UINT32_C(5)) #define SVP64_OPERAND_NONZERO (UINT32_C(1) << UINT32_C(6)) #define SVP64_OPERAND_ADDRESS (UINT32_C(1) << UINT32_C(7)) From now on, every operand has flags assigned as well, including tricky cases like address operands and operands split accross multiple fields: case 0x0e: /* target_addr */ value = ( ( ( /* 16 */ (((insn >> UINT32_C(15)) & UINT32_C(1)) << UINT32_C(13)) | /* 17 */ (((insn >> UINT32_C(14)) & UINT32_C(1)) << UINT32_C(12)) | /* 18 */ (((insn >> UINT32_C(13)) & UINT32_C(1)) << UINT32_C(11)) | /* 19 */ (((insn >> UINT32_C(12)) & UINT32_C(1)) << UINT32_C(10)) | /* 20 */ (((insn >> UINT32_C(11)) & UINT32_C(1)) << UINT32_C(9)) | /* 21 */ (((insn >> UINT32_C(10)) & UINT32_C(1)) << UINT32_C(8)) | /* 22 */ (((insn >> UINT32_C(9)) & UINT32_C(1)) << UINT32_C(7)) | /* 23 */ (((insn >> UINT32_C(8)) & UINT32_C(1)) << UINT32_C(6)) | /* 24 */ (((insn >> UINT32_C(7)) & UINT32_C(1)) << UINT32_C(5)) | /* 25 */ (((insn >> UINT32_C(6)) & UINT32_C(1)) << UINT32_C(4)) | /* 26 */ (((insn >> UINT32_C(5)) & UINT32_C(1)) << UINT32_C(3)) | /* 27 */ (((insn >> UINT32_C(4)) & UINT32_C(1)) << UINT32_C(2)) | /* 28 */ (((insn >> UINT32_C(3)) & UINT32_C(1)) << UINT32_C(1)) | /* 29 */ (((insn >> UINT32_C(2)) & UINT32_C(1)) << UINT32_C(0)) | UINT32_C(0) ) ^ (UINT32_C(1) << (UINT32_C(14) - 1)) ) - (UINT32_C(1) << (UINT32_C(14) - 1)) ); flags = SVP64_OPERAND_SIGNED; break; case 0x12: /* D */ value = ( ( ( /* 16 */ (((insn >> UINT32_C(15)) & UINT32_C(1)) << UINT32_C(15)) | /* 17 */ (((insn >> UINT32_C(14)) & UINT32_C(1)) << UINT32_C(14)) | /* 18 */ (((insn >> UINT32_C(13)) & UINT32_C(1)) << UINT32_C(13)) | /* 19 */ (((insn >> UINT32_C(12)) & UINT32_C(1)) << UINT32_C(12)) | /* 20 */ (((insn >> UINT32_C(11)) & UINT32_C(1)) << UINT32_C(11)) | /* 21 */ (((insn >> UINT32_C(10)) & UINT32_C(1)) << UINT32_C(10)) | /* 22 */ (((insn >> UINT32_C(9)) & UINT32_C(1)) << UINT32_C(9)) | /* 23 */ (((insn >> UINT32_C(8)) & UINT32_C(1)) << UINT32_C(8)) | /* 24 */ (((insn >> UINT32_C(7)) & UINT32_C(1)) << UINT32_C(7)) | /* 25 */ (((insn >> UINT32_C(6)) & UINT32_C(1)) << UINT32_C(6)) | /* 11 */ (((insn >> UINT32_C(20)) & UINT32_C(1)) << UINT32_C(5)) | /* 12 */ (((insn >> UINT32_C(19)) & UINT32_C(1)) << UINT32_C(4)) | /* 13 */ (((insn >> UINT32_C(18)) & UINT32_C(1)) << UINT32_C(3)) | /* 14 */ (((insn >> UINT32_C(17)) & UINT32_C(1)) << UINT32_C(2)) | /* 15 */ (((insn >> UINT32_C(16)) & UINT32_C(1)) << UINT32_C(1)) | /* 31 */ (((insn >> UINT32_C(0)) & UINT32_C(1)) << UINT32_C(0)) | UINT32_C(0) ) ^ (UINT32_C(1) << (UINT32_C(16) - 1)) ) - (UINT32_C(1) << (UINT32_C(16) - 1)) ); flags = SVP64_OPERAND_SIGNED; break; case 0x1b: /* SVxd */ value = ( /* 6 */ (((insn >> UINT32_C(25)) & UINT32_C(1)) << UINT32_C(4)) | /* 7 */ (((insn >> UINT32_C(24)) & UINT32_C(1)) << UINT32_C(3)) | /* 8 */ (((insn >> UINT32_C(23)) & UINT32_C(1)) << UINT32_C(2)) | /* 9 */ (((insn >> UINT32_C(22)) & UINT32_C(1)) << UINT32_C(1)) | /* 10 */ (((insn >> UINT32_C(21)) & UINT32_C(1)) << UINT32_C(0)) | UINT32_C(0) ); flags = SVP64_OPERAND_NONZERO; break;
Relevant commits: https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=b055332d2fd33e2becc1182f1077dbba53295559 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=2bff80cf8a2aa6aac029386c25303f2fea991503 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=77ee7339f8efaa983bdff4f0a645f31c54714065 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=131b0ef445ad8f8fa66edfcb8f53dd06dc716ea0 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=683338af09efd2e07a212bd98f3b7bc1bd142854 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=91159f431d52604e510ad18af899526bb23ae87c https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=69755aff4f33f447b2a2a05f4c252d1b609f8e45 https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=20e350bf93fd4b80b5de2eb01ff75add9b4999a3
(In reply to Dmitry Selyutin from comment #36) > From now on, every operand has flags assigned as well, including tricky > cases like address operands and operands split accross multiple fields: > > case 0x0e: /* target_addr */ ahh fantastic, i wasgoing to ask about those: really we need some expressions (pseudocode fragments), i cannot say further as it is confidential but that is a big hint. ermermer! you said "svp64_operand", that should be "powerisa_operand" or just "power_operand"?
(In reply to Dmitry Selyutin from comment #36) > Good news everyone! I've updated the code generation so that it also > includes the details about the operand flags: > > struct svp64_operand { > uint32_t value; > uint32_t flags; > }; > > #define SVP64_OPERAND_SIGNED (UINT32_C(1) << UINT32_C(0)) yreah these should all be POWERISA_OPERAND_xxx SVP64 is *only* the prefix. nothing to do with Power ISA 32-bit instructions at all.
(In reply to Luke Kenneth Casson Leighton from comment #39) > (In reply to Dmitry Selyutin from comment #36) > > Good news everyone! I've updated the code generation so that it also > > includes the details about the operand flags: > > > > struct svp64_operand { > > uint32_t value; > > uint32_t flags; > > }; > > > > #define SVP64_OPERAND_SIGNED (UINT32_C(1) << UINT32_C(0)) > > yreah these should all be POWERISA_OPERAND_xxx > > SVP64 is *only* the prefix. nothing to do with Power ISA 32-bit > instructions at all. I used svp64 as prefix just becase the lib is called libsvp64. :-) I wanted it to be called libresoc, but libibresoc sounds disguisting (although I know one similar naming case). I think that'd be great to enforse this naming due to popularisation. Other naming options are welcome, as long as they're short.
https://git.libre-soc.org/?p=openpower-isa.git;a=shortlog;h=refs/heads/libsvp64 sorry dmitry, can you please rename it to libpowerisa or libpower? or libopenpower or libopenpowerisa? otherwise it implies that we are attempting to dominate and subsume the entirety of Power ISA which is Trademarked, and the OPF and IBM would have justification to jump on us from a very great height. this is serious. it is actually a Criminal Offense to take Trademarked material and "pass it off" as your own under a different brand. It's called "Counterfeiting" and it's illegal (jail time). please rename the library at your earliest convenience to include "power" or "openpower" in order to respect the Trademark.
(In reply to Dmitry Selyutin from comment #40) > to popularisation. whilst not hijacking the Trademark and attempting to pass off Trademarked material as "belonging to Libre-SOC". that's called "Counterfeiting". > Other naming options are welcome, as long as they're > short. libpower libopenpowerisa libopenpower or libpowerisa.
(In reply to Luke Kenneth Casson Leighton from comment #41) > https://git.libre-soc.org/?p=openpower-isa.git;a=shortlog;h=refs/heads/ > libsvp64 > > sorry dmitry, can you please rename it to libpowerisa or libpower? > or libopenpower or libopenpowerisa? > > otherwise it implies that we are attempting to dominate and > subsume the entirety of Power ISA which is Trademarked, and > the OPF and IBM would have justification to jump on us from > a very great height. > > this is serious. it is actually a Criminal Offense to take > Trademarked material and "pass it off" as your own under > a different brand. It's called "Counterfeiting" and it's > illegal (jail time). Hm. I haven't thought about this aspect. OK then... > please rename the library at your earliest convenience to include > "power" or "openpower" in order to respect the Trademark. How about libop? Or perhaps libopium, OpenPower Instruction Universal-or-Unified M-something? I like op as prefix. But opisa sounds awful.
(In reply to Dmitry Selyutin from comment #43) > How about libop? Or perhaps libopium, OpenPower Instruction > Universal-or-Unified M-something? I like op as prefix. But opisa sounds > awful. Maybe libppcx, PPC extensions?
(In reply to Dmitry Selyutin from comment #44) > Maybe libppcx, PPC extensions? Hm, this would likely hit the same issue as svp64 prefix. Damn, naming things is always the most difficult thing to do. I thought about libinsndb, but that's too vague. libopinsndb is better, but stuff like `opinsndb_record` looks too long... or is it?
(In reply to Dmitry Selyutin from comment #45) > (In reply to Dmitry Selyutin from comment #44) > > Maybe libppcx, PPC extensions? > > Hm, this would likely hit the same issue as svp64 prefix. Damn, naming > things is always the most difficult thing to do. > I thought about libinsndb, but that's too vague. libopinsndb is better, but > stuff like `opinsndb_record` looks too long... or is it? libopid? openpower instruction database?
(In reply to Jacob Lifshay from comment #46) > libopid? openpower instruction database? plus it seems to have no relevant search results so should be unique
do note that if we name it openpower or similar, openpower would reasonably expect it to eventually implement all instructions in the powerisa spec., including po1. that can happen much later tho.
(In reply to Jacob Lifshay from comment #46) > libopid? openpower instruction database? Oh, nice! I totally like it. Luke, do you have any objections if this library is called libopid?
(In reply to Dmitry Selyutin from comment #49) > Oh, nice! I totally like it. Luke, do you have any objections if this > library is called libopid? love it.
Done, the library is now named libopid: https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=905dd24c769e74247935514f5ab8d462724ef798 Further directions are mentioned in comment #13 and comment #33. These, I think, should be done in scope of separate tasks. I think the current state is sufficient to mark this task as completed. Any objections?
(In reply to Dmitry Selyutin from comment #51) > Done, the library is now named libopid: > https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff; > h=905dd24c769e74247935514f5ab8d462724ef798 awesome. > I think the current state is sufficient to mark this task as completed. ah sorry, i'm not seeing any unit tests. that's a hell of a lot of money for such a short amount of code, and i am therefore uncomfortable signing it off. i do appreciate the Visitor system is that powerful, but it in turn means we can achieve more with less (our key strategy) could you do say... mmm... 3-4 instructions that demonstrate how to use it? with real simple "exit(-1)" if fail and exit(0) if true on verifying that the operands decode correctly? the other alternative is to also auto-generate a *python c-module* interface on top, and we can then accelerate *ISACaller* by importing it, replace PowerDecoder entirely (hooray).
(In reply to Luke Kenneth Casson Leighton from comment #52) > could you do say... mmm... > 3-4 instructions that demonstrate how to use it? with real simple > "exit(-1)" if fail and exit(0) if true on verifying that the operands > decode correctly? No, if we do it, we'll do it correctly. I'll come back with something which can be used to check (almost) arbitrary instructions. I have an idea on how to do it so that this will be a cross-check. Otherwise there's no point in illustrating the API with 3-4 instructions, since for now API is dead simple and completely self-explanatory. Tests, on the other hand, are much a better idea. > the other alternative is to also auto-generate a *python c-module* > interface on top, and we can then accelerate *ISACaller* by importing > it, replace PowerDecoder entirely (hooray). Using your own wording, I'd say that's a hell of a little of money for such a big work, and I am therefore uncomfortable starting it in scope of this task. :-) > for such a short amount of code, and i am therefore uncomfortable > signing it off. i do appreciate the Visitor system is that powerful, > but it in turn means we can achieve more with less (our key strategy) Luke, I have to stop at this paragraph, even though I'm not comfortable doing it. Judging the payment based on the code size is a totally wrong metric, which will likely yield not the outcome you expect. The fact that you see this code the way it looks like is because there were _many_ efforts before this code came to life. Don't think it was easy, because in reality wasn't, quite the contrary. It ended up simpler I expected, that's true, but it was not a simple 5-minute job. I ask you, please, don't ever, ever use this as an argument. The only possible outcome we could gain from such strategy is that people would write more code only to justify the payment. Or they might think against writing it at all. I'm writing this, because I've seen this metric in action, and I know the outcome pretty well. Again: please don't use this as an argument, ever. So, to summarize: I'll return with something to be used for testing purposes, because this is The Right Thing To Do. This decision has nothing to do with the nonsensical argument about the code size.
(In reply to Dmitry Selyutin from comment #53) > No, if we do it, we'll do it correctly. I'll come back with something which > can be used to check (almost) arbitrary instructions. if you're happy to do that and it's easy within the budget _great_. but remember what i said (and you do it below) always always assert/state what *you* value the code at. NLnet's guide is EUR 3,000 per *month* so anything over that, we have to have a *really* good reason. > I have an idea on how > to do it so that this will be a cross-check. Otherwise there's no point in > illustrating the API with 3-4 instructions, since for now API is dead simple > and completely self-explanatory. to you, it is, because you wrote it. remember: other users who are unfamiliar *need* documentation and/or examples. > Tests, on the other hand, are much a better idea. they'll illustrate the usage in the same way as examples. i'm happy with that. > > the other alternative is to also auto-generate a *python c-module* > > interface on top, and we can then accelerate *ISACaller* by importing > > it, replace PowerDecoder entirely (hooray). > > Using your own wording, I'd say that's a hell of a little of money for such > a big work, and I am therefore uncomfortable starting it in scope of this > task. :-) i don't have a problem with that at all. > Luke, I have to stop at this paragraph, even though I'm not comfortable > doing it. Judging the payment based on the code size is a totally wrong > metric, i remember the "$1 for tapping with a hammer, $9999 for knowing where to tap" anecdote, and you've given NLnet (and the EU Auditor) enough here to appreciate the value of what you've done. > I'll return with something to be used for testing purposes, because this is > The Right Thing To Do perfect. no rush, but don't push it outside of what you value your contribution and effort, either.
I re-read my comment and came to conclusion it might have given you the wrong impression. To clarify: adding the test is a perfectly fine and valid idea. The only item which attracted my attention was this code size thing, so there was my reaction. Perhaps that was somewhat a bit more exaggerated than needed; if the comment made that impression, I'm sorry, Luke, I didn't want it to appear this way. As for the tests, I actually liked the idea, and the motto of the comment overall was "that's The Right Thing To Do, I really missed it, thank you for pointing that!". I've been thinking of the following: 1. Standalone C executable (opid-dis), which is capable of base instruction decoding (just gets the raw data and outputs in some simple-ish format easy to parse). 2. Some Python test which builds the instructions, assembles them and then feeds to opid-dis and gets its output. 3. When opid-dis output is captured, the Python test compares the results to the expected. The rationale: 1. Most of the logic is in Python. Checking new instructions and extending the test is dead simple. 2. opid-dis can be converted into a real disassembler. Not that we actually need it (perhaps we don't, perhaps we do), but we can grasp what's missing on API side for somebody who wants a real disassembler. 3. At some later stage, we might opt to use the same approach for the assembler. I really would like to have both assembly and disassembly generated, adding each and every instruction to binutils manually is the worst choice.
(In reply to Dmitry Selyutin from comment #55) > > As for the tests, I actually liked the idea, and the motto of the comment > overall was "that's The Right Thing To Do, I really missed it, thank you for > pointing that!". I've been thinking of the following: > 1. Standalone C executable (opid-dis), which is capable of base instruction > decoding (just gets the raw data and outputs in some simple-ish format easy > to parse). > 2. Some Python test which builds the instructions, assembles them and then > feeds to opid-dis and gets its output. > 3. When opid-dis output is captured, the Python test compares the results to > the expected. If you don't mind me butting in, I really like the idea. Because, to my understanding, all supported instructions of the disassembler can be tested with this python script (eventually, probably out-of-scope/unnecessary right now). And having opid-dis as a standalone tool means it can eventually be matured to become a full disassembler (as you have already rationalised). > > 3. At some later stage, we might opt to use the same approach for the > assembler. Making the future work of the assembler easier by having this executable/python test framework sounds great as well. > I really would like to have both assembly and disassembly > generated, adding each and every instruction to binutils manually is the > worst choice. I'm sure we all agree that autogenerating is better. Perhaps another task we can allocate some funding to from one of the grants that need to be used up?
(In reply to Andrey Miroshnikov from comment #56) > (In reply to Dmitry Selyutin from comment #55) > If you don't mind me butting in, I really like the idea. No, I'm really glad that you are interested in this, and it's perfect that I can know your opinion on this. It's difficult to develop something without a feedback; that's one of the reasons I'm writing so many comments during the development. :-) > Because, to my understanding, all supported instructions of the disassembler > can be tested with this python script (eventually, probably > out-of-scope/unnecessary right now). Exactly! I suspect we'll unlikely be able to test each and every instruction at this point, since this took literally months and separate works on rewriting Python asm and disasm to reach even the previous point, but the overall idea is that they must be on par, Python version and C version. And they kinda complement each other: C code is based on Python code, but at some places in Python, where we strive for speed, we'll have to switch to C code. > And having opid-dis as a standalone tool means it can eventually be matured > to become a full disassembler (as you have already rationalised). I have some doubts on the latter part, to be honest. I have no illusions on us being used as a replacement on binutils or clang: they are de facto standard. If we want to survive -- their support is mandatory. However, I'm really, really tired of adding all code in binutils manually, and doing the same work for clang would be completely insane. Since we basically want to support all our code in binutils and might (and likely will) opt to support it in clang, we basically want to have everything ready for writing assembler and disassembler. And the best way to ensure that we indeed have everything ready for assembler and disassembler is writing at least trivial verions of them. Plus it helps to invent the overall API architecture: updating it on the later stages would be quite a difficult task. > Making the future work of the assembler easier by having this > executable/python test framework sounds great as well. Yep. I also have a confession to make: we currently have quite a limited set of tests for assembly and disassembly in Python, and completely lack them for binutils. The latter is mostly caused by the fact that until recent we lacked a way to generate so much code, we're just in the beginning of the way. Until insndb, we had no generalized and uniform way to process all data we had in repository; until visitors, we had no good way to re-use this information for other means (including code generation). > I'm sure we all agree that autogenerating is better. Perhaps another task we > can allocate some funding to from one of the grants that need to be used up? Yep, I think we need to have it. Especially if we want to support other tools, like clang. That's actually the main reason why I chose to implement this code as a library with explicit dis-related parts. Besides assembler, I also shared some future directions. I'll try and list them again in a separate comment (there is some information here and there, some also in my head). Then we can put priorities to these tasks and find volunteers who can help with them: there's a huge amount of work to do, I will be glad for help from all people interested.
(In reply to Andrey Miroshnikov from comment #56) > I'm sure we all agree that autogenerating is better. Perhaps another task we > can allocate some funding to from one of the grants that need to be used up? bug #984. create a subtask under there. the rationale is: it is "progress *towards*" SVP64 decode. don't take the piss (allocate too large an amount) eq/under EUR 2500, 3 such bugs is ok as long as they are still "towards" SVP64. given that 32bit decode is needed before SVP64, you get the idea.
Hi folks, I've created a test which can be as a foundation to check the generated code. The idea is to provide standalone micro-disassembler, which just fetches the instructions from stdin and prints the very basic disassembly (instruction name, operands values, operands flags). The Python test builds the instructions from plain text via insndb, then feeds them to micro-disassembler, then checks whether the expected output matches the actual one. https://git.libre-soc.org/?p=openpower-isa.git;a=commitdiff;h=77fc07a0cb5819a908da042d7a980b44ca782d74 Currently I've checked three variants of addpcis (these have split fields and signed operands), and svshape vs svshape2 (because these have non-zero operands and complicated opcode mapping). Luke, please check whether these are sufficient to mark the task as completed.
(In reply to Dmitry Selyutin from comment #59) > Luke, please check whether these are sufficient to mark the task as > completed. Perfect.