Thanks, that helps. However, the discussion has stopped some months ago and there’s an unfinished PR to switch the image to x86_64. Do I understand correctly that it is unclear whether changes to the container hosts are required, and if yes, whether they’re acceptable? AFAIU, there should be no issue running the tests with an x86_64 image on an unmodified x86_64 container host. We could just do the exact same thing that students are asked to do locally: use a cross-compiler to build an aarch64 binary and then invoke the QEMU userspace emulator via qemu-aarch64 to run it. In addition to the cross compiler we’d need a minimal sysroot containing libc, but that should be about it. I tried doing that based on a debian:12-slim image and it works fine. Alpine doesn’t include all the tools necessary out-of-the-box, so some manual effort is involved, but it is just as well possible. Should I just open another PR in that repo?
About native macOS support, I do understand that syscalls would be different between Linux and macOS, but what exercises are there even that require the student to use syscalls? Other than that, the only difference seems to be that macOS needs a leading underscore when calling a function that’s implemented in assembly code, whereas on Linux no leading underscore should be added. That could be easily handled in the test code using the preprocessor. I played with two or three exercises and the example implementations worked fine on macOS for me. I suppose there would be more students using Apple Silicon than those with Raspberry Pis. Not being able to target that out of the box I would find really sad. x86_64 macOS would have to use emulation, just like x86_64 Linux.
This is all excellent. Would you also be interested in helping with a risc-v test runner?
About 35 tracks use Alpine, 7 use Debian, 11 use Ubuntu, and others use existing images for their languages. There isn’t a strict requirement, but larger images cost Exercism more.
We can list grep as foregone, i.e. an exercise the track won’t implement. So none of the track’s exercises require syscalls.
We currently have
extern const char *hello(void);
and
hello:
...
Can we have something like
extern const char* ASSEMBLY hello(void);
where the Makefile would define ASSEMBLY to tell macOS there is no leading underscore?
I think we want a solution where no changes to container hosts are required.
PRs very welcome. I think there is a test runner reviewer team that gives helpful feedback.
About the test runner image, I played a bit more with different options and it looks as if ubuntu is actually the best base here, since we end up with the smallest image (~370 MB) without having to do any hacks. Best of all, the existing Makefiles work unmodified. alpine doesn’t support cross compilation that targets Linux. Their aarch64 gcc is apparently meant to build code for MCUs. I was able to manually install (i.e., extract) aarch64 musl and musl-dev and then tell the aarch64 gcc to link against those, but only static binaries worked and quite a few extra flags are necessary to make it work. So that’d require changes to the makefiles. Fedora has a cross compiler and a sysroot package with the libs and all. That worked also, but at least the --sysroot argument is required, so also changes to the Makefile. And the resulting image was also 330 MB or so, so not that much smaller than ubuntu. Anyway, I will put the proposal with ubuntu as a base in a PR.
About RISC V, I think the exact same pattern could be used. Simply replace aarch64 with riscv64 in the package names.
Lastly, I also looked a bit into the macOS issue. I was under the impression that the underscore needs to be added from C, but it is actually the other way around: when you have extern const char* hello(void); in C, your asssembly code must export _hello. So a preprocessor #ifdef doesn’t really help here. One approach could be requesting from everyone, no matter the platform, to use leading underscores in their assembly code and then add it to the calling code via preprocessor for Linux. Another approach that works is telling the compiler explicitly the name of the assembly symbol:
extern const char* hello(void) __asm__("hello");
This works for gcc and clang with this syntax and also makes Linux gcc as well as macOS clang accept the same assembly. However, when playing with other exercises, I got some warnings about alignment requirements that apparently exist on macOS and not on Linux. So it’s possible that there are more differences than just syscalls and leading underscore. If those are a show stopper remains to be seen. Anyway, that’s for later. Let’s focus on the test runner for now to get this track going. Not supporting macOS for local development for now is fine.
I opened a PR now: Switch test runner image to amd64 by ahans · Pull Request #25 · exercism/arm64-assembly-test-runner · GitHub
I hope that this is good enough to launch the track! Let’s discuss what else is missing.
Would you and Isaac like to be added as track maintainers?
Here are the next steps as I understand them.
We need to request that branch protection be enabled, so no individual maintainer can commit a change without it having been reviewed. (This was very convenient earlier when I was rapidly adding exercises.)
After merging your test runner PR, we need to update config.json to say we have a test runner.
Then we need to verify the track works as expected, and invite all maintainers to give feedback. (At this stage exercism.org/tracks/arm64-assembly/ will still only work for maintainers.)
Then we update config.json again to say the track is active, and celebrate by telling this forum the track is live.
In my list of steps, I forgot about macOS and enabling CI for arm64 Mac.
It might be less distracting for students if we tell the linker instead of the compiler.
As an experiment I tried the following:
Changed the symbol used in the example solution:
.text
.global actual_hello
actual_hello:
...
Created a linker script text file containing
PROVIDE(hello = actual_hello);
Changed the Makefile so the file name for the above linker script is passed to the linker:
LDFLAGS = my_linker_script
This passes. So perhaps we can use this sort of approach to alias _hello to hello
generators/generate has in prop the name of the function being called, so it could generate the linker script.
(Alternately, if we came up with a different arrangement where we made a tiny per-exercise change to the Makefile instead of introducing a new linker script file, we could generate the various Makefiles from a template.)
No thanks. I don’t write assembly and I don’t have bandwidth to take on additional work.
I can try to help out for sure. But there’s no way I can put in the officially stated 4 h/week. So if you can live with less, feel free to sign me up.
Just curious. Where do you see that? I’m definitely not putting in 4 hours a week on the tracks I maintain!
Your list looks excellent. I guess you have the rights to merge the runner PR? If so, you can go ahead and merge that.
About native macOS support, I played with it a bit more and came across more differences that could be too problematic. Concretely, I looked at hello_world. Apple clang’s as doesn’t like .section .rodata. Putting just .data works. However, even then it doesn’t work:
❯ as hello_world.s
hello_world.s:7:5: error: ADR/ADRP relocations must be GOT relative
adrp x0, msg
^
hello_world.s:7:5: error: unknown AArch64 fixup kind!
adrp x0, msg
^
hello_world.s:8:5: error: unknown AArch64 fixup kind!
add x0, x0, :lo12:msg
^
Looks like loading addresses into registers is done differently on macOS. I was able to make it work at some point, but then it didn’t compile on Linux. Maybe there is a syntax that will work for both, but for sure there are things that will compile on macOS but will not work on Linux. It will be fairly tricky to make that work with the Linux test runner. So for now I would suggest we go with the original idea of only targeting Linux.
But I think we should just the makefiles to support native Linux on arm64. Then we could also add a paragraph to the installation instructions for macOS users: they would use a Linux VM (or Docker container) for their native architecture.
That document is from 2021 and should probably be updated. It seems out-of-date in a few spots.
In case 4 h/wk are not expected, feel free to sign me up!
The PR is merged now. Can we change the active entry in config.json then to see the test runner in action?
Open items from my perspective:
- Update makefiles to support native arm64 Linux and emulated. I have a draft for that locally and could open a PR. There’s actually not much to change, the compiler and assembler calls work in both versions. Only the qemu call needs to go for native arm64.
- Update installation documentation: here, we should also differentiate between native arm64 and other platforms, because you don’t need qemu when using a native arm64 platform; also add a paragraph for macOS users. I’ve played with Lima recently and it’s what I would recommend.
Anything I forgot?
You’re special though ![]()
In general, maintainers being added to tracks are asked to commit to them, else we get the situation where we have tons of inactive maintainers and then blocked PRs (ie, we removed like 200 inactive maintainers in our last “purge”).
For tracks with single maintainers, or responsive-if-needed, this doesn’t matter as much so we don’t enforce it.
Everything can be on a case-by-case basis, but in general that document represents(/ed) what we felt was a good set of criteria/guidelines at the time.
Awesome. Congrats and thank you! Want to PR the config.json flag?
I think it’d be good to have at least a few other maintainers look at the track before we officially launch it to the public. @keiraville has created all the exercises and I tried three or so (I’m a beginner with arm64 myself, so it’s quite challenging for me), but that was about it.
Track docs look fine to me.