phasm/phasm/compiler.py
Johan B.W. de Vries c8009403c4 Separates out TypeVariable and constraints
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.
2025-04-27 15:28:08 +02:00

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)