Previously, it was hardcoded at 'compile' time (in as much Python has that). This would make it more difficult to add stuff to it. Also, in a lot of places we made assumptions about prelude instead of checking properly.
340 lines
11 KiB
Python
340 lines
11 KiB
Python
"""
|
|
The base class for build environments.
|
|
|
|
Contains nothing but the explicit compiler builtins.
|
|
"""
|
|
from typing import Any, Callable, NamedTuple, 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 ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
|
|
from . import builtins
|
|
|
|
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',
|
|
'function',
|
|
'static_array',
|
|
'struct',
|
|
'tuple_',
|
|
|
|
'none_',
|
|
'bool_',
|
|
|
|
'type_info_map',
|
|
'type_info_constructed',
|
|
|
|
'types',
|
|
'type_classes',
|
|
'type_class_instances',
|
|
'type_class_instance_methods',
|
|
'methods',
|
|
'operators',
|
|
|
|
'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.
|
|
"""
|
|
|
|
function: TypeConstructor_Function
|
|
"""
|
|
This is a function.
|
|
|
|
It should be applied with one or more arguments. The last argument is the 'return' type.
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
|
|
none_: Type3
|
|
"""
|
|
The none type, for when functions simply don't return anything. e.g., IO().
|
|
"""
|
|
|
|
bool_: Type3
|
|
"""
|
|
The bool type, either True or False
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
|
|
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.
|
|
"""
|
|
|
|
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_
|
|
|
|
self.bool_ = builtins.bool_
|
|
self.none_ = builtins.none_
|
|
|
|
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.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)
|
|
|
|
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}')
|