Decrement a given pointer.

G

glen herrmannsfeldt

(snip, I wrot)
68K was pretty solidly 32 bit from day one (registers, addressing,
operations, etc.) even though the original had a 16 bit ALU, and a
16 bit databus. All the 32 bit ops just required multiple cycles.

There are various ways to define the bitness of a processor, but
ALU size and data bus size are some of the popular ways.

Note that the 8 bit processors (8080, 6800, 6502) all had some
registers that were 16 bits.
The 68000 was missing a few things, like addressing physically only
generated 24 bit addresses, although the computed values in registers
was the full 32 bits (and IIRC, the high byte wasn't checked), and
only had 16x16 multiplication and 32x16 division.

Most 8 bit processors don't have multiply, so that doesn't
help much in the definition. For processors that do have
multiply, the multiplier/multiplicand width seems to me a
useful indicator of width.

-- glen
 
G

glen herrmannsfeldt

Gordon Burditt said:
Consider this loop to traverse an array of structures backwards:
struct huge *p;
struct huge bigarray[MAX];
/* WRONG! */
for (p = &bigarray[MAX-1]; p >= &bigarray[0]; p--) {
... do something with struct huge
pointed at by p ...;
}

(snip, I wrote)
Are you assuming that 'huge mode' is the same on all CPUs that
have a 'huge mode'? I'm not so sure that's true.

Well, I wrote that after writing some about what I used to do
on the 80286. Much of it is still true of IA32 processors, except
that no-one uses large or huge model.

I believe that the Watcom 32 bit compilers will generate large
model code, with 16 bit selector and 32 bit offset, such that
C pointers are 48 bits. It might be that OS/2 2.0 can support
such, but most other OS don't.

(snip, I wrote)
The term "N-bit processor" refers to about 9 things that rarely all
give the same value of N for a given processor. The whole idea
is silly and marketing hype. However, there usually is a significant
difference between what's called an 8-bit and a 32-bit processor, or
a 16-bit and a 64-bit processor.

At the time it came out, the 68000 was considered 16 bit, but
otherwise it is somewhere between the Intel 80286 and 80386.
That is, in actual use it is more powerful than the 80286,
and less than the 80386.

If I really had to give a bit count, I would use something like
the geometric mean of those 8 things that you mention, but some
people are bothered by it not being an integer.
The 68000 data registers were 32-bit. You can argue whether the
address registers were 24-bit or 32-bit. There was no built-in MMU
(that came with the 68010 and 68020) and no segment registers. The
C implementation used a 32-bit int. However, the data bus was 16
bits, and lots of the microcode did stuff in 16-bit pieces. It
didn't have a 32x32->64 multiply instruction (that came with the
68020). The instructions were multiples of 16 bits in size.

The first computer I owned used a Motorola 6809, which Motorola
called the missing link between 8 and 16 bit processors.
It did some things in 16 bits unusual for 8 bit processors,
and had an 8x8=16 multiply.

So, yes, the 68000 is more powerful than many other 16 bit
processors, but less than most 32 bit processors.

(snip)
The Tandy 6000 used a 68000 with about the simplest MMU you can get
- a base/limit register for user mode, and no translation for
supervisor mode. This system could not do virtual memory. Besides
having only one segment in the Tandy 6000, the 68000 could not
restart instructions after a bus error in all cases because it didn't
save enough state to do this. For example, if you execute an add
instruction with the source data in memory (and paged out) and
the destination is a register, when you get a page fault,
the the destination register has already been clobbered.

I programmed a Masscomp machine for some years, mostly in C.
I don't remember much about its MMU, though.

-- glen
 
S

Stephen Sprunk

However, the 68000 (as opposed to 68010 or 68020) had no MMU, so
the only way to tell if the address was invalid would be for the
processor to generate a memory read cycle on loading an address
register and see if the memory responded with an error or data.
This would have raised havoc with memory-mapped I/O, (where a read
cycle can *do* something, like clear a "data available" flag) and
made loading an address register slower. Also, I don't believe
that there was any warning that address registers couldn't be used
to hold arbitrary data if you ran out of data registers.

