Sunday, January 6, 2013

After looking at avr-libc, I've decided that it won't work either. There are a lot of assembly modules in there, as well as several modules which are hardware-dependant, all of which would have to be removed for the library to be of any use.

I could make a new library using all the relevant parts of avr-libc, but it uses a modified BSD license, which I would have to maintain for any extensions I made later. I don't have a problem with the BSD license, and it's probably the right one to use, but I don't want to deal with legalisms at this point.

Months earlier, I started working on my own libc implementation. At this point, it looks like my best bet is to just complete that project and call it a day.

By the way, there is some nice documentation on the libc functions at http://nongnu.org/avr-libc/user-manual/modules.html. That could be very handy for coverage checks.

Saturday, January 5, 2013

Over the last few days I've been trying to get a libc library working for the TMS9900.

I started with libiberty, but got stuck trying to compile regexp.c. There were a lot of missing header files, and I wasn't able to find functional replacements. I gave up on it because even if I got past regexp, there were a lot of OS-related functions which would need to be removed. These are functions like file IO, syscalls, threading and timezones. Actually, after all that was removed, there wouldn't be much left.

So next I tried uclibc. This is a popular replacement for the standard glibc library, and is often used for embedded applications. There are a lot of configuration options, and many categories of functions can be simply disabled by using its configuration menu. This seemed like uclibc was perfect for our needs. I got as far as adding the TMS9900 as a valid target and running some test builds. Again I found problems. All of uclibc assumes 32-bit words. It also assumes that certain classes of algorithms will compile to small and fast code. Based of the strategies used, it seems to be aimed primarily at the ARM family of processors. I know from past experience that the ARM instruction set encourages heavy use of 32-bit shifts and integer operations, discourages branches and nearly all instructions execute at the same rate. Memory is plentiful and fast, so large lookup tables are encouraged. ARM instructions have three operands: two inputs and an output, so equvalent code  for the TMS9900 would, if nothing else, add a lot of MOV instructions. This model is a really bad match for the TMS9900, where all 32-bit operations are costly, shifts are slow, branches are fast, memory is tiny and slow, and there is a lot of variation in instruction speed. Ultimately, the resulting code would be huge and slow, even after the word size issues were resolved. I gave up and moved on.

After some research, I came across avr-libc. This is is a libcc replacement for the Atmel family of processors. These are low power 8 and 16 bit processors intended solely for embedded applications. They are most commonly found in Arduino development boards, and typically do not use an operating system. The instruction set is limited, and shares many aspects with the TMS9900. There is also a lot of documentation for the library and best practice guides. This seems encouraging.

Wednesday, January 2, 2013

I was reading some GCC bug reports and I found a more correct way to build libgcc:

  $ mkdir build
  $ cd build
  $ ../configure --prefix /home/eric/dev/tios/toolchain --target=tms9900 --enable-languages=c
  $ make all-gcc all-target-libgcc
  $ make install

This is way easier and a lot more convenient than the directions I had before. I think that as long as I'm doing libraries, I'll try libiberty and see what happens.
So now I've excised all the fake PC stuff, and I feel much better about the situation.

I've been looking through the GCC internal documentation looking for additional features I may have missed which would help. Unfortunately, I didn't notice anything I haven't tried before. There were a few macros that caught my eye, but sadly are of no use for this project.

SUBREG_PROMOTED_UNSIGNED_P
SUBREG_PROMOTED_UNSIGNED_SET
SUBREG_PROMOTED_VAR
These macros defines how byte values are converted to word values when stored in registers, which sound like it would be perfect for the crazy byte representation the TMS9900 uses. Unfortunately, this isn't useful. These macros can only select between signed and unsigned extension. Regardless of the selection made, GCC assumes that the byte value can be retrieved from the least significant 8 bits of the register. This is no good.

