Small and simple C compiler for x86 (DOS, Windows, Linux, Mac OS X) and MIPS (RetroBSD).
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
The compiler can now self-host on Mac OS X and cross-compile for it, hooray!
Just added strtof(), strtod(), atof() and float parsing in *scanf()!
I've adapted the D-Flat Windowing System for compilation with Smaller C.
Here's a screenshot of a demo application, MemoPad, using it.
Unreal mode is now supported in the compiler! This is a screen capture from DOSBox running tests/vesalfb.c compiled using the -dosu option. VirtualBox produces similar results (different color palette, no text message).
I've adapted ucpp 1.3.2 for use with Smaller C. No need for workarounds such as invoking gcc (e.g. with the -ppg option). smlrpp should now be invoked automatically and it should just work.
Just added an SDL clock demo for Windows.
The Mandelbrot set image you're seeing here has been generated by a new test/demo program, which uses floating point.
Late last year we made a series of improvements in RetroBSD and Smaller C (switched to compiling apps and libraries as MIPS16e, made numerous fixes and improvements in the emulator (including MIPS16e support) and shrank one large array in the compiler), which significantly reduced the compiler in size, like by 20+ KB. That extra space made me think of what else could be implemented or improved in Smaller C.
Of the largest language debts stood out floating point. I looked at it and decided to give it a try and it was perfect timing as the long Xmas/NY break was approaching. So, over the period of several weeks I've implemented and tested basic support for float. RetroBSD builds with double equivalent to float (32-bit single precision, software implementation as the PIC32MX CPU does not contain an FPU) and Smaller C follows suit.
This still is a work in progress and there are a few other limitations like several operators still unsupported with floats (++, --, +=, -=, *=, /=), but other than that things seem to be working quite well.
Hooray!
Evolution / source code visualization of Smaller C. I have no idea why someone would make one for my project.
I've adapted João André Esteves Vilaça's Arduino Tetris for compilation with Smaller C on RetroBSD. Here it is running on the PICadillo-35T board with the Funduino joystick shield v1.a attached (invisible because it's on the back).
Teh codez.
Create an account to leave a comment. Already have an account? Log In.
See here: https://github.com/alexfru/SmallerC/blob/master/v0100/include/sys/80186.h
Hi, is there a way to use variables in the inline assembler?
For example instead of using asm("mov ax, [ss:bp+6]"); using asm("mov ax, %size"); or something like that? This would be really benefical for return values. I have an assembly routine returning more than one register and i want to assign those to a struct, but doing this via stack is very laborius...
For example: asm("mov %memory.size, ax");
The "inline assembler" is almost nonexistent. The assembly code is emitted as-is.
But you can use NASM's struc directive to define a structure and then access its members.
Example:
// file: astr.c
// compile: smlrcc -doss astr.c -o astr.exe
#include <stdio.h>
asm(
"struc REMQUO\n"
" .rem resw 1\n"
" .quo resw 1\n"
"endstruc\n"
);
struct REMQUO
{
unsigned short rem, quo;
};
void mydiv(unsigned short dividend, unsigned short divisor, struct REMQUO* result)
{
asm(
"mov ax, [bp + 4]\n"
"xor dx, dx\n"
"div word [bp + 6]\n"
"mov bx, [bp + 8]\n"
"mov [bx + REMQUO.rem], dx\n"
"mov [bx + REMQUO.quo], ax\n"
);
}
int main(void)
{
struct REMQUO r;
mydiv(10, 6, &r);
printf("10 %% 6 = %u\n10 / 6 = %u\n", r.rem, r.quo);
return 0;
}
Thank you for your help, i'll try that.
Edit: It worked like a charm!
Now I tried SmallerC on Linux. I compiled it with:
gcc -Wall -Wextra -O2 smlrc.c -o smlrc
then I compile a hello world program with:
./smlrc -SI ../v0100/include hello.c hello.asm
However NASM complains when I use -f aout:
hello.asm:212: error: segment name `.rodata' not recognized
I can replace .rodata with .data and NASM will compile. But can I use a parameter to avoid .rodata in the generated NASM code? Or is there a parameter so NASM will compile in spite of the .rodata section?
The compiler was designed to produce code that can be compiled into ELF object files (with NASM, YASM, FASM+n2f), which smlrl would then link into one of the many different supported executable formats (ELF, PE, Mach-O, a.out, DOS EXE, flat).
Can you generate 16bit executables for Minix in a.out executable format with SmallerC?
I don't think so. a.out support (in smlrl) was meant to be 32-bit only.
Thank you Alexey. However, I needed an aout executable without relocation information. After further testing, I found that these lines do work for me:
./smlrc -SI ../v0100/include hello.c hello.asm
nasm -f as86 -D.rodata=.data hello.asm -o hello.o
bcc -o hello hello.o
bcc calls ld86 to link the object file in as86 format but uses its own libraries, crt0.o and libc in the process
Hi.
I'm using SmallerC to write a bootloader capable to put in operation a 64-bit operating system starting with PXE. The compilation options -huge and -unreal are ideal for generating appropriate binaries in this situation, allowing to make use of the 16 bits PXE API as well to allocate the operating system above the first mega of RAM. Comparing the sources of the c0du and c0dh libraries I was surprised that the _start function gives the control to __start__ in a case (c0dh.asm) by a far return:
119 push eax
120 retf ; __start__() will set up argc and argv for main() and call exit(main(argc, argv))
and in another case (c0du.asm) through a call to a far function (callf, opcode 0x9A):
151 ; Call __start__(), which will set up argc and argv for main() and call exit(main(argc, argv))
152 db 0x9A
153 .patch_start_addr:
154 dd ___start__
155 ; __start__() shouldn't return
I find it strange that in c0du.asm (-unreal) is put in the stack an unnecessary return address instead of carrying out just a long jump (jmpf, opcode 0xEA) that does not put anything on the stack, in a way similar to the far return that is done in c0dh.asm (-huge).
So the question is if you have any special motivation for the use on line 152 of callf instead of jmpf. The only one motivation that occurred to me is to ease transfer parameters to the function __start__ which would require adding a fill return address, which is what ultimately does the callf of line 152.
Thank you very much for the SmallerC project.
Pedro Pablo.
A far call/jump should work in both cases as long as there's a proper entry in the .relot section to relocate the target address in the call/jump instruction and turn into seg16:ofs16. If you compile "void foo(); void bar() { foo(); }" with -dosh/-huge and -dosu/-unreal, you'll see identical assembly output, doing just that. There's no particular reason for this difference in c0dh.asm and c0du.asm. You don't always do the same thing the same way.
Okay, life would be very boring if things went always do the same.
On the other hand SmallerC provides the macros __HUGE__ and __UNREAL__ to be able to distinguish the two models within of my network boot program:
38 #ifndef __UNREAL__
39 "mov eax, 0x0E0E0E0E \ n" / * fill with a fictitious return address * /
40 "push eax \ n" / * oeeeee oe oe oeeeee ... * /
41 #endif
So I have implemented it successfully in my first version most elementary of the PXE network boot program written with SmallerC that I posted at: https://github.com/so1h/bldr_s.
Thank you very much for the reply.
Hello. How can I access the source code?? Or the binaries?? Is free this project?? I want to join it too. I am also looking for people interested in see my blog. Here is my blog enriquemesa8080.blogspot.com. It is a great place where you can see interesting things.
Hello, and thanks for making this project available. What would need to change inside the compiler to support, say, 64-bit RISC-V or MIPS processors? Thanks.
Currently there's zero support for 64-bit types and types that are double the machine word size. Plenty of code needs to be rewritten and some written anew for 64-bit. There's MIPS64 support in gcc and clang. There's also gcc/clang for RISC-V, although I don't recall if there's any RISC-V 64-bit support. Can you use those?
I can, but with significantly greater difficulty, because they only support generating ELF files. As well, I was looking for a C compiler that I could easily recompile on the host eventually as well. I was just looking for a simpler way out.
Thanks for the reply!
You typically do compile the compiler on the host if your target is limited. :) As for ELF, it's not a difficult format to understand and process. It's much easier to transform it than to write a significant chunk of compiler code. :)
ELF on RISC-V is a moving target. Also, I've had the distinct displeasure of working with x86 ELF, and it's not anything I would call "not difficult." Generating it is fine for static binaries only; as soon as you involve dynamically linked libraries, complexity is literally through the roof.
Similarly, an ELF loader is a trivial exercise if, and only if, (1) your environment supports memory protection, and (2) your loader and linker origins match. If any of these preconditions fail to hold, you're again in a world of pain.
After spending about four days trying hard to work with ELF, I've come to the conclusion that this is dead wrong. ELF is an exercise in clinical insanity.
I think I'm going to ignore your advice and see what I can do to support SmallerC for my own project on my own. The worst that can happen is nothing comes of it. At best, you get a new back-end. :)
It looks like you're bound to remain in the world of pain for a while. Either you follow the moving target (perhaps, by contributing to gcc/clang) or you do something else. You can probably develop your own assembler and linker in the hope that the generated assembly code won't change unlike the binary formats into which it assembles and links. My compiler wouldn't help you even if it did support 64 bits. There's no assembler in Smaller C and the linker is static and x86-only. Either way some code needs to be written.
Yeah, you may roll out your own format, simpler than ELF and more suitable for your task.
Hi , I have downloaded the zip file from GitHub my Question is : can I compile to exe just with the package I have downloaded , or should I install Nasm ?
Hello. I have a problem compiling this fragment of code:
#define word_t uint16_t ;
#define dword_t uint32_t ;
/* sizeof(cabecera_t) == 0x20 */
word_t CS_Inicial ( void ) ; /* WORD, 16 bits */
dword_t dirInicial ; /* DWORD, 32 bits */
.................
/* here is the problem */
dirInicial = (((dword_t)(CSInicial())) << 4) + sizeof(cabecera_t) ;
Translation with Smaller C compiler and NASM:
============================================
smlrcc -huge -SI X:\SO1H\PRACT0 -S PROG_0.c -o PROG_0.asm
nasm -f elf -o PROG_0.o -l PROG_0.lst PROG_0.asm
smlrcc -huge -map PROG_0.map -o PROG_0.exe ..\..\so1hpub\obj\ajustusr.o
PROG_0.o ..\..\SO1H\obj\c0dh.o Y:\SmallerC\v0100\lib\lcdh.a
Run time disassembling:
=======================
.....
mov eax,0x0009a044 ; address of dirInicial
push eax
callf 0x900a:0001 ; ===> ax = 0x005E (after call to CS_Inicial)
shl eax,0x04 ; ===> eax = 0x009005E0 != 0x000005E0 expected !!!!!!!!!!
add eax,0x00000020
pop ebx
.....
The register eax finishes with 0x009005E0, but this value is wrong
because the high word of eax is no set to zeros before.
Thank you for Smaller C.
What are you trying to achieve with this code? It doesn't make much (if any) sense.
Ah, I'm starting to get it.
First, in mode(l)s other than tiny and small, the returned value is expected in EAX, the whole 32-bit register, not just AX (did you read the wiki? did you look at how C functions returning types smaller than 32-bit get translated into assembly? if you did not, why not?).
Second, did you notice tests/strtstp.c? Perhaps, the value you need is already available as &_start_allcode__?
I'm sorry. The problem is only in my function CS_Inicial because
not has a return instruction (C). The essence of my problem
is clear in the call to functionASM in the next code:
/* ----------------------------------------------------------------------- */
/* test.c */
/* ----------------------------------------------------------------------- */
#include /* uint8_t, uint16_t, uint32 */
asm("section .text") ;
asm("resb 0xFFFE") ; /* ====> address of dw > 0x10000 */
uint16_t w = 0xABCD ;
uint32_t dw ;
uint16_t function ( void ) {
return(0xABCD) ;
}
uint16_t functionASM ( void ) {
asm("mov ax,0xABCD") ;
}
int main ( void )
{
printf("\n &dw = 0x%08X \n", &dw) ; /* &dw > 0x10000 */
dw = (uint32_t)w ;
printf("\n dw = 0x%08X \n", dw) ; /* w Ok */
dw = (uint32_t)function() ;
printf("\n dw = 0x%08X \n", dw) ; /* function Ok */
dw = (uint32_t)functionASM() ;
printf("\n dw = 0x%08X \n", dw) ; /* functionASM ERROR */
getchar() ;
return(0) ;
}
/* compile:
smlrcc -v -dosh -S test.c -o test_asm.asm
smlrcc -v -dosh test.c -o test.exe
msdos test.exe
*/
/* one output:
C:\TEST>msdos test.exe
&dw = 0x0001B180
dw = 0x0000ABCD (Ok)
dw = 0x0000ABCD (Ok)
dw = 0x0001ABCD (ERROR)
*/
Secondly, &_start_allcode__ is very usefull, but in my case
first the program (a bootloader) is charged whithout reubication
in the address 0x00600. After, the program is moved to
address 0x90000 and reubicated calling there to function __start.
In this instant the symbol _start_allcode__ has the
value 0x90000 and not 0x00600. This is why I not use
&_start_allcode__, and I call to CS_Inicial.
Finally a question. ¿In Smaller C the reubication via __start
is static? ¿Is posible to reubicate a second time a program?
Thank you very much.
Repito de nuevo. Tienes que usar el registro entero (EAX) para "return <value>". Por ejemplo: mov eax, 0xABCD.
No entiendo el problema con el movimiento desde 0x600 a 0x90000. ¿Por qué no cargar el programa a 0x90000 directamente? Al otro lado, ¿no puede moverlo el cargador mismo? No me gusta la idea de que el programa se mueva por si mismo.
I want to study your code and project sir. but I don't know how to start, which file?
I am a newbie. sorry for that.
I'm afraid it may be far from the best choice. I strongly recommend studying the original Small-C by Cain/Hendrix (see https://en.wikipedia.org/wiki/Small-C) or SubC (see http://www.t3x.org/subc/) instead. There're books on them.
I am using the bcc compiler (Bruce C compiler) to write code for ELKS. Is it possible to compile small model programs for Linux? For ELKS I need 8086 code with one code and one data section of max. 64k each. This with 0x80 system calls.
With smaller C I would like to generate assembler code for NASM and compile that with NASM's "-f as86" option.
George
Currently, in the small memory model my compiler does not support integer/pointer types larger than 16-bit and there's no support for floating point in it either. Also, it generates some 80386 instructions (e.g. setcc, movsx, movzx). I'm not familiar with as86. Wouldn't it be easier to extend bcc instead?
Windows to DOS SmallerC cross compiler. Please.
Hello Mr. Frunze.
I am eager to use the compiler SmallerC for my operating
system of educational purposes, operating in real mode.
In principle, the idea is that the system kernel and drivers
use the huge model, while more normal user programs can simply
use the small model (CS < DS == SS).
A first problem is that the binaries are DOS applications (16
bits), so they can not directly run on Windows 64
bits. An alternative is to use a virtual machine as DOSBox, Qemu
or Bochs. Another alternative is to use the good MSDOS Player
of Takeda Toshiya (MSDOS.EXE).
However for me the preferred option would be to dispose of
SmallerC also as a cross compiler like the compiler Borland C
Version 5.02 (BCC.EXE) I'm currently using. To clarify
we should add a directory binw2d or so, where all executables
were Windows apps, but that would generate 16-bit code
ready to run on MSDOS.
Thank you for SmallerC!
Sorry, I don't quite understand what you want. bind and bindp contain DOS binaries of the compiler and they will work in DOS, 32-bit Windows and DOSBox. binw contains Windows binaries, which will work on any Windows from XP and up, 32-bit and 64-bit. binl contains Linux binaries. All those binaries (from bind, bindp, binw, binl) can compile for DOS, Windows and Linux. It's not like Windows binaries will only compile for Windows.
Btw, unreal mode support is in the works.
Sorry, I was wrong completely.
C:\SmallerC-master\V0100\binw\smlrcc -dosh hello.c -o hello.exe
It works perfectly on Windows (32 and 64) generating an executable
for MSDOS.
Evidently
C:\SmallerC-master\V0100\bind\smlrcc -dosh hello.c -o hello.exe
not work on Windows 64 by the specification itself, since it is
designed to run on MSDOS or Windows 32 (NTVDM). I could use these
binaries on Windows 64 with MSDOS Player (MSDOS.EXE):
C:\SmallerC-master\V0100\bind\msdos smlrcc -dosh hello.c -o hello.exe
but is preferable to use the Windows binaries as they work
both in Windows 32 and Windows 64.
Thanks for getting me out of my error. Now I can start working on my
project SmallerC operating system.
Thank you very much and apologize for my mistake because today SmallerC is
effectively a cross compiler from Windows (64) to MSDOS.
Hello Alexey, I'm trying to build a (un)real mode 16-bit operating system with SmallerC+nasm, but I'm having several problems. It's intended to work with nasm and -f bin format?
Thank you for this very useful project!
Normally, the core compiler (smlrc) is supposed to be invoked via smlrcc (the compiler driver), which executes smlrpp, smlrc, nasm and smlrl for you. smlrcc has a special option to show you what it executes, -v. The linker (smlrl) can generate a variety of executable formats: flat 16 or 32-bit, DOS .COM (tiny model), DOS 16-bit (small model) or 32-bit (huge model) .EXE, DOS 32-bit DPMI .EXE (a.out with a DPMI stub), Linux/ELF, Windows/PE, a.out. See the wiki on github for the details. Unreal/big real mode is not supported out of the box. Your options at the moment: use what is supported or hack it.
Unreal mode support is in the works if you're still interested.
I'm still interested. I'll take a look soon.
Thank you!
Alexey, I'm trying to set up SmallerC on ia64 Linux to be a MIPS cross-compiler, ideally to produce ELF binaries but for now I just need assembly output. I've added CFLAGS += -DMIPS to common.mk, but doing a make fails. Looks like it fails trying to build the library. I've installed the smlr* binaries into /usr/local. I thought I could at least get MIPS assembly by doing smlrcc -S hello_world.c, but that also isn't working. Can you advise me on what I need to do to get a cross-compiler up. Many thanks, Warren
The library contains quite a bit of x86 assembly code, so the failure to compile it as-is for MIPS is expected (look for the asm keyword). If you want to use it for MIPS, you'll need to remove x86- and DOS/Windows/Linux-specific parts of code or put them under some kind of ifdef and probably provide something for MIPS and the target OS instead (there are already a number of OS-specific macros that should give you some hints, most notably _DOS, _WINDOWS, _LINUX).
Similarly, smlrcc is written with x86 only in mind and it passes to smlrc options for x86 (e.g. -winstack, -seg32), which cgmips.c's GenInitParams() does not recognize. You can hack either smlrcc.c or smlrc.c/cgmips.c to work around these options.
The thing is, right now only the core compiler of Smaller C (smlrc.c) is truly MIPS-ready. On RetroBSD, where Smaller C produces MIPS code, there's a different library, a different preprocessor and a different compiler driver.
However, you should be able to preprocess your C code with gcc or smlrpp and then feed preprocessed code into smlrc to get MIPS assembly.
Indeed, to make smlrc.c target MIPS you need to compile it with -DMIPS.
If you tell me what you're trying to do, I might give you more relevant answers.
@Yann Guidon / YGDES You're mistaken. Your code won't even compile because you can't do bitwise operations on pointers. The increment operator in my and plopez' cases increments chars, not pointers to chars.
¿What is the reason of the SmallerC limitation in real mode / MSDOS
to work with the type long and far pointers when the
programming model used is TINY or SMALL?
This limitation is crucial when what you want is
write an educational 16-bit operating system.
At the moment I have one OS written for Borland C, and I
would rewrite it to SmallerC. I wanted
NASM assembler to use as instead of TASM.
TASM / TASM32 meets all my needs, but
I really attracts the possibility of using NASM
because is open source.
With SmallerC without the long type and without pointers
far I do not see how I can rewrite the OS in
SmallerC, for example with regard to the memory management
of processes.
Anyway, many congratulations for the project
SmallerC. I will be pending the progress of the project,
I think it's great.
Greetings.
The compiler is very simple by design and only supports primitive data types that aren't larger than the machine word. In the tiny and small memory models the machine word is 16-bit. That's what limits you to having 16-bit integers and 16-bit near pointers in these memory models. However, there's also the huge memory model, in which the machine word is 32-bit and so you can have 32-bit integers, 32-bit flat pointers (really, 20-bit, the compiler breaks down pointers into 16-bit segments and offsets for you behind the scenes) and 32-bit single-precision floats. You can use the huge memory model and greatly simplify things as you'll only need to deal with segmentation when using segmentation-oriented APIs of the BIOS. See the huge model implementation of DosDelete() in srclib/unlink.c for an example of passing a pointer as a segment and an offset to DOS int 21h.
Thank you very much for your instructions. The question of why there is no direct alternative in SmallerC to the declaration:
char far * ptr = MK_FP(0x0B800, 0x0000) ; / * Byte 0 video mem. */
while (TRUE) (* ptr) ++ ; / * Forever incr. byte 0 video mem. */
(that is valid in tiny or small Borland C programs) has been made clear by what has been explained in relation to the objectives of SmallerC design.
I will try to rewrite my bootloader (in C language) with huge programming model implemented in SmallerC, rather than in Turbo C (tiny / small models) as it has been doing with.
Very grateful for the SmallerC project and your attention.
Check this out:
// file: far.c
// compile: smlrcc -dosh far.c -o far.exe
int main(void)
{
char* ptr = 0xB8000;
while (1) (*ptr)++;
}
int main(void){
char* ptr = 0xB8000;
while (1) {
(*ptr)++;
ptr &= 0xFFFF;
}
}
(this would be more equivalent to the tiny/small version because the 16-bits pointers wrap around after 64K)
Pretty cool, I'll try to port this to my own architecture.
Anything I should know in advance?
I don't know what you (don't) know, so there's no simple answer here. However, if your arch is regular and 32-bit, you should probably model your code generator after the MIPS and TR3200 code generators.
How difficult it would be to make generic (maybe stack-machine based) backend for Smaller-C? Maybe something like a simple FORTH language. That way it would make porting to other backends easier. I am looking for a way to implement a compiler for a simple 8-bit processor (without much code optimisation) and Smaller-C seems to be good match for my needs. If there was some documentation on the backend generation... ;-)
See GenExpr0() in cgx86.c. It does just that. It uses (e)ax as the top of the expression evaluation stack and the CPU stack for the rest. Other registers (except (e)bp and (e)sp) are used as temporaries.
Do the file operations from dpstub.asm work if the DPMI host doesn't extend int 21h? I think that you have to use int 31h ax=300h for opening and reading the exe file.
If the host doesn't extend BIOS/DOS service functions on their respective software interrupts, it only means that you can't pass addresses to them in segment registers (or receive addresses in segment registers from them). Section 3.2 "Default Interrupt Reflection" of the 0.9 DPMI spec requires DPMI hosts to reflect most software interrupts to real mode. CWSDPMI does that. And I tested the uploaded code in DOSBox with CWSDPMI. File operations not requiring addresses (e.g. close, seek), work via int 0x21. Those that do require (e.g. open, read), indeed go through int 0x31 function 0x300, which then invokes int 0x21 in real (or v86) mode with proper addresses in segment registers. dpstub opens the file in real mode and reads its headers in real mode, so no int 0x31 there. It then loads the code and data sections from the file in protected mode and for that it does use int 0x31 function 0x300, see code below the load_buf label.
You are right, my apologies and thank you for your prompt reply. I had the wrong impression that you use int 21h open and reading file operations after switching to protected mode.
> The only thing like that that still remains is a pair of jumps from
> the function prologue code to to near the function epilogue code and
> back. They solve the problem of not knowing in advance how much stack
> space needs to be allocated for all the local variables. It's possible
> to fix it relatively cheaply and easily. Emit sub [e]sp, xxxxxxxxx in
> the prologue and then at the end of function compilation temporarily
> fseek() back to that sub [e]sp, xxxxxxxxx and overwrite xxxxxxxxx with
> the actual number. But this will require output to be a real binary
> file and not just any character device, e.g. console. If you want to
> discuss Smaller C, either send me e-mail or use this (it's about the
> only place I know of that accepts github credentials and can be used
> for discussions).
Why not use a local symbol and let NASM resolve the issue?
E.g.:
_funcname:
...
sub [e]sp, L__funcname_localsize
...
L__funcname_localsize equ xxxxxxxx
And that should be generic enough for it to work on any other multi-pass assembler...
To my knowledge, EQU's aren't generally guaranteed to work like global symbols (forward referenceable before being declared/defined). Likewise, not every assembler supports definition of absolute global symbols not tied to any memory location but rather having a constant compile-time value.
While not guaranteed, it is supported by most assemblers (IIRC, at least
NASM, YASM, A(3)86, TASM, MASM, (J)WASM, and FASM), and not difficult
to support, specially if implemented as symbols.
; nasm equ test
; file: nabs.asm
; assemble (with nasm 2.10): nasm nabs.asm -f elf
bits 32
bar equ 0xffff
mov eax, bar
mov eax, foo ; no error
foo equ 0x1234
; fasm equ test
; file: fabs.asm
; assemble (with fasm 1.71.22): fasm fabs.asm fabs.o
format elf
bar equ 0xffff
mov eax, bar
mov eax, foo ; error: undefined symbol 'foo'.
foo equ 0x1234
; tasm equ test
; file: tabs.asm
; assemble (with tasm 3.2): tasm tabs.asm
public fxn
code segment public para use16
fxn:
mov ax, foo ; no error
foo equ 1234h
code ends
end
; vasm equ test
; file: vabs.asm
; assemble (with vasm 1.7c): vasm -dotdir vabs.asm -Fvobj -o vabs.o
section .text
.global _f
_f:
mov %r0, foo ; no error
foo .equ 0x1234
; nbasmm equ test
; file: nbabs.asm
; assemble (with NewBasic Assembler 00.26.54): nbasmw nbabs.asm
.model tiny
.code
org 0x100
bar equ 0xffff
mov ax, bar
mov ax, foo ; no error
foo equ 0x1234
.end
# GNU as / RetroBSD's as .equ/.set test
# file: gabs.s
# assemble (with PIC32/MIPS GNU as): pic32-gcc gabs.s -c
# disassemble: pic32-objdump -d gabs.o
# assemble (with RetroBSD as): as gabs.s -o gabs.o
.text
.globl foo
.type foo, @function
foo:
# li $0, bar # GNU as Error: absolute expression required `li' / RetroBSD's as error: absolute value required
la $0, bar # the only instruction as lets to use with symbol defined later with .equ/.set
# subu $0, $0, bar # GNU as Error: absolute expression required `li' / RetroBSD's as error: cannot negate relocatable literal
# .equ bar, 0x1234 # GNU as OK / RetroBSD's as error: bad syntax
bar .equ 0x1234 # GNU as Error: unrecognized opcode `bar .equ 0x1234' / RetroBSD's as OK
# .set bar, 0x1234
It turns out, = must be used instead of EQU in FASM.
There's another problem. When generating code for Windows, the stack may need to be touched before subtracting the locals size from ESP. This is needed when there are 4KB or more worth of locals only, IOW, when there's more than one page crossing on the stack. Currently the stack is only touched when needed. If I remove the two jumps, I need to touch the stack always or I need to generate code to check whether the EQU'd symbol is large enough, which will cost mov + cmp + jcc + call.
Better ideas?
Would you mind to elaborate about that problem in Windows? why the need to manipulate the stack beyond the subtraction?
Description of the stack checking for Windows NT-based applications: https://support.microsoft.com/en-us/kb/100775
And I definitely don't want to drag any kind of %if L1234 >= 4096 into this. :)
Ah, I see. Well, what about emitting the whole prelude after the function and save a jump? still not ideal, but a little bit better.
Too little gain. I'll take a closer look at the ftell()/fseek()-based solution with overwriting the immediate operand of sub [e]sp, xxxxxxxxx in the output file. It has its little problems as well (e.g. NASM being picky about line terminations, there being no any kind of [lf]seek()/ftell() in lb.c and being tight on memory on RetroBSD).
I've uploaded the change and the unnecessary branches are no longer generated.
Become a member to follow this project and never miss any updates
By using our website and services, you expressly agree to the placement of our performance, functionality, and advertising cookies. Learn More
Can SmallerC 's assembler output 8088/80186 machine language? I'm thinking of trying this out to make 16-bit .EXE/.COM files that work on the 80186-bearing HP 200LX.