[llvm-dev] Clang for the PlayStation 2

445 views
Skip to first unread message

Dan Ravensloft via llvm-dev

unread,
Sep 2, 2018, 5:14:19 PM9/2/18
to llvm...@lists.llvm.org
Hello,

I'm part of the (sadly fairly small) community of PS2 hackers. The current cross-toolchain for the PS2 is based on GCC 3.2.3, an outdated and buggy compiler, which I have personally gotten tired of working with, so I would like to port Clang as a newer cross-compiler for the PS2.

However, the PS2 has some notable quirks which make this a non-trivial task for the current compiler. It has two main CPUs, the "Emotion Engine" (EE), which controls the main operating system, and the "I/O Processor" (IOP), which is used for PS1 compatibility and for I/O.

The EE is based on a custom chip called the R5900, which implements most of MIPS III (except the ll and sc instructions, which make little sense on a single-core CPU), as well as some instructions from MIPS IV (pref, movz/movn, rsqrt.s), and a set of SIMD instructions known as Multimedia Instructions (MMI). It also contains a non-IEEE 754 single-precision FPU (which has provided a lot of headaches). It was later re-used by Toshiba as the TX79, along with a proper FPU.

The IOP is based on the MIPS I R3051A, and was also used as the PS1 CPU.

I'd like to go through some of the issues I've had so far.

I've tried to use `mips64el` as a target, but it would invoke the system GCC with obvious bad results, so I have used the `mips64el-img-linux` target to generate ELF files which the PS2 BIOS can load (is there a better solution to generate bare-metal ELF?).

I then tried to compile newlib 3.0.0-20180802, as newlib is used as the standard C library for the PS2, and hit multiple asserts, which I have attached below.

I don't mind trying to fix these myself, but I would appreciate some pointers.

llvm-bugs.tgz

Tim Northover via llvm-dev

unread,
Sep 3, 2018, 8:31:40 AM9/3/18
to dan.rav...@gmail.com, LLVM Developers Mailing List
Hi Dan,

On Sun, 2 Sep 2018 at 22:14, Dan Ravensloft via llvm-dev
<llvm...@lists.llvm.org> wrote:
> I then tried to compile newlib 3.0.0-20180802, as newlib is used as the standard C library for the PS2, and hit multiple asserts, which I have attached below.

After a quick look, three of the issues seem to be related to "long
double", which is translated to fp128:

vfprintf: invalid bitcast from f128 to f64. Part of some call, maybe?
ldtoa: obviously a function taking "long double".
vfscanf: Mishandling return of f128 from a libcall.

Now, mapping long double to fp128 is actually a choice you have as an
ABI creator. It's possible you feel constrained to follow GCC here and
so need that, but if not the simplest fix might well be to just
declare long double is the same as double. PS2 doesn't strike me as a
system where you'd often want a 128-bit float. You'd do that by
modifying tools/clang/lib/Basic/Targets/Mips.h (the setN32N64ABITypes
function, notice FreeBSD has already done this).

If you do have to support f128, you'd probably start by focusing on
MipsISelLowering.cpp. Specifically the functions named things like
LowerFormalArguments, LowerCall, LowerReturn. I'm not sure what the
ABI is likely to be there, but I suspect you'll end up assigning an
fp128 to 2 64-bit integer registers. If needed I'm sure me or a Mips
expert could provide more details.

The other problem (lrintfp) seems to involve an invalid truncation
from f32 to f64, possibly inserted by code unaware of the single-float
option. Nothing looks obviously wrong, so it's the usual debugging
procedure:

1. Try to produce a reduced test-case
2. gdb/lldb it, since not many places produce the Mips-specific
TruncIntFP node that's causing the problem.

For 1, I've actually already got one for you:

void foo(float *in, long long *out) {
*out = *in;
}

