Jump to content

LLVM-MOS


DigitalMonk
 Share

Recommended Posts

6 minutes ago, Scott Robison said:

The only reason C or C++ might be possible on X16 would be the 512K to 2048K of banked RAM. All those floppies were compensating for the lack of RAM (or so is generally my experience).

C is possible, there were at least three commercial C compilers back in the 80s.

C++, well, maaaaaaaybe C++98ish.  But just as a meaningless point of information, clang++ (the C++ compiler for LLVM-MOS) is 84.5MB.  That's not it's memory footprint, just the executable size.  Now, granted, clang, clang-13, and clang++ are all the same size, so I suspect that is one mega compiler/librarian/linker application for multiple similar languages, but it's waaaaaay beyond the 2MB for the big X16...

But I love to see people tackling impossible odds.  Frequently they found out that they're merely ludicrously difficult 🙂

Link to comment
Share on other sites

5 minutes ago, DigitalMonk said:

C is possible, there were at least three commercial C compilers back in the 80s.

C++, well, maaaaaaaybe C++98ish.  But just as a meaningless point of information, clang++ (the C++ compiler for LLVM-MOS) is 84.5MB.  That's not it's memory footprint, just the executable size.  Now, granted, clang, clang-13, and clang++ are all the same size, so I suspect that is one mega compiler/librarian/linker application for multiple similar languages, but it's waaaaaay beyond the 2MB for the big X16...

But I love to see people tackling impossible odds.  Frequently they found out that they're merely ludicrously difficult 🙂

When I say "possible" I mean "possible to do without needing to swap to disk". In other words, floppy disks are just very slow RAM. Hard disks are slightly faster RAM. CD / DVD / bluray is slow ROM (ignoring the possibility of -RW or -RAM versions). Printers are WOM. 🙂

Further, by "possible" I do not mean "feature parity with an x64 native edition of the same compiler".

Edited by Scott Robison
Link to comment
Share on other sites

2 hours ago, DigitalMonk said:

DO IT!!!

Upon reflection:  Oh lord...  I mean, I suppose you could always cram the LLVM source code through LLVM-MOS.  I don't know how huge the resulting PRG would be, since there is a LOT of logic in LLVM.  ...

The tack to take might be to use LLVM to speed optimize a C++ compiler that had been written to fit into a small (for C++) space, and adapt a smart linker to use LLVM to compile libraries for that compiler, so you take the normal C performance hit on code compiled by the the hosted C++ compiler, but it is calling speed optimized libraries to help make up for it.

For me, the appeal would be to get AWK up and running without having to write it from scratch.

Edited by BruceMcF
Link to comment
Share on other sites

I've had a lot of fun writing software for the Commodore 64 in Rust with this over the past week. One nice thing about Rust is how it divides the standard library into three parts: completely freestanding, things that only require heap allocation, and things which require a full OS. So much of the standard library is already available on the C64, although I did end up replacing the formatting stuff with a third-party library in order to get smaller executable sizes.

The LLVM backend is still not ready for making finished software yet, due to a few limitations such as not being able to handle code being called from interrupts, since it tries to statically allocate memory for functions instead of using the stack, which breaks down when interrupts get involved. Nonetheless, it's definitely ready for experimentation and writing fun things for 6502-based platforms. They have a Slack group that you can join from the link on their wiki.

  • Like 2
Link to comment
Share on other sites

I mentioned it was in active development, but just for a sense of scale:

 

@github-actions github-actions released this 21 hours ago · 8911 commits to fd5a4cc2c8cb064afe6df5ccb436831ef8743bda since this release

 

Almost 9000 commits in less than a day.

Basically, if it's doing what you want, just use what you have.  But if you have any issues, grab a new build coz they may have already fixed your problem...

Link to comment
Share on other sites

19 minutes ago, DigitalMonk said:

Awwwww...  Did you have to ruin my fantasy of hundreds of retro-enthusiasts frantically hacking towards getting this completed?

😁

Still, 2-3 a day is pretty good!

I suspect it is 9000+ total commits over the lifetime of the project on the master branch (or whatever the main branch is called). And agreed, any project with multiple commits per day is pretty active. Hopefully good in that they aren't continually breaking new things and having to fix them with subsequent updates. 🙂

Link to comment
Share on other sites

They seem to be _VERY_ strict about there unit and integration testing.  No Pull Requests are allowed unless they are covered by an existing test or include new ones.  All code has to follow the LLVM coding and quality guidelines.  None of that stops errors getting in, of course, but it should severely limit the "quick hack" kind of coding that leads to fix/re-fix/fix-again/no-this-time-really/argh commits...

