Compare commits

...

9 Commits

Author SHA1 Message Date
Johan B.W. de Vries
be28450658 Converted fractional, fixed integral 2025-04-06 12:58:40 +02:00
Johan B.W. de Vries
4001b086db Cleanup, got rid of OPERATOR_MAP 2025-04-06 12:58:40 +02:00
Johan B.W. de Vries
19a29b7327 Migrated Num 2025-04-06 12:58:40 +02:00
Johan B.W. de Vries
ffd11c4f72 Started on a type class system 2025-04-06 12:58:40 +02:00
Johan B.W. de Vries
5d9ef0e276 Code review
Added a test, simplified another and added
a lot of TODO's.
2025-04-06 12:58:20 +02:00
Johan B.W. de Vries
521171540b Properly implemented test_crc32
Also extended the hack around u8 to u32 cast used
in crc32 - its type was lost, so it would just cast
to whatever the environment was expecting.

Had to do_format_check=False since the file has
some comments and such.
2025-04-05 16:41:46 +02:00
Johan B.W. de Vries
3e916a242e Minor fixes
- Fix python version in docs
- Made a note on which CRC32 we're using in the example
- Removed a redundant parameter
2025-04-05 16:19:26 +02:00
Johan B.W. de Vries
96f52a274c Minor cleanup
- Removed unused stubs
- Removed wabt dependency since wasmtime can compile
  wat2wasm as well.
- Removed wasm2c references, it was never tested.
2025-04-05 16:02:55 +02:00
Johan B.W. de Vries
5c537f712e Project update
Various updates to bring the project uptodate.

- Updated required packages
- Removed runtimes that are not being updated
- wasmtime is for now the only supported runtime
- Implements imports for wasmtime runtime
- Fixes a memory access bug for wasmtime runtime
- compile_wasm is now optional - runtimes have to
  implement and call this themselves
- Typing fixes
- Linting fixes
2025-04-05 15:43:49 +02:00
35 changed files with 763 additions and 422 deletions

View File

@ -1,7 +1,4 @@
WABT_DIR := /home/johan/sources/github.com/WebAssembly/wabt WAT2WASM := venv/bin/python wat2wasm.py
WAT2WASM := $(WABT_DIR)/bin/wat2wasm
WASM2C := $(WABT_DIR)/bin/wasm2c
%.wat: %.py $(shell find phasm -name '*.py') venv/.done %.wat: %.py $(shell find phasm -name '*.py') venv/.done
venv/bin/python -m phasm $< $@ venv/bin/python -m phasm $< $@
@ -15,12 +12,6 @@ WASM2C := $(WABT_DIR)/bin/wasm2c
%.wasm: %.wat %.wasm: %.wat
$(WAT2WASM) $^ -o $@ $(WAT2WASM) $^ -o $@
%.c: %.wasm
$(WASM2C) $^ -o $@
# %.exe: %.c
# cc $^ -o $@ -I $(WABT_DIR)/wasm2c
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py)) examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
venv/bin/python3 -m http.server --directory examples venv/bin/python3 -m http.server --directory examples
@ -31,10 +22,10 @@ lint: venv/.done
venv/bin/ruff check phasm tests venv/bin/ruff check phasm tests
typecheck: venv/.done typecheck: venv/.done
venv/bin/mypy --strict phasm tests/integration/helpers.py tests/integration/runners.py venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/runners.py
venv/.done: requirements.txt venv/.done: requirements.txt
python3.10 -m venv venv python3.12 -m venv venv
venv/bin/python3 -m pip install wheel pip --upgrade venv/bin/python3 -m pip install wheel pip --upgrade
venv/bin/python3 -m pip install -r $^ venv/bin/python3 -m pip install -r $^
touch $@ touch $@

View File

@ -32,7 +32,7 @@ make lint typecheck
To compile a Phasm file: To compile a Phasm file:
```sh ```sh
python3.10 -m phasm source.py output.wat python3.12 -m phasm source.py output.wat
``` ```
Additional required tools Additional required tools

17
TODO.md
View File

@ -10,3 +10,20 @@
- 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. - 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 - Implement a FizzBuzz example
- Also, check the codes for FIXME and TODO - Also, check the codes for FIXME and TODO
- Allocation is done using pointers for members, is this desired?
- Merge in type classes
- test_bitwise_or_inv_type
- test_bytes_index_out_of_bounds vs static trap(?)
- test_num.py is probably best as part of the generator?
- Find pytest.mark.skip
- There's a weird resolve_as reference in calculate_alloc_size
- Either there should be more of them or less
- At first glance, looks like failure in the typing system
- Related to the FIXME in phasm_type3?
- WEBASSEMBLY_BUILTIN_FLOAT_OPS and WEBASSEMBLY_BUILTIN_BYTES_OPS are special cased
- Should be part of a prelude
- Casting is not implemented except u32 which is special cased
- Parser is putting stuff in ModuleDataBlock
- Compiler should probably do that

View File

@ -1,3 +1,5 @@
# CRC-32/ISO-HDLC
#
# #include <inttypes.h> // uint32_t, uint8_t # #include <inttypes.h> // uint32_t, uint8_t
# #
# uint32_t CRC32(const uint8_t data[], size_t data_length) { # uint32_t CRC32(const uint8_t data[], size_t data_length) {

View File

