You can't really detect the memory model the C code was compiled with at runtime. I suppose you check some sort of variable that indicates what model was used but you'd be constantly testing it in your assembly code making your code horribly inefficient. A much better way to handle multiple memory models with a library is to assemble a separate version of each function for each memory model. Fortunately this doesn't mean you need write a separate function for each memory model. You can use certain features of your assembler, assuming you're using TASM or other MASM derived assembler, so that you only write each function once.
Handling far and near RET
The specific problem of RETN or RETF can be handled more or less automatically. Normally you would just use the RET instruction, and the assembler will automatically pick the correct near or far return instruction based on how the procedure you use them in is defined. For example:
nearproc PROC NEAR
ret ; generates opcode C3, near RET
nearproc ENDP
farproc PROC FAR
ret ; generates opcode CB, far RET
farproc ENDP
The .MODEL directive
Normally if you don't specify NEAR or FAR, the default is NEAR, but this can be changed by the .MODEL directive:
.MODEL SMALL
.CODE
smallproc PROC
ret ; generates opcode C3, near RET
smallproc ENDP
.MODEL LARGE
.CODE
largeproc PROC
ret ; generates opcode CB, far RET
largeproc ENDP
Specifying a calling convention
The .MODEL directive can also set a default calling convention when using the assemblers facilities for handling arguments to functions:
.MODEL COMPACT, C
.CODE
memcpy PROC dest:PTR, src:PTR, len:WORD
push cx
IF @DataSize ; FAR data model
push ds
push es
les di, [dest]
lds si, [src]
ELSE ; NEAR data model
mov di, [dest]
mov si, [src]
ENDIF
mov cx, [len]
cld
rep movsb
IF @DataSize ; FAR data model
pop es
pop ds
ENDIF
pop cx
ret
memcpy ENDP
Because the "C" language was specified with the .MODEL directive, the assembler handles generating the necessary prologue (push bp
mov bp, sp
) and epilogue (pop bp
) and figures out where the arguments to function live on the stack relative to BP. It automatically handles the fact that arguments will have different sizes depending on whether the code is using a near or far data model. It also automatically adds an underscore (_
) to the name of the function, so the actual symbol being defined is _memcpy
.
It doesn't however handle picking the instructions needed to load the arguments. As you can see this code is conditionalized on the @DataSize
predefined symbol. This symbol is set to 0 for near data models and to 1 for far data models. There's also @CodeSize
predefined symbol you can use to determine the code model.
The .MODEL directive and the @DataSize
and @CodeSize
symbols it sets are all you need to not only ensure the correct RET instruction is used, but also to lets you handle the fact that your code will need to change in order accommodate the different memory models.
This is true whether you use the argument handling and automatic prologue and epilogue and other similar facilities of the compiler. These can be a double edged sword though as the code that they generate isn't always optimal. Instead of specifying a calling convention with the .MODEL directive you can also specify it on a procedure by procedure basis as an additional parameter in the PROC directive. Use NOLANGUAGE to tell the assembler not to do any of this code generation for the current procedure.
How to pass a memory model on the command line
Now there's one problem left, how to tell the assembler which memory model to use. The simple way would be define a symbol using the /D
command line option, giving the name of the memory model you wanted:
ml /c /DMemModel=MEDIUM memcpy.asm
You could then use MemModel
with the .MODEL directive:
.MODEL MemModel, C
The problem with this is that only works with MASM 6. With TASM or MASM 5 the /D
option works differently. These assemblers would define MemModel
as a symbol that's equal to the symbol MEDIUM
, where as MASM 6 defines MemModel
as a text macro that expands to MEDIUM
. Since TASM and MASM 5 would define MemModel
as a symbol, you'll get an error because the .MODEL directive doesn't accept symbols.
Unfortunately, if you're using either of these older assemblers you need something more complicated. The command line is almost the same, but note that the quotes are important:
tasm /ml /DMemModel="MEDIUM" memcpy.asm
masm /ml /DMemModel="MEDIUM" memcpy.asm;
(The /ml
options are there so the assemblers define _memcpy
in lower case, otherwise they'll convert it to uppercase _MEMCPY
.)
For this work, you'll need this code at the start of your assembly source:
IFIDNI <"TINY">, %MemModel
.MODEL TINY, C
ELSEIFIDNI <"SMALL">, %MemModel
.MODEL SMALL, C
ELSEIFIDNI <"MEDIUM">, %MemModel
.MODEL MEDIUM, C
ELSEIFIDNI <"COMPACT">, %MemModel
.MODEL COMPACT, C
ELSEIFIDNI <"LARGE">, %MemModel
.MODEL LARGE, C
ELSEIFIDNI <"HUGE">, %MemModel
.MODEL HUGE, C
ENDIF
To save having to having to repeat this code in every source file you can put it in an include file:
INCLUDE memmodel.inc