diff mbox

[v2] sparse: add LLVM code generation backend

Message ID 20090426205806.GA20933@havoc.gtf.org (mailing list archive)
State Rejected, archived
Headers show

Commit Message

Jeff Garzik April 26, 2009, 8:58 p.m. UTC
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
---
Here is an update, from the last release.  On top of Chris Li's repo
(not that it really matters, when simply adding files).


 .gitignore |    1 
 Makefile   |    3 
 s2l-gen.c  | 2097 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 slcc.c     |   77 ++
 4 files changed, 2177 insertions(+), 1 deletion(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Christopher Li April 27, 2009, 7:15 p.m. UTC | #1
On Sun, Apr 26, 2009 at 1:58 PM, Jeff Garzik <jeff@garzik.org> wrote:
>
>+       case EXPR_DEREF:
>+       case EXPR_SIZEOF:
>+       case EXPR_ALIGNOF:
>+               warning(expr->pos, "invalid expression after evaluation");
>+               return NULL;

Regarding using liniearize instruction vs rolling your own.

I don't think you can get more information than linearize instruction
here. EXPR_DEREF has already been processed during evaluation.

As far as I can see, what you do in this patch can be done using
linearized instructions.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik April 27, 2009, 10:39 p.m. UTC | #2
Christopher Li wrote:
> On Sun, Apr 26, 2009 at 1:58 PM, Jeff Garzik <jeff@garzik.org> wrote:
>> +       case EXPR_DEREF:
>> +       case EXPR_SIZEOF:
>> +       case EXPR_ALIGNOF:
>> +               warning(expr->pos, "invalid expression after evaluation");
>> +               return NULL;
> 
> Regarding using liniearize instruction vs rolling your own.
> 
> I don't think you can get more information than linearize instruction
> here. EXPR_DEREF has already been processed during evaluation.

Recall the history of the code:  show-parse.c -> compile-i386.c -> 
s2l-gen.c.

You can see the above code was taken verbatim from show-parse.c, and is 
probably nothing more than a check the original author (Linus?) felt 
appropriate at the time, for show-parse.c.  I wouldn't read too much 
into its presence in s2l-gen.c -- maybe those checks can simply be deleted.

	Jeff



--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li April 27, 2009, 11:27 p.m. UTC | #3
On Mon, Apr 27, 2009 at 3:39 PM, Jeff Garzik <jeff@garzik.org> wrote:
> You can see the above code was taken verbatim from show-parse.c, and is
> probably nothing more than a check the original author (Linus?) felt
> appropriate at the time, for show-parse.c.  I wouldn't read too much into
> its presence in s2l-gen.c -- maybe those checks can simply be deleted.

I did not explain it clear it enough. It is not about the code is not
used. My point is:

1) linearize instruction does not lose the information you care about
   generating better LLVM code. The evaluation does.

2) By writing your own AST recursive code, it does not gain more
   information than what you can already do with current linearize
   instruction. The EXPR_DEREF is processed in evaluation stage.
   The structure laid you care about is already gone by the time your
   s2l-gen about to emit LLVM byte codes. You did not generate any
   GET_ELEMENT_PTR in your LLVM byte code, right?

I still think you should write your LLVM back end base on linearized
instructions. Avoiding it right now does not gain your new features, but
duplicate a lot of complexity. I am not convinced those complexity is
justifiable yet.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik April 28, 2009, 12:32 a.m. UTC | #4
Christopher Li wrote:
> On Mon, Apr 27, 2009 at 3:39 PM, Jeff Garzik <jeff@garzik.org> wrote:
>> You can see the above code was taken verbatim from show-parse.c, and is
>> probably nothing more than a check the original author (Linus?) felt
>> appropriate at the time, for show-parse.c.  I wouldn't read too much into
>> its presence in s2l-gen.c -- maybe those checks can simply be deleted.
> 
> I did not explain it clear it enough. It is not about the code is not
> used. My point is:
> 
> 1) linearize instruction does not lose the information you care about
>    generating better LLVM code. The evaluation does.

> 2) By writing your own AST recursive code, it does not gain more
>    information than what you can already do with current linearize
>    instruction. The EXPR_DEREF is processed in evaluation stage.
>    The structure laid you care about is already gone by the time your
>    s2l-gen about to emit LLVM byte codes. You did not generate any
>    GET_ELEMENT_PTR in your LLVM byte code, right?

Not true -- I can walk SYM_STRUCT of the function arguments' base_type 
passed to a SYM_FN.  Similarly so for struct-based variable declarations.

With that information, you can easily back-reference lvalue uses to the 
original struct.

	Jeff



--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li April 28, 2009, 1:04 a.m. UTC | #5
On Mon, Apr 27, 2009 at 5:32 PM, Jeff Garzik <jeff@garzik.org> wrote:
> Not true -- I can walk SYM_STRUCT of the function arguments' base_type
> passed to a SYM_FN.  Similarly so for struct-based variable declarations.
>
> With that information, you can easily back-reference lvalue uses to the
> original struct.

Let say I follow this route, isn't that you can apply the same trick for the
linearize instruction case? struct instruction has a type member give a
pointer to C type.

I still don't see a reason why you have to use your own AST recursive
code.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik April 28, 2009, 1:21 a.m. UTC | #6
Christopher Li wrote:
> On Mon, Apr 27, 2009 at 5:32 PM, Jeff Garzik <jeff@garzik.org> wrote:
>> Not true -- I can walk SYM_STRUCT of the function arguments' base_type
>> passed to a SYM_FN.  Similarly so for struct-based variable declarations.
>>
>> With that information, you can easily back-reference lvalue uses to the
>> original struct.
> 
> Let say I follow this route, isn't that you can apply the same trick for the
> linearize instruction case? struct instruction has a type member give a
> pointer to C type.
> 
> I still don't see a reason why you have to use your own AST recursive
> code.

You mean, besides the reasons already listed?  Namely, no upstream 
changes are required, and I already have something that works.

Sure, the same trick can be applied.  But that requires a total backend 
rewrite plus dealing with linearize obstacles already described (ref 
linearize_load_gen, linearize_store_gen).  Thus it is obviously a lot 
more work, with additional obstacles (patching upstream, which affects 
existing users), just get back to achieving the same result :)

	Jeff



--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li April 28, 2009, 2:15 a.m. UTC | #7
On Mon, Apr 27, 2009 at 6:21 PM, Jeff Garzik <jeff@garzik.org> wrote:
> Christopher Li wrote:
>>
>> On Mon, Apr 27, 2009 at 5:32 PM, Jeff Garzik <jeff@garzik.org> wrote:
>>>
>>> Not true -- I can walk SYM_STRUCT of the function arguments' base_type
>>> passed to a SYM_FN.  Similarly so for struct-based variable declarations.
>>>
>>> With that information, you can easily back-reference lvalue uses to the
>>> original struct.
>>
>> Let say I follow this route, isn't that you can apply the same trick for
>> the
>> linearize instruction case? struct instruction has a type member give a
>> pointer to C type.
>>
>> I still don't see a reason why you have to use your own AST recursive
>> code.
>
> You mean, besides the reasons already listed?  Namely, no upstream changes
> are required, and I already have something that works.
>
> Sure, the same trick can be applied.  But that requires a total backend
> rewrite plus dealing with linearize obstacles already described (ref
> linearize_load_gen, linearize_store_gen).  Thus it is obviously a lot more

OK. The current handling of linearize_load_gen and linearize_store_gen is
annoying when you try to treat bit field as a type. On the other hand, your
V2 patch does not have bit field (yet).

In the long run, I would rather have only one implementation of the
linearization.

I will take a look at how LLVM does bit fields and get back to you.

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Li April 28, 2009, 10:59 p.m. UTC | #8
On Mon, Apr 27, 2009 at 6:21 PM, Jeff Garzik <jeff@garzik.org> wrote:
> You mean, besides the reasons already listed?  Namely, no upstream changes
> are required, and I already have something that works.
>
> Sure, the same trick can be applied.  But that requires a total backend
> rewrite plus dealing with linearize obstacles already described (ref
> linearize_load_gen, linearize_store_gen).  Thus it is obviously a lot more
> work, with additional obstacles (patching upstream, which affects existing
> users), just get back to achieving the same result :)

I take a look at the how LLVM handle bit fields. Please correct me if
I am wrong.
LLVM does not take bit field member as a type. Given the example:

struct s {
   int a:3;
   int b:3;
} foo;

void func (struct s *a)
{
   a->a = 6;
}

The LLVM outputs:

target datalayout =
"e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"
	%struct.s = type <{ i8, [3 x i8] }>
