They look a lot like placeholders, but they exist before the typing system takes place. And there's a (much smaller) context to deal with. For now, removes Placeholders in user function definitions as they were not implemented. Adds signature to function to try to get them closer to type class methods. Already seeing some benefit in the constraint generator. Stricter zipping for safety.
991 lines
34 KiB
Python
991 lines
34 KiB
Python
"""
|
|
This module contains the code to convert parsed Ourlang into WebAssembly code
|
|
"""
|
|
import struct
|
|
from typing import Dict, List, Optional
|
|
|
|
from . import codestyle, ourlang, prelude, wasm
|
|
from .runtime import calculate_alloc_size, calculate_member_offset
|
|
from .stdlib import alloc as stdlib_alloc
|
|
from .stdlib import types as stdlib_types
|
|
from .type3 import functions as type3functions
|
|
from .type3 import placeholders as type3placeholders
|
|
from .type3 import typeclasses as type3classes
|
|
from .type3 import types as type3types
|
|
from .wasmgenerator import Generator as WasmGenerator
|
|
|
|
LOAD_STORE_TYPE_MAP = {
|
|
'i8': 'i32', # Have to use an u32, since there is no native i8 type
|
|
'u8': 'i32', # Have to use an u32, since there is no native u8 type
|
|
|
|
'i32': 'i32',
|
|
'i64': 'i64',
|
|
'u32': 'i32',
|
|
'u64': 'i64',
|
|
'f32': 'f32',
|
|
'f64': 'f64',
|
|
|
|
'bytes': 'i32', # Bytes are passed around as pointers
|
|
}
|
|
|
|
# For now this is nice & clean, but this will get messy quick
|
|
# Especially once we get functions with polymorphying applied types
|
|
INSTANCES = {
|
|
prelude.Eq.operators['==']: {
|
|
'a=u8': stdlib_types.u8_eq_equals,
|
|
'a=u32': stdlib_types.u32_eq_equals,
|
|
'a=u64': stdlib_types.u64_eq_equals,
|
|
'a=i8': stdlib_types.i8_eq_equals,
|
|
'a=i32': stdlib_types.i32_eq_equals,
|
|
'a=i64': stdlib_types.i64_eq_equals,
|
|
'a=f32': stdlib_types.f32_eq_equals,
|
|
'a=f64': stdlib_types.f64_eq_equals,
|
|
},
|
|
prelude.Eq.operators['!=']: {
|
|
'a=u8': stdlib_types.u8_eq_not_equals,
|
|
'a=u32': stdlib_types.u32_eq_not_equals,
|
|
'a=u64': stdlib_types.u64_eq_not_equals,
|
|
'a=i8': stdlib_types.i8_eq_not_equals,
|
|
'a=i32': stdlib_types.i32_eq_not_equals,
|
|
'a=i64': stdlib_types.i64_eq_not_equals,
|
|
'a=f32': stdlib_types.f32_eq_not_equals,
|
|
'a=f64': stdlib_types.f64_eq_not_equals,
|
|
},
|
|
prelude.Ord.methods['min']: {
|
|
'a=u8': stdlib_types.u8_ord_min,
|
|
'a=u32': stdlib_types.u32_ord_min,
|
|
'a=u64': stdlib_types.u64_ord_min,
|
|
'a=i8': stdlib_types.i8_ord_min,
|
|
'a=i32': stdlib_types.i32_ord_min,
|
|
'a=i64': stdlib_types.i64_ord_min,
|
|
'a=f32': stdlib_types.f32_ord_min,
|
|
'a=f64': stdlib_types.f64_ord_min,
|
|
},
|
|
prelude.Ord.methods['max']: {
|
|
'a=u8': stdlib_types.u8_ord_max,
|
|
'a=u32': stdlib_types.u32_ord_max,
|
|
'a=u64': stdlib_types.u64_ord_max,
|
|
'a=i8': stdlib_types.i8_ord_max,
|
|
'a=i32': stdlib_types.i32_ord_max,
|
|
'a=i64': stdlib_types.i64_ord_max,
|
|
'a=f32': stdlib_types.f32_ord_max,
|
|
'a=f64': stdlib_types.f64_ord_max,
|
|
},
|
|
prelude.Ord.operators['<']: {
|
|
'a=u8': stdlib_types.u8_ord_less_than,
|
|
'a=u32': stdlib_types.u32_ord_less_than,
|
|
'a=u64': stdlib_types.u64_ord_less_than,
|
|
'a=i8': stdlib_types.i8_ord_less_than,
|
|
'a=i32': stdlib_types.i32_ord_less_than,
|
|
'a=i64': stdlib_types.i64_ord_less_than,
|
|
'a=f32': stdlib_types.f32_ord_less_than,
|
|
'a=f64': stdlib_types.f64_ord_less_than,
|
|
},
|
|
prelude.Ord.operators['<=']: {
|
|
'a=u8': stdlib_types.u8_ord_less_than_or_equal,
|
|
'a=u32': stdlib_types.u32_ord_less_than_or_equal,
|
|
'a=u64': stdlib_types.u64_ord_less_than_or_equal,
|
|
'a=i8': stdlib_types.i8_ord_less_than_or_equal,
|
|
'a=i32': stdlib_types.i32_ord_less_than_or_equal,
|
|
'a=i64': stdlib_types.i64_ord_less_than_or_equal,
|
|
'a=f32': stdlib_types.f32_ord_less_than_or_equal,
|
|
'a=f64': stdlib_types.f64_ord_less_than_or_equal,
|
|
},
|
|
prelude.Ord.operators['>']: {
|
|
'a=u8': stdlib_types.u8_ord_greater_than,
|
|
'a=u32': stdlib_types.u32_ord_greater_than,
|
|
'a=u64': stdlib_types.u64_ord_greater_than,
|
|
'a=i8': stdlib_types.i8_ord_greater_than,
|
|
'a=i32': stdlib_types.i32_ord_greater_than,
|
|
'a=i64': stdlib_types.i64_ord_greater_than,
|
|
'a=f32': stdlib_types.f32_ord_greater_than,
|
|
'a=f64': stdlib_types.f64_ord_greater_than,
|
|
},
|
|
prelude.Ord.operators['>=']: {
|
|
'a=u8': stdlib_types.u8_ord_greater_than_or_equal,
|
|
'a=u32': stdlib_types.u32_ord_greater_than_or_equal,
|
|
'a=u64': stdlib_types.u64_ord_greater_than_or_equal,
|
|
'a=i8': stdlib_types.i8_ord_greater_than_or_equal,
|
|
'a=i32': stdlib_types.i32_ord_greater_than_or_equal,
|
|
'a=i64': stdlib_types.i64_ord_greater_than_or_equal,
|
|
'a=f32': stdlib_types.f32_ord_greater_than_or_equal,
|
|
'a=f64': stdlib_types.f64_ord_greater_than_or_equal,
|
|
},
|
|
prelude.Bits.methods['shl']: {
|
|
'a=u8': stdlib_types.u8_bits_logical_shift_left,
|
|
'a=u32': stdlib_types.u32_bits_logical_shift_left,
|
|
'a=u64': stdlib_types.u64_bits_logical_shift_left,
|
|
},
|
|
prelude.Bits.methods['shr']: {
|
|
'a=u8': stdlib_types.u8_bits_logical_shift_right,
|
|
'a=u32': stdlib_types.u32_bits_logical_shift_right,
|
|
'a=u64': stdlib_types.u64_bits_logical_shift_right,
|
|
},
|
|
prelude.Bits.methods['rotl']: {
|
|
'a=u8': stdlib_types.u8_bits_rotate_left,
|
|
'a=u32': stdlib_types.u32_bits_rotate_left,
|
|
'a=u64': stdlib_types.u64_bits_rotate_left,
|
|
},
|
|
prelude.Bits.methods['rotr']: {
|
|
'a=u8': stdlib_types.u8_bits_rotate_right,
|
|
'a=u32': stdlib_types.u32_bits_rotate_right,
|
|
'a=u64': stdlib_types.u64_bits_rotate_right,
|
|
},
|
|
prelude.Bits.operators['&']: {
|
|
'a=u8': stdlib_types.u8_bits_bitwise_and,
|
|
'a=u32': stdlib_types.u32_bits_bitwise_and,
|
|
'a=u64': stdlib_types.u64_bits_bitwise_and,
|
|
},
|
|
prelude.Bits.operators['|']: {
|
|
'a=u8': stdlib_types.u8_bits_bitwise_or,
|
|
'a=u32': stdlib_types.u32_bits_bitwise_or,
|
|
'a=u64': stdlib_types.u64_bits_bitwise_or,
|
|
},
|
|
prelude.Bits.operators['^']: {
|
|
'a=u8': stdlib_types.u8_bits_bitwise_xor,
|
|
'a=u32': stdlib_types.u32_bits_bitwise_xor,
|
|
'a=u64': stdlib_types.u64_bits_bitwise_xor,
|
|
},
|
|
prelude.Floating.methods['sqrt']: {
|
|
'a=f32': stdlib_types.f32_floating_sqrt,
|
|
'a=f64': stdlib_types.f64_floating_sqrt,
|
|
},
|
|
prelude.Fractional.methods['ceil']: {
|
|
'a=f32': stdlib_types.f32_fractional_ceil,
|
|
'a=f64': stdlib_types.f64_fractional_ceil,
|
|
},
|
|
prelude.Fractional.methods['floor']: {
|
|
'a=f32': stdlib_types.f32_fractional_floor,
|
|
'a=f64': stdlib_types.f64_fractional_floor,
|
|
},
|
|
prelude.Fractional.methods['trunc']: {
|
|
'a=f32': stdlib_types.f32_fractional_trunc,
|
|
'a=f64': stdlib_types.f64_fractional_trunc,
|
|
},
|
|
prelude.Fractional.methods['nearest']: {
|
|
'a=f32': stdlib_types.f32_fractional_nearest,
|
|
'a=f64': stdlib_types.f64_fractional_nearest,
|
|
},
|
|
prelude.Fractional.operators['/']: {
|
|
'a=f32': stdlib_types.f32_fractional_div,
|
|
'a=f64': stdlib_types.f64_fractional_div,
|
|
},
|
|
prelude.Integral.operators['//']: {
|
|
'a=u32': stdlib_types.u32_integral_div,
|
|
'a=u64': stdlib_types.u64_integral_div,
|
|
'a=i32': stdlib_types.i32_integral_div,
|
|
'a=i64': stdlib_types.i64_integral_div,
|
|
},
|
|
prelude.Integral.operators['%']: {
|
|
'a=u32': stdlib_types.u32_integral_rem,
|
|
'a=u64': stdlib_types.u64_integral_rem,
|
|
'a=i32': stdlib_types.i32_integral_rem,
|
|
'a=i64': stdlib_types.i64_integral_rem,
|
|
},
|
|
prelude.IntNum.methods['abs']: {
|
|
'a=i32': stdlib_types.i32_intnum_abs,
|
|
'a=i64': stdlib_types.i64_intnum_abs,
|
|
'a=f32': stdlib_types.f32_intnum_abs,
|
|
'a=f64': stdlib_types.f64_intnum_abs,
|
|
},
|
|
prelude.IntNum.methods['neg']: {
|
|
'a=i32': stdlib_types.i32_intnum_neg,
|
|
'a=i64': stdlib_types.i64_intnum_neg,
|
|
'a=f32': stdlib_types.f32_intnum_neg,
|
|
'a=f64': stdlib_types.f64_intnum_neg,
|
|
},
|
|
prelude.NatNum.operators['+']: {
|
|
'a=u32': stdlib_types.u32_natnum_add,
|
|
'a=u64': stdlib_types.u64_natnum_add,
|
|
'a=i32': stdlib_types.i32_natnum_add,
|
|
'a=i64': stdlib_types.i64_natnum_add,
|
|
'a=f32': stdlib_types.f32_natnum_add,
|
|
'a=f64': stdlib_types.f64_natnum_add,
|
|
},
|
|
prelude.NatNum.operators['-']: {
|
|
'a=u32': stdlib_types.u32_natnum_sub,
|
|
'a=u64': stdlib_types.u64_natnum_sub,
|
|
'a=i32': stdlib_types.i32_natnum_sub,
|
|
'a=i64': stdlib_types.i64_natnum_sub,
|
|
'a=f32': stdlib_types.f32_natnum_sub,
|
|
'a=f64': stdlib_types.f64_natnum_sub,
|
|
},
|
|
prelude.NatNum.operators['*']: {
|
|
'a=u32': stdlib_types.u32_natnum_mul,
|
|
'a=u64': stdlib_types.u64_natnum_mul,
|
|
'a=i32': stdlib_types.i32_natnum_mul,
|
|
'a=i64': stdlib_types.i64_natnum_mul,
|
|
'a=f32': stdlib_types.f32_natnum_mul,
|
|
'a=f64': stdlib_types.f64_natnum_mul,
|
|
},
|
|
prelude.NatNum.operators['<<']: {
|
|
'a=u32': stdlib_types.u32_natnum_arithmic_shift_left,
|
|
'a=u64': stdlib_types.u64_natnum_arithmic_shift_left,
|
|
'a=i32': stdlib_types.i32_natnum_arithmic_shift_left,
|
|
'a=i64': stdlib_types.i64_natnum_arithmic_shift_left,
|
|
'a=f32': stdlib_types.f32_natnum_arithmic_shift_left,
|
|
'a=f64': stdlib_types.f64_natnum_arithmic_shift_left,
|
|
},
|
|
prelude.NatNum.operators['>>']: {
|
|
'a=u32': stdlib_types.u32_natnum_arithmic_shift_right,
|
|
'a=u64': stdlib_types.u64_natnum_arithmic_shift_right,
|
|
'a=i32': stdlib_types.i32_natnum_arithmic_shift_right,
|
|
'a=i64': stdlib_types.i64_natnum_arithmic_shift_right,
|
|
'a=f32': stdlib_types.f32_natnum_arithmic_shift_right,
|
|
'a=f64': stdlib_types.f64_natnum_arithmic_shift_right,
|
|
},
|
|
prelude.Sized_.methods['len']: {
|
|
'a=bytes': stdlib_types.bytes_sized_len,
|
|
},
|
|
}
|
|
|
|
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
|
"""
|
|
Public method for compiling a parsed Phasm module into
|
|
a WebAssembly module
|
|
"""
|
|
return module(inp)
|
|
|
|
def type3(inp: type3placeholders.Type3OrPlaceholder) -> wasm.WasmType:
|
|
"""
|
|
Compile: type
|
|
|
|
Types are used for example in WebAssembly function parameters
|
|
and return types.
|
|
"""
|
|
assert isinstance(inp, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if inp == prelude.none:
|
|
return wasm.WasmTypeNone()
|
|
|
|
if inp == prelude.bool_:
|
|
# WebAssembly stores booleans as i32
|
|
# See e.g. f32.eq, which is [f32 f32] -> [i32]
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if inp == prelude.u8:
|
|
# WebAssembly has only support for 32 and 64 bits
|
|
# So we need to store more memory per byte
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if inp == prelude.u32:
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if inp == prelude.u64:
|
|
return wasm.WasmTypeInt64()
|
|
|
|
if inp == prelude.i8:
|
|
# WebAssembly has only support for 32 and 64 bits
|
|
# So we need to store more memory per byte
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if inp == prelude.i32:
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if inp == prelude.i64:
|
|
return wasm.WasmTypeInt64()
|
|
|
|
if inp == prelude.f32:
|
|
return wasm.WasmTypeFloat32()
|
|
|
|
if inp == prelude.f64:
|
|
return wasm.WasmTypeFloat64()
|
|
|
|
if inp == prelude.bytes_:
|
|
# bytes are passed as pointer
|
|
# And pointers are i32
|
|
return wasm.WasmTypeInt32()
|
|
|
|
if prelude.InternalPassAsPointer in inp.classes:
|
|
return wasm.WasmTypeInt32()
|
|
|
|
raise NotImplementedError(type3, inp)
|
|
|
|
def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None:
|
|
"""
|
|
Compile: Instantiation (allocation) of a tuple
|
|
"""
|
|
assert isinstance(inp.type3, type3types.Type3)
|
|
|
|
args: list[type3types.Type3] = []
|
|
|
|
sa_args = prelude.static_array.did_construct(inp.type3)
|
|
if sa_args is not None:
|
|
sa_type, sa_len = sa_args
|
|
args = [sa_type for _ in range(sa_len.value)]
|
|
|
|
if not args:
|
|
tp_args = prelude.tuple_.did_construct(inp.type3)
|
|
if tp_args is None:
|
|
raise NotImplementedError
|
|
|
|
args = list(tp_args)
|
|
|
|
comment_elements = ''
|
|
for element in inp.elements:
|
|
assert isinstance(element.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
comment_elements += f'{element.type3.name}, '
|
|
|
|
tmp_var = wgn.temp_var_i32('tuple_adr')
|
|
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
|
|
|
|
# Allocated the required amounts of bytes in memory
|
|
wgn.i32.const(calculate_alloc_size(inp.type3, is_member=False))
|
|
wgn.call(stdlib_alloc.__alloc__)
|
|
wgn.local.set(tmp_var)
|
|
|
|
# Store each element individually
|
|
offset = 0
|
|
for element, exp_type3 in zip(inp.elements, args, strict=True):
|
|
if isinstance(exp_type3, type3placeholders.PlaceholderForType):
|
|
assert exp_type3.resolve_as is not None
|
|
assert isinstance(exp_type3.resolve_as, type3types.Type3)
|
|
exp_type3 = exp_type3.resolve_as
|
|
|
|
assert element.type3 == exp_type3
|
|
|
|
if prelude.InternalPassAsPointer in exp_type3.classes:
|
|
mtyp = 'i32'
|
|
else:
|
|
assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
|
|
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
|
|
|
|
wgn.add_statement('nop', comment='PRE')
|
|
wgn.local.get(tmp_var)
|
|
expression(wgn, element)
|
|
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
|
|
wgn.add_statement('nop', comment='POST')
|
|
|
|
offset += calculate_alloc_size(exp_type3, is_member=True)
|
|
|
|
# Return the allocated address
|
|
wgn.local.get(tmp_var)
|
|
|
|
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|
"""
|
|
Compile: Any expression
|
|
"""
|
|
if isinstance(inp, (ourlang.ConstantStruct, ourlang.ConstantTuple, )):
|
|
# These are implemented elsewhere
|
|
raise Exception
|
|
|
|
if isinstance(inp, ourlang.ConstantPrimitive):
|
|
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if inp.type3 in (prelude.i8, prelude.u8, ):
|
|
# No native u8 type - treat as i32, with caution
|
|
assert isinstance(inp.value, int)
|
|
wgn.i32.const(inp.value)
|
|
return
|
|
|
|
if inp.type3 in (prelude.i32, prelude.u32, ):
|
|
assert isinstance(inp.value, int)
|
|
wgn.i32.const(inp.value)
|
|
return
|
|
|
|
if inp.type3 in (prelude.i64, prelude.u64, ):
|
|
assert isinstance(inp.value, int)
|
|
wgn.i64.const(inp.value)
|
|
return
|
|
|
|
if inp.type3 == prelude.f32:
|
|
assert isinstance(inp.value, float)
|
|
wgn.f32.const(inp.value)
|
|
return
|
|
|
|
if inp.type3 == prelude.f64:
|
|
assert isinstance(inp.value, float)
|
|
wgn.f64.const(inp.value)
|
|
return
|
|
|
|
raise NotImplementedError(f'Constants with type {inp.type3:s}')
|
|
|
|
if isinstance(inp, ourlang.ConstantBytes):
|
|
assert inp.data_block.address is not None, 'Value not allocated'
|
|
wgn.i32.const(inp.data_block.address)
|
|
return
|
|
|
|
if isinstance(inp, ourlang.VariableReference):
|
|
if isinstance(inp.variable, ourlang.FunctionParam):
|
|
wgn.add_statement('local.get', '${}'.format(inp.variable.name))
|
|
return
|
|
|
|
if isinstance(inp.variable, ourlang.ModuleConstantDef):
|
|
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if prelude.InternalPassAsPointer in inp.type3.classes:
|
|
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
|
|
|
|
address = inp.variable.constant.data_block.address
|
|
assert address is not None, 'Value not allocated'
|
|
wgn.i32.const(address)
|
|
return
|
|
|
|
if isinstance(inp.type3, type3types.Type3):
|
|
expression(wgn, inp.variable.constant)
|
|
return
|
|
|
|
raise NotImplementedError(expression, inp)
|
|
|
|
raise NotImplementedError(expression, inp.variable)
|
|
|
|
if isinstance(inp, ourlang.BinaryOp):
|
|
expression(wgn, inp.left)
|
|
expression(wgn, inp.right)
|
|
|
|
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {}
|
|
|
|
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
|
|
if not isinstance(type_var, type3functions.TypeVariable):
|
|
# Fixed type, not part of the lookup requirements
|
|
continue
|
|
|
|
assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
type_var_map[type_var] = arg_expr.type3
|
|
|
|
instance_key = ','.join(
|
|
f'{k.letter}={v.name}'
|
|
for k, v in type_var_map.items()
|
|
)
|
|
|
|
instance = INSTANCES.get(inp.operator, {}).get(instance_key, None)
|
|
if instance is not None:
|
|
instance(wgn)
|
|
return
|
|
|
|
raise NotImplementedError(inp.operator, instance_key)
|
|
|
|
if isinstance(inp, ourlang.UnaryOp):
|
|
expression(wgn, inp.right)
|
|
|
|
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if inp.operator == 'cast':
|
|
if inp.type3 == prelude.u32 and inp.right.type3 == prelude.u8:
|
|
# Nothing to do, you can use an u8 value as a u32 no problem
|
|
return
|
|
|
|
raise NotImplementedError(expression, inp.type3, inp.operator)
|
|
|
|
if isinstance(inp, ourlang.FunctionCall):
|
|
for arg in inp.arguments:
|
|
expression(wgn, arg)
|
|
|
|
if isinstance(inp.function, type3classes.Type3ClassMethod):
|
|
# FIXME: Duplicate code with BinaryOp
|
|
type_var_map = {}
|
|
|
|
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
|
|
if not isinstance(type_var, type3functions.TypeVariable):
|
|
# Fixed type, not part of the lookup requirements
|
|
continue
|
|
|
|
assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
type_var_map[type_var] = arg_expr.type3
|
|
|
|
instance_key = ','.join(
|
|
f'{k.letter}={v.name}'
|
|
for k, v in type_var_map.items()
|
|
)
|
|
|
|
instance = INSTANCES.get(inp.function, {}).get(instance_key, None)
|
|
if instance is not None:
|
|
instance(wgn)
|
|
return
|
|
|
|
raise NotImplementedError(inp.function, instance_key)
|
|
|
|
wgn.add_statement('call', '${}'.format(inp.function.name))
|
|
return
|
|
|
|
if isinstance(inp, ourlang.TupleInstantiation):
|
|
tuple_instantiation(wgn, inp)
|
|
return
|
|
|
|
if isinstance(inp, ourlang.Subscript):
|
|
assert isinstance(inp.varref.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if inp.varref.type3 is prelude.bytes_:
|
|
expression(wgn, inp.varref)
|
|
expression(wgn, inp.index)
|
|
wgn.call(stdlib_types.__subscript_bytes__)
|
|
return
|
|
|
|
assert isinstance(inp.varref.type3, type3types.Type3)
|
|
|
|
sa_args = prelude.static_array.did_construct(inp.varref.type3)
|
|
if sa_args is not None:
|
|
el_type, el_len = sa_args
|
|
|
|
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
|
|
# and we don't need to do the out of bounds check
|
|
|
|
expression(wgn, inp.varref)
|
|
|
|
tmp_var = wgn.temp_var_i32('index')
|
|
expression(wgn, inp.index)
|
|
wgn.local.tee(tmp_var)
|
|
|
|
# Out of bounds check based on el_len.value
|
|
wgn.i32.const(el_len.value)
|
|
wgn.i32.ge_u()
|
|
with wgn.if_():
|
|
wgn.unreachable(comment='Out of bounds')
|
|
|
|
wgn.local.get(tmp_var)
|
|
wgn.i32.const(calculate_alloc_size(el_type))
|
|
wgn.i32.mul()
|
|
wgn.i32.add()
|
|
|
|
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
|
|
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
|
|
|
wgn.add_statement(f'{mtyp}.load')
|
|
return
|
|
|
|
tp_args = prelude.tuple_.did_construct(inp.varref.type3)
|
|
if tp_args is not None:
|
|
assert isinstance(inp.index, ourlang.ConstantPrimitive)
|
|
assert isinstance(inp.index.value, int)
|
|
|
|
offset = 0
|
|
for el_type in tp_args[0:inp.index.value]:
|
|
assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
offset += calculate_alloc_size(el_type)
|
|
|
|
el_type = tp_args[inp.index.value]
|
|
assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
expression(wgn, inp.varref)
|
|
|
|
if prelude.InternalPassAsPointer in el_type.classes:
|
|
mtyp = 'i32'
|
|
else:
|
|
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
|
|
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
|
|
|
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
|
|
return
|
|
|
|
raise NotImplementedError(expression, inp, inp.varref.type3)
|
|
|
|
if isinstance(inp, ourlang.AccessStructMember):
|
|
assert isinstance(inp.struct_type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
st_args = prelude.struct.did_construct(inp.struct_type3)
|
|
assert st_args is not None
|
|
|
|
member_type = st_args[inp.member]
|
|
|
|
assert isinstance(member_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
|
|
mtyp = LOAD_STORE_TYPE_MAP[member_type.name]
|
|
|
|
expression(wgn, inp.varref)
|
|
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset(
|
|
inp.struct_type3.name, st_args, inp.member
|
|
)))
|
|
return
|
|
|
|
if isinstance(inp, ourlang.Fold):
|
|
expression_fold(wgn, inp)
|
|
return
|
|
|
|
raise NotImplementedError(expression, inp)
|
|
|
|
def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
|
"""
|
|
Compile: Fold expression
|
|
"""
|
|
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
|
|
|
|
if inp.iter.type3 is not prelude.bytes_:
|
|
raise NotImplementedError(expression_fold, inp, inp.iter.type3)
|
|
|
|
wgn.add_statement('nop', comment='acu :: u8')
|
|
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
|
|
wgn.add_statement('nop', comment='adr :: bytes*')
|
|
adr_var = wgn.temp_var_i32('fold_i32_adr')
|
|
wgn.add_statement('nop', comment='len :: i32')
|
|
len_var = wgn.temp_var_i32('fold_i32_len')
|
|
|
|
wgn.add_statement('nop', comment='acu = base')
|
|
expression(wgn, inp.base)
|
|
wgn.local.set(acu_var)
|
|
|
|
wgn.add_statement('nop', comment='adr = adr(iter)')
|
|
expression(wgn, inp.iter)
|
|
wgn.local.set(adr_var)
|
|
|
|
wgn.add_statement('nop', comment='len = len(iter)')
|
|
wgn.local.get(adr_var)
|
|
wgn.i32.load()
|
|
wgn.local.set(len_var)
|
|
|
|
wgn.add_statement('nop', comment='i = 0')
|
|
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
|
|
wgn.i32.const(0)
|
|
wgn.local.set(idx_var)
|
|
|
|
wgn.add_statement('nop', comment='if i < len')
|
|
wgn.local.get(idx_var)
|
|
wgn.local.get(len_var)
|
|
wgn.i32.lt_u()
|
|
with wgn.if_():
|
|
# From here on, adr_var is the address of byte we're referencing
|
|
# This is akin to calling stdlib_types.__subscript_bytes__
|
|
# But since we already know we are inside of bounds,
|
|
# can just bypass it and load the memory directly.
|
|
wgn.local.get(adr_var)
|
|
wgn.i32.const(3) # Bytes header -1, since we do a +1 every loop
|
|
wgn.i32.add()
|
|
wgn.local.set(adr_var)
|
|
|
|
wgn.add_statement('nop', comment='while True')
|
|
with wgn.loop():
|
|
wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
|
|
wgn.local.get(acu_var)
|
|
|
|
# Get the next byte, write back the address
|
|
wgn.local.get(adr_var)
|
|
wgn.i32.const(1)
|
|
wgn.i32.add()
|
|
wgn.local.tee(adr_var)
|
|
wgn.i32.load8_u()
|
|
|
|
wgn.add_statement('call', f'${inp.func.name}')
|
|
wgn.local.set(acu_var)
|
|
|
|
wgn.add_statement('nop', comment='i = i + 1')
|
|
wgn.local.get(idx_var)
|
|
wgn.i32.const(1)
|
|
wgn.i32.add()
|
|
wgn.local.set(idx_var)
|
|
|
|
wgn.add_statement('nop', comment='if i >= len: break')
|
|
wgn.local.get(idx_var)
|
|
wgn.local.get(len_var)
|
|
wgn.i32.lt_u()
|
|
wgn.br_if(0)
|
|
|
|
# return acu
|
|
wgn.local.get(acu_var)
|
|
|
|
def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
|
|
"""
|
|
Compile: Return statement
|
|
"""
|
|
expression(wgn, inp.value)
|
|
wgn.return_()
|
|
|
|
def statement_if(wgn: WasmGenerator, inp: ourlang.StatementIf) -> None:
|
|
"""
|
|
Compile: If statement
|
|
"""
|
|
expression(wgn, inp.test)
|
|
with wgn.if_():
|
|
for stat in inp.statements:
|
|
statement(wgn, stat)
|
|
|
|
if inp.else_statements:
|
|
raise NotImplementedError
|
|
# yield wasm.Statement('else')
|
|
# for stat in inp.else_statements:
|
|
# statement(wgn, stat)
|
|
|
|
def statement(wgn: WasmGenerator, inp: ourlang.Statement) -> None:
|
|
"""
|
|
Compile: any statement
|
|
"""
|
|
if isinstance(inp, ourlang.StatementReturn):
|
|
statement_return(wgn, inp)
|
|
return
|
|
|
|
if isinstance(inp, ourlang.StatementIf):
|
|
statement_if(wgn, inp)
|
|
return
|
|
|
|
if isinstance(inp, ourlang.StatementPass):
|
|
return
|
|
|
|
raise NotImplementedError(statement, inp)
|
|
|
|
def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
|
|
"""
|
|
Compile: function argument
|
|
"""
|
|
return (inp.name, type3(inp.type3), )
|
|
|
|
def import_(inp: ourlang.Function) -> wasm.Import:
|
|
"""
|
|
Compile: imported function
|
|
"""
|
|
assert inp.imported
|
|
|
|
return wasm.Import(
|
|
inp.imported,
|
|
inp.name,
|
|
inp.name,
|
|
[
|
|
function_argument(x)
|
|
for x in inp.posonlyargs
|
|
],
|
|
type3(inp.returns_type3)
|
|
)
|
|
|
|
def function(inp: ourlang.Function) -> wasm.Function:
|
|
"""
|
|
Compile: function
|
|
"""
|
|
assert not inp.imported
|
|
|
|
wgn = WasmGenerator()
|
|
|
|
if isinstance(inp, ourlang.StructConstructor):
|
|
_generate_struct_constructor(wgn, inp)
|
|
else:
|
|
for stat in inp.statements:
|
|
statement(wgn, stat)
|
|
|
|
return wasm.Function(
|
|
inp.name,
|
|
inp.name if inp.exported else None,
|
|
[
|
|
function_argument(x)
|
|
for x in inp.posonlyargs
|
|
],
|
|
[
|
|
(k, v.wasm_type(), )
|
|
for k, v in wgn.locals.items()
|
|
],
|
|
type3(inp.returns_type3),
|
|
wgn.statements
|
|
)
|
|
|
|
def module_data_u8(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, u8 value
|
|
|
|
# FIXME: All u8 values are stored as u32
|
|
"""
|
|
return struct.pack('<I', inp) # Should be 'B'
|
|
|
|
def module_data_u32(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, u32 value
|
|
"""
|
|
return struct.pack('<I', inp)
|
|
|
|
def module_data_u64(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, u64 value
|
|
"""
|
|
return struct.pack('<Q', inp)
|
|
|
|
def module_data_i8(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, i8 value
|
|
|
|
# FIXME: All i8 values are stored as i32
|
|
"""
|
|
return struct.pack('<i', inp) # Should be a 'b'
|
|
|
|
def module_data_i32(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, i32 value
|
|
"""
|
|
return struct.pack('<i', inp)
|
|
|
|
def module_data_i64(inp: int) -> bytes:
|
|
"""
|
|
Compile: module data, i64 value
|
|
"""
|
|
return struct.pack('<q', inp)
|
|
|
|
def module_data_f32(inp: float) -> bytes:
|
|
"""
|
|
Compile: module data, f32 value
|
|
"""
|
|
return struct.pack('<f', inp)
|
|
|
|
def module_data_f64(inp: float) -> bytes:
|
|
"""
|
|
Compile: module data, f64 value
|
|
"""
|
|
return struct.pack('<d', inp)
|
|
|
|
def module_data(inp: ourlang.ModuleData) -> bytes:
|
|
"""
|
|
Compile: module data
|
|
"""
|
|
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
|
|
|
|
allocated_data = b''
|
|
|
|
for block in inp.blocks:
|
|
block.address = unalloc_ptr + 4 # 4 bytes for allocator header
|
|
|
|
data_list: List[bytes] = []
|
|
|
|
for constant in block.data:
|
|
assert isinstance(constant.type3, type3types.Type3), (id(constant), type3placeholders.TYPE3_ASSERTION_ERROR)
|
|
|
|
if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block:
|
|
# It's stored in a different block
|
|
# We only need to store its address
|
|
# This happens for example when a tuple refers
|
|
# to a bytes constant
|
|
assert constant.data_block.address is not None, 'Referred memory not yet stored'
|
|
data_list.append(module_data_u32(constant.data_block.address))
|
|
continue
|
|
|
|
if constant.type3 == prelude.u8:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_u8(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.u32:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_u32(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.u64:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_u64(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.i8:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_i8(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.i32:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_i32(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.i64:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, int)
|
|
data_list.append(module_data_i64(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.f32:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, float)
|
|
data_list.append(module_data_f32(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.f64:
|
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
|
assert isinstance(constant.value, float)
|
|
data_list.append(module_data_f64(constant.value))
|
|
continue
|
|
|
|
if constant.type3 == prelude.bytes_:
|
|
assert isinstance(constant, ourlang.ConstantBytes)
|
|
assert isinstance(constant.value, bytes)
|
|
data_list.append(module_data_u32(len(constant.value)))
|
|
data_list.append(constant.value)
|
|
continue
|
|
|
|
raise NotImplementedError(constant, constant.type3)
|
|
|
|
block_data = b''.join(data_list)
|
|
|
|
allocated_data += module_data_u32(len(block_data)) + block_data
|
|
|
|
unalloc_ptr += 4 + len(block_data)
|
|
|
|
return (
|
|
# Store that we've initialized the memory
|
|
module_data_u32(stdlib_alloc.IDENTIFIER)
|
|
# Store the first reserved i32
|
|
+ module_data_u32(0)
|
|
# Store the pointer towards the first free block
|
|
# In this case, 0 since we haven't freed any blocks yet
|
|
+ module_data_u32(0)
|
|
# Store the pointer towards the first unallocated block
|
|
# In this case the end of the stdlib.alloc header at the start
|
|
+ module_data_u32(unalloc_ptr)
|
|
# Store the actual data
|
|
+ allocated_data
|
|
)
|
|
|
|
def module(inp: ourlang.Module) -> wasm.Module:
|
|
"""
|
|
Compile: module
|
|
"""
|
|
result = wasm.Module()
|
|
|
|
result.memory.data = module_data(inp.data)
|
|
|
|
result.imports = [
|
|
import_(x)
|
|
for x in inp.functions.values()
|
|
if x.imported
|
|
]
|
|
|
|
result.functions = [
|
|
stdlib_alloc.__find_free_block__,
|
|
stdlib_alloc.__alloc__,
|
|
stdlib_types.__alloc_bytes__,
|
|
stdlib_types.__subscript_bytes__,
|
|
stdlib_types.__u32_ord_min__,
|
|
stdlib_types.__u64_ord_min__,
|
|
stdlib_types.__i32_ord_min__,
|
|
stdlib_types.__i64_ord_min__,
|
|
stdlib_types.__u32_ord_max__,
|
|
stdlib_types.__u64_ord_max__,
|
|
stdlib_types.__i32_ord_max__,
|
|
stdlib_types.__i64_ord_max__,
|
|
stdlib_types.__i32_intnum_abs__,
|
|
stdlib_types.__i64_intnum_abs__,
|
|
stdlib_types.__u32_pow2__,
|
|
stdlib_types.__u8_rotl__,
|
|
stdlib_types.__u8_rotr__,
|
|
] + [
|
|
function(x)
|
|
for x in inp.functions.values()
|
|
if not x.imported
|
|
]
|
|
|
|
return result
|
|
|
|
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
|
st_args = prelude.struct.did_construct(inp.struct_type3)
|
|
assert st_args is not None
|
|
|
|
tmp_var = wgn.temp_var_i32('struct_adr')
|
|
|
|
# Allocated the required amounts of bytes in memory
|
|
wgn.i32.const(calculate_alloc_size(inp.struct_type3))
|
|
wgn.call(stdlib_alloc.__alloc__)
|
|
wgn.local.set(tmp_var)
|
|
|
|
# Store each member individually
|
|
for memname, mtyp3 in st_args.items():
|
|
mtyp: Optional[str]
|
|
if prelude.InternalPassAsPointer in mtyp3.classes:
|
|
mtyp = 'i32'
|
|
else:
|
|
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
|
|
|
if mtyp is None:
|
|
raise NotImplementedError(expression, inp, mtyp3)
|
|
|
|
wgn.local.get(tmp_var)
|
|
wgn.add_statement('local.get', f'${memname}')
|
|
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset(
|
|
inp.struct_type3.name, st_args, memname
|
|
)))
|
|
|
|
# Return the allocated address
|
|
wgn.local.get(tmp_var)
|