@ -6,6 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Generator from typing import Generator
from . import ourlang from . import ourlang
from .type3 import typeclasses as type3classes
from .type3 import types as type3types from .type3 import types as type3types
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
@ -103,7 +104,12 @@ def expression(inp: ourlang.Expression) -> str:
return f'{inp.operator}{expression(inp.right)}' return f'{inp.operator}{expression(inp.right)}'
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}' if isinstance(inp.operator, type3classes.Type3ClassMethod):
operator = inp.operator.name
else:
operator = inp.operator
return f'{expression(inp.left)} {operator} {expression(inp.right)}'
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
args = ', '.join( args = ', '.join(

View File

@ -2,12 +2,13 @@
This module contains the code to convert parsed Ourlang into WebAssembly code This module contains the code to convert parsed Ourlang into WebAssembly code
""" """
import struct import struct
from typing import List, Optional from typing import Dict, List, Optional
from . import codestyle, ourlang, wasm from . import codestyle, ourlang, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types from .stdlib import types as stdlib_types
from .type3 import typeclasses as type3classes
from .type3 import types as type3types from .type3 import types as type3types
from .wasmgenerator import Generator as WasmGenerator from .wasmgenerator import Generator as WasmGenerator
@ -25,6 +26,55 @@ LOAD_STORE_TYPE_MAP = {
'bytes': 'i32', # Bytes are passed around as pointers '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 = {
type3classes.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,
},
type3classes.Fractional.operators['/']: {
'a=f32': stdlib_types.f32_fractional_div,
'a=f64': stdlib_types.f64_fractional_div,
},
type3classes.Integral.methods['div']: {
'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,
},
type3classes.Num.operators['+']: {
'a=u32': stdlib_types.u32_num_add,
'a=u64': stdlib_types.u64_num_add,
'a=i32': stdlib_types.i32_num_add,
'a=i64': stdlib_types.i64_num_add,
'a=f32': stdlib_types.f32_num_add,
'a=f64': stdlib_types.f64_num_add,
},
type3classes.Num.operators['-']: {
'a=u32': stdlib_types.u32_num_sub,
'a=u64': stdlib_types.u64_num_sub,
'a=i32': stdlib_types.i32_num_sub,
'a=i64': stdlib_types.i64_num_sub,
'a=f32': stdlib_types.f32_num_sub,
'a=f64': stdlib_types.f64_num_sub,
},
type3classes.Num.operators['*']: {
'a=u32': stdlib_types.u32_num_mul,
'a=u64': stdlib_types.u64_num_mul,
'a=i32': stdlib_types.i32_num_mul,
'a=i64': stdlib_types.i64_num_mul,
'a=f32': stdlib_types.f32_num_mul,
'a=f64': stdlib_types.f64_num_mul,
},
}
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
Public method for compiling a parsed Phasm module into Public method for compiling a parsed Phasm module into
@ -44,6 +94,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp == type3types.none: if inp == type3types.none:
return wasm.WasmTypeNone() return wasm.WasmTypeNone()
if inp == type3types.bool_:
# WebAssembly stores booleans as i32
# See e.g. f32.eq, which is [f32 f32] -> [i32]
return wasm.WasmTypeInt32()
if inp == type3types.u8: if inp == type3types.u8:
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
@ -92,14 +147,6 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
# Operators that work for i32, i64, f32, f64
OPERATOR_MAP = {
'+': 'add',
'-': 'sub',
'*': 'mul',
'==': 'eq',
}
U8_OPERATOR_MAP = { U8_OPERATOR_MAP = {
# Under the hood, this is an i32 # Under the hood, this is an i32
# Implementing Right Shift XOR, OR, AND is fine since the 3 remaining # Implementing Right Shift XOR, OR, AND is fine since the 3 remaining
@ -120,7 +167,6 @@ U32_OPERATOR_MAP = {
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
} }
U64_OPERATOR_MAP = { U64_OPERATOR_MAP = {
@ -133,7 +179,6 @@ U64_OPERATOR_MAP = {
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
} }
I32_OPERATOR_MAP = { I32_OPERATOR_MAP = {
@ -141,7 +186,6 @@ I32_OPERATOR_MAP = {
'>': 'gt_s', '>': 'gt_s',
'<=': 'le_s', '<=': 'le_s',
'>=': 'ge_s', '>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
} }
I64_OPERATOR_MAP = { I64_OPERATOR_MAP = {
@ -149,15 +193,6 @@ I64_OPERATOR_MAP = {
'>': 'gt_s', '>': 'gt_s',
'<=': 'le_s', '<=': 'le_s',
'>=': 'ge_s', '>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
}
F32_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
}
F64_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
} }
def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None: def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None:
@ -303,6 +338,30 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
expression(wgn, inp.right) expression(wgn, inp.right)
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if isinstance(inp.operator, type3classes.Type3ClassMethod):
type_var_map: Dict[type3classes.TypeVariable, type3types.Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
if not isinstance(type_var, type3classes.TypeVariable):
# Fixed type, not part of the lookup requirements
continue
assert isinstance(arg_expr.type3, type3types.Type3), type3types.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)
# FIXME: Re-implement build-in operators # FIXME: Re-implement build-in operators
# Maybe operator_annotation is the way to go # Maybe operator_annotation is the way to go
# Maybe the older stuff below that is the way to go # Maybe the older stuff below that is the way to go
@ -315,56 +374,27 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if operator_annotation == '(<) :: u64 -> u64 -> bool': if operator_annotation == '(<) :: u64 -> u64 -> bool':
wgn.add_statement('i64.lt_u') wgn.add_statement('i64.lt_u')
return return
if operator_annotation == '(==) :: u64 -> u64 -> bool':
wgn.add_statement('i64.eq')
return
if inp.type3 == type3types.u8: if inp.type3 == type3types.u8:
if operator := U8_OPERATOR_MAP.get(inp.operator, None): if operator := U8_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if inp.type3 == type3types.u32: if inp.type3 == type3types.u32:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if operator := U32_OPERATOR_MAP.get(inp.operator, None): if operator := U32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if inp.type3 == type3types.u64: if inp.type3 == type3types.u64:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if operator := U64_OPERATOR_MAP.get(inp.operator, None): if operator := U64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if inp.type3 == type3types.i32: if inp.type3 == type3types.i32:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if operator := I32_OPERATOR_MAP.get(inp.operator, None): if operator := I32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if inp.type3 == type3types.i64: if inp.type3 == type3types.i64:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if operator := I64_OPERATOR_MAP.get(inp.operator, None): if operator := I64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if inp.type3 == type3types.f32:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if operator := F32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if inp.type3 == type3types.f64:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
if operator := F64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.operator, inp.left.type3, inp.right.type3, inp.type3) raise NotImplementedError(expression, inp.operator, inp.left.type3, inp.right.type3, inp.type3)
@ -399,6 +429,30 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
for arg in inp.arguments: for arg in inp.arguments:
expression(wgn, arg) 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, inp.arguments + [inp]):
if not isinstance(type_var, type3classes.TypeVariable):
# Fixed type, not part of the lookup requirements
continue
assert isinstance(arg_expr.type3, type3types.Type3), type3types.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)) wgn.add_statement('call', '${}'.format(inp.function.name))
return return

View File

@ -6,6 +6,7 @@ from typing import Dict, Iterable, List, Optional, Union
from typing_extensions import Final from typing_extensions import Final
from .type3 import typeclasses as type3typeclasses
from .type3 import types as type3types from .type3 import types as type3types
from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder
@ -149,11 +150,11 @@ class BinaryOp(Expression):
""" """
__slots__ = ('operator', 'left', 'right', ) __slots__ = ('operator', 'left', 'right', )
operator: str operator: Union[str, type3typeclasses.Type3ClassMethod]
left: Expression left: Expression
right: Expression right: Expression
def __init__(self, operator: str, left: Expression, right: Expression) -> None: def __init__(self, operator: Union[str, type3typeclasses.Type3ClassMethod], left: Expression, right: Expression) -> None:
super().__init__() super().__init__()
self.operator = operator self.operator = operator
@ -169,10 +170,10 @@ class FunctionCall(Expression):
""" """
__slots__ = ('function', 'arguments', ) __slots__ = ('function', 'arguments', )
function: 'Function' function: Union['Function', type3typeclasses.Type3ClassMethod]
arguments: List[Expression] arguments: List[Expression]
def __init__(self, function: 'Function') -> None: def __init__(self, function: Union['Function', type3typeclasses.Type3ClassMethod]) -> None:
super().__init__() super().__init__()
self.function = function self.function = function

View File