Worse, code often stored data in the high byte of pointers because the
68000 _didn't_ validate the address registers (even though it could
have), which meant that such code broke when ported to later models that
had >24 address pins.

AMD obviously learned from that mistake; sign extension is mandatory for
pointers in x86-64, though it can only be enforced at dereference since
there are no dedicated address registers.

S
 
S

Stephen Sprunk

Yes, they might do it. Do they have a C compiler?

Of course. Otherwise, it wouldn't be a useful example.
I am not sure at all what it does in huge mode, I never used that. In
large mode, the offset is in an ordinary register, and will wrap back
again before the dereference.

The segment selector has to exist when the value is loaded into a
segment register, but the offset isn't checked until an actual
dereference (load or store).

That's only true for selectors in x86 protected mode; in x86 real mode,
all segment values are valid--even if there is no memory behind them.

(I'm not sure how unreal mode works in this case, but that was never
officially supported anyway.)
In 32 bit protected mode, I believe it is usual to set the limit to,
as you note, (unsigned)(-1), but in 16 bit mode, no. In 32 bit mode,
you have the PMMU to validate addresses, in 16 bit the only
validation is the segment selector limit.

I believe it is usual to do pointer assignment without loading the
pointer into a segment register. As above, loading the segment
register would require the segment be valid.

There are few 16-bit (really 24-bit) x86 protected mode systems of any
significance, at least compared to x86 real and 32-bit protected modes.

It is unlikely that subtracting one from the offset would cause a
problem in either x86 protected mode, since the paucity of selector
values led all (AFAIK) implementations to pack multiple objects into
each segment, rather than using one segment per object as on the AS/400.

S
 
M

Michael Press

Joe Pfeiffer said:
Michael Press said:
Joe Pfeiffer said:
Given a pointer, p, can I set pm1 = p-1 and use pm1
without worrying that an implementation will object
or do other than what one expects? The idea is to
get offset one arrays, e.g.,

<snip>

Keith has already given what I expect is the right answer to your
question, but I'd go on to ask "why?". Unless there's a *really* good
reason, you should simply use the language as designed.

Indexing into a heap. The top of the heap is heap[1].
The two subsidiary nodes to heap[k] are
heap[2 * k] and heap[2 * k + 1].

Ah, should have thought of that one. While it's not quite as elegant as
the standard scheme, using heap[2*k + 1] and heap[2*k + 2] works just
fine with 0-offset arrays and and doesn't involve weird messing with
pointers.

And the immediate predecessor of heap[k] is heap[(k-1)/2].
The only neat way to run a heap is with a 1 offset array.
This is the only complex data structure I've come across
where 1 offsets are the elegant solution. I've rewritten
reams of FORTRAN code and it always comes out prettier with
0 offset arrays. Of course, things like an initial segment
of the prime numbers are best done with a 1 offset array.

0 1 2 3 4 5 ...
* 2 3 5 7 11 ...
 
G

glen herrmannsfeldt

(snip, I wrote)
Of course. Otherwise, it wouldn't be a useful example.

IBM has had much less interest in C than most other languages,
so I wouldn't have been surprised if there wasn't one.

(snip)
That's only true for selectors in x86 protected mode; in x86 real mode,
all segment values are valid--even if there is no memory behind them.

I switched to OS/2 (starting with 1.0) about as soon as I could.

The MSC 6.0 compiler came with both DOS and OS/2 install disks.
Besides the programs I was actually working on, I also compiled
GNU grep, and diff, and a vi-like editor called stevie.
All three were useful for program development.

When a program generated a fault, OS/2 would display a window
with the register contents, including the segment and offset
address of the fault instruction. (Or maybe the following
instruction.) Using the link map and compiler maps it wasn't
hard at all to find the problem.
(I'm not sure how unreal mode works in this case, but that
was never officially supported anyway.)
There are few 16-bit (really 24-bit) x86 protected mode systems of any
significance, at least compared to x86 real and 32-bit protected modes.

