Help porting newlib to a new CPU architecture (sorta)

Orlando Arias
Tue Jul 6 19:04:21 GMT 2021


On 7/6/21 2:08 PM, Brian Inglis wrote:
> C has no assumptions or requirements for outdated von Neumann
> architectures, but it also may not assume it is not running on such
> architectures.
> Early C and Unix took advantage of the Harvard architecture of larger
> PDP-11s which supported split I/D space with mapping registers to
> support double the program size (64KB each 16b I and D) as well as
> splitting kernel/supervisor/runtime/user spaces [and similar features on
> other systems], and shared text (the original meaning of the 't' "sticky
> text" perm on executables), which is why there is wording in the
> standards about unmodifiable constant values.

Right, went back and looked at the standard. There is no description of
what the abstract machine for the execution environment should be. I
guess my confusion came from the second paragraph in [1]. Harvard
architectures still have the thing that you have to define whether a
pointer refers to something in program space or data space, and standard
C has no way of signaling this. In AVR, for example, requires the use of
the LPM instruction to perform loads from program memory, and the use of
SPM to store data into program memory (there are limitations to the
latter). In contrast, accesses to data memory are performed using
LDS/STS and family. The compiler will normally issue LD*/ST*
instructions when dereferencing pointers. If program memory is desired,
then the PROGMEM macro needs to be used for variables that need to be in
program memory, and a series of macros/intrinsics need to be used to
actually load/store the data in that region [forcing the use of the
LPM/SPM instructions].

I/O space is also something that can be hard to handle in C. IA-32
requires the use of the IN/OUT instructions to talk to devices in the
I/O space. The compiler will not emit this stuff unless you use some
form of macro/intrinsic. AVR has an I/O space that is also memory
mapped, so the compiler can optimize assembly output and use the I/O
space when talking to some addresses, avoiding the use of load/store
instructions (which are larger and take longer to execute). The
optimization is only possible though if the address is known at compile
time, or that it can be replaced with a symbol address at link time. For
the latter, the linker needs to check that the instruction can be
properly `patched' with the address being accessed.

This is what I meant by the von Neumann requirement: all pointers
dereference to the same address space. In a hosted system, the OS may be
able to perform some tricks to mask away different address spaces
[trapping on a memory access to a specific address range and emulating
the access for the program, for example]. In a freestanding system, your
[statically linked] C runtime is responsible for initializing the data
section of the program somehow. Usually, this involves copying data from
a read-only location [storage medium, such as memory-mapped flash] to a
read/write location [such as SRAM]. If you need to cross different
address spaces when performing this copy operation, then standard C will
not help you. You need an extension to the language of some sort [or to
use assembly]. Yes, most modern machines have split I/D caches, and the
processor's implementation is effectively that of a Harvard machine.
Some CPUs have multiple ports which can be used to service different
memory access requests to a unified memory system. Cortex-M processors,
for example, have multiple AMBA AXI ports [PPB, D-bus, I-bus, and
S-bus], but the memory system is still unified into a single address
space making the overall machine follow the von Neumann paradigm. This
is not the case with AVR, where address 0 means different things when
talking about data space and program spaces.

One thing though, I was under the impression that the PDP-11 was a von
Neumann machine with its unified memory model and all. Unix on the
machine made use of memory management facilities provided by the
hardware to provide a virtual memory environment for executing processes
[this being more of a function of the OS rather than C itself]?

> One of the big issues with systems around the time C was developed was
> the unreliability resulting from systems, runtimes, and programs using
> self-modifying code "tricks", and programs ability to change constant
> data values used in programs (PI did not always stay π).
> That was one of the benefits touted for higher level system
> implementation languages such as Bliss, C, PL/S.
> In the face of failures frequently caused by arbitrary code and data
> modifications, branches and jumps, worries about trivial issues such as
> delimited strings, unsized APIs, dynamic memory management were decades
> in the future!
>> Various implementations, ABIs, and systems require modifiable code and
> constant data, but it is *NOT* a C language assumption or requirement;
> in fact I would consider it to be a C anti-pattern, that has long
> limited the abilities of compilers and optimizers to do better jobs.
> Copying sections tends to be due to limitations of CPUs, systems,
> runtimes, or compilers to DTRT to make programs more reliable. ;^>

This latter part is really dependent on the system, and I would consider
it outside the scope of the C language proper and more towards the C
runtime environment, program loader, and dynamic linker [which the
standard makes no assumption of as you state]. For example, on systems
that support ASLR, binaries may need to be `patched' when loaded to fix
relocation information. I believe DLLs in Windows have some weird thing
like they have a preferred base address to be loaded, otherwise the DLL
needs to be `rewritten' in memory as it is loaded using information from
one of the PE sections. Not too sure how that works or if they still do
that. I have not looked at Windows internals in a long time. IA-32 code
has some limitations on that too, given that %eip is not
addressable/accessible without using some hacks [which require the use
of assembly: call a stub, load the value of %eip from the stack into a
general purpose register such as %eax from the stack, then return. The
value in %eax is now the same as the address of the instruction you
returned to]. AMD64 allows for %rip to be indexed, which allows for
position independent code to be generated a lot easier,  although some
relocation information may still be needed [again, not too familiar with
this aspects of the architecture/ABI].



-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature
Type: application/pgp-signature
Size: 195 bytes
Desc: OpenPGP digital signature
URL: <>

More information about the Newlib mailing list