Discussion:
error for small data model
(too old to reply)
Paul Edwards
2022-11-14 02:30:52 UTC
Permalink
What is the best way of producing an error here?

; push the psp now, ready for calling start

if @DataSize
push ds
else
error you must use a model with far data pointers
endif


Also, is there something like:

if not @DataSize
error ...
endif

???

Thanks. Paul.
R.Wieser
2022-11-14 08:03:17 UTC
Permalink
Paul,
Post by Paul Edwards
What is the best way of producing an error here?
That depends on 1) what kind of error you want (run or assmble time) 2) the
assembler you're using.

Looking at the example code it looks to me you want #2. In that case you
could check if your assember has something like "@err" (for errors) and
"@out" (for warnings).

And a suggestion : when one of the blocks contains an program (error) exit
than you do not need to use an "else". Just make sure that the error-exit
is in the first, main block :
- - - - - - - - - -
if not @DataSize
error you must use a model with far data pointers
endif
push ds
- - - - - - - - - -
notice the "not" after the "if"

Regards,
Rudy Wieser
Paul Edwards
2022-11-15 05:45:26 UTC
Permalink
Post by R.Wieser
Paul,
Post by Paul Edwards
What is the best way of producing an error here?
That depends on 1) what kind of error you want (run or assmble time) 2) the
assembler you're using.
Looking at the example code it looks to me you want #2. In that case you
And a suggestion : when one of the blocks contains an program (error) exit
than you do not need to use an "else". Just make sure that the error-exit
- - - - - - - - - -
error you must use a model with far data pointers
endif
push ds
- - - - - - - - - -
notice the "not" after the "if"
Hi Rudy, thanks for your reply.

Yes, I am after an assembly error.

But neither @err nor not seem to work on masm or wasm.

@err gives an error and "not" seems to be used as a variable
which is false.

However, I now accept small data pointers so I no longer
need to produce an error so it doesn't matter any more.

On the subject of small and medium memory model -
how is it possible for them to work? Functions can
accept pointers that originated from either the stack
or the data segment, or potentially even the code
section if literals are stored there.

Those are different address spaces. How did DOS compiler
vendors manage to get a sensible result?

Did they define the buffer in fwrite etc to be "far" to force
an override? That would work for the C library, but fall down
when it comes to user code.

Thanks. Paul.
R.Wieser
2022-11-15 09:03:03 UTC
Permalink
Paul,
Post by Paul Edwards
Yes, I am after an assembly error.
@err gives an error and "not" seems to be used as a variable
which is false.
My suggestions where based on Borlands Tasm32 (yeah I know, ancient). But
now you've specified which assembler you're using you stand a chance that
someone here uses the same and knows the answer to all three of the above.

Though you could try using "ifn" instead of "if not". Bottom line, take a
peek at your assemblers documentation.
Post by Paul Edwards
On the subject of small and medium memory model -
how is it possible for them to work?
...
Post by Paul Edwards
Those are different address spaces.
I had the same thoughts/problem. Its almost impossible to use stack-based
structures/strings when DS and SS are not the same. As such they are most
always (made) the same.

If you want to store data in / retrieve from the code segment of your
program (you should not, but its your choice) than you have no other option
than to make sure that all three segment registers are the same (locking you
into the smaller memory models).

That, or special functions that override the pointers default segment with
one of your own choice (for example, mov al,[cs:si] ).

Regards,
Rudy Wieser
Paul Edwards
2022-11-16 02:01:29 UTC
Permalink
Post by Paul Edwards
On the subject of small and medium memory model -
how is it possible for them to work?
...
Post by Paul Edwards
Those are different address spaces.
I had the same thoughts/problem. Its almost impossible to use stack-based
structures/strings when DS and SS are not the same. As such they are most
always (made) the same.
If you want to store data in / retrieve from the code segment of your
program (you should not, but its your choice) than you have no other option
than to make sure that all three segment registers are the same (locking you
into the smaller memory models).
What do you mean by "smaller memory models"?

The only ones that conceivably work are tiny, compact,
large and huge.

The ones that don't are small and medium.
That, or special functions that override the pointers default segment with
one of your own choice (for example, mov al,[cs:si] ).
But from memory small was the default memory model for
DOS C compilers.

How did they manage to make it work?