OS/2 1.x were much nicer to work with than DOS 5.x or 6.x, and not
so much later OS/2 2.x. OS/2 2.x will run either 16 bit (1.x) or
32 bit (2.x) executables.
It is unlikely that subtracting one from the offset would cause a
problem in either x86 protected mode, since the paucity of selector
values led all (AFAIK) implementations to pack multiple objects into
each segment, rather than using one segment per object as on
the AS/400.

To make debugging easier, I would directly call the OS/2 segment
allocator with the exact length needed, then add an offset of zero
and store into the large model pointer. The 64K limit wasn't much of
a problem at the time, and even now it wouldn't be. The program I
was working on at the time works on an arbitrary shaped 2D region,
so can have up to 16K columns of up to 8K elements.

Generating them that way, I get a hardware interrupt on either
fetch or store outside the array. (Unless it is so far out that
it wraps around back in again.)

-- glen
 
G

glen herrmannsfeldt

Stephen Sprunk said:
On 09-Jul-13 21:12, Gordon Burditt wrote:
(snip)
(snip)
Worse, code often stored data in the high byte of pointers because the
68000 _didn't_ validate the address registers (even though it could
have), which meant that such code broke when ported to later models that
had >24 address pins.

This was well known as IBM did the same thing with OS/360.
S/360 has 24 bit addresses in 32 bit registers. Core memory was
expensive enough that it seemed worthwhile to use the high byte
in many system control blocks.

S/370 extended to 24 bit virtual addresses, and XA/370 to 31
bit virtual addresses. Many control blocks have to stay
"below the line" (16M) so that they can be addressed.

But Apple used the high byte in their code, complicating the
change to 32 bit addressing in later systems.

Well, the early Macintosh were pretty memory limited, but still,
they should have known.
AMD obviously learned from that mistake; sign extension is
mandatory for pointers in x86-64, though it can only be
enforced at dereference since there are no dedicated
address registers.

-- glen
 
S

Siri Cruise

Robert Wessel said:
Certainly true, and some processors are hard to pin down no matter
what definition you use. OTOH, by far the most generally accepted
method is to use the size of the (most) general purpose registers. The
size of the ALU and memory bus are implementation details that don't
impact the ISA at all. Consider the S/360 line - IBM shipped machines

The old Cyber 170 PPs had 18 bit accumulator, 12 bit memory words PP memory, 60
bit path to CPU memory, and 6, 7, 8, or 12 bit characters.
 
J

James Kuyper

Of course. Otherwise, it wouldn't be a useful example.

That's not "of course" - people have brought up examples here before of
systems with peculiar features (such as 9-bit bytes) for which a
conforming C compiler would be possible, but no such compiler actually
existed.

For my purposes, the fact that an implementation that had such a feature
could be fully conforming is all that matters, whether one actually
exists is irrelevant to me. Other people don't want to bother worrying
about issues unless there are actual, commonly used fully-conforming
implementations of C where it matters.

....
It is unlikely that subtracting one from the offset would cause a
problem in either x86 protected mode, since the paucity of selector
values led all (AFAIK) implementations to pack multiple objects into
each segment, rather than using one segment per object as on the AS/400.

The existence of even one object per segment for which it would be a
problem is more than sufficient reason not to rely upon such
subtractions. Murphy's law guarantees that if you make a habit of
relying upon such code, it will be applied to such an object, sooner or
later.
 
G

glen herrmannsfeldt

Robert Wessel said:
On Tue, 09 Jul 2013 23:22:41 -0500, Stephen Sprunk
(snip)

(snip, someone wrote)
(snip where I disagree)
FWIW, this made me curious, so I hauled out an old 16-bit C compiler
(MSVC v1.50). Try as I might, with huge pointers, he always did the
pointer arithmetic without moving the results back into the segment
register (or for that batter the base register) until just before the
access. Basically using huge pointers inhibited most of the usual
pointer related optimizations.

