Closed TheRealOwenRees closed 9 months ago
Nice catch!
I ported this exercise to the x86 track. I can see why this could happen - by calling convention the return(al
in x86) isn't changed at all and could contain some garbage value from a previous call value which could trigger this outcome.
Here I see that rax could contain the address of the function being tested and that pointer would always pass the TRUE check. For FALSE check only the LSB is negated by xor, al, 1
and that passes the check again since its non zero.
Changing datatype to int might work here - I don't have a plan to fix this at the moment.
I have not tested this with a CLI submission.
An empty solution does not pass the tests locally, at least not in the following environment:
glibc version: 2.36
gcc (Debian 12.2.0-14) 12.2.0
GNU Make 4.3
$ make
triangle_test.c:24:test_equilateral_triangle_all_sides_are_equal:FAIL: Expected TRUE Was FALSE
triangle_test.c:138:test_equilateral_triangle_any_side_is_unequal:PASS
triangle_test.c:139:test_equilateral_triangle_no_sides_are_equal:PASS
triangle_test.c:140:test_equilateral_triangle_all_zero_sides_is_not_a_triangle:PASS
triangle_test.c:48:test_equilateral_triangle_sides_may_be_floats:FAIL: Expected TRUE Was FALSE
triangle_test.c:54:test_isosceles_triangle_last_two_sides_are_equal:FAIL: Expected TRUE Was FALSE
triangle_test.c:60:test_isosceles_triangle_first_two_sides_are_equal:FAIL: Expected TRUE Was FALSE
triangle_test.c:66:test_isosceles_triangle_first_and_last_sides_are_equal:FAIL: Expected TRUE Was FALSE
triangle_test.c:72:test_isosceles_triangle_equilateral_triangles_are_also_isosceles:FAIL: Expected TRUE Was FALSE
triangle_test.c:146:test_isosceles_triangle_no_sides_are_equal:PASS
triangle_test.c:147:test_isosceles_triangle_first_triangle_inequality_violation:PASS
triangle_test.c:148:test_isosceles_triangle_second_triangle_inequality_violation:PASS
triangle_test.c:149:test_isosceles_triangle_third_triangle_inequality_violation:PASS
triangle_test.c:102:test_isosceles_triangle_sides_may_be_floats:FAIL: Expected TRUE Was FALSE
triangle_test.c:108:test_scalene_triangle_no_sides_are_equal:FAIL: Expected TRUE Was FALSE
triangle_test.c:152:test_scalene_triangle_all_sides_are_equal:PASS
triangle_test.c:153:test_scalene_triangle_two_sides_are_equal:PASS
triangle_test.c:154:test_scalene_triangle_may_not_violate_triangle_inequality:PASS
triangle_test.c:155:test_scalene_triangle_sides_may_be_floats:PASS
-----------------------
19 Tests 8 Failures 0 Ignored
FAIL
make: *** [Makefile:29: all] Error 8
See next comment
The real problem is the test runner ignores the exit code of the make
task: https://github.com/exercism/x86-64-assembly-test-runner/blob/2360f54b486d33974faa1dbe96b6811f7ea270c9/run.sh#L13
The shell only gets the exit code returned by the python3
command. As long as it runs the script successfully (and it always will), the exit code is 0 regardless of what "results.out" actually contains. It's an easy enough oversight to make; the Scheme track is currently suffering from a similar one.
A simple fix would be storing the exit code of stdbuf
(which is non-zero when a test fails). Then, after running the Python script, exit with whatever stdbuf
returned
I've had this issue before because of using bool
as a return type. My fix last time was to switch to returning an int
instead.
See https://github.com/exercism/x86-64-assembly/issues/69 and https://github.com/exercism/x86-64-assembly/pull/70
It seems an outdated version of GCC was the problem all along.
A Debian 10 image with only the standard-issue compiler behaves identically to Alpine 3.10 (the runner's current base image):
$ docker-run.sh triangle ../exercism-x86-64-assembly/exercises/practice/triangle
[+] Building 0.8s (11/11) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 430B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 113B 0.0s
=> [internal] load metadata for docker.io/library/debian:buster-slim 0.4s
=> [internal] load build context 0.0s
=> => transferring context: 707B 0.0s
=> [1/6] FROM docker.io/library/debian:buster-slim@sha256:738002a6e94e8629a0f869be8ea56519887979d5ed411486441981a8b16f2fc8 0.0s
=> CACHED [2/6] RUN apt update && apt -qqy --no-install-recommends install gcc libc-dev make nasm python3-minimal 0.0s
=> CACHED [3/6] WORKDIR /opt/test-runner 0.0s
=> [4/6] COPY run.sh bin/ 0.0s
=> [5/6] COPY process_results.py ./ 0.0s
=> [6/6] COPY debug.asm ./ 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:8a393b11a87b18db6d913b0faf5d314c44fb7943821b29712536b26231432501 0.0s
=> => naming to docker.io/library/test-runner 0.0s
Solution code:
section .text
global is_equilateral
is_equilateral:
; Provide your implementation here
ret
global is_isosceles
is_isosceles:
; Provide your implementation here
ret
global is_scalene
is_scalene:
; Provide your implementation here
ret
%ifidn __OUTPUT_FORMAT__,elf64
section .note.GNU-stack noalloc noexec nowrite progbits
%endif-e
triangle_test.c:137:test_equilateral_triangle_all_sides_are_equal:PASS
triangle_test.c:138:test_equilateral_triangle_any_side_is_unequal:PASS
triangle_test.c:139:test_equilateral_triangle_no_sides_are_equal:PASS
triangle_test.c:140:test_equilateral_triangle_all_zero_sides_is_not_a_triangle:PASS
triangle_test.c:141:test_equilateral_triangle_sides_may_be_floats:PASS
triangle_test.c:142:test_isosceles_triangle_last_two_sides_are_equal:PASS
triangle_test.c:143:test_isosceles_triangle_first_two_sides_are_equal:PASS
triangle_test.c:144:test_isosceles_triangle_first_and_last_sides_are_equal:PASS
triangle_test.c:145:test_isosceles_triangle_equilateral_triangles_are_also_isosceles:PASS
triangle_test.c:146:test_isosceles_triangle_no_sides_are_equal:PASS
triangle_test.c:147:test_isosceles_triangle_first_triangle_inequality_violation:PASS
triangle_test.c:148:test_isosceles_triangle_second_triangle_inequality_violation:PASS
triangle_test.c:149:test_isosceles_triangle_third_triangle_inequality_violation:PASS
triangle_test.c:150:test_isosceles_triangle_sides_may_be_floats:PASS
triangle_test.c:151:test_scalene_triangle_no_sides_are_equal:PASS
triangle_test.c:152:test_scalene_triangle_all_sides_are_equal:PASS
triangle_test.c:153:test_scalene_triangle_two_sides_are_equal:PASS
triangle_test.c:154:test_scalene_triangle_may_not_violate_triangle_inequality:PASS
triangle_test.c:155:test_scalene_triangle_sides_may_be_floats:PASS
-----------------------
19 Tests 0 Failures 0 Ignored
OK
gcc (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
At least there's plenty of time to get this fixed before Nybbly November.
Well, I don't think we should depend on codegen by a specific compiler version. It might break again on the next upgrade. I still think the best way is to change the return type to int
.
For illustration, attached is the disassembled triangle test program, generated with objdump -M intel -S tests
, after building with gcc 8.3 and then gcc 12.
The first major difference is the .init
section. GCC 8.3 calls the initialization subroutines frame_dummy
and __do_global_ctors_aux
, while gcc 12 does not:
$ diff -b tests.gcc.8.3.asm.txt tests.gcc.12.asm.txt | head -n 12
2c2
< tests.gcc.8.3: file format elf64-x86-64
---
> tests.gcc.12: file format elf64-x86-64
9,12c9,10
< 1001: e8 a5 01 00 00 call 11ab <frame_dummy>
< 1006: e8 e7 2e 00 00 call 3ef2 <__do_global_ctors_aux>
< 100b: 58 pop rax
< 100c: c3 ret
---
> 1001: 58 pop rax
> 1002: c3 ret
The purpose of frame_dummy
seems to be initializing RSI and RDI whenever the register frame at rip+0x5e45
is not NULL
:
00000000000011ab <frame_dummy>:
11ab: 48 83 3d 45 5e 00 00 cmp QWORD PTR [rip+0x5e45],0x0 # 6ff8 <__register_frame_info>
11b2: 00
11b3: 74 18 je 11cd <frame_dummy+0x22>
11b5: 55 push rbp
11b6: 48 8d 35 a3 5e 00 00 lea rsi,[rip+0x5ea3] # 7060 <object.6149>
11bd: 48 8d 3d 4c 38 00 00 lea rdi,[rip+0x384c] # 4a10 <__EH_FRAME_BEGIN__>
11c4: 48 89 e5 mov rbp,rsp
11c7: e8 b4 fe ff ff call 1080 <__register_frame_info@plt>
11cc: 5d pop rbp
11cd: e9 1b ff ff ff jmp 10ed <register_tm_clones>
This might be eliminated with optimization, but I think that would ultimately impoverish students' learning opportunities, at least for the ones using their PCs.
In the browser code editor, all tests pass if you do not submit any code. This user did just that and it has passed.
I have not tested this with a CLI submission.