========================================== CPW Part 12: Interface for Code Generation [TOC] ========================================== If you are using my makefile from __Session 21, Page 2__ be sure that you are using tha latest version. In a first versions all source files with the prefix "gen" were considered as generated source files. In the updated version only source files with the prefix "gen_" are considered as generated files. This is important as the source file "gen.c" will be used for implementing an interface for code generation. :links: Session 21, Page 2 -> doc:session21/page02 ---- VIDEO ------------------------------ https://www.youtube.com/embed/yuhFKKUgscE ----------------------------------------- Underlying ULM Instruction Set to Use ===================================== Use the __ULM Instruction Set__ from __Session 14, Page 4__ extended for an _idivq_ instruction where all operands are registers. Here the `isa.txt`: :import: session14/stack/0_ulm_variants/stack/isa.txt [fold] ---- SHELL (path=session14/stack/, hide) -------------------------------------- make make refman mkdir -p /home/www/htdocs/numerik/hpc/ss22/hpc0/session14/stack/ cp 1_ulm_build/stack/refman.pdf /home/www/htdocs/numerik/hpc/ss22/hpc0/session14/stack/ -------------------------------------------------------------------------------- :links: ULM Instruction Set -> https://www.mathematik.uni-ulm.de/numerik/hpc/ss22/hpc0/session14/stack/refman.pdf Session 14, Page 4 -> doc:session14/page04 Initial Interface ================= Below the initial interface for code generation. In the video you saw how parts of it can be implemented. ---- CODE (file=session22/gen.h) ----------------------------------------------- #ifndef ABC_GEN_H #define ABC_GEN_H #include #include typedef uint8_t GenReg; // before the interface can be used an output needs to be specified void genSetOutput(FILE *out); // header / footer void genHeader(void); void genFooter(void); // set active segment void genText(void); void genData(void); void genBSS(void); // generate data void genLabeledUInt(const char *label, uint64_t val); // acquire / release register GenReg genGetReg(void); void genUngetReg(GenReg reg); // load literal into register void genLoadUInt(uint64_t val, GenReg reg); void genLoadLabel(const char *label, GenReg reg); // fetch / store quad word (8 bytes) void genFetch(GenReg addr, GenReg dest); void genStore(GenReg src, GenReg addr); enum GenOp { GEN_OP3R_BEGIN, GEN_ADD_R = GEN_OP3R_BEGIN, GEN_OP3R_END, GEN_OP3I_BEGIN = GEN_OP3R_END, GEN_ADD_I = GEN_OP3I_BEGIN, GEN_OP3I_END, }; // 3 address instructions void genOp3r(enum GenOp op, GenReg reg0, GenReg reg1, GenReg reg2); void genOp3i(enum GenOp op, uint64_t val, GenReg reg1, GenReg reg2); #endif // ABC_GEN_H -------------------------------------------------------------------------------- Implement the interface or extend the implementation as follows: - With _genSetOutput()_ it is possible to specify an open file pointer to which all generated code will be written. It will be necessary to call _genSetOutput()_ to use the interface. Define in the implementation a global variable for the file pointer used by the interface, e.g. ---- CODE (type=c) ----------------------------------------------------------- static FILE *out; ------------------------------------------------------------------------------ With _genSetOutput()_ this file pointer can be set. All internal print functions should write to this file pointer. In the test program you can call _genSetOutput(stdout)_ or you can practise handling of program arguments and file handling ;-) - Change _genGetReg()_ so that only registers with an id of 6 or larger can be acquired. For supporting interger division it will be convenient to reserve register %4 and %5 for internal usage. - In general labels are 64 bit literals. So _genLoadLabel()_ needs to be changed. For keeping it simple we will not use literal pools however. Instead we use the __word operators__ _@w[0-3]. Calling _genLoadLabel("foo", 42)_ should produce the code (without the comment): ---- CODE (type=s) ----------------------------------------------------------- # produced by genLoadLabel("foo", 42) ldzwq @w3(foo), %42 shldwq @w2(foo), %42 shldwq @w1(foo), %42 shldwq @w0(foo), %42 ------------------------------------------------------------------------------ :links: word operators -> doc:session13/page02 - Also _genLoadUInt()_ should be able to load 64-bit literals into a register. Compared to labels this can be supported more efficient. Unsigned integer values in the range $[0, 2^{16})$ can be loaded with a single _ldzwq_ instruction. For example: ---- CODE (type=s) ----------------------------------------------------------- # produced by genLoadUInt(0xFFFF, 42) ldzwq 0xFFFF, %42 ------------------------------------------------------------------------------ If the values are in the range $[2^{16}, 2^{32})$ two instructions can be used. For example: ---- CODE (type=s) ----------------------------------------------------------- # produced by genLoadUInt(0x1234FFFF, 42) ldzwq @w1(0x1234FFFF), %42 shldwq @w0(0x1234FFFF), %42 ------------------------------------------------------------------------------ Analogously all 64-bit literals can be handled. - If in the call of _genOp3i()_ the immediate value is out of range then the implementation should acquire a register, produce an instruction to load the immediate value into this register and then call _genOp3r()_ to produce a corresponding instruction that has only register operands. Of course acquired registers need to be released. - _genOp3r()_ and _genOp3i()_ should support integer operation for addition, subtraction, multiplication and modulo. Change the _enum GenOp_ type to ---- CODE (type=c) ----------------------------------------------------------- enum GenOp { GEN_OP3R_BEGIN, GEN_ADD_R = GEN_OP3R_BEGIN, // addition GEN_SUB_R, // subtraction GEN_IMUL_R, // multiplication GEN_DIV_R, // division GEN_MOD_R, // modulo GEN_OP3R_END, GEN_OP3I_BEGIN = GEN_OP3R_END, GEN_ADD_I = GEN_OP3I_BEGIN, GEN_SUB_I, GEN_IMUL_I, GEN_DIV_I, GEN_MOD_I, GEN_OP3I_END, }; ------------------------------------------------------------------------------ Integer subtraction and multiplication can be implemented analogously to addition. Subtraction can be mapped to the instruction with mnemonic _subq_ and multiplication to the instruction with mnemonic _imulq_. For example: ---- CODE (type=s) ----------------------------------------------------------- # produced by genOp3r(GEN_SUB_R, 6, 7, 9) subq %6, %7, %9 ------------------------------------------------------------------------------ And ---- CODE (type=s) ----------------------------------------------------------- # produced by genOp3i(GEN_MUL_I, 6, 7, 9) imulq 6, %7, %9 ------------------------------------------------------------------------------ The operations for division and modulo require some special treatment. They both can be mapped to an instruction with mnemonic _divq_. The corresponding instruction always computes both, the quotient and the remainder of the division. And therefore actually a register pairs needs to be acquired for this operations. For avoiding the treatment of this special case let the instruction store the result in the register pair %4 and %5. Afterwards copy from one of these registers what is needed into the destination register. For example: ---- CODE (type=s) ----------------------------------------------------------- # produced by genOp3r(GEN_DIV_R, 6, 7, 9) divq %6, %7, %4 movq %4, %9 ------------------------------------------------------------------------------ And ---- CODE (type=s) ----------------------------------------------------------- # produced by genOp3i(GEN_MOD_R, 6, 7, 9) divq 6, %7, %4 movq %5, %9 ------------------------------------------------------------------------------ This certainly can be optimized. But in a first approach a working solution is sufficient, so keep it simple. Quiz 25 ======= Submit your solution with ---- CODE (type=txt) ----------------------------------------------------------- submit hpc quiz25 gen.h gen.c finalize.h finalize.c -------------------------------------------------------------------------------- The underlying test is eventually to harsh. It for example requires that _genGetReg()_ always returns the smallest register that is unused. This is actual not a requirement, just one why to fulfill the requirements. However, this makes it easier for us to test your code.