Cleanup: TYPE_INFO_MAP
This also removes the InternalPassAsPointer experiment. This also fixes that u8 values were stores as 32 bits in structs and tuples (but not dynamic arrays since that is special cased as bytes). Also, fixes allocation issue wi th dynamic arrays, it would allocate quadratic amount of memory.
This commit is contained in:
parent
83186cce78
commit
46b06dbcf1
3
TODO.md
3
TODO.md
@ -7,7 +7,6 @@
|
||||
|
||||
- Implement a trace() builtin for debugging
|
||||
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
|
||||
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
|
||||
- Implement a FizzBuzz example
|
||||
- Also, check the codes for FIXME and TODO
|
||||
- Allocation is done using pointers for members, is this desired?
|
||||
@ -17,9 +16,11 @@
|
||||
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
|
||||
- Have a set of rules or guidelines for the constraint comments, they're messy.
|
||||
- Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not?
|
||||
- calculate_alloc_size can be reworked; is_member isn't useful with TYPE_INFO_MAP
|
||||
|
||||
- Parser is putting stuff in ModuleDataBlock
|
||||
- Surely the compiler should build data blocks
|
||||
- Also put the struct.pack constants in TYPE_INFO_MAP
|
||||
- Make prelude more an actual thing
|
||||
- Merge in compiler.INSTANCES
|
||||
- Make it less build in - have a environment class of some kind
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||
"""
|
||||
import struct
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
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 .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from .type3.functions import TypeVariable
|
||||
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
from .type3.typeclasses import Type3ClassMethod
|
||||
@ -27,20 +28,6 @@ from .wasmgenerator import Generator as WasmGenerator
|
||||
|
||||
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
||||
"""
|
||||
Public method for compiling a parsed Phasm module into
|
||||
@ -55,53 +42,8 @@ def type3(inp: Type3) -> wasm.WasmType:
|
||||
Types are used for example in WebAssembly function parameters
|
||||
and return types.
|
||||
"""
|
||||
assert inp is not None, 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, (inp, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
raise NotImplementedError(type3, inp)
|
||||
typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED)
|
||||
return typ_info.wasm_type()
|
||||
|
||||
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None:
|
||||
"""
|
||||
@ -121,7 +63,9 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
|
||||
sa_type, = inp.type3.application.arguments
|
||||
|
||||
args = tuple(sa_type for _ in inp.elements)
|
||||
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=False) * len(inp.elements)
|
||||
# Can't use calculate_alloc_size directly since that doesn't
|
||||
# know the dynamic array's length
|
||||
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
|
||||
alloc_size_header = len(inp.elements)
|
||||
elif isinstance(inp.type3.application, TypeApplication_TypeStar):
|
||||
# Possibly paranoid assert. If we have a future variadic type,
|
||||
@ -165,15 +109,12 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
|
||||
for element, exp_type3 in zip(inp.elements, args, strict=True):
|
||||
assert element.type3 == exp_type3
|
||||
|
||||
if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
|
||||
exp_type_info = TYPE_INFO_MAP.get(exp_type3.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
wgn.add_statement('nop', comment='PRE')
|
||||
wgn.local.get(tmp_var)
|
||||
expression(wgn, mod, element)
|
||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
|
||||
wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset))
|
||||
wgn.add_statement('nop', comment='POST')
|
||||
|
||||
offset += calculate_alloc_size(exp_type3, is_member=True)
|
||||
@ -213,14 +154,14 @@ def expression_subscript_static_array(
|
||||
with wgn.if_():
|
||||
wgn.unreachable(comment='Out of bounds')
|
||||
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.i32.const(calculate_alloc_size(el_type))
|
||||
wgn.i32.const(el_type_info.alloc_size)
|
||||
wgn.i32.mul()
|
||||
wgn.i32.add()
|
||||
|
||||
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
||||
|
||||
wgn.add_statement(f'{mtyp}.load')
|
||||
wgn.add_statement(el_type_info.wasm_load_func)
|
||||
|
||||
def expression_subscript_tuple(
|
||||
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
|
||||
@ -234,19 +175,16 @@ def expression_subscript_tuple(
|
||||
offset = 0
|
||||
for el_type in args[0:inp.index.value]:
|
||||
assert el_type is not None, TYPE3_ASSERTION_ERROR
|
||||
offset += calculate_alloc_size(el_type)
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
offset += el_type_info.alloc_size
|
||||
|
||||
el_type = args[inp.index.value]
|
||||
assert el_type is not None, TYPE3_ASSERTION_ERROR
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
|
||||
if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
||||
|
||||
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
|
||||
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
|
||||
|
||||
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]()
|
||||
SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes)
|
||||
@ -305,7 +243,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
if isinstance(inp.variable, ourlang.ModuleConstantDef):
|
||||
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
|
||||
|
||||
if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
if inp.type3.name not in TYPE_INFO_MAP:
|
||||
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
@ -413,11 +351,10 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
||||
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
|
||||
|
||||
member_type = dict(inp.struct_type3.application.arguments)[inp.member]
|
||||
|
||||
mtyp = LOAD_STORE_TYPE_MAP[member_type.name]
|
||||
member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
expression(wgn, mod, inp.varref)
|
||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset(
|
||||
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset(
|
||||
inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
|
||||
)))
|
||||
return
|
||||
@ -600,10 +537,8 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
|
||||
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'
|
||||
return struct.pack('<B', inp)
|
||||
|
||||
def module_data_u32(inp: int) -> bytes:
|
||||
"""
|
||||
@ -620,10 +555,8 @@ def module_data_u64(inp: int) -> bytes:
|
||||
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'
|
||||
return struct.pack('<b', inp)
|
||||
|
||||
def module_data_i32(inp: int) -> bytes:
|
||||
"""
|
||||
@ -812,18 +745,11 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
||||
|
||||
# Store each member individually
|
||||
for memname, mtyp3 in st_args:
|
||||
mtyp: Optional[str]
|
||||
if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||
|
||||
if mtyp is None:
|
||||
raise NotImplementedError(expression, inp, mtyp3)
|
||||
mtyp3_info = TYPE_INFO_MAP.get(mtyp3.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.add_statement('local.get', f'${memname}')
|
||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset(
|
||||
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset(
|
||||
inp.struct_type3.name, st_args, memname
|
||||
)))
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ from ..type3.functions import (
|
||||
from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup
|
||||
from ..type3.typeclasses import Type3Class, Type3ClassMethod
|
||||
from ..type3.types import (
|
||||
IntType3,
|
||||
Type3,
|
||||
TypeApplication_Nullary,
|
||||
TypeConstructor_Base,
|
||||
@ -159,10 +158,7 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None))
|
||||
A 32-bits IEEE 754 float, of 64 bits width.
|
||||
"""
|
||||
|
||||
def da_on_create(args: tuple[Type3], typ: Type3) -> None:
|
||||
instance_type_class(InternalPassAsPointer, typ)
|
||||
|
||||
dynamic_array = TypeConstructor_DynamicArray('dynamic_array', on_create=da_on_create)
|
||||
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
|
||||
"""
|
||||
This is a dynamic length piece of memory.
|
||||
|
||||
@ -170,10 +166,7 @@ It should be applied with two arguments. It has a runtime
|
||||
determined length, and each argument is the same.
|
||||
"""
|
||||
|
||||
def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
|
||||
instance_type_class(InternalPassAsPointer, typ)
|
||||
|
||||
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
|
||||
static_array = TypeConstructor_StaticArray('static_array')
|
||||
"""
|
||||
This is a fixed length piece of memory.
|
||||
|
||||
@ -181,10 +174,7 @@ It should be applied with two arguments. It has a compile time
|
||||
determined length, and each argument is the same.
|
||||
"""
|
||||
|
||||
def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
|
||||
instance_type_class(InternalPassAsPointer, typ)
|
||||
|
||||
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
|
||||
tuple_ = TypeConstructor_Tuple('tuple')
|
||||
"""
|
||||
This is a fixed length piece of memory.
|
||||
|
||||
@ -192,22 +182,14 @@ It should be applied with zero or more arguments. It has a compile time
|
||||
determined length, and each argument can be different.
|
||||
"""
|
||||
|
||||
def fn_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
|
||||
# Not really a pointer; but still a i32
|
||||
# (It's actually a table lookup)
|
||||
instance_type_class(InternalPassAsPointer, typ)
|
||||
|
||||
function = TypeConstructor_Function('function', on_create=fn_on_create)
|
||||
function = TypeConstructor_Function('function')
|
||||
"""
|
||||
This is a function.
|
||||
|
||||
It should be applied with one or more arguments. The last argument is the 'return' type.
|
||||
"""
|
||||
|
||||
def st_on_create(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None:
|
||||
instance_type_class(InternalPassAsPointer, typ)
|
||||
|
||||
struct = TypeConstructor_Struct('struct', on_create=st_on_create)
|
||||
struct = TypeConstructor_Struct('struct')
|
||||
"""
|
||||
This is like a tuple, but each argument is named, so that developers
|
||||
can get and set fields by name.
|
||||
@ -218,16 +200,6 @@ b = TypeVariable('b', TypeVariableApplication_Nullary(None, None))
|
||||
|
||||
t = TypeConstructorVariable('t')
|
||||
|
||||
InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, operators={})
|
||||
"""
|
||||
Internal type class to keep track which types we pass arounds as a pointer.
|
||||
"""
|
||||
|
||||
# instance_type_class(InternalPassAsPointer, bytes_)
|
||||
# instance_type_class(InternalPassAsPointer, static_array)
|
||||
# instance_type_class(InternalPassAsPointer, tuple_)
|
||||
# instance_type_class(InternalPassAsPointer, struct)
|
||||
|
||||
Eq = Type3Class('Eq', (a, ), methods={}, operators={
|
||||
'==': [a, a, bool_],
|
||||
'!=': [a, a, bool_],
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
from . import prelude
|
||||
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
from .type3.types import IntType3, Type3
|
||||
|
||||
|
||||
def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int:
|
||||
if is_member:
|
||||
return 4
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
sa_type, sa_len = args
|
||||
|
||||
@ -13,7 +14,7 @@ def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntTyp
|
||||
|
||||
def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
|
||||
if is_member:
|
||||
return 4
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
return sum(
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
@ -22,7 +23,7 @@ def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
|
||||
|
||||
def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int:
|
||||
if is_member:
|
||||
return 4
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
return sum(
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
@ -35,14 +36,9 @@ ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct)
|
||||
ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple)
|
||||
|
||||
def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
|
||||
if typ in (prelude.u8, prelude.i8, ):
|
||||
return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32
|
||||
|
||||
if typ in (prelude.u32, prelude.i32, prelude.f32, ):
|
||||
return 4
|
||||
|
||||
if typ in (prelude.u64, prelude.i64, prelude.f64, ):
|
||||
return 8
|
||||
typ_info = TYPE_INFO_MAP.get(typ.name)
|
||||
if typ_info is not None:
|
||||
return typ_info.alloc_size
|
||||
|
||||
try:
|
||||
return ALLOC_SIZE_ROUTER(is_member, typ)
|
||||
@ -50,7 +46,7 @@ def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
|
||||
if is_member:
|
||||
# By default, 'boxed' or 'constructed' types are
|
||||
# stored as pointers when a member of a struct or tuple
|
||||
return 4
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
|
||||
raise NotImplementedError(typ)
|
||||
|
||||
|
||||
@ -1,13 +1,61 @@
|
||||
"""
|
||||
stdlib: Standard types that are not wasm primitives
|
||||
"""
|
||||
from typing import Mapping, NamedTuple, Type
|
||||
|
||||
from phasm.stdlib import alloc
|
||||
from phasm.type3.routers import TypeVariableLookup
|
||||
from phasm.type3.types import IntType3, Type3
|
||||
from phasm.wasm import (
|
||||
WasmType,
|
||||
WasmTypeFloat32,
|
||||
WasmTypeFloat64,
|
||||
WasmTypeInt32,
|
||||
WasmTypeInt64,
|
||||
WasmTypeNone,
|
||||
)
|
||||
from phasm.wasmgenerator import Generator, func_wrapper
|
||||
from phasm.wasmgenerator import VarType_i32 as i32
|
||||
from phasm.wasmgenerator import VarType_i64 as i64
|
||||
|
||||
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),
|
||||
])
|
||||
|
||||
TYPE_INFO_MAP: Mapping[str, TypeInfo] = {
|
||||
'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0),
|
||||
'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
|
||||
|
||||
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
|
||||
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1),
|
||||
|
||||
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
|
||||
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
|
||||
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
|
||||
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
|
||||
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4),
|
||||
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8),
|
||||
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4),
|
||||
}
|
||||
|
||||
# By default, constructed types are passed as pointers
|
||||
# NOTE: ALLOC SIZE HERE DOES NOT WORK FOR CONSTRUCTED TYPES
|
||||
# USE runtime.calculate_alloc_size FOR ACCURATE RESULTS
|
||||
# Functions count as constructed types - even though they are
|
||||
# not memory pointers but table addresses instead.
|
||||
TYPE_INFO_CONSTRUCTED = TypeInfo('t a', WasmTypeInt32, 'i32.load', 'i32.store', 4)
|
||||
|
||||
@func_wrapper()
|
||||
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
||||
@ -1098,26 +1146,9 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
|
||||
if sa_len.value < 1:
|
||||
raise NotImplementedError('Default value in case sum is empty')
|
||||
|
||||
# FIXME: We should probably use LOAD_STORE_TYPE_MAP for this?
|
||||
mtyp_map = {
|
||||
'u32': 'i32',
|
||||
'u64': 'i64',
|
||||
'i32': 'i32',
|
||||
'i64': 'i64',
|
||||
'f32': 'f32',
|
||||
'f64': 'f64',
|
||||
}
|
||||
|
||||
# FIXME: We should probably use calc_alloc_size for this?
|
||||
type_var_size_map = {
|
||||
'u32': 4,
|
||||
'u64': 8,
|
||||
'i32': 4,
|
||||
'i64': 8,
|
||||
'f32': 4,
|
||||
'f64': 8,
|
||||
}
|
||||
sa_type_info = TYPE_INFO_MAP.get(sa_type.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
# FIXME: This breaks when users start implementing their own NatNum classes
|
||||
type_var_add_generator = {
|
||||
'u32': u32_natnum_add,
|
||||
'u64': u64_natnum_add,
|
||||
@ -1126,11 +1157,6 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
|
||||
'f32': f32_natnum_add,
|
||||
'f64': f64_natnum_add,
|
||||
}
|
||||
|
||||
# By default, constructed types are passed as pointers
|
||||
# FIXME: We don't know what add function to call
|
||||
sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32')
|
||||
sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4)
|
||||
sa_type_add_gen = type_var_add_generator[sa_type.name]
|
||||
|
||||
# Definitions
|
||||
@ -1149,7 +1175,7 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
|
||||
# Stack: []
|
||||
g.nop(comment='Calculate address at which to stop looping')
|
||||
g.local.get(sum_adr)
|
||||
g.i32.const(sa_len.value * sa_type_alloc_size)
|
||||
g.i32.const(sa_len.value * sa_type_info.alloc_size)
|
||||
g.i32.add()
|
||||
g.local.set(sum_stop)
|
||||
|
||||
@ -1157,30 +1183,30 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
|
||||
# Stack: [] -> [sum]
|
||||
g.nop(comment='Get the first array value as starting point')
|
||||
g.local.get(sum_adr)
|
||||
g.add_statement(f'{sa_type_mtyp}.load')
|
||||
g.add_statement(sa_type_info.wasm_load_func)
|
||||
|
||||
# Since we did the first one, increase adr
|
||||
# adr = adr + sa_type_alloc_size
|
||||
# Stack: [sum] -> [sum]
|
||||
g.local.get(sum_adr)
|
||||
g.i32.const(sa_type_alloc_size)
|
||||
g.i32.const(sa_type_info.alloc_size)
|
||||
g.i32.add()
|
||||
g.local.set(sum_adr)
|
||||
|
||||
if sa_len.value > 1:
|
||||
with g.loop(params=[sa_type_mtyp], result=sa_type_mtyp):
|
||||
with g.loop(params=[sa_type_info.wasm_type().to_wat()], result=sa_type_info.wasm_type().to_wat()):
|
||||
# sum = sum + *adr
|
||||
# Stack: [sum] -> [sum + *adr]
|
||||
g.nop(comment='Add array value')
|
||||
g.local.get(sum_adr)
|
||||
g.add_statement(f'{sa_type_mtyp}.load')
|
||||
g.add_statement(sa_type_info.wasm_load_func)
|
||||
sa_type_add_gen(g, {})
|
||||
|
||||
# adr = adr + sa_type_alloc_size
|
||||
# Stack: [sum] -> [sum]
|
||||
g.nop(comment='Calculate address of the next value')
|
||||
g.local.get(sum_adr)
|
||||
g.i32.const(sa_type_alloc_size)
|
||||
g.i32.const(sa_type_info.alloc_size)
|
||||
g.i32.add()
|
||||
g.local.tee(sum_adr)
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ Contains the final types for use in Phasm, as well as construtors.
|
||||
"""
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Hashable,
|
||||
Self,
|
||||
Tuple,
|
||||
@ -141,27 +140,21 @@ class TypeConstructor_Base[T]:
|
||||
"""
|
||||
Base class for type construtors
|
||||
"""
|
||||
__slots__ = ('name', 'on_create', '_cache', )
|
||||
__slots__ = ('name', '_cache', )
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the type constructor
|
||||
"""
|
||||
|
||||
on_create: Callable[[T, Type3], None]
|
||||
"""
|
||||
Who to let know if a type is created
|
||||
"""
|
||||
|
||||
_cache: dict[T, Type3]
|
||||
"""
|
||||
When constructing a type with the same arguments,
|
||||
it should produce the exact same result.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
self.on_create = on_create
|
||||
|
||||
self._cache = {}
|
||||
|
||||
@ -188,7 +181,6 @@ class TypeConstructor_Base[T]:
|
||||
result = self._cache.get(key, None)
|
||||
if result is None:
|
||||
self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
|
||||
self.on_create(key, result)
|
||||
|
||||
return result
|
||||
|
||||
@ -292,7 +284,6 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]
|
||||
|
||||
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
|
||||
result = Type3(name, self.make_application(args))
|
||||
self.on_create(args, result)
|
||||
return result
|
||||
|
||||
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
|
||||
|
||||
@ -10,6 +10,7 @@ from phasm.runtime import (
|
||||
calculate_alloc_size_struct,
|
||||
calculate_alloc_size_tuple,
|
||||
)
|
||||
from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
|
||||
from phasm.type3 import types as type3types
|
||||
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||
|
||||
@ -134,7 +135,7 @@ def _write_memory_stored_value(
|
||||
try:
|
||||
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
return TYPE_INFO_CONSTRUCTED.alloc_size
|
||||
except NoRouteForTypeException:
|
||||
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
||||
runner.interpreter_write_memory(adr, to_write)
|
||||
@ -286,43 +287,36 @@ def _load_memory_stored_returned_value(
|
||||
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
|
||||
|
||||
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
||||
typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED)
|
||||
|
||||
assert len(inp) == typ_info.alloc_size
|
||||
|
||||
if typ is prelude.u8:
|
||||
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8
|
||||
assert len(inp) == 4
|
||||
return struct.unpack('<I', inp)[0]
|
||||
return struct.unpack('<B', inp)[0]
|
||||
|
||||
if typ is prelude.u32:
|
||||
assert len(inp) == 4
|
||||
return struct.unpack('<I', inp)[0]
|
||||
|
||||
if typ is prelude.u64:
|
||||
assert len(inp) == 8
|
||||
return struct.unpack('<Q', inp)[0]
|
||||
|
||||
if typ is prelude.i8:
|
||||
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_i8
|
||||
assert len(inp) == 4
|
||||
return struct.unpack('<i', inp)[0]
|
||||
return struct.unpack('<b', inp)[0]
|
||||
|
||||
if typ is prelude.i32:
|
||||
assert len(inp) == 4
|
||||
return struct.unpack('<i', inp)[0]
|
||||
|
||||
if typ is prelude.i64:
|
||||
assert len(inp) == 8
|
||||
return struct.unpack('<q', inp)[0]
|
||||
|
||||
if typ is prelude.f32:
|
||||
assert len(inp) == 4
|
||||
return struct.unpack('<f', inp)[0]
|
||||
|
||||
if typ is prelude.f64:
|
||||
assert len(inp) == 8
|
||||
return struct.unpack('<d', inp)[0]
|
||||
|
||||
if (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
|
||||
if typ_info is TYPE_INFO_CONSTRUCTED:
|
||||
# Note: For applied types, inp should contain a 4 byte pointer
|
||||
assert len(inp) == 4
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)
|
||||
|
||||
@ -23,6 +23,23 @@ def testEntry(f: {type_}) -> u8:
|
||||
|
||||
assert exp_result == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('(u8, u8, u8, )', (45, 46, 47), 46, ),
|
||||
('u8[5]', (45, 46, 47, 48, 49), 46, ),
|
||||
('bytes', b'This is a test', 104)
|
||||
])
|
||||
def test_subscript_1(type_, in_put, exp_result):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(f: {type_}) -> u8:
|
||||
return f[1]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_put)
|
||||
|
||||
assert exp_result == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('(u8, u8, u8, )', (45, 46, 47), 47, ),
|
||||
@ -82,7 +99,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
@pytest.mark.parametrize('type_, in_put', [
|
||||
('(u8, u8, )', (45, 46), ),
|
||||
('u8[2]', (45, 46), ),
|
||||
# bytes isn't known at runtime so works like normal
|
||||
# dynamic_array isn't known at runtime so works like normal
|
||||
])
|
||||
def test_subscript_oob_constant_low(type_, in_put):
|
||||
code_py = f"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user