31

By the time the C language started to gain popularity outside of the PDP-11 circles (mid-1970s), mainframes with "weird" word sizes, and no capability to address individual bytes efficiently were still in use: for example, Wikipedia mentions

  • CDC 6600 (1964) with 60-bit words using 6-bit chars,
  • PDP-6/PDP-10 (1963/1966) with 36-bit words using 6-bit or 9-bit chars,
  • Electrologica X8 (1965) - the chronologically last one in the table with a non-power-of-2 word size - with 27-bit words using 6-bit or 7-bit chars.

CDC 7600 (1969) is not listed but it had the same architecture as CDC 6600.

Were there successful C language implementations (commercially or widespread informally) for those architectures, and how was it done?

Incidentally, on the BESM-6 (1967) with 48-bit words using 8-bit bytes, the C language was but a toy, implemented as Johnson's PCC porting experiment, and hasn't reached a quality of implementation good enough for practical use.

Leo B.
  • 19,082
  • 5
  • 49
  • 141
  • 5
    Wikipedia says "a [C] compiler for the Honeywell 6000 was written within the first year of C's history". This system used 9-bit bytes, 18-bit addresses, and 36-bit words. However, it is possible that such an early version of C may not have had standard data types like it does today (char, short, int, long, etc.) and thus "weird" word sizes may not have been an issue at all. But I don't know enough about early C to say for sure. – snips-n-snails Aug 04 '17 at 00:14
  • For 36-bit words it is relatively easy: the number of bytes, whether 9-bit of 8-bit, per word is four, which is a power of 2, allowing to represent pointers uniformly in terms of bytes and shifting right by 2 before dereferencing word-sized objects, if non-trivial casting between different types of pointers appears to be a problem. CDC and Electrologica are more interesting specimens in that regard. – Leo B. Aug 04 '17 at 00:28
  • 11
    Why do you think the word size has to be a power of two? – user253751 Aug 04 '17 at 05:57
  • 2
    What makes you think it would be hard to port C to any of these, so called, weird architectures? About the only real difficulty I can think of would be would be in implementing the types in stdint.h – JeremyP Aug 04 '17 at 08:40
  • @immibis I don't think that. For an efficient implementation, the word size has better be a power of 2 multiple (otherwise byte addressing is slow) of 8 or slightly greater (if much greater, it is wasteful) bits, or be slightly greater than such a value. 36 satisfy this; 27 or 60 don't. – Leo B. Aug 04 '17 at 08:45
  • @JeremyP It won't be hard to do as a toy implementation, but hard to do efficiently. What byte size would you use for a CDC with its 60-bit words? – Leo B. Aug 04 '17 at 08:47
  • @LeoB.What do you mean by "efficiently"? I'd go with a 12 bit byte on the CDC6600, but that wouldn't concern me so much as the 1's complement native integer. Note that all the issues you think would affect a C implementation would have been problems for other languages too. There's no reason to suppose a C implementation would be any more "toy" than the Fortran implementation. – JeremyP Aug 04 '17 at 08:58
  • The really strange one is the AS400 port - it can be forced to use the RPG pointers which are 32 bytes (not bit) wide – cup Aug 04 '17 at 12:30
  • 1
    @LeoB. You are assuming the C implementation had to be "efficient" to be useful. The first C compiler on the Cray (word-addressable, word size 64 bits) was horribly inefficient at handling char data, and had no vector functionality at all. Nobody would have even considered it for running compute-intensive work. But it was good enough to port a version of Unix (UNICOS) to replace the original COS operating system. With a typical "real life" workload, the amount of computer time spent running the operating system was only of the order of 1% or less, so the inefficiency didn't matter much. – alephzero Aug 04 '17 at 14:39
  • Back at 1974, our school had a PDP/8 with 12-bit word size - but IIRC, there was no C compiler for it. And I once saw C code prepared for a really strange machine (I don't remember the type, maybe a Data General?) where the numeric value of a byte pointer and an int pointer were different when pointing to the same memory cell. So the code had lots of pointer casts, internally multiplying or dividing the pointer value by 2 or 4. – Ralf Kleberhoff Aug 04 '17 at 14:56
  • @JeremyP 5 bytes per word? Dividing by 5 every time you need to read or write a byte? In Fortran and Pascal, packed arrays are accessed, if an implementation allows it, via indexing, and the problem of pointer representation or dealing with strings of arbitrary word alignment doesn't exist. – Leo B. Aug 04 '17 at 16:24
  • @alephzero That inefficiency was somehow mitigated by not having to perform actual division to convert byte pointers to word addresses. – Leo B. Aug 04 '17 at 16:31
  • @LeoB Have you any evidence for that assertion? I actually USED that C compiler (and Cray assembler!). Storing a single byte from a register to memory required a sequence of 5 machine code instructions, for example. And of course you don't need to do an "actual division" (on any machine, not just a Cray) to divide by a power of two - just use a shift instruction! – alephzero Aug 04 '17 at 17:20
  • A significant amount effort was put into handling 8-bit characters and character strings efficiently in Cray Fortran (including vectorising operations on "long" character strings). But the pointer-based syntax of C code broke those optimisations, until a lot more work was done on semantic analysis of the code. The Cray 1 must have been one of the few machines where character handling was faster in Fortran than in C! – alephzero Aug 04 '17 at 17:27
  • @alephzero Which assertion? My point is that if a word contains a number of bytes that isn't a power of 2, which can happen on a machine with a "weird" word size, you either have to use a division instruction, or do other non-obvious tricks for char pointer operations. You're right about Fortran; in my comment to JeremyP I've mentioned that Pascal and Fortran implementations don't face as many problems as a C implementation. – Leo B. Aug 04 '17 at 17:58
  • FYI, Dennis M. Ritchie's article "The Development of the C Language" talks about how C was typeless until around 1977. – snips-n-snails Aug 04 '17 at 19:06
  • On the Honeywell 6000 at my college, there was a compiler, "B". We were told that it was a predecessor of what became C. – zarchasmpgmr Aug 04 '17 at 23:30
  • @LeoB. What makes you think that whatever method Fortran uses for indexing bytes can't be used for a C implementation? A pointer in C does not have to be a memory address. – JeremyP Aug 06 '17 at 16:06
  • Here's the definitive history from the horses mouth so to speak https://www.bell-labs.com/usr/dmr/www/chist.html – JeremyP Aug 06 '17 at 16:12
  • @JeremyP I've read that before. It doesn't cover the efficiency issues arising when a word doesn't contain a number of bytes equal to a power of 2. – Leo B. Aug 06 '17 at 19:14
  • @JeremyP The Fortran method of indexing bytes (base + offset) can be used to represent C pointers but it will require a branch for increment/decrement (likely still cheaper than division but more costly in code size). That's what my question is about: which method was actually used? – Leo B. Aug 07 '17 at 18:55
  • @traal: read again. B was typeless, but 'new B' in 1971 added a basic concept of typing, and 'neonatal C' 'by early 1973' had most of the types we know today. What he says for 1977 is that there were still signs of the typeless origin. – dave_thompson_085 Sep 03 '17 at 01:24
  • 2
    @JeremyP: standard C (and C++) has wording that is for support of non 8-bit bytes, non power of two types and even some non-twos-complement stuff. As for stdint.h, the exact bit types are an option, they are not required by a conformant implementation, which is why you have least_XX bit types. – PlasmaHH Jan 18 '18 at 13:43
  • 1
    @PlasmaHH - right, [u]intNN_t is not strictly required for ISO C compliance, although it is worth noting that they are a requirement for POSIX-1 (IEEE 1003.1-2001) compliance. – Jules May 22 '18 at 20:15
  • @phuclv That link doesn't appear to say what was the minimal addressable memory unit, and how the character arrays are implemented. – Leo B. Jun 25 '18 at 21:22
  • @LeoB. those are just examples and not the answer to how they pack the char arrays. But you can see in the documents that most TI DSPs have 16-bit char which is also the addressable memory unit, and Motorota DSPs have 16 or 24-bit minimum addressable unit (MAU) of 8/16/24 depending on variants: Note that the size is expressed in MAUs... All pointers are 1-word pointers. (24-bit for the DSP5600x and DSP563xx, 16-bit for the DSP566xx – phuclv Jun 26 '18 at 02:12
  • @phuclv When memory is plenty, using one MAU per char this is the most convenient thing to do; that was not the case 40-50 years ago. From your link it is unclear if there was a C compiler for CDC or Unisys. – Leo B. Jun 30 '18 at 01:30

5 Answers5

47

Word size is not really an issue in implementing C on a given architecture. There's no requirement that C types have a power-of-two size. There's only a problem if the word size is too small, but this can be simply worked around by using multiple consecutive words to represent C types like int and long. There's a long list of C compiler implementations for 8-bit and 16-bit targets that do this.

The problem with porting C is with the size of a byte, which C defines as the smallest addressable unit and the size of the char type. The size of a byte doesn't need to be a power of two (for example, there was a fairly early port of C to the 9-bit byte, 36-bit word Honeywell GCOS mainframes), but it does need to be a least 8 bits. If it's smaller than that, then it's awkward to work around, and as far I know C has rarely if ever been ported to machine with a natural byte size smaller than 8 bits.

If the size of a smallest addressable unit is significantly larger than 8-bits, then this also causes problems. While there's no conformance issue with this, having a such a large byte size would mean that ordinary ASCII (or EBCDIC) character strings end up wasting a lot of memory. C compilers that were implemented on systems where the smallest addressable unit was 16 bits or larger made char an 8-bit (or maybe 9-bit) type and thus made a C byte the same size.

In order for the C compiler to make the size of a C byte be smaller than the target machine's smallest addressable unit, the compiler needs to generate special code to extract the smaller C bytes out of the larger native bytes. This also means that the C compiler needs to handle pointer to char (char *) types differently. Typically, they were larger than other pointer types in order to store extra bits denoting which part of a native byte was being pointed to. This, in turn, meant that the pointer to void types (void *) also needed to be larger than most other pointer types.

All of this is supported by the C standard. A byte is required to be at least 8 bits in size. The size of char is required to be one byte. The size of short, int, long, and long long are required to be at least 16 bits, 16 bits, 32 bits, and 64 bits, respectively. All of these types are required to be integer multiples of the byte size. There's no requirement that any of these types be a power of two in size. Pointer sizes and representations are allowed to vary according to the type being pointed to.

If you want to write strictly conforming C code that works on any compliant C compiler ever made, then you can't depend on the size of types being nice, even powers of two. You should also be prepared to deal with padding bits, trap representations, integer overflow exceptions, and non-twos-complement arithmetic.

Cody Gray - on strike
  • 1,554
  • 14
  • 21
  • given that a char is, well, used often to store characters, didn't NOT defining a char as 6 bit on a platform that used 6 bit characters cause immense problems? – rackandboneman Aug 03 '17 at 20:37
  • 1
    @rackandboneman I'm not sure if C was ever ported to such a platform. –  Aug 03 '17 at 20:42
  • This is not an answer to my question. – Leo B. Aug 03 '17 at 22:51
  • 4
    @LeoB. I directly answered your question in the first two sentences. Your question is based on the false premise that a non-power-of-two word size would be an obstacle to implementing a C compiler on a given platform. –  Aug 03 '17 at 23:24
  • 1
    My question is abut actual implementations and their specifics. I could have written all the generalities myself. – Leo B. Aug 03 '17 at 23:27
  • 5
    @LeoB. The false premise of your question means that there's no reason to go into specific detail about how implementations overcame an obstacle that doesn't exist. –  Aug 03 '17 at 23:43
  • 1
    A difficulty exists. If native pointers cannot represent char *, casting between pointers to different types ceases to be purely notional, which could have been overlooked in "portable" compilers. Were the compilers written from scratch, or painstakingly adapted from the Johnson's PCC? If the number of chars per word is not a power of 2, the char pointer representation and arithmetic must be tricky to be efficient. Did CDC use 6 10-bit bytes per word, or 7 8-bit bytes, or, wastefully and likely lacking compatibility with storage formats, four 15-bit bytes? These are the details I'm after. – Leo B. Aug 03 '17 at 23:57
  • 3
    @LeoB. This isn't a programming site, so I don't think it's appropriate to go in to great detail about how exactly various different implementations overcame actual byte and word size related issues. Moreover just going into detail about one specific implementation could easily bump against the limit of what can be answered here. I don't think this is place to explain how to implement a C compiler on some "weird" architecture let alone a wide range of them. –  Aug 04 '17 at 00:13
  • 1
    @RossRidge I'm not asking to quote any code or to go into compiler implementation specifics; the byte size of a few retro-computers used in their C implementations, and whether their C compilers were adaptations or re-implementations from scratch is hardly "great detail". And what "wide range" of architectures do you have in mind? – Leo B. Aug 04 '17 at 00:20
  • @rackandboneman On Honeywell/GE systems, C used 9-bit characters "natively" but had the ability to pack 6-bit characters into a word using \STRING`` constants. The original C compiler has an error message for that syntax not being valid in Unix C. – Random832 Aug 04 '17 at 04:16
  • 4
    Just a nitpick: There was certainly no void (and hence no void *) back then. char * played the untyped pointer role, and return types of functions (where void proper comes into play) were ignored anyway with respect to the type system. – Peter - Reinstate Monica Aug 04 '17 at 15:58
  • @LeoB.: On systems where the smallest addressable object is int, a char* would often combine an int* with an int that would identify a char-size chunk within it. It would be useful if the C Standard would recognize a category of implementations where all data pointers have compatible representations, since most common implementations work that way, but as far as the Standard is concerned, void* and character pointers must use the same representation as each other, all struct pointers must use the same as each other, and likewise union pointers, but otherwise anything goes. – supercat Jul 29 '21 at 21:19
20

There is some code left over in the pcc codebase showing how the GCOS compiler (for the Honeywell/GE 6000 series) worked, it used 9-bit (ASCII, most likely) characters natively, but supported 6-bit BCD characters with `STRING` syntax for encoding them in the source.

The lexer support for these string tokens can be seen at old/pcc/mip/scan.c, the strtob function it calls is not present in the code, but may have been a library function available on those systems. According to this B language reference manual (B was an earlier language by Ken Thompson and Dennis Ritchie), B had similar syntactic support and dual BCD/ASCII strings, and the print function (like C's sprintf) could be used for converting from BCD to ASCII.

On further refinement of my google searches, I was able to find a C manual for GCOS. BCD strings could be printed with %_s (and had been %b in early versions), and various library routines for working with the BCD characters (which are packed six per 36-bit word) are documented.

Random832
  • 496
  • 2
  • 6
7

An interesting contemporary example is the Analog Devices SHARC 21488 and friends.

sizeof(char) == sizeof(short) == sizeof(int) == 1

This is fully compliant with the C standard (But not POSIX), and given that the usual use for these parts is as special purpose audio processors the extra space for character strings is not a problem.

Stephen Kitt
  • 121,835
  • 17
  • 505
  • 462
Dan Mills
  • 481
  • 2
  • 3
7

The TI TMS34010 Graphics System Processor was capable of addressing memory in any particular transfer size you liked (2, 4, 6, 8, whatever bits). This was a natural outgrowth of its focus on graphics, where different color depths might be represented by different #s of bits per pixel.

The C implementation simply deviated from the standard, and had the pointers be 32 bit, BITWISE pointers. The bus was 32 bits, and if your access straddled a 32 bit boundary, the CPU would simply do the extra memory accesses to make it transparent to the code.

This did mean that you had to pay attention to what you were doing when you were doing pointer math, but other than that it was quite pleasant to use.

Leo B.
  • 19,082
  • 5
  • 49
  • 141
Michael Kohne
  • 863
  • 6
  • 8
  • 1
    So a char * increment operation had to add 8 instead of 1, etc.? That's cool. – Leo B. Aug 04 '17 at 16:37
  • 2
    The only reason I can see that such an implementation would need to deviate from the Standard would be if it wanted to allow code to declare arrays of objects smaller than 8 bits, and code did in fact declare such arrays and coerced the addresses of elements to char*. Such array types might be useful even on "normal" platforms, if the Standard allowed for arrays whose elements could only be accessed via [], and limiting arrays in such fashion would so far as I can tell allow an implementation to uphold the Standard's requirements for pointers. – supercat Dec 14 '17 at 18:24
3

AT&T's 1984 official The C Programming Handbook recognizes one compiler with 9-bit chars.

Plus another compiler in EBCDIC!

Simply put, there is nothing inherent in C that would require word size to be a power of 2. The reason we expect it today is because all modern systems do so, in turn related to computing history.

In one word: packaging.

Summary

  1. There is no absolute reason why word size has to be a power of 2.
  2. For mainframe/minicomputers, the size of an IC package is irrelevant. The computers are built from individual low-level logic chips, so they could (and often did) have strange word sizes.
  3. The first microprocessor was 4 bits because of packaging constraints.
  4. For several reasons, microprocessor word size has grown by doubling. Thus, microprocessor word sizes have become powers of 2.
  5. Microprocessor speed was severely limited by packaging limitations, until they started using larger non-DIP packages. Then microprocessor speed surpassed mainframe/minicomputers, leading to the demise of the latter.
  6. Because nearly every computer today is built from microprocessors, we mistakenly assume that word size always must be a power of 2.

(1) No C requirement for a power of 2.

This manual was written in 1984 by AT&T Bell Labs to train programmers to write in C. It was published in the time between the 1st and 2nd editions of Kernighan and Ritchie. C Programmer's Handbook cover .

Compilers have been written for at least one platform with a non-power-of-2 word size. Examine the entry for the Honeywell 6000: C Programmer's Handbook page 8 .

Also note that C doesn't have to be in ASCII; the entry for the IBM 360/370 shows EBCDIC.

The 2nd edition of K&R reinforces this point:

Each compiler is free to choose appropriate sizes for its own hardware, subject only to the restriction that shorts and ints are at least 16 bits, longs are at least 32 bits, and short is no longer than int, which is no longer than long.

Kernighan and Ritchie, The C Programming Language, 2nd edition, 1988. p. 36

Do characters have to be individually addressable by hardware? No, there have been plenty of systems that load/store whole words and then let the compiler do the magic. On the Honeywell 6000, sometimes programmers would pack 5x 7-bit mixed-case ASCII, or 6x 6-bit upper-case ASCII, into one 36-bit word. This was especially popular in COBOL programs to save memory.

Do larger data sizes have to be a whole multiple of smaller data sizes? In theory, no. You could theoretically have 9 bit char, 15 bit short, and 33 bit longs. In practice, either the processor or the compiler has to deal with this weirdness, and it's not a trivial task. So in practice, every compiler ever made has simply made all data sizes a multiple of a char.

(2) Mainframes and minicomputers

Early computers were constructed from individual vacuum tubes, transistors, or small-scale integrated circuits (such as the 7400 and 74000 series). Those that filled an entire room were generally called mainframes, whereas minicomputers were typically the size of one equipment rack or smaller. The former usually had more memory, mass storage, and peripherals than the latter, hence the increased size and cost.

Designers of these systems picked a word size that was appropriate for their needs. Many systems had word sizes that weren't powers of 2. The GE 600 (and its successor the Honeywell 6000) had 36 bits. The PDP 8 had 12 bits. The Nicolet 1080 had 20 bits.

Because the processor is not confined to one chip, it didn't matter how many pins were on the ICs that were used. If one IC wasn't enough for your needs, you just wire up more. That's why these computers took up so much space and cost.

On the other hand, these computers could be quite fast. With full-sized address and data buses, they could load or store an entire word in just one clock cycle.

(3) The first microprocessor

Microprocessors consolidate all arithmetic, logic, and control functions on to one integrated circuit. The Intel 4004 is often cited as the first microprocessor.

Side notes: Some claim the MP944 as the first microprocessor, but it was a military design that was classified until 1998, so it had no impact on future processor design. Microcontrollers are one-chip solutions that also include ROM, RAM, and I/O on one chip; most of what I say about microprocessors also applies to microcontrollers.

The 4004 project was initiated by a customer who wanted to build a calculator. Intel quickly realized that the customer's design would require a 40-pin package, when Intel had manufacturing equipment only for 16-pin chips. Thus, the design was scaled back to using 4-bit words. Conveniently, 4 bits is just enough to support the binary-coded-decimal used in a calculator.

Although the 4004 is 4 bits wide externally, it uses 8-bit instructions and 12-bit addresses. This is accomplished by multiplexing the external bus for both address and data. Loading or storing data requires three cycles for the address and one cycle for the data; an instruction fetch uses three cycles for address and two for the instruction.

This "bus bottleneck" was a result of packaging limitations, made the 4004 much slower than a similarly-clocked minicomputer would be. It also required extra hardware outside the chip to decode and latch signals. These issues would continue for about another 25 years in further microprocessor architectures.

(4) Word sizes double

Four bits is simply not enough for most applications (especially not a calculator). However, one can create larger data types by operating on multiple words. For example, you can add two 8-bit numbers by combining the additions of two pairs of 4-bit numbers.

You could hypothetically use a similar process to add 12-bit numbers. (This was in fact used in the 12-bit Intersil 6100 used by the PDP-8.) However, as long as you have written the code to add two 8-bit numbers, why not re-use that code to add two 16-bit numbers? And then re-use that to add 32-bit numbers? You can see how this doubles the number of bits each time you increase the data size, leading to data sizes that are powers of 2.

Another issue with the 4004 was that the instruction size was two words (8 bits). Even later processor designs often required some multi-word instructions. If you double the bits of the external bus, it will take fewer cycles to fetch instructions.

Powers of 2 are also attractive to computer designers, as they are already familiar with them through other aspects of computing.

Thus, Intel's next processor (the 8008) had double the word size of the 4004: 8 bits. The package now had 18 pins, again creating a bus bottleneck. This was followed by the 8080, which had 40 pins, was much faster, and was easier to interface.

Other processors entered the market after the 8008. By then, the usefulness of word sizes that are powers of 2 had already been established, and processor designers eagerly accepted them.

(5) New packaging overcomes limits

Business users continued to purchase mainframes and minicomputers into the mid-1990s, as they were significantly faster than microprocessor-based machines. The reason was the bus bottleneck imposed by microprocessor packaging.

Until the 1990s, most processors were housed in dual inline packages (DIP). Most designs had 40 pins or less, although the Motorola MC68000 is an impressive exception at 64 pins. As the number of pins on a DIP grows, the package takes up a lot more board space, and the wires that connect the corner pins grow longer, which is a bad thing at high frequencies. Thus, microprocessor designers did their best to limit pin count, even if this meant reducing the size of external buses. This causes the bus bottleneck, previously described.

In contrast, mainframes and minicomputers use as many chips as they need to build the processor. Buses are the full width throughout the machine, so there is no bus bottleneck, making them faster than contemporary microprocessors.

During the 1990s, chipmakers began using other forms of packaging, such as quad flat packs, plastic leadless chip carriers, pin-grid arrays, and ball-grid arrays. These new forms allowed more than 40 pins, enabling larger external microprocessor buses.

Freed of the bus bottleneck, microprocessor instruction speeds increased dramatically. Of course, there were other factors in play. Tricks such as caching and pipelining were "borrowed" from mainframes. Reduced Instruction Set Computing (RISC) made one-cycle instruction fetches. Smaller transistor sizes and computer-aided chip design helped increase the number of transistors per chip, as well as boost clock speeds. Compilers became smarter. Regardless, microprocessors quickly outpaced the speed of mainframes and minicomputers.

(6) Computers today

Mainframes and minicomputers became obsolete by the end of the 1990s. Why bother making a system out of discrete chips when a microprocessor is cheaper, smaller, and faster? Of course, there are still room-sized computers -- but they are now server farms made of hundreds or thousands of microprocessors, not discrete chips.

Practically all computers today use microprocessors, which in turn have word sizes that are powers of 2. Thus, we often assume that a processor's word size has to be a power of 2. Although practical, it is not a requirement.

DrSheldon
  • 15,979
  • 5
  • 49
  • 113
  • Well, Much typeed, but somhow you mostly missed the question, as it doesn't ask for any part in integration or other design philosopies. Beside that, it's not icroprocessor vs. mainframe. that would be microcomputer vs. mainframe, ad these are still way different designs. – Raffzahn Aug 01 '18 at 03:40
  • 1
    there have been plenty of systems that load/store whole words and then let the compiler do the magic And that's exactly what I'm asking about. How were pointers to characters implemented? – Leo B. Aug 01 '18 at 20:49
  • I think the 4004 is in many ways closer to a microcontroller than a microprocessor, in that almost all devices that would today be classified as microprocessors use a common address space for code and data, but the 4004 did not. – supercat Sep 09 '22 at 17:04