mirror of
https://github.com/egormanga/Slang.git
synced 2025-03-13 23:42:44 +03:00
236 lines
6.1 KiB
Python
236 lines
6.1 KiB
Python
|
#!/usr/bin/python3
|
||
|
# Slang Native (ASM) x86_64 compiler target
|
||
|
|
||
|
from . import *
|
||
|
|
||
|
word_size = 8
|
||
|
registers = ('rax', 'rcx', 'rdx', 'rbx', 'rsi', 'rdi', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15')
|
||
|
regs64 = {i: () for i in registers}
|
||
|
regs32 = {'e'+i: ('r'+i, *regs64['r'+i]) for i in ('ax', 'cx', 'dx', 'bx', 'si', 'di')}
|
||
|
regs16 = {i: ('e'+i, *regs32['e'+i]) for i in ('ax', 'cx', 'dx', 'bx', 'si', 'di')}
|
||
|
regs8 = {i+j: (i+'x', *regs16[i+'x']) for i in 'acdb' for j in 'lh'}
|
||
|
|
||
|
class Instrs:
|
||
|
binopmap = {
|
||
|
'+': 'add',
|
||
|
}
|
||
|
stacksize = 0x10 # TODO
|
||
|
|
||
|
@init_defaults
|
||
|
def __init__(self, *, name, ns, filename, nargs: int, freeregs: set):
|
||
|
self.name, self.ns, self.filename, self.nargs, self.freeregs = name, ns, filename, nargs, freeregs
|
||
|
self.instrs = list()
|
||
|
self.varoff = bidict.OrderedBidict()
|
||
|
self.varsize = dict()
|
||
|
self.regpool = list(registers)
|
||
|
self.clobbered = set()
|
||
|
|
||
|
def compile(self):
|
||
|
saveregs = tuple(sorted(self.clobbered - self.freeregs))
|
||
|
instrs = [
|
||
|
"push rbp",
|
||
|
"mov rbp, rsp",
|
||
|
*(f"push {i}" for i in saveregs),
|
||
|
f"sub rsp, {self.stacksize}",
|
||
|
'\n',
|
||
|
*self.instrs,
|
||
|
'\n',
|
||
|
*(f"pop {i}" for i in saveregs[::-1]),
|
||
|
"leave",
|
||
|
f"ret {self.nargs}" if (self.nargs > 0) else "ret",
|
||
|
]
|
||
|
return f"global {self.name}\n{self.name}:\n\t"+'\n\t'.join('\t'.join(i.split(maxsplit=1)) for i in instrs)+'\n'
|
||
|
|
||
|
def var(self, name):
|
||
|
if (self.varoff[name] == 0): return '[rbp]'
|
||
|
return "[rbp%+d]" % -self.varoff[name]
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def vars(self, *names):
|
||
|
yield tuple(map(self.var, names))
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def regs(self, n):
|
||
|
regs = {self.regpool.pop(0) for _ in range(n)}
|
||
|
self.clobbered |= regs
|
||
|
try: yield regs
|
||
|
finally: self.regpool = [i for i in registers if i in {*self.regpool, *regs}]
|
||
|
|
||
|
#def syscall(self, rax, rdi=None, rsi=None, rdx=None, r10=None, r8=None, r9=None):
|
||
|
# self.instrs.append("
|
||
|
|
||
|
@dispatch
|
||
|
def add(self, x: ASTRootNode):
|
||
|
self.add(x.code)
|
||
|
|
||
|
@dispatch
|
||
|
def add(self, x: ASTCodeNode):
|
||
|
for i in x.nodes:
|
||
|
self.add(i)
|
||
|
|
||
|
@dispatch
|
||
|
def add(self, x: ASTVardefNode):
|
||
|
name = x.name.identifier
|
||
|
try:
|
||
|
ln, lo = last(self.varoff.items())
|
||
|
self.varoff[name] = lo+self.varsize[ln]
|
||
|
except StopIteration: self.varoff[name] = word_size
|
||
|
self.varsize[name] = self.sizeof(x.type)
|
||
|
if (x.value is not None): self.set(x.name, x.value)
|
||
|
|
||
|
@dispatch
|
||
|
def add(self, x: ASTFunccallNode):
|
||
|
callarguments = CallArguments.build(x, self.ns)
|
||
|
fsig = Signature.build(x.callable, self.ns)
|
||
|
fcall = fsig.compatible_call(callarguments, self.ns)
|
||
|
if (fcall is None): raise TODO(fcall)
|
||
|
|
||
|
n = int()
|
||
|
if (fcall[0] is not None): fname = f"{fsig.name}({CallArguments(args=fcall[0], ns=self.ns)})"
|
||
|
else:
|
||
|
if (isinstance(x.callable.value, ASTAttrgetNode)):
|
||
|
ofsig = Signature.build(x.callable.value.value, self.ns)
|
||
|
if (isinstance(fsig, stdlib.Builtin)):
|
||
|
fname = x.callable
|
||
|
else: raise NotImplementedError(ofsig)
|
||
|
else: raise NotImplementedError(x.callable.value)
|
||
|
|
||
|
for i in x.callargs.callargs:
|
||
|
self.push(i)
|
||
|
n += 1
|
||
|
|
||
|
if (x.callargs.starargs or x.callkwargs.callkwargs or x.callkwargs.starkwargs): raise NotImplementedError()
|
||
|
|
||
|
self.instrs.append(f"call {fname}")
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x: ASTValueNode, *reg):
|
||
|
return self.load(x.value, *reg)
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x: ASTLiteralNode):
|
||
|
return x.literal
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x):
|
||
|
with self.regs(1) as (reg,):
|
||
|
self.load(x, reg)
|
||
|
return reg
|
||
|
#reg = self.regpool.pop(0)
|
||
|
#self.clobbered.add(reg)
|
||
|
#self.load(x, reg)
|
||
|
#return reg
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x: ASTIdentifierNode, reg):
|
||
|
self.instrs.append(f"mov {reg}, {self.var(x.identifier)}")
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x: ASTBinaryExprNode, reg):
|
||
|
self.load(x.lvalue, reg)
|
||
|
self.instrs.append(f"{self.binopmap[x.operator.operator]} {reg}, {self.load(x.rvalue)}")
|
||
|
|
||
|
@dispatch
|
||
|
def load(self, x: str, reg):
|
||
|
self.instrs.append(f"mov {reg}, {x}")
|
||
|
|
||
|
@dispatch
|
||
|
def set(self, name: ASTIdentifierNode, value):
|
||
|
self.set(name.identifier, value)
|
||
|
|
||
|
@dispatch
|
||
|
def set(self, name: str, value: ASTValueNode):
|
||
|
self.set(name, value.value)
|
||
|
|
||
|
@dispatch
|
||
|
def set(self, name: str, value: ASTLiteralNode):
|
||
|
self.set(name, self.load(value))
|
||
|
|
||
|
@dispatch
|
||
|
def set(self, name: str, value):
|
||
|
with self.vars(name) as (var,):
|
||
|
self.instrs.append(f"mov {var}, {self.load(value)}")
|
||
|
|
||
|
def push(self, x):
|
||
|
self.instrs.append(f"push {self.load(x)}")
|
||
|
|
||
|
@dispatch
|
||
|
def sizeof(self, x: ASTTypedefNode):
|
||
|
return self.sizeof(Signature.build(x, self.ns))
|
||
|
|
||
|
@dispatch
|
||
|
def sizeof(self, x: lambda x: hasattr(x, 'fmt')):
|
||
|
return struct.calcsize(x.fmt)
|
||
|
|
||
|
class BuiltinInstrs(Instrs):
|
||
|
@init_defaults
|
||
|
def __init__(self, *, freeregs: set):
|
||
|
self.freeregs = freeregs
|
||
|
|
||
|
@singleton
|
||
|
class builtin_stdio_println(BuiltinInstrs):
|
||
|
name = 'stdio.println'
|
||
|
instrs = [
|
||
|
"mov al, [rbp+0x10]",
|
||
|
"add al, '0'",
|
||
|
"mov [rbp-2], al",
|
||
|
"mov al, 10",
|
||
|
"mov [rbp-1], al",
|
||
|
|
||
|
"mov rax, 1 ; sys_write",
|
||
|
"mov rdi, 1 ; stdout",
|
||
|
"lea rsi, [rbp-2] ; buf",
|
||
|
"mov rdx, 2 ; count",
|
||
|
"syscall",
|
||
|
]
|
||
|
nargs = 1
|
||
|
stacksize = 2
|
||
|
clobbered = {'rax', 'rcx', 'rdx', 'rdi', 'rsi', 'r11'}
|
||
|
|
||
|
class x86_64Compiler(NativeCompiler):
|
||
|
ext = '.o'
|
||
|
|
||
|
header = """
|
||
|
section .text
|
||
|
|
||
|
global _start
|
||
|
_start:
|
||
|
call main
|
||
|
mov rax, 60 ; sys_exit
|
||
|
mov rdi, 0 ; error_code
|
||
|
syscall
|
||
|
hlt
|
||
|
"""
|
||
|
|
||
|
@classmethod
|
||
|
def compile_ast(cls, ast, ns, *, filename):
|
||
|
instrs = Instrs(name='main', ns=ns, filename=filename)
|
||
|
instrs.add(ast)
|
||
|
src = f"# {filename}\n{S(cls.header).unindent().strip()}\n"
|
||
|
src += '\n'+builtin_stdio_println.compile()
|
||
|
src += '\n'+instrs.compile()
|
||
|
|
||
|
sname = os.path.splitext(filename)[0]+'.s'
|
||
|
sfd = open(sname, 'w')
|
||
|
try:
|
||
|
sfd.write(src)
|
||
|
sfd.close()
|
||
|
log("Src:\n"+src)
|
||
|
|
||
|
ofd, oname = tempfile.mkstemp(prefix=f"slc-{os.path.splitext(filename)[0]}-", suffix='.o')
|
||
|
try:
|
||
|
subprocess.run(('yasm', '-felf64', '-o', oname, sname), stderr=subprocess.PIPE, text=True, check=True)
|
||
|
return os.fdopen(ofd, 'rb').read()
|
||
|
except subprocess.CalledProcessError as ex: raise SlCompilationError('\n'+S(ex.stderr).indent(), ast, scope=instrs.ns) from ex
|
||
|
finally:
|
||
|
try: os.remove(oname)
|
||
|
except OSError: pass
|
||
|
finally:
|
||
|
sfd.close()
|
||
|
try: os.remove(sname)
|
||
|
except OSError: pass
|
||
|
|
||
|
compiler = x86_64Compiler
|
||
|
|
||
|
# by Sdore, 2021
|