129 lines
3.6 KiB
JavaScript
129 lines
3.6 KiB
JavaScript
|
|
const rname = () => (new Array(8).fill(''))
|
|
.map(() => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[
|
|
Math.floor(Math.random() * 26)
|
|
]).join('');
|
|
|
|
const linkables = {
|
|
log: require('./linkables/log.js'),
|
|
exit: require('./linkables/exit.js')
|
|
};
|
|
|
|
const callingConvention = {
|
|
SYSCALL_REGISTER: 'rax',
|
|
PARAMETER_REGISTERS: ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9']
|
|
}
|
|
|
|
const staticVariables = new Map();
|
|
const linkedLibraries = new Map();
|
|
const statements = [];
|
|
// this maybe should be absctracted into scopes but /shrug
|
|
const localVariables = new Map();
|
|
|
|
const sections = {
|
|
data() {
|
|
const convert = ([name, xs]) => name + ' db ' + xs.join(',')
|
|
return 'section .data\n '
|
|
+ [...staticVariables.entries()]
|
|
.map(convert)
|
|
.join('\n ')
|
|
},
|
|
text() {
|
|
return 'section .text\n global _start\n_start:\n push rbp\n mov rbp, rsp\n '
|
|
+ statements.join('\n ')
|
|
+ '\n mov rsp, rbp\n pop rbp\n mov rax, 60\n mov rdi, 0\n syscall\n'
|
|
+ [...linkedLibraries.values()].map(({asmName, asm}) => asmName + ':\n' + asm).join('\n')
|
|
}
|
|
}
|
|
|
|
function zip(a, b) {
|
|
const len = Math.min(a.length, b.length);
|
|
const ret = [];
|
|
for(let i = 0; i < len; i ++) {
|
|
ret.push([a[i], b[i]]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function compileStringLiteral(str) {
|
|
const varName = rname();
|
|
staticVariables.set(varName, Buffer.from(str + '\x00'));
|
|
return varName;
|
|
}
|
|
|
|
function compileRef(name) {
|
|
console.assert(localVariables.has(name), 'unknown variable ' + name);
|
|
return `[rbp - ${localVariables.get(name).offset}]`;
|
|
}
|
|
|
|
function compileInvocation(name, ...args) {
|
|
console.assert(linkedLibraries.has(name), 'unknown function ' + name);
|
|
console.assert(args.length <= 6, 'functions cannot have more than 6 arguments');
|
|
const argMoves = [];
|
|
for(const [register, argument] of zip(callingConvention.PARAMETER_REGISTERS, args)) {
|
|
if(typeof argument === 'object') {
|
|
switch (argument.type) {
|
|
case 'string': {
|
|
argMoves.push(`mov ${register}, ${compileStringLiteral(argument.value)}`)
|
|
break;
|
|
}
|
|
case 'ref': {
|
|
const reference = compileRef(argument.value);
|
|
argMoves.push(`mov ${register}, ${reference}`)
|
|
break;
|
|
}
|
|
default: {
|
|
console.error('unhandled argument type ' + argument.type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
statements.push(...argMoves);
|
|
statements.push('call ' + linkedLibraries.get(name).asmName);
|
|
}
|
|
|
|
function compileVariable(name, value) {
|
|
const typesize = 8;
|
|
console.assert(!localVariables.has(name), 'duplicate variable name ' + name);
|
|
localVariables.set(name, {
|
|
// !! this is wrong. \/ not everything is a 64 bit #
|
|
offset: typesize + localVariables.size * typesize,
|
|
size: typesize
|
|
});
|
|
if(value.type === 'string') {
|
|
const variableName = compileStringLiteral(value.value);
|
|
statements.push('push ' + variableName)
|
|
} else {
|
|
console.error('dont know how to set a variable to a non string lol')
|
|
}
|
|
}
|
|
|
|
function compileStatement(item) {
|
|
switch(item.type) {
|
|
case 'link': {
|
|
console.assert(item.value in linkables, 'Cannot find linked module: ' + item.value);
|
|
linkedLibraries.set(item.value, linkables[item.value]);
|
|
break;
|
|
}
|
|
case 'invo': {
|
|
compileInvocation(item.value, ...item.args);
|
|
break;
|
|
}
|
|
case 'var': {
|
|
compileVariable(item.name, item.value);
|
|
break;
|
|
}
|
|
default: {
|
|
console.error('unhandled statement ' + item.type);
|
|
}
|
|
}
|
|
}
|
|
|
|
function compile(tree) {
|
|
for(const item of tree.value) {
|
|
compileStatement(item);
|
|
}
|
|
return sections.data() + '\n' + sections.text();
|
|
}
|
|
|
|
module.exports = compile; |