phasm/py2wasm/compiler.py
Johan B.W. de Vries 453c2865a8 Structs
2022-06-19 16:09:06 +02:00

228 lines
7.0 KiB
Python

"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import Generator, Tuple
from . import ourlang
from . import wasm
Statements = Generator[wasm.Statement, None, None]
def type_(inp: ourlang.OurType) -> wasm.OurType:
if isinstance(inp, ourlang.OurTypeInt32):
return wasm.OurTypeInt32()
if isinstance(inp, ourlang.OurTypeInt64):
return wasm.OurTypeInt64()
if isinstance(inp, ourlang.OurTypeFloat32):
return wasm.OurTypeFloat32()
if isinstance(inp, ourlang.OurTypeFloat64):
return wasm.OurTypeFloat64()
if isinstance(inp, ourlang.Struct):
# Structs are passed as pointer
# And pointers are i32
return wasm.OurTypeInt32()
raise NotImplementedError(type_, inp)
OPERATOR_MAP = {
'+': 'add',
'-': 'sub',
}
I32_OPERATOR_MAP = { # TODO: Introduce UInt32 type
'<': 'lt_s',
'>': 'gt_s',
'<=': 'le_s',
'>=': 'ge_s',
}
def expression(inp: ourlang.Expression) -> Statements:
if isinstance(inp, ourlang.ConstantInt32):
yield wasm.Statement('i32.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantInt64):
yield wasm.Statement('i64.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantFloat32):
yield wasm.Statement('f32.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantFloat64):
yield wasm.Statement('f64.const', str(inp.value))
return
if isinstance(inp, ourlang.VariableReference):
yield wasm.Statement('local.get', '${}'.format(inp.name))
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression(inp.left)
yield from expression(inp.right)
if isinstance(inp.type, ourlang.OurTypeInt32):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i32.{operator}')
return
if operator := I32_OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i32.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeInt64):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i64.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeFloat32):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f32.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeFloat64):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.type, inp.operator)
if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments:
yield from expression(arg)
yield wasm.Statement('call', '${}'.format(inp.function.name))
return
if isinstance(inp, ourlang.AccessStructMember):
# FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(inp.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64,
)):
raise NotImplementedError(inp, inp.type)
yield from expression(inp.varref)
yield wasm.Statement(inp.type.render() + '.load', 'offset=' + str(inp.member.offset))
return
raise NotImplementedError(expression, inp)
def statement_return(inp: ourlang.StatementReturn) -> Statements:
yield from expression(inp.value)
yield wasm.Statement('return')
def statement_if(inp: ourlang.StatementIf) -> Statements:
yield from expression(inp.test)
yield wasm.Statement('if')
for stat in inp.statements:
yield from statement(stat)
if inp.else_statements:
yield wasm.Statement('else')
for stat in inp.else_statements:
yield from statement(stat)
yield wasm.Statement('end')
def statement(inp: ourlang.Statement) -> Statements:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(inp)
return
raise NotImplementedError(statement, inp)
def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param:
return (inp[0], type_(inp[1]), )
def function(inp: ourlang.Function) -> wasm.Function:
if isinstance(inp, ourlang.StructConstructor):
statements = [
*_generate_struct_constructor(inp)
]
locals_ = [
('___new_reference___addr', wasm.OurTypeInt32(), ),
]
else:
statements = [
x
for y in inp.statements
for x in statement(y)
]
locals_ = [] # TODO
return wasm.Function(
inp.name,
inp.exported,
[
function_argument(x)
for x in inp.posonlyargs
],
locals_,
type_(inp.returns),
statements
)
def module(inp: ourlang.Module) -> wasm.Module:
result = wasm.Module()
result.functions = [
_generate_allocator(inp),
] + [
function(x)
for x in inp.functions.values()
]
return result
def _generate_allocator(mod: ourlang.Module) -> wasm.Function:
return wasm.Function(
'___new_reference___',
False,
[
('alloc_size', type_(mod.types['i32']), ),
],
[
('result', type_(mod.types['i32']), ),
],
type_(mod.types['i32']),
[
wasm.Statement('i32.const', '0'),
wasm.Statement('i32.const', '0'),
wasm.Statement('i32.load'),
wasm.Statement('local.tee', '$result', comment='Address for this call'),
wasm.Statement('local.get', '$alloc_size'),
wasm.Statement('i32.add'),
wasm.Statement('i32.store', comment='Address for the next call'),
wasm.Statement('local.get', '$result'),
],
)
def _generate_struct_constructor(inp: ourlang.StructConstructor) -> Statements:
yield wasm.Statement('i32.const', str(inp.struct.alloc_size()))
yield wasm.Statement('call', '$___new_reference___')
yield wasm.Statement('local.set', '$___new_reference___addr')
for member in inp.struct.members:
# FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(member.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64,
)):
raise NotImplementedError
yield wasm.Statement('local.get', '$___new_reference___addr')
yield wasm.Statement('local.get', f'${member.name}')
yield wasm.Statement(f'{member.type.render()}.store', 'offset=' + str(member.offset))
yield wasm.Statement('local.get', '$___new_reference___addr')