6.7 Using C for ASCII Input and Output

The debugger, while a useful tool for exploration, is obviously not ideal for general input and output, especially for ASCII text strings. Textbook presentations of assembly language programming often imply that routines written in assembly language can be called from high-level languages, but perhaps more by silence than by statement routines written in a high-level language cannot or should not be called from assembly language programs. In actuality, mixed-language programming at the assembly language level is possible, but does require strict adherence to published calling standards.

6.7.1 GETPUT: Encapsulating C Functions

Our minimal illustration (Figure 6-6) involves a pair of C external routines, chrput (encapsulating the C library putchar function) to print a character on the screen, and chrget (encapsulating getchar) to obtain a character from the keyboard. Even though these routines process individual characters, we need to be aware that the operating system handles terminal input and output a line at a time.

Figure 6-6 Encapsulating the putchar and getchar routines of C
 // getput.c // Encapsulate putchar and getchar #include <stdio.h> long long int chrput(long long int ch) {   return (long long int) putchar(ch); } long long int chrget() {   return (long long int) getchar(); } 

We encapsulated the putchar and getchar functions because the routines they represent may be implemented as "macros" (instructions inserted inline by the compiler) rather than as called functions. Encapsulation allows us to specify explicitly that we want to work with 64-bit integer values (long long int), rather than rely on a system default. Encapsulation also permits us to hide system-dependent or distracting details irrelevant to our algorithms.

6.7.2 IO_C: A Simple Test Program

The Linux and HP-UX programming environments for Itanium systems specify similar conventions for the use of registers and passing of arguments. The conventions include: what a caller must do, what a called procedure must do, and what resources are not to be overwritten. Since our encapsulated routines specify long long int as the data type, the incoming and outgoing values correspond to quad word (64-bit) quantities.

Figure 6-7 shows a minimal test program that calls the encapsulated C routines for single-character input and output in order to validate their operation. We preview here another Itanium instruction, alloc, which allocates general registers Gr32 Gr127 on a module-by-module basis, and some of its associated assembler directives. Simple procedures interact with their callers using the general registers numbered Gr32 and up for their inputs, and register Gr8 for the value returned. This program uses additional forms of the Itanium branch instructions that perform call and return operations.

Focus first on the central portion between the labels first and done. The external routines are called using the br.call instruction, which sets the instruction pointer (IP) to the address of the routine being called and stores a return address in branch register Br0. The completers sptk and many have the same meaning and purpose as for ordinary branch instructions. We have seen the function return instruction br.ret at the bottom of previous programs.

Routines pass values, or addresses to values, as arguments. A function like chrget returns its value (the ASCII code for one character as a 64-bit value) in general register Gr8. A function like chrput will look for its input value in a specific region of the Itanium register stack, general registers Gr32 Gr127. The IO_C program passes a character code to chrput in register r35; choosing the correct register is explained more fully in Chapter 7.

The files containing the C routines and the assembly language program can be processed with a single command line. Linking and production of an executable file is automatic, unless errors occur. Suitable system commands are as follows:

 L> gcc  O0 -o bin/io_c io_c.s getput.c L> bin/io_c L> ecc  O0 -o bin/io_c io_c.s getput.c L> bin/io_c H> cc +DD64 -o bin/io_c io_c.s getput.c H> bin/io_c 

HP-UX compiler tools may produce separate object modules, io_c.o and getput.o, in addition to an executable output file. Those files may be reused by the linker when their corresponding source files have not been modified.

Figure 6-7 IO_C: A test of input and output
 // IO_C         Test character I/O using C         NL      = 0xa            // Newline character         .global chrget,chrput    // External references         .type   chrget,@function //  and their         .type   chrput,@function //   types         .text                    // Section for code         .align  32               // Desired alignment         .global main             // These three lines         .proc   main             //  mark the mandatory main:                            //   'main' program entry         .prologue 12,r33         // rp, ar.pfs @r33+         .save   ar.pfs,r34       // Previous function state         alloc   r34 = ar.pfs,0,3,1,0  // 3 locals, 1 out         .save   rp,r33           // Say where we store         mov     r33 = b0         //  the return address         .body                    // End of prologue first:                           // Now we really begin...         mov     r32 = r1;;       // Save gp across calls         br.call.sptk.many b0=chrget;;  // Get one character         mov     r1 = r32         // Restore gp         mov     r35 = r8         // r35 = character to send         br.call.sptk.many b0=chrput;;  // Put one character         mov     r1 = r32         // Restore gp         mov     r35 = NL         // r35 = newline to send         br.call.sptk.many b0=chrput;;  // Put one character         mov     r1 = r32         // Restore gp done:   mov     r8 = 0           // Signal all is normal         mov     ar.pfs = r34     // Restore previous state         mov     b0 = r33         // Restore exit address         br.ret.sptk.many b0;;    // Back to command line 

The linker collects the data and executable portions from the IO_C program and the other two modules into a unified address space and assures that all necessary intermodule pointers are accessible at runtime.

By default, the executable file will be named a.out unless a name is specified with the -o option. It is important to ensure that only one module contains code designated as the main entry point; all the rest should be callable functions.

If, at the keyboard, we type some characters like abc, say and then push the return (or enter) key, this program should output the first character that we typed on a line by itself. As the system input is actually line-oriented, the program waits until we have indeed pushed return (or enter) before proceeding; chrget (getchar) receives a line, no matter how small, rather than single characters. Similarly, no output will become visible until a program has sent a "newline" character through chrput (putchar).

6.7.3 Additional Concepts

Register-level programming involves adherence to calling conventions in order to prevent the loss or corruption of the contents of the shared processor registers.

Some Itanium assemblers, compilers, and linkers need to have global symbols explicitly indicated. The .global and .type directives describe the two encapsulated functions that we named chrget and chrput.

Proper operation of the debugger, and recovery from errors detected by the system software, both require unwind information that the programming environment tools prepare as part of an executable program. Mosberger and Eranian describe the specific unwinding arrangements for Linux, while the Itanium Software Conventions and Runtime Architecture Guide sets forth general unwinding expectations that any operating system must provide.

By passing assembly language programs through the C/C++ compiler front-ends (gcc, ecc, cc) instead of directly to the assemblers, some of the unwind information is built for us. We still must write appropriate prologue and epilogue segments.

The prologue, extending from the .prologue directive to the .body directive, involves an additional directive (.save), a previously unseen Itanium instruction (alloc), and another application register (ar.pfs). The epilogue does not have a special directive to mark its top, but in IO_C it extends from the line after the label done to the program exit.

Since every calling level might need a different address in the global pointer (gp, or register r1), it is proper to save register r1 before the first br.call and restore it after every br.call statement.



ItaniumR Architecture for Programmers. Understanding 64-Bit Processors and EPIC Principles
ItaniumR Architecture for Programmers. Understanding 64-Bit Processors and EPIC Principles
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net