Thanks. Paul.
R.Wieser
2022-11-16 07:52:35 UTC
Permalink
Paul,
Post by Paul Edwards
What do you mean by "smaller memory models"?
The ones that keep DS and SS (and possibly CS) the same.
Post by Paul Edwards
But from memory small was the default memory model for
DOS C compilers.
How did they manage to make it work?
As long as they kept SS the same as DS that one is easy - as long as you do
not put (directly accessed) data in the CS segment. No extra work is
needed

If not ? Well, they would have to use segment-register overrides a lot.

Assume that SS differs from DS and you have some "local string variable" (on
the stack) which you want to display. You can't use INT 21h AH=09h
directly, as it expects the string to be in the data segment.

So, you have to either write funtions which grab a byte a time while
overriding the default segment for that register (mov dl,[ss:dx]), or wrap
the call into a bit of code which sets DS to be the same as SS and
afterwards restores it. (push ds | mov ax,ss | mov ds,ax | .... | pop ds).

Ofcourse, if you have DS different from CS and put strings into the code
segment which you want to display you would have the same problem. Heck,
the same happens when you allocate memory and want to access it : you mostly
have to juggle DS and ES around to get the "movs" instruction to do its
thing. :-)

In other words : Its not a rocket science needing problem, but its solution
is one that needs to be applied rigorously and correctly *all the time*.

Which is the reason that, when I'm writing Assembly, I seldom (if ever) feel
the need to pick a memory model where SS is different from DS. :-)

Regards,
Rudy Wieser
Paul Edwards
2022-11-16 08:46:44 UTC
Permalink
Post by R.Wieser
Paul,
Post by Paul Edwards
What do you mean by "smaller memory models"?
The ones that keep DS and SS (and possibly CS) the same.
There is no choice in that case - tiny is the only one.
Post by R.Wieser
Which is the reason that, when I'm writing Assembly, I seldom (if ever) feel
the need to pick a memory model where SS is different from DS. :-)
So long as you use far data pointers, ie compact, large and
huge, it should all be fine. Otherwise you only have the one
choice, above.

Unless I'm missing something.

BFN. Paul.
R.Wieser
2022-11-16 09:45:50 UTC
Permalink
Paul,
Post by Paul Edwards
There is no choice in that case - tiny is the only one.
As you have not mentioned anything about your requirements there is no way
for me to agree or disagree with the above I'm afraid ...
Post by Paul Edwards
So long as you use far data pointers, ie compact, large and
huge, it should all be fine.
And create the replacement or wrapper functions for INT 21h (and other) "one
segment only" calls.

As I said, its not rocket science. But you might find programming Assembly
in those models becoming tiresome rather quickly.

Regards,
Rudy Wieser
Paul Edwards
2022-11-16 13:33:51 UTC
Permalink
Post by R.Wieser
Paul,
Post by Paul Edwards
There is no choice in that case - tiny is the only one.
As you have not mentioned anything about your requirements there is no way
for me to agree or disagree with the above I'm afraid ...
I was trying to implement my C library (PDPCLIB) for MSDOS
when I encountered this issue.
Post by R.Wieser
Post by Paul Edwards
So long as you use far data pointers, ie compact, large and
huge, it should all be fine.
And create the replacement or wrapper functions for INT 21h (and other) "one
segment only" calls.
As I said, its not rocket science. But you might find programming Assembly
in those models becoming tiresome rather quickly.
I'm not trying to program in assembly, except for the minimal
wrappers.

I'm wondering how a C "hello world" could have worked in small
memory model C compiler (any vendor) when the "hello world"
string could come from either a buffer on the stack or a data
variable.

It somehow magically worked.

I didn't need to stuff around in my hello world application program -
someone else magically dealt with that.

Now I'm trying to understand magic.

Thanks. Paul.
R.Wieser
2022-11-16 16:16:07 UTC
Permalink
Paul,
Post by Paul Edwards
I'm wondering how a C "hello world" could have worked in small
memory model C compiler (any vendor) when the "hello world"
string could come from either a buffer on the stack or a data
variable.
It somehow magically worked.
:-) Thats the problem when you use a compiler, it hides a lot of the "magic"
from you.

When using a compiler it just silently switches to the library matching the
chosen memory model and "upgrades" your involved pointers accordingly.

Maybe you can tell your compiler to generate an assembly listing of the
program you're compiling, and see how it works ?