@foo = common global %struct.s zeroinitializer, align 4		;
<%struct.s*> [#uses=0]

define void @func(%struct.s* nocapture %a) nounwind {
entry:
	%0 = getelementptr %struct.s* %a, i32 0, i32 0		; <i8*> [#uses=2]
	%1 = load i8* %0, align 1		; <i8> [#uses=1]
	%2 = or i8 %1, 6		; <i8> [#uses=1]
	%3 = and i8 %2, -2		; <i8> [#uses=1]
	store i8 %3, i8* %0, align 1
	ret void
}

LLVM does not treat bit field as first class types. It apply bit mask
before/after
the load/store to fix things up. It actually is the same reason
linearize_load_gen
and linearize_store_gen exists in the first place. It does the same thing.

So the so call "obstacles" are actually what you need to handle bit
field correctly.
If you use linearized instruction in the first place, you would
already have bit field handle
correctly.

Let's review why you insist on duplicating the lineariztion effort.

1) You have the code already working.
    True. But a back end base on linearize instruction will be much
    simpler to your existing code. It does not make sense to have two
    linearization implementation while they can share one.

2) Require up stream changes.
    Let's compare apple to apple. Using linearize instruction to generate
    a output exactly as your V2 does not require up stream changes.
    It is not a reason to pick one over another.

3) Obstacles in linearize_load_gen and linearize_store_gen.
   It turns out those "obstacles" are exactly what you need to handle
   bit fields. One more reason to use linearize instructions.

4) More work to get the same results.
   It will be more work for *you* to write yet another back end.
   But if you compare the end result. The one base on linearize instruction
   will be much simpler. It will be less new code get check in to the sparse
   tree, less code for people other than you to review. Less work to maintain
   one implementation of linearization instead of two.

Still not convinced using the linearization is a better way to go?

Chris
--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik April 29, 2009, 10:25 p.m. UTC | #9
Christopher Li wrote:
> On Mon, Apr 27, 2009 at 6:21 PM, Jeff Garzik <jeff@garzik.org> wrote:
>> You mean, besides the reasons already listed?  Namely, no upstream changes
>> are required, and I already have something that works.
>>
>> Sure, the same trick can be applied.  But that requires a total backend
>> rewrite plus dealing with linearize obstacles already described (ref
>> linearize_load_gen, linearize_store_gen).  Thus it is obviously a lot more
>> work, with additional obstacles (patching upstream, which affects existing
>> users), just get back to achieving the same result :)
> 
> I take a look at the how LLVM handle bit fields. Please correct me if
> I am wrong.
> LLVM does not take bit field member as a type. Given the example:
> 
> struct s {
>    int a:3;
>    int b:3;
> } foo;
> 
> void func (struct s *a)
> {
>    a->a = 6;
> }
> 
> The LLVM outputs:
> 
> target datalayout =
> "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
> target triple = "i386-pc-linux-gnu"
> 	%struct.s = type <{ i8, [3 x i8] }>
> @foo = common global %struct.s zeroinitializer, align 4		;
> <%struct.s*> [#uses=0]
> 
> define void @func(%struct.s* nocapture %a) nounwind {
> entry:
> 	%0 = getelementptr %struct.s* %a, i32 0, i32 0		; <i8*> [#uses=2]
> 	%1 = load i8* %0, align 1		; <i8> [#uses=1]
> 	%2 = or i8 %1, 6		; <i8> [#uses=1]
> 	%3 = and i8 %2, -2		; <i8> [#uses=1]
> 	store i8 %3, i8* %0, align 1
> 	ret void
> }
> 
> LLVM does not treat bit field as first class types. It apply bit mask
> before/after
> the load/store to fix things up. It actually is the same reason
> linearize_load_gen
> and linearize_store_gen exists in the first place. It does the same thing.

LLVM treats all "i%u" types: i1, i11, i16, i32, i64, etc. as first class 
types.  See http://www.llvm.org/docs/LangRef.html

Therefore, the above is not a fundamental instrinsic of LLVM, but rather 
  how one LLVM _emitter_ -- either llvm-gcc or clang, I assume -- chose 
to handle bitfields.

Big, big difference!

Next, you can see from C99 6.7.2.1 P9, the layout of bit-fields may not 
necessary correspond to ad->bit_offset as linearize_{load,store}_gen assume.

Run "info gcc" and do a search on "bitfield", after reviewing C99 again. 
  linearize hardcodes behavior it should not.



> Let's review why you insist on duplicating the lineariztion effort.
> 
> 1) You have the code already working.
>     True. But a back end base on linearize instruction will be much
>     simpler to your existing code.

This is what we call in the engineering business a "wild-assed guess."

You have obviously not reviewed C99 on bitfields nor looked closely at 
LLVM, so judgement of "will be much simpler" is easily questioned.


> 2) Require up stream changes.
>     Let's compare apple to apple. Using linearize instruction to generate
>     a output exactly as your V2 does not require up stream changes.
>     It is not a reason to pick one over another.

That is a silly comparison, because my LLVM backend is not static at V2. 
It should be self-evident that one looks at future changes needed to 
complete an LLVM backend.


> 3) Obstacles in linearize_load_gen and linearize_store_gen.
>    It turns out those "obstacles" are exactly what you need to handle
>    bit fields. One more reason to use linearize instructions.

False, as shown above.


> 4) More work to get the same results.
>    It will be more work for *you* to write yet another back end.

Yes, and I am doing the work.  For fun.  So this is supremely relevant.

It's amusing how easily you hand-wave away my engineering time :) 
Shades of the kernel...


>    But if you compare the end result. The one base on linearize instruction
>    will be much simpler.

We have no end result to compare.


>    It will be less new code get check in to the sparse
>    tree, less code for people other than you to review. Less work to maintain
>    one implementation of linearization instead of two.

Do you not see the difference between simple tree walking, and forced 
generation of basic blocks and instructions chosen by sparse?  There are 
multiple tree walkers in sparse -- not just linearize.c.


Overall, I think linearized format is too limiting and low level for 
LLVM, and thus chose a different starting point.


I wouldn't mind seeing a single tree walker implementation in sparse, 
but it would be incorrect to assume that linearize.c is a generic tree 
walker.

	Jeff


--
To unsubscribe from this list: send the line "unsubscribe linux-sparse" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/.gitignore b/.gitignore
index 8ea8feb..9a1d027 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@  example
 test-unssa
 ctags
 c2xml
+slcc
 
 # tags
 tags
diff --git a/Makefile b/Makefile
index 15daba5..2d0be10 100644
--- a/Makefile
+++ b/Makefile
@@ -35,7 +35,7 @@  INCLUDEDIR=$(PREFIX)/include
 PKGCONFIGDIR=$(LIBDIR)/pkgconfig
 
 PROGRAMS=test-lexing test-parsing obfuscate compile graph sparse \
-	 test-linearize example test-unssa test-dissect ctags
+	 test-linearize example test-unssa test-dissect ctags slcc
 INST_PROGRAMS=sparse cgcc
 INST_MAN1=sparse.1 cgcc.1
 
@@ -108,6 +108,7 @@  sparse.pc: sparse.pc.in
 
 
 compile_EXTRA_DEPS = compile-i386.o
+slcc_EXTRA_DEPS = s2l-gen.o
 
 PROG_LINK_CMD = $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $($@_EXTRA_OBJS) 
 
diff --git a/s2l-gen.c b/s2l-gen.c
new file mode 100644
index 0000000..ac440b5
--- /dev/null
+++ b/s2l-gen.c
@@ -0,0 +1,2097 @@ 
+
+/*
+ * sparse/s2l-gen.c
+ *
+ * Copyright (C) 2003 Transmeta Corp.
+ *               2003 Linus Torvalds
+ * Copyright 2003 Jeff Garzik
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * Licensed under the Open Software License version 1.1
+ *
+ *
+ * A cheesy LLVM backend for sparse.  Outputs LLVM ASCII bitcode,
+ * given a C source code input.
+ *
+ *
+ * TODO list:
+ * 1) fill in TODO list
+ * 2) ?
+ * 3) Profit!
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "lib.h"
+#include "allocate.h"
+#include "token.h"
+#include "parse.h"
+#include "symbol.h"
+#include "scope.h"
+#include "expression.h"
+#include "target.h"
+#include "compile.h"
+#include "bitmap.h"
+
+struct textbuf {
+	unsigned int	len;	/* does NOT include terminating null */
+	char		*text;
+	struct textbuf	*next;
+	struct textbuf	*prev;
+};
+
+struct loop_stack {
+	int		continue_lbl;
+	int		loop_bottom_lbl;
+	struct loop_stack *next;
+};
+
+struct atom;
+struct storage;
+DECLARE_PTR_LIST(str_list, struct atom);
+DECLARE_PTR_LIST(atom_list, struct atom);
+DECLARE_PTR_LIST(storage_list, struct storage);
+
+struct function {
+	int stack_size;
+	int pseudo_nr;
+	struct storage_list *pseudo_list;
+	struct atom_list *atom_list;
+	struct str_list *str_list;
+	struct loop_stack *loop_stack;
+	struct symbol **argv;
+	unsigned int argc;
+	int ret_target;
+};
+
+enum storage_type {
+	STOR_PSEUDO,	/* variable stored on the stack */
+	STOR_ARG,	/* function argument */
+	STOR_SYM,	/* a symbol we can directly ref in the asm */
+	STOR_VALUE,	/* integer constant */
+	STOR_LABEL,	/* label / jump target */
+	STOR_LABELSYM,	/* label generated from symbol's pointer value */
+};
+
+struct storage {
+	enum storage_type type;
+	unsigned long flags;
+
+	/* STOR_REG */
+	struct symbol *ctype;
+
+	union {
+		/* STOR_PSEUDO */
+		struct {
+			int pseudo;
+			int offset;
+			int size;
+		};
+		/* STOR_ARG */
+		struct {
+			int idx;
+		};
+		/* STOR_SYM */
+		struct {
+			struct symbol *sym;
+		};
+		/* STOR_VALUE */
+		struct {
+			long long value;
+		};
+		/* STOR_LABEL */
+		struct {
+			int label;
+		};
+		/* STOR_LABELSYM */
+		struct {
+			struct symbol *labelsym;
+		};
+	};
+};
+
+enum {
+	STOR_LABEL_VAL	= (1 << 0),
+	STOR_WANTS_FREE	= (1 << 1),
+};
+
+struct symbol_private {
+	struct storage *addr;
+};
+
+enum atom_type {
+	ATOM_TEXT,
+	ATOM_CSTR,
+};
+
+struct atom {
+	enum atom_type type;
+	union {
+		/* stuff for text */
+		struct {
+			char *text;
+			unsigned int text_len;  /* w/o terminating null */
+		};
+
+		/* stuff for C strings */
+		struct {
+			struct string *string;
+			int label;
+		};
+	};
+};
+
+
+static struct function *current_func = NULL;
+static struct textbuf *unit_post_text = NULL;
+static const char *current_section;
+
+static void emit_comment(const char * fmt, ...) FORMAT_ATTR(1);
+static void emit_move(struct storage *src, struct storage *dest,
+		      struct symbol *ctype, const char *comment);
+static int type_is_signed(struct symbol *sym);
+static struct storage *s2l_gen_address(struct expression *expr);
+static struct storage *s2l_gen_symbol_expr(struct symbol *sym);
+static void s2l_gen_symbol(struct symbol *sym);
+static struct storage *s2l_gen_statement(struct statement *stmt);
+static struct storage *s2l_gen_expression(struct expression *expr);
+
+static void stor_sym_init(struct symbol *sym)
+{
+	struct storage *stor;
+	struct symbol_private *priv;
+
+	priv = calloc(1, sizeof(*priv) + sizeof(*stor));
+	if (!priv)
+		die("OOM in stor_sym_init");
+
+	stor = (struct storage *) (priv + 1);
+
+	priv->addr = stor;
+	stor->type = STOR_SYM;
+	stor->sym = sym;
+	stor->size = sym->ctype.base_type->bit_size / 8;
+
+	sym->aux = priv;
+}
+
+static const char *stor_op_name(struct storage *s)
+{
+	static char name[32];
+
+	switch (s->type) {
+	case STOR_PSEUDO:
+		sprintf(name, "%%tmp%u", s->pseudo);
+		break;
+	case STOR_ARG:
+		sprintf(name, "%%arg%u", s->idx);
+		break;
+	case STOR_SYM:
+		sprintf(name, "@%s", show_ident(s->sym->ident));
+		break;
+	case STOR_VALUE:
+		sprintf(name, "%Ld", s->value);
+		break;
+	case STOR_LABEL:
+		sprintf(name, "@L%d", s->label);
+		break;
+	case STOR_LABELSYM:
+		sprintf(name, "%%LS%p", s->labelsym);
+		break;
+	}
+
+	return name;
+}
+
+static struct atom *new_atom(enum atom_type type)
+{
+	struct atom *atom;
+
+	atom = calloc(1, sizeof(*atom));	/* TODO: chunked alloc */
+	if (!atom)
+		die("nuclear OOM");
+
+	atom->type = type;
+
+	return atom;
+}
+
+static inline void push_cstring(struct function *f, struct string *str,
+				int label)
+{
+	struct atom *atom;
+
+	atom = new_atom(ATOM_CSTR);
+	atom->string = str;
+	atom->label = label;
+
+	add_ptr_list(&f->str_list, atom);	/* note: _not_ atom_list */
+}
+
+static inline void push_atom(struct function *f, struct atom *atom)
+{
+	add_ptr_list(&f->atom_list, atom);
+}
+
+static void push_text_atom(struct function *f, const char *text)
+{
+	struct atom *atom = new_atom(ATOM_TEXT);
+
+	atom->text = strdup(text);
+	atom->text_len = strlen(text);
+
+	push_atom(f, atom);
+}
+
+static struct storage *new_storage(enum storage_type type)
+{
+	struct storage *stor;
+
+	stor = calloc(1, sizeof(*stor));
+	if (!stor)
+		die("OOM in new_storage");
+
+	stor->type = type;
+
+	return stor;
+}
+
+static struct storage *stack_alloc(int n_bytes)
+{
+	struct function *f = current_func;
+	struct storage *stor;
+
+	assert(f != NULL);
+
+	stor = new_storage(STOR_PSEUDO);
+	stor->type = STOR_PSEUDO;
+	stor->pseudo = f->pseudo_nr;
+	stor->offset = f->stack_size; /* FIXME: stack req. natural align */
+	stor->size = n_bytes;
+	f->stack_size += n_bytes;
+	f->pseudo_nr++;
+
+	add_ptr_list(&f->pseudo_list, stor);
+
+	return stor;
+}
+
+static struct storage *new_labelsym(struct symbol *sym)
+{
+	struct storage *stor;
+
+	stor = new_storage(STOR_LABELSYM);
+
+	if (stor) {
+		stor->flags |= STOR_WANTS_FREE;
+		stor->labelsym = sym;
+	}
+
+	return stor;
+}
+
+static int new_label(void)
+{
+	static int label = 0;
+	return ++label;
+}
+
+static void textbuf_push(struct textbuf **buf_p, const char *text)
+{
+	struct textbuf *tmp, *list = *buf_p;
+	unsigned int text_len = strlen(text);
+	unsigned int alloc_len = text_len + 1 + sizeof(*list);
+
+	tmp = calloc(1, alloc_len);
+	if (!tmp)
+		die("OOM on textbuf alloc");
+
+	tmp->text = ((void *) tmp) + sizeof(*tmp);
+	memcpy(tmp->text, text, text_len + 1);
+	tmp->len = text_len;
+
+	/* add to end of list */
+	if (!list) {
+		list = tmp;
+		tmp->prev = tmp;
+	} else {
+		tmp->prev = list->prev;
+		tmp->prev->next = tmp;
+		list->prev = tmp;
+	}
+	tmp->next = list;
+
+	*buf_p = list;
+}
+
+static void textbuf_emit(struct textbuf **buf_p)
+{
+	struct textbuf *tmp, *list = *buf_p;
+
+	while (list) {
+		tmp = list;
+		if (tmp->next == tmp)
+			list = NULL;
+		else {
+			tmp->prev->next = tmp->next;
+			tmp->next->prev = tmp->prev;
+			list = tmp->next;
+		}
+
+		fputs(tmp->text, stdout);
+
+		free(tmp);
+	}
+
+	*buf_p = list;
+}
+
+static void emit_comment(const char *fmt, ...)
+{
+	struct function *f = current_func;
+	static char tmpbuf[100] = "\t\t\t\t\t; ";
+	va_list args;
+	int i;
+
+	va_start(args, fmt);
+	i = vsnprintf(tmpbuf+7, sizeof(tmpbuf)-4, fmt, args);
+	va_end(args);
+	tmpbuf[i+7] = '\n';
+	tmpbuf[i+8] = '\0';
+	push_text_atom(f, tmpbuf);
+}
+
+static void emit_label (int label, const char *comment)
+{
+	struct function *f = current_func;
+	char s[64];
+
+	if (!comment)
+		sprintf(s, ".L%d:\n", label);
+	else
+		sprintf(s, ".L%d:\t\t\t\t\t; %s\n", label, comment);
+
+	push_text_atom(f, s);
+}
+
+static void emit_labelsym (struct symbol *sym, const char *comment)
+{
+	struct function *f = current_func;
+	char s[64];
+
+	if (!comment)
+		sprintf(s, ".LS%p:\n", sym);
+	else
+		sprintf(s, ".LS%p:\t\t\t\t; %s\n", sym, comment);
+
+	push_text_atom(f, s);
+}
+
+void emit_unit_begin(const char *basename)
+{
+}
+
+void emit_unit_end(void)
+{
+	textbuf_emit(&unit_post_text);
+}
+
+/* conditionally switch sections */
+static void emit_section(const char *s)
+{
+	if (s == current_section)
+		return;
+	if (current_section && (!strcmp(s, current_section)))
+		return;
+
+#if 0
+	printf("\t%s\n", s);
+#endif
+	current_section = s;
+}
+
+static void emit_atom_list(struct function *f)
+{
+	struct atom *atom;
+
+	FOR_EACH_PTR(f->atom_list, atom) {
+		switch (atom->type) {
+		case ATOM_TEXT: {
+			ssize_t rc = write(STDOUT_FILENO, atom->text,
+					   atom->text_len);
+			(void) rc;	/* FIXME */
+			break;
+		}
+		case ATOM_CSTR:
+			assert(0);
+			break;
+		}
+	} END_FOR_EACH_PTR(atom);
+}
+
+static void emit_string_list(struct function *f)
+{
+	struct atom *atom;
+	size_t len;
+
+	emit_section(".section\t.rodata");
+
+	FOR_EACH_PTR(f->str_list, atom) {
+		/* FIXME: escape " in string */
+		/* FIXME 2: add trailing nul!!! */
+		len = strlen(show_string(atom->string)),
+		printf("@.L%d = internal constant [%lu x i8] c%s\n",
+		       atom->label,
+		       (unsigned long) len,
+		       show_string(atom->string));
+
+		free(atom);
+	} END_FOR_EACH_PTR(atom);
+
+	printf("\n");
+}
+
+static void func_cleanup(struct function *f)
+{
+	struct storage *stor;
+	struct atom *atom;
+
+	FOR_EACH_PTR(f->pseudo_list, stor) {
+		free(stor);
+	} END_FOR_EACH_PTR(stor);
+
+	FOR_EACH_PTR(f->atom_list, atom) {
+		if ((atom->type == ATOM_TEXT) && (atom->text))
+			free(atom->text);
+		free(atom);
+	} END_FOR_EACH_PTR(atom);
+
+	free_ptr_list(&f->pseudo_list);
+	free(f);
+}
+
+static const char *s2l_show_type(struct symbol *base_type)
+{
+	static char buf[256];
+
+	if (base_type == &void_ctype)
+		return "void";
+
+	sprintf(buf, "i%d", base_type->bit_size);
+	return buf;
+}
+
+/* function prologue */
+static void emit_func_pre(struct symbol *sym)
+{
+	struct function *f;
+	struct symbol *arg, *arg_bt;
+	unsigned int i, argc = 0, alloc_len;
+	unsigned char *mem;
+	struct symbol_private *privbase;
+	struct storage *storage_base;
+	struct symbol *base_type = sym->ctype.base_type;
+	struct symbol *ret_type = sym->ctype.base_type->ctype.base_type;
+	int first_arg = 1;
+	char defstr[512], stmp[128];
+
+	sprintf(defstr, "define %s%s @%s (",
+	       (sym->ctype.modifiers & MOD_STATIC) ? "internal " : "",
+	       s2l_show_type(ret_type),
+	       show_ident(sym->ident));
+
+	FOR_EACH_PTR(base_type->arguments, arg) {
+		const char *s;
+
+		arg_bt = arg->ctype.base_type;
+
+		s = s2l_show_type(arg_bt);
+
+		sprintf(stmp, "%s%s %%arg%u",
+		       first_arg ? "" : ", ",
+		       s, argc++);
+
+		strcat(defstr, stmp);
+
+		first_arg = 0;
+	} END_FOR_EACH_PTR(arg);
+
+	strcat(defstr, ") nounwind {\nentry:\n");
+
+	alloc_len =
+		sizeof(*f) +
+		(argc * sizeof(struct symbol *)) +
+		(argc * sizeof(struct symbol_private)) +
+		(argc * sizeof(struct storage));
+	mem = calloc(1, alloc_len);
+	if (!mem)
+		die("OOM on func info");
+
+	f		=  (struct function *) mem;
+	mem		+= sizeof(*f);
+	f->argv		=  (struct symbol **) mem;
+	mem		+= (argc * sizeof(struct symbol *));
+	privbase	=  (struct symbol_private *) mem;
+	mem		+= (argc * sizeof(struct symbol_private));
+	storage_base	=  (struct storage *) mem;
+
+	f->argc = argc;
+	f->ret_target = new_label();
+
+	i = 0;
+	FOR_EACH_PTR(base_type->arguments, arg) {
+		f->argv[i] = arg;
+		arg->aux = &privbase[i];
+		storage_base[i].type = STOR_ARG;
+		storage_base[i].idx = i;
+		storage_base[i].size = arg->ctype.base_type->bit_size / 8;
+		privbase[i].addr = &storage_base[i];
+		i++;
+	} END_FOR_EACH_PTR(arg);
+
+	assert(current_func == NULL);
+	current_func = f;
+
+	push_text_atom(current_func, defstr);
+
+}
+
+/* function epilogue */
+static void emit_func_post(struct symbol *sym)
+{
+	struct function *f = current_func;
+
+	if (f->str_list)
+		emit_string_list(f);
+
+	/* function epilogue */
+
+	/* output everything to stdout */
+	fflush(stdout);		/* paranoia; needed? */
+	emit_atom_list(f);
+
+	/* function footer */
+	printf("}\n\n");
+
+	/* FIXME: issue 'ret' if not already done */
+
+	func_cleanup(f);
+	current_func = NULL;
+}
+
+#if 0
+/* emit object (a.k.a. variable, a.k.a. data) prologue */
+static void emit_object_pre(const char *name, unsigned long modifiers,
+			    unsigned long alignment, unsigned int byte_size)
+{
+	char defstr[128];
+
+	sprintf(defstr, "@%s = %sglobal i%d 0, align %lu\n",
+		name,
+		modifiers & MOD_STATIC ? "internal " : "",
+		byte_size * 8,
+		alignment);
+	textbuf_push(&unit_post_text, defstr);
+
+	if ((modifiers & MOD_STATIC) == 0)
+		printf(".globl %s\n", name);
+	emit_section(".data");
+	if (alignment)
+		printf("\t.align %lu\n", alignment);
+	printf("\t.type\t%s, @object\n", name);
+	printf("\t.size\t%s, %d\n", name, byte_size);
+	printf("%s:\n", name);
+}
+#endif
+
+/* emit value (only) for an initializer scalar */
+static void emit_scalar(struct symbol *sym, int bit_size)
+{
+	struct expression *expr = sym->initializer;
+
+	assert(expr->type == EXPR_VALUE);
+
+	char defstr[128];
+
+	sprintf(defstr, "@%s = %sglobal i%d %lld, align %lu\n",
+		show_ident(sym->ident),
+		sym->ctype.modifiers & MOD_STATIC ? "internal " : "",
+		bit_size,
+		expr->value,
+		sym->ctype.alignment);
+	textbuf_push(&unit_post_text, defstr);
+}
+
+static void emit_global_noinit(const char *name, unsigned long modifiers,
+			       unsigned long alignment, unsigned int byte_size)
+{
+	char defstr[128];
+
+	sprintf(defstr, "@%s = %sglobal i%d 0, align %lu\n",
+		name,
+		modifiers & MOD_STATIC ? "internal " : "",
+		byte_size * 8,
+		alignment);
+	textbuf_push(&unit_post_text, defstr);
+}
+
+static int ea_current, ea_last;
+
+static void emit_array_initializer(struct symbol *sym,
+			     struct expression *expr)
+{
+	int distance = ea_current - ea_last - 1;
+
+	if (distance > 0)
+		printf("\t.zero\t%d\n", (sym->bit_size / 8) * distance);
+
+	if (expr->type == EXPR_VALUE) {
+		struct symbol *base_type = sym->ctype.base_type;
+		assert(base_type != NULL);
+
+		emit_scalar(sym, sym->bit_size / get_expression_value(base_type->array_size));
+		return;
+	}
+	if (expr->type != EXPR_INITIALIZER)
+		return;
+
+	assert(0); /* FIXME */
+}
+
+static int sort_array_cmp(const struct expression *a,
+			  const struct expression *b)
+{
+	int a_ofs = 0, b_ofs = 0;
+
+	if (a->type == EXPR_POS)
+		a_ofs = (int) a->init_offset;
+	if (b->type == EXPR_POS)
+		b_ofs = (int) b->init_offset;
+
+	return a_ofs - b_ofs;
+}
+
+/* move to front-end? */
+static void sort_array(struct expression *expr)
+{
+	struct expression *entry, **list;
+	unsigned int elem, sorted, i;
+
+	elem = 0;
+	FOR_EACH_PTR(expr->expr_list, entry) {
+		elem++;
+	} END_FOR_EACH_PTR(entry);
+
+	if (!elem)
+		return;
+
+	list = malloc(sizeof(entry) * elem);
+	if (!list)
+		die("OOM in sort_array");
+
+	/* this code is no doubt evil and ignores EXPR_INDEX possibly
+	 * to its detriment and other nasty things.  improvements
+	 * welcome.
+	 */
+	i = 0;
+	sorted = 0;
+	FOR_EACH_PTR(expr->expr_list, entry) {
+		if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE)) {
+			/* add entry to list[], in sorted order */
+			if (sorted == 0) {
+				list[0] = entry;
+				sorted = 1;
+			} else {
+				for (i = 0; i < sorted; i++)
+					if (sort_array_cmp(entry, list[i]) <= 0)
+						break;
+
+				/* If inserting into the middle of list[]
+				 * instead of appending, we memmove.
+				 * This is ugly, but thankfully
+				 * uncommon.  Input data with tons of
+				 * entries very rarely have explicit
+				 * offsets.  convert to qsort eventually...
+				 */
+				if (i != sorted)
+					memmove(&list[i + 1], &list[i],
+						(sorted - i) * sizeof(entry));
+				list[i] = entry;
+				sorted++;
+			}
+		}
+	} END_FOR_EACH_PTR(entry);
+
+	i = 0;
+	FOR_EACH_PTR(expr->expr_list, entry) {
+		if ((entry->type == EXPR_POS) || (entry->type == EXPR_VALUE))
+			*THIS_ADDRESS(entry) = list[i++];
+	} END_FOR_EACH_PTR(entry);
+
+}
+
+static void emit_array(struct symbol *sym)
+{
+	struct symbol *base_type = sym->ctype.base_type;
+	struct expression *expr = sym->initializer;
+	struct expression *entry;
+	int sym_bytes = sym->bit_size / 8;
+	int base_bytes = base_type->bit_size / 8;
+
+	assert(base_type != NULL);
+	assert(base_bytes > 0);
+	assert(sym_bytes > 0);
+
+	stor_sym_init(sym);
+
+	ea_last = -1;
+
+#if 0
+	emit_object_pre(show_ident(sym->ident), sym->ctype.modifiers,
+		        sym->ctype.alignment,
+			sym_bytes);
+#endif
+
+	sort_array(expr);
+
+	FOR_EACH_PTR(expr->expr_list, entry) {
+		if (entry->type == EXPR_VALUE) {
+			ea_current = 0;
+			emit_array_initializer(sym, entry);
+			ea_last = ea_current;
+		} else if (entry->type == EXPR_POS) {
+			ea_current = entry->init_offset / base_bytes;
+			emit_array_initializer(sym, entry->init_expr);
+			ea_last = ea_current;
+		}
+	} END_FOR_EACH_PTR(entry);
+}
+
+void emit_one_symbol(struct symbol *sym)
+{
+	s2l_gen_symbol(sym);
+}
+
+static void emit_copy(struct storage *src, struct storage *dest,
+		      struct symbol *ctype)
+{
+	struct storage *tmp;
+	unsigned int bit_size;
+
+	/* FIXME: Bitfield copy! */
+
+	bit_size = src->size * 8;
+	if (!bit_size)
+		bit_size = 32;
+	if ((src->type == STOR_ARG) && (bit_size < 32))
+		bit_size = 32;
+
+	tmp = stack_alloc(bit_size / 8);
+
+	emit_move(src, tmp, ctype, "begin copy ..");
+
+	bit_size = dest->size * 8;
+	if (!bit_size)
+		bit_size = 32;
+	if ((dest->type == STOR_ARG) && (bit_size < 32))
+		bit_size = 32;
+
+	emit_move(tmp, dest, ctype, ".... end copy");
+}
+
+static void emit_store(struct expression *dest_expr, struct storage *dest,
+		       struct storage *src, int bits)
+{
+	/* FIXME: Bitfield store! */
+	printf("\tst.%d\t\tv%d,[v%d]\n", bits, src->pseudo, dest->pseudo);
+}
+
+static void emit_scalar_noinit(struct symbol *sym)
+{
+	emit_global_noinit(show_ident(sym->ident),
+			   sym->ctype.modifiers, sym->ctype.alignment,
+			   sym->bit_size / 8);
+	stor_sym_init(sym);
+}
+
+static void emit_array_noinit(struct symbol *sym)
+{
+	emit_global_noinit(show_ident(sym->ident),
+			   sym->ctype.modifiers, sym->ctype.alignment,
+			   get_expression_value(sym->array_size) * (sym->bit_size / 8));
+	stor_sym_init(sym);
+}
+
+static void emit_move(struct storage *src, struct storage *dest,
+		      struct symbol *ctype, const char *comment)
+{
+	unsigned int bits;
+	unsigned int is_signed;
+	char insnstr[128];
+	char stor_src[16], stor_dest[16];
+
+	if (ctype) {
+		bits = ctype->bit_size;
+		is_signed = type_is_signed(ctype);
+	} else {
+		bits = 32;
+		is_signed = 0;
+	}
+
+	strcpy(stor_src, stor_op_name(src));
+	strcpy(stor_dest, stor_op_name(dest));
+
+	sprintf(insnstr, "\t%s = add i%d 0, %s\t; %s\n",
+		stor_dest,
+		bits,
+		stor_src,
+		comment ? comment : "copy val");
+	push_text_atom(current_func, insnstr);
+}
+
+static struct storage *emit_compare(struct expression *expr)
+{
+	struct function *f = current_func;
+	struct storage *left = s2l_gen_expression(expr->left);
+	struct storage *right = s2l_gen_expression(expr->right);
+	struct storage *val;
+	const char *opname = NULL;
+	unsigned int right_bits = expr->right->ctype->bit_size;
+	char insnstr[128];
+	char stor_val[16], stor_left[16], stor_right[16];
+
+	switch(expr->op) {
+	case '<':
+		opname = "slt";
+		break;
+	case SPECIAL_UNSIGNED_LT:
+		opname = "ult";
+		break;
+	case '>':
+		opname = "sgt";
+		break;
+	case SPECIAL_UNSIGNED_GT:
+		opname = "ugt";
+		break;
+	case SPECIAL_LTE:
+		opname = "sle";
+		break;
+	case SPECIAL_UNSIGNED_LTE:
+		opname = "ule";
+		break;
+	case SPECIAL_GTE:
+		opname = "sge";
+		break;
+	case SPECIAL_UNSIGNED_GTE:
+		opname = "uge";
+		break;
+	case SPECIAL_EQUAL:
+		opname = "eq";
+		break;
+	case SPECIAL_NOTEQUAL:
+		opname = "ne";
+		break;
+	default:
+		assert(0);
+		break;
+	}
+
+	/* init to 0 */
+	val = stack_alloc(right_bits / 8);
+	val->flags = STOR_WANTS_FREE;
+
+	strcpy(stor_val, stor_op_name(val));
+	strcpy(stor_left, stor_op_name(left));
+	strcpy(stor_right, stor_op_name(right));
+
+	sprintf(insnstr, "\t%s = icmp %s i%d %s, %s\n",
+		stor_val,
+		opname,
+		right_bits,
+		stor_left,
+		stor_right);
+	push_text_atom(f, insnstr);
+
+	return val;
+}
+
+static struct storage *emit_value(struct expression *expr)
+{
+	struct storage *val;
+
+	val = new_storage(STOR_VALUE);
+	val->value = (long long) expr->value;
+
+	return val;	/* FIXME: memory leak */
+}
+
+static struct storage *emit_binop(struct expression *expr)
+{
+	struct function *f = current_func;
+	struct storage *left = s2l_gen_expression(expr->left);
+	struct storage *right = s2l_gen_expression(expr->right);
+	struct storage *new;
+	const char *opname = NULL;
+	char insnstr[128];
+	char stor_new[16], stor_left[16], stor_right[16];
+	int is_signed;
+
+	is_signed = type_is_signed(expr->ctype);
+
+	switch (expr->op) {
+	case '+':
+		opname = "add";
+		break;
+	case '-':
+		opname = "sub";
+		break;
+	case '&':
+		opname = "and";
+		break;
+	case '|':
+		opname = "or";
+		break;
+	case '^':
+		opname = "xor";
+		break;
+	case SPECIAL_LEFTSHIFT:
+		opname = "shl";
+		break;
+	case SPECIAL_RIGHTSHIFT:
+		if (is_signed)
+			opname = "lshr";
+		else
+			opname = "ashr";
+		break;
+	case '*':
+		opname = "mul";
+		break;
+	case '/':
+		if (is_signed)
+			opname = "sdiv";
+		else
+			opname = "udiv";
+		break;
+	case '%':
+		if (is_signed)
+			opname = "srem";
+		else
+			opname = "urem";
+		break;
+	default:
+		error_die(expr->pos, "unhandled binop '%s'\n", show_special(expr->op));
+		break;
+	}
+
+	new = stack_alloc(expr->ctype->bit_size / 8);
+
+	strcpy(stor_new, stor_op_name(new));
+	strcpy(stor_left, stor_op_name(left));
+	strcpy(stor_right, stor_op_name(right));
+
+	sprintf(insnstr, "\t%s = %s i%d %s, %s\n",
+		stor_new,
+		opname,
+		expr->ctype->bit_size,
+		stor_left,
+		stor_right);
+	push_text_atom(f, insnstr);
+
+	return new;
+}
+
+static void emit_conditional_test(struct storage *val,
+				  struct storage **t_out,
+				  struct storage **f_out)
+{
+	struct storage *tmp, *lbl_true, *lbl_false;
+	int target_true, target_false;
+	char insnstr[128];
+	char stor_val[16], stor_tmp[16];
+	char stor_true[16], stor_false[16];
+
+	emit_comment("begin if/conditional");
+
+	tmp = stack_alloc(val->size);
+
+	target_true = new_label();
+	lbl_true = new_storage(STOR_LABEL);
+	lbl_true->label = target_true;
+	lbl_true->flags = STOR_WANTS_FREE;
+
+	*t_out = lbl_true;
+
+	target_false = new_label();
+	lbl_false = new_storage(STOR_LABEL);
+	lbl_false->label = target_false;
+	lbl_false->flags = STOR_WANTS_FREE;
+
+	*f_out = lbl_false;
+
+	strcpy(stor_val, stor_op_name(val));
+	strcpy(stor_tmp, stor_op_name(tmp));
+	strcpy(stor_true, stor_op_name(lbl_true));
+	strcpy(stor_false, stor_op_name(lbl_false));
+
+	sprintf(insnstr, "\t%s = icmp eq i%d 0, %s\n",
+		stor_tmp,
+		val->size * 8,
+		stor_val);
+	push_text_atom(current_func, insnstr);
+
+	sprintf(insnstr, "\tbr i1 %s, label %s, label %s\n",
+		stor_tmp,
+		stor_true,
+		stor_false);
+	push_text_atom(current_func, insnstr);
+}
+
+static void emit_conditional_end(struct storage *l_false,
+				 struct storage *cond_end_st)
+{
+	char insnstr[128];
+
+	sprintf(insnstr, "\tbr label %s\n",
+		stor_op_name(cond_end_st));
+	push_text_atom(current_func, insnstr);
+
+	emit_label(l_false->label, "if false");
+}
+
+static void emit_if_conditional(struct statement *stmt)
+{
+	struct storage *val;
+	struct storage *l_true = NULL, *l_false = NULL;
+	struct storage *cond_end_st;
+	int cond_end;
+
+	cond_end = new_label();
+	cond_end_st = new_storage(STOR_LABEL);
+	cond_end_st->label = cond_end;
+	cond_end_st->flags = STOR_WANTS_FREE;
+
+	/* emit test portion of conditional */
+	val = s2l_gen_expression(stmt->if_conditional);
+	emit_conditional_test(val, &l_true, &l_false);
+
+	emit_label(l_true->label, "if true");
+
+	/* emit if-true statement */
+	s2l_gen_statement(stmt->if_true);
+
+	emit_conditional_end(l_false, cond_end_st);
+
+	/* emit if-false statement, if present */
+	s2l_gen_statement(stmt->if_false);
+
+	/* end of conditional; jump target for if-true branch */
+	emit_label(cond_end, "end if");
+}
+
+static struct storage *emit_inc_dec(struct expression *expr, int postop)
+{
+	struct storage *addr = s2l_gen_address(expr->unop);
+	struct storage *retval;
+	char opname[16];
+	char insnstr[128];
+	char stor_addr[16], stor_retval[16];
+
+	strcpy(opname, expr->op == SPECIAL_INCREMENT ? "add" : "sub");
+
+	if (postop) {
+		struct storage *new = stack_alloc(4);
+
+		emit_copy(addr, new, expr->unop->ctype);
+
+		retval = new;
+	} else
+		retval = addr;
+
+	strcpy(stor_addr, stor_op_name(addr));
+	strcpy(stor_retval, stor_op_name(retval));
+
+	sprintf(insnstr, "\t%s = %s i%d %s, 1\n",
+		stor_retval,
+		opname,
+		expr->ctype->bit_size,
+		stor_addr);
+	push_text_atom(current_func, insnstr);
+
+	return retval;
+}
+
+static struct storage *emit_postop(struct expression *expr)
+{
+	return emit_inc_dec(expr, 1);
+}
+
+static struct storage *emit_return_stmt(struct statement *stmt)
+{
+	struct function *f = current_func;
+	struct expression *expr = stmt->ret_value;
+	struct storage *val = NULL;
+
+	if (expr && expr->ctype) {
+		char s[64];
+
+		val = s2l_gen_expression(expr);
+		assert(val != NULL);
+
+		sprintf(s, "\tret i%d %s\n",
+			val->size * 8,
+			stor_op_name(val));
+		push_text_atom(f, s);
+	} else
+		push_text_atom(f, "\tret void\n");
+
+	return val;
+}
+
+static struct storage *emit_conditional_expr(struct expression *expr)
+{
+	struct storage *cond, *true = NULL, *false = NULL;
+	struct storage *new = stack_alloc(expr->ctype->bit_size / 8);
+	struct storage *l_true = NULL, *l_false = NULL;
+	struct storage *cond_end_st;
+	int cond_end;
+
+	cond_end = new_label();
+	cond_end_st = new_storage(STOR_LABEL);
+	cond_end_st->label = cond_end;
+	cond_end_st->flags = STOR_WANTS_FREE;
+
+	/* evaluate conditional */
+	cond = s2l_gen_expression(expr->conditional);
+	emit_conditional_test(cond, &l_true, &l_false);
+
+	emit_label(l_true->label, "if true");
+
+	/* handle if-true part of the expression */
+	true = s2l_gen_expression(expr->cond_true);
+
+	emit_copy(true, new, expr->ctype);
+
+	emit_conditional_end(l_false, cond_end_st);
+
+	/* handle if-false part of the expression */
+	false = s2l_gen_expression(expr->cond_false);
+
+	emit_copy(false, new, expr->ctype);
+
+	/* end of conditional; jump target for if-true branch */
+	emit_label(cond_end, "end conditional");
+
+	return new;
+}
+
+static struct storage *emit_symbol_expr_init(struct symbol *sym)
+{
+	struct expression *expr = sym->initializer;
+	struct symbol_private *priv = sym->aux;
+
+	if (priv == NULL) {
+		priv = calloc(1, sizeof(*priv));
+		sym->aux = priv;
+
+		if (expr == NULL) {
+			struct storage *new = stack_alloc(4);
+			fprintf(stderr, "FIXME! no value for symbol %s.  creating pseudo %d (stack offset %d)\n",
+				show_ident(sym->ident),
+				new->pseudo, new->pseudo * 4);
+			priv->addr = new;
+		} else {
+			priv->addr = s2l_gen_expression(expr);
+		}
+	}
+
+	return priv->addr;
+}
+
+static struct storage *emit_string_expr(struct expression *expr)
+{
+	struct function *f = current_func;
+	int label = new_label();
+	struct storage *new;
+
+	push_cstring(f, expr->string, label);
+
+	new = new_storage(STOR_LABEL);
+	new->label = label;
+	new->flags = STOR_LABEL_VAL | STOR_WANTS_FREE;
+	return new;
+}
+
+static struct storage *emit_cast_expr(struct expression *expr)
+{
+	struct symbol *old_type, *new_type;
+	struct storage *op = s2l_gen_expression(expr->cast_expression);
+	int oldbits, newbits, old_is_signed;
+	struct storage *new;
+	char insnstr[128];
+	char stor_src[16], stor_dest[16];
+
+	old_type = expr->cast_expression->ctype;
+	old_is_signed = type_is_signed(expr->cast_expression->ctype);
+	new_type = expr->cast_type;
+
+	oldbits = old_type->bit_size;
+	newbits = new_type->bit_size;
+	if (oldbits >= newbits)
+		return op;
+
+	new = stack_alloc(newbits / 8);
+
+	strcpy(stor_src, stor_op_name(op));
+	strcpy(stor_dest, stor_op_name(new));
+
+	sprintf(insnstr, "\t%s = %cext i%d %s to i%d\n",
+		stor_dest,
+		old_is_signed ? 's' : 'z',
+		oldbits,
+		stor_src,
+		newbits);
+	push_text_atom(current_func, insnstr);
+
+	return new;
+}
+
+static struct storage *emit_regular_preop(struct expression *expr)
+{
+	struct storage *target = s2l_gen_expression(expr->unop);
+	struct storage *val;
+	int bit_size = expr->unop->ctype->bit_size;
+	struct storage *new;
+	char insnstr[128];
+	char stor_target[16];
+	int lbl_true, lbl_false, lbl_end;
+
+	new = stack_alloc(bit_size / 8);
+
+	strcpy(stor_target, stor_op_name(target));
+
+	switch (expr->op) {
+	case '!':
+		/* compare target with zero */
+		sprintf(insnstr, "\t%%tmp = icmp i%d eq 0, %s\n",
+			bit_size,
+			stor_op_name(target));
+		push_text_atom(current_func, insnstr);
+
+		lbl_true = new_label();
+		lbl_false = new_label();
+		lbl_end = new_label();
+
+		/* if true, goto lbl_true, else goto lbl_false */
+		sprintf(insnstr, "\tbr i1 %%tmp, label %%L%d, label %%L%d\n",
+			lbl_true, lbl_false);
+		push_text_atom(current_func, insnstr);
+
+		/* label lbl_true outputted */
+		emit_label(lbl_true, "preop '!': value is zero");
+
+		/* move constant 1 to result */
+		val = new_storage(STOR_VALUE);
+		val->flags = STOR_WANTS_FREE;
+		val->value = 1;
+
+		emit_move(val, new, expr->unop->ctype, NULL);
+
+		/* jump to end of comparison */
+		sprintf(insnstr, "\tbr label %%L%d\n", lbl_end);
+
+		/* label lbl_false outputted */
+		emit_label(lbl_false, "preop '!': value not zero");
+
+		/* move constant 0 to result */
+		val = new_storage(STOR_VALUE);
+		val->flags = STOR_WANTS_FREE;
+		val->value = 0;
+
+		emit_move(val, new, expr->unop->ctype, NULL);
+
+		/* emit end label */
+		emit_label(lbl_end, "end preop '!'");
+		break;
+
+	case '~':
+		sprintf(insnstr, "\t%s = xor i%d %s, -1\n",
+			stor_op_name(new),
+			bit_size,
+			stor_target);
+		push_text_atom(current_func, insnstr);
+		break;
+
+	case '-':
+		sprintf(insnstr, "\t%s = sub i%d 0, %s\n",
+			stor_op_name(new),
+			bit_size,
+			stor_target);
+		push_text_atom(current_func, insnstr);
+		break;
+	default:
+		assert(0);
+		break;
+	}
+
+	return new;
+}
+
+static void emit_case_statement(struct statement *stmt)
+{
+	emit_labelsym(stmt->case_label, NULL);
+	s2l_gen_statement(stmt->case_statement);
+}
+
+static void emit_switch_statement(struct statement *stmt)
+{
+	struct storage *val = s2l_gen_expression(stmt->switch_expression);
+	struct symbol *sym, *default_sym = NULL;
+	struct storage *labelsym;
+	char insnstr[128];
+	char stor_val[16], stor_lbl_def[16], stor_lbl_sym[16];
+
+	FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
+		struct statement *case_stmt = sym->stmt;
+		struct expression *expr = case_stmt->case_expression;
+		if (!expr)
+			default_sym = sym;
+	} END_FOR_EACH_PTR(sym);
+
+	if (default_sym)
+		labelsym = new_labelsym(default_sym);
+	else {
+		labelsym = new_storage(STOR_LABEL);
+		labelsym->label = new_label();
+		labelsym->flags = STOR_WANTS_FREE;
+	}
+
+	strcpy(stor_val, stor_op_name(val));
+	strcpy(stor_lbl_def, stor_op_name(labelsym));
+
+	sprintf(insnstr, "\tswitch i%d %s, label %s [\n",
+		val->size * 8,
+		stor_val,
+		stor_lbl_def);
+	push_text_atom(current_func, insnstr);
+
+	FOR_EACH_PTR(stmt->switch_case->symbol_list, sym) {
+		struct statement *case_stmt = sym->stmt;
+		struct expression *expr = case_stmt->case_expression;
+		/* struct expression *to = case_stmt->case_to; FIXME! */
+
+		/* case NNN: */
+		if (expr) {
+			assert (expr->type == EXPR_VALUE);
+
+			labelsym = new_labelsym(sym);
+			strcpy(stor_lbl_sym, stor_op_name(labelsym));
+
+			sprintf(insnstr, "\t\t\t\ti%d %lld, label %s\n",
+				val->size * 8,
+				expr->value,
+				stor_lbl_sym);
+			push_text_atom(current_func, insnstr);
+		}
+	} END_FOR_EACH_PTR(sym);
+
+	strcpy(insnstr, "\t\t\t\t]\n");
+	push_text_atom(current_func, insnstr);
+
+	s2l_gen_statement(stmt->switch_statement);
+
+	if (stmt->switch_break->used)
+		emit_labelsym(stmt->switch_break, NULL);
+}
+
+static void s2l_gen_struct_member(struct symbol *sym)
+{
+	printf("\t%s:%d:%ld at offset %ld.%d", show_ident(sym->ident), sym->bit_size, sym->ctype.alignment, sym->offset, sym->bit_offset);
+	printf("\n");
+}
+
+static void s2l_gen_symbol(struct symbol *sym)
+{
+	struct symbol *type;
+
+	if (!sym)
+		return;
+
+	type = sym->ctype.base_type;
+	if (!type)
+		return;
+
+	/*
+	 * Show actual implementation information
+	 */
+	switch (type->type) {
+
+	case SYM_ARRAY:
+		if (sym->initializer)
+			emit_array(sym);
+		else
+			emit_array_noinit(sym);
+		break;
+
+	case SYM_BASETYPE:
+		if (sym->initializer) {
+			emit_scalar(sym, sym->bit_size);
+			stor_sym_init(sym);
+		} else
+			emit_scalar_noinit(sym);
+		break;
+
+	case SYM_STRUCT:
+	case SYM_UNION: {
+		struct symbol *member;
+
+		printf(" {\n");
+		FOR_EACH_PTR(type->symbol_list, member) {
+			s2l_gen_struct_member(member);
+		} END_FOR_EACH_PTR(member);
+		printf("}\n");
+		break;
+	}
+
+	case SYM_FN: {
+		struct statement *stmt = type->stmt;
+		if (stmt) {
+			emit_func_pre(sym);
+			s2l_gen_statement(stmt);
+			emit_func_post(sym);
+		}
+		break;
+	}
+
+	default:
+		break;
+	}
+
+	if (sym->initializer && (type->type != SYM_BASETYPE) &&
+	    (type->type != SYM_ARRAY)) {
+		printf(" = \n");
+		s2l_gen_expression(sym->initializer);
+	}
+}
+
+static void s2l_gen_symbol_init(struct symbol *sym);
+
+static void s2l_gen_symbol_decl(struct symbol_list *syms)
+{
+	struct symbol *sym;
+	FOR_EACH_PTR(syms, sym) {
+		s2l_gen_symbol_init(sym);
+	} END_FOR_EACH_PTR(sym);
+}
+
+static void loopstk_push(int cont_lbl, int loop_bottom_lbl)
+{
+	struct function *f = current_func;
+	struct loop_stack *ls;
+
+	ls = malloc(sizeof(*ls));
+	ls->continue_lbl = cont_lbl;
+	ls->loop_bottom_lbl = loop_bottom_lbl;
+	ls->next = f->loop_stack;
+	f->loop_stack = ls;
+}
+
+static void loopstk_pop(void)
+{
+	struct function *f = current_func;
+	struct loop_stack *ls;
+
+	assert(f->loop_stack != NULL);
+	ls = f->loop_stack;
+	f->loop_stack = f->loop_stack->next;
+	free(ls);
+}
+
+static int loopstk_break(void)
+{
+	return current_func->loop_stack->loop_bottom_lbl;
+}
+
+static int loopstk_continue(void)
+{
+	return current_func->loop_stack->continue_lbl;
+}
+
+static void emit_loop(struct statement *stmt)
+{
+	struct statement  *pre_statement = stmt->iterator_pre_statement;
+	struct expression *pre_condition = stmt->iterator_pre_condition;
+	struct statement  *statement = stmt->iterator_statement;
+	struct statement  *post_statement = stmt->iterator_post_statement;
+	struct expression *post_condition = stmt->iterator_post_condition;
+	int loop_top = 0, loop_bottom, loop_continue;
+	int have_bottom = 0, dummy;
+	struct storage *val, *tmp;
+	char insnstr[128];
+	char stor_val[16], stor_tmp[16], stor_lbv[16];
+
+	loop_bottom = new_label();
+	loop_continue = new_label();
+	loopstk_push(loop_continue, loop_bottom);
+
+	s2l_gen_symbol_decl(stmt->iterator_syms);
+	s2l_gen_statement(pre_statement);
+
+	loop_top = new_label();
+	emit_label(loop_top, "loop top");
+
+	if (pre_condition) {
+		if (pre_condition->type == EXPR_VALUE) {
+			if (!pre_condition->value) {
+				struct storage *lbv;
+				lbv = new_storage(STOR_LABEL);
+				lbv->label = loop_bottom;
+				lbv->flags = STOR_WANTS_FREE;
+
+				sprintf(insnstr, "\tbr label %s\n",
+					stor_op_name(lbv));
+				push_text_atom(current_func, insnstr);
+
+				have_bottom = 1;
+			}
+		} else {
+			struct storage *lbv;
+
+			lbv = new_storage(STOR_LABEL);
+			lbv->label = loop_bottom;
+			lbv->flags = STOR_WANTS_FREE;
+			have_bottom = 1;
+
+			val = s2l_gen_expression(pre_condition);
+
+			tmp = stack_alloc(val->size);
+
+			strcpy(stor_val, stor_op_name(val));
+			strcpy(stor_tmp, stor_op_name(tmp));
+			strcpy(stor_lbv, stor_op_name(lbv));
+
+			sprintf(insnstr, "\t%s = icmp eq i%d 0, %s\n",
+				stor_tmp,
+				tmp->size * 8,
+				stor_val);
+			push_text_atom(current_func, insnstr);
+
+			dummy = new_label();
+
+			sprintf(insnstr, "\tbr i1 %s, label %s, label %%L%d\n",
+				stor_tmp,
+				stor_lbv,
+				dummy);
+			push_text_atom(current_func, insnstr);
+
+			emit_label(dummy, NULL);
+		}
+	}
+
+	s2l_gen_statement(statement);
+	if (stmt->iterator_continue->used)
+		emit_label(loop_continue, "'continue' iterator");
+	s2l_gen_statement(post_statement);
+	if (!post_condition) {
+		struct storage *lbv = new_storage(STOR_LABEL);
+		lbv->label = loop_top;
+		lbv->flags = STOR_WANTS_FREE;
+
+		sprintf(insnstr, "\tbr label %s\n", stor_op_name(lbv));
+		push_text_atom(current_func, insnstr);
+	} else if (post_condition->type == EXPR_VALUE) {
+		if (post_condition->value) {
+			struct storage *lbv = new_storage(STOR_LABEL);
+			lbv->label = loop_top;
+			lbv->flags = STOR_WANTS_FREE;
+
+			sprintf(insnstr, "\tbr label %s\n", stor_op_name(lbv));
+			push_text_atom(current_func, insnstr);
+		}
+	} else {
+		struct storage *lbv = new_storage(STOR_LABEL);
+		lbv->label = loop_top;
+		lbv->flags = STOR_WANTS_FREE;
+
+		val = s2l_gen_expression(post_condition);
+
+		tmp = stack_alloc(val->size);
+
+		strcpy(stor_val, stor_op_name(val));
+		strcpy(stor_tmp, stor_op_name(tmp));
+		strcpy(stor_lbv, stor_op_name(lbv));
+
+		sprintf(insnstr, "\t%s = icmp eq i%d 0, %s\n",
+			stor_tmp,
+			tmp->size * 8,
+			stor_val);
+		push_text_atom(current_func, insnstr);
+
+		dummy = new_label();
+
+		sprintf(insnstr, "\tbr i1 %s, label %s, label %%L%d\n",
+			stor_tmp,
+			stor_lbv,
+			dummy);
+		push_text_atom(current_func, insnstr);
+
+		emit_label(dummy, NULL);
+	}
+	if (have_bottom || stmt->iterator_break->used)
+		emit_label(loop_bottom, "loop bottom");
+
+	loopstk_pop();
+}
+
+static void emit_goto_statement(struct statement *stmt)
+{
+	char insnstr[128];
+
+	insnstr[0] = 0;
+
+	if (stmt->goto_expression) {
+		struct storage *val = s2l_gen_expression(stmt->goto_expression);
+		printf("\tFIXME goto *v%d\n", val->pseudo);
+	}
+
+	else if (!strcmp("break", show_ident(stmt->goto_label->ident))) {
+		struct storage *lbv = new_storage(STOR_LABEL);
+		lbv->label = loopstk_break();
+		lbv->flags = STOR_WANTS_FREE;
+
+		sprintf(insnstr, "\tbr label %s\n", stor_op_name(lbv));
+	}
+
+	else if (!strcmp("continue", show_ident(stmt->goto_label->ident))) {
+		struct storage *lbv = new_storage(STOR_LABEL);
+		lbv->label = loopstk_continue();
+		lbv->flags = STOR_WANTS_FREE;
+
+		sprintf(insnstr, "\tbr label %s\n", stor_op_name(lbv));
+	}
+
+	else {
+		struct storage *labelsym = new_labelsym(stmt->goto_label);
+
+		sprintf(insnstr, "\tbr label %s\n", stor_op_name(labelsym));
+	}
+
+	if (insnstr[0])
+		push_text_atom(current_func, insnstr);
+}
+
+/*
+ * Print out a statement
+ */
+static struct storage *s2l_gen_statement(struct statement *stmt)
+{
+	if (!stmt)
+		return NULL;
+	switch (stmt->type) {
+	default:
+		return NULL;
+	case STMT_RETURN:
+		return emit_return_stmt(stmt);
+	case STMT_DECLARATION:
+		s2l_gen_symbol_decl(stmt->declaration);
+		break;
+	case STMT_COMPOUND: {
+		struct statement *s;
+		struct storage *last = NULL;
+
+		FOR_EACH_PTR(stmt->stmts, s) {
+			last = s2l_gen_statement(s);
+		} END_FOR_EACH_PTR(s);
+
+		return last;
+	}
+
+	case STMT_EXPRESSION:
+		return s2l_gen_expression(stmt->expression);
+	case STMT_IF:
+		emit_if_conditional(stmt);
+		return NULL;
+
+	case STMT_CASE:
+		emit_case_statement(stmt);
+		break;
+	case STMT_SWITCH:
+		emit_switch_statement(stmt);
+		break;
+
+	case STMT_ITERATOR:
+		emit_loop(stmt);
+		break;
+
+	case STMT_NONE:
+		break;
+
+	case STMT_LABEL:
+		printf(".L%p:\n", stmt->label_identifier);
+		s2l_gen_statement(stmt->label_statement);
+		break;
+
+	case STMT_GOTO:
+		emit_goto_statement(stmt);
+		break;
+	case STMT_ASM:
+		printf("\tasm( .... )\n");
+		break;
+	}
+	return NULL;
+}
+
+static struct storage *s2l_gen_call_expression(struct expression *expr)
+{
+	struct symbol *direct;
+	struct expression *arg, *fn;
+	struct storage *retval, *fncall;
+	int first_arg = 1;
+	char s[64];
+	char arg_str[1024];
+	char callstr[1024];
+
+	if (!expr->ctype) {
+		warning(expr->pos, "\tcall with no type!");
+		return NULL;
+	}
+
+	retval = stack_alloc(expr->ctype->bit_size / 8);
+
+	arg_str[0] = 0;
+
+	sprintf(callstr, "\t%s = call i%d ",
+		stor_op_name(retval),
+		expr->ctype->bit_size);
+
+	FOR_EACH_PTR(expr->args, arg) {
+		struct storage *new = s2l_gen_expression(arg);
+		int size = arg->ctype->bit_size;
+
+		/*
+		 * FIXME: i386 SysV ABI dictates that values
+		 * smaller than 32 bits should be placed onto
+		 * the stack as 32-bit objects.  We should not
+		 * blindly do a 32-bit push on objects smaller
+		 * than 32 bits.
+		 */
+		if (size < 32)
+			size = 32;
+
+		sprintf(s, "%si%d %s",
+			first_arg ? "" : ", ",
+			size,
+			stor_op_name(new));
+
+		strcat(arg_str, s);
+
+		first_arg = 0;
+	} END_FOR_EACH_PTR(arg);
+
+	fn = expr->fn;
+
+	/* Remove dereference, if any */
+	direct = NULL;
+	if (fn->type == EXPR_PREOP) {
+		if (fn->unop->type == EXPR_SYMBOL) {
+			struct symbol *sym = fn->unop->symbol;
+			if (sym->ctype.base_type->type == SYM_FN)
+				direct = sym;
+		}
+	}
+	if (direct) {
+		struct storage *direct_stor = new_storage(STOR_SYM);
+		direct_stor->flags |= STOR_WANTS_FREE;
+		direct_stor->sym = direct;
+
+		sprintf(s, "@%s(", stor_op_name(direct_stor));
+		strcat(callstr, s);
+	} else {
+		fncall = s2l_gen_expression(fn);
+
+		sprintf(s, "%%%s(", stor_op_name(fncall));
+		strcat(callstr, s);
+	}
+
+	strcat(callstr, arg_str);
+	strcat(callstr, ")\n");
+	push_text_atom(current_func, callstr);
+
+	return retval;
+}
+
+static struct storage *s2l_gen_address(struct expression *expr)
+{
+	struct storage *addr;
+	struct storage *new;
+	int bits = expr->ctype->bit_size;
+	char insnstr[128];
+	char stor_addr[16], stor_new[16];
+
+	addr = s2l_gen_expression(expr);
+	if (expr->unop->type == EXPR_SYMBOL)
+		return addr;
+
+	new = stack_alloc(bits / 8);
+
+	strcpy(stor_new, stor_op_name(new));
+	strcpy(stor_addr, stor_op_name(addr));
+
+	sprintf(insnstr, "\t%s = load i%d* %s\n",
+		stor_new,
+		bits,
+		stor_addr);
+	push_text_atom(current_func, insnstr);
+
+	return new;
+}
+
+static struct storage *s2l_gen_assignment(struct expression *expr)
+{
+	struct expression *target = expr->left;
+	struct storage *val, *addr;
+
+	if (!expr->ctype)
+		return NULL;
+
+	val = s2l_gen_expression(expr->right);
+	addr = s2l_gen_address(target);
+
+	switch (val->type) {
+	/* copy, where both operands are memory */
+	case STOR_PSEUDO:
+	case STOR_ARG:
+		emit_copy(val, addr, expr->ctype);
+		break;
+
+	/* copy, one or zero operands are memory */
+	case STOR_SYM:
+	case STOR_VALUE:
+	case STOR_LABEL:
+		emit_move(val, addr, expr->left->ctype, NULL);
+		break;
+
+	case STOR_LABELSYM:
+		assert(0);
+		break;
+	}
+	return val;
+}
+
+static int s2l_gen_initialization(struct symbol *sym, struct expression *expr)
+{
+	struct storage *val, *addr;
+	int bits;
+
+	if (!expr->ctype)
+		return 0;
+
+	bits = expr->ctype->bit_size;
+	val = s2l_gen_expression(expr);
+	addr = s2l_gen_symbol_expr(sym);
+	// FIXME! The "target" expression is for bitfield store information.
+	// Leave it NULL, which works fine.
+	emit_store(NULL, addr, val, bits);
+	return 0;
+}
+
+static struct storage *s2l_gen_preop(struct expression *expr)
+{
+	/*
+	 * '*' is an lvalue access, and is fundamentally different
+	 * from an arithmetic operation. Maybe it should have an
+	 * expression type of its own..
+	 */
+	if (expr->op == '*')
+		return s2l_gen_address(expr->unop);
+	if (expr->op == SPECIAL_INCREMENT || expr->op == SPECIAL_DECREMENT)
+		return emit_inc_dec(expr, 0);
+	return emit_regular_preop(expr);
+}
+
+static struct storage *s2l_gen_symbol_expr(struct symbol *sym)
+{
+	struct storage *new = stack_alloc(4);
+
+	if (sym->ctype.modifiers & (MOD_TOPLEVEL | MOD_EXTERN | MOD_STATIC)) {
+		printf("\tmovi.%d\t\tv%d,$%s\n", bits_in_pointer, new->pseudo, show_ident(sym->ident));
+		return new;
+	}
+	if (sym->ctype.modifiers & MOD_ADDRESSABLE) {
+		printf("\taddi.%d\t\tv%d,vFP,$%lld\n", bits_in_pointer, new->pseudo, sym->value);
+		return new;
+	}
+	printf("\taddi.%d\t\tv%d,vFP,$offsetof(%s:%p)\n", bits_in_pointer, new->pseudo, show_ident(sym->ident), sym);
+	return new;
+}
+
+static void s2l_gen_symbol_init(struct symbol *sym)
+{
+	struct symbol_private *priv = sym->aux;
+	struct expression *expr = sym->initializer;
+	struct storage *new;
+
+	new = stack_alloc(sym->bit_size / 8);
+	if (expr) {
+		struct storage *val;
+
+		val = s2l_gen_expression(expr);
+
+		emit_copy(val, new, expr->ctype);
+	}
+
+	if (!priv) {
+		priv = calloc(1, sizeof(*priv));
+		sym->aux = priv;
+		/* FIXME: leak! we don't free... */
+		/* (well, we don't free symbols either) */
+	}
+
+	priv->addr = new;
+}
+
+static int type_is_signed(struct symbol *sym)
+{
+	if (sym->type == SYM_NODE)
+		sym = sym->ctype.base_type;
+	if (sym->type == SYM_PTR)
+		return 0;
+	return !(sym->ctype.modifiers & MOD_UNSIGNED);
+}
+
+static struct storage *s2l_gen_label_expr(struct expression *expr)
+{
+	struct storage *new = stack_alloc(4);
+	printf("\tmovi.%d\t\tv%d,.L%p\n", bits_in_pointer, new->pseudo, expr->label_symbol);
+	return new;
+}
+
+static struct storage *s2l_gen_statement_expr(struct expression *expr)
+{
+	return s2l_gen_statement(expr->statement);
+}
+
+static int s2l_gen_position_expr(struct expression *expr, struct symbol *base)
+{
+	struct storage *new = s2l_gen_expression(expr->init_expr);
+	struct symbol *ctype = expr->init_expr->ctype;
+
+	printf("\tinsert v%d at [%d:%d] of %s\n", new->pseudo,
+		expr->init_offset, ctype->bit_offset,
+		show_ident(base->ident));
+	return 0;
+}
+
+static void s2l_gen_initializer_expr(struct expression *expr, struct symbol *ctype)
+{
+	struct expression *entry;
+
+	FOR_EACH_PTR(expr->expr_list, entry) {
+		// Nested initializers have their positions already
+		// recursively calculated - just output them too
+		if (entry->type == EXPR_INITIALIZER) {
+			s2l_gen_initializer_expr(entry, ctype);
+			continue;
+		}
+
+		// Ignore initializer indexes and identifiers - the
+		// evaluator has taken them into account
+		if (entry->type == EXPR_IDENTIFIER || entry->type == EXPR_INDEX)
+			continue;
+		if (entry->type == EXPR_POS) {
+			s2l_gen_position_expr(entry, ctype);
+			continue;
+		}
+		s2l_gen_initialization(ctype, entry);
+	} END_FOR_EACH_PTR(entry);
+}
+
+/*
+ * Print out an expression. Return the pseudo that contains the
+ * variable.
+ */
+static struct storage *s2l_gen_expression(struct expression *expr)
+{
+	if (!expr)
+		return NULL;
+
+	if (!expr->ctype) {
+		struct position *pos = &expr->pos;
+		printf("\tno type at %s:%d:%d\n",
+			stream_name(pos->stream),
+			pos->line, pos->pos);
+		return NULL;
+	}
+
+	switch (expr->type) {
+	default:
+		return NULL;
+	case EXPR_CALL:
+		return s2l_gen_call_expression(expr);
+
+	case EXPR_ASSIGNMENT:
+		return s2l_gen_assignment(expr);
+
+	case EXPR_COMPARE:
+		return emit_compare(expr);
+	case EXPR_BINOP:
+	case EXPR_COMMA:
+	case EXPR_LOGICAL:
+		return emit_binop(expr);
+	case EXPR_PREOP:
+		return s2l_gen_preop(expr);
+	case EXPR_POSTOP:
+		return emit_postop(expr);
+	case EXPR_SYMBOL:
+		return emit_symbol_expr_init(expr->symbol);
+	case EXPR_DEREF:
+	case EXPR_SIZEOF:
+	case EXPR_ALIGNOF:
+		warning(expr->pos, "invalid expression after evaluation");
+		return NULL;
+	case EXPR_CAST:
+	case EXPR_FORCE_CAST:
+	case EXPR_IMPLIED_CAST:
+		return emit_cast_expr(expr);
+	case EXPR_VALUE:
+		return emit_value(expr);
+	case EXPR_STRING:
+		return emit_string_expr(expr);
+	case EXPR_INITIALIZER:
+		s2l_gen_initializer_expr(expr, expr->ctype);
+		return NULL;
+	case EXPR_SELECT:
+	case EXPR_CONDITIONAL:
+		return emit_conditional_expr(expr);
+	case EXPR_STATEMENT:
+		return s2l_gen_statement_expr(expr);
+	case EXPR_LABEL:
+		return s2l_gen_label_expr(expr);
+
+	// None of these should exist as direct expressions: they are only
+	// valid as sub-expressions of initializers.
+	case EXPR_POS:
+		warning(expr->pos, "unable to show plain initializer position expression");
+		return NULL;
+	case EXPR_IDENTIFIER:
+		warning(expr->pos, "unable to show identifier expression");
+		return NULL;
+	case EXPR_INDEX:
+		warning(expr->pos, "unable to show index expression");
+		return NULL;
+	case EXPR_TYPE:
+		warning(expr->pos, "unable to show type expression");
+		return NULL;
+	case EXPR_FVALUE:
+		warning(expr->pos, "floating point support is not implemented");
+		return NULL;
+	}
+	return NULL;
+}
diff --git a/slcc.c b/slcc.c
new file mode 100644
index 0000000..adba48a
--- /dev/null
+++ b/slcc.c
@@ -0,0 +1,77 @@ 
+/*
+ * client program that uses the sparse library and LLVM.
+ *
+ * Copyright (C) 2003 Transmeta Corp.
+ *               2003 Linus Torvalds
+ * Copyright 2003 Jeff Garzik
+ * Copyright 2009 Red Hat, Inc.
+ *
+ *  Licensed under the Open Software License version 1.1
+ *
+ *
+ * Example usage:
+ *
+ *	./s2l -I/usr/local/include -DHARPSICHORD myfile.c > myfile.ll
+ *	llvm-as myfile.ll	# produces myfile.bc
+ *	llc myfile.bc		# produces myfile.s, target-specific asm
+ *	as -o myfile.o myfile.s	# GNU assembler for the final step
+ *
+ */
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "lib.h"
+#include "allocate.h"
+#include "token.h"
+#include "parse.h"
+#include "symbol.h"
+#include "expression.h"
+#include "compile.h"
+
+static void clean_up_symbols(struct symbol_list *list)
+{
+	struct symbol *sym;
+
+	FOR_EACH_PTR(list, sym) {
+		expand_symbol(sym);
+		emit_one_symbol(sym);
+	} END_FOR_EACH_PTR(sym);
+}
+
+int main(int argc, char **argv)
+{
+	char *file;
+	struct string_list *filelist = NULL;
+
+	clean_up_symbols(sparse_initialize(argc, argv, &filelist));
+	add_pre_buffer("#define __x86_64__ 1\n");
+	FOR_EACH_PTR_NOTAG(filelist, file) {
+		struct symbol_list *list;
+		const char *basename = strrchr(file, '/');
+		basename = basename ?  basename+1 : file;
+
+		list = sparse(file);
+
+		// Do type evaluation and simplification
+		emit_unit_begin(basename);
+		clean_up_symbols(list);
+		emit_unit_end();
+	} END_FOR_EACH_PTR_NOTAG(file);
+
+#if 0
+	// And show the allocation statistics
+	show_ident_alloc();
+	show_token_alloc();
+	show_symbol_alloc();
+	show_expression_alloc();
+	show_statement_alloc();
+	show_string_alloc();
+	show_bytes_alloc();
+#endif
+	return 0;
+}