Adds arithmetic shift shift

"An arithmetic shift is usually equivalent to
multiplying the number by a positive or a negative
integral power of the radix." -- Federal Standard 1037C

"This is the same as multiplying x by 2**y."

"A right shift by n bits is defined as floor division
by pow(2,n). A left shift by n bits is defined as
multiplication with pow(2,n)."
This commit is contained in:
Johan B.W. de Vries 2025-04-09 14:23:51 +02:00
parent da381e4a48
commit 99e407c3c0
5 changed files with 156 additions and 10 deletions

View File

@ -181,6 +181,22 @@ INSTANCES = {
'a=f32': stdlib_types.f32_natnum_mul, 'a=f32': stdlib_types.f32_natnum_mul,
'a=f64': stdlib_types.f64_natnum_mul, 'a=f64': stdlib_types.f64_natnum_mul,
}, },
type3classes.NatNum.operators['<<']: {
'a=u32': stdlib_types.u32_natnum_arithmic_shift_left,
'a=u64': stdlib_types.u64_natnum_arithmic_shift_left,
'a=i32': stdlib_types.i32_natnum_arithmic_shift_left,
'a=i64': stdlib_types.i64_natnum_arithmic_shift_left,
'a=f32': stdlib_types.f32_natnum_arithmic_shift_left,
'a=f64': stdlib_types.f64_natnum_arithmic_shift_left,
},
type3classes.NatNum.operators['>>']: {
'a=u32': stdlib_types.u32_natnum_arithmic_shift_right,
'a=u64': stdlib_types.u64_natnum_arithmic_shift_right,
'a=i32': stdlib_types.i32_natnum_arithmic_shift_right,
'a=i64': stdlib_types.i64_natnum_arithmic_shift_right,
'a=f32': stdlib_types.f32_natnum_arithmic_shift_right,
'a=f64': stdlib_types.f64_natnum_arithmic_shift_right,
},
} }
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
@ -256,26 +272,18 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
U8_OPERATOR_MAP = { U8_OPERATOR_MAP = {
# Under the hood, this is an i32
# Implementing Right Shift XOR, OR, AND is fine since the 3 remaining
# bytes stay zero after this operation
'>>': 'shr_u',
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
} }
U32_OPERATOR_MAP = { U32_OPERATOR_MAP = {
'<<': 'shl',
'>>': 'shr_u',
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
} }
U64_OPERATOR_MAP = { U64_OPERATOR_MAP = {
'<<': 'shl',
'>>': 'shr_u',
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
@ -983,6 +991,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
stdlib_types.__i64_ord_max__, stdlib_types.__i64_ord_max__,
stdlib_types.__i32_intnum_abs__, stdlib_types.__i32_intnum_abs__,
stdlib_types.__i64_intnum_abs__, stdlib_types.__i64_intnum_abs__,
stdlib_types.__u32_pow2__,
] + [ ] + [
function(x) function(x)
for x in inp.functions.values() for x in inp.functions.values()

View File

@ -211,7 +211,7 @@ def __i32_intnum_abs__(g: Generator, x: i32) -> i32:
g.i32.sub() g.i32.sub()
g.return_() g.return_()
return i32('return') return i32('return') # To satisfy mypy
@func_wrapper() @func_wrapper()
def __i64_intnum_abs__(g: Generator, x: i64) -> i64: def __i64_intnum_abs__(g: Generator, x: i64) -> i64:
@ -237,8 +237,26 @@ def __i64_intnum_abs__(g: Generator, x: i64) -> i64:
g.i64.sub() g.i64.sub()
g.return_() g.return_()
return i64('return') return i64('return') # To satisfy mypy
@func_wrapper()
def __u32_pow2__(g: Generator, x: i32) -> i32:
# 2^0 == 1
g.local.get(x)
g.i32.eqz()
with g.if_():
g.i32.const(1)
g.return_()
# 2 ^ x == 2 << (x - 1)
# (when x > 1)
g.i32.const(2)
g.local.get(x)
g.i32.const(1)
g.i32.sub()
g.i32.shl()
return i32('return') # To satisfy mypy
## ### ## ###
## class Eq ## class Eq
@ -564,6 +582,54 @@ def f32_natnum_mul(g: Generator) -> None:
def f64_natnum_mul(g: Generator) -> None: def f64_natnum_mul(g: Generator) -> None:
g.add_statement('f64.mul') g.add_statement('f64.mul')
def u32_natnum_arithmic_shift_left(g: Generator) -> None:
g.i32.shl()
def u64_natnum_arithmic_shift_left(g: Generator) -> None:
g.i64.extend_i32_u()
g.i64.shl()
def i32_natnum_arithmic_shift_left(g: Generator) -> None:
g.i32.shl()
def i64_natnum_arithmic_shift_left(g: Generator) -> None:
g.i64.extend_i32_u()
g.i64.shl()
def f32_natnum_arithmic_shift_left(g: Generator) -> None:
g.add_statement('call $stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.mul()
def f64_natnum_arithmic_shift_left(g: Generator) -> None:
g.add_statement('call $stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()
g.f64.mul()
def u32_natnum_arithmic_shift_right(g: Generator) -> None:
g.i32.shr_u()
def u64_natnum_arithmic_shift_right(g: Generator) -> None:
g.i64.extend_i32_u()
g.i64.shr_u()
def i32_natnum_arithmic_shift_right(g: Generator) -> None:
g.i32.shr_s()
def i64_natnum_arithmic_shift_right(g: Generator) -> None:
g.i64.extend_i32_u()
g.i64.shr_s()
def f32_natnum_arithmic_shift_right(g: Generator) -> None:
g.add_statement('call $stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.div()
def f64_natnum_arithmic_shift_right(g: Generator) -> None:
g.add_statement('call $stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()
g.f64.div()
## ### ## ###
## class IntNum ## class IntNum

View File

@ -112,6 +112,8 @@ NatNum = Type3Class('NatNum', ['a'], methods={}, operators={
'+': 'a -> a -> a', '+': 'a -> a -> a',
'-': 'a -> a -> a', '-': 'a -> a -> a',
'*': 'a -> a -> a', '*': 'a -> a -> a',
'<<': 'a -> u32 -> a', # Arithmic shift left
'>>': 'a -> u32 -> a', # Arithmic shift right
}) })
IntNum = Type3Class('IntNum', ['a'], methods={ IntNum = Type3Class('IntNum', ['a'], methods={

View File

@ -47,6 +47,9 @@ class Generator_i32i64:
self.rotl = functools.partial(self.generator.add_statement, f'{prefix}.rotl') self.rotl = functools.partial(self.generator.add_statement, f'{prefix}.rotl')
self.rotr = functools.partial(self.generator.add_statement, f'{prefix}.rotr') self.rotr = functools.partial(self.generator.add_statement, f'{prefix}.rotr')
# itestop
self.eqz = functools.partial(self.generator.add_statement, f'{prefix}.eqz')
# irelop # irelop
self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq') self.eq = functools.partial(self.generator.add_statement, f'{prefix}.eq')
self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne') self.ne = functools.partial(self.generator.add_statement, f'{prefix}.ne')
@ -75,6 +78,10 @@ class Generator_i64(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i64', generator) super().__init__('i64', generator)
# 2.4.1. Numeric Instructions
self.extend_i32_s = functools.partial(self.generator.add_statement, 'i64.extend_i32_s')
self.extend_i32_u = functools.partial(self.generator.add_statement, 'i64.extend_i32_u')
class Generator_f32f64: class Generator_f32f64:
def __init__(self, prefix: str, generator: 'Generator') -> None: def __init__(self, prefix: str, generator: 'Generator') -> None:
self.prefix = prefix self.prefix = prefix
@ -107,6 +114,12 @@ class Generator_f32f64:
self.le = functools.partial(self.generator.add_statement, f'{prefix}.le') self.le = functools.partial(self.generator.add_statement, f'{prefix}.le')
self.ge = functools.partial(self.generator.add_statement, f'{prefix}.ge') self.ge = functools.partial(self.generator.add_statement, f'{prefix}.ge')
# Other instr - convert
self.convert_i32_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_s')
self.convert_i32_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i32_u')
self.convert_i64_s = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_s')
self.convert_i64_u = functools.partial(self.generator.add_statement, f'{prefix}.convert_i64_u')
# 2.4.4. Memory Instructions # 2.4.4. Memory Instructions
self.load = functools.partial(self.generator.add_statement, f'{prefix}.load') self.load = functools.partial(self.generator.add_statement, f'{prefix}.load')
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store') self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')

View File

@ -138,6 +138,62 @@ def testEntry() -> {type_}:
assert 4.0 == result.returned_value assert 4.0 == result.returned_value
assert TYPE_MAP[type_] is type(result.returned_value) assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES)
def test_arithmic_shift_right_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 100 >> 3
"""
result = Suite(code_py).run_code()
assert 12 == result.returned_value
assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES)
def test_arithmic_shift_right_float(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 100.0 >> 3
"""
result = Suite(code_py).run_code()
assert 12.5 == result.returned_value
assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', INT_TYPES)
def test_arithmic_shift_left_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 3 << 3
"""
result = Suite(code_py).run_code()
assert 24 == result.returned_value
assert TYPE_MAP[type_] is type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', FLOAT_TYPES)
def test_arithmic_shift_left_float(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 3.5 << 3
"""
result = Suite(code_py).run_code()
assert 28.0 == 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)
def test_call_with_expression_int(type_): def test_call_with_expression_int(type_):