package cd.backend.codegen;

import cd.Config;
import cd.Main;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.*;

import java.io.Writer;
import java.util.Collections;
import java.util.List;

import static cd.Config.*;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.AssemblyEmitter.registerOffset;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;

public class AstCodeGenerator {

	protected RegsNeededVisitor rnv;

	protected ExprGenerator eg;
	protected StmtGenerator sg;

	protected final Main main;

	protected final AssemblyEmitter emit;
	protected final RegisterManager rm = new RegisterManager();

	protected ExprGeneratorRef egRef;
	protected StmtGeneratorRef sgRef;

	AstCodeGenerator(Main main, Writer out) {
		initMethodData();

		this.emit = new AssemblyEmitter(out);
		this.main = main;
		this.rnv = new RegsNeededVisitor();

		this.eg = new ExprGenerator(this);
		this.sg = new StmtGenerator(this);
	}

	protected void debug(String format, Object... args) {
		this.main.debug(format, args);
	}

	public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
		return new AstCodeGeneratorRef(main, out);
	}


	/**
	 * Main method. Causes us to emit x86 assembly corresponding to {@code ast}
	 * into {@code file}. Throws a {@link RuntimeException} should any I/O error
	 * occur.
	 *
	 * <p>
	 * The generated file will be divided into two sections:
	 * <ol>
	 * <li>Prologue: Generated by {@link #emitPrefix()}. This contains any
	 * introductory declarations and the like.
	 * <li>Body: Generated by {@link ExprGenerator}. This contains the main
	 * method definitions.
	 * </ol>
	 */
	public void go(List<? extends ClassDecl> astRoots) {
		for (ClassDecl ast : astRoots) {
			sg.gen(ast);
		}
	}


	protected void initMethodData() {
		rm.initRegisters();
	}


	protected void emitMethodSuffix(boolean returnNull) {
		if (returnNull)
			emit.emit("movl", "$0", Register.EAX);
		emit.emitMove(BASE_REG, STACK_REG);
		emit.emit("pop", BASE_REG);
		emit.emitRaw("ret");
	}
}

class AstCodeGeneratorRef extends AstCodeGenerator {
	/**
	 * The address of the this ptr relative to the BP. Note that the this ptr is
	 * always the first argument. Default offset value is 8 but this can change
	 * depending on the number of parameters pushed on the stack.
	 */
	protected int THIS_OFFSET = 8;

	/**
	 * Name of the internal Javali$CheckCast() helper function we generate.
	 */
	static final String CHECK_CAST = "Javali$CheckCast";

	/**
	 * Name of the internal Javali$CheckNull() helper function we generate.
	 */
	static final String CHECK_NULL = "Javali$CheckNull";

	/**
	 * Name of the internal Javali$CheckNonZero() helper function we generate.
	 */
	static final String CHECK_NON_ZERO = "Javali$CheckNonZero";

	/**
	 * Name of the internal Javali$CheckArraySize() helper function we generate.
	 */
	static final String CHECK_ARRAY_SIZE = "Javali$CheckArraySize";

	/**
	 * Name of the internal Javali$CheckArrayBounds() helper function we
	 * generate.
	 */
	static final String CHECK_ARRAY_BOUNDS = "Javali$CheckArrayBounds";

	/**
	 * Name of the internal Javali$Alloc() helper function we generate.
	 */
	static final String ALLOC = "Javali$Alloc";

	/**
	 * Name of the internal Javali$PrintNewLine() helper function we generate.
	 */
	static final String PRINT_NEW_LINE = "Javali$PrintNewLine";

	/**
	 * Name of the internal Javali$PrintInteger() helper function we generate.
	 */
	static final String PRINT_INTEGER = "Javali$PrintInteger";

	/**
	 * Name of the internal Javali$ReadInteger() helper function we generate.
	 */
	static final String READ_INTEGER = "Javali$ReadInteger";

	public AstCodeGeneratorRef(Main main, Writer out) {
		super(main, out);

		this.egRef = new ExprGeneratorRef(this);
		this.eg = this.egRef;
		this.sgRef = new StmtGeneratorRef(this);
		this.sg = this.sgRef;
	}


	protected void emitPrefix(List<? extends ClassDecl> astRoots) {
		// compute method and field offsets
		for (ClassDecl ast : astRoots) {
			computeFieldOffsets(ast.sym);
			computeVtableOffsets(ast.sym);
		}

		// emit vtables
		for (TypeSymbol ts : main.allTypeSymbols)
			emitVtable(ts);

		// Emit some useful string constants and static data:
		emit.emitRaw(Config.DATA_STR_SECTION);
		emit.emitLabel("STR_NL");
		emit.emitRaw(Config.DOT_STRING + " \"\\n\"");
		emit.emitLabel("STR_D");
		emit.emitRaw(Config.DOT_STRING + " \"%d\"");
		emit.emitLabel("STR_F");
		emit.emitRaw(Config.DOT_STRING + " \"%.5f\"");
		emit.emitLabel("SCANF_STR_F");
		emit.emitRaw(Config.DOT_STRING + " \"%f\"");
		emit.emitRaw(Config.DATA_INT_SECTION);

		emit.emitRaw(Config.TEXT_SECTION);

		// Generate a helper method for checking casts:
		// It takes first a vtable and second an object ptr.
		{
			Register obj = RegisterManager.CALLER_SAVE[0];
			Register cls = RegisterManager.CALLER_SAVE[1];
			String looplbl = emit.uniqueLabel();
			String donelbl = emit.uniqueLabel();
			String faillbl = emit.uniqueLabel();
			emit.emitCommentSection(CHECK_CAST + " function");
			emit.emitLabel(CHECK_CAST);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, cls);
			emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, obj);
			emit.emit("cmpl", constant(0), obj);
			emit.emit("je", donelbl); // allow null objects to pass
			emit.emitLoad(0, obj, obj); // load vtbl of object
			emit.emitLabel(looplbl);
			emit.emit("cmpl", obj, cls);
			emit.emit("je", donelbl);
			emit.emit("cmpl", constant(0), obj);
			emit.emit("je", faillbl);
			emit.emitLoad(0, obj, obj); // load parent vtable
			emit.emit("jmp", looplbl);
			emit.emitLabel(faillbl);
			emit.emitStore(constant(ExitCode.INVALID_DOWNCAST.value), 0, STACK_REG);
			emit.emit("call", Config.EXIT);
			emit.emitLabel(donelbl);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for checking for null ptrs:
		{
			String oknulllbl = emit.uniqueLabel();
			emit.emitCommentSection(CHECK_NULL + " function");
			emit.emitLabel(CHECK_NULL);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
			emit.emit("jne", oknulllbl);
			emit.emitStore(constant(ExitCode.NULL_POINTER.value), 0, STACK_REG);
			emit.emit("call", Config.EXIT);
			emit.emitLabel(oknulllbl);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for checking that we don't divide by zero:
		{
			String oknzlbl = emit.uniqueLabel();
			emit.emitCommentSection(CHECK_NON_ZERO + " function");
			emit.emitLabel(CHECK_NON_ZERO);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
			emit.emit("jne", oknzlbl);
			emit.emitStore(constant(ExitCode.DIVISION_BY_ZERO.value), 0, STACK_REG);
			emit.emit("call", Config.EXIT);
			emit.emitLabel(oknzlbl);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for checking array size:
		{
			String okunqlbl = emit.uniqueLabel();
			emit.emitCommentSection(CHECK_ARRAY_SIZE + " function");
			emit.emitLabel(CHECK_ARRAY_SIZE);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
			emit.emit("jge", okunqlbl);
			emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
			emit.emit("call", Config.EXIT);
			emit.emitLabel(okunqlbl);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for checking array bounds:
		{
			Register arr = RegisterManager.CALLER_SAVE[0];
			Register idx = RegisterManager.CALLER_SAVE[1];
			String faillbl = emit.uniqueLabel();
			emit.emitCommentSection(CHECK_ARRAY_BOUNDS + " function");
			emit.emitLabel(CHECK_ARRAY_BOUNDS);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, idx);
			emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, arr);
			emit.emit("cmpl", constant(0), idx); // idx < 0
			emit.emit("jl", faillbl);
			emit.emit("cmpl", registerOffset(Config.SIZEOF_PTR, arr), idx); // idx >= len
			emit.emit("jge", faillbl);
			// done
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
			// fail
			emit.emitLabel(faillbl);
			emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
			emit.emit("call", Config.EXIT);

		}

		// Generate a helper method for allocating objects/arrays
		{
			Register size = RegisterManager.CALLER_SAVE[0];
			emit.emitCommentSection(ALLOC + " function");
			emit.emitLabel(ALLOC);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emitLoad(8, BASE_REG, size);
			emit.emitStore(size, 0, STACK_REG);
			emit.emitStore(constant(1), 4, STACK_REG);
			emit.emit("call", Config.CALLOC);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for printing a new line
		{
			emit.emitCommentSection(PRINT_NEW_LINE + " function");
			emit.emitLabel(PRINT_NEW_LINE);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emitStore("$STR_NL", 0, STACK_REG);
			emit.emit("call", Config.PRINTF);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for printing an integer
		{
			Register temp = RegisterManager.CALLER_SAVE[0];
			emit.emitCommentSection(PRINT_INTEGER + " function");
			emit.emitLabel(PRINT_INTEGER);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emitLoad(8, BASE_REG, temp);
			emit.emitStore(temp, 4, STACK_REG);
			emit.emitStore("$STR_D", 0, STACK_REG);
			emit.emit("call", Config.PRINTF);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate a helper method for reading an integer
		{
			Register number = RegisterManager.CALLER_SAVE[0];
			emit.emitCommentSection(READ_INTEGER + " function");
			emit.emitLabel(READ_INTEGER);

			emit.emit("push", BASE_REG);
			emit.emitMove(STACK_REG, BASE_REG);
			emit.emit("sub", constant(8), STACK_REG);

			emit.emit("and", constant(-16), STACK_REG);
			emit.emit("sub", constant(16), STACK_REG);
			emit.emit("leal", registerOffset(8, STACK_REG), number);
			emit.emitStore(number, 4, STACK_REG);
			emit.emitStore("$STR_D", 0, STACK_REG);
			emit.emit("call", SCANF);
			emit.emitLoad(8, STACK_REG, Register.EAX);
			emit.emitMove(BASE_REG, STACK_REG);
			emit.emit("pop", BASE_REG);
			emit.emitRaw("ret");
		}

		// Generate AST for main() method:
		// new Main().main()
		Ast.NewObject newMain = new Ast.NewObject("Main");
		newMain.type = main.mainType;

		Ast.MethodCallExpr mce = new Ast.MethodCallExpr(newMain, "main", Collections.<Expr>emptyList());
		Ast.MethodCall callMain = new Ast.MethodCall(mce);
		mce.sym = main.mainType.getMethod("main");

		// Emit the main() method:
		// new Main().main();
		emit.emitCommentSection("main() function");
		emit.emitRaw(".globl " + MAIN);
		emit.emitLabel(MAIN);

		emit.emit("push", BASE_REG);
		emit.emitMove(STACK_REG, BASE_REG);
		emit.emit("sub", constant(8), STACK_REG);

		emit.emit("and", -16, STACK_REG);
		sg.gen(callMain);
		emit.emit("movl", constant(ExitCode.OK.value), Register.EAX); // normal termination:
		emit.emitMove(BASE_REG, STACK_REG);
		emit.emit("pop", BASE_REG);
		emit.emitRaw("ret");

	}

	@Override
	public void go(List<? extends ClassDecl> astRoots) {
		emitPrefix(astRoots);
		super.go(astRoots);
	}

	/**
	 * Computes the vtable offset for each method defined in the class
	 * {@code sym}.
	 */

	protected int computeVtableOffsets(ClassSymbol sym) {

		if (sym == null)
			return 0;

		if (sym.totalMethods != -1)
			return sym.totalMethods;

		int index = computeVtableOffsets(sym.superClass);
		for (MethodSymbol ms : sym.methods.values()) {
			assert ms.vtableIndex == -1;
			if (ms.overrides != null)
				ms.vtableIndex = ms.overrides.vtableIndex;
			else
				ms.vtableIndex = index++;
		}
		sym.totalMethods = index;
		return index;
	}

	/**
	 * Computes the offset for each field.
	 */

	protected int computeFieldOffsets(ClassSymbol sym) {
		if (sym == null)
			return 0;

		if (sym.totalFields != -1)
			return sym.totalFields;

		int index = computeFieldOffsets(sym.superClass);
		for (VariableSymbol fs : sym.fields.values()) {
			assert fs.offset == -1;
			// compute offset in bytes; note that 0 is the vtable
			fs.offset = (index * SIZEOF_PTR) + SIZEOF_PTR;
			index++;
		}
		sym.totalFields = index;
		sym.sizeof = (sym.totalFields + 1) * Config.SIZEOF_PTR;
		return index;
	}

	private void collectVtable(MethodSymbol[] vtable, ClassSymbol sym) {
		if (sym.superClass != null)
			collectVtable(vtable, sym.superClass);
		for (MethodSymbol ms : sym.methods.values())
			vtable[ms.vtableIndex] = ms;
	}

	protected void emitVtable(TypeSymbol ts) {
		if (ts instanceof ClassSymbol) {
			ClassSymbol cs = (ClassSymbol) ts;

			// Collect the vtable:
			MethodSymbol[] vtable = new MethodSymbol[cs.totalMethods];
			collectVtable(vtable, cs);

			// Emit vtable for this class:
			emit.emitLabel(vtable(cs));
			if (cs.superClass != null)
				emit.emitConstantData(vtable(cs.superClass));
			else
				emit.emitConstantData("0");
			for (int i = 0; i < cs.totalMethods; i++)
				emit.emitConstantData(methodLabel(vtable[i]));
		} else if (ts instanceof ArrayTypeSymbol) {
			ArrayTypeSymbol as = (ArrayTypeSymbol) ts;
			emit.emitLabel(vtable(as));
			emit.emitConstantData(vtable(ClassSymbol.objectType));
		}
	}

	protected String vtable(TypeSymbol ts) {
		if (ts instanceof ClassSymbol) {
			return "vtable_" + ((ClassSymbol) ts).name;
		} else if (ts instanceof ArrayTypeSymbol) {
			return "vtablearr_" + ((ArrayTypeSymbol) ts).elementType.name;
		} else {
			throw new RuntimeException("No vtable for " + ts.name);
		}
	}


	@Override
	protected void initMethodData() {
		THIS_OFFSET = 8;
		bytes = 0;
		super.initMethodData();
	}

	protected int padding(int numberOfParameters) {
		int padding = (bytes + numberOfParameters * Config.SIZEOF_PTR + 15) & 0xFFFFFFF0;
		return padding - bytes - numberOfParameters * Config.SIZEOF_PTR;
	}

	protected void push(int padding) {
		if (padding > 0) {
			emit.emit("sub", padding, STACK_REG);
			bytes += padding;
		}
	}

	protected void pop(int padding) {
		if (padding > 0) {
			emit.emit("add", padding, STACK_REG);
			bytes -= padding;
		}
		assert bytes >= 0;
	}

	protected void push(String reg) {
		emit.emit("push", reg);
		bytes += Config.SIZEOF_PTR;
	}

	protected void pop(String reg) {
		emit.emit("pop", reg);
		bytes -= Config.SIZEOF_PTR;
		assert bytes >= 0;
	}

	protected void restoreCalleeSaveRegs() {
		for (int reg = RegisterManager.CALLEE_SAVE.length - 1; reg >= 0; reg--) {
			emit.emit("pop", RegisterManager.CALLEE_SAVE[reg]);
		}
	}

	protected void storeCalleeSaveRegs() {
		bytes = 0;
		for (int reg = 0; reg < RegisterManager.CALLEE_SAVE.length; reg++) {
			emit.emit("push", RegisterManager.CALLEE_SAVE[reg]);
			bytes += Config.SIZEOF_PTR;
		}
	}

	protected void restoreCallerSaveRegs(Register res) {
		for (int reg = RegisterManager.CALLER_SAVE.length - 1; reg >= 0; reg--) {
			if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
				continue; // not in use
			if (RegisterManager.CALLER_SAVE[reg].equals(res))
				continue; // contains our result
			pop(RegisterManager.CALLER_SAVE[reg].repr);
		}
	}

	protected void storeCallerSaveRegs(Register res) {
		for (int reg = 0; reg < RegisterManager.CALLER_SAVE.length; reg++) {
			if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
				continue; // not in use
			if (RegisterManager.CALLER_SAVE[reg].equals(res))
				continue; // will contain our result
			push(RegisterManager.CALLER_SAVE[reg].repr);
		}
	}

	protected int emitCallPrefix(Register res, int numberOfParameters) {
		storeCallerSaveRegs(res);
		int padding = padding(numberOfParameters);
		push(padding);
		return padding;
	}

	protected void emitCallSuffix(Register res, int numberOfParameters,
								  int padding) {
		pop(numberOfParameters * Config.SIZEOF_PTR + padding);
		if (res != null) {
			emit.emitMove(Register.EAX, res);
		}
		restoreCallerSaveRegs(res);
	}


	/**
	 * Generates code which evaluates {@code ast} and branches to {@code lbl} if
	 * the value generated for {@code ast} is false.
	 */

	protected void genJumpIfFalse(Expr ast, String lbl) {
		// A better way to implement this would be with a separate
		// visitor.
		Register reg = eg.gen(ast);
		emit.emit("cmpl", "$0", reg);
		emit.emit("je", lbl);
		rm.releaseRegister(reg);
	}


	/**
	 * Used to store the temporaries. We grow our stack dynamically, we allocate
	 * "temporary" values on this stack during method execution. Values can be
	 * stored and retrieved using {@link #push(String)} and {@link #pop(String)}
	 * , which use the program stack.
	 */

	protected int bytes = 0;

	protected String methodLabel(MethodSymbol msym) {
		return "meth_" + msym.owner.name + "_" + msym.name;
	}

	protected void emitMethodPrefix(MethodDecl ast) {

		// Emit the label for the method:
		emit.emitRaw(Config.TEXT_SECTION);
		emit.emitCommentSection(String.format("Method %s.%s", ast.sym.owner.name,
				ast.name));
		emit.emitRaw(".globl " + methodLabel(ast.sym));
		emit.emitLabel(methodLabel(ast.sym));

		// Compute the size and layout of the stack frame. Our
		// frame looks like (the numbers are relative to our ebp):
		//
		// (caller's locals)
		// (padding)
		// arg 0 (this ptr)
		// ...
		// 12 arg N - 1
		// 8 arg N
		// 4 linkage ptr (return address)
		// 0 saved ebp
		// -4 locals
		// (callee's arguments + temporaries)
		//
		// We allocate on the stack during the course of
		// a function call using push(...) and pop(...) instructions.
		//
		// Stack slots fall into several
		// categories:
		// - "Linkage": overhead for function calls.
		// This includes the return address and saved ebp.
		// - locals: these store the value of user-declared local
		// variables.
		// - temporaries: these are stack slots used to store
		// values during expression evaluation when we run out
		// of registers, saving caller-saved registers, and
		// other miscellaneous purposes.
		// - padding: only there to ensure the stack size is a multiple
		// of 16.
		// - arguments: values we will pass to functions being
		// invoked.
		//
		// We calculate all address relative to the base pointer.

		// Initialize method-specific data
		initMethodData();

		// Assign parameter offsets:
		// As shown above, these start from 8.
		// Being able to evaluate parameters like in Java
		// with left-to-right evaluation order they result
		// on the stack in reversed order.
		// The "this" parameter is the first pushed on the stack
		// thus receiving higher offset.
		int paramOffset = Config.SIZEOF_PTR * 2;
		for (int i = ast.sym.parameters.size() - 1; i >= 0; i--) {
			final VariableSymbol param = ast.sym.parameters.get(i);
			assert param.offset == -1;
			param.offset = paramOffset;
			paramOffset += Config.SIZEOF_PTR;
		}
		THIS_OFFSET = paramOffset;
		paramOffset += Config.SIZEOF_PTR;

		// First few slots are reserved for caller save regs:
		int localSlot = RegisterManager.CALLER_SAVE.length * RegisterManager.SIZEOF_REG;

		// Assign local variable offsets:
		emit.emitComment(String.format("%-10s   Offset", "Variable"));
		for (VariableSymbol local : ast.sym.locals.values()) {
			assert local.offset == -1;
			local.offset = -localSlot;
			localSlot += Config.SIZEOF_PTR;
			emit.emitComment(String.format("%-10s   %d", local, local.offset));
		}

		// Round up stack size to make it a multiple of 16.
		// The actual amount passed to the enter instruction is 8
		// less, however, because it wants to know the amount
		// in addition to the linkage ptr and saved ebp.
		int implicit = Config.SIZEOF_PTR * 2;
		int stackSize = (implicit + localSlot + 15) & 0xFFFFFFF0;
		stackSize -= implicit;

		emit.emitComment(String.format("implicit=%d localSlot=%d sum=%d", implicit,
				localSlot, implicit + localSlot));


		// emit.emitRaw(String.format("enter $%d, $0", stackSize));
		emit.emit("push", BASE_REG);
		emit.emitMove(STACK_REG, BASE_REG);
		emit.emit("sub", constant(stackSize), STACK_REG);

		emit.emit("and", -16, STACK_REG);

		storeCalleeSaveRegs();

		// zero-initialize locals
		for (VariableSymbol local : ast.sym.locals.values()) {
			emit.emitMove(constant(0), registerOffset(local.offset, BASE_REG));
		}
	}

	@Override
	protected void emitMethodSuffix(boolean returnNull) {
		if (returnNull)
			emit.emit("movl", "$0", Register.EAX);
		restoreCalleeSaveRegs();
		emit.emitMove(BASE_REG, STACK_REG);
		emit.emit("pop", BASE_REG);
		emit.emitRaw("ret");
	}
}

class AstCodeGeneratorNop90 extends AstCodeGeneratorRef {
	public AstCodeGeneratorNop90(Main main, Writer out, CfgCodeGenerator cfgCg) {
		super(main, out);
		this.egRef = new ExprGeneratorNop90(this, cfgCg);
		this.eg = this.egRef;
		this.sgRef = new StmtGeneratorNop90(this, cfgCg);
		this.sg = this.sgRef;
	}


}
