Hi,
I was just wondering if any compilers use NASM as a backend? Do you think it would be a good choice, in terms of speed, syntax etc.?
Posted on 2005-02-07 00:44:58 by StefanD
Hi,
I was just wondering if any compilers use NASM as a backend? Do you think it would be a good choice, in terms of speed, syntax etc.?


Not an up-to-date information, so take it with a grain of salt.

There were several attempts to use nasm with gcc. One was for 32-bit and one was for x86-64. The 32-bit version did not work out very well, and quickly died - or so I heard. x86-64 version was initiated by AMD, and used for initial development of OS on x86-64. But, not sure it is still available.

lcc from Princeton can be configured to run nasm. This is not applicable to native win-32 port of lcc maintained by someone else - it has its own assembler, which looks much more like djasm from djgpp. I don't know about Peles C, which, BTW, seems to be a rip-off of lcc. (The original lcc license does not permit that kind of distribution.)

And, maybe you want to check out nasm specific forum for more up-to-date info.
Posted on 2005-02-07 02:17:06 by Starless
Perhaps you should try reading the lcc license one more time.
Posted on 2005-02-07 02:35:08 by Jibz
Wow, I'm surprised more people haven't taken it on. I think its a pretty well designed piece of software, so I don't know why it isn't more popular. Most people are using MASM for windows programming aren't they?
Posted on 2005-02-07 05:53:36 by StefanD
> I was just wondering if any compilers use NASM as a backend?

I've used a win32 c compiler that uses NASM as a backend: CC386.
In http://www.members.tripod.com/~ladsoft/cc386.htm

For C compilers programmers:
http://www.geocities.com/nilzone/

For Linux:
http://retro.tunes.org/simplec/
Posted on 2005-02-10 10:08:14 by n u M I T_o r
Jibz is correct here, Pelle's C last I knew uses part of the LCC code properly in complaince with the licence and if fully legal software. Noting that Pelle wrote the vast majority of his system himself to produce a very good freeware C compiler, IDE and supporting system, in no sense could you call his work a rip off, just a very good project that is worth supporting.

To address the original question, NASM is a very good multiport / multiplatform assembler but it is probably not fast enough to use as a dedicated back end for a C compiler. Something more bare bones like GAS is the right tool here if you need to take that approach.
Posted on 2005-02-10 18:15:46 by hutch--
Hrm, GAS more barebone than NASM? GAS supports multiple architectures and both intel and AT&T syntax... I haven't done any direct speed comparisons, but GAS feels heavier. Might just be me, though :)

Anyway, I'd suggest using FASM for compiler backends - it's very fast, and Privalov has put a pride in making it faster whenever it's not "fast enough".

And, well, ideally a compiler shouldn't use an assembler as a backend anyway - it should generate "pcode" in memory and then directly output x86 code from that. Going from pcode->asm->x86 code is a silly additional step of parsing, and a relic of old tool design.
Posted on 2005-02-10 18:28:10 by f0dder
Yes,

FASM is a good choice for a compiler back end as it is genuinely fast enough and certainly a lot easier to construct the front end output for.

An interesting comment on pcode production, I know that almost all compilers use an internal image of the source loaded into a variety of complex data structures but I miss the value of the pcode generation first. It sounds like a redundant step.
Posted on 2005-02-10 18:57:26 by hutch--
Disclaimer: I haven't done compiler theory, so this is a bit sketchy, but I'll try anyway.

As I understand it, a compiler parses (tokenizes, lexes, whatever) it's source into an abstract syntax tree. This is done to verify correct input, and because the AST is a handy way of representing the source; you have it pre-parsed in a binary format, so you don't need to constantly call strlen etc. This AST is also easier to manipulate than text source.

Thus, once you have the AST (or "pcode"), you can start doing optimizations, constant folding, expression reduction, ... - so it is not a redundant step at all, it's actually required :)

From the pcode, you can directly generate native output for whatever CPUs you choose to support. Hell, you can consider .asm output as a specific CPU and fileformat combination. You need a pcode->whatever backend no matter what you do, and I guess choosing .asm output is "the easy way" to get multiple file format support for a given CPU, without having to add a file-format layer too.

And again, the above is mostly guesswork, so I could be terribly wrong - it makes sense to me, though :)

Btw, an interesting thing about using pcode representation. If your format is flexible enough, you can have multiple languages using the same pcode format... and thus the same optimizer. Iirc Microsoft did this for VC6 and VB6. Of course VB doesn't go all that fast, because of it's runtime and the language itself. But it's interesting nevertheless.
Posted on 2005-02-10 19:19:35 by f0dder
They essentially do that for .NET; compiling all languages to an "intermediate language" then to native code.
Posted on 2005-02-10 21:18:20 by drhowarddrfine
Before .NET is used as a case against pcode in compiler technology, keep in mind that .NET is usually JITed (and that the JITer could still use a lot of work), that the .NET framework is "pretty big", and that there may be better pcode formats than MSIL (is it stack or register based, btw?)
Posted on 2005-02-10 21:25:11 by f0dder
Ok, I get the word usage, I usually don't associate pcode with data abstraction in a data structure. I remember pcode as the crap that old VB used to save as a file and it was not very nice stuff.

