Rust on P2: It's rough, but it works
I got Rust working on P2. This is rather lengthy and manual process to set it up right now, but it does work and steps to get running are below. Plan for about 3 hours to go through it all. @DavidZemon looking to you to help clean things up
Prerequisites
- Unix. Not planning to figure out how to do this on Windows any time soon
- Can successfully compile p2llvm (the LLVM compiler suite and P2 support libraries)
- p2llvm installed somewhere. you'll need the linker (ld.lld) and libp2.a from the installation
Building Rust
- Clone the rust fork: https://github.com/ne75/rust
- Within the repo, run
./x.py setup
.x.py
is the primary tool used to bootstrap the rust compiler, which is built in stages. This will create aconfig.toml
. This is used to actually configure the build.- During the run, it will ask what you are installing for. I selected (c), probably what you should do to.
- Fill out
config.toml
. I've attached the one I used, which is for an arm64 Mac.- The main thing to change for other hosts is to change which LLVM targets are built. set
targets
to your host. LLVM will be built and used to build rust during the bootstrapping process, so it needs to be able to build binaries for your host, as well as P2. Basically, set this toX86
for intel processors,Aarch64
for Apple ARM. - This sets the install directory to
/opt/p2rust
. Change as desired. - See config.toml.example for a detailed documentation of this file, available keys, etc.
- The main thing to change for other hosts is to change which LLVM targets are built. set
- Update the submodules (including pulling LLVM).
git submodule update --init
- You can skip this by changing
submodules
totrue
in the configuration file
- You can skip this by changing
- Build rustc with
./x.py build
.- This will build LLVM and the several stages of the compiler.
- In theory, you shouldn't need to rebuild LLVM if you build and install the
rust
branch for p2llvm, but I haven't figured out the details there, so for now, just rebuild llvm.
- Install rustc:
./x.py install
- Install cargo:
./x.py install cargo
- Add the rust install path (
/opt/p2rust/bin
or wherever) to your PATH - Build libcore:
cd library/core
cargo build --target p2
cd ../..
- This build is not optimized (which would be done by adding
--release
to the build command). I need to figure out how to make rustc not try to emit jump tables, which the backend doesn't currently support.
- Build compiler builtins
cd compiler_builtins
cargo build
cd ..
- Copy everything to the sysroot
mkdir /opt/p2rust/lib/rustlib/p2/lib/
cp target/p2/debug/libcore.rlib /opt/p2rust/lib/rustlib/p2/lib/
cp compiler_builtins/target/p2/debug/libcompiler_builtins.rlib /opt/p2rust/lib/rustlib/p2/lib/
- Copy in
libp2
from an llvm build (included in the attached zip)cp <path to libp2.a> /opt/p2rust/lib/rustlib/p2/lib
Building an Application
- Use the attached blink program as a starting point, or create a new crate with
cargo new
. - Fill out the build script,
build.rs
with the script attached, and cargo.toml- update path in
build.rs
to the linker script - update path in
.cargo/config.toml
to the linker installation.
- update path in
- Write your application
- Compile with
cargo build --release
(YMMV with--release
, if it tries to use jump tables, same as above) - Load with loadp2 (compiled binary is in
target/p2/debug
ortarget/p2/release
)
I tried all of these instructions out as I was writing them, so they should work okay. Reach out if there are glaring problems, hopefully this is enough to get going.
An unrelated aside
I think this effort demonstrates the importance of the LLVM effort and why getting a fully featured P2 target in the LLVM system is necessary. In just 3 days, I was able to add support for a language I don't even know to this chip. Which is kind of incredible and shows the power of LLVM. In principle any language that uses LLVM as a backend is now on the quick horizon. I think that's pretty awesome.
Comments
@n_ermosh
I didn't see anywhere in the instructions that ld.lld was referenced. Is libp2.a the only thing we need from the p2-llvm fork?
When you actually build an application, you'll need to provide the path to ld.lld in the
.cargo/config
configuration.Very nice... on a parallel path, if I were smarter and slept less, I'd be trying to make Zig work with what y'all have been doing. The P2 gets mentioned on the Zig/Discord list a few times.
It’s probably not that hard either. I had pretty much no idea what I was doing going into this and it turns out it wasn’t that bad
Great going, congrats Nikita
@n_ermosh
just fyi, i was able to compile p2rust successfully locally and am working on getting the CI server to build it. I have all the steps configured but the build is failing. No time to investigate, but will try to get it going soon. Once I get it working, I'll extract it to a template which can then be re-used for building a mac version.
Sounds great! Does it make sense to try to integrate with p2llvm in some way? So that one p2llvm is built once to get the llvm binaries and P2 libraries, and have rust build off that, rather than its build its own?
Oh absolutely. Let me know when you have that working
The only hiccup is that there a few rust-specific changes to core LLVM libraries, so if I merge those into the main p2llvm branch, we will unlikely be able to merge into upstream llvm. This should be solved once rust upgrades to LLVM 14 (currently they use 13, but p2llvm is 14). However those aren’t features that p2llvm uses, so might be able to get away with one branch for everything.
I also want to keep the core P2 library constant between the two languages. I.e don’t write a separate libp2 implementation in rust, just make bindings to the existing functions. So that as I upgrade/fix libp2 in C, those changes carry over instantly to the rust side.
Just leaving a “hell yeah” here, as this is something I’m very excited about!
Wowza this is slow progress. Ran some more builds last night - figured out that it was failing on my 8-core VM (the CI build agent) despite having passed on my 16-core host. Switched the CI server to build with "-j1" overnight and it finally got passed that step then failed on disk space lol. Increased the VM's disk size this morning, bumped the build parallelism to -j4 and after an hour it finally failed because pkg-config wasn't installed. Sigh. Another build was just triggered. Hopefully this one will work!
There's a way to turn on incremental builds that might be helpful until you get all the build kinks sorted out--but I have noticed the builds are annoyingly slow, especially with a debug build of LLVM...
Woohoo! I got a successful build of p2rust Folks can go download the Linux x86 version from https://ci.zemon.name/project/P2rust?mode=builds&guest=1
@n_ermosh, regarding that ld.lld prereq. Linux versions of p2llvm haven't built on the CI server for a little while and I haven't taken the time to dig into it. But you mentioned that llvm is getting built as part of p2rust as well. So I did a search in the p2rust directory for any reference to ld.lld and founnd a bunch:
Any chance one of those is what we're looking for?
The artifact available in build #1 from the above Linux build contains libp2.a (pulled from the Mac release of p2llvm) but does not yet include a copy of ld.lld since that's an exe compiled for the host system and therefore I can't use the one from the Mac build.
Yeah you can pull ld.lld from rust’s build (one of the last two from that list you gave). Only thing to watch out for is any shared libraries that it relies on—I’m not sure if it’s a completely standalone binary or not.
Awesome, thanks. Seems to be mostly statically linked, so that should work out very nicely:
@n_ermosh, Linux llvm build is working on the CI server again. The only thing I really had to change was removing the default value for -j and force it to -j4. Same solution I had to apply to p2rust. I think there is a dependency issue with p2llvm's build system that is causing it to fail with -j8 but not -j4 or -j16. Not sure if that is unique to p2llvm or common to upstream as well.
Anyway, llvm builds are now available again for Linux (including WSL... in case anyone is interested) and the p2rust build should be available in an hour or so (one that includes lld this time). Once that build finishes, I'll download it and give it a try to make sure it can compile a P2 program. My new p2 board has arrived yet, so I'll post it here for someone else to try running once it's ready.
nice! I'm still digging through issues in the backend that clang didn't cause before, but rust now is. something with jump analysis... but they only crop up when optimization is turned on.
Okay, still working out some issues with the generated artifact. New build is running which fixes the path of libp2.a, removes duplicate copies of lld (what should have been symlinks ended up being copies) and adds source code for Rust standard library to the installable archive (helpful for IDEs).
I'm running into an issue trying to use the toolchain from CLion though. When I first ran this on TeamCity, I didn't think too much of it and just changed the build config around a little to avoid the problem. But I know CLion works with the default Rust toolchain, so this is specific to p2llvm or p2rust. Somewhere rustc is being invoked with "rustc -vV" and it ends up getting interpretted as a single command named "rustc -vV" instead of the command "rustc" with the argument "-vV". This, of course, throws a "file not found" error.
This was originally happening in the TeamCity build step for building the P2 core library. I had it configured as a executable with arguments:
Exe:
env
Arguments:
PATH="%env.DESTDIR%%PREFIX%/bin:$PATH" cargo build --target p2
And I fixed it by changing to custom script with value
I kept cleaning up old build logs, so I don't have that failure log anymore, but CLion fails with a similar error:
Yeah thats what I don't like about cargo--it relies on rustc being in your path and doesn't use proper subprocess execution to invoke rustc, resulting in the errors like above.
Also I think I got everything updates to be able to build core in release mode (with optimizations). The solution is hacky--there's a assertion that will sometimes get triggered, but only in a debug build. In a release build that assertion is skipped over entirely. Not great to not figure out whats causing it but it still compiles...so I'll call it good until that code doesn't work
Using all my cores.... I hope I can contribute to Rust on the P2. After spending quite some time staring at SPIN, I'm ready for a language that supports some nicer abstractions and types.
Ok, just to follow up: it works! I got the blinking program to work! Which is of course amazing. Now there are a metric ton of stuff to understand 😅
So I tried getting a more elaborate example to work. Success has been mixed. I managed to call into the libp2 uart functions, and produced output. Great! But I then tried to spin up a second cog, and that failed in myserious ways. It appears as if Rust creates code to initialize the stack that makes little sense to me. First my Rust program:
The memset deliberately does nothing, because I don't care about the stack having a special format.
However this is the main assembly:
Below the main are the two code snippets that apparently the code jumps into. I can't make head or tail of them.
Any pointers on how to get to the bottom of this? I have successfully used objdump to extract the assembly, but any other tipps on how to progress are highly appreciated.
Great progress so far!
Yeah the call to unorddf2 seems to be incorrect, and I'd guess that that's a mistake in the linking process. I'd disassemble the intermediate object file the compiler should be generating before linking to the standard library (I'm assuming that's how rustc works?), and see what it's trying to call. the actual operand will be 0 so it won't list the correct function name, but you can look at the relocation table (the
-t
flag I think) in the object file and see what the function is supposed to be after linking. From there, we can figure out why it's linking this function instead.Also, I'd make sure to pull from my llvm-project/master branch into my llvm-project/rust branch and recompile. There have been some changed (including around the linker) that might be leading to this.
And, if the startup code is being called correctly, you might be able to use my debugger. It's super duper hacky right now but useful for single-stepping code and seeing what's going on.
It seems that didn't work:
I saw that there are commits in the p2rust submodule that I didn't find in your upstream master, so maybe that's the cause? Can you comment on that?
I also figured out how to get IR, if you care about that.
IR is useful--it can help see what it's feeding the P2 target backend.
Yeah, the rust branch has a few specific things that aren't in master (I use LLVM 14, rust is still on LLVM 13 and they aren't backwards compatible. Once rust upgrades to 14, it should be the same), but master has new features for how startup and library linking works that are probably needed, so you should pull those into the submodule, and get the latest libp2 and libc.
For clarity, we have the following branches:
ne75/p2llvm/master: the main p2llvm repo. Contains the P2 llvm branch, libp2, libc, etc.
ne75/llvm-project/master: The main llvm branch for p2llvm. Contains all the P2-specific code. Use this for C/C++.
ne75/llvm-project/rust: The llvm branch that contains p2 code and a few things to make rust compile correctly. Use this for rust.
ne75/rust/master: The rust branch that contains the P2 target. Use this to actually build rustc.
I've gone ahead and merged the newer things from ne75/llvm-project/master into ne75/llvm-project/rust, and updated ne75/rust with the latest ne75/llvm-project/rust. Let me know if you are able to build it.
Ok, fetching the rust branch, and building everything restored the basic functionality.
The cogstart still does not work. The IR looks good (to my rather uninitiated eye), but there is something odd in the disassembly: the memset became a weird number, #\547. Instead of the existing (and simple "reta") memset.
Find the assorted files attached.
okay interesting. So it's doing what it's supposed to, but doing it wrong
Basically, special runtime functions (such as memset) are loaded into the LUT on cog start. As a result, they need to be placed at a special memory address so that the linker correctly computes their LUT RAM address, rather than their usual flash memory address. So, if you look at where that memset function is (0xa8c), that linker will convert that to LUT address 0x223 = 547 (which is incorrect, but expected behavior). The compiler expects all of these special functions to be continuous starting at address 0x200 in hub memory, and it copies the 512 longs starting at 0x200 into LUT RAM on cog startup, and the linker converts the byte-address to a long-address during linking for each call to one of these functions. The fact that the memset function is empty is also odd--are you defining an empty one in your source?
Now, for why this is happening in the first place. My best guess is you have an out-of-date libc--I think I actually remember memset not being properly implemented and leading to this exact bug not too long ago. I've attached my very recent build of libc and libp2 libraries so you can try that.
There might be a future development where I make this more flexible to avoid issues like these--it's very opaque currently and without knowing the compiler you'd probably have no way of figuring it out. Maybe have a runtime lookup of where these functions ended up and call them that way instead of having the linker try to figure out what the address will be when the program runs.
Oh yes, the memset is in fact empty. I had to define it because Rust doesn’t allow uninitialized arrays. But I know it’s stack, so no need to clear it. That’s why I preliminarily defined it as empty, so that’s correct.
I’ll try your libs soon, I also can also build them myself though - I have your other LLVM project running, to. I just prefer Rust over C++. And why do we need this lib in the first place? Does rust depend on a libc or newlib or something, and that’s what you basically define?
Any hints on how to get deeper into this? I did peek around, but this is a LOT of moving pieces. I’d love to be able to help.
I also wonder why these functions need to be in the LUT. Can’t they be in the main memory, as the rest of the code ? We are using hubexec, don’t we? I
@deets ,
By putting memcpy and memset into LUT and then using rdfast and wrfast we can make them fly much faster than hub ram.
Mike