552 lines
18 KiB
Python
552 lines
18 KiB
Python
"""
|
|
The base class for build environments.
|
|
|
|
Contains nothing but the explicit compiler builtins.
|
|
"""
|
|
from typing import Any, Callable, NamedTuple, Sequence, Type
|
|
from warnings import warn
|
|
|
|
from ..type3.functions import (
|
|
TypeConstructorVariable,
|
|
TypeVariable,
|
|
)
|
|
from ..type3.routers import (
|
|
NoRouteForTypeException,
|
|
TypeApplicationRouter,
|
|
TypeClassArgsRouter,
|
|
TypeVariableLookup,
|
|
)
|
|
from ..type3.typeclasses import Type3Class, Type3ClassMethod
|
|
from ..type3.types import (
|
|
IntType3,
|
|
Type3,
|
|
TypeConstructor_Base,
|
|
TypeConstructor_DynamicArray,
|
|
TypeConstructor_Function,
|
|
TypeConstructor_StaticArray,
|
|
TypeConstructor_Struct,
|
|
TypeConstructor_Tuple,
|
|
)
|
|
from ..type5 import kindexpr as type5kindexpr
|
|
from ..type5 import record as type5record
|
|
from ..type5 import typeexpr as type5typeexpr
|
|
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
|
|
from . import builtins
|
|
from .typerouter import TypeName
|
|
|
|
TypeInfo = NamedTuple('TypeInfo', [
|
|
# Name of the type
|
|
('typ', str, ),
|
|
# What WebAssembly type to use when passing this value around
|
|
# For example in function arguments
|
|
('wasm_type', Type[WasmType]),
|
|
# What WebAssembly function to use when loading a value from memory
|
|
('wasm_load_func', str),
|
|
# What WebAssembly function to use when storing a value to memory
|
|
('wasm_store_func', str),
|
|
# When storing this value in memory, how many bytes do we use?
|
|
# Only valid for non-constructed types, see calculate_alloc_size
|
|
# Should match wasm_load_func / wasm_store_func
|
|
('alloc_size', int),
|
|
# When storing integers, the values can be stored as natural number
|
|
# (False) or as integer number (True). For other types, this is None.
|
|
('signed', bool | None),
|
|
])
|
|
|
|
class MissingImplementationWarning(Warning):
|
|
pass
|
|
|
|
class BuildBase[G]:
|
|
__slots__ = (
|
|
'dynamic_array',
|
|
'dynamic_array_type5_constructor',
|
|
'function',
|
|
'function_type5_constructor',
|
|
'static_array',
|
|
'static_array_type5_constructor',
|
|
'struct',
|
|
'tuple_',
|
|
'tuple_type5_constructor_map',
|
|
|
|
'none_',
|
|
'none_type5',
|
|
'unit_type5',
|
|
'bool_',
|
|
'bool_type5',
|
|
'u8_type5',
|
|
'u32_type5',
|
|
|
|
'type_info_map',
|
|
'type_info_constructed',
|
|
|
|
'types',
|
|
'type5s',
|
|
'type_classes',
|
|
'type_class_instances',
|
|
'type_class_instance_methods',
|
|
'methods',
|
|
'operators',
|
|
|
|
'type5_name',
|
|
'alloc_size_router',
|
|
)
|
|
|
|
dynamic_array: TypeConstructor_DynamicArray
|
|
"""
|
|
This is a dynamic length piece of memory.
|
|
|
|
It should be applied with two arguments. It has a runtime
|
|
determined length, and each argument is the same.
|
|
"""
|
|
|
|
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
|
|
|
|
function: TypeConstructor_Function
|
|
"""
|
|
This is a function.
|
|
|
|
It should be applied with one or more arguments. The last argument is the 'return' type.
|
|
"""
|
|
|
|
function_type5_constructor: type5typeexpr.TypeConstructor
|
|
|
|
static_array: TypeConstructor_StaticArray
|
|
"""
|
|
This is a fixed length piece of memory.
|
|
|
|
It should be applied with two arguments. It has a compile time
|
|
determined length, and each argument is the same.
|
|
"""
|
|
static_array_type5_constructor: type5typeexpr.TypeConstructor
|
|
|
|
struct: TypeConstructor_Struct
|
|
"""
|
|
This is like a tuple, but each argument is named, so that developers
|
|
can get and set fields by name.
|
|
"""
|
|
|
|
tuple_: TypeConstructor_Tuple
|
|
"""
|
|
This is a fixed length piece of memory.
|
|
|
|
It should be applied with zero or more arguments. It has a compile time
|
|
determined length, and each argument can be different.
|
|
"""
|
|
|
|
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
|
|
|
|
none_: Type3
|
|
"""
|
|
The none type, for when functions simply don't return anything. e.g., IO().
|
|
"""
|
|
none_type5: type5typeexpr.AtomicType
|
|
|
|
unit_type5: type5typeexpr.AtomicType
|
|
|
|
bool_: Type3
|
|
"""
|
|
The bool type, either True or False
|
|
"""
|
|
|
|
bool_type5: type5typeexpr.AtomicType
|
|
|
|
u8_type5: type5typeexpr.AtomicType
|
|
u32_type5: type5typeexpr.AtomicType
|
|
|
|
type_info_map: dict[str, TypeInfo]
|
|
"""
|
|
Map from type name to the info of that type
|
|
"""
|
|
|
|
type_info_constructed: TypeInfo
|
|
"""
|
|
By default, constructed types are passed as pointers
|
|
NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES
|
|
USE calculate_alloc_size FOR ACCURATE RESULTS
|
|
Functions count as constructed types - even though they are
|
|
not memory pointers but table addresses instead.
|
|
"""
|
|
|
|
types: dict[str, Type3]
|
|
"""
|
|
Types that are available without explicit import.
|
|
"""
|
|
|
|
type5s: dict[str, type5typeexpr.TypeExpr]
|
|
"""
|
|
Types that are available without explicit import.
|
|
"""
|
|
|
|
type_classes: dict[str, Type3Class]
|
|
"""
|
|
Type classes that are available without explicit import.
|
|
"""
|
|
|
|
type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]]
|
|
"""
|
|
Type class instances that are available without explicit import.
|
|
"""
|
|
|
|
type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]]
|
|
"""
|
|
Methods (and operators) for type class instances that are available without explicit import.
|
|
"""
|
|
|
|
methods: dict[str, Type3ClassMethod]
|
|
"""
|
|
Methods that are available without explicit import.
|
|
"""
|
|
|
|
operators: dict[str, Type3ClassMethod]
|
|
"""
|
|
Operators that are available without explicit import.
|
|
"""
|
|
|
|
type5_name: TypeName
|
|
"""
|
|
Helper router to turn types into their human readable names.
|
|
"""
|
|
|
|
alloc_size_router: TypeApplicationRouter['BuildBase[G]', int]
|
|
"""
|
|
Helper value for calculate_alloc_size.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self.dynamic_array = builtins.dynamic_array
|
|
self.function = builtins.function
|
|
self.static_array = builtins.static_array
|
|
self.struct = builtins.struct
|
|
self.tuple_ = builtins.tuple_
|
|
|
|
S = type5kindexpr.Star()
|
|
N = type5kindexpr.Nat()
|
|
|
|
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
|
|
self.tuple_type5_constructor_map = {}
|
|
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
|
|
self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
|
|
|
|
self.bool_ = builtins.bool_
|
|
self.bool_type5 = type5typeexpr.AtomicType('bool')
|
|
self.unit_type5 = type5typeexpr.AtomicType('()')
|
|
self.none_ = builtins.none_
|
|
self.none_type5 = type5typeexpr.AtomicType('None')
|
|
self.u8_type5 = type5typeexpr.AtomicType('u8')
|
|
self.u32_type5 = type5typeexpr.AtomicType('u32')
|
|
|
|
self.type_info_map = {
|
|
'None': TypeInfo('ptr', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
|
|
'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
|
|
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
|
|
}
|
|
self.type_info_constructed = self.type_info_map['ptr']
|
|
|
|
self.types = {
|
|
'None': self.none_,
|
|
'bool': self.bool_,
|
|
}
|
|
self.type5s = {
|
|
'bool': self.bool_type5,
|
|
'u8': self.u8_type5,
|
|
'u32': self.u32_type5,
|
|
}
|
|
self.type_classes = {}
|
|
self.type_class_instances = set()
|
|
self.type_class_instance_methods = {}
|
|
self.methods = {}
|
|
self.operators = {}
|
|
|
|
self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]()
|
|
self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array)
|
|
self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct)
|
|
self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple)
|
|
|
|
self.type5_name = TypeName(self)
|
|
|
|
def register_type_class(self, cls: Type3Class) -> None:
|
|
"""
|
|
Register that the given type class exists
|
|
"""
|
|
old_len_methods = len(self.methods)
|
|
old_len_operators = len(self.operators)
|
|
|
|
self.type_classes[cls.name] = cls
|
|
self.methods.update(cls.methods)
|
|
self.operators.update(cls.operators)
|
|
|
|
assert len(self.methods) == old_len_methods + len(cls.methods), 'Duplicated method detected'
|
|
assert len(self.operators) == old_len_operators + len(cls.operators), 'Duplicated operator detected'
|
|
|
|
def instance_type_class(
|
|
self,
|
|
cls: Type3Class,
|
|
*typ: Type3 | TypeConstructor_Base[Any],
|
|
methods: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
|
|
operators: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
|
|
) -> None:
|
|
"""
|
|
Registered the given type class and its implementation
|
|
"""
|
|
assert len(cls.args) == len(typ)
|
|
|
|
for incls in cls.inherited_classes:
|
|
if (incls, tuple(typ), ) not in self.type_class_instances:
|
|
warn(MissingImplementationWarning(
|
|
incls.name + ' ' + ' '.join(x.name for x in typ) + ' - required for ' + cls.name
|
|
))
|
|
|
|
# First just register the type
|
|
self.type_class_instances.add((cls, tuple(typ), ))
|
|
|
|
# Then make the implementation findable
|
|
# We route based on the type class arguments.
|
|
tv_map: dict[TypeVariable, Type3] = {}
|
|
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
|
|
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
|
|
if isinstance(arg_tv, TypeVariable):
|
|
assert isinstance(arg_tp, Type3)
|
|
tv_map[arg_tv] = arg_tp
|
|
elif isinstance(arg_tv, TypeConstructorVariable):
|
|
assert isinstance(arg_tp, TypeConstructor_Base)
|
|
tc_map[arg_tv] = arg_tp
|
|
else:
|
|
raise NotImplementedError(arg_tv, arg_tp)
|
|
|
|
for method_name, method in cls.methods.items():
|
|
router = self.type_class_instance_methods.get(method)
|
|
if router is None:
|
|
router = TypeClassArgsRouter[G, None](cls.args)
|
|
self.type_class_instance_methods[method] = router
|
|
|
|
try:
|
|
generator = methods[method_name]
|
|
except KeyError:
|
|
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
|
continue
|
|
|
|
router.add(tv_map, tc_map, generator)
|
|
|
|
for operator_name, operator in cls.operators.items():
|
|
router = self.type_class_instance_methods.get(operator)
|
|
if router is None:
|
|
router = TypeClassArgsRouter[G, None](cls.args)
|
|
self.type_class_instance_methods[operator] = router
|
|
|
|
try:
|
|
generator = operators[operator_name]
|
|
except KeyError:
|
|
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
|
continue
|
|
|
|
router.add(tv_map, tc_map, generator)
|
|
|
|
|
|
def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int:
|
|
"""
|
|
Helper method for calculate_alloc_size - static_array
|
|
"""
|
|
sa_type, sa_len = args
|
|
|
|
return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True)
|
|
|
|
def calculate_alloc_size_tuple(self, args: tuple[Type3, ...]) -> int:
|
|
"""
|
|
Helper method for calculate_alloc_size - tuple
|
|
"""
|
|
return sum(
|
|
self.calculate_alloc_size(x, is_member=True)
|
|
for x in args
|
|
)
|
|
|
|
def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int:
|
|
"""
|
|
Helper method for calculate_alloc_size - struct
|
|
"""
|
|
return sum(
|
|
self.calculate_alloc_size(x, is_member=True)
|
|
for _, x in args
|
|
)
|
|
|
|
def calculate_alloc_size(self, typ: Type3, is_member: bool = False) -> int:
|
|
"""
|
|
Calculates how much bytes you need to allocate when reserving memory for the given type.
|
|
"""
|
|
typ_info = self.type_info_map.get(typ.name)
|
|
if typ_info is not None:
|
|
return typ_info.alloc_size
|
|
|
|
if is_member:
|
|
return self.type_info_constructed.alloc_size
|
|
|
|
try:
|
|
return self.alloc_size_router(self, typ)
|
|
except NoRouteForTypeException:
|
|
raise NotImplementedError(typ)
|
|
|
|
def calculate_member_offset(self, st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
|
|
"""
|
|
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
|
|
"""
|
|
result = 0
|
|
|
|
for memnam, memtyp in st_args:
|
|
if needle == memnam:
|
|
return result
|
|
|
|
result += self.calculate_alloc_size(memtyp, is_member=True)
|
|
|
|
raise Exception(f'{needle} not in {st_name}')
|
|
|
|
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
|
|
if not args:
|
|
raise TypeError("Functions must at least have a return type")
|
|
|
|
if len(args) == 1:
|
|
# Functions always take an argument
|
|
# To distinguish between a function without arguments and a value
|
|
# of the type, we have a unit type
|
|
# This type has one value so it can always be called
|
|
args = [self.unit_type5, *args]
|
|
|
|
res_type5 = None
|
|
|
|
for arg_type5 in reversed(args):
|
|
if res_type5 is None:
|
|
res_type5 = arg_type5
|
|
continue
|
|
|
|
res_type5 = type5typeexpr.TypeApplication(
|
|
constructor=type5typeexpr.TypeApplication(
|
|
constructor=self.function_type5_constructor,
|
|
argument=arg_type5,
|
|
),
|
|
argument=res_type5,
|
|
)
|
|
|
|
assert res_type5 is not None # type hint
|
|
|
|
return res_type5
|
|
|
|
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
|
|
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
return None
|
|
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
|
return None
|
|
if typeexpr.constructor.constructor != self.function_type5_constructor:
|
|
return None
|
|
|
|
arg0 = typeexpr.constructor.argument
|
|
if arg0 is self.unit_type5:
|
|
my_args = []
|
|
else:
|
|
my_args = [arg0]
|
|
|
|
arg1 = typeexpr.argument
|
|
more_args = self.type5_is_function(arg1)
|
|
if more_args is None:
|
|
return my_args + [arg1]
|
|
|
|
return my_args + more_args
|
|
|
|
def type5_make_tuple(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeApplication:
|
|
if not args:
|
|
raise TypeError("Tuples must at least one field")
|
|
|
|
arlen = len(args)
|
|
constructor = self.tuple_type5_constructor_map.get(arlen)
|
|
if constructor is None:
|
|
star = type5kindexpr.Star()
|
|
|
|
kind: type5kindexpr.Arrow = star >> star
|
|
for _ in range(len(args) - 1):
|
|
kind = star >> kind
|
|
constructor = type5typeexpr.TypeConstructor(kind=kind, name=f'tuple_{arlen}')
|
|
self.tuple_type5_constructor_map[arlen] = constructor
|
|
|
|
result: type5typeexpr.TypeApplication | None = None
|
|
|
|
for arg in args:
|
|
if result is None:
|
|
result = type5typeexpr.TypeApplication(
|
|
constructor=constructor,
|
|
argument=arg
|
|
)
|
|
continue
|
|
|
|
result = type5typeexpr.TypeApplication(
|
|
constructor=result,
|
|
argument=arg
|
|
)
|
|
|
|
assert result is not None # type hint
|
|
result.name = '(' + ', '.join(x.name for x in args) + ', )'
|
|
return result
|
|
|
|
def type5_is_tuple(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
|
|
arg_list = []
|
|
|
|
while isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
arg_list.append(typeexpr.argument)
|
|
typeexpr = typeexpr.constructor
|
|
|
|
if not isinstance(typeexpr, type5typeexpr.TypeConstructor):
|
|
return None
|
|
|
|
if typeexpr not in self.tuple_type5_constructor_map.values():
|
|
return None
|
|
|
|
return list(reversed(arg_list))
|
|
|
|
def type5_make_record(self, name: str, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...]) -> type5record.Record:
|
|
return type5record.Record(name, fields)
|
|
|
|
def type5_is_record(self, arg: type5typeexpr.TypeExpr) -> tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...] | None:
|
|
if not isinstance(arg, type5record.Record):
|
|
return None
|
|
|
|
return arg.fields
|
|
|
|
def type5_make_dynamic_array(self, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
|
|
return type5typeexpr.TypeApplication(
|
|
constructor=self.dynamic_array_type5_constructor,
|
|
argument=arg,
|
|
)
|
|
|
|
def type5_is_dynamic_array(self, typeexpr: type5typeexpr.TypeExpr) -> type5typeexpr.TypeExpr | None:
|
|
"""
|
|
Check if the given type expr is a concrete dynamic array type.
|
|
|
|
The element argument type is returned if so. Else, None is returned.
|
|
"""
|
|
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
return None
|
|
if typeexpr.constructor != self.dynamic_array_type5_constructor:
|
|
return None
|
|
|
|
return typeexpr.argument
|
|
|
|
def type5_make_static_array(self, len: int, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
|
|
return type5typeexpr.TypeApplication(
|
|
constructor=type5typeexpr.TypeApplication(
|
|
constructor=self.static_array_type5_constructor,
|
|
argument=type5typeexpr.TypeLevelNat(len),
|
|
),
|
|
argument=arg,
|
|
)
|
|
|
|
def type5_is_static_array(self, typeexpr: type5typeexpr.TypeExpr) -> tuple[int, type5typeexpr.TypeExpr] | None:
|
|
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
|
return None
|
|
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
|
return None
|
|
if typeexpr.constructor.constructor != self.static_array_type5_constructor:
|
|
return None
|
|
|
|
assert isinstance(typeexpr.constructor.argument, type5typeexpr.TypeLevelNat) # type hint
|
|
|
|
return (
|
|
typeexpr.constructor.argument.value,
|
|
typeexpr.argument,
|
|
)
|