In brief, how I got it was:
1. Run lldb on the crashing Clang command. Go up the backtrace until I
got usable C++ code. DAG.viewGraph() produces a very nice pictorial
representation of the code being selected, it had load(f32) ->
MipsISD::TruncIntFP (f64) -> store. That middle trunc is obviously
really dodgy.
2. From there you could try to write IR that did the same thing.
Instead I printed "BlockName" that was available in one of the frames.
Then I dumped the IR from Clang (-emit-llvm) and just looked at that
IR snippet:

%23 = load float, float* %x.addr, align 4
%conv33 = fptosi float %23 to i64
store i64 %conv33, i64* %retval, align 8
br label %return

From there it's a lot easier to write down the C function I gave,
which is what I did.

So the next step is to debug where Mips is producing those TruncIntFP
nodes. There'll be some constraint it's not checking or an unexpected
node type, probably related to -msingle-float. I'm afraid I'm not sure
what yet.

Cheers.

Tim.
_______________________________________________
LLVM Developers mailing list
llvm...@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev

Dan Ravensloft via llvm-dev

unread,
Sep 3, 2018, 9:16:01 AM9/3/18
to t.p.no...@gmail.com, llvm...@lists.llvm.org
Thanks Tim, that's actually a very useful guide, and debugging this is going to be much easier with that knowledge.

First step: rebuild Clang.

Dan Ravensloft via llvm-dev

unread,
Sep 6, 2018, 11:01:06 AM9/6/18
to Tim Northover, llvm...@lists.llvm.org
On Mon, 3 Sep 2018 at 13:31, Tim Northover <t.p.no...@gmail.com> wrote:
So the next step is to debug where Mips is producing those TruncIntFP
nodes. There'll be some constraint it's not checking or an unexpected
node type, probably related to -msingle-float. I'm afraid I'm not sure
what yet.

I'm reasonably sure the function producing that node is lowerFP_TO_SINT_STORE in lib/Target/Mips/MipsISelLowering.cpp.

The node before that function executes has an fp_to_sint node which seems to want to convert an i64 to an f32 which seems...rather odd to me, honestly. The PS2, for what it's worth, only has an i32 -> f32 instruction, so I think there's an impedance mismatch somewhere.

Tim Northover via llvm-dev

unread,
Sep 6, 2018, 11:31:40 AM9/6/18
to Dan Ravensloft, LLVM Developers Mailing List
On Thu, 6 Sep 2018 at 16:00, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> I'm reasonably sure the function producing that node is lowerFP_TO_SINT_STORE in lib/Target/Mips/MipsISelLowering.cpp.

That doesn't surprise me.

> The node before that function executes has an fp_to_sint node which seems to want to convert an i64 to an f32 which seems...rather odd to me, honestly.

It's a pretty common kind of operation. C says you're allowed to cast
an int64_t to a float, and that's the IR you get when you try.

> The PS2, for what it's worth, only has an i32 -> f32 instruction, so I think there's an impedance mismatch somewhere.

This is also a fairly common situation. If the operation can be
emulated with a reasonably small number of native instructions you can
often get LLVM to do that.

In this case it would probably be a libcall though because it's quite
complex. LLVM would generate a call to __floatdisf, which will be
provided by compiler-rt (there are C implementations for all kinds of
floating-point operations there).

You should see the same thing if you compile a function doing that
conversion with GCC.

Daniel Sanders via llvm-dev

unread,
Sep 6, 2018, 11:35:33 AM9/6/18
to Dan Ravensloft, llvm...@lists.llvm.org

On 6 Sep 2018, at 08:00, Dan Ravensloft via llvm-dev <llvm...@lists.llvm.org> wrote:

On Mon, 3 Sep 2018 at 13:31, Tim Northover <t.p.no...@gmail.com> wrote:
So the next step is to debug where Mips is producing those TruncIntFP
nodes. There'll be some constraint it's not checking or an unexpected
node type, probably related to -msingle-float. I'm afraid I'm not sure
what yet.

FWIW, I'm not sure how well tested -msingle-float was on MIPS. I don't think we had any bots testing it.


I'm reasonably sure the function producing that node is lowerFP_TO_SINT_STORE in lib/Target/Mips/MipsISelLowering.cpp.