I am incredibly stoked that there are so many C compiler efforts out there now for 6502:

  • cc65, of course, which is pretty rock solid but unfortunately generates (by far) the slowest/largest code.  But it always works.
  • gcc-6502 has the GCC front end goodness, but still some backend issues, and is pretty much dead unfortunately...
  • KickC is quite active and the lead developer is responsive and helpful.  Very cool if you want to mix and match with KickAssembler
  • NutStudio has been mentioned in another thread here.  I had good luck in my initial forays with it.  He's not ready to release, but is open to beta testers
  • LLVM-MOS which appears to be very serious about the whole effort
Link to comment
Share on other sites

2 hours ago, DigitalMonk said:

Awwwww...  Did you have to ruin my fantasy of hundreds of retro-enthusiasts frantically hacking towards getting this completed?

😁

Still, 2-3 a day is pretty good!

Heh, I don't think even Linux gets that many commits. But yes, it's very active. I'm on the Slack group and they're constantly thinking of new optimizations. One that I saw mentioned (but I don't think is in there yet) is an optimization which turns loops with 16-bit indices into nested loops with 8-bit ones. They're also really innovative with their use of the zero page, and nearly never actually needing to use the stack. Generating assembly that resembles what people write for the 6502 by hand, which is very different from assembly for modern machines, is a big priority.

Today I've been playing with writing stuff for the X16 with it, and it works fine there as well. I just had to tweak the C64 linker scripts to match the X16's memory layout. Regardless of improvements in optimization, we'll probably see around a 15% improvement in code size and possibly performance once they add support for the instructions added in the 65C02.

Link to comment
Share on other sites

1 hour ago, Serentty said:

Heh, I don't think even Linux gets that many commits. But yes, it's very active. I'm on the Slack group and they're constantly thinking of new optimizations. One that I saw mentioned (but I don't think is in there yet) is an optimization which turns loops with 16-bit indices into nested loops with 8-bit ones. They're also really innovative with their use of the zero page, and nearly never actually needing to use the stack. Generating assembly that resembles what people write for the 6502 by hand, which is very different from assembly for modern machines, is a big priority.

Today I've been playing with writing stuff for the X16 with it, and it works fine there as well. I just had to tweak the C64 linker scripts to match the X16's memory layout. Regardless of improvements in optimization, we'll probably see around a 15% improvement in code size and possibly performance once they add support for the instructions added in the 65C02.

I think Linux actually gets far more commits, but it is harder to see because it is such a hierarchical organization that many of those commits never make it directly into the master branch of the "root" repository. The LLVM-MOS team is much smaller (I assume) and thus most things go straight to their "root" repo.

Or so I guess.

Link to comment
Share on other sites

On 6/26/2021 at 6:01 PM, Serentty said:

Today I've been playing with writing stuff for the X16 with it, and it works fine there as well. I just had to tweak the C64 linker scripts to match the X16's memory layout.

I would be very interested in the details of your tweaks. Did you make an X16 target alongside the existing 64 target, or did you just modify the 64 files into X16 files?

I'm trying to make my little game for the 128, 64, VIC, and PET, and they all put BASIC in different places...  64 works, of course.

I'm trying to get 128 working next.  My first attempt modified files directly in the 'build' directories.  I copied the 64 source directory and renamed it to 128.  I modified the ldscripts/link.ld to use the 1c01/1c0d addresses needed on the 128.  I renamed 64.cfg to 128.cfg and tweaked the comments (the actual commands didn't appear to need modification).

Got a valid PRG.  Tried to autostart it in VICE and it exploded.  Automounted it instead so that I could list it, and it was "7773SYS2061", so the basic header didn't autoadjust to the linker start point (I got lazy with KickC, because it generates the basic header on the fly). 

Realizing that I'd been hacking on output files instead of editing inputs, I moved out to the actual source code directories.  Did the equivalent edits from above to the source.  Then I adjusted various CMakeLists.txt files to include the new directory.  I modified the lib/basic_header.s to use 7181 (1c0d) in the SYS command.  Ran ninja to rebuild and I get

```

[0/1] /usr/bin/cmake -S/home/mac/games/llvm-mos-sdk -B/home/mac/games/llvm-mos-sdk/build
CMake Error at cmake/modules/AddObjectFile.cmake:10 (add_library):
  add_library cannot create target "basic_header" because another target with
  the same name already exists.  The existing target is created in source
  directory "/home/mac/games/llvm-mos-sdk/commodore/64/lib".  See
  documentation for policy CMP0002 for more details.
Call Stack (most recent call first):
  commodore/128/lib/CMakeLists.txt:6 (add_object_file)

```

I'm not much of a CMake or ninja user, just following steps and extrapolating what I can.  I don't quite see why the 128's basic_header is conflicting with the 64's basic_header.  They should be in separate directories.  But they only have one target machine under each "brand" of computer, so there may be some assumption buried somewhere that I'm just missing.  I think I looked at all the CMakeLists.txt from the root down and I can't see it, but that doesn't surprise me, really...

Link to comment
Share on other sites

I, also, don't understand why CMake doesn't recognize that they have different paths.  However, I worked around it by renaming the new header -- output -- file to "cx16_header".  (It needs to be renamed in both a CMakeLists.txt file and the link.ld script.)

  • Like 1
Link to comment
Share on other sites

*SMH*  D'oh!  

Thank you for that...  Just got jammed into my mental rut...

OK, all four platforms at least build and link now.  They load and (except for the VIC) have the correct BASIC SYS command waiting.  Now I just have to be more careful about linker files and where I'm placing my fonts and graphics and where the stack goes, and so forth ('coz they CPU JAM "immediately" if I run them 😞 )