@ -32,8 +32,22 @@ from .ourlang import (
UnaryOp, UnaryOp,
VariableReference, VariableReference,
) )
from .type3 import typeclasses as type3typeclasses
from .type3 import types as type3types from .type3 import types as type3types
PRELUDE_OPERATORS = {
**type3typeclasses.Eq.operators,
**type3typeclasses.Fractional.operators,
**type3typeclasses.Integral.operators,
**type3typeclasses.Num.operators,
}
PRELUDE_METHODS = {
**type3typeclasses.Eq.methods,
**type3typeclasses.Fractional.methods,
**type3typeclasses.Integral.methods,
**type3typeclasses.Num.methods,
}
def phasm_parse(source: str) -> Module: def phasm_parse(source: str) -> Module:
""" """
@ -338,6 +352,8 @@ class OurVisitor:
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp): if isinstance(node, ast.BinOp):
operator: Union[str, type3typeclasses.Type3ClassMethod]
if isinstance(node.op, ast.Add): if isinstance(node.op, ast.Add):
operator = '+' operator = '+'
elif isinstance(node.op, ast.Sub): elif isinstance(node.op, ast.Sub):
@ -359,6 +375,9 @@ class OurVisitor:
else: else:
raise NotImplementedError(f'Operator {node.op}') raise NotImplementedError(f'Operator {node.op}')
if operator in PRELUDE_OPERATORS:
operator = PRELUDE_OPERATORS[operator]
return BinaryOp( return BinaryOp(
operator, operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
@ -391,6 +410,9 @@ class OurVisitor:
else: else:
raise NotImplementedError(f'Operator {node.ops}') raise NotImplementedError(f'Operator {node.ops}')
if operator in PRELUDE_OPERATORS:
operator = PRELUDE_OPERATORS[operator]
return BinaryOp( return BinaryOp(
operator, operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
@ -452,7 +474,11 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load): if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.func.id in module.struct_definitions: func: Union[Function, type3typeclasses.Type3ClassMethod]
if node.func.id in PRELUDE_METHODS:
func = PRELUDE_METHODS[node.func.id]
elif node.func.id in module.struct_definitions:
struct_definition = module.struct_definitions[node.func.id] struct_definition = module.struct_definitions[node.func.id]
struct_constructor = StructConstructor(struct_definition.struct_type3) struct_constructor = StructConstructor(struct_definition.struct_type3)
@ -471,10 +497,12 @@ class OurVisitor:
if 1 != len(node.args): if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
return UnaryOp( unary_op = UnaryOp(
'cast', 'cast',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
) )
unary_op.type3 = type3types.u32
return unary_op
elif node.func.id == 'len': elif node.func.id == 'len':
if 1 != len(node.args): if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
@ -511,13 +539,15 @@ class OurVisitor:
func = module.functions[node.func.id] func = module.functions[node.func.id]
if len(func.posonlyargs) != len(node.args): exp_arg_count = len(func.posonlyargs) if isinstance(func, Function) else len(func.signature) - 1
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
if exp_arg_count != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires {exp_arg_count} arguments but {len(node.args)} are given')
result = FunctionCall(func) result = FunctionCall(func)
result.arguments.extend( result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr) self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
for arg_expr, param in zip(node.args, func.posonlyargs) for arg_expr in node.args
) )
return result return result
@ -675,7 +705,7 @@ def _not_implemented(check: Any, msg: str) -> None:
if not check: if not check:
raise NotImplementedError(msg) raise NotImplementedError(msg)
def _raise_static_error(node: Union[ast.mod, ast.stmt, ast.expr], msg: str) -> NoReturn: def _raise_static_error(node: Union[ast.stmt, ast.expr], msg: str) -> NoReturn:
raise StaticError( raise StaticError(
f'Static error on line {node.lineno}: {msg}' f'Static error on line {node.lineno}: {msg}'
) )

View File

@ -65,3 +65,99 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
g.return_() g.return_()
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy
def u8_eq_equals(g: Generator) -> None:
g.add_statement('i32.eq')
def u32_eq_equals(g: Generator) -> None:
g.add_statement('i32.eq')
def u64_eq_equals(g: Generator) -> None:
g.add_statement('i64.eq')
def i8_eq_equals(g: Generator) -> None:
g.add_statement('i32.eq')
def i32_eq_equals(g: Generator) -> None:
g.add_statement('i32.eq')
def i64_eq_equals(g: Generator) -> None:
g.add_statement('i64.eq')
def f32_eq_equals(g: Generator) -> None:
g.add_statement('f32.eq')
def f64_eq_equals(g: Generator) -> None:
g.add_statement('f64.eq')
def f32_fractional_div(g: Generator) -> None:
g.add_statement('f32.div')
def f64_fractional_div(g: Generator) -> None:
g.add_statement('f64.div')
def u32_integral_div(g: Generator) -> None:
g.add_statement('i32.div_u')
def u64_integral_div(g: Generator) -> None:
g.add_statement('i64.div_u')
def i32_integral_div(g: Generator) -> None:
g.add_statement('i32.div_s')
def i64_integral_div(g: Generator) -> None:
g.add_statement('i64.div_s')
def u32_num_add(g: Generator) -> None:
g.add_statement('i32.add')
def u64_num_add(g: Generator) -> None:
g.add_statement('i64.add')
def i32_num_add(g: Generator) -> None:
g.add_statement('i32.add')
def i64_num_add(g: Generator) -> None:
g.add_statement('i64.add')
def f32_num_add(g: Generator) -> None:
g.add_statement('f32.add')
def f64_num_add(g: Generator) -> None:
g.add_statement('f64.add')
def u32_num_sub(g: Generator) -> None:
g.add_statement('i32.sub')
def u64_num_sub(g: Generator) -> None:
g.add_statement('i64.sub')
def i32_num_sub(g: Generator) -> None:
g.add_statement('i32.sub')
def i64_num_sub(g: Generator) -> None:
g.add_statement('i64.sub')
def f32_num_sub(g: Generator) -> None:
g.add_statement('f32.sub')
def f64_num_sub(g: Generator) -> None:
g.add_statement('f64.sub')
def u32_num_mul(g: Generator) -> None:
g.add_statement('i32.mul')
def u64_num_mul(g: Generator) -> None:
g.add_statement('i64.mul')
def i32_num_mul(g: Generator) -> None:
g.add_statement('i32.mul')
def i64_num_mul(g: Generator) -> None:
g.add_statement('i64.mul')
def f32_num_mul(g: Generator) -> None:
g.add_statement('f32.mul')
def f64_num_mul(g: Generator) -> None:
g.add_statement('f64.mul')

View File

@ -6,7 +6,7 @@ These need to be resolved before the program can be compiled.
from typing import Dict, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
from .. import ourlang from .. import ourlang
from . import types from . import typeclasses, types
class Error: class Error:
@ -288,21 +288,21 @@ class MustImplementTypeClassConstraint(ConstraintBase):
""" """
__slots__ = ('type_class3', 'type3', ) __slots__ = ('type_class3', 'type3', )
type_class3: str type_class3: Union[str, typeclasses.Type3Class]
type3: types.Type3OrPlaceholder type3: types.Type3OrPlaceholder
DATA = { DATA = {
'u8': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'}, 'u8': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'u32': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'}, 'u32': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'u64': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'}, 'u64': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'i32': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'}, 'i32': {'EqualComparison', 'StrictPartialOrder'},
'i64': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'}, 'i64': {'EqualComparison', 'StrictPartialOrder'},
'bytes': {'Foldable', 'Sized'}, 'bytes': {'Foldable', 'Sized'},
'f32': {'BasicMathOperation', 'FloatingPoint'}, 'f32': {'Fractional', 'FloatingPoint'},
'f64': {'BasicMathOperation', 'FloatingPoint'}, 'f64': {'Fractional', 'FloatingPoint'},
} }
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None: def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
self.type_class3 = type_class3 self.type_class3 = type_class3
@ -316,6 +316,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
if isinstance(typ, types.PlaceholderForType): if isinstance(typ, types.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
if isinstance(self.type_class3, typeclasses.Type3Class):
if self.type_class3 in typ.classes:
return None
else:
if self.type_class3 in self.__class__.DATA.get(typ.name, set()): if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
return None return None
@ -325,7 +329,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
return ( return (
'{type3} derives {type_class3}', '{type3} derives {type_class3}',
{ {
'type_class3': self.type_class3, 'type_class3': str(self.type_class3),
'type3': self.type3, 'type3': self.type3,
}, },
) )

View File

@ -6,6 +6,7 @@ The constraints solver can then try to resolve all constraints.
from typing import Generator, List from typing import Generator, List
from .. import ourlang from .. import ourlang
from . import typeclasses as type3typeclasses
from . import types as type3types from . import types as type3types
from .constraints import ( from .constraints import (
CanBeSubscriptedConstraint, CanBeSubscriptedConstraint,
@ -65,6 +66,37 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
raise NotImplementedError(expression, inp, inp.operator) raise NotImplementedError(expression, inp, inp.operator)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
if isinstance(inp.operator, type3typeclasses.Type3ClassMethod):
type_var_map = {
x: type3types.PlaceholderForType([])
for x in inp.operator.signature
if isinstance(x, type3typeclasses.TypeVariable)
}
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
for type_var in inp.operator.type3_class.args:
assert type_var in type_var_map # When can this happen?
yield MustImplementTypeClassConstraint(
inp.operator.type3_class,
type_var_map[type_var],
)
for sig_part, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
if isinstance(sig_part, type3typeclasses.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
continue
if isinstance(sig_part, type3typeclasses.TypeReference):
# On key error: We probably have to a lot of work to do refactoring
# the type lookups
exp_type = type3types.LOOKUP_TABLE[sig_part.name]
yield SameTypeConstraint(exp_type, arg_expr.type3)
continue
return
if inp.operator in ('|', '&', '^', ): if inp.operator in ('|', '&', '^', ):
yield from expression(ctx, inp.left) yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right) yield from expression(ctx, inp.right)
@ -83,15 +115,6 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
comment=f'({inp.operator}) :: a -> a -> a') comment=f'({inp.operator}) :: a -> a -> a')
return return
if inp.operator in ('+', '-', '*', '/', ):
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BasicMathOperation', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
comment=f'({inp.operator}) :: a -> a -> a')
return
if inp.operator == '==': if inp.operator == '==':
yield from expression(ctx, inp.left) yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right) yield from expression(ctx, inp.right)
@ -117,6 +140,39 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
if isinstance(inp.function, type3typeclasses.Type3ClassMethod):
# FIXME: Duplicate code with BinaryOp
type_var_map = {
x: type3types.PlaceholderForType([])
for x in inp.function.signature
if isinstance(x, type3typeclasses.TypeVariable)
}
for call_arg in inp.arguments:
yield from expression(ctx, call_arg)
for type_var in inp.function.type3_class.args:
assert type_var in type_var_map # When can this happen?
yield MustImplementTypeClassConstraint(
inp.function.type3_class,
type_var_map[type_var],
)
for sig_part, arg_expr in zip(inp.function.signature, inp.arguments + [inp]):
if isinstance(sig_part, type3typeclasses.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
continue
if isinstance(sig_part, type3typeclasses.TypeReference):
# On key error: We probably have to a lot of work to do refactoring
# the type lookups
exp_type = type3types.LOOKUP_TABLE[sig_part.name]
yield SameTypeConstraint(exp_type, arg_expr.type3)
continue
return
yield SameTypeConstraint(inp.function.returns_type3, inp.type3, yield SameTypeConstraint(inp.function.returns_type3, inp.type3,
comment=f'The type of a function call to {inp.function.name} is the same as the type that the function returns') comment=f'The type of a function call to {inp.function.name} is the same as the type that the function returns')

103
phasm/type3/typeclasses.py Normal file
View File

@ -0,0 +1,103 @@
from typing import Any, Dict, Iterable, List, Mapping, Union
class TypeVariable:
__slots__ = ('letter', )
letter: str
def __init__(self, letter: str) -> None:
assert len(letter) == 1, f'{letter} is not a valid type variable'
self.letter = letter
def __hash__(self) -> int:
return hash(self.letter)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
return self.letter == other.letter
def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})'
class TypeReference:
__slots__ = ('name', )
name: str
def __init__(self, name: str) -> None:
assert len(name) > 1, f'{name} is not a valid type reference'
self.name = name
def __hash__(self) -> int:
return hash(self.name)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeReference):
raise NotImplementedError
return self.name == other.name
def __repr__(self) -> str:
return f'TypeReference({repr(self.name)})'
class Type3ClassMethod:
__slots__ = ('type3_class', 'name', 'signature', )
type3_class: 'Type3Class'
name: str
signature: List[Union[TypeReference, TypeVariable]]
def __init__(self, type3_class: 'Type3Class', name: str, signature: str) -> None:
self.type3_class = type3_class
self.name = name
self.signature = [
TypeVariable(x) if len(x) == 1 else TypeReference(x)
for x in signature.split(' -> ')
]
def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.type3_class)}, {repr(self.name)}, {repr(self.signature)})'
class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', )
name: str
args: List[TypeVariable]
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
def __init__(self, name: str, args: Iterable[str], methods: Mapping[str, str], operators: Mapping[str, str]) -> None:
self.name = name
self.args = [TypeVariable(x) for x in args]
self.methods = {
k: Type3ClassMethod(self, k, v)
for k, v in methods.items()
}
self.operators = {
k: Type3ClassMethod(self, k, v)
for k, v in operators.items()
}
def __repr__(self) -> str:
return self.name
Eq = Type3Class('Eq', ['a'], methods={}, operators={
'==': 'a -> a -> bool',
})
Fractional = Type3Class('Fractional', ['a'], methods={}, operators={
'/': 'a -> a -> a',
})
Integral = Type3Class('Eq', ['a'], methods={
'div': 'a -> a -> a',
}, operators={})
Num = Type3Class('Num', ['a'], methods={}, operators={
'+': 'a -> a -> a',
'-': 'a -> a -> a',
'*': 'a -> a -> a',
})

