FPU software emulation
A question, not an issue, if you please. I see: "i386 processor (with floating-point processor)" in the minimum requirements. Are you planning to implement FPU software emulation, perhaps, at some point? I'd be interesting in reviving my 386sx machine with some modern, yet lightweight OS.
I'm in the same situation. I've a PC/104 with a 386SX with 4MB of RAM but without floating-point processor. I like to power on this machine from time to time and I saw that it 's somehow able to run FiwixOS 3.2, but some binaries crash because they try to run some FPU instructions.
I was investigating if there is a way to tell GCC to not use any FPU instruction (or use an emulation on its own instead), but I didn't see any option for this.
Sincerely, give FPU support to the Fiwix kernel is out of scope of my possibilities and is not in my priority list right now. Maybe in the future someone finds interesting to include a minimal FPU support.
Sorry for the inconveniences.
@mikaku thank you very much for the answer. I'll follow you and keep eye on the FiwixOS . Good luck with the amazing project.
Thank you!
Hello @mikaku, I think "-msoft-float" should work
@glegris, I've just added -msoft-float to CFLAGS in the Makefile and rebuilt the kernel but I still get the No Math Coprocessor exception:
GCC's manual says:
-msoft-float
Generate output containing library calls for floating point. Warning: the requisite libraries are not part of GCC. Normally the facilities of the machine's usual C compiler are used, but this can't be done directly in cross-compilation. You must make your own arrangements to provide suitable library functions for cross-compilation.
On machines where a function returns floating point results in the 80387 register stack, some floating point opcodes may be emitted even if -msoft-float is used.
Perhaps GCC requires a special configuration parameter during the build to accomplish this?
In my opinion, since FiwixOS's newlib is not compiled in soft float mode, the binaries of the code (df in your case) linked against newlib contains FP instructions. Newlib's printf uses FP for example.
Oh yes, you're right. I understood it in the other way around.
I should compile df (or any other program that uses FP instructions) to see the difference.
I've made a test with a simple program that generates Floating Point instructions:
#include <stdio.h>
static double test(double d)
{
printf("d = %.5G\n", (double)d);
return (d * 100);
}
int main(int argc, char** argv)
{
int res;
res = test(19.67);
printf("res = %d\n", res);
return 0;
}
It works well on an processor with a FPU:
(root):~# gcc -o fpu fpu.c
(root):~# ./fpu
d = 19.67
res = 1967
As you can see, It uses FP instructions:
[...]
08048229 <main>:
8048229: 55 push %ebp
804822a: 89 e5 mov %esp,%ebp
804822c: 83 e4 f0 and $0xfffffff0,%esp
804822f: 83 ec 30 sub $0x30,%esp
8048232: b8 ec 51 b8 1e mov $0x1eb851ec,%eax
8048237: ba 85 ab 33 40 mov $0x4033ab85,%edx
804823c: 89 04 24 mov %eax,(%esp)
804823f: 89 54 24 04 mov %edx,0x4(%esp)
8048243: e8 a8 ff ff ff call 80481f0 <test>
8048248: d9 7c 24 1e fnstcw 0x1e(%esp)
804824c: 66 8b 44 24 1e mov 0x1e(%esp),%ax
8048251: b4 0c mov $0xc,%ah
8048253: 66 89 44 24 1c mov %ax,0x1c(%esp)
8048258: d9 6c 24 1c fldcw 0x1c(%esp)
804825c: db 5c 24 2c fistpl 0x2c(%esp)
8048260: d9 6c 24 1e fldcw 0x1e(%esp)
8048264: 8b 44 24 2c mov 0x2c(%esp),%eax
8048268: 89 44 24 04 mov %eax,0x4(%esp)
[...]
When I compile it with the parameter -msoft-float I get undefined references (even with -lgcc parameter):
(root):~# gcc -msoft-float -o fpu fpu.c
/tmp/cc000039.o: In function `test':
fpu.c:(.text+0x4c): undefined reference to `__muldf3'
/tmp/cc000039.o: In function `main':
fpu.c:(.text+0x79): undefined reference to `__fixdfsi'
collect2: error: ld returned 1 exit status
I'm not sure but I think that GCC Toolchain must be built with a special configuration parameter that is not currently passed during the compilation on FiwixOS.
I need to investigate more on this.
After reading this I'm a bit skeptic (at least for i386).
I see that soft-fp is not integrated in the FiwixOS's gcc package. You can build the soft-fp library yourself and compile your code against it:
gcc -msoft-float test.c -lsoft-fp
I can't compile soft-fp for gcc-4.7.4, it seems broken. I found a temporary solution based on a third party library in the meantime. The source code to compile your example is attached (I wrote a small README at the root). softfloat.zip
Nice, it seems to work on an i386 without FPU.
I think the best way to compile the ieeelib under FiwixOS is integrate it into GCC Toolchain build, so it can use the libgcc directory directly. Then, I'll try rebuild the packages of FiwixOS using the softfloat library and see how it works.
Some progress ...
I've included the ieeelib project into the build process of the GNU Toolchain. So now, the script make-toolchain.sh also generates the file ieeelib_2c52005_i386.ipk which will install the library /usr/i386-pc-fiwix/lib/libsoft-fp.a into the system.
Then I've modified the file makeall.sh, which is the script that builds all the packages that conform the FiwixOS, to introduce the ability to build some packages with -msoft-float and with the link-time parameter -lsoft-fp. Since the command free is one of those that generate the No Math Coprocessor exception, I've modified the script to also create the package procps_3.2.8_i386-softfp.ipk and it worked nicely.
The next command was grep, which also worked perfectly and now there is the new package grep_3.11_i386-softfp.ipk.
The problem was with the command awk. During its build I got the following error messages:
[...]
gcc -msoft-float -s -o gawk array.o awkgram.o builtin.o dfa.o ext.o field.o floatcomp.o gawkmisc.o getopt.o getopt1.o io.o main.o msg.o node.o random.o re.o regex.o replace.o version.o eval.o profile.o -lm -lm -lsoft-fp
builtin.o: In function `format_tree':
builtin.c:(.text+0x253a): undefined reference to `__unorddf2'
builtin.c:(.text+0x258e): undefined reference to `__unorddf2'
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:491: gawk] Error 1
make[2]: Leaving directory '/mnt/disk/gawk-3.1.8'
make[1]: *** [Makefile:550: all-recursive] Error 1
make[1]: Leaving directory '/mnt/disk/gawk-3.1.8'
make: *** [Makefile:397: all] Error 2
*** An error occurred. Build has been stopped ***
It looks like the original code of ieeelib did not include any reference to the function __unorddf2.
I've been investigating and I discovered an email in the GCC Project Mailing List where a user called Joseph S. Myers submitted a patch which hopefully will fix this. The same email also references another one that fixes a different bug.
I plan to introduce both patches into the build process of the ieeelib, so eventually I'll be able to compile awk and hopefully the rest of programs.
Well, I have bad news. This was a false positive: the free and the grep commands worked because I had an old configuration in the 86Box. The i386 motherboard was setup with FPU. D'oh! :disappointed:
I think that these commands still have the FPU instructions that come with the Newlib static libraries. So, in the next days I'll try to rebuild the Newlib C library using the msoft-float, and link it with -lsoft-fp.
In fact, the fpu in the softfloat.zip doesn't work either.
In fact, the
fpuin the softfloat.zip doesn't work either.
Could you give me your 86Box configuration? So I can try to fix things too.
[General]
vid_renderer = qt_software
host_cpu = Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
emu_build_num = 6000
uuid = 56db3ab3-69ea-5a67-a191-d9f03826b693
[Machine]
machine = asus386
cpu_family = i386dx
cpu_speed = 40000000
cpu_multi = 1
cpu_use_dynarec = 0
time_sync = local
fpu_softfloat = 0
cpu = 4
mem_size = 65536
[Video]
gfxcard = vga
[Input devices]
mouse_type = none
[Storage controllers]
hdc = ide_isa_2ch
cassette_mode = load
[Hard disks]
hdd_01_parameters = 63, 16, 2080, 0, ide
hdd_01_fn = /mnt/storage/kvm/fiwix-ext2-1GB.img
hdd_01_speed = 1997_5400rpm
hdd_01_ide_channel = 0:0
[Floppy and CD-ROM drives]
fdd_01_fn = /mnt/storage/kvm/fiwix-floppy.img
fdd_01_type = 35_2hd
fdd_02_type = none
cdrom_01_parameters = 1, atapi
cdrom_01_ide_channel = 1:0
cdrom_01_image_path = /mnt/storage/kvm/FiwixOS-3.3-i386.iso
cdrom_01_type = 86BOX_CD-ROM_1.00
[Sound]
fm_driver = nuked
[Serial Passthrough Device #1]
mode = 0
data_bits = 8
stop_bits = 1
baudrate = 115200
[Ports (COM & LPT)]
serial1_passthrough_enabled = 1
just wanted to say thank you for trying to get this to work. it would be awesome to have; if you get it to work, people will be able to run Fiwix on the 486 MiSTer core
Yeah, the main problem I found is that GCC appears to create function references that ieeelib doesn't support (eqxf2, mulxf2, and others), probably because this library needs some update.
In any case, I don't have enough expertise on this field, so I'll try to do my best until someone with more knowledge can come to help us.
Reference: https://github.com/richfelker/musl-cross-make/issues/76
I recently made progress with the help of @mikaku. We managed to get a minimal version of Fiwix OS to boot on a 386 without an FPU.
To run Fiwix on a processor without an FPU (386SX / 486SX), you need to be able to compile userland programs with soft float. The kernel itself doesn’t need to be recompiled because it doesn’t use floating point. Compiling in soft float means that floating-point calculations will be handled by the CPU instead of the FPU. Therefore, we must ensure that whenever the compiler encounters floating-point calculations in source code, it calls specific routines instead of emitting FPU instructions.
GCC
Ideally, we would have a version of gcc that does this automatically, but some older versions are finicky and/or complex to configure. As a first step, we will ensure that by default gcc always generates soft float code (-msoft-float) and does not use FPU extensions (-mno-80387 -mno-fp-ret-in-387) nor instructions too recent to exist on 386/486 (-mno-mmx -mno-sse -mno-sse2). The mno-fp-ret-in-387 flag is important because it prevents gcc from using an FPU register to return long double (it is stored in memory with this flag). Using this flag requires that all other code compiled elsewhere and interacting with the current code follows the same convention. One can also try to avoid using long double as much as possible.
# cat softfloat.specs
*cc1:
%{!msoft-float:-msoft-float -mno-80387 -mno-fp-ret-in-387 -mno-mmx -mno-sse -mno-sse2}
*libgcc:
-lsoftfloat -lgcc
*libgcc_eh:
-lgcc_eh
For use by default, these specs must be placed in a specific location that gcc loads automatically: /usr/lib/gcc/i386-pc-fiwix/4.7.4/specs
cp softfloat.specs /usr/lib/gcc/i386-pc-fiwix/4.7.4
cp softfloat.specs /usr/lib/gcc/i386-pc-fiwix/4.7.4/specs
In some complex cases, it may be necessary to specify the specs on the command line:
gcc -specs /usr/lib/gcc/i386-pc-fiwix/4.7.4/softfloat.specs
We must provide certain soft float routines because they are missing in libgcc.a (in this version of GCC).
gcc -O2 -ffreestanding -fno-builtin -msoft-float -mno-80387 -mno-mmx -mno-sse -mno-sse2 -march=i386 -m32 -c soft_float32.c -o soft_float32.o
gcc -O2 -ffreestanding -fno-builtin -msoft-float -mno-80387 -mno-mmx -mno-sse -mno-sse2 -march=i386 -m32 -c soft_float64.c -o soft_float64.o
gcc -O2 -ffreestanding -fno-builtin -msoft-float -mno-80387 -mno-fp-ret-in-387 -mno-mmx -mno-sse -mno-sse2 -march=i386 -m32 -c soft_float80.c -o soft_float80.o
ar rc libsoftfloat.a soft_float32.o soft_float64.o soft_float80.o
ranlib libsoftfloat.a
objcopy --weaken libsoftfloat.a
cp libsoftfloat.a /usr/i386-pc-fiwix/lib
"objcopy --weaken" is necessary because libgcc.a routines must take priority over those in libsoftfloat.a.
Let’s do a test:
# cat test.c
double f(double x) {
return x * 1.5 + 2.0 / x;
}
int main() {
double result = f(3.14);
return (int) result;
}
# gcc -o test test.c
# objdump -d test | less
We can see in the code below that there are no floating-point instructions (fadd/fmul/fdiv...). Instead, specific routines are called: __muldf3, __divdf3, __adddf3.
080481e8 <f>:
80481e8: 55 push %ebp
80481e9: 89 e5 mov %esp,%ebp
80481eb: 56 push %esi
80481ec: 53 push %ebx
80481ed: 83 ec 30 sub $0x30,%esp
80481f0: 8b 45 08 mov 0x8(%ebp),%eax
80481f3: 89 45 f0 mov %eax,-0x10(%ebp)
80481f6: 8b 45 0c mov 0xc(%ebp),%eax
80481f9: 89 45 f4 mov %eax,-0xc(%ebp)
80481fc: b8 00 00 00 00 mov $0x0,%eax
8048201: ba 00 00 f8 3f mov $0x3ff80000,%edx
8048206: 89 44 24 08 mov %eax,0x8(%esp)
804820a: 89 54 24 0c mov %edx,0xc(%esp)
804820e: 8b 45 f0 mov -0x10(%ebp),%eax
8048211: 8b 55 f4 mov -0xc(%ebp),%edx
8048214: 89 04 24 mov %eax,(%esp)
8048217: 89 54 24 04 mov %edx,0x4(%esp)
804821b: e8 80 06 00 00 call 80488a0 <__muldf3>
8048220: 89 c3 mov %eax,%ebx
8048222: 89 d6 mov %edx,%esi
8048224: 8b 45 f0 mov -0x10(%ebp),%eax
8048227: 8b 55 f4 mov -0xc(%ebp),%edx
804822a: 89 45 e8 mov %eax,-0x18(%ebp)
804822d: 89 55 ec mov %edx,-0x14(%ebp)
8048230: b8 00 00 00 00 mov $0x0,%eax
8048235: ba 00 00 00 40 mov $0x40000000,%edx
804823a: 89 45 e0 mov %eax,-0x20(%ebp)
804823d: 89 55 e4 mov %edx,-0x1c(%ebp)
8048240: 8b 45 e8 mov -0x18(%ebp),%eax
8048243: 8b 55 ec mov -0x14(%ebp),%edx
8048246: 89 44 24 08 mov %eax,0x8(%esp)
804824a: 89 54 24 0c mov %edx,0xc(%esp)
804824e: 8b 45 e0 mov -0x20(%ebp),%eax
8048251: 8b 55 e4 mov -0x1c(%ebp),%edx
8048254: 89 04 24 mov %eax,(%esp)
8048257: 89 54 24 04 mov %edx,0x4(%esp)
804825b: e8 bc 0a 00 00 call 8048d1c <__divdf3>
8048260: 89 44 24 08 mov %eax,0x8(%esp)
8048264: 89 54 24 0c mov %edx,0xc(%esp)
8048268: 89 1c 24 mov %ebx,(%esp)
804826b: 89 74 24 04 mov %esi,0x4(%esp)
804826f: e8 9c 00 00 00 call 8048310 <__adddf3>
8048274: 83 c4 30 add $0x30,%esp
8048277: 5b pop %ebx
8048278: 5e pop %esi
8048279: 5d pop %ebp
804827a: c3 ret
Newlib
Newlib is a standard C library, but it’s still just code that must comply with what we said above. We will therefore ensure that Newlib does not contain any FPU instructions. This method was found through trial and error since it is not well documented.
cp src/newlib-4.4.0.20231231.tar.gz .
tar xvf newlib-4.4.0.20231231.tar.gz
cd newlib-4.4.0.20231231
patch -p1 < ../src/newlib-4.4.0.20231231.patch
patch -p1 < ../src/newlib-4.4.0.20231231-fiwix.patch
Now we can configure Newlib with the correct flags.
# Configure hints
export newlib_cv_ldbl_eq_dbl=yes # long double == double
export newlib_cv_printf_long_double=no # no I/O long double
export ac_cv_func_frexpl=no
export ac_cv_func_strtold=no
./configure --target=i386-fiwix --host=i386 --prefix=/usr --with-arch=i386 --disable-newlib-io-long-double --enable-libstdcxx --enable-newlib-io-c99-formats --enable-newlib-long-time_t CFLAGS_FOR_TARGET="-Os -msoft-float -mno-80387 -mno-fp-ret-in-387 -mno-mmx -mno-sse -mno-sse2" CPPFLAGS_FOR_TARGET="-D_LDBL_EQ_DBL=1 -D__NO_LONG_DOUBLE_MATH=1 -D_NO_LONGDBL=1 -D_WANT_IO_LONG_DOUBLE=0"
make
The build will fail because there is still floating-point assembly code in Newlib. Let’s manually modify the Makefile (ideally, one should inspect what Newlib does to create a proper patch) by replacing it with this one if nothing was changed in the configure step, or by comparing this patch with i386-fiwix/newlib/Makefile. Restart the build and install:
make
make install
Copy the files from /usr/i386-fiwix/lib to /usr/i386-pc-fiwix/lib (be careful, this overwrites your old Newlib library, so make a backup if necessary).
cp -rf /usr/i386-fiwix/lib/* /usr/i386-pc-fiwix/lib
Let’s do a test that uses Newlib:
cat test.c
#include <stdio.h>
double f(double x) {
return x * 1.5 + 2.0 / x;
}
int main() {
double result = f(3.14);
printf("result=%f\n", result);
return (int) result;
}
# gcc -o test test.c
# objdump -d test | less
You will not find FPU instructions in the test binary because Newlib’s printf no longer uses them.
Creating a minimal FiwixOS derivative to test on a 386sx/486sx
BusyBox is not too complex to compile and can serve as a first step.
Let’s compile BusyBox using this script. Check at the bottom of the file that only BusyBox is going to be built:
...
# build bc 1.07.1 i386 "" "GNU's bc (a numeric processing language) and dc (a calculator)"
build busybox 1.01 i386 "" "Statically linked binary providing simplified versions of system commands"
# build busybox-mini 1.01 i386 "" "Statically linked binary providing simplified versions of system commands"
...
./makeall-softfloat.sh
Install the busybox package which is in the builds directory
When the Fiwix kernel starts, it launches the /sbin/init process. We will therefore replace /sbin/init with BusyBox’s init routine.
cp /sbin/init /sbin/init.orig
ln -s /sbin/busybox /sbin/init
Replace all the basic utilities with BusyBox versions.
cp -rf /bin /bin.orig
ln -sf /sbin/busybox /bin/ash
ln -sf /sbin/busybox /bin/sh
ln -sf /sbin/busybox /bin/mount
ln -sf /sbin/busybox /bin/umount
ln -sf /sbin/busybox /bin/init
ln -sf /sbin/busybox /bin/login
ln -sf /sbin/busybox /bin/getty
ln -sf /sbin/busybox /bin/tty
ln -sf /sbin/busybox /bin/stty
ln -sf /sbin/busybox /bin/su
ln -sf /sbin/busybox /bin/reboot
ln -sf /sbin/busybox /bin/poweroff
ln -sf /sbin/busybox /bin/passwd
ln -sf /sbin/busybox /bin/ps
ln -sf /sbin/busybox /bin/pwd
ln -sf /sbin/busybox /bin/kill
ln -sf /sbin/busybox /bin/killall
ln -sf /sbin/busybox /bin/ls
ln -sf /sbin/busybox /bin/cp
ln -sf /sbin/busybox /bin/rm
ln -sf /sbin/busybox /bin/rmdir
ln -sf /sbin/busybox /bin/sync
ln -sf /sbin/busybox /bin/vi
ln -sf /sbin/busybox /bin/which
ln -sf /sbin/busybox /bin/xargs
ln -sf /sbin/busybox /bin/yes
ln -sf /sbin/busybox /bin/dd
ln -sf /sbin/busybox /bin/df
ln -sf /sbin/busybox /bin/echo
ln -sf /sbin/busybox /bin/env
ln -sf /sbin/busybox /bin/false
ln -sf /sbin/busybox /bin/fdisk
ln -sf /sbin/busybox /bin/find
ln -sf /sbin/busybox /bin/fgrep
ln -sf /sbin/busybox /bin/free
ln -sf /sbin/busybox /bin/grep
ln -sf /sbin/busybox /bin/gzip
ln -sf /sbin/busybox /bin/gunzip
ln -sf /sbin/busybox /bin/halt
ln -sf /sbin/busybox /bin/head
ln -sf /sbin/busybox /bin/hexdump
ln -sf /sbin/busybox /bin/id
ln -sf /sbin/busybox /bin/last
ln -sf /sbin/busybox /bin/length
ln -sf /sbin/busybox /bin/md5sum
ln -sf /sbin/busybox /bin/mkdir
ln -sf /sbin/busybox /bin/more
ln -sf /sbin/busybox /bin/rm
ln -sf /sbin/busybox /bin/touch
ln -sf /sbin/busybox /bin/pidof
ln -sf /sbin/busybox /bin/sed
ln -sf /sbin/busybox /bin/sha1sum
ln -sf /sbin/busybox /bin/sleep
ln -sf /sbin/busybox /bin/sort
ln -sf /sbin/busybox /bin/tail
ln -sf /sbin/busybox /bin/tar
ln -sf /sbin/busybox /bin/tee
ln -sf /sbin/busybox /bin/time
ln -sf /sbin/busybox /bin/top
ln -sf /sbin/busybox /bin/touch
ln -sf /sbin/busybox /bin/tr
ln -sf /sbin/busybox /bin/true
ln -sf /sbin/busybox /bin/uname
ln -sf /sbin/busybox /bin/uniq
ln -sf /sbin/busybox /bin/unix2dos
ln -sf /sbin/busybox /bin/unzip
ln -sf /sbin/busybox /bin/uptime
ln -sf /sbin/busybox /bin/watch
ln -sf /sbin/busybox /bin/wc
ln -sf /sbin/busybox /bin/who
ln -sf /sbin/busybox /bin/whoami
ln -sf /sbin/busybox /bin/zcat
The boot sequence is:
/sbin/init (busybox/init) -> /etc/inittab (calls /etc/init.d/rcS) -> busybox/getty -> busybox/login -> busybox/ash
Create a minimal inittab:
cat /etc/inittab
#
# inittab This file describes how the INIT process should set up
# the system in a certain run-level.
#
# Default runlevel. The runlevels used by RHS are:
# 0 - halt (Do NOT set initdefault to this)
# 1 - Single user mode
# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
#
# System initialization.
::sysinit:/etc/init.d/rcS
# Virtual consoles
tty1::respawn:/bin/getty 38400 tty1
tty2::respawn:/bin/getty 38400 tty2
# Serial ttys
#ttyS0::respawn:/sbin/busyboxsf getty -L ttyS0 115200 vt100
# Trap CTRL-ALT-DELETE
::ctrlaltdel:/bin/reboot
And our /etc/init.d/rcS file is:
cat etc/init.d/rcS
#!/bin/sh
/sbin/busybox mount -t proc proc /proc
/sbin/busybox mount -o remount,rw /
exit 0
Let’s modify the /etc/profile file to adapt it to ash, which supports fewer features than bash.
cp /etc/profile /etc/profile.orig
cat etc/profile
# System wide environment
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"
umask 022
unset LS_OPTIONS
alias ls='ls -CF'
alias ll='ls -l'
PS1='\w \$ '
USER="`id -un`"
LOGNAME=$USER
HISTSIZE=10000
HISTCONTROL=ignoredups
if [ -z "$INPUTRC" ] ; then
INPUTRC=/etc/inputrc
fi
export PATH PS1 USER LOGNAME HISTSIZE INPUTRC
if [ $(id -u) -eq 0 ] ; then
HOME=/root
export HOME
cd $HOME
fi
Change the root user’s shell by editing /etc/passwd. Set /bin/ash instead of /bin/bash.
The mini-OS is now ready. It will need to be enriched with the other FiwixOS programs.
Excellent @glegris, this is a great job, and thanks for sharing all this valuable information.
The next step will be bootstrap a new GNU Toolchain using this Newlib without FPU instructions and installing it on the system. Then, start building every single package in makeall.sh.