Forums » Programming with the ECS » Intermediate Code »
Need help with test case 00041
Added by Rochus Keller over 1 year ago
Meanwhile I was able to successfully compile and run ~130 test cases with my chibicc Eigen intermediate code generator and ECS 0.0.40 (not yet upgraded).
Here is the tested commit: https://github.com/rochus-keller/EiGen/tree/chibicc 769a375.
Testcase 00041 (not yet on Github) makes an infinite loop though and I'm simply not able to recognize the reason for this in the intermediate code.
Please find attached the modified 00041.c (all code not required to reproduce the issue is commented out) and the generated cod, obf, map and lst files.
I haven't looked yet at the generated x86 assembler, but just try to understand what's happening in the intermediate code. For this purpose I also added a version of the cod file with my manual comments. From my point of view the IR doesn't represent an infinite loop, but maybe I just overlook the bug.
cdcheck doesn't complain; cdrun gives an error with all tried cod files (so not related to this one).
Could you please have a quick look at the cod files whether you see anything suspicious, thanks. I will compile the 0.0.41 version on the Debian machine and also try with this version.
Replies (42)
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
The conditional branch in line 36 compares a value of type s4
with a register that holds a value of type u1
, see Section 23.3.1: "in order to ensure a consistent virtual representation, all data shall be read using the same type it was written beforehand". The result register by the way should only be used to store the return value of a function call.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Ok, that was helpful, thank you very much! I now set the type of the boolean result according to the pointer width of the target platform (i.e. u4 on x86 and u8 on x86_64, etc.) which made four more testcases work.
The result register by the way should only be used to store the return value of a function call.
What is the disadvantage when using it like this, or what is the advantage of e.g. using $0 for all expression results?
I'm using $res because the original code generator uses %rax or %eax for this purpose.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
There is no advantage or disadvantage, but this usage defeats the purpose of the abstraction of general-purpose registers for which the physical register mapping should ideally not matter.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
May I suggest that you also conduct your tests using the intermediate code interpreter which is able to detect issues like this. You just have to make sure that your code generator and the interpreter use the same platform layout.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Ok, I see. So it seems to be rather a conceptual than a technical issue.
Can you envision a case where using $res as another general purpose register would fail?
Why not allow the result register to be used as another general purpose register during the function call?
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
It is part of the calling convention and can be used like any other general-purpose register as it is designed to allow direct access to the result of a function call, hence its name. Therefore, using it for any purpose other than its intended one should not fail, but it is confusing.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
There are also some operations that implicitly clobber the RAX
register which is why code misusing the result register for expression results might have to spill the register on the stack whereas general-purpose register usually don't have this problem.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
May I suggest that you also conduct your tests using the intermediate code interpreter which is able to detect issues like this
I now refactored cdrun in my code base so it compiles on my development machine. Here is how I adjusted the layout: https://github.com/rochus-keller/EiGen/blob/660be134df23bf46404d0b9c465b033ad3756425/interpreter.cpp#L27
Interestingly I get a lot of errors when runing cdrun on the 136 test cases which natively run correctly; the errors are "misaligned access", "invalid address" and "inconsistent operand type".
I checked some of the errors (though it's a bit tedious since I have to manually count instructions) but didn't understand the cause.
Examples (files attached):
running 00015.cod
main:35: error: misaligned access
running 00021.cod
foo:3: error: inconsistent operand type
running 00023.cod
main:6: error: invalid address
Is the reason that I use $res for different data types?
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
PS: meanwhile I changed the code so that $0 is used instead of $res and the latter is only assigned at the end.
No change to the cdrun results, all exactly the same. So I currently have no theory why cdrun reports these errors.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
After more fixes and refactorings my ECS chibicc version now passes all 145 test cases not depending on the standard library.
I will now start to migrate the most important parts of Newlib (see https://sourceware.org/newlib/) to ECS chibicc.
Only 55 of the 145 test cases work with cdrun so far.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
Interestingly I get a lot of errors when runing cdrun on the 136 test cases which natively run correctly; the errors are "misaligned access", "invalid address" and "inconsistent operand type".
Misaligned accesses indicate memory accesses using misaligned addresses. For most architectures this would also generate an exception. Regarding AMD64 this behaviour is controlled by the AC flag of the RFLAGS register. By default, alignment checks are disabled which allows misaligned memory accesses at potential cost of performance.
Invalid addresses are pointers to undefined memory regions. The interpreter checks that there are only pointers to memory of actually defined or currently allocated data sections.
Inconsistent operand types occur when data in memory or registers is read using a different type than written beforehand.
Is the reason that I use $res for different data types?
No. The reason is probably a misconfigured platform layout. Please use the definition I gave here and here. For the interpreter the lines 26 and 41 of tools/interpreter.cpp
have to be changed accordingly.
Test cases 00015 and 00021 seem to work fine with the correct platform layout in place.
Test case 00023 accesses an empty data section. The given size and alignment can be expressed like this:
.data x res 4 .alignment 4
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Thanks for the feedback.
As it seems I still don't get the correct meaning of your Layout class. Given that in the C compiler I have these definitions:
#define CHIBICC_POINTER_WIDTH sizeof(void*) #define CHIBICC_INT_WIDTH sizeof(int) #define CHIBICC_LONG_WIDTH sizeof(long) #define CHIBICC_STACK_ALIGN sizeof(void*) Type *ty_uint = &(Type){TY_INT, CHIBICC_INT_WIDTH, CHIBICC_INT_WIDTH, true}; Type *ty_ulong = &(Type){TY_LONG, CHIBICC_LONG_WIDTH, CHIBICC_LONG_WIDTH, true}; Type *ty_float = &(Type){TY_FLOAT, 4, 4}; Type *ty_double = &(Type){TY_DOUBLE, 8, 8};
From my point of view this should not produce misaligned access, neither on x86 nor x64.
I concluded that cdrun should have the following layout, which from my point of view fits the above definitions:
static Layout layout(Layout::Type(sizeof(int), sizeof(char), sizeof(long long)), // integer Layout::Type(sizeof(float),sizeof(float),sizeof(double)), // float Layout::Type(sizeof(void*)), // ptr Layout::Type(sizeof(void (*)(void))), // fun Layout::Type(0,sizeof(void*),sizeof(void*)), // stack alignment true);
Both my compiler, the generated executables and cdrun run on a Linux i386 machine.
Why is the above layout wrong, and why is the following layout (provided by you) is correct?
Layout layout {{4, 1, 4}, {8, 4, 4}, 4, 4, {0, 4, 8}, true};
Why are you using 4 (instead of 8) as maximum integer and float alignment, and why the difference between min and max stack alignment?
Test case 00023 accesses an empty data section.
Ok, I fixed that in the code generator (forgot to handle -fcommon), but the result of cdrun has only improved by five test cases.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
Why is the above layout wrong, and why is the following layout (provided by you) is correct?
I don't know if is wrong but it matches the layout as expected by 32-bit AMD64 code generator. This may or may not match the layout of the compiler you are using to translate the target compiler, even on the same platform.
Why are you using 4 (instead of 8) as maximum integer and float alignment, and why the difference between min and max stack alignment?
The float-alignment is an issue I need to fix. The integer alignment is 4 because all 64-bit values are represented using two 32-bit values. The alignment of a value corresponds to its size but is at least the minimal alignment and at most the maximal alignment.
Ok, I fixed that in the code generator (forgot to handle -fcommon), but the result of cdrun has only improved by five test cases.
Your code generator does not move the result register into register $0
after a function call that returns a value. Other issues include that a boolean operator generates an unsigned value rather than a signed int.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Thanks again.
because all 64-bit values are represented using two 32-bit values
Ok. How does it handle 64 bit integer return types on a 32 bit machine? Am I supposed to take provisions on the compiler side, or is this automatically handled by cdamd32?
Why is the size of the Float layout actually 8? Does this value matter at all?
The alignment of a value corresponds to its size but is at least the minimal alignment and at most the maximal alignment.
I still don't understand how the stack Layout works. Does the maximum alignment override the one of the actual value, i.e. what happens when I pass an Int64 param and both min and max stack alignment are 4 bytes?
Your code generator does not move the result register into register $0
Oops, yet another thing I forgot, thanks for the hint.
a boolean operator generates an unsigned value rather than a signed int
Why is this an issue?
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
How does it handle 64 bit integer return types on a 32 bit machine?
64-bit wide results are stored in registers eax
and edx
, see Section 9.4.3.
Am I supposed to take provisions on the compiler side, or is this automatically handled by cdamd32?
This is all being take care of by the code generator. You can safely assume that all features of the intermediate code representation are also supported natively.
Why is the size of the Float layout actually 8? Does this value matter at all?
It is the default float size for languages that provide a default floating-point type like double
in C or REAL
in Oberon, at least in my implementation.
Does the maximum alignment override the one of the actual value, i.e. what happens when I pass an Int64 param and both min and max stack alignment are 4 bytes?
I am not quite sure what you are referring to. The minimal and maximal alignment for data in data sections and inside a stack frame is given by the second and third parameters to a Layout::Type
for integer, floating-point, and pointer types. The corresponding alignment range of the type descriptor called stack
is used for values pushed onto the stack and the stack frame themselves. This is for example needed in 64-bit mode where basically all push instructions need 8 bytes.
Why is this an issue?
Because you are comparing the result of a boolean operator with a signed immediate, for example in conditional expressions of if statements. This causes "inconsistent operand type" errors since s4
and u4
are different types even if they have the same size and memory representation.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Sorry again for asking questions I could answer myself reading the handbook, but the latter is just too much information to keep in my old brain, even to remember which section to re-read.
The corresponding alignment range of the type descriptor called stack is used for values pushed onto the stack
From that I conclude that pusing an S8 to a stack with "0,4,4" would align on 4 and with "0,4,8" would align on 8 bytes; is this correct? That's what I meant by "override", i.e. the alignment range of the stack would be stronger than that of integer. Or would pushing an S8 to a stack with "0,4,4" rather align on 8, if S8 would align on 8?
I'm currently implementing a long long data type for the compiler (chibicc originally only has long assumed to be 64 bit wide) and debugging a test case which works with cdrun but segfaults natively. Will be back later.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
I stare at the attached cod file and don't see what could make it crash; what do I overlook?
The assigment is ok; the segfault seems to be associated with the comparison operation.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
From that I conclude that pusing an S8 to a stack with "0,4,4" would align on 4 and with "0,4,8" would align on 8 bytes; is this correct?
Yes.
Or would pushing an S8 to a stack with "0,4,4" rather align on 8, if S8 would align on 8?
No. But the stack alignment is never weaker than the natural alignment and in your case its the same.
I stare at the attached cod file and don't see what could make it crash; what do I overlook?
Sorry, this is a bug in the code generator. Here is the corresponding erroneous assembly:
; line 33: mov s8 $0, s8 [$0] mov ebx, dword [ebx] mov esi, dword [ebx + 4]
You can fix the problem by using two different registers for the time being. Thanks for reporting.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Ok, so I deactivate the long long type for the time being. I attached the test cases which fail if I enable long long.
I also changed the bool representation from u4 to s4, which increased the successful test cases with cdrun to 111 of 149.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Meanwhile I analyzed the cod files which don't work with cdrun. I made some fixes and could improve the result to 116 of 149.
But now I'm stuck with two versions of issues. I attached three cod files.
The 45 and 69 files represent most remaining issues. It's mostly this kind of statement which fails:
mov s4 $0, s4 [$0]
$0 was set to a pointer, but it points to a s4 and I have no idea what I should change to make cdrun happy. Glad if you can have a look at the files.
The 134 file is a single issue. The statement
lsh s4 $0, s4 $0, s4 $1
looks correct to me, so again I have no clue what to do to satisfy cdrun checks.
cdamd32 reports no issue and the executable does what it should.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
- 00045 and 00069: Data type consistency is also checked for values defined in data sections: Do not encode multibyte immediates yourself, just use a single datum definition instruction with the correct type like you do for pointers:
.data y .alignment 4 def s4 6 .data x .alignment 4 def s4 5
- 00134: The result of shifting is only defined for values less than the bit width.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Ok, thank you for the feedback.
Do not encode multibyte immediates yourself
Unfortunately I would have pretty much to redesign for this, since the encoding is already done in the parser; I only get a byte array and a size in the code generator ; not sure yet whether it's worthwhile, since as I understand it's mostly to meet the cdrun checker requirements.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
This is rather unfortunate: You have to make sure then that this encoding uses exactly the same alignments, endianess and memory representations of signed/unsigned integers, floating-point values, and pointers as the back-end.
RE: Need help with test case 00041 - Added by Florian Negele over 1 year ago
I think you might get away with adding a pointer to the Initializer
struct created by the parser in function gvar_initializer
next to the init_data
member of the Obj
struct. Your code generator can then traverse each node in that initialiser for generating the necessary datum definitions similar to function write_gvar_data
. The latter function reuses the memory representation of its own runtime environment by the way, which may already be different and also means that the output of your compiler will depend on the compiler used to compile your compiler and is thus not portable at all.
RE: Need help with test case 00041 - Added by Rochus Keller over 1 year ago
Ok, thanks. I agree that I likely could just ignore the linearized version created in the parser and duplicate some code to make my own layout in the code generator. But it's not my top priority at the moment. Instead I want to implement a minimal set of the standard library to make the remaining ~80 test cases run, and then run all tests on an ARM32 machine and later on both 64 bit versions. Then it's eventually time to go ahead with my Micron compiler, this time equipped with the necessary knowledge and some experience with ECS.
I actually primarily selected chibicc because its backend seemed to be easiest to migrate; but as already mentioned the parser fails with my generated C99 reference test project. Anyway it might be useful to migrate some important libraries to ECS (such as e.g. garbage collection and low-level hardware drivers etc.). I already looked at Orange C, which is yet another "lean" and retargetable compiler infrastructure which also supports some C++ and has an optimizer, but is also more complex (see https://github.com/rochus-keller/OrangeC/commits/busy_frontend/).