View File

@ -6,6 +6,8 @@ constraint generator works with.
""" """
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
from .typeclasses import Eq, Fractional, Integral, Num, Type3Class
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method' TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol): class ExpressionProtocol(Protocol):
@ -22,18 +24,24 @@ class Type3:
""" """
Base class for the type3 types Base class for the type3 types
""" """
__slots__ = ('name', ) __slots__ = ('name', 'classes', )
name: str name: str
""" """
The name of the string, as parsed and outputted by codestyle. The name of the string, as parsed and outputted by codestyle.
""" """
def __init__(self, name: str) -> None: classes: List[Type3Class]
"""
The type classes that this type implements
"""
def __init__(self, name: str, classes: Iterable[Type3Class]) -> None:
self.name = name self.name = name
self.classes = [*classes]
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3("{self.name}")' return f'Type3({repr(self.name)}, {repr(self.classes)})'
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -79,7 +87,7 @@ class IntType3(Type3):
value: int value: int
def __init__(self, value: int) -> None: def __init__(self, value: int) -> None:
super().__init__(str(value)) super().__init__(str(value), [])
assert 0 <= value assert 0 <= value
self.value = value self.value = value
@ -164,7 +172,8 @@ class AppliedType3(Type3):
base.name base.name
+ ' (' + ' ('
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution? + ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
+ ')' + ')',
[]
) )
self.base = base self.base = base
@ -213,7 +222,7 @@ class StructType3(Type3):
""" """
def __init__(self, name: str, members: Dict[str, Type3]) -> None: def __init__(self, name: str, members: Dict[str, Type3]) -> None:
super().__init__(name) super().__init__(name, [])
self.name = name self.name = name
self.members = dict(members) self.members = dict(members)
@ -221,38 +230,40 @@ class StructType3(Type3):
def __repr__(self) -> str: def __repr__(self) -> str:
return f'StructType3(repr({self.name}), repr({self.members}))' return f'StructType3(repr({self.name}), repr({self.members}))'
none = PrimitiveType3('none') none = PrimitiveType3('none', [])
""" """
The none type, for when functions simply don't return anything. e.g., IO(). The none type, for when functions simply don't return anything. e.g., IO().
""" """
bool_ = PrimitiveType3('bool') bool_ = PrimitiveType3('bool', [])
""" """
The bool type, either True or False The bool type, either True or False
Suffixes with an underscores, as it's a Python builtin
""" """
u8 = PrimitiveType3('u8') u8 = PrimitiveType3('u8', [Eq, Integral])
""" """
The unsigned 8-bit integer type. The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8. Operations on variables employ modular arithmetic, with modulus 2^8.
""" """
u32 = PrimitiveType3('u32') u32 = PrimitiveType3('u32', [Eq, Integral, Num])
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32. Operations on variables employ modular arithmetic, with modulus 2^32.
""" """
u64 = PrimitiveType3('u64') u64 = PrimitiveType3('u64', [Eq, Integral, Num])
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64. Operations on variables employ modular arithmetic, with modulus 2^64.
""" """
i8 = PrimitiveType3('i8') i8 = PrimitiveType3('i8', [Eq, Integral])
""" """
The signed 8-bit integer type. The signed 8-bit integer type.
@ -260,7 +271,7 @@ Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0. with the middel point being 0.
""" """
i32 = PrimitiveType3('i32') i32 = PrimitiveType3('i32', [Eq, Integral, Num])
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
@ -268,7 +279,7 @@ Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0. with the middel point being 0.
""" """
i64 = PrimitiveType3('i64') i64 = PrimitiveType3('i64', [Eq, Integral, Num])
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
@ -276,22 +287,22 @@ Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0. with the middel point being 0.
""" """
f32 = PrimitiveType3('f32') f32 = PrimitiveType3('f32', [Eq, Fractional, Num])
""" """
A 32-bits IEEE 754 float, of 32 bits width. A 32-bits IEEE 754 float, of 32 bits width.
""" """
f64 = PrimitiveType3('f64') f64 = PrimitiveType3('f64', [Eq, Fractional, Num])
""" """
A 32-bits IEEE 754 float, of 64 bits width. A 32-bits IEEE 754 float, of 64 bits width.
""" """
bytes = PrimitiveType3('bytes') bytes = PrimitiveType3('bytes', [])
""" """
This is a runtime-determined length piece of memory that can be indexed at runtime. This is a runtime-determined length piece of memory that can be indexed at runtime.
""" """
static_array = PrimitiveType3('static_array') static_array = PrimitiveType3('static_array', [])
""" """
This is a fixed length piece of memory that can be indexed at runtime. This is a fixed length piece of memory that can be indexed at runtime.
@ -299,7 +310,7 @@ It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated. of the same type repeated.
""" """
tuple = PrimitiveType3('tuple') # pylint: disable=W0622 tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.