The use of assembler output, (for whatever processor) is a useful step in that the data once it is ordered in the data structures can then be passed through an optimisation stage which generally produces a lot better code if the optimiser design is up to scratch.

I tend to see .NET as a big VB runtime wired into later OS versions and I doubt the code quality is all that good but then for its target market, it most probably does not matter. The idea of retaining "unmanaged code" solves the problem where .NET is simply not fast enough or powerful enough to perform a specialised task.
Posted on 2005-02-10 22:26:57 by hutch--

The use of assembler output, (for whatever processor) is a useful step in that the data once it is ordered in the data structures can then be passed through an optimisation stage which generally produces a lot better code if the optimiser design is up to scratch.

Optimization is being done before assembly output, on the program while it is in pcode form. A lot of internal information is lost once assembly output is generated, making it a lot harder to optimize (optimizing assembly output is roughly equivalent to optimizing an existing .exe, although you do have a bit more information available.)

Unfortunately I've lost the link, but there was an interesting article on the architecture of Visual C++ - basically how it first does generic (platform-independent) optimizations on the pcode (like simplyfying expressions), then the backend (itanium, x86, x86-64) does CPU-specific optimizations, and then finally code is generated.
Posted on 2005-02-11 01:51:16 by f0dder
NASM used to be the assembler used by PureBasic, but it switched to FASM a year ago or so.

Regards,
Posted on 2005-02-11 02:05:37 by El_Choni
Yes,

The 2 stage methods makes sense. Expression simplification tends to cover up bad programming practice where the backend optimisation is capable of delivering good performance if it is well designed. One of the problem that shows even in very recent compilers is the RISC based register theory where you see far more redundant loads and stores to memory under x86 because of the limited number of registers.

The real problem is that different hardware has different assumptions and optimising for a processor with many more registers than x86 is a very different matter to dealing with a processor that has 8 general purpose registers.

I am inclined to think that a heuristic approach based on the known chaacteristics of a processor family yield better results than more generalised RISC based compiler theory. I am of the view that the gap between well written assembler code and compiler output could be closed a lot by the heuristic approach.

Regards,

hutch at movsd dot com
Posted on 2005-02-11 03:45:51 by hutch--

Expression simplification tends to cover up bad programming practice

Leaving expressions in a "non-optimal" (mathematically wise) form isn't always because of bad programming practice - it can improve code readability (although, of course, code itself should not be the only form of documentation). This kind of optimization also has other purposes - you might need the same subexpression multiple times. Instead of calculating it and putting it in a temporary variable, you can use the subexpression multiple times and have the compiler doing the tedious job - again improving code readability. I remember how some code looked in the old days when compilers where a lot more stupid, and I certainly don't want to go back ;)


The real problem is that different hardware has different assumptions and optimising for a processor with many more registers than x86 is a very different matter to dealing with a processor that has 8 general purpose registers.

Which is why you split the optimization in two parts - first the generic "logical" optimizations, dead code elimination, expression folding (like inlining function calls, or replacing them by constants when you can (which happens more often than you might think, when using link-time code generation)), et cetera. And then the architecture specific optimization stage, which does register allocation and such.


I am of the view that the gap between well written assembler code and compiler output could be closed a lot by the heuristic approach.

Using profiler-based based compilation also helps - it lets the compiler see which code is run most often, which branches are usually taken, et cetera. I'm sure how much this gains you in percent speedwise, but every little bit helps :)
Posted on 2005-02-11 03:56:06 by f0dder
Here is where I see the problem, writing lousy code and hoping the compiler will fix it up has a very bad track record and while modern compilers will fix up a lot of mistakes or inefficient code, you will never get good code that way. Many modern compilers are capable of producing code at the binary output and that is within range of well written assembler code but it will come from programmers who understand the machine and write their code accordingly.

To this extent I fully agree with Randy Hyde in his new book about writing GREAT CODE. Without a grasp of what a compiler is doing under the hood, you end up with rubbish which many produce with even very good compilers.

I see the real action in compiler design as producing high quality binary output and for what is a little extra programmer effort, not making obvious mistakes in terms of code efficiency and writing decent code to start with will deliver code that is good enough.

The alternative is the bloated garbage that many produce because they either know no better or could not care less. It used to be viable to assume that the next generation of computer hardware would fix the problems of slow lousy code but the advance of computer hardware has slowed a lot in the last couple of years and software will have to get better to keep up with demand.

Regards,

hutch at movsd dot com
Posted on 2005-02-12 00:08:42 by hutch--

hoping the compiler will fix it up has a very bad track record

Indeed.


Without a grasp of what a compiler is doing under the hood, you end up with rubbish

Indeed.

However, with a HLL, the programmer should be freed from certain tasks. Of course it's still your own responsibility to use your brain and math skills, select optimal algorithms, choose good data structures, order data in an efficient way (like array of structures vs. structure of arrays) and so on.

But you should be free of "micro-management" tasks - otherwise there isn't much benefit from using a HLL. Fortunately, compilers of today tend to handle this well.
Posted on 2005-02-12 07:34:57 by f0dder