Edited by DigitalMonk
Link to comment
Share on other sites

I also edited the output files, making a copy of the C64 ones, but thus far haven't bothered to move those to the input files. It could be useful to try to upstream some of these configurations you've been working on! One important thing not to miss is the list of free zero page locations.

Link to comment
Share on other sites

12 minutes ago, Serentty said:

One important thing not to miss is the list of free zero page locations.

Yeah...  I was really hoping I could just slip by on those...  A lot of them are BASIC workspaces that shouldn't matter much, but there are also KERNAL workspaces that would be very bad to stomp on.  I'll have to break out all my ZP memory maps and compare them. 

I'm really glad that the number of pseudo-registers and their locations is completely configurable through just text files.

Once I can get all my stuff running, I will make a cleanup pass to make sure I don't have "hackery" left sitting around, and then I'll definitely send a pull-request...  Hmmm, gotta fork the repo inside GitHub first, probably, instead of just messing with it on my local machine 🙂

Link to comment
Share on other sites

This is what I put in the linker script for the X16 (at the bottom—the rest is the same as the C64).

Quote

PROVIDE(__rc0 = 0x0002);
INCLUDE imag_reg/125.ld
ASSERT(__rc0 == 0x0002, "Inconsistent zero page map.")
ASSERT(__rc125 == 0x007f, "Inconsistent zero page map.")

__stack = 0xBFFF;

This should match the locations of the free zero page space. Also, since I haven't used banking yet, I placed the software stack (which doesn't get used that often thanks to the static stack optimization) in the banked area to give the maximum possible space for my program. I imagine that you would want this to be in low RAM in actual practice, though.

  • Like 1
Link to comment
Share on other sites

20 minutes ago, StephenHorn said:

By the way, does LLVM-MOS include a way to implement interrupt routines in C++?

First, their focus has been on clang, not clang++, so I'm not sure how much C++ support is present (I would expect all the language features to be there because that's a front-end common thing, but I know that the runtime library doesn't exist because that's a backend supplied library and they haven't worked on it yet).  I do want to start poking around with C++ language features, just to see how they go, but I want to get all my platforms working again first.

