Sunday, July 18, 2010

Translator – Operators (New Design)

The new design for operators will be that each data type combination will have it's own code. There will be associated codes for the first operand and associated codes for the second operand. For efficiency, there will be just one associated codes array with the second operand associated codes after the first operand associated codes. In addition to the number of associated codes value, there will be a new secondary associated codes index that will point to the first second operand associated, which can also be used to determine the end of the first operand associated codes.

Again using plus as an example, the main codes along with associated codes are list below. The convention will still be that the default operator has double arguments. The “I1” and “I2” codes represent the first or second operand being an integer, and the “Int” code for two integer operands. Similarly for strings where the “T1” and “T2” codes represent the first or second operand being a temporary string, and the “TT” code will for two temporary string operands.
Add  (4, 3)  AddI1, CatStr, CatStrT1, AddI2
AddI1  (1, 0)  AddInt
CatStr  (1, 0)  CatStrT2
CatStrT1  (1, 0)  CatStrTT
The second operand associated codes are underlined. The first number in parentheses is the number of associated codes and the second underlined number is the start index of the second operand associated codes. The decision for this design was arrived at by looking at the assembly language generated for approximately what the run-time will look like. Information about the assembly language research after the Continued break...

The first thing learned with the assembly language research is that the program was being compiled without optimization turned on. The project configuration was modified to compile with the -O2 optimization level (using -O3 is a little too aggressive and tends to inline many functions creating larger code, though it would probably run faster).

Anyway, a little operator test program was written for approximately how the run-time code will look like. An evaluation stack was created and the four numeric add functions were implemented. In the main function, the added functions were used to demonstrate that the code actually worked (no point in looking at generated assembly language of non-working code).

A change was made to the push function of the SimpleStack template. Instead of having the whole push function defined outside the template, the main push code was defined in the main class, so that it would be compiled inline, and the code that grows the stack when needed was put into a grow function outside the template, so that it will not be inlined when compiled. (Though using the -O3 optimization compiler option inlines it regardless.)

This test program was compiled on the command line. There are two ways to look at the generated assembly code. The first was to have the compiler generate it using this command with the -S option:
g++ -S -g -O2 -I . test/test_ops.cpp
The problem is that the original source code is not included in the test_ops1.s output file. Adding the -g option includes debug information, which only consists of the file name and line numbers. A second way was found by using the objdump command (included with MSYS) on the final executable:
g++ -g -O2 -I . test/test_ops1.cpp -o test_ops.exe
objdump –disassemble -S -l –no-show-raw-insn test_ops1.exe >test_ops.s
The -S includes the original source lines (-g compile option must be used). The -l includes source file name and line numbers, which are not necessary, but were very helpful to figure where the original source lines were. The -no-show-raw-insn turns off the raw machine code, which was not necessary. Unfortunately, all the code was disassembled including the C++ program overhead, so this was just deleted (this was another reason to add the -l as this helped finding the assembly code for the actual source code).

I learned that there are assembly instructions for working with integer values directly with floating point values. There is fild that loads an integer value into the floating point register after converting it, and there is fiadd for adding an integer value to a floating point value. Having individual codes will be much more efficient than trying to use the CvtDbl hidden code, which will add a lot of run-time overhead. The CvtDbl (and CvtInt) will still be used with array subscripts and function arguments.

I also realized that for ultimate run-time speed, much of the run-time module will need to be written in pure assembly language and not C++. There is too much overhead with the SimpleStack class with it's auto expansion feature. Even when it doesn't need expanding upon a push call, it still needs to do the expansion check. There is also no easy way to eliminate the expansion feature as determining the total size needed for the evaluation stack would be difficult. It would be much more efficient to use the native processor stack. There is no way to do that with C++. But this a problem is way off in the future – the run-time will at least be initially written in C++.

No comments:

Post a Comment

All comments and feedback welcomed, whether positive or negative.
(Anonymous comments are allowed, but comments with URL links or unrelated comments will be removed.)