Installing lcc

Christopher W. Fraser, Microsoft Research
David R. Hanson, Department of Computer Science, Princeton University


  1. Introduction
  2. Building the Driver
  3. Building the Compiler and Accessories
  4. Reporting Bugs
  5. Keeping in Touch

1. Introduction

lcc is the ANSI C compiler described in our book A Retargetable C Compiler: Design and Implementation (Addison-Wesley, 1995, ISBN 0-8053-1670-1).

Extract the distribution into its own directory. All non-absolute paths below are relative to this directory. The distribution holds the following subdirectories.

src source code
etc driver, accessories
cpp preprocessor source code
lburg code-generator generator source code
doc this document, man pages
include/*/* include files
tst test suite
mips/*/tst MIPS test outputs
sparc/*/tst SPARC test outputs
x86/*/tst X86 test outputs

doc/install.html is the HTML file for this document.

The installation makefile is designed so that lcc can be installed from a read-only file system or directory, which is common in networked environments, so the distribution can be unloaded on a central file server.

The compilation components (the preprocessor, include files, and compiler proper, etc.) are installed in a single build directory. On multi-platform systems supported by a central file server, it's common to store the build directory in a location specific to the platform and to the version of lcc, and to point a symbolic link to this location. For example,

% ln -s /usr/local/lib/lcc-3.6/sparc/solaris /usr/local/lib/lcc

points /usr/local/lib/lcc to a build directory for lcc version 3.6 on the SPARC under Solaris. Links into /usr/local/lib are created for the programs lcc and bprint. Thus, a new distribution can be installed by building it in its own build directory and changing one symbolic link to point to that directory. If these conventions or their equivalents are followed, the host-specific parts of the driver program, lcc, can be used unmodified.

Installation on UNIX systems involves the following steps. Below, the build directory is referred to as BUILDDIR. You will need an existing ANSI/ISO C compiler to install lcc.

  1. Create the build directory, using a version- and platform-specific naming convention as suggested above, and record the name of this directory in the BUILDDIR environment variable:
    % setenv BUILDDIR /usr/local/lib/lcc-3.6/sparc/solaris
    % mkdir -p $BUILDDIR
    Here and below, commands assume the C shell. Also, you'll need a version of mkdir that supports the -p option, which creates intermediate directories as necessary.
  2. Copy the man pages to the repository for local man pages, e.g.,
    % cp doc/*.1 /usr/local/man/man1
    Some users copy the man pages to the build directory and create the appropriate symbolic links, e.g.,
    % cp doc/*.1 $BUILDDIR
    % ln -s $BUILDDIR/*.1 /usr/local/man/man1
  3. Platform-specific include files are in directories named include/target/os. Create the include directory in the build directory, and copy the include files for your platform to this directory, e.g.,
    % mkdir $BUILDDIR/include
    % rcp -p include/sparc/solaris/*.h $BUILDDIR/include
    Some users create a symbolic link to the appropriate directory in the distribution instead of copying the include files. For example, at Princeton, the distributions are stored under /proj/pkg/lcc, so the included files are "installed" by creating one symbolic link:
    % ln -s /proj/pkg/lcc/3.6/include/sparc/solaris $BUILDDIR/include
  4. Build the host-specific driver, creating a custom host-specific part, if necessary. See Sec. 2.
  5. Build the preprocessor, compiler proper, and other accessories. See Sec. 3.
  6. Plant symbolic links to the build directory and to the installed programs, e.g.,
    % ln -s $BUILDDIR /usr/local/lib/lcc
    % ln -s /usr/local/lib/{lcc,bprint} /usr/local/bin
    Some users copy bprint and lcc into /usr/local/bin instead of creating symbolic links. The advantange of creating the links for lcc and bprint as shown is that, once established, they point indirectly to whatever /usr/local/lib/lcc points to; installing a new version of lcc, say, 3.7, can be done by changing /usr/local/lib/lcc to point to the 3.7 build directory.

2. Building the Driver

The preprocessor, compiler, assembler, and loader are invoked by a driver program, lcc, which is similar to cc on most systems. It's described in the man page doc/lcc.1. The driver is built by combining the host-independent part, etc/lcc.c, with a small host-specific part. Distributed host-specific parts are named etc/os.c, where os is the name of the operating system for the host on which lcc is being installed. If you're following the installations conventions described above, you can probably use one of the host-specific parts unmodified; otherwise, pick one that is closely related to your platform, copy it to whatever.c, and edit it as described below. You should not have to edit etc/lcc.c.

We'll use etc/solaris.c as an example in describing how the host-specific part works. This example illustrates all the important features. Make sure you have the environment variable BUILDDIR set correctly, and build the driver with a make command, e.g.,

% make HOSTFILE=etc/solaris.c lcc
cc -c  -o /usr/local/lib/lcc-3.6/sparc-solaris/lcc.o etc/lcc.c
cc -c  -o /usr/local/lib/lcc-3.6/sparc-solaris/host.o etc/solaris.c
cc -o /usr/local/lib/lcc-3.6/sparc-solaris/lcc  /usr/local/lib/lcc-3.6/sparc-solaris/lcc.o /usr/local/lib/lcc-3.6/sparc-solaris/host.o

The symbolic name HOSTFILE specifies the path to the host-specific part, either one in the distribution or whatever.c. Some versions of make may require the -e option in order to read the environment.

Here's etc/solaris.c:

/* Sparcs running Solaris 2.5.1 at CS Dept., Princeton University */