View File

@ -1,10 +1,19 @@
mypy==0.991 marko==2.1.3
pygments==2.12.0 mypy==1.15.0
pytest==7.2.0 pygments==2.19.1
pytest==8.3.5
pytest-integration==0.2.2 pytest-integration==0.2.2
pywasm==1.0.7 ruff==0.11.4
pywasm3==0.5.0
ruff==0.1.5
wasmer==1.1.0 wasmtime==31.0.0
wasmer_compiler_cranelift==1.1.0
wasmtime==3.0.0 # TODO:
# extism?
# wasmedge
# Check 2025-04-05
# wasm3: minimal maintenance phase
# py-wasm: last updated 6 years ago
# wasmer-python: Not compatible with python3.12, last updated 2 years ago
# WAVM: Last updated 3 years ago

View File

@ -1,14 +0,0 @@
from typing import Any, Dict, List, Optional, Union
from . import binary
from . import option
from . import execution
class Runtime:
store: execution.Store
def __init__(self, module: binary.Module, imps: Optional[Dict[str, Any]] = None, opts: Optional[option.Option] = None):
...
def exec(self, name: str, args: List[Union[int, float]]) -> Any:
...

View File

@ -1,6 +0,0 @@
from typing import BinaryIO
class Module:
@classmethod
def from_reader(cls, reader: BinaryIO) -> 'Module':
...

View File

@ -1,10 +0,0 @@
from typing import List
class Result:
...
class MemoryInstance:
data: bytearray
class Store:
memory_list: List[MemoryInstance]

View File

@ -1,2 +0,0 @@
class Option:
...

View File

@ -1,23 +0,0 @@
from typing import Any, Callable
class Module:
...
class Runtime:
...
def load(self, wasm_bin: Module) -> None:
...
def get_memory(self, memid: int) -> memoryview:
...
def find_function(self, name: str) -> Callable[[Any], Any]:
...
class Environment:
def new_runtime(self, mem_size: int) -> Runtime:
...
def parse_module(self, wasm_bin: bytes) -> Module:
...

View File

@ -1,39 +0,0 @@
from typing import Any, Dict, Callable, Union
def wat2wasm(inp: str) -> bytes:
...
class Store:
...
class Function:
def __init__(self, store: Store, func: Callable[[Any], Any]) -> None:
...
class Module:
def __init__(self, store: Store, wasm: bytes) -> None:
...
class Uint8Array:
def __getitem__(self, index: Union[int, slice]) -> int:
...
def __setitem__(self, idx: int, value: int) -> None:
...
class Memory:
def uint8_view(self, offset: int = 0) -> Uint8Array:
...
class Exports:
...
class ImportObject:
def register(self, region: str, values: Dict[str, Function]) -> None:
...
class Instance:
exports: Exports
def __init__(self, module: Module, imports: ImportObject) -> None:
...

View File