Regards,
Rudy Wieser
George Neuner
2022-11-16 17:14:03 UTC
Permalink
On Wed, 16 Nov 2022 05:33:51 -0800 (PST), Paul Edwards
Post by Paul Edwards
I'm wondering how a C "hello world" could have worked in small
memory model C compiler (any vendor) when the "hello world"
string could come from either a buffer on the stack or a data
variable.
It somehow magically worked.
I may be mis-remembering [it has been a long time], but I believe the
"small" model was 64K code, 64K data, DS = SS.

"tiny" was 64K code + data, CS = DS = SS.
"compact" was 64K code, 1MB data, DS != SS
"medium" was 1MB code, 64K data, DS = SS
"large" was 1MB code, 1MB data, DS != SS, 4:16 pointers
"huge" was 1MB code, 1MB data, DS != SS, 16:4 pointers
Post by Paul Edwards
I didn't need to stuff around in my hello world application program -
someone else magically dealt with that.
Now I'm trying to understand magic.
No magic, just either saving/restoring the segment register around
code that messes with it. Or liberal use of segment overrides.
Post by Paul Edwards
Thanks. Paul.
George
Paul Edwards
2022-11-18 01:36:24 UTC
Permalink
Post by George Neuner
Post by Paul Edwards
I'm wondering how a C "hello world" could have worked in small
memory model C compiler (any vendor) when the "hello world"
string could come from either a buffer on the stack or a data
variable.
It somehow magically worked.
I may be mis-remembering [it has been a long time], but I believe the
"small" model was 64K code, 64K data, DS = SS.
Thanks, that was the secret!