#include <string.h>

#ifndef LCCDIR
#define LCCDIR "/usr/local/lib/lcc/"
#ifndef SUNDIR
#define SUNDIR "/opt/SUNWspro/SC4.0/lib/"

char *cpp[] = { LCCDIR "cpp",
	"-D__STDC__=1", "-Dsparc", "-D__sparc__", "-Dsun", "-D__sun__",
	"$1", "$2", "$3", 0 };
char *include[] = { "-I" LCCDIR "include", "-I/usr/local/include",
	"-I/usr/include", 0 };
char *com[] = { LCCDIR "rcc", "-target=sparc/solaris",
	"$1", "$2", "$3", 0 };
char *as[] = { "/usr/ccs/bin/as", "-Qy", "-s", "-o", "$3", "$1", "$2", 0 };
char *ld[] = { "/usr/ccs/bin/ld", "-o", "$3", "$1",
	SUNDIR "crti.o", SUNDIR "crt1.o",
	SUNDIR "values-xa.o", "$2", "",
	"-Y", "P," SUNDIR ":/usr/ccs/lib:/usr/lib", "-Qy",
	"-lm", "-lc", "", SUNDIR "crtn.o", 0 };
static char *bbexit = LCCDIR "bbexit.o";

extern char *concat(char *, char *);
extern int access(const char *, int);

int option(char *arg) {
	if (strncmp(arg, "-lccdir=", 8) == 0) {
		cpp[0] = concat(&arg[8], "/cpp");
		include[0] = concat("-I", concat(&arg[8], "/include"));
		com[0] = concat(&arg[8], "/rcc");
		bbexit = concat(&arg[8], "/bbexit.o");
	} else if (strcmp(arg, "-g") == 0)
	else if (strcmp(arg, "-p") == 0) {
		ld[5] = SUNDIR "mcrt1.o";
		ld[10] = "P," SUNDIR "libp:/usr/ccs/lib/libp:/usr/lib/libp:"
			 SUNDIR ":/usr/ccs/lib:/usr/lib";
	} else if (strcmp(arg, "-b") == 0 && access(bbexit, 4) == 0)
		ld[8] = bbexit;
		return 0;
	return 1;

LCCDIR defaults to "/usr/local/lib/lcc/" unless it's defined by a -D option as part of CFLAGS in the make command, e.g.,

% make HOSTFILE=etc/solaris.c CFLAGS='-DLCCDIR=\"/v/lib/lcc/\"' lcc

Note the trailing slash. SUNDIR is provided so you can use etc/solaris.c even if you have a different version of the Sun Pro compiler suite. If you're using the gcc compiler tools instead of the Sun Pro tools, see etc/gcc-solaris.c.

Most of the host-specific code is data that gives templates for the commands that invoke the preprocessor, compiler, assembler, and loader. Each command template is an array of pointers to strings terminated with a null pointer; the first string is the full path name of the command and the others are the arguments or argument placeholders, which are described below.

The cpp array gives the command for running lcc's preprocessor, cpp. Literal arguments specified in templates, e.g., "-Dsparc" in the cpp command above, are passed to the command as given.

The strings "$1", "$2", and "$3" in templates are placeholders for lists of arguments that are substituted in a copy of the template before the command is executed. $1 is replaced by the options specified by the user; for the preprocessor, this list always contains at least -Dunix and -D__LCC__. $2 is replaced by the input files, and $3 is replaced by the output file.

Zero-length arguments after replacement are removed from the argument list before the command is invoked. So, for example, if the preprocessor is invoked without an output file, "$3" becomes "", which is removed from the final argument list.

The include array is a list of -I options that specify which directives should be searched to satisfy include directives. These directories are searched in the order given. The first directory should be the one to which the ANSI header files were copied in Sec. 1. The driver adds these options to cpp's arguments when it invokes the preprocessor, except when -N is specified.

com gives the command for invoking the compiler. This template can appear as shown above in a custom host-specific part, but the option -target=sparc/solaris should be edited to the target/os for your platform. lcc can generate code for all of the target/os combinations listed in the file src/bind.c. The -target option specifies the default combination. The driver's -Wf option can be used to specify other combinations; the man page elaborates.

as gives the command for invoking the assembler.

ld gives the command for invoking the loader. For the other commands, the list $2 contains a single file; for ld, $2 contains all ".o" files and libraries, and $3 is a.out, unless the -o option is specified. As suggested in the code above, ld must also specify the appropriate startup code and default libraries.

The option function is described below; the minimal option function just returns 0.

You can test lcc with the options -v -v to display the commands that would be executed, e.g.,

% $BUILDDIR/lcc -v -v foo.c baz.c mylib.a -lX11 /usr/local/lib/lcc-3.6/sparc-solaris/lcc $Name$($Id$)
/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include foo.c | /usr/local/lib/lcc/rcc -target=sparc/solaris -v - /tmp/lcc23900.s
/usr/ccs/bin/as -Qy -s -o /tmp/lcc23901.o /tmp/lcc23900.s
/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include baz.c | /usr/local/lib/lcc/rcc -target=sparc/solaris -v - /tmp/lcc23900.s
/usr/ccs/bin/as -Qy -s -o /tmp/lcc23902.o /tmp/lcc23900.s
/usr/ccs/bin/ld -o a.out /opt/SUNWspro/SC4.0/lib/crti.o /opt/SUNWspro/SC4.0/lib/crt1.o /opt/SUNWspro/SC4.0/lib/values-xa.o /tmp/lcc23901.o /tmp/lcc23902.o mylib.a -lX11 -Y P,/opt/SUNWspro/SC4.0/lib/:/usr/ccs/lib:/usr/lib -Qy -lm -lc /opt/SUNWspro/SC4.0/lib/crtn.o
rm /tmp/lcc23902.o /tmp/lcc23900.s /tmp/lcc23901.o

Note the use of a pipeline to connect the preprocessor and compiler. lcc arranges this pipeline itself; it does not call the shell. If you want lcc to use temporary files instead of a pipeline, define PIPE=0 in CFLAGS when building the driver. The option -pipe forces lcc to use a pipeline between the preprocessor and the compiler regardless of PIPE's value; -nopipe has just the opposite effect.

As the output shows, lcc places temporary files in /tmp. Alternatives can be specified by defining TEMPDIR in CFLAGS when building the driver, and with the -tempdir=dir option.

The option function is called for the options -Wo, -g, -p, -pg, and -b because these compiler options might also affect the loader's arguments. For these options, the driver calls option(arg) to give the host-specific code an opportunity to edit the ld command, if necessary. option can change ld, if necessary, and return 1 to announce its acceptance of the option. If the option is unsupported, option should return 0.

For example, in response to -g, the option function shown above accepts the option but does nothing else, because the ld and as commands don't need to be modified on the SPARC. -g will also be added to the compiler's options by the host-independent part of the driver. The -p causes option to change the name of the startup code and changed the list of libraries. The -b option turns on lcc's per-expression profiling, which needs the special exit routine, bbexit.o, when the program is loaded, so option adds it to an otherwise empty slot in ld's arguments. The call to access ensures that bbexit.o is added to the list only if it was installed.

On SPARCs, the driver also recognizes -Bstatic and -Bdynamic as linker options, and recognizes but ignores "-target name" option.

The option -Woarg causes the driver to pass arg to option. Such options have no other effect; this mechanism is provided to support system-specific options that affect the commands executed by the driver. As illustrated above, host-specific parts should support the -Wo-lccdir=dir option, which causes lcc's compilation components to be found in dir, because this option is used by the test scripts. The code above rebuilds the paths to the include files, preprocessor, compiler, and profiling exit routine by calling concat, which is defined in etc/lcc.c.

3. Building the Compiler and Accessories

To build the rest of compilation components make sure BUILDDIR is set appropriately and type "make all". This command builds rcc (the compiler proper), lburg (the code-generator generator), cpp (the preprocessor), bbexit.o (the profiling exit routine), and bprint (the profile printer), all in BUILDDIR. There may be a few warnings, but there should be no errors. If you're using an ANSI/ISO compiler other than cc, specify its name with the CC= option, e.g., "make CC=gcc all".

Once rcc is built with the host C compiler, run the test suite to verify that rcc is working correctly. If any of the steps below fail, contact us (see Sec. 4). The commands in the makefile run the shell script src/run on each C program in the test suite, tst/*.c. It uses the driver, $BUILDDIR/lcc, so you must have the driver in the build directory before testing rcc. The target/os combination is read from the variable TARGET, which must be specified when invoking make:

% make TARGET=sparc/solaris test
mkdir -p /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/8q.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/array.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/cf.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/cq.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/cvt.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/fields.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/front.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/incr.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/init.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/limits.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/paranoia.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/sort.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/spill.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/stdarg.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/struct.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/switch.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/wf1.s:
/usr/local/lib/lcc-3.6/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-3.6/sparc-solaris/sparc/solaris/tst/yacc.s:

Each line in the output above is of the form

$BUILDDIR/rcc -target=target/os$BUILDDIR/target/os/X.s:

where X is the base name of the C program X.c in the test suite. This output identifies the compiler and the target, e.g., "$BUILDDIR/rcc is generating code for a sparc running the solaris operating system."

For each program in the test suite, src/run compiles the program, drops the generated assembly language code in BUILDDIR/target/os, and uses diff to compare the generated assembly code with the expected code (the code expected for tst/8q.c on the SPARC under Solaris is in sparc/solaris/tst/8q.sbk, etc.). If there are differences, the script executes the generated code with the input given in tst (the input for tst/8q.c is in tst/8q.0, etc.) and compares the output with the expected output (the expected output from tst/8q.c on the SPARC under Solaris is in sparc/solaris/tst/8q.1bk, etc.). The script also compares the diagnostics from the compiler with the expected diagnostics.

On some systems, there may be a few differences between the generated code and the expected code. These differences occur because the expected code is generated by cross compilation and the least significant bits of some floating-point constants differ from those bits in constants generated on your system. There should be no differences in the output from executing the test programs.

Next, run the "triple test", which builds rcc using itself:

% make triple
/usr/local/lib/lcc-3.6/sparc-solaris/lcc -o /usr/local/lib/lcc-3.6/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-3.6/sparc-solaris -B/usr/local/lib/lcc-3.6/sparc-solaris/  -Isrc src/*.c
/usr/local/lib/lcc-3.6/sparc-solaris/lcc -o /usr/local/lib/lcc-3.6/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-3.6/sparc-solaris -B/usr/local/lib/lcc-3.6/sparc-solaris/  -Isrc src/*.c
strip /usr/local/lib/lcc-3.6/sparc-solaris/[12]rcc
dd if=/usr/local/lib/lcc-3.6/sparc-solaris/1rcc of=/usr/local/lib/lcc-3.6/sparc-solaris/rcc1 bs=512 skip=1
769+1 records in
769+1 records out
dd if=/usr/local/lib/lcc-3.6/sparc-solaris/2rcc of=/usr/local/lib/lcc-3.6/sparc-solaris/rcc2 bs=512 skip=1
769+1 records in
769+1 records out
if cmp /usr/local/lib/lcc-3.6/sparc-solaris/rcc[12]; then \
        mv /usr/local/lib/lcc-3.6/sparc-solaris/2rcc /usr/local/lib/lcc-3.6/sparc-solaris/rcc; \
        rm -f /usr/local/lib/lcc-3.6/sparc-solaris/rcc[12]; fi

This command builds rcc twice; once using the rcc built by cc and again using the rcc built by lcc. The resulting binaries are compared. They should be identical, as shown at the end of the output above. If they aren't, our compiler is generating incorrect code; contact us.

The final version of rcc should also pass the test suite; that is, the output from

% make TARGET=sparc/solaris test

should be identical to that from the previous make test.

The command "make clean" cleans up, but does not remove rcc, etc., and "make clobber" cleans up and removes lcc, rcc, and the other accessories. Test directories under BUILDDIR are not removed; you'll need to remove these by hand, e.g.,

% rm -fr $BUILDDIR/sparc

The code generators for the other targets can be tested by specifying the desired target/os and setting an environment variable that controls what src/run does. e.g., to test the MIPS code generator, type

% setenv REMOTEHOST noexecute
% make TARGET=mips/irix test

As above, src/run compares the MIPS code generated with what's expected. There should be no differences. Setting REMOTEHOST to noexecute suppresses the assembly and execution of the generated code. If you set REMOTEHOST to the name of a MIPS machine to which you can rlogin, src/run will rcp the generated code to that machine and execute it there, if necessary. See src/run for the details.

You can use lcc as a cross compiler. The options -S and -Wf-target=target/os generate assembly code for the specified target, which is any of those listed in the file src/bind.c. For example,

% lcc -Wf-target=mips/irix -S tst/8q.c

generates MIPS code for tst/8q.c in 8q.s.

lcc can also generate code for a "symbolic" target. This target is used routinely in front-end development, and its output is a printable representation of the input program, e.g., the dags constructed by the front end are printed, and other interface functions print their arguments. You can specify this target with the option -Wf-target=symbolic. For example,

% lcc -Wf-target=symbolic -S tst/8q.c

generates symbolic output for tst/8q.c in 8q.s. Finally, the option -Wf-target=null specifies the "null" target for which lcc emits nothing and thus only checks the syntax and semantics of its input files.

4. Reporting Bugs

lcc is a large, complex program. We find and repair errors routinely. If you think that you've found a error, follow the steps below, which are adapted from the instructions in Chapter 1 of A Retargetable C Compiler: Design and Implementation.

  1. If you don't have a source file that displays the error, create one. Most errors are exposed when programmers try to compile a program they think is valid, so you probably have a demonstration program already.
  2. Preprocess the source file and capture the preprocessor output. Discard the original code.
  3. Prune your source code until it can be pruned no more without sending the error into hiding. We prune most error demonstrations to fewer than five lines.
  4. Confirm that the source file displays the error with the distributed version of lcc. If you've changed lcc and the error appears only in your version, then you'll have to chase the error yourself, even if it turns out to be our fault, because we can't work on your code.
  5. Annotate your code with comments that explain why you think that lcc is wrong. If lcc dies with an assertion failure, please tell us where it died. If lcc crashes, please report the last part of the call chain if you can. If lcc is rejecting a program you think is valid, please tell us why you think it's valid, and include supporting page numbers in the ANSI Standard, Appendix A in The C Programming Language, or the appropriate section in C: A Reference Manual, 4th edition by S. B. Harbison and G. L. Steele, Jr. (Prentice Hall, 1995). If lcc silently generates incorrect code for some construct, please include the corrupt assembly code in the comments and flag the incorrect instructions if you can.
  6. Confirm that your error hasn't been fixed already. The latest version of lcc is always available for anonymous ftp from in pub/lcc. A README file there gives acquistion details, and a LOG file reports what errors were fixed and when they were fixed. If you report a error that's been fixed, you might get a canned reply.
  7. Send your program by electronic mail to Please send only valid C programs; put all remarks in C comments so that we can process reports semiautomatically.

5. Keeping in Touch

There is an lcc mailing list for general information about lcc. To be added to the list, send a message with the 1-line body

subscribe lcc

to This line must appear in the message body; "Subject:" lines are ignored. To learn more about mailing lists served by majordomo, send a message with the 1-word body "help" to Mail sent to is forwarded to everyone on the mailing list.

There is also an lcc-bugs mailing list for reporting bugs; subscribe to it by sending a message with the 1-line body

subscribe lcc-bugs

to Mail addressed to is forwarded to everyone on this list.

Chris Fraser /
David Hanson /