Did MSVC still support OS/2? I remember the pre-MSVC compilers on
OS/2, but not the later ones.

Anyway, I believe that there are no arithmetic operations on
segment registers, so any pointer arithmetic has to be done
in other registers.

Following the usual C convention, I made my 2D arrays as an
array of pointers, and each one would address less than 64K bytes,
so large model was fine.

Which pointer related optimizations were you thinking about?

-- glen
 
G

glen herrmannsfeldt

James Kuyper said:
On 07/10/2013 12:22 AM, Stephen Sprunk wrote:
(snip)
That's not "of course" - people have brought up examples here before of
systems with peculiar features (such as 9-bit bytes) for which a
conforming C compiler would be possible, but no such compiler
actually existed.

There are rumors of a C compiler for the PDP-10, which use 9 bit
bytes. I never actually saw one, though.
For my purposes, the fact that an implementation that had such a feature
could be fully conforming is all that matters, whether one actually
exists is irrelevant to me. Other people don't want to bother worrying
about issues unless there are actual, commonly used fully-conforming
implementations of C where it matters.

I wouldn't require it to be commonly used, but it should at
least exist, though maybe an alpha or beta version would be enough.
The existence of even one object per segment for which it would be a
problem is more than sufficient reason not to rely upon such
subtractions. Murphy's law guarantees that if you make a habit of
relying upon such code, it will be applied to such an object, sooner or
later.

I believe even with one, malloc() will never return a pointer with
zero offset. To do that, I had to generate the pointers myself,
allocating segments directly. But it still works, as the offset
wraps, and then wraps back.

-- glen
 
G

glen herrmannsfeldt

Robert Wessel said:
(snip, I wrote)
(snip)
Certainly true, and some processors are hard to pin down no matter
what definition you use. OTOH, by far the most generally accepted
method is to use the size of the (most) general purpose registers.
The size of the ALU and memory bus are implementation details that
don't impact the ISA at all.
Consider the S/360 line - IBM shipped machines with identical
ISAs that had ALU sizes from 8 bits to 32 bits, and
memory bus sizes from 8 to 64 bits. Universally the S/360s are
considered 32 bit machines.

Well, S/360 was the first to have an "architecture". If you asked
for the bitness of the machines, separate from the architecture,
yes, the 360/30 is 8 bit (ALU and memory), the 360/40 8 bit ALU,
but 16 bit memory (data bus), and on up from there.

And when you are paying that much for a machine, you better
be getting 32 bits! Also, note that all have a 32x32=64 multiply,
and 64/32=32+32 divide (quotient and remainder). As noted,
the 68000 didn't have that. (Except maybe the 360/20.)

It might be now that we talk about the 68xxx architecture,
but maybe not, and not when only the 68000 was available.
Nor have we seen a (mainstream) x86 with a 32 bit memory bus
since well before AMD64, yet no one counts those pre-AMD64
machines as 64 bit. And again, there are some oddball CPUs
which are hard to measure.

Yes, the 80486 has a 32 bit data bus. One result was that
systems kept aligning data on 32 bit boundaries way too long.
For the pentium and later, 64 bit (double) values should be
aligned on 8 byte boundaries.
But those tended to be more than a bit special purpose and/or limited
in terms of the instructions that could manipulate them as a whole.
(snip)

I meant those only as examples of some (minor) holes in the
32-bittedness of the 68000 - the 68000 could add, subtract, and, or,
xor, shit, rotate, compare, load, store (and do most everything else
to) 32 bit values, but it couldn't multiply them.
Motorola, for some reason, emphasized the 16 bit nature of the
implementation when marketing the device initially, although they
pretty quickly changed the description to "16/32".

Yes, I remember back to when it was 16 bit.

-- glen
 
J

James Kuyper

On 07/10/2013 09:43 AM, glen herrmannsfeldt wrote:
....
allocating segments directly. But it still works, as the offset
wraps, and then wraps back.