Does this code look right to you (it is working, but that
doesn't necessarily mean I have adjusted sp correctly):

(btw, "ifn @DataSize" didn't work)

https://sourceforge.net/p/pdos/gitcode/ci/master/tree/pdpclib/dosstart.asm#l70

; It appears that in the tiny memory model, you are still required
; to set ds to the same as cs yourself, presumably because ds is
; pointing to the PSP while cs is probably pointing to the beginning
; of the executable. DGROUP may also get the correct value, presumably
; zero. es is set to ds a bit later. And you need to set ss to that
; value too

if @Model eq 1
mov dx, cs
else
mov dx,DGROUP
endif

mov ds,dx

; In tiny, small and medium memory models, you need to set
; ss to ds (MSDOS will have set them to different values
; when it loaded the executable).
; ds and ss are the same so that
; near pointers can refer to either stack or data and still work

if @DataSize
else

mov bx,ss
mov ax,ds
sub bx,ax
mov cl,4
shl bx,cl

mov bp, sp
sub bp, bx
mov ss, dx
mov sp, bp
; And that null PSP thing needs to be redone
mov ax, 0
push ax

endif


My testing suggests that there is a problem with tiny memory
model, but the other memory models seem to work.

Thanks. Paul.
Terje Mathisen
2022-11-19 15:24:02 UTC
Permalink
Post by Paul Edwards
Post by George Neuner
Post by Paul Edwards
I'm wondering how a C "hello world" could have worked in small
memory model C compiler (any vendor) when the "hello world"
string could come from either a buffer on the stack or a data
variable.
It somehow magically worked.
I may be mis-remembering [it has been a long time], but I believe the
"small" model was 64K code, 64K data, DS = SS.
Thanks, that was the secret!
Does this code look right to you (it is working, but that
https://sourceforge.net/p/pdos/gitcode/ci/master/tree/pdpclib/dosstart.asm#l70
; It appears that in the tiny memory model, you are still required
; to set ds to the same as cs yourself, presumably because ds is
; pointing to the PSP while cs is probably pointing to the beginning
; of the executable. DGROUP may also get the correct value, presumably
; zero. es is set to ds a bit later. And you need to set ss to that
; value too
mov dx, cs
else
mov dx,DGROUP
endif
mov ds,dx
; In tiny, small and medium memory models, you need to set
; ss to ds (MSDOS will have set them to different values
; when it loaded the executable).
; ds and ss are the same so that
; near pointers can refer to either stack or data and still work
else
mov bx,ss
mov ax,ds
sub bx,ax
mov cl,4
shl bx,cl
mov bp, sp
sub bp, bx
mov ss, dx
mov sp, bp
; And that null PSP thing needs to be redone
mov ax, 0
push ax
endif
My testing suggests that there is a problem with tiny memory
model, but the other memory models seem to work.
Tiny is the one used by all .COM files, in those the binary has no
relocation info, it is just block copied into a newly allocated 64KB
block of memory, at offset 100h, and with those first 256 bytes (the
PSP) initialized by the OS.

In this model all segment registers are the same, i.e. CS=DS=ES=SS
because the OS and CPU are pretending to be a Z80 with no support for
extended/segmented memory addressing.

Terje
--
- <Terje.Mathisen at tmsw.no>
"almost all programming can be viewed as an exercise in caching"
Paul Edwards
2022-11-19 16:32:58 UTC
Permalink
Post by Terje Mathisen
Post by Paul Edwards
else
mov bx,ss
mov ax,ds
sub bx,ax
mov cl,4
shl bx,cl
mov bp, sp
sub bp, bx
This should have been an add, not a subtract. :-)
Post by Terje Mathisen
Post by Paul Edwards
mov ss, dx
mov sp, bp
; And that null PSP thing needs to be redone
mov ax, 0
push ax
endif
My testing suggests that there is a problem with tiny memory
model, but the other memory models seem to work.
Tiny is the one used by all .COM files, in those the binary has no
relocation info, it is just block copied into a newly allocated 64KB
block of memory, at offset 100h, and with those first 256 bytes (the
PSP) initialized by the OS.
In this model all segment registers are the same, i.e. CS=DS=ES=SS
because the OS and CPU are pretending to be a Z80 with no support for
extended/segmented memory addressing.
That's only if you deliberately choose to produce a COM file,
and also you need an org 100h in the startup code.

Otherwise, tiny memory model produces a perfectly fine
normal MSDOS executable, and the entry point can be
anywhere.

My hello world program, linking in the whole C library
in tiny memory model is this size:

Memory size: e110 (57616.)

_TEXT CODE DGROUP 0000:0000 000053c6
CONST DATA DGROUP 053c:0006 000002be
CONST2 DATA DGROUP 0568:0004 00000000
_DATA DATA DGROUP 0569:0000 00000668
_BSS BSS DGROUP 05cf:0008 00006412
STACK STACK DGROUP 0c11:0000 00002000

Entry point address: 0000:02fa

Which is cool.

I have a lot of BSS because of 6144-byte buffers for files which
gave a great speedup on a real XT in the 1990s.

My tiny mode doesn't support malloc though, so you can't
do much. Doesn't support parameters either. :-) Large is
fine though. It was interesting to see.

PDOS/86 now has INT 3 and INT 1 support as a result of
debugging that.

BFN. Paul.

Terje Mathisen
2022-11-16 12:09:43 UTC
Permalink
Post by R.Wieser
Paul,
Post by Paul Edwards
What do you mean by "smaller memory models"?
The ones that keep DS and SS (and possibly CS) the same.
Post by Paul Edwards
But from memory small was the default memory model for
DOS C compilers.
How did they manage to make it work?
As long as they kept SS the same as DS that one is easy - as long as you do
not put (directly accessed) data in the CS segment. No extra work is
needed
If not ? Well, they would have to use segment-register overrides a lot.
Assume that SS differs from DS and you have some "local string variable" (on
the stack) which you want to display. You can't use INT 21h AH=09h
directly, as it expects the string to be in the data segment.
So, you have to either write funtions which grab a byte a time while
overriding the default segment for that register (mov dl,[ss:dx]), or wrap
the call into a bit of code which sets DS to be the same as SS and
afterwards restores it. (push ds | mov ax,ss | mov ds,ax | .... | pop ds).
Ofcourse, if you have DS different from CS and put strings into the code
segment which you want to display you would have the same problem. Heck,
the same happens when you allocate memory and want to access it : you mostly
have to juggle DS and ES around to get the "movs" instruction to do its
thing. :-)
In other words : Its not a rocket science needing problem, but its solution
is one that needs to be applied rigorously and correctly *all the time*.
Which is the reason that, when I'm writing Assembly, I seldom (if ever) feel
the need to pick a memory model where SS is different from DS. :-)
For my word count code I needed a 64 KB lookup table, so I put that in a
separate ES segment, then I could put another largish table in the SS
segment and access it with BP, leaving disk buffers etc in DS.

Terje
--
- <Terje.Mathisen at tmsw.no>
"almost all programming can be viewed as an exercise in caching"
Loading...