@ -16,10 +16,7 @@ class SuiteResult:
self.returned_value = None self.returned_value = None
RUNNER_CLASS_MAP = { RUNNER_CLASS_MAP = {
'pywasm': runners.RunnerPywasm,
'pywasm3': runners.RunnerPywasm3,
'wasmtime': runners.RunnerWasmtime, 'wasmtime': runners.RunnerWasmtime,
'wasmer': runners.RunnerWasmer,
} }
class Suite: class Suite:
@ -29,7 +26,7 @@ class Suite:
def __init__(self, code_py: str) -> None: def __init__(self, code_py: str) -> None:
self.code_py = code_py self.code_py = code_py
def run_code(self, *args: Any, runtime: str = 'pywasm3', func_name: str = 'testEntry', imports: runners.Imports = None) -> Any: def run_code(self, *args: Any, runtime: str = 'wasmtime', func_name: str = 'testEntry', imports: runners.Imports = None, do_format_check: bool = True) -> Any:
""" """
Compiles the given python code into wasm and Compiles the given python code into wasm and
then runs it then runs it
@ -50,11 +47,11 @@ class Suite:
write_header(sys.stderr, 'Assembly') write_header(sys.stderr, 'Assembly')
runner.dump_wasm_wat(sys.stderr) runner.dump_wasm_wat(sys.stderr)
runner.compile_wasm()
runner.interpreter_setup() runner.interpreter_setup()
runner.interpreter_load(imports) runner.interpreter_load(imports)
# Check if code formatting works # Check if code formatting works
if do_format_check:
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs] func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
@ -252,6 +249,10 @@ def _load_memory_stored_returned_value(
if ret_type3 is type3types.none: if ret_type3 is type3types.none:
return None return None
if ret_type3 is type3types.bool_:
assert isinstance(wasm_value, int), wasm_value
return 0 != wasm_value
if ret_type3 in (type3types.i32, type3types.i64): if ret_type3 in (type3types.i32, type3types.i64):
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
return wasm_value return wasm_value

View File

@ -2,12 +2,8 @@
Runners to help run WebAssembly code on various interpreters Runners to help run WebAssembly code on various interpreters
""" """
import ctypes import ctypes
import io
from typing import Any, Callable, Dict, Iterable, Optional, TextIO from typing import Any, Callable, Dict, Iterable, Optional, TextIO
import pywasm.binary
import wasm3
import wasmer
import wasmtime import wasmtime
from phasm import ourlang, wasm from phasm import ourlang, wasm
@ -65,7 +61,7 @@ class RunnerBase:
""" """
Compiles the WebAssembly AST into WebAssembly Binary Compiles the WebAssembly AST into WebAssembly Binary
""" """
self.wasm_bin = wasmer.wat2wasm(self.wasm_asm) raise NotImplementedError
def interpreter_setup(self) -> None: def interpreter_setup(self) -> None:
""" """
@ -103,77 +99,6 @@ class RunnerBase:
""" """
raise NotImplementedError raise NotImplementedError
class RunnerPywasm(RunnerBase):
"""
Implements a runner for pywasm
See https://pypi.org/project/pywasm/
"""
module: pywasm.binary.Module
runtime: pywasm.Runtime
def interpreter_setup(self) -> None:
# Nothing to set up
pass
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
if imports is not None:
raise NotImplementedError
bytesio = io.BytesIO(self.wasm_bin)
self.module = pywasm.binary.Module.from_reader(bytesio)
self.runtime = pywasm.Runtime(self.module, {}, None)
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
for idx, byt in enumerate(data):
self.runtime.store.memory_list[0].data[offset + idx] = byt
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
return self.runtime.store.memory_list[0].data[offset:length]
def interpreter_dump_memory(self, textio: TextIO) -> None:
_dump_memory(textio, self.runtime.store.memory_list[0].data)
def call(self, function: str, *args: Any) -> Any:
return self.runtime.exec(function, [*args])
class RunnerPywasm3(RunnerBase):
"""
Implements a runner for pywasm3
See https://pypi.org/project/pywasm3/
"""
env: wasm3.Environment
rtime: wasm3.Runtime
mod: wasm3.Module
def interpreter_setup(self) -> None:
self.env = wasm3.Environment()
self.rtime = self.env.new_runtime(1024 * 1024)
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
if imports is not None:
raise NotImplementedError
self.mod = self.env.parse_module(self.wasm_bin)
self.rtime.load(self.mod)
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
memory = self.rtime.get_memory(0)
for idx, byt in enumerate(data):
memory[offset + idx] = byt
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
memory = self.rtime.get_memory(0)
return memory[offset:offset + length].tobytes()
def interpreter_dump_memory(self, textio: TextIO) -> None:
_dump_memory(textio, self.rtime.get_memory(0))
def call(self, function: str, *args: Any) -> Any:
return self.rtime.find_function(function)(*args)
class RunnerWasmtime(RunnerBase): class RunnerWasmtime(RunnerBase):
""" """
Implements a runner for wasmtime Implements a runner for wasmtime
@ -184,15 +109,44 @@ class RunnerWasmtime(RunnerBase):
module: wasmtime.Module module: wasmtime.Module
instance: wasmtime.Instance instance: wasmtime.Instance
@classmethod
def func2type(cls, func: Callable[[Any], Any]) -> wasmtime.FuncType:
params: list[wasmtime.ValType] = []
code = func.__code__
for idx in range(code.co_argcount):
varname = code.co_varnames[idx]
vartype = func.__annotations__[varname]
if vartype is int:
params.append(wasmtime.ValType.i32())
else:
raise NotImplementedError
results: list[wasmtime.ValType] = []
if func.__annotations__['return'] is None:
pass # No return value
elif func.__annotations__['return'] is int:
results.append(wasmtime.ValType.i32())
else:
raise NotImplementedError('Return type', func.__annotations__['return'])
return wasmtime.FuncType(params, results)
def interpreter_setup(self) -> None: def interpreter_setup(self) -> None:
self.store = wasmtime.Store() self.store = wasmtime.Store()
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None: def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
if imports is not None: functions: list[wasmtime.Func] = []
raise NotImplementedError
self.module = wasmtime.Module(self.store.engine, self.wasm_bin) if imports is not None:
self.instance = wasmtime.Instance(self.store, self.module, []) functions = [
wasmtime.Func(self.store, self.__class__.func2type(f), f)
for f in imports.values()
]
self.module = wasmtime.Module(self.store.engine, self.wasm_asm)
self.instance = wasmtime.Instance(self.store, self.module, functions)
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None: def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
exports = self.instance.exports(self.store) exports = self.instance.exports(self.store)
@ -217,8 +171,7 @@ class RunnerWasmtime(RunnerBase):
data_len = memory.data_len(self.store) data_len = memory.data_len(self.store)
raw = ctypes.string_at(data_ptr, data_len) raw = ctypes.string_at(data_ptr, data_len)
return raw[offset:offset + length]
return raw[offset:length]
def interpreter_dump_memory(self, textio: TextIO) -> None: def interpreter_dump_memory(self, textio: TextIO) -> None:
exports = self.instance.exports(self.store) exports = self.instance.exports(self.store)
@ -237,63 +190,6 @@ class RunnerWasmtime(RunnerBase):
return func(self.store, *args) return func(self.store, *args)
class RunnerWasmer(RunnerBase):
"""
Implements a runner for wasmer
See https://pypi.org/project/wasmer/
"""
# pylint: disable=E1101
store: wasmer.Store
module: wasmer.Module
instance: wasmer.Instance
def interpreter_setup(self) -> None:
self.store = wasmer.Store()
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
import_object = wasmer.ImportObject()
if imports:
import_object.register('imports', {
k: wasmer.Function(self.store, v)
for k, v in (imports or {}).items()
})
self.module = wasmer.Module(self.store, self.wasm_bin)
self.instance = wasmer.Instance(self.module, import_object)
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
exports = self.instance.exports
memory = getattr(exports, 'memory')
assert isinstance(memory, wasmer.Memory)
view = memory.uint8_view(offset)
for idx, byt in enumerate(data):
view[idx] = byt
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
exports = self.instance.exports
memory = getattr(exports, 'memory')
assert isinstance(memory, wasmer.Memory)
view = memory.uint8_view(offset)
return bytes(view[offset:length])
def interpreter_dump_memory(self, textio: TextIO) -> None:
exports = self.instance.exports
memory = getattr(exports, 'memory')
assert isinstance(memory, wasmer.Memory)
view = memory.uint8_view()
_dump_memory(textio, view) # type: ignore
def call(self, function: str, *args: Any) -> Any:
exports = self.instance.exports
func = getattr(exports, function)
return func(*args)
def _dump_memory(textio: TextIO, mem: bytes) -> None: def _dump_memory(textio: TextIO, mem: bytes) -> None:
line_width = 16 line_width = 16

View File

@ -8,7 +8,7 @@ def test_index():
with open('examples/buffer.py', 'r', encoding='ASCII') as fil: with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read() code_py = "\n" + fil.read()
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index', runtime='wasmtime') result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index')
assert 44 == result.returned_value assert 44 == result.returned_value
@pytest.mark.slow_integration_test @pytest.mark.slow_integration_test

