variables
parent
8adccb7b65
commit
3c99623b6a
|
|
@ -0,0 +1,10 @@
|
||||||
|
module.exports = {
|
||||||
|
Body(statements) { return { type: 'body', value: statements } },
|
||||||
|
Link(identifier) { return { type: 'link', value: identifier } },
|
||||||
|
Invocation(identifier, ...args) { return { type: 'invo', value: identifier, args } },
|
||||||
|
Const(name, value) { return { type: 'const', value, name } },
|
||||||
|
Int(n) { return { type: 'int', value: n } },
|
||||||
|
String(s) { return { type: 'string', value: s } },
|
||||||
|
Variable(name, value) { return { type: 'var', value, name } },
|
||||||
|
VariableReference(name) { return { type: 'ref', value: name } }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
|
|
||||||
section .data
|
section .data
|
||||||
text db "abc",10,0
|
|
||||||
|
|
||||||
section .bss
|
section .bss
|
||||||
stackArguments resb 64
|
stackArguments resb 64
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
link log
|
link log
|
||||||
log("hello\n")
|
log("Hello")
|
||||||
|
log("World")
|
||||||
150
disco.js
150
disco.js
|
|
@ -1,140 +1,26 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const AST = {
|
const AST = require('./ast.js');
|
||||||
Body(statements) { return { type: 'body', value: statements } },
|
const compile = require('./compiler.js');
|
||||||
Identifier(name) { return { type: 'iden', value: name } },
|
|
||||||
Link(identifier) { return { type: 'link', value: identifier } },
|
|
||||||
Const(name, value) { return { type: 'const', name, value } },
|
|
||||||
Int(n) { return { type: 'int', value: n } },
|
|
||||||
String(s) { return { type: 'string', value: s } },
|
|
||||||
Invocation(identifier, ...args) { return { type: 'invo', value: identifier, args } }
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkables = {
|
|
||||||
log: {
|
|
||||||
asmName: '_log',
|
|
||||||
asm: `\
|
|
||||||
push rax
|
|
||||||
mov rbx, 0
|
|
||||||
_log_loop:
|
|
||||||
mov cl, [rax]
|
|
||||||
cmp cl, 0
|
|
||||||
je _log_loop_end
|
|
||||||
inc rax
|
|
||||||
inc rbx
|
|
||||||
jmp _log_loop
|
|
||||||
_log_loop_end:
|
|
||||||
mov rdx, rbx
|
|
||||||
mov rax, 1
|
|
||||||
mov rdi, 1
|
|
||||||
pop rsi
|
|
||||||
syscall
|
|
||||||
ret`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compile(body) {
|
|
||||||
const linkedFunctions = {}
|
|
||||||
|
|
||||||
// add linked functions
|
|
||||||
// add static literals
|
|
||||||
// add main statments
|
|
||||||
const rname = () => (new Array(8).fill('')).map(() => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Math.floor(Math.random() * 26)]).join('');
|
|
||||||
const literals = {
|
|
||||||
|
|
||||||
}
|
|
||||||
let mainStatements = '';
|
|
||||||
|
|
||||||
const parseLink = (identifier) => {
|
|
||||||
console.assert(identifier.type === 'iden', 'EXPECTED IDENTIFIER AFTER LINK');
|
|
||||||
console.assert(identifier.value in linkables, 'CANNOT FIND LINK ' + identifier.value);
|
|
||||||
linkedFunctions[identifier.value] = linkables[identifier.value];
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseString = (string) => {
|
|
||||||
const name = rname();
|
|
||||||
literals[name] = string.value
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseInvocation = (invocation) => {
|
|
||||||
console.assert(invocation.value.value in linkedFunctions, 'UNKNOWN FUNCTION ' + invocation.value);
|
|
||||||
|
|
||||||
const stuffIdk = [];
|
|
||||||
|
|
||||||
for(const arg of invocation.args) {
|
|
||||||
switch(arg.type) {
|
|
||||||
case 'string': {
|
|
||||||
const asmName = parseString(arg);
|
|
||||||
stuffIdk.push(asmName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainStatements += `\
|
|
||||||
mov rax, ${stuffIdk[0]}
|
|
||||||
call ${linkedFunctions[invocation.value.value].asmName}
|
|
||||||
`
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseBody = (body) => {
|
|
||||||
for(const node of body.value) {
|
|
||||||
switch(node.type) {
|
|
||||||
case 'link': {
|
|
||||||
parseLink(node.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'invo': {
|
|
||||||
parseInvocation(node);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseBody(body);
|
|
||||||
|
|
||||||
return `\
|
|
||||||
section .data
|
|
||||||
${Object.entries(literals).map(([name, string]) => {
|
|
||||||
return " " + name + " db \"" + string + "\",10,0";
|
|
||||||
})}
|
|
||||||
section .text
|
|
||||||
global _start
|
|
||||||
|
|
||||||
_start:
|
|
||||||
${mainStatements}
|
|
||||||
call _exit
|
|
||||||
|
|
||||||
_exit:
|
|
||||||
mov rax, 60
|
|
||||||
mov rdi, 0
|
|
||||||
syscall
|
|
||||||
|
|
||||||
${Object.values(linkedFunctions).map(({asmName, asm}) => {
|
|
||||||
return `${asmName}:\n${asm}`
|
|
||||||
}).join('\n\n')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
require('fs').writeFileSync('out.asm', compile(AST.Body([
|
|
||||||
AST.Link(AST.Identifier('log')),
|
|
||||||
AST.Invocation(
|
|
||||||
AST.Identifier('log'),
|
|
||||||
AST.String('Hello World')
|
|
||||||
)
|
|
||||||
])));
|
|
||||||
|
|
||||||
|
const myProgram = AST.Body([
|
||||||
|
AST.Variable('test1', AST.String('This String is Contained in a variable')),
|
||||||
|
AST.Variable('test2', AST.String('This is a second string in a variable')),
|
||||||
|
AST.Link('log'),
|
||||||
|
AST.Invocation('log', AST.String('hello')),
|
||||||
|
AST.Invocation('log', AST.String('world')),
|
||||||
|
AST.Invocation('log', AST.VariableReference('test1')),
|
||||||
|
AST.Invocation('log', AST.VariableReference('test2')),
|
||||||
|
AST.Invocation('log', AST.VariableReference('test1')),
|
||||||
|
AST.Invocation('log', AST.VariableReference('test2')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const asmFile = compile(myProgram)
|
||||||
try {
|
try {
|
||||||
require('child_process').execSync('nasm -f elf64 out.asm -o out.o');
|
require('fs').writeFileSync('out.asm', asmFile);
|
||||||
require('child_process').execSync('ld out.o -o out');
|
require('child_process').execSync('nasm -f elf64 out.asm -o out.o', { stdio: 'inherit' });
|
||||||
|
require('child_process').execSync('ld out.o -o out', { stdio: 'inherit' });
|
||||||
require('child_process').execSync('./out', { stdio: 'inherit' });
|
require('child_process').execSync('./out', { stdio: 'inherit' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
asmName: '_exit_with_code',
|
||||||
|
asm: `\
|
||||||
|
push rax
|
||||||
|
mov rax, 60
|
||||||
|
pop rdi
|
||||||
|
syscall
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
module.exports = {
|
||||||
|
asmName: '_log',
|
||||||
|
asm: `\
|
||||||
|
push rdi
|
||||||
|
mov rbx, 0
|
||||||
|
_log_loop:
|
||||||
|
mov cl, [rdi]
|
||||||
|
cmp cl, 0
|
||||||
|
je _log_loop_end
|
||||||
|
inc rdi
|
||||||
|
inc rbx
|
||||||
|
jmp _log_loop
|
||||||
|
_log_loop_end:
|
||||||
|
mov rdx, rbx
|
||||||
|
mov rax, 1
|
||||||
|
mov rdi, 1
|
||||||
|
pop rsi
|
||||||
|
syscall
|
||||||
|
push 10
|
||||||
|
mov rax, 1
|
||||||
|
mov rdi, 1
|
||||||
|
mov rsi, rsp
|
||||||
|
mov rdx, 1
|
||||||
|
syscall
|
||||||
|
pop rdi
|
||||||
|
ret`
|
||||||
|
}
|
||||||
42
out.asm
42
out.asm
|
|
@ -1,27 +1,40 @@
|
||||||
section .data
|
section .data
|
||||||
YMYJYNCD db "Hello World",10,0
|
QVGWSIUM db 84,104,105,115,32,83,116,114,105,110,103,32,105,115,32,67,111,110,116,97,105,110,101,100,32,105,110,32,97,32,118,97,114,105,97,98,108,101,0
|
||||||
|
ZYXGJUBF db 84,104,105,115,32,105,115,32,97,32,115,101,99,111,110,100,32,115,116,114,105,110,103,32,105,110,32,97,32,118,97,114,105,97,98,108,101,0
|
||||||
|
GPBLFTCX db 104,101,108,108,111,0
|
||||||
|
GXMDWCDF db 119,111,114,108,100,0
|
||||||
section .text
|
section .text
|
||||||
global _start
|
global _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
mov rax, YMYJYNCD
|
push rbp
|
||||||
|
mov rbp, rsp
|
||||||
|
push QVGWSIUM
|
||||||
|
push ZYXGJUBF
|
||||||
|
mov rdi, GPBLFTCX
|
||||||
call _log
|
call _log
|
||||||
|
mov rdi, GXMDWCDF
|
||||||
call _exit
|
call _log
|
||||||
|
mov rdi, [rbp - 8]
|
||||||
_exit:
|
call _log
|
||||||
|
mov rdi, [rbp - 16]
|
||||||
|
call _log
|
||||||
|
mov rdi, [rbp - 8]
|
||||||
|
call _log
|
||||||
|
mov rdi, [rbp - 16]
|
||||||
|
call _log
|
||||||
|
mov rsp, rbp
|
||||||
|
pop rbp
|
||||||
mov rax, 60
|
mov rax, 60
|
||||||
mov rdi, 0
|
mov rdi, 0
|
||||||
syscall
|
syscall
|
||||||
|
|
||||||
_log:
|
_log:
|
||||||
push rax
|
push rdi
|
||||||
mov rbx, 0
|
mov rbx, 0
|
||||||
_log_loop:
|
_log_loop:
|
||||||
mov cl, [rax]
|
mov cl, [rdi]
|
||||||
cmp cl, 0
|
cmp cl, 0
|
||||||
je _log_loop_end
|
je _log_loop_end
|
||||||
inc rax
|
inc rdi
|
||||||
inc rbx
|
inc rbx
|
||||||
jmp _log_loop
|
jmp _log_loop
|
||||||
_log_loop_end:
|
_log_loop_end:
|
||||||
|
|
@ -30,4 +43,11 @@ _log_loop_end:
|
||||||
mov rdi, 1
|
mov rdi, 1
|
||||||
pop rsi
|
pop rsi
|
||||||
syscall
|
syscall
|
||||||
|
push 10
|
||||||
|
mov rax, 1
|
||||||
|
mov rdi, 1
|
||||||
|
mov rsi, rsp
|
||||||
|
mov rdx, 1
|
||||||
|
syscall
|
||||||
|
pop rdi
|
||||||
ret
|
ret
|
||||||
Loading…
Reference in New Issue