Does the wrapped pointer compare as being less than the zero offset
pointer? If not, some code that expects p-1 < p to be true could
malfunction rather spectacularly. Of course, when p points at the first
element of an array, such expectations are unjustified, but code that
assumes that p-1 is safe and meaningful is precisely the topic under
discussion.
 
K

Keith Thompson

Michael Press said:
<snip>

Keith has already given what I expect is the right answer to your
question, but I'd go on to ask "why?". Unless there's a *really* good
reason, you should simply use the language as designed.

Indexing into a heap. The top of the heap is heap[1].
The two subsidiary nodes to heap[k] are
heap[2 * k] and heap[2 * k + 1].
[...]

To be clear, the word "heap" is used for two unrelated things, the data
structure Michael is talking about, and the region of memory from which
malloc() allocates.
 
C

Charles Richmond

glen herrmannsfeldt said:
[snip...] [snip...]
[snip...]

The 68000 is a 16 bit processor, but able to address more than 64K.
I don't remember quite how they did it.

Some time before the 68020, I used a 68010 system with a custom MMU.

ISTM that the Motorola 68000 had a 16-bit address bus, but 32-bit registers.
The 68000 had a 24-bit address, so it could address 16 meg of memory
directly... although I think it only brought to the outside world 23 bits
of the address. So the external address had to be even. Trying to read an
odd address caused an exception.

Also, ISTM that the modern IBM AS/400's had Power PC processors in them.
 
G

glen herrmannsfeldt

James Kuyper said:
On 07/10/2013 09:43 AM, glen herrmannsfeldt wrote:

(snip, I wrote)
Does the wrapped pointer compare as being less than the zero offset
pointer? If not, some code that expects p-1 < p to be true could
malfunction rather spectacularly.

Yes, it should wrap such thatit isn't less than zero for large model.
Huge model should decrement the segment selector, though that could
also wrap.

For protected mode huge model, it allocates a series of segment
selectors with a constant stride, which might be greater than one.
Make that eight, as the global/local and ring bits are also
in there.

But I pretty much don't write programs that depend on that kind
of pointer comparison. The program I was working on with OS/2 was
translated from Fortran, and so is even less likely than it otherwise
might be to do such pointer comparison.

But yes, those who do pointer comparison should be careful.

Also, the requirement to allow the pointer to go one past the end
means that one can't allocate the full 64K to a segment.
The popular malloc() implementations store data just before
the address returned, which would reduce the maximum even more.
Of course, when p points at the first element of an array,
such expectations are unjustified, but code that assumes that
p-1 is safe and meaningful is precisely the topic under
discussion.

-- glen
 
G

glen herrmannsfeldt

Keith Thompson said:
(snip)
Indexing into a heap. The top of the heap is heap[1].
The two subsidiary nodes to heap[k] are
heap[2 * k] and heap[2 * k + 1]. [...]

To be clear, the word "heap" is used for two unrelated things, the data
structure Michael is talking about, and the region of memory from which
malloc() allocates.

It took me a few seconds to notice that on the first post mentioning it.
It has been many years since I wrote a heapsort for an undergrad
programming assignment. Haven't done any heaps since.

Also, note that the former is often stored in the latter.
There should be a name for that.

-- glen
 
C

Charles Richmond

glen herrmannsfeldt said:
There are rumors of a C compiler for the PDP-10, which use 9 bit
bytes. I never actually saw one, though.

There is a C compiler on the XKL machine that runs TOPS-20 at the Living
Computer Museum in Seattle. It uses 9-bit bytes.
 
G

glen herrmannsfeldt

(snip, I wrote)
There is a C compiler on the XKL machine that runs TOPS-20 at the Living
Computer Museum in Seattle. It uses 9-bit bytes.

I have an account on that machine! I will have to log in and
try it.

The first DEC machine I ever had an account on was a KA-10
running TOPS-10.

-- glen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,076
Messages
2,570,565
Members
47,201
Latest member
IvyTeeter

Latest Threads

Top