View File

@ -1,5 +1,3 @@
import binascii
import pytest import pytest
from ..helpers import Suite from ..helpers import Suite
@ -7,28 +5,15 @@ from ..helpers import Suite
@pytest.mark.slow_integration_test @pytest.mark.slow_integration_test
def test_crc32(): def test_crc32():
# FIXME: Stub with open('examples/crc32.py', 'r', encoding='ASCII') as fil:
# crc = 0xFFFFFFFF code_py = "\n" + fil.read()
# byt = 0x61
# => (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ byt]
# (crc >> 8) = 0x00FFFFFF
# => 0x00FFFFFF ^ _CRC32_Table[(crc & 0xFF) ^ byt]
# (crc & 0xFF) = 0xFF
# => 0x00FFFFFF ^ _CRC32_Table[0xFF ^ byt]
# 0xFF ^ 0x61 = 0x9E
# => 0x00FFFFFF ^ _CRC32_Table[0x9E]
# _CRC32_Table[0x9E] = 0x17b7be43
# => 0x00FFFFFF ^ 0x17b7be43
code_py = """ # https://reveng.sourceforge.io/crc-catalogue/legend.htm#crc.legend.params
def _crc32_f(crc: u32, byt: u8) -> u32: in_put = b'123456789'
return 16777215 ^ 397917763
def testEntry(data: bytes) -> u32: # https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc
return 4294967295 ^ _crc32_f(4294967295, data[0]) check = 0xcbf43926
"""
exp_result = binascii.crc32(b'a')
result = Suite(code_py).run_code(b'a') result = Suite(code_py).run_code(in_put, func_name='crc32', do_format_check=False)
assert exp_result == result.returned_value assert check == result.returned_value

View File

@ -102,6 +102,7 @@ def generate_code(markdown, template, settings):
print() print()
print('from ..helpers import Suite') print('from ..helpers import Suite')
print() print()
print()
for test in get_tests(template): for test in get_tests(template):
assert len(test) == 4, test assert len(test) == 4, test
@ -121,7 +122,7 @@ def generate_code(markdown, template, settings):
print('@pytest.mark.integration_test') print('@pytest.mark.integration_test')
print(f'def test_{type_name}_{test_id}():') print(f'def test_{type_name}_{test_id}():')
print(' """') print(' """')
print(' ' + user_story.replace('\n', '\n ')) print(' ' + user_story.strip().replace('\n', '\n '))
print(' """') print(' """')
print(' code_py = """') print(' code_py = """')
if 'CODE_HEADER' in settings: if 'CODE_HEADER' in settings:

View File

@ -3,16 +3,15 @@ import sys
import pytest import pytest
from ..helpers import Suite, write_header from ..helpers import Suite, write_header
from ..runners import RunnerPywasm from ..runners import RunnerWasmtime
def setup_interpreter(phash_code: str) -> RunnerPywasm: def setup_interpreter(phash_code: str) -> RunnerWasmtime:
runner = RunnerPywasm(phash_code) runner = RunnerWasmtime(phash_code)
runner.parse() runner.parse()
runner.compile_ast() runner.compile_ast()
runner.compile_wat() runner.compile_wat()
runner.compile_wasm()
runner.interpreter_setup() runner.interpreter_setup()
runner.interpreter_load() runner.interpreter_load()
@ -38,16 +37,16 @@ def testEntry(b: bytes) -> u8:
result = suite.run_code(b'') result = suite.run_code(b'')
assert 128 == result.returned_value assert 128 == result.returned_value
result = suite.run_code(b'\x80', runtime='pywasm') result = suite.run_code(b'\x80')
assert 128 == result.returned_value assert 128 == result.returned_value
result = suite.run_code(b'\x80\x40', runtime='pywasm') result = suite.run_code(b'\x80\x40')
assert 192 == result.returned_value assert 192 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10', runtime='pywasm') result = suite.run_code(b'\x80\x40\x20\x10')
assert 240 == result.returned_value assert 240 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01', runtime='pywasm') result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01')
assert 255 == result.returned_value assert 255 == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -0,0 +1,101 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
INT_TYPES = ['u8', 'u32', 'u64', 'i8', 'i32', 'i64']
FLOAT_TYPES = ['f32', 'f64']
TYPE_MAP = {
'u8': int,
'u32': int,
'u64': int,
'i8': int,
'i32': int,
'i64': int,
'f32': float,
'f64': float,
}
@pytest.mark.integration_test
def test_equals_not_implemented():
code_py = """
class Foo:
val: i32
@exported
def testEntry(x: Foo, y: Foo) -> Foo:
return x + y
"""
with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES)
def test_equals_int_same(type_):
code_py = f"""
CONSTANT0: {type_} = 10
CONSTANT1: {type_} = 10
@exported
def testEntry() -> bool:
return CONSTANT0 == CONSTANT1
"""
result = Suite(code_py).run_code()
assert True is result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES)
def test_equals_int_different(type_):
code_py = f"""
CONSTANT0: {type_} = 10
CONSTANT1: {type_} = 3
@exported
def testEntry() -> bool:
return CONSTANT0 == CONSTANT1
"""
result = Suite(code_py).run_code()
assert False is result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES)
def test_equals_float_same(type_):
code_py = f"""
CONSTANT0: {type_} = 10.125
CONSTANT1: {type_} = 10.125
@exported
def testEntry() -> bool:
return CONSTANT0 == CONSTANT1
"""
result = Suite(code_py).run_code()
assert True is result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES)
def test_equals_float_different(type_):
code_py = f"""
CONSTANT0: {type_} = 10.32
CONSTANT1: {type_} = 10.33
@exported
def testEntry() -> bool:
return CONSTANT0 == CONSTANT1
"""
result = Suite(code_py).run_code()
assert False is result.returned_value

