# HG changeset patch # User Mike Pavone # Date 1381720988 25200 # Node ID 6fe9343b140045b0bf227c835296cf60d82280d6 # Parent a8dffa4d4b548689ea28133176f558c4bc9157ad Some minor work on creating an IL backend based on the C backend diff -r a8dffa4d4b54 -r 6fe9343b1400 ilbackend.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ilbackend.js Sun Oct 13 20:23:08 2013 -0700 @@ -0,0 +1,1397 @@ +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(''); + //-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(''); + os.addInclude(''); + os.addInclude(''); + os.addInclude(''); + os.addInclude(''); + 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) + ';'] +}; diff -r a8dffa4d4b54 -r 6fe9343b1400 tpc.js --- a/tpc.js Wed Aug 28 21:50:22 2013 -0700 +++ b/tpc.js Sun Oct 13 20:23:08 2013 -0700 @@ -94,6 +94,8 @@ load(basedir + 'compiler.js'); if (backend == 'C') { load(basedir + 'cbackend.js'); + } else if (backend == 'IL') { + load(basedir + 'ilbackend.js'); } else { load(basedir + 'jsbackend.js'); } @@ -108,6 +110,8 @@ case 'C': var c = parsed.toCModule(); break; + case 'IL': + var c = parsed.toILModule(); case 'JS': var c = makeJSProg(parsed); break;