Second, I'd swear that I've seen somewhere (thought it was this thread, but can't find it) that interrupt handlers couldn't be written yet because of an implementation detail about how they handle function calls...  __BUT__ I've been trying to compare and contrast 5 different C compilers, so I could very easily be thinking of one of the others...

Link to comment
Share on other sites

Oh yeah, with all due respect to @SlithyMatt (Hi, I love your videos!), I've been trying to push the limits of how high-level I can write my code and still have it compile down to the same assembly as if I had written it in a low-level style, and the sheer amount of optimization that LLVM does makes a lot of stuff which is higher-level than C useful without being slower.

I just had a cool idea that worked out very well. Initially, I just wrote a simple copy_to_vram() function that copied blocks of data into VRAM using the autoincrement feature of the VERA. But then it occurred to me that this could be more general. Instead of making the function take data directly, I could instead make it take an iterator. Now, I can encapsulate the concept of writing something to VRAM with autoincrement, even if it's being calculated on the fly, without having to write code for autoincrement in the calculation code. The concept is very similar to passing in a closure or a generator. It goes without saying that the compiled assembly was the same when I tell it to iterate over an array. I've heard people talk about how powerful iterators are and how they're more than just a replacement for indexed loops, but this is the first time I've run into a situation like this where they're very handy. It allows me to separate the high-level calculations from the low-level bit fiddling and memory accesses, but have them compiled to be all mixed together the way you would writing the assembly by hand.

I also write functions to set VERA registers which take lots of different enumerations, and constant folding can turn something like this:
 

Quote

Layer::One.set_config(MapDimension::Tiles32,
                      MapDimension::Tiles32,
                      T256C::Off,
                      Bitmap::On,
                      ColourDepth::Bits4);

Into this:
 

Quote

lda #6
sta 40756


In terms of bad generated assembly, most of the limitations at the moment have to do with making the best use of the 6502's instruction set—it doesn't know about all of the NMOS instructions yet, and absolutely none of the CMOS ones. But that's something which isn't hard to fix, and is being worked on. So far I haven't run into a single case of bad assembly where the cause was my code being too high-level, and LLVM's optimization's not being able to make it lower-level, instead of the immaturity of the backend, and it not being able to make the best use of the instructions.

  • Like 1
Link to comment
Share on other sites

17 minutes ago, DigitalMonk said:

First, their focus has been on clang, not clang++, so I'm not sure how much C++ support is present (I would expect all the language features to be there because that's a front-end common thing, but I know that the runtime library doesn't exist because that's a backend supplied library and they haven't worked on it yet).  I do want to start poking around with C++ language features, just to see how they go, but I want to get all my platforms working again first.

This makes me thankful how Rust divides its standard library between freestanding and non-freestanding parts, so huge swaths of it are already available on the Commander X16, and indeed even the VIC-20. I suppose I'm living up to the Rust programmer stereotype by taking every opportunity to praise it relative to C++. 😁

Quote

Second, I'd swear that I've seen somewhere (thought it was this thread, but can't find it) that interrupt handlers couldn't be written yet because of an implementation detail about how they handle function calls...  __BUT__ I've been trying to compare and contrast 5 different C compilers, so I could very easily be thinking of one of the others...

That's correct. There's an optimization pass which checks to see which functions are recursive (even if indirectly). If a function is not recursive, as most are not, then instead of putting local variables on the stack, it allocates memory statically for them. In the future, the plan is also to check to see if two functions can ever be running at the same time, and if they can't be, to make them share the same memory for their locals. It's all very fascinating stuff. But the effect of this is that if you call a function from an interrupt handler, you could very well be violating the assumption that there can't be multiple instances of that function running at once, and overwrite the locals of the instance.

I actually have managed to write an interrupt handler in Rust for the Commodore 64 regardless, but only because the last thing my program did was set up a raster interrupt, and then just wait for it every frame, doing all further processing in interrupts. So interrupts are perfectly usable if you never have to come back from them. 😁 I managed to get a sprite bouncing around in the style of the old DVD screensaver. And then I increased it to eight at once.

Edited by Serentty
  • Like 1
Link to comment
Share on other sites

20 minutes ago, Serentty said:

This makes me thankful how Rust divides its standard library between freestanding and non-freestanding parts, so huge swaths of it are already available on the Commander X16, and indeed even the VIC-20. I suppose I'm living up to the Rust programmer stereotype by taking every opportunity to praise it relative to C++. 😁

Really, you're living up to the stereotype of all language evangelists. Python, Rust, C++, Java, Assembly... it's as much religion as it is art and science. 🙂

Link to comment
Share on other sites

1 hour ago, Serentty said:

PROVIDE(__rc0 = 0x0002);
INCLUDE imag_reg/125.ld
ASSERT(__rc0 == 0x0002, "Inconsistent zero page map.")
ASSERT(__rc125 == 0x007f, "Inconsistent zero page map.")

There are two register lists:

  1. pseudo-registers (used by X16's Kernal)
  2. imaginary registers (used by LLVM-MOS)

Be careful how much you overlap them!  Imaginary register "__rs0" is used as the permanent soft-stack pointer.

Edited by Greg King
Link to comment
Share on other sites

2 minutes ago, Greg King said:

There are two register lists:

  1. pseudo-registers (used by X16's Kernal)
  2. imaginary registers (used by LLVM-MOS)

Be careful how much you overlap them!  Imaginary register "__rs0" is used as the permanent soft-stack pointer.

Whoops!  Sorry for mis-using the term "pseudo-registers" when I was talking about "imaginary registers"...  I haven't started X16 programming yet, so I was just thinking about imaginary registers LLVM-MOS uses for PET/VIC/C64/C128/Atari/etc.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use