Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting JWasm to ELKS using OpenWatcom C #2103

Open
ghaerr opened this issue Nov 17, 2024 · 26 comments
Open

Porting JWasm to ELKS using OpenWatcom C #2103

ghaerr opened this issue Nov 17, 2024 · 26 comments

Comments

@ghaerr
Copy link
Owner

ghaerr commented Nov 17, 2024

@rafael2k writes (from #1443 (comment)):

In the meantime I tried to compile a full feature assembler, and managed to compile all files of JWasm (created a fork here: https://github.com/rafael2k/emasm ), just got some linking errors:

wmake -f OWELKS.mak
(...)
Error! E2028: alloca_ is an undefined reference
Error! E2028: strupr_ is an undefined reference
Error! E2028: remove_ is an undefined reference
Error! E2028: clock_ is an undefined reference
Error! E2028: __U8RS is an undefined reference
Error! E2028: _U8LS is an undefined reference
Error! E2028: ftruncate
is an undefined reference
Error! E2028: __PIA is an undefined reference
Next for me is understand the owc build system to "remove" wlink and wasm from the owc build system.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 17, 2024

The reasons for the undefined references and what needs to be done are as follows:

Error! E2028: remove_ is an undefined reference

remove is a system call that needs a wrapper written for ELKS, which will be similar to libc/watcom/syscall/rename.c.

Error! E2028: ftruncate is an undefined reference

ftruncate is a system call that is not implemented on ELKS. As such, some code within JWasm may need to be (re)written to truncate a file using creat possibly during the open. Further details after evaluating what's happening in JWasm.

Error! E2028: strupr_ is an undefined reference

We'll need to find a strupr implementation and add it to libc/string/strupr.c.

Error! E2028: __U8RS is an undefined reference
Error! E2028: _U8LS is an undefined reference
Error! E2028: __PIA is an undefined reference

These are OW library routines called from the OWC code generator. We'll need to pull them from the OWC source at bld/clib/cgsupp/a/pia.asm etc and copy them to libc/watcom/asm.

Error! E2028: alloca_ is an undefined reference

I'm pretty sure alloca is working with my OWC port. [EDIT: #include <alloca.h> will fix this].

Error! E2028: clock_ is an undefined reference

We'll need to read the OWC man page on its libc clock function, and implement that or call an ELKS libc equivalent.

All of this shouldn't a big deal, the question is how seriously you want to work get JWasm ported. I can help a lot and do some of the OW heavy lifting, but I'm not doing the actual port of JWasm. Let me know what you're thinking.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 17, 2024

@rafael2k, I went ahead and added almost everything you should need for getting JWasm linked to #2104.

Reading the JWasm source, it seems ftruncate isn't needed anymore, change #define TRUNCATE 0 at the top of JWasm/omf.c.

Also, for now create a dummy clock() routine and add it to JWasm as follows. I haven't added a full version since I haven't been able to test it.

#include <time.h>

clock_t clock(void)
{
    return 0;
}

Later, we can add a full version, something like this (untested):

#include <time.h>
#include <sys/time.h>
#include <sys/times.h>

clock_t times(struct tms *tp)
{
        struct timeval tv;

        if (gettimeofday(&tv, (void *)0) < 0)
                return -1;

        if (tp) {
                clock_t usecs = tv.tv_sec * 1000000LL + tv.tv_usec; // FIXME calculate CLK_TCKs not usecs

                /* return user and system same since ELKS doesn't implement*/
                tp->tms_utime = usecs;
                tp->tms_stime = usecs;
                tp->tms_cutime = usecs;
                tp->tms_cstime = usecs;
        }

        return tv.tv_sec;
}

clock_t clock(void)
{
    struct tms  buf;

    times( &buf );
    return buf.tms_utime * (CLOCKS_PER_SEC / CLK_TCK);
}

@rafael2k
Copy link

Thanks a lot @ghaerr! Today at night I'll see if I can get jwasm linked!
I've already fixed the clock and remove (although may be I wrongfully substituted remove with unlink).

@rafael2k
Copy link

Btw, I'm doing the port and it is very close to working. Next I'll just need some help / advice on what to use to link the object file in ELKS (or may be just add rudimentary support for ELKS binary directly in JWasm?)

@rafael2k
Copy link

Just tested it again, now I get:

Error! E2028: __PIA is an undefined reference
Error! E2028: alloca_ is an undefined reference
Error! E2028: __U8RS is an undefined reference
Error! E2028: __U8LS is an undefined reference

I'll investigate a bit further.
Btw, to build for elks, just:

wmake -f OWELKS.mak

@rafael2k
Copy link

Got alloca. I had to include malloc.h first to alloca.h.
Now just:

Error! E2028: __PIA is an undefined reference
Error! E2028: __U8RS is an undefined reference
Error! E2028: __U8LS is an undefined reference

Which header provides them?

@rafael2k
Copy link

Btw copying over parts of your commit, now I get:

file out.lib(/home/rafael2k/programs/devel/elks/libc/watcom/asm/pia.asm): undefined symbol __HShift

@rafael2k
Copy link

Btw, the linking line:

owcc -bos2 -s -Wl,option -Wl,start=_start -Wl,option -Wl,dosseg -Wl,option -Wl,nodefaultlibs -Wl,option -Wl,stack=0x1000 -Wl,option -Wl,heapsize=0x1000 -Wl,library -Wl,/home/rafael2k/programs/devel/elks/libc/libc.lib -o OWELKSR/apiemu.obj OWELKSR/assemble.obj OWELKSR/assume.obj OWELKSR/atofloat.obj OWELKSR/backptch.obj OWELKSR/bin.obj OWELKSR/branch.obj OWELKSR/cmdline.obj OWELKSR/codegen.obj OWELKSR/coff.obj OWELKSR/condasm.obj OWELKSR/context.obj OWELKSR/cpumodel.obj OWELKSR/data.obj OWELKSR/dbgcv.obj OWELKSR/directiv.obj OWELKSR/elf.obj OWELKSR/end.obj OWELKSR/equate.obj OWELKSR/errmsg.obj OWELKSR/expans.obj OWELKSR/expreval.obj OWELKSR/extern.obj OWELKSR/fastpass.obj OWELKSR/fixup.obj OWELKSR/fpfixup.obj OWELKSR/hll.obj OWELKSR/input.obj OWELKSR/invoke.obj OWELKSR/label.obj OWELKSR/linnum.obj OWELKSR/listing.obj OWELKSR/loop.obj OWELKSR/lqueue.obj OWELKSR/macro.obj OWELKSR/mangle.obj OWELKSR/memalloc.obj OWELKSR/msgtext.obj OWELKSR/omf.obj OWELKSR/omffixup.obj OWELKSR/omfint.obj OWELKSR/option.obj OWELKSR/parser.obj OWELKSR/posndir.obj OWELKSR/preproc.obj OWELKSR/proc.obj OWELKSR/queue.obj OWELKSR/reswords.obj OWELKSR/safeseh.obj OWELKSR/segment.obj OWELKSR/simsegm.obj OWELKSR/string.obj OWELKSR/symbols.obj OWELKSR/tbyte.obj OWELKSR/tokenize.obj OWELKSR/types.obj OWELKSR/main.obj out.lib

out.lib from the watcom/asm directory

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 17, 2024

undefined symbol __HShift

For now, add unsigned char _HShift; (single underbar, not double) to your JSasm source code somewhere, this should allow it to link. I'll produce a PR with the proper fix and you can remove this later.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 17, 2024

Actually, a unsigned char _HShift = 12; is required. We'll see whether JWasm runs or not... it seems there is some tricky handling of "huge" pointers within OWC's runtime which may require their own version of malloc, which could get complicated.

@rafael2k
Copy link

Yay, it runs! I'll try with different source files to check stability.

image

@rafael2k
Copy link

And you were also right about some tricky memory stuff. I might switch to official ow wasm.

image

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 17, 2024

out.lib from the watcom/asm directory

This is already included in the ELKS libc/libc.lib library - make sure that is both compiled by OWC correctly and in your link line.

The ELKS Watcom libc.lib is compiled using cd libc; make -f watcom.mk after setting libc/watcom.model's MODEL=l (for large) to match your compilation. I can't tell from your posts what model you are compiling JWasm but the library model has to match. It defaults to large model, which I would recommend for your first pass at all this.

And you were also right about some tricky memory stuff.

There are lots of potential issues but you've probably only set -Wl,option -Wl,heapsize=0x1000 which is a 4k heap. Try -Wl,option -Wl,heapsize=0x8000 which is 32k and see what happens.

@rafael2k
Copy link

Compiled ELKS/OS2 binary:
emasm.tar.gz

@rafael2k
Copy link

I can definitely see less brk() errors, but I can not raise too much the heap, as I get:

        owcc -bos2 -s -Wl,option -Wl,start=_start -Wl,option -Wl,dosseg -Wl,option -Wl,nodefaultlibs -Wl,option -Wl,stack=0x1000 -Wl,option -Wl,heapsize=0x3000 ....
Error! E3173: default data segment exceeds maximum size by 2096 bytes

@rafael2k
Copy link

I'm thinking is switching to ow wasm, as it might be that we are importing a lot of features / size from jwasm that might not even be useful on a 8086 arch.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 18, 2024

I'm thinking is switching to ow wasm, as it might be that we are importing a lot of features / size from jwasm

Oh, don't do that - you're just getting started with the real work of of moving a 32-bit application to 16-bits, and certain things need to be learned about JWasm to know how to proceed (or not). More on that below.

With regards to wasm, I briefly tried compiling it (with OWC) yesterday. It's going to be quite a bit harder to port, as it relies on a number of host-based tools that also need to be ported, which convert various "*.hg" files to "*.h" files just in order to get the header files pre-processed. And the entire process is built around the non-Makefile driven OWC build environment which is very complicated and involves yet more tools that need porting. And after all that, I suspect it'll require OWC's complex memory management routines in order to run, and I haven't ported them over (yet). Instead ELKS' normal C library memory allocation routines are being used.

How does one port 32-bit software to ELKS?

One must understand that porting present day software to a legacy 16-bit CPU almost always involves more work than just compiling C files and getting them linked. An analogy that comes to mind is stuffing 10 lbs of stuff into a 5 lb bag. There's never enough room to do things the simple way nowadays of just allocating memory; instead one has to figure out how to memory is going to be managed, as 64k blocks of memory just aren't enough to do much useful. Consider, for instance, that on any modern (now old) 32-bit system, one can allocate 100MB of memory with a single malloc, or declare an array of a billion bytes of memory - and it just works. The address space is so large that it's introduced the modern (lazy) mentality of just using a huge amount of memory for any reason at all, and makes for less discipline. This is one of the reasons I've enjoyed working on ELKS - we're trying to do big things with little memory.

OK, what actually needs to be done to port 32-bit software to ELKS?

The first step has already been done (for JWasm) - getting the source to compile, look at the unresolved symbols, and resolve them with an eye towards grouping them into external libraries needed, or unimplemented system calls, and what that looks like. After that, a successful link indicates they're all resolved. Usually at this time the software is run, and it usually crashes.

The next step is to learn how the application manages memory. As described above, this has to be known, since 16-bit applications can't allocate more than 32K at a time, and very little 32-bit software bothers with that. This can only be learned by reading (mostly) every file in the source base. For this step, what I do is:

  • vi *.c and use :n to look at every file, scrolling through.
  • Look at all global data declarations. Are there any over 1-2K bytes? The OWC compiler has an option to move .data declarations over a certain size to a far data section in large model. This can solve a lot of static data size issues. Lots of character strings is another thing to watch out for, but can't usually be moved as easily. Any static data declaration of over 64K is going to indicate major porting problems.
  • Find and look at all the memory allocations. Are they large or small? As above, any allocation larger than 64K will be a huge (no pun intended) issue. The default malloc only allocates from the near heap, so another allocator may have to be written, but if the allocations are < 64K, this won't be an issue. We can support large (~128-256k) allocations, but this will involve getting OWC's "huge" model working, which I haven't done yet.
  • After understanding how most of the dynamically allocated memory is done, this knowledge can be used to either set the heap size in the linker line, or whether a far memory allocator (and large or huge data model) has to be used for the application.
  • Look at "auto" (stack-based local variable) data allocations via the stack: how large are they? These are also limited to (way less than) 64k, and on 8086, the near data PLUS stack has to be less than 64K. The main purpose here is to determine how much stack to allocate to the application, given on the link line. The default stack is 4K, and too small will cause a crash.

A big benefit of a quick read-through of the entire source base will also give the programmer a basic understanding of how the application basically works, which goes a long way when thinking about how to get it ported.

We'll stop there. @rafael2k, my questions for you at this point are:

  • Does the application run at all? Will it assemble a tiny program? What exactly happens when started, or does it just crash? Does it spit out garbage or just give sbrk errors (indicating not enough heap)?
  • What model (large, compact, etc) is being used?

Sometimes, even though memory allocations in the source are large, they can be reduced to a lot smaller, with the effect of adding a limitation on the size of input data files that can be processed.

There's a lot more that can be said, but I generally will do these things (not necessarily in the same order) when questions come up about trying to get this or that program made available for ELKS.

@rafael2k
Copy link

Thanks a lot for the explanation. I'm using the large model, and the application runs (help and so on, all good). It crashes when I try to assemble even a small file, during the first pass in the file, it crashes with brk() errors (most likely from malloc() calls I think).
I'll check the first malloc() which gives the brk() error just to make sure it is what it seems.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 18, 2024

I'll check the first malloc() which gives the brk() error

It seems JWasm uses MemAlloc as an allocation wrapper. I sometimes will just add a printf in that routine the shows the size requested, rather than chasing down the call in the source. Also, as you know, the size_t (or int) parameter changes from 32 to 16 bits when porting, so you'll want to get an idea of how the parameter to MemAlloc gets calculated to ensure its not being truncated, etc. when chasing this down.

Depending on what you learn about JWasm, I have some ideas about a replacement memory allocator that would allocate large chunks from main memory, but smaller (< 1-2k?) from the near heap. Also, any instances of huge or __huge will have to be studied, as these definitely indicate the possibility of > 64k allocations. For now, the keyword(s) can be left in for large model compilations, but that'll be another thing to talk about.

If you can successfully trim down the default allocations to much smaller, you might be able to get JWasm running for very small input files. That'd be great, because then we can work on adding a better memory allocator while the program is runnable. I use the analogy of "keeping the patient alive" while doing heart surgery for this kind of work, since it much harder to get feedback when dead!

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 18, 2024

Hello @rafael2k,

After adding #2106, we are able to write a alloc/free wrapper that allocates memory from the near heap if below a certain threshold, or otherwise allocate from main memory. I've written the following which you might be able to use in JWasm. The MAX_NEAR_ALLOC may have to be lowered, especially if you're already out of data segment size at 0x3000 (12k). In general though it'd be a good idea to keep very small allocations out of the main memory area.

Note that the experimental functions take an unsigned long argument, not size_t.

#include <stdlib.h>

/* add these declarations to a JWasm header that's included for all MemAlloc allocations */
void __far *memalloc(unsigned long size);
void memfree(void __far *ptr);

/* put the remainder into memalloc.c and call these functions instead of malloc/free */
#define MAX_NEAR_ALLOC  1024UL  /* max size to allocate from near heap */

void __far *memalloc(unsigned long size)
{
    char *p;
    char __far *fp;

    if (size <= MAX_NEAR_ALLOC) {
        p = malloc((unsigned int)size);
        if (p)
            return (void __far *)p;
    }
    fp = fmemalloc(size);
    return fp;
}

#define SEGMENT(ptr)    ((unsigned long)(char __far *)(ptr) >> 16)

void memfree(void __far *ptr)
{
    if (SEGMENT(ptr) == SEGMENT(&ptr)) {    /* near pointer */
        free((char *)ptr);
    } else {
        fmemfree(ptr);
    }
}

You'll want to ensure that the memalloc/memfree function prototypes are in a JWasm header file so that the normal size_t argument is properly expanded to unsigned long, and then using this inside MemAlloc/MemFree in JWasm. You might also want to check for NULL and printf an error inside memalloc to see directly when its failing.

With some tuning, I think this should allow you to get JWasm running so we can understand its memory usage better.

Thank you!

@rafael2k
Copy link

rafael2k commented Nov 19, 2024

Thanks. I tested already and now the brk() errors are gone. The first pass of the assembler seems to work (with an input assembly with error, errors are shown), but then at some point it crashes (nothing on the screen, just freezes). I need to reboot. I'll instrument a bit more the code.

@rafael2k
Copy link

rafael2k commented Nov 19, 2024

image

After the last free the software gets lost. I'll investigate a bit.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 19, 2024

It seems its going through the assembly passes (3 of them?). The 3600 byte allocation looks good, returning a segment of 37f1 offset 0 in main memory. The other allocations are local, with your application data segment 7e72.

@rafael2k
Copy link

So, after a bit of frustration, I managed to port nasm!
https://github.com/rafael2k/8086-toolchain/tree/dev/assembler

Already compiled some code, so far so good. I'm not compiling all output formats, as I need to understand which output format is better.

The only dirty stuff I did was implement the realloc using fmemalloc and fmemfree...

It is a nasm fork with some tasm compatibility added. It reports version 0.98.35.

@ghaerr
Copy link
Owner Author

ghaerr commented Nov 20, 2024

So, after a bit of frustration, I managed to port nasm! https://github.com/rafael2k/8086-toolchain/tree/dev/assembler

Already compiled some code, so far so good. I'm not compiling all output formats, as I need to understand which output format is better.

The only dirty stuff I did was implement the realloc using fmemalloc and fmemfree...

It is a nasm fork with some tasm compatibility added. It reports version 0.98.35.

I've answered over at #1443 (comment).

Have you officially given up on JWasm then? I have no idea whether NASM and JWasm are compatible, nor whether OWC will work with NASM.

@rafael2k
Copy link

I learned some caveats when successfully porting NASM. I'll go back to to JWASM at some point (like erasing > 8086 instructions and using fmemalloc even for small allocations).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants