view ilbackend.js @ 251:2557ce4e671f

Fix a couple of compiler bugs. topenv was getting initialized in multiple places. This resulted in multiple copies of modules getting created which caused problems for macro expansion. Additionally, arguments were not being marked as declared during code generation so assigning to an argument that was not closed over generated invalid C code.
author Michael Pavone <pavone@retrodev.com>
date Fri, 11 Apr 2014 22:29:32 -0700
parents 6fe9343b1400
children d1dc2d70bdfd
line wrap: on
line source

var mainModule;
var modules = {};

var methodIds = {};
var methodNames = [];
var assignNames;

function register(num)
{
	this.num = num;
}

register.prototype = {
	valueOf: function() {
		return 'r' + this.num;
	},
	get isRegister() { return true; },
	get isArgument() { return false; }
};

function argument(num)
{
	this.num = num;
}

argument.prototype = {
	valueOf: function() {
		return 'a' + this.num;
	},
	get isRegister() { return true; },
	get isArgument() { return true; }
};

function retreg(size)
{
	this.size = size;
	this.reg = NULL;
}

retreg.prototype = {
	valueOf: function() {
		if (!this.reg) {
			return 'retr';
		}
		return this.reg.valueOf();
	},
	clobber: function(code) {
		this.reg = code.getReg();
		code.addInst('mov', 'retr', this.reg, this.size);
	},
	get isRegister() { return true; },
	get isArgument() { return false; }
}

function indexed(base, offset, index, size)
{
	this.base = base;
	this.offset = offset;
	this.index = index;
	this.size = size;
}

indexed.prototype = {
	valueOf: function() {
		return this.offset + '[' + this.base + ' ' + this.index + '*' + this.size + ']';
	},
	get isRegister() { return false; },
	get isArgument() { return false; }
};

function offset(base, offset)
{
	this.base = base;
	this.offset = offset;
}

offset.prototype = {
	valueOf: function() {
		var out = '';
		if (this.offset) {
			out += this.offset;
		}
		return out + '[' + this.base + ']';
	},
	get isRegister() { return false; },
	get isArgument() { return false; }
};

function funCode(name)
{
	this.name = name;
	this.instructions = [];
	this.nextReg = 0;
	this.toclobber = [];
	this.envreg = null;
}

funCode.prototype = {
	addInst: function() {
		var inst = '';
		for (var i = 0; i < arguments.length; i++) {
			if (arguments[0] == 'call') {
				for (var i = 0; i < this.toclobber.length; i++) {
					this.toclobber[i].clobber();
				}
				this.toclobber = [];
			}
			if (inst) {
				inst += ' ';
			}
			inst += arguments[i];
		}
		this.instructions.push(inst);
	},
	getReg: function() {
		return new register(this.nextReg++);
	},
	getRetReg: function(size) {
		var reg = new retreg(size);
		this.toclobber.push(reg);
		return reg;
	},
	getEnvReg: function() {
		if (!this.envreg) {
			this.envreg = this.getReg();
		}
		return this.envreg;
	},
	valueOf: function() {
		return this.name + ':\n\t' + this.instructions.join('\n\t') + '\n';
	}
};
function getMethodId(methodName)
{
	if (!(methodName in methodIds)) {
		methodIds[methodName] = methodNames.length;
		methodNames.push(methodName);
	}
	return methodIds[methodName];
}

function getOpMethodName(opname)
{
	var optoMeth = {'+': 'ADD_', '-': 'SUB_', '*': 'MUL_', '/': 'DIV_', '%': 'MOD_',
	                '=': 'EQ_', '!=': 'NEQ_', '<': 'LT_', '>': 'GT_', '>=': 'GEQ_', '<=': 'LEQ_',
					'.': 'CAT_', '&&':'if', '||':'ifnot', '|': 'CONS_'};
	if (opname in optoMeth) {
		return optoMeth[opname];
	} else {
		return opname;
	}
}

var slot_arr_offset = 8;
function getMethodSlot(code, base, methodid)
{
	var reg;
	if (!base.isRegister()) {
		reg = code.getReg();
		code.addInst('mov', base, reg, 'q');
		base = reg;
	}
	reg = code.getReg();
	code.addInst('mov', new offset(base, 0), reg, 'q');
	return new offset(reg, slot_arr_offset + (methodid & 0xF)*8);
}

op.prototype.toIL = function(code, isReceiver) {
	var method = getOpMethodName(this.op);
	var left = this.left.toIL(code);
	var right = this.right.toIL(code);
	var methId = getMethodId(method);
	if (this.op == '|') {
		code.addInst('call', getMethodSlot(code, right, methId), methId, right, left);
	} else {
		code.addInst('call', getMethodSlot(code, left, methId), methId, left, right);
	}
	return code.getRetReg();
};
op.prototype.toILLLExpr = function(vars) {
	var opmap = {'=': '==', 'xor': '^', 'or': '|', 'and': '&'};
	return this.left.toILLLExpr(vars) + (this.op in opmap ? opmap[this.op] : this.op) + this.right.toILLLExpr(vars);
};
op.prototype.toILLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toILLLExpr(vars) + ';'];
};


function escapeCName(name)
{
	if (name == 'self') {
		return name;
	}
	name = name.replace(/_/g, "UN_").replace(/:/g, "CN_").replace(/!/g, "EX_").replace(/\?/g, 'QS_').replace(/@/g, 'AT_');
	name = 'tp_' + name;
	return name;
}

function getSymbolPrefix(info, symbols)
{
	var pre = '';
	switch(info.type) {
	case 'self':

		pre = (new symbol('self', symbols)).toIL() + '->';
		break;
	case 'parent':
		pre = (new symbol('self', symbols)).toIL() + '->header.';
		for (var i = 0; i < info.depth; ++i) {
			pre += (i ? '->' : '') + 'parent';
		}
		pre = '((' + info.selftype + ' *)' + pre + ')->';
		break;
	case 'upvar':
		pre = 'env->';
		for (var i = info.startdepth; i < info.depth; ++i) {
			pre += 'parent->';
		}
		break;
	case 'recupvar':
		if (info.subtype == 'object') {
			pre = 'self->env->';
		} else {
			//TODO: fill this case in if necessary
		}
		pre += getSymbolPrefix(info.parent);
	case 'closedover':
		pre = 'myenv->';
	}
	return pre;
}

symbol.prototype.toIL = function(code) {
	var name = this.cleanName();
	var info = this.symbols.find(name);
	if (!info) {
		throw new Error('symbol ' + name + ' not found in ' + assignNames.join('<-'));
	}
	switch (info.type)
	{
	case 'toplevel':
		return toplevel.moduleVar(name);
	case 'self':
		if (info.def instanceof lambda) {
			var self = (new symbol('self', this.symbols)).toIL(code);
			var methId = getMethodId(name);
			code.addInst('call', getMethodSlot(code, self, methId), methId, self);
			return code.getRetReg();
		} else {
			throw new Error('implement me');
		}
	case 'parent':
		if (info.def instanceof lambda) {
			throw new Error('implement me');
			var obj = (new symbol('self', this.symbols)).toIL() + '->header.';
			for (var i = 0; i < info.depth; ++i) {
				obj += (i ? '->' : '') + 'parent';
			}
			return 'mcall(' + getMethodId(name) + '/* ' + name + ' */, 1, ' + obj + ')';
		} else {
			throw new Error('implement me');
		}
	case 'closedover':
		var env = code.getEnvReg();
		return new offset(env, info.offset)
	default:
		throw new Error('implement ' + info.type);
	}
};
symbol.prototype.toILTypeName = function() {
	return this.cleanName();
};
symbol.prototype.toILLLExpr = function(vars) {
	var name = this.cleanName();
	if (name in vars) {
		return name;
	}
	if (name == 'self') {
		return 'self';
	}
	var info = this.symbols.find(name, false, true);
	var symbols = this.symbols;
	while (info && info.type == 'local') {
		symbols = symbols.parent;
		info = symbols.find(name, false, true);
	}
	if (!info) {
		return name;
	}
	if (info.type == 'toplevel') {
		return toplevel.moduleVar(name);
	} else if (info.type == 'self') {
		if (info.isll || !(info.def instanceof lambda)) {
			return 'self->' + name;
		} else {
			return 'mcall(' + getMethodId(name) + '/* ' + name + ' */, 1, self)';
		}
	}
	throw new Error('Unsupported reference type ' + info.type + ' for variable ' + name);
};
symbol.prototype.toILLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toILLLExpr(vars) + ';' ];
};

var declaredInts = {};

intlit.prototype.toIL = function() {
	var intType = (this.unsigned ? 'u' : '') + 'int' + this.bits;
	var str =  intType + '_' + (this.val < 0 ? 'neg_' + (0-this.val).toString() : this.val.toString());
	if (!(str in declaredInts)) {
		toplevelcode += 'obj_' + intType + ' ' + str + ' = {{&obj_' + intType + '_meta, NULL}, ' + this.val.toString() + '};\n';
		declaredInts[str] = true;
	}
	return '((object *)&' + str + ')';
}
intlit.prototype.toILLLExpr = function(vars) {
	return this.val.toString();
};
intlit.prototype.toILLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toILLLExpr(vars) + ';' ];
};

floatlit.prototype.toIL = function() {
	return 'make_float(' + this.val.toString() + ')';
}
floatlit.prototype.toILLLExpr = function(vars) {
	return this.val.toString();
};
floatlit.prototype.toILLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toILLLExpr(vars) + ';' ];
};

var declaredStrings = {};
var nextStringId = 0;

strlit.prototype.toIL = function() {
	if (!(this.val in declaredStrings)) {
		//TODO: get the proper byte length
		toplevelcode += 'string str_' + nextStringId + ' = {{&string_meta, NULL}, ' + this.val.length + ', ' + this.val.length + ', "' + this.val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '"};\n';
		declaredStrings[this.val] = nextStringId++;
	}
	return '((object *)&str_' + declaredStrings[this.val] + ')';
};
strlit.prototype.toILLLExpr = function(vars) {
	return '"' + this.val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') + '"';
};
strlit.prototype.toILLines = function(vars, needsreturn) {
	return [ (needsreturn ? 'return (object *)' : '' ) + this.toILLLExpr(vars) +';' ];
};

listlit.prototype.toIL = function() {
	var ret = 'make_list(' + this.val.length;
	for (var i = this.val.length-1; i >= 0; i--) {
		ret += ', ' + this.val[i].toIL();
	}
	return ret + ')';
}

arraylit.prototype.toIL = function() {
	var ret = 'make_array(' + this.val.length;
	for (var i = 0; i < this.val.length; i++) {
		ret += ', ' + this.val[i].toIL();
	}
	return ret + ')';
}

funcall.prototype.toIL = function() {
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	if (name == 'foreign') {
		if ((this.args[0] instanceof lambda) || (this.args[0] instanceof object)) {
			return null;
		} else if(this.args[0] instanceof symbol) {
			return this.args[0].name;
		} else {
			throw new Error("Unexpected AST type for foreign:");
		}
	} else if(name == 'llProperty:withType' || name == 'llProperty:withVars:andCode') {
		return null;
	}
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, this.receiver);
	}
	var method = false;
	var funinfo = this.symbols.find(name);
	var start = 0;
	if (!funinfo || funinfo.def instanceof setter || funinfo.type == 'toplevel') {
		method = true;
	} else {
		switch(funinfo.type)
		{
		case 'self':
		case 'parent':
			var defargs = funinfo.def.args.length;
			if (!defargs || funinfo.def.args[0].name != 'self') {
				defargs ++
			}
			if (args.length < defargs) {
				if (funinfo.type == 'self') {
					args.splice(0, 0, new symbol('self', this.symbols));
				} else {
					var obj = (new symbol('self', this.symbols)).toIL() + '->header.';
					for (var i = 0; i < funinfo.depth; ++i) {
						obj += (i ? '->' : '') + 'parent';
					}
					args.splice(0, 0, ', ' + obj);
					start = 1;
				}
			} else if(!(args[0] instanceof symbol) || args[0].name != 'self') {
				funinfo = null;
			}
			method = true;
			break;
		}
	}
	for (var i = start; i < args.length; i++) {
		args[i] = ', ' + (!i && method ? '(object *)(' : '') + args[i].toIL() + (!i && method ? ')' : '');
	}
	var callpart;
	if (method) {
		if (funinfo && funinfo.type == 'self' && funinfo.def.name) {
			callpart =  funinfo.def.name + '(' + (funinfo.def.symbols.parent.needsenv ? (new symbol('self', this.symbols)).toIL() + '->env' : 'NULL' );
		} else {
			callpart = 'mcall(' + getMethodId(name) + '/* ' + name + ' */';
		}
	} else {
		var closVar = (new symbol(name, this.symbols)).toIL();
		callpart = '((lambda *)' + closVar + ')->func(((lambda *)' + closVar + ')->env';
	}
	return callpart + ', ' + args.length + args.join('') + ')';
};
funcall.prototype.toILTypeName = function() {
	switch(this.name)
	{
	case 'ptr:':
	case 'ptr':
		var receiver = this.receiver ? this.receiver : this.args[0];
		return receiver.toILTypeName() + ' *';
		break;
	case 'struct:':
	case 'struct':
		var receiver = this.receiver ? this.receiver : this.args[0];
		return 'struct ' + receiver.toILTypeName();
		break;
	case 'funptr:':
	case 'funptr':
		var rettype;
		var firstArg;
		if (this.receiver) {
			rettype = this.receiver;
			firstArg = 0;
		} else {
			rettype = this.args[0];
			firstArg = 1;
		}
		var argtypes = '';
		for (var i = firstArg; i < this.args.length; i++) {
			if (argtypes) {
				argtypes += ', '
			}
			argtypes += this.args[i].toILTypeName();
		}
		return [rettype.toILTypeName() + '(*', ')(' + argtypes + ')'];
		break;
	default:
		throw new Error('invalid use of funcall expression where a C type name is expected');
	}
};
funcall.prototype.toILLines = function(vars, needsreturn) {
	var lines = [];
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		args.splice(0, 0, [this.receiver]);
	}
	switch(name)
	{
	case 'if':
		lines.push('if (' + this.args[0].toILLLExpr(vars) + ') {');
		var blines = this.args[1].toILLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		if (needsreturn) {
			lines.push('} else {');
			lines.push('\t return module_false;');
			lines.push('}');
		} else {
			lines.push('}');
		}
		break;
	case 'if:else':
		lines.push('if (' + this.args[0].toILLLExpr(vars) + ') {');
		var blines = this.args[1].toILLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('} else {');
		blines = this.args[2].toILLines(vars, needsreturn);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('}');
		break;
	case 'while:do':
		if (needsreturn) {
			throw new Error("while:do can't be last statement in llMessage code block");
		}
		lines.push('while (' + this.args[0].toILLLExpr(vars) + ') {');
		var blines = this.args[1].toILLines(vars);
		for (var i in blines) {
			lines.push('\t' + blines[i]);
		}
		lines.push('}');
		break;
	default:
		lines.push( (needsreturn ? 'return (object *)' : '') + this.toILLLExpr(vars) + ';');
	}
	return lines;
};

funcall.prototype.toILLLExpr = function(vars) {
	var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name;
	var args = this.args.slice(0, this.args.length);
	if (this.receiver) {
		if(this.args.length == 0) {
			if (this.receiver instanceof symbol && this.receiver.name in vars && vars[this.receiver.name].substr(-1) != "*") {
				return this.receiver.toILLLExpr(vars) + '.' + this.name;
			} else {
				return this.receiver.toILLLExpr(vars) + '->' + this.name;
			}
		} else if (this.args.length == 1 && name[name.length-1] == '!') {
			if (this.receiver instanceof symbol && this.receiver.name in vars && vars[this.receiver.name].substr(-1) != "*") {
				return this.receiver.toILLLExpr(vars) + '.' + this.name.substr(0, name.length-1) + ' = ' + args[0].toILLLExpr(vars);
			} else {
				return this.receiver.toILLLExpr(vars) + '->' + this.name.substr(0, name.length-1) + ' = ' + args[0].toILLLExpr(vars);
			}
		} else {
			args.splice(0, 0, this.receiver);
		}
	}
	switch(name)
	{
	case 'if':
		return '((' + args[0].toILLLExpr(vars) + ') ? (' + args[1].toILLLExpr(vars) + ') : 0)';
	case 'if:else':
		return '((' + args[0].toILLLExpr(vars) + ') ? (' + args[1].toILLLExpr(vars) + ') : (' + args[2].toILLLExpr(vars) + '))';
	case 'while:do':
		throw new Error('while:do not allowed in expression context in llMessage block');
	case 'addr_of':
		return '&(' + args[0].toILLLExpr(vars) + ')';
	case 'sizeof':
		return 'sizeof(' + args[0].toILTypeName() + ')';
	case 'get':
		return args[0].toILLLExpr(vars) + '[' + args[1].toILLLExpr(vars) + ']';
	case 'set':
		return args[0].toILLLExpr(vars) + '[' + args[1].toILLLExpr(vars) + '] = ' + args[2].toILLLExpr(vars);
	case 'not':
		return '!(' + args[0].toILLLExpr(vars) + ')';
	case 'castTo':
		return '((' + args[1].toILTypeName() + ')(' + args[0].toILLLExpr(vars) + '))';
	case 'mcall':
		if (args[0] instanceof symbol) {
			args[0] = new intlit(getMethodId(args[0].name));
		}
	default:
		for (var i in args) {
			args[i] = args[i].toILLLExpr(vars);
		}
		return name + '(' + args.join(', ') + ')';
	}
};

function cObject(name) {
	this.name = name;
	this.slots = {};
	this.properties = [];
	this.values = [];
	this.slotvars = {};
	this.includes = {};
	this.parent = 'NULL';
	this.init = [];
	this.initmsgadded = false;
}

cObject.prototype.addInclude = function(includefile) {
	this.includes[includefile] = true;
}

cObject.prototype.addMessage = function(msgname, implementation) {
	var methodid = getMethodId(msgname);
	var trunc = methodid & 0xF;
	if (!(trunc in this.slots)) {
		this.slots[trunc] = [];
	}
	this.slots[trunc].push([methodid, '\t\t' + implementation.lines.join('\n\t\t') + '\n', msgname]);
	if (!(trunc in this.slotvars)) {
		this.slotvars[trunc] = {};
	}
	for (var varname in implementation.vars) {
		this.slotvars[trunc][varname] = implementation.vars[varname];
	}
}

cObject.prototype.addProperty = function(propname, value, type) {
	if (type != undefined) {
		this.properties.push([propname, type]);
		if (value !== null) {
			this.values.push(value);
		}
	} else {
		var escaped = escapeCName(propname);
		this.addMessage(propname, {
			vars: {},
			lines: [
				'return self->' + escaped + ';'
		]});
		this.addMessage(propname + '!', {
			vars: {},
			lines: [
				'self->' + escaped + ' = va_arg(args, object *);',
				'return (object *)self;'
		]});
		this.properties.push(escaped);
		this.values.push(value);
	}
}

cObject.prototype.addInit = function(statement) {
	this.init.push(statement);
};

cObject.prototype.addImport = function(symbols, source) {
	this.imported.push(source);
	var importNum = imported.length - 1;
	if (symbols) {
		each(symbols, function(i, sym) {
			this.addMessage(sym.name, {
				vars: {},
				lines: [
					'return self->import_' + importNum + '->meta->meth_lookup[method_id & 0xF](method_id, num_args, self->import_' + importNum + ', args);'
				]
			});
		});
	} else {
		//TODO: handle proxying for unimplemented methods
	}
};

cObject.prototype.checkInitMsg = function() {
	if (!this.initmsgadded && this.init.length) {
		var init = this.init.slice(0, this.init.length);
		init.push('return (object *)self;');
		this.addMessage('_init', {
			vars: {},
			lines: init
		});
		this.initmsgadded = true;
	}
}

cObject.prototype.populateSymbols = function() {};

cObject.prototype.toEarlyCDef = function() {
	this.checkInitMsg();
	var includes = '';
	for (var file in this.includes) {
		includes += '#include ' + file + '\n';
	}
	var objdef =  'typedef struct ' + this.name + ' {\n\tobject header;\n';
	for (var i in this.properties) {
		if (this.properties[i] instanceof Array) {
			if (this.properties[i][1] instanceof Array) {
				objdef += '\t' + this.properties[i][1][0] + this.properties[i][0] + this.properties[i][1][1] + ';\n';
			} else {
				objdef += '\t' + this.properties[i][1] + ' ' + this.properties[i][0] + ';\n';
			}
		} else {
			objdef += '\tobject * ' + this.properties[i] + ';\n'
		}
	}
	objdef += '} ' + this.name + ';\nobj_meta ' + this.name + '_meta;\n';
	return includes + objdef;
}

cObject.prototype.toILDef = function() {
	this.checkInitMsg();
	var slotdefs = '';
	var metadef = 'obj_meta ' + this.name + '_meta = {sizeof(' + this.name +'), {';
	for (var i = 0; i < 16; i++) {
		if (i) {
			metadef += ', ';
		}
		if (i in this.slots) {
			slotdefs += 'object * ' + this.name + '_slot_' + i + '(uint32_t method_id, uint32_t num_params, object * oself, va_list args) {\n\t' +
				this.name + ' *self = (' + this.name + ' *)oself;\n';
			for (var varname in this.slotvars[i]) {
				if (this.slotvars[i][varname] instanceof Array) {
					slotdefs += '/*foo*/\t' + this.slotvars[i][varname][0] + ' ' + varname + this.slotvars[i][varname][1] + ';\n';
				} else {
					slotdefs += '/*bar*/\t' + this.slotvars[i][varname] + ' ' + varname + ';\n';
				}
			}
			if (this.slots[i].length == 1) {
				slotdefs += '\tif (method_id == ' + this.slots[i][0][0] + ') { /* ' + this.slots[i][0][2] + '*/\n' +
					'\t\t' + this.slots[i][0][1] + '\n' +
					'\t}\n' +
					'\treturn no_impl(method_id, num_params, (object *)self, args);\n}\n';
			} else {
				slotdefs += '\tswitch(method_id) {\n';
				for (j in this.slots[i]) {
					slotdefs += '\t\tcase ' + this.slots[i][j][0] + ': /* ' + this.slots[i][j][2] + '*/\n' +
						'\t\t\t' + this.slots[i][j][1] + '\n';
				}
				slotdefs += '\t\tdefault:\n' +
					'\t\t\treturn no_impl(method_id, num_params, (object *)self, args);\n\t}\n}\n';

			}
			metadef += this.name + '_slot_' + i;
		} else {
			metadef += 'no_impl';
		}
	}
	metadef += '}};\n';
	return slotdefs + metadef;
}

cObject.prototype.toILInstance = function() {
	this.checkInitMsg();
	var ret = 'make_object(&' + this.name + '_meta, ' + this.parent + ', ' + this.values.length + (this.values.length ? ', ' : '') + this.values.join(', ') + ')';
	if (this.initmsgadded) {
		ret = 'mcall(' + getMethodId('_init') + ', 1, ' + ret + ')'
	}
	return ret;
}

cObject.prototype.toIL = function() {
	forwarddec += this.toEarlyCDef();
	toplevelcode += this.toILDef();
	return this.toILInstance();
}

var nextobject = 0;


object.prototype.toILObject = function() {
	var messages = this.messages;
	if (!this.name) {
		this.name = 'object_' + nextobject++;
	}
	var me = new cObject(this.name);
	this.symbols.typename = me.name;
	if (this.symbols.needsenv) {
		me.addProperty('env', this.symbols.envVar(), 'struct ' + this.symbols.getEnvType() + ' * ');
		me.hasenv = true;
	}
	if (this.symbols.needsparent && !(this.symbols.parent instanceof osymbols)) {
		me.parent = '(object *)(' + (new symbol('self', this.symbols.parent)).toIL() + ')';
	}
	for (var i in messages) {
		if (messages[i] instanceof funcall) {
			if (messages[i].name == 'import:' && messages[i].args.length == 1) {
				me.addImport(false, messages[i].args[0]);
			} else if(messages[i].name == 'import:from:' && messages[i].args.length == 2) {
				var importsyms = [];
				each(messages[i].args[0].val, function(i, el) {
					if (!(el instanceof symbol)) {
						throw new Error('Names in import:from statement must be symbols');
					}
					importsyms.push(el);
				});
				me.addImport(importsyms, messages[i].args[1]);
			} else if(messages[i].name == 'llProperty:withType:' && messages[i].args.length == 2) {
				me.addProperty(messages[i].args[0].name, null, messages[i].args[1].toILTypeName());
			} else if(messages[i].name == 'llMessage:withVars:andCode:' && messages[i].args.length == 3) {
				var msgname = messages[i].args[0].name
				var rawvars = messages[i].args[1].expressions;
				var vars = {};
				for(var v in rawvars) {
					vars[rawvars[v].symbol.name] = rawvars[v].expression.toILTypeName();
				}
				me.addMessage(msgname, {
					vars: vars,
					lines: messages[i].args[2].toILLines(vars, true)
				});
			} else if(messages[i].name == 'includeSystemHeader:' && messages[i].args.length == 1) {
				me.addInclude("<" + messages[i].args[0].val + ">");
			} else {

				throw new Error('Only import and import:from calls allowed in object context. ' + messages[i].name + 'with ' + messages[i].args.length + ' arguments found instead.');
			}
		} else {
			messages[i].toILObject(me);
		}
	}

	return me;
};

object.prototype.toIL = function() {
	return this.toILObject().toIL();
};

var toplevelcode;
var forwarddec;

function addBinaryOp(cobject, opname, cop, objtype)
{
	cobject.addMessage(opname, {
		vars: {ret: objtype + ' *', argb: objtype +' *'},
		lines: [
			'argb = va_arg(args, ' + objtype + ' *);',
			'ret = (' + objtype + ' *)make_object(&' + objtype + '_meta, NULL, 0);',
			'ret->num = self->num ' + cop + ' argb->num;',
			'return &(ret->header);'
		]
	});
}

function addCompOp(cobject, opname, cop, objtype)
{
	cobject.addMessage(opname, {
		vars: {argb: objtype + ' *'},
		lines: [
			'argb = va_arg(args, ' + objtype + ' *);',
			'if (self->num ' + cop + ' argb->num) {',
			'	return ' + toplevel.moduleVar('true') + ';',
			'}',
			'return ' + toplevel.moduleVar('false') + ';',
		]
	});
}

function makeInt(bits, unsigned)
{
	var typename = 'obj_' + (unsigned ? 'u' : '') + 'int' + bits;
	var intObj = new cObject(typename);
	intObj.addProperty('num', null, (unsigned ? 'u' : '') + 'int' + bits +'_t');
	addBinaryOp(intObj, 'ADD_', '+', typename);
	addBinaryOp(intObj, 'SUB_', '-', typename);
	addBinaryOp(intObj, 'MUL_', '*', typename);
	addBinaryOp(intObj, 'DIV_', '/', typename);
	addBinaryOp(intObj, 'MOD_', '%', typename);
	addBinaryOp(intObj, 'or', '|', typename);
	addBinaryOp(intObj, 'xor', '^', typename);
	addBinaryOp(intObj, 'and', '&', typename);
	addBinaryOp(intObj, 'lshift:by', '<<', typename);
	addBinaryOp(intObj, 'rshift:by', '>>', typename);
	addCompOp(intObj, 'LT_', '<', typename);
	addCompOp(intObj, 'GT_', '>', typename);
	addCompOp(intObj, 'EQ_', '==', typename);
	addCompOp(intObj, 'NEQ_', '!=', typename);
	addCompOp(intObj, 'GEQ_', '>=', typename);
	addCompOp(intObj, 'LEQ_', '<=', typename);
	intObj.addInclude('<string.h>');
	//-9223372036854775808
	//01234567890123456789
	intObj.addMessage('string', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC(' + (bits == 64 ? 21 : 12) + ');',
			'sprintf(str->data, "%' + (bits == 64 ? 'l' : '') + (unsigned ? 'u' : 'd') + '", self->num);',
			'str->len = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	//7FFFFFFFFFFFFFFF
	//01234567890123456789
	intObj.addMessage('hex', {
		vars: {str: 'string *'},
		lines: [
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC(' + (bits == 64 ? 17 : 9) + ');',
			'sprintf(str->data, "%' + (bits == 64 ? 'l' : '') +'X", self->num);',
			'str->len = str->bytes = strlen(str->data);',
			'return &(str->header);'
		]
	});
	intObj.addMessage('isInteger?', {
		vars: {},
		lines: [
			'return ' + toplevel.moduleVar('true') + ';'
		]
	});
	intObj.addMessage('hash', {
		vars: {},
		lines: [
			'return &(self->header);'
		]
	});
	intObj.addMessage('signed?', {
		vars: {},
		lines: [
			'return ' + toplevel.moduleVar(unsigned ? 'false' : 'true') + ';'
		]
	});
	var sizes = [8, 16, 32, 64];
	var destunsigned = [false, true];
	for (var i = 0; i < sizes.length; i++) {
		size = sizes[i];
		for (var j = 0; j < destunsigned.length; j++) {
			uns = destunsigned[j];
			if (uns == unsigned && size == bits) {
				intObj.addMessage((uns ? 'u' : '') + 'int' + size, {
					vars: {},
					lines: [
						'return &(self->header);'
					]
				});
			} else {
				var retType = 'obj_' + (uns ? 'u' : '') + 'int' + size;
				var retName = 'ret' + (uns ? 'u' : '') + size;
				var vars = {};
				vars[retName] = retType + ' *';
				intObj.addMessage((uns ? 'u' : '') + 'int' + size, {

					vars: vars,
					lines: [
						retName + ' = ('+retType+' *)make_object(&' + retType +'_meta, NULL, 0);',
						retName + '->num = self->num;',
						'return &(' + retName + '->header);'
					]
				});
			}
		}
	}

	return intObj;
}

function makeArray()
{
	var arrayfile = toplevel.names['array'];
	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
	ast.name = 'array';
	ast.populateSymbols(toplevel);
	return ast.toILObject();
}

function makeString()
{
	var arrayfile = toplevel.names['string'];
	var ast = parseFile(arrayfile.path + '/' + arrayfile.file);
	ast.name = 'string';
	ast.populateSymbols(toplevel);
	return ast.toILObject();
}

function makelambda()
{
	var clos = new cObject('lambda');
	clos.addProperty('env', null, 'void *');
	clos.addProperty('func', null, 'closure_func');
	clos.addMessage('while:do', {
		vars: {action: 'lambda *', ret: 'object *'},
		lines: [
			'action = va_arg(args, lambda *);',
			'ret = ' + toplevel.moduleVar('true') + ';',
			'while(' + toplevel.moduleVar('true') + ' == ccall(self, 0)) {',
			'	ccall(action, 0);',
			'}',
			'return ret;'
		]
	});
	return clos;
}

function builtinTypes()
{
	return [makeInt(64, false), makeInt(32, false), makeInt(16, false), makeInt(8, false),
	        makeInt(64, true) , makeInt(32, true),  makeInt(16, true),  makeInt(8, true),
	        makeArray(), makeString(), makelambda()];
}

function addBuiltinModules(toplevel)
{
	var os = new cObject('mod_obj_os');
	os.addInclude('<sys/stat.h>');
	os.addInclude('<fcntl.h>');
	os.addInclude('<stdlib.h>');
	os.addInclude('<time.h>');
	os.addInclude('<unistd.h>');
	os.addMessage('write', {
		vars: {str: 'string *', intret: 'obj_int32 *', filedes: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'str = va_arg(args, string *);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = write(filedes->num, str->data, str->bytes);',
			'return &(intret->header);'
		]
	});
	os.addMessage('read', {
		vars: {str: 'string *', size: 'obj_int32 *', filedes: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'size = va_arg(args, obj_int32 *);',
			'str = (string *)make_object(&string_meta, NULL, 0);',
			'str->data = GC_MALLOC_ATOMIC(size->num + 1);',
			'str->len = str->bytes = read(filedes->num, str->data, size->num);',
			'if (str->bytes < 0) { str->bytes = str->len = 0; }',
			'str->data[str->bytes] = 0;',
			'return &(str->header);'
		]
	});
	os.addMessage('open', {
		vars: {str: 'string *', flags: 'obj_int32 *', filedes: 'obj_int32 *', perm: 'obj_int32 *'},
		lines: [
			'str = va_arg(args, string *);',
			'flags = va_arg(args, obj_int32 *);',
			'filedes = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'if (num_params == 3) {',
			'	filedes->num = open(str->data, flags->num);',
			'} else if (num_params == 4) {',
			'	perm = va_arg(args, obj_int32 *);',
			'	filedes->num = open(str->data, flags->num, perm->num);',
			'} else {',
			'	filedes->num = -1;',
			'}',
			'return &(filedes->header);'
		]
	});
	os.addMessage('close', {
		vars: {filedes: 'obj_int32 *', intret: 'obj_int32 *'},
		lines: [
			'filedes = va_arg(args, obj_int32 *);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = close(filedes->num);',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_RDONLY', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_RDONLY;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_WRONLY', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_WRONLY;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_RDWR', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_RDWR;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_CREAT', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_CREAT;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_APPEND', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_APPEND;',
			'return &(intret->header);'
		]
	});
	os.addMessage('O_TRUNC', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = O_TRUNC;',
			'return &(intret->header);'
		]
	});
	os.addMessage('rand', {
		vars: {intret: 'obj_int32 *'},
		lines: [
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = rand();',
			'return &(intret->header);'
		]
	});
	os.addMessage('rand64', {
		vars: {intret64: 'obj_int64 *'},
		lines: [
			'intret64 = (obj_int64 *)make_object(&obj_int64_meta, NULL, 0);',
			'intret64->num = (((int64_t)rand()) << 32 ) | rand();',
			'return &(intret64->header);'
		]
	});
	os.addMessage('srand', {
		vars: {oseed: 'object *', seed: 'obj_int32 *'},
		lines: [
			'oseed = va_arg(args, object *);',
			'seed = mcall(' + getMethodId("int32") + ', 1, oseed);',
			'srand(seed->num);',
			'return &(seed->header);'
		]
	});
	os.addMessage('time', {
		vars: {intret64: 'obj_int64 *'},
		lines: [
			'intret64 = (obj_int64 *)make_object(&obj_int64_meta, NULL, 0);',
			'intret64->num = time(NULL);',
			'return &(intret64->header);'
		]
	});
	os.addMessage('sleep', {
		vars: {osecs: 'object *', secs: 'obj_int32 *', intret: 'obj_int32 *'},
		lines: [
			'osecs = va_arg(args, object *);',
			'secs = mcall(' + getMethodId("int32") + ', 1, osecs);',
			'intret = (obj_int32 *)make_object(&obj_int32_meta, NULL, 0);',
			'intret->num = sleep(secs->num);',
			'return &(intret->header);'
		]
	});
	toplevel.names['os'] = os;
}

modulefile.prototype.toIL = function(){
	return this.ast.toILModuleInstance();
};

function processUsedToplevel(toplevel)
{
	var alwaysused = ['true', 'false', 'list'];
	var ret = '';
	var modulenum = 0;
	var visited = {};
	for (var i in alwaysused) {
		toplevel.used[alwaysused[i]] = true;
	}
	var newused = Object.keys(toplevel.used);
	var allused = newused;
	while (newused.length) {
		for (var i in newused) {
			debugprint('//---module', newused[i], '--- populate symbols');
			forwarddec += 'object * ' + toplevel.moduleVar(newused[i]) + ';\n';
			toplevel.names[newused[i]].populateSymbols(toplevel);
			visited[newused[i]] = true;
		}
		newused = [];
		for (var symbol in toplevel.used) {
			if (!(symbol in visited)) {
				debugprint('//found new usage of module', symbol);
				newused.push(symbol);
				allused.push(symbol);
			}
		}
	}

	for (var i = allused.length-1; i >= 0; i--) {
		var symbol = allused[i];
		debugprint('//---module', symbol, '(' + i +')--- compile');
		assignNames.push(symbol);
		ret += '\t' + toplevel.moduleVar(symbol) + ' = ' + toplevel.names[symbol].toIL() + ';\n';
		assignNames.pop();
	}
	return ret;
}

function makeCProg(obj)
{
	forwarddec = toplevelcode = '';
	assignNames = [];
	var builtins = builtinTypes();
	for (var i in builtins) {
		forwarddec += builtins[i].toEarlyCDef();
		toplevelcode += builtins[i].toILDef();
	}
	addBuiltinModules(toplevel);
	debugprint('//------POPULATING SYMBOLS-----');
	obj.populateSymbols(toplevel);
	var moduleinit = processUsedToplevel(toplevel);
	debugprint('//------COMPILING AST-----');
	var rest = 'object * mainModule() {\n' + moduleinit + '\tmain_module = ' + obj.toILModuleInstance() + ';\n\treturn main_module;\n}\n';
	var mnames = 'char * methodNames[] = {\n';
	for (var i = 0; i < methodNames.length; i++) {
		mnames += '\t"' + methodNames[i].replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n") + '",\n';
	}
	mnames += '};\n';
	return '#include "runtime/proghead.inc"\n' +
		'#define METHOD_ID_MAIN ' + getMethodId('main') + '\n' +
		'#define METHOD_ID_EMPTY ' + getMethodId('empty') + '\n' +
		'#define METHOD_ID_CONS ' + getMethodId(getOpMethodName('|')) + '\n' +
		mnames + forwarddec + toplevelcode + rest + '#include "runtime/progfoot.inc"\n';
}

object.prototype.toILModule = function() {
	return makeCProg(this);
}

object.prototype.toILModuleInstance = function() {
	return this.toIL();
}

lambda.prototype.toIL = function(parentcode) {
	var code = new funCode(this.name);
	var args = this.args ? this.args.slice(0, this.args.length) : [];
	var exprs = this.expressions;
	var assignPath = assignNames.join('<-');
	debugprint('//', this.name, assignPath);
	var addedTypeDef = false;
	if (Object.keys(this.symbols.closedover).length) {
		this.symbols.envtype = this.name + '_env';
		forwarddec += 'typedef struct ' + this.symbols.envtype + '  ' + this.symbols.envtype + ';\n'
		var addedTypeDef = true;
	}
	if (this.selftype) {
		this.symbols.defineVar('self', this.selftype);
		if (args[0] && args[0].cleanName() == 'self') {
			args.splice(0, 1);
		}
		var offset = 1;
	} else {
		var offset = 0;
	}
	for (var i = 0; i < args.length; ++i) {
		var argname = args[i].toIL(code);

		args[i] = (argname.indexOf('->') < 0 ? '\tobject * ' : '\t') + argname + ' = va_arg(args, object *);\n';
	}
	var compiled = []
	for (var i in exprs) {
		var js = exprs[i].toIL(code);
		if (js) {
			compiled.push(indent(js));
		}
	}
	if (compiled.length) {
		if (exprs[exprs.length - 1] instanceof assignment) {
			compiled.push('return ' + exprs[exprs.length - 1].symbol.toIL() + ';');
		} else {
			compiled[compiled.length-1] = 'return (object *)(' + compiled[compiled.length-1] + ');';
		}
	}
	exprs = compiled;

	if (Object.keys(this.symbols.closedover).length) {
		if (!addedTypeDef) {
			for (var key in this.symbols.closedover) {
				print(key, ": ", this.symbols.closedover[key]);
			}
			throw new Error('this.symbols.closedover is not empty, but it was when compilation of ' + this.name + ' "' + assignPath + '" started');
		}
		forwarddec += 'struct ' + this.name + '_env {\n';
		if (this.symbols.needsParentEnv) {
			forwarddec += '\tstruct ' + this.symbols.parentEnvType() + ' * parent;\n';
		}
		for (var varname in this.symbols.closedover) {
			if (varname == 'self' && this.selftype) {
				forwarddec += '\tstruct ' + this.selftype + ' * self;\n';
			} else {
				forwarddec += '\tobject * ' + escapeCName(varname) + ';\n';
			}
		}
		forwarddec += '};\n'

		var myenvinit = '\t' + this.name + '_env * myenv = GC_MALLOC(sizeof(' + this.name + '_env));\n';
		if (this.symbols.needsParentEnv) {
			myenvinit += '\tmyenv->parent = env;\n';
		}
		this.symbols.envtype = this.name + '_env';
	} else {
		var myenvinit = '';
	}
	forwarddec += 'object *' + this.name + ' (' + this.symbols.parentEnvType() + ' * env, uint32_t num_args, ...);\n';

	toplevelcode += '//' + assignPath + "\n";
	toplevelcode +=  'object * ' + this.name + ' ( ' + this.symbols.parentEnvType() + ' * env, uint32_t num_args, ...) {\n\tva_list args;\n' + myenvinit + '\tva_start(args, num_args);\n';
	if (this.selftype) {
		var selfvar = (new symbol('self', this.symbols)).toIL();
		if (selfvar == 'self') {
			toplevelcode += '\t' + this.selftype + ' * self = va_arg(args, ' + this.selftype + ' *);\n';
		} else {
			toplevelcode += '\t' + selfvar  + ' = va_arg(args, ' + this.selftype + ' *);\n';
		}

	}
	toplevelcode += args.join('') + '\tva_end(args);\n' + exprs.join(';\n\t') + '\n}\n';

	if (this.selftype) {
		return this.name;
	} else {
		if (this.symbols.parentEnvType() != 'void') {
			if (this.symbols.parent.passthruenv) {
				var envvar = 'env';
			} else {
				var envvar = 'myenv';
			}
			debugprint('//' + this.name, 'has envvar:', envvar, 'num vars closed over:', Object.keys(this.symbols.closedover).length);
			return 'make_lambda(' + envvar + ', (closure_func)' + this.name + ')';
		} else {
			toplevelcode += 'lambda ' + this.name + '_obj = {{&lambda_meta, NULL}, NULL, ' + this.name + '};\n';
			return '((object *)&' + this.name + '_obj)';
		}
	}
};
lambda.prototype.toILModuleInstance = function() {
	this.toIL();
	return this.name + '(NULL, 0)';
};
lambda.prototype.toILObject = function(typename) {
	this.selftype = typename;
	return this.toIL();
};
lambda.prototype.toILModule = function() {
	return makeCProg(this);
};
lambda.prototype.toILLines = function(vars, needsreturn) {
	var lines = [];
	for (var i in this.args) {
		var name = this.args[i].name;
		if (name[0] == ':') {
			name = name.substr(1);
		}
		if(name != 'self') {
			lines.push(name + ' = va_arg(args, ' + vars[name] + ');');
		}
	}
	for (var i in this.expressions) {
		var exprlines = this.expressions[i].toILLines(vars, needsreturn && i == this.expressions.length - 1);
		for (var j in exprlines) {
			lines.push('\t' + exprlines[j]);
		}
	}
	return lines;
}
lambda.prototype.toILLLExpr = function(vars) {
	if (this.expressions.length != 1) {
		throw new Error('lambda in expression context must have a single statement in llMessage block');
	}
	return this.expressions[0].toILLLExpr(vars);
}

assignment.prototype.toIL = function() {
	debugprint('//assignment', this.symbol.name);
	var existing = this.symbols.find(this.symbol.name);
	var prefix = '';
	assignNames.push(this.symbol.name);
	var val = this.expression.toIL();
	assignNames.pop(this.symbol.name);
	if (val === null) {
		return null;
	}
	if (existing.type == 'local' && !existing.isdeclared) {
		prefix = 'object *';
		this.symbols.declareVar(this.symbol.name);
		debugprint('//declared var', this.symbol.name);
	}
	return prefix + this.symbol.toIL() + ' = ' + val;
};
assignment.prototype.toILObject = function(cobj) {
	debugprint('//message definition', this.symbol.name);
	assignNames.push('#' + this.symbol.name);
	if (this.expression.toILObject) {
		var val = this.expression.toILObject(cobj.name);
	} else {
		var val = this.expression.toIL();
	}
	assignNames.pop();
	if (val === null) {
		return;
	}
	if (this.expression instanceof lambda) {
		var params = ['((object *)self)'];
		var paramget = '';
		var messagevars = {};
		for (var i in this.expression.args) {
			var escaped = escapeCName(this.expression.args[i].cleanName());
			if (escaped != 'self') {
				messagevars[escaped] = 'object *';
				params.push(escaped);
				paramget += escaped + ' =  va_arg(args, object *); ';
			}
		}
		cobj.addMessage(getOpMethodName(this.symbol.name), {
			vars: messagevars,
			lines: [paramget + 'return ' + val + '(' + (cobj.hasenv ? 'self->env' : 'NULL') + ', ' + params.length + (params.length ? ', ' : '') + params.join(', ') + ');']
		});
	} else {
		cobj.addProperty(this.symbol.name, val);
		if (this.expression instanceof object && this.expression.symbols.needsparent) {
			cobj.addInit('self->' + escapeCName(this.symbol.name) + '->parent = (object *)self;');
		}
	}
};
assignment.prototype.toILLines = function(vars, needsreturn) {
	return [(needsreturn ? 'return ' : '') + this.symbol.toILLLExpr(vars) + ' = ' + this.expression.toILLLExpr(vars) + ';']
};