HARD_REGNO_MODE_OK
MODES_TIEABLE_P
CANNOT_CHANGE_MODE_CLASS
These macros deal with which registers are used to store values, and how values can be converted between data types. Again, this sounds great for dealing with byte values. Again, they are not useful. GCC only uses these for values larger than a word. In our case, values larger than 16 bits. GCC reasonably assumes that any register which can be used to store word values can be used to store values smaller than a word. It also assumes that byte values are always stored in the least significant bits of any register used.

So, having gone through all the documentation again, the only way to have a proper GCC port is to follow the path we ultimately took: make changes to the internals of the compiler and force mode to changes be preserved until instruction generation. This is fine, but there are a lot of places where these mode changes can happen. We'll likely be dealing with faulty byte-to-word conversions for a long time as user code explores the edge cases. Ick.

Nothing left to do now but track down the reported bugs.

Tuesday, January 1, 2013

I'm about halfway through removing the fake PC register. Right now, there's a new "*rt" instruction using the UNSPEC framework to emit "b *r11".

The UNSPEC functions are intended to handle instructions which are otherwise undescribable using normal RTX expressions. This is perfect for the TMS9900 return sequence, since we can't use the normal return instruction. The reason for that is if a return instruction is defined, we cannot apply a function epilogue since that is implemented in the place where a normal return instruction would live. Also, we need a specific label for any type of explicit jump, which won't work for a return.

The fake PC register has been a worrying point for a long time, and we've been burned by it before. Removing it will eliminate all possibility for further unexpected messes.

All this is great, but now I have to remove all the hackery I used earlier to insert the fake PC register. That involves register definitions, location of the first pseudoregister, a special case for the MOV instruction as well as other special cases in the register allocation directives.

That's quite a bit of work, and it's late. We'll try that tomorrow.

Monday, December 31, 2012

I've been working for an hour or less most days since the last update, and it didn't make sense to write an update for each day. So here's a summary of what's been happening.

I had originally intended 32-bit multiplies to be implemented in libgcc, but instead GCC is trying to cobble something together using 16-bit multiplies. That results in unexpectedly wrong results when signed multiplication is used. The problem here is that MPY does only unsigned multiplication, but the way the MULHI3 pattern is written, GCC assumes that signed multiplication is used. Unfortunately, I could find no way to rewrite this instruction to result in nice-looking code.

The only way I could resolve the situation was to implement a MULSI3 instruction for inline 32-bit multiplies. There was an implementation commented out in the machine description file, but I was able to reduce it to nine instructions and two temporary registers. I'm not happy about this, but I can't think of a better solution.

The implementation of the TMS9900 libgcc source code has been totally refactored. Initially, I had an individual file for each libgcc function. There were quite a lot of them, and I would see default code unexpectedly linked into the test code. The problem there was that libgcc expects all assembly optimizations to be stored in a single file, pointed to the LIB1ASMSRC symbol in the makefile. Additionally, the LIB1ASMFUNCS symbol needs to contain a list of the implemented functions so the default ones will not be linked into the library. This was annoying to track down, since I couldn't find documentation for any of this, and had to rely on code used by other processors. All the libgcc functions have been tested, and work as expected.

I looked at the floating point emulation code, but it is mostly a mess of precompiler macros, and fails to build for 16-bit architectures. I think I got an error like "Here's a nickel kid, buy yourself a real processor..." This did not make me happy. My options now are to either refactor the existing code to support 16-bit machines, or write a new library from scratch. If I'm honest the second option seems more interesting.

After looking though the GCC documentation, I may have found a way to get rid of the fake PC register using UNSPEC instructions. These are unspecified machine-specific operations that might be used to handle returns or maybe even byte-to-word conversions. I would need to do some major testing for that one though.

Thursday, December 13, 2012

All functions are now implemented, now it's time for validation testing. I've already checked out the divide and modulus functions, since I knew that they would tricky.

I've started testing 32-bit multiplies, and I keep seeing results consistent with unsigned multiply, regardless of the type of operation called for. I'm also seeing that the __mulsi3 function in libgcc is not being used. It's been long enough that I've forgotten if I've implemented a 32-bit multiply recipe in the machine description file. I better check that out.