The node before that function executes has an fp_to_sint node which seems to want to convert an i64 to an f32 which seems...rather odd to me, honestly. The PS2, for what it's worth, only has an i32 -> f32 instruction, so I think there's an impedance mismatch somewhere.

Did you mean those types to be the other way around? fp_to_sint is supposed to take a floating point type and produce an integer type so if you're seeing them backwards like this then that would definitely be a bug.

If you're referring to the size mismatch though, that's ok within the IR and DAG nodes. There's no relationship between the size of the input and output. 

Dan Ravensloft via llvm-dev

unread,
Sep 6, 2018, 11:56:13 AM9/6/18
to Daniel Sanders, llvm...@lists.llvm.org

On Thu, 6 Sep 2018, 16:35 Daniel Sanders, <daniel_l...@apple.com> wrote:


On 6 Sep 2018, at 08:00, Dan Ravensloft via llvm-dev <llvm...@lists.llvm.org> wrote:

On Mon, 3 Sep 2018 at 13:31, Tim Northover <t.p.no...@gmail.com> wrote:
So the next step is to debug where Mips is producing those TruncIntFP
nodes. There'll be some constraint it's not checking or an unexpected
node type, probably related to -msingle-float. I'm afraid I'm not sure
what yet.

FWIW, I'm not sure how well tested -msingle-float was on MIPS. I don't think we had any bots testing it.

Well, now we have hardware, if all of one machine, where it's worth testing. I'm happy to try it myself if needed, though rigging it up to happen automatically would be difficult.
I'm reasonably sure the function producing that node is lowerFP_TO_SINT_STORE in lib/Target/Mips/MipsISelLowering.cpp.

The node before that function executes has an fp_to_sint node which seems to want to convert an i64 to an f32 which seems...rather odd to me, honestly. The PS2, for what it's worth, only has an i32 -> f32 instruction, so I think there's an impedance mismatch somewhere.

Did you mean those types to be the other way around? fp_to_sint is supposed to take a floating point type and produce an integer type so if you're seeing them backwards like this then that would definitely be a bug.

Yes, my mistake; I got confused by the direction of the arrows on the graph, they're pointing to their parents, but I thought they were pointing to their children.

Dan Ravensloft via llvm-dev

unread,
Sep 6, 2018, 12:56:03 PM9/6/18
to Tim Northover, LLVM Developers Mailing List
On Thu, 6 Sep 2018, 16:31 Tim Northover, <t.p.no...@gmail.com> wrote:
> The PS2, for what it's worth, only has an i32 -> f32 instruction, so I think there's an impedance mismatch somewhere.

This is also a fairly common situation. If the operation can be
emulated with a reasonably small number of native instructions you can
often get LLVM to do that.

In this case it would probably be a libcall though because it's quite
complex. LLVM would generate a call to __floatdisf, which will be
provided by compiler-rt (there are C implementations for all kinds of
floating-point operations there).

You should see the same thing if you compile a function doing that
conversion with GCC.

Cheers.

Tim.

So I was rereading this; do you think the lowering function should instead emit a library call instead, then?

If so, would you mind pointing me to a function which performs this, or otherwise give a high-level description of how this is done?

Tim Northover via llvm-dev

unread,
Sep 6, 2018, 1:08:28 PM9/6/18
to Dan Ravensloft, LLVM Developers Mailing List
On Thu, 6 Sep 2018 at 17:55, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> So I was rereading this; do you think the lowering function should instead emit a library call instead, then?

I suspect so, though I'm not familiar enough with MIPS or the PS2 to
be 100% sure -- checking what GCC does in this case would be good
confirmation.

> If so, would you mind pointing me to a function which performs this, or otherwise give a high-level description of how this is done?