View File

@ -20,7 +20,7 @@ def testEntry() -> {type_}:
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_LIST) @pytest.mark.parametrize('type_', TYPE_LIST)
def test_division_zero_let_it_crash_float(type_): def test_division_float_follow_ieee_so_inf_pos(type_):
code_py = f""" code_py = f"""
@exported @exported
def testEntry() -> {type_}: def testEntry() -> {type_}:
@ -31,3 +31,17 @@ def testEntry() -> {type_}:
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2 # https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert float('+inf') == result.returned_value assert float('+inf') == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_LIST)
def test_division_float_follow_ieee_so_inf_neg(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return -10.0 / 0.0
"""
# WebAssembly dictates that float division follows the IEEE rules
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
result = Suite(code_py).run_code()
assert float('-inf') == result.returned_value

View File

@ -21,7 +21,6 @@ def testEntry() -> i32:
return 4238 * mul return 4238 * mul
result = Suite(code_py).run_code( result = Suite(code_py).run_code(
runtime='wasmer',
imports={ imports={
'helper': helper, 'helper': helper,
} }
@ -47,7 +46,6 @@ def testEntry() -> None:
prop = mul prop = mul
result = Suite(code_py).run_code( result = Suite(code_py).run_code(
runtime='wasmer',
imports={ imports={
'helper': helper, 'helper': helper,
} }
@ -73,7 +71,6 @@ def testEntry(x: u32) -> u8:
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'): with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
Suite(code_py).run_code( Suite(code_py).run_code(
runtime='wasmer',
imports={ imports={
'helper': helper, 'helper': helper,
} }

View File

@ -10,7 +10,7 @@ def test_division_int(type_):
code_py = f""" code_py = f"""
@exported @exported
def testEntry() -> {type_}: def testEntry() -> {type_}:
return 10 / 3 return div(10, 3)
""" """
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
@ -24,7 +24,7 @@ def test_division_zero_let_it_crash_int(type_):
code_py = f""" code_py = f"""
@exported @exported
def testEntry() -> {type_}: def testEntry() -> {type_}:
return 10 / 0 return div(10, 0)
""" """
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0) # WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)

View File

@ -1,5 +1,7 @@
import pytest import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite from ..helpers import Suite
INT_TYPES = ['u32', 'u64', 'i32', 'i64'] INT_TYPES = ['u32', 'u64', 'i32', 'i64']
@ -14,6 +16,20 @@ TYPE_MAP = {
'f64': float, 'f64': float,
} }
@pytest.mark.integration_test
def test_addition_not_implemented():
code_py = """
class Foo:
val: i32
@exported
def testEntry(x: Foo, y: Foo) -> Foo:
return x + y
"""
with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'):
Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES) @pytest.mark.parametrize('type_', INT_TYPES)
def test_addition_int(type_): def test_addition_int(type_):
@ -26,7 +42,7 @@ def testEntry() -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 13 == result.returned_value assert 13 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES) @pytest.mark.parametrize('type_', FLOAT_TYPES)
@ -40,7 +56,7 @@ def testEntry() -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 32.125 == result.returned_value assert 32.125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES) @pytest.mark.parametrize('type_', INT_TYPES)
@ -54,7 +70,7 @@ def testEntry() -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 7 == result.returned_value assert 7 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES) @pytest.mark.parametrize('type_', FLOAT_TYPES)
@ -68,23 +84,59 @@ def testEntry() -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 32.125 == result.returned_value assert 32.125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test') def test_subtraction_negative_result():
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8 code_py = """
def test_subtraction_underflow(type_):
code_py = f"""
@exported @exported
def testEntry() -> {type_}: def testEntry() -> i32:
return 10 - 11 return 10 - 11
""" """
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 0 < result.returned_value assert -1 == result.returned_value
# TODO: Multiplication @pytest.mark.integration_test
def test_subtraction_underflow():
code_py = """
@exported
def testEntry() -> u32:
return 10 - 11
"""
result = Suite(code_py).run_code()
assert 4294967295 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES)
def test_multiplication_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 * 3
"""
result = Suite(code_py).run_code()
assert 30 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES)
def test_multiplication_float(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 32.0 * 0.125
"""
result = Suite(code_py).run_code()
assert 4.0 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES) @pytest.mark.parametrize('type_', INT_TYPES)
@ -101,7 +153,7 @@ def helper(left: {type_}, right: {type_}) -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 22 == result.returned_value assert 22 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES) @pytest.mark.parametrize('type_', FLOAT_TYPES)
@ -118,4 +170,4 @@ def helper(left: {type_}, right: {type_}) -> {type_}:
result = Suite(code_py).run_code() result = Suite(code_py).run_code()
assert 32.125 == result.returned_value assert 32.125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)

View File

@ -1,4 +1,5 @@
import pytest import pytest
import wasmtime
from phasm.type3.entry import Type3Exception from phasm.type3.entry import Type3Exception
@ -64,7 +65,7 @@ def testEntry(x: u32) -> u8:
return CONSTANT[x] return CONSTANT[x]
""" """
with pytest.raises(RuntimeError): with pytest.raises(wasmtime.Trap):
Suite(code_py).run_code(3) Suite(code_py).run_code(3)
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -74,11 +74,9 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
@pytest.mark.integration_test @pytest.mark.integration_test
def test_tuple_must_use_literal_for_indexing(): def test_tuple_must_use_literal_for_indexing():
code_py = """ code_py = """
CONSTANT: u32 = 0
@exported @exported
def testEntry(x: (u8, u32, u64)) -> u64: def testEntry(x: (u8, u32, u64), y: u8) -> u64:
return x[CONSTANT] return x[y]
""" """
with pytest.raises(Type3Exception, match='Must index with literal'): with pytest.raises(Type3Exception, match='Must index with literal'):

View File

@ -3,7 +3,7 @@ import sys
import pytest import pytest
from ..helpers import write_header from ..helpers import write_header
from ..runners import RunnerPywasm3 as Runner from ..runners import RunnerWasmtime as Runner
def setup_interpreter(phash_code: str) -> Runner: def setup_interpreter(phash_code: str) -> Runner:
@ -12,7 +12,6 @@ def setup_interpreter(phash_code: str) -> Runner:
runner.parse() runner.parse()
runner.compile_ast() runner.compile_ast()
runner.compile_wat() runner.compile_wat()
runner.compile_wasm()
runner.interpreter_setup() runner.interpreter_setup()
runner.interpreter_load() runner.interpreter_load()

11
wat2wasm.py Normal file
View File

@ -0,0 +1,11 @@
import sys
from wasmtime import wat2wasm
def main(_prog: str, inp: str, _dash_o: str, out: str) -> None:
with open(inp, 'rb') as inp_obj:
with open(out, 'wb') as out_obj:
out_obj.write(wat2wasm(inp_obj.read()))
if __name__ == '__main__':
main(*sys.argv)