I *think* you should be able to just tell the lower function to ignore
this case (maybe based on the types, maybe just because we're in
SingleFloat mode, I'll need to read more code to be sure). Then the
generic handling should get involved and expand it to a libcall if the
operation isn't natively supported.

Tim Northover via llvm-dev

unread,
Sep 6, 2018, 3:01:32 PM9/6/18
to Dan Ravensloft, LLVM Developers Mailing List
Hi again,

On Thu, 6 Sep 2018 at 17:55, Dan Ravensloft <dan.rav...@gmail.com> wrote:

> If so, would you mind pointing me to a function which performs this, or otherwise give a high-level description of how this is done?

I just did a very quick experiment where I made lowerFP_TO_SINT and
lowerFP_TO_SINT_STORE return SDValue() (which is the marker for "I
don't want to handle this"). It looks like someone was enthusiastic
enough to think this operation did actually deserve inlining (by
TargetLowering::expandFP_TO_SINT) so instead of a libcall it produces
a bunch of weird operations implementing that. I assume they're right,
but haven't checked.

To make it production-quality you'd want to predicate the changes I
made on Subtarget->isSingleFloat() I think (probably in combination
with the actual types, since f32 -> i32 ought to still be OK with the
existing code). The main annoyance there is that lowerFP_TO_SINT_STORE
is static rather than a member of MipsISelLowering so it doesn't have
access to Subtarget. Personally, I'd just make it a member function to
fix that.

Dan Ravensloft via llvm-dev

unread,
Sep 7, 2018, 2:39:02 AM9/7/18
to Tim Northover, LLVM Developers Mailing List
On Thu, 6 Sep 2018 at 20:01, Tim Northover <t.p.no...@gmail.com> wrote:
I just did a very quick experiment where I made lowerFP_TO_SINT and
lowerFP_TO_SINT_STORE return SDValue() (which is the marker for "I
don't want to handle this").

I just tried this, but the compiler still crashes with the same error. Maybe our experiments were different.

To make it production-quality you'd want to predicate the changes I
made on Subtarget->isSingleFloat() I think (probably in combination
with the actual types, since f32 -> i32 ought to still be OK with the
existing code). The main annoyance there is that lowerFP_TO_SINT_STORE
is static rather than a member of MipsISelLowering so it doesn't have
access to Subtarget. Personally, I'd just make it a member function to
fix that.

lowerFP_TO_SINT_STORE is only ever called by lowerFP_TO_SINT, so I'm just passing single-floatness (we need a better name for that) as an argument to lowerFP_TO_SINT_STORE at the moment.

Cheers.

Tim.

Dan Ravensloft via llvm-dev

unread,
Sep 7, 2018, 6:57:15 AM9/7/18
to uexplo...@gmail.com, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 09:58, UE US <uexplo...@gmail.com> wrote:
Hi,

Years ago, I got to go through the magical adventure of building a working Cell compiler based on LLVM / Clang for the PS3.  It was supposed to be open sourced / upstreamed to llvm, but of course the company didn't want to pay for the extra hours that would take. :P
Heh, I'm not getting paid anything for this.
 
I'm not sure how bad the GCC compiler for PS2 is, but if it's anything like the PS3 one I'd suggest to watch out that any Sony ABI docs probably have glaring faults somewhere.  :D
Yup, the PS2 docs are...okay, but not great. The IOP is entirely undocumented, for example. There's no specific Sony ABI, though.
 
I don't know how much I can help with the PS2 / EE but I've noticed that some of that type of design stuff tends to persist so when you get to that area of it let me know if you run into any problems and they might refresh my memory on something that can help;  never know.
Sure, I'm going to try to make my misadventures public for posterity so that anybody who comes along later can continue my work if they wish.

I've got a debug/test PS3 with a hardware Emotion Engine as well if you want any spot checks on that particular version of the platform.  I'm guessing the actual debugging part is either already covered or easier via an emulator + PC at this point, and I don't have it set up for that. 
Can't say no to free hardware, although a TOOL might be nice, providing Sony haven't destroyed them all.

Just thought I'd offer, not much but it can't hurt when diving into that kind of "fun" :D :D

Cheers,
-G

Tim Northover via llvm-dev

unread,
Sep 7, 2018, 11:59:29 AM9/7/18
to Dan Ravensloft, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 07:38, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> I just tried this, but the compiler still crashes with the same error. Maybe our experiments were different.

Strange. I only tested it on a simple reproducer rather than newlib:

void foo(float *in, long long *out) {
*out = *in;
}

$ clang -target mips64el-img-linux -mcpu=mips3 -S -o- -Os tmp.c
[...]

Is it possible you were hitting a different error with roughly similar output?

> lowerFP_TO_SINT_STORE is only ever called by lowerFP_TO_SINT, so I'm just passing single-floatness (we need a better name for that) as an argument to lowerFP_TO_SINT_STORE at the moment.

Are you sure? I see it being called by lowerSTORE.

Dan Ravensloft via llvm-dev

unread,
Sep 7, 2018, 12:30:20 PM9/7/18
to Tim Northover, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 16:59, Tim Northover <t.p.no...@gmail.com> wrote:
On Fri, 7 Sep 2018 at 07:38, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> I just tried this, but the compiler still crashes with the same error. Maybe our experiments were different.

Strange. I only tested it on a simple reproducer rather than newlib:

void foo(float *in, long long *out) {
       *out = *in;
}
$ clang -target mips64el-img-linux -mcpu=mips3 -S -o- -Os tmp.c
[...]

Is it possible you were hitting a different error with roughly similar output?

Both your simple reproducer and newlib give the same crash for me (Unexpected illegal type), though my build of LLVM doesn't list any direct symbols in the backtrace despite compiling in Debug mode. (maybe I missed a cmake option?)
 
> lowerFP_TO_SINT_STORE is only ever called by lowerFP_TO_SINT, so I'm just passing single-floatness (we need a better name for that) as an argument to lowerFP_TO_SINT_STORE at the moment.

Are you sure? I see it being called by lowerSTORE.

You are correct, my apologies.

So we're on the same metaphorical page, I've attached my git diffs; the build target I'm using is mips64el-scei-ps2. ps2-clang.diff applies to clang, while ps2-llvm applies to llvm, but that should be obvious.
ps2-llvm.diff
ps2-clang.diff

Dan Ravensloft via llvm-dev

unread,
Sep 7, 2018, 12:33:25 PM9/7/18
to Tim Northover, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 16:59, Tim Northover <t.p.no...@gmail.com> wrote:
$ clang -target mips64el-img-linux -mcpu=mips3 -S -o- -Os tmp.c
Actually, I just tried your flags; you're missing `-msingle-float`, which is what reproduces the crash on my end. Without it there is no problem.

Tim Northover via llvm-dev

unread,
Sep 7, 2018, 12:48:26 PM9/7/18
to Dan Ravensloft, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 17:33, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> Actually, I just tried your flags; you're missing `-msingle-float`, which is what reproduces the crash on my end. Without it there is no problem.

Oops, yes, I was then. Fortunately not last night though, and my Clang
still works with -msingle-float.

I looked at your diffs and you've only changed one of the functions to
return SDValue(), you need to change lowerFP_TO_SINT itself too. The
one with the store is just there as an optimization; if it doesn't
trigger (because of your diff) then lowerFP_TO_SINT will still create
a bad node afterwards.

Dan Ravensloft via llvm-dev

unread,
Sep 7, 2018, 1:53:21 PM9/7/18
to Tim Northover, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 17:48, Tim Northover <t.p.no...@gmail.com> wrote:
I looked at your diffs and you've only changed one of the functions to
return SDValue(), you need to change lowerFP_TO_SINT itself too. The
one with the store is just there as an optimization; if it doesn't
trigger (because of your diff) then lowerFP_TO_SINT will still create
a bad node afterwards.

Thank you!

With that, the four bugs I found in newlib's C code are dead. I think the lrint fix should be upstreamed right away; would you mind if I credited you in the patch?

Unfortunately, we are not out of the woods yet. If memory serves me correctly, newlib has some assembly code in used for things like `_start` to bring up the C runtime, which GNU binutils accepts and Clang's assembler currently does not.

I'll go build that so I'm not talking out of my ass.

Cheers, Tim!

Tim Northover via llvm-dev

unread,
Sep 7, 2018, 2:33:02 PM9/7/18
to Dan Ravensloft, LLVM Developers Mailing List
On Fri, 7 Sep 2018 at 18:53, Dan Ravensloft <dan.rav...@gmail.com> wrote:
> With that, the four bugs I found in newlib's C code are dead.

Excellent!

> I think the lrint fix should be upstreamed right away; would you mind if I credited you in the patch?

Not at all. Thanks!

Alexander Richardson via llvm-dev

unread,
Sep 8, 2018, 6:32:41 PM9/8/18
to dan.rav...@gmail.com, llvm-dev

Hi Dan,

In case you are interested in the baremetal libgloss bits, I made some
changes to get that to compile with clang in
<https://github.com/CTSRD-CHERI/newlib>. This also contains some
unrelated changes to make it easier to use on QEMU MALTA and to build
in CHERI pure-capability mode but the changes to the MIPS crt0 bits
might be useful.
I believe <https://github.com/CTSRD-CHERI/newlib/commit/2e686345f77a1b745e504ffdbdf012ec78fbfc74>
and possibly some follow-up commits should be sufficient to compile
libgloss with clang (at least for MIPS64).


Alex

> Cheers, Tim!

Dan Ravensloft via llvm-dev

unread,
Sep 16, 2018, 5:34:42 AM9/16/18
to Tim Northover, LLVM Developers Mailing List
I've been somewhat busy with life over the past week, but I thought I'd come back to this all the same.

On Sat, 8 Sep 2018 at 19:04, Dan Ravensloft <dan.rav...@gmail.com> wrote:
On Sat, 8 Sep 2018 at 15:01, Tim Northover <t.p.no...@gmail.com> wrote:
At the moment I'm really quite confused about where that PseudoCVT_S_L
is coming from though, the pattern generating it ought to be disabled.
I commented out that pattern and Clang now complains about not being able to select a valid instruction, but it doesn't segfault, suggesting that pattern is definitely causing the crash.
I suppose I'll need to familiarise myself with the Clang pattern syntax anyway, because of the various major inconsistencies of the R5900 (it has no DMULT(U) or DDIV(U) instructions, which means it's not entirely MIPS III compliant).

In other news, the "unexpected illegal type" assert is now fixed in trunk, which is the first step in a long, long path to bringing up support for this...quirky...architecture.

Dan Ravensloft via llvm-dev

unread,
Sep 16, 2018, 1:17:01 PM9/16/18
to xkrebstarx, LLVM Developers Mailing List
I have a GitHub page containing my patches and a rough writeup of the issues I've encountered at https://github.com/ZirconiumX/ps2-clang-patches

There's still a *lot* of work to be done before I'm going to consider upstreaming the backend, if at all, but I'll do my best to get bugfixes sorted quickly.

On Sun, 16 Sep 2018, 18:10 xkrebstarx, <xkreb...@gmail.com> wrote:
Hi Dan,

This is pretty interesting. Do you have a project page (or something) that I can check out? 
_______________________________________________

Dan Ravensloft via llvm-dev

unread,
Oct 23, 2018, 12:30:59 PM10/23/18
to xkrebstarx, LLVM Developers Mailing List, Tim Northover
So, I've been busy with life over the past month, but that has given me chance to reflect on what this should look like.

I would like to reach parity (plus or minus the FPU) with GCC 3.2.3, and hopefully be stable enough to run Rust on the PS2. By having a better compiler we can squeeze more out of the chip; in particular I'm curious about the second pipeline of the EE; despite stating that the chip is 2-wide superscalar, the docs are unclear on whether this is automatic or manual. Since the chip has dedicated instructions for running on the second pipeline, I'm inclined to think manual.

There is also both MMI (which is internal to the chip) and VU0 macro mode (which uses VU0 as COP2) as possibilities for autovectorisation, but that is a long way off.
Reply all
Reply to author
Forward
0 new messages