From e8bf9f1a260aadf1c959bfbd7278ef4a0d90ec29 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 3 Oct 2025 23:26:51 +0200 Subject: [PATCH] read_value function added to read result variable values --- src/copapy/__init__.py | 114 ++++++++++++++++++++++++++------ src/copapy/binwrite.py | 4 -- src/copapy/generate_stencils.py | 7 +- src/copapy/stencils.c | 54 +++++++++++++++ src/coparun/coparun_module.c | 25 +++++++ tests/test_compile.py | 8 +-- tests/test_coparun_module.py | 33 +++------ tests/test_coparun_module2.py | 40 +++++++++++ 8 files changed, 233 insertions(+), 52 deletions(-) create mode 100644 tests/test_coparun_module2.py diff --git a/src/copapy/__init__.py b/src/copapy/__init__.py index a8d3a26..4e94607 100644 --- a/src/copapy/__init__.py +++ b/src/copapy/__init__.py @@ -3,6 +3,8 @@ from typing import Generator, Iterable, Any from . import binwrite as binw from .stencil_db import stencil_database from collections import defaultdict, deque +from coparun_module import coparun, read_data_mem +import struct Operand = type['Net'] | float | int @@ -60,10 +62,31 @@ class Net: def __rtruediv__(self, other: Any) -> 'Net': return _add_op('div', [other, self]) + def __neg__(self) -> 'Net': + return _add_op('sub', [const(0), self]) + + def __gt__(self, other: Any) -> 'Net': + return _add_op('gt', [self, other]) + + def __lt__(self, other: Any) -> 'Net': + return _add_op('gt', [other, self]) + + def __eq__(self, other: Any) -> 'Net': + return _add_op('eq', [self, other]) + + def __mod__(self, other: Any) -> 'Net': + return _add_op('mod', [self, other]) + + def __rmod__(self, other: Any) -> 'Net': + return _add_op('mod', [other, self]) + def __repr__(self) -> str: names = get_var_name(self) return f"{'name:' + names[0] if names else 'id:' + str(id(self))[-5:]}" + def __hash__(self) -> int: + return id(self) + class Const(Node): def __init__(self, value: float | int | bool): @@ -239,7 +262,7 @@ def add_read_ops(node_list: list[Node]) -> Generator[tuple[Net | None, Node], No for node in node_list: if not node.name.startswith('const_'): for i, net in enumerate(node.args): - if net != registers[i]: + if id(net) != id(registers[i]): #if net in registers: # print('x swap registers') type_list = ['int' if r is None else r.dtype for r in registers] @@ -295,12 +318,9 @@ def get_nets(*inputs: Iterable[Iterable[Any]]) -> list[Net]: return list(nets) -def compile_to_instruction_list(end_nodes: Iterable[Node] | Node) -> binw.data_writer: - if isinstance(end_nodes, Node): - node_list = [end_nodes] - else: - node_list = list(end_nodes) - +def compile_to_instruction_list(node_list: Iterable[Node], sdb: stencil_database) -> tuple[binw.data_writer, dict[Net, tuple[int, int, str]]]: + variables: dict[Net, tuple[int, int, str]] = dict() + ordered_ops = list(stable_toposort(get_all_dag_edges(node_list))) const_net_list = get_const_nets(ordered_ops) output_ops = list(add_read_ops(ordered_ops)) @@ -329,7 +349,7 @@ def compile_to_instruction_list(end_nodes: Iterable[Node] | Node) -> binw.data_w dw.write_int(data_section_lengths) for net, out_offs, lengths in object_list: - dw.add_variable(net, out_offs, lengths, net.dtype) + variables[net] = (out_offs, lengths, net.dtype) if isinstance(net.source, Const): dw.write_com(binw.Command.COPY_DATA) dw.write_int(out_offs) @@ -389,16 +409,72 @@ def compile_to_instruction_list(end_nodes: Iterable[Node] | Node) -> binw.data_w dw.write_int(patch_type) dw.write_int(object_addr, signed=True) - # set entry point - dw.write_com(binw.Command.SET_ENTR_POINT) - dw.write_int(0) - - return dw + return dw, variables -def read_variable(bw: binw.data_writer, net: Net) -> None: - assert net in bw.variables, f"Variable {net} not found in data writer variables" - addr, lengths, _ = bw.variables[net] - bw.write_com(binw.Command.READ_DATA) - bw.write_int(addr) - bw.write_int(lengths) +class Target(): + + def __init__(self, arch: str = 'x86_64', optimization: str = 'O3') -> None: + self.sdb = stencil_database(f"src/copapy/obj/stencils_{arch}_{optimization}.o") + self._variables: dict[Net, tuple[int, int, str]] = dict() + + + def compile(self, *variables: list[Net] | list[list[Net]]) -> None: + nodes: list[Node] = [] + for s in variables: + if isinstance(s, Net): + nodes.append(Write(s)) + else: + for net in s: + assert isinstance(net, Net) + nodes.append(Write(net)) + + + dw, self._variables = compile_to_instruction_list(nodes, self.sdb) + dw.write_com(binw.Command.END_PROG) + assert coparun(dw.get_data()) > 0 + + + def run(self) -> None: + # set entry point and run code + dw = binw.data_writer(self.sdb.byteorder) + dw.write_com(binw.Command.SET_ENTR_POINT) + dw.write_int(0) + dw.write_com(binw.Command.END_PROG) + assert coparun(dw.get_data()) > 0 + + + def read_value(self, net: Net) -> float | int: + assert net in self._variables, f"Variable {net} not found" + addr, lengths, var_type = self._variables[net] + data = read_data_mem(addr, lengths) + assert data is not None and len(data) == lengths, f"Failed to read variable {net}" + en = {'little': '<', 'big': '>'}[self.sdb.byteorder] + if var_type == 'float': + if lengths == 4: + value = struct.unpack(en + 'f', data)[0] + assert isinstance(value, float) + return value + elif lengths == 8: + value = struct.unpack(en + 'd', data)[0] + assert isinstance(value, float) + return value + else: + raise ValueError(f"Unsupported float length: {lengths}") + elif var_type == 'int': + if lengths in (1, 2, 4, 8): + value = int.from_bytes(data, byteorder=self.sdb.byteorder, signed=True) + assert isinstance(value, int) + return value + else: + raise ValueError(f"Unsupported int length: {lengths}") + else: + raise ValueError(f"Unsupported variable type: {var_type}") + + def read_variable_remote(self, bw: binw.data_writer, net: Net) -> None: + assert net in self._variables, f"Variable {net} not found in data writer variables" + dw = binw.data_writer(self.sdb.byteorder) + addr, lengths, _ = self._variables[net] + bw.write_com(binw.Command.READ_DATA) + bw.write_int(addr) + bw.write_int(lengths) \ No newline at end of file diff --git a/src/copapy/binwrite.py b/src/copapy/binwrite.py index 2740ac3..4dcd890 100644 --- a/src/copapy/binwrite.py +++ b/src/copapy/binwrite.py @@ -14,10 +14,6 @@ class data_writer(): def __init__(self, byteorder: Literal['little', 'big']): self._data: list[tuple[str, bytes, int]] = list() self.byteorder: Literal['little', 'big'] = byteorder - self.variables: dict[Any, tuple[int, int, str]] = dict() - - def add_variable(self, net: Any, addr: int, lengths: int, var_type: str) -> None: - self.variables[net] = (addr, lengths, var_type) def write_int(self, value: int, num_bytes: int = 4, signed: bool = False) -> None: self._data.append((f"INT {value}", value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=signed), 0)) diff --git a/src/copapy/generate_stencils.py b/src/copapy/generate_stencils.py index 65191ee..ccd7aa9 100644 --- a/src/copapy/generate_stencils.py +++ b/src/copapy/generate_stencils.py @@ -1,7 +1,8 @@ from typing import Generator -op_signs = {'add': '+', 'sub': '-', 'mul': '*', 'div': '/'} +op_signs = {'add': '+', 'sub': '-', 'mul': '*', 'div': '/', + 'gt': '>', 'eq': '==', 'mod': '%'} def get_function_start() -> str: @@ -89,7 +90,7 @@ def permutate(*lists: list[str]) -> Generator[list[str], None, None]: if __name__ == "__main__": types = ['int', 'float'] - ops = ['add', 'sub', 'mul', 'div'] + ops = ['add', 'sub', 'mul', 'div', 'gt', 'eq'] code = """ // Auto-generated stencils for copapy @@ -109,6 +110,8 @@ if __name__ == "__main__": t_out = t1 if t1 == t2 else 'float' code += get_op_code(op, t1, t2, t_out) + code += get_op_code('mod', 'int', 'int', 'int') + for t1, t2, t_out in permutate(types, types, types): code += get_read_reg0_code(t1, t2, t_out) code += get_read_reg1_code(t1, t2, t_out) diff --git a/src/copapy/stencils.c b/src/copapy/stencils.c index 76cf938..c214630 100644 --- a/src/copapy/stencils.c +++ b/src/copapy/stencils.c @@ -113,6 +113,60 @@ asm volatile (".long 0xF27ECAFE"); } + void gt_int_int(int arg1, int arg2) { + asm volatile (".long 0xF17ECAFE"); + result_int_int(arg1 > arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void gt_int_float(int arg1, float arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_float(arg1 > arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void gt_float_int(float arg1, int arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_int(arg1 > arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void gt_float_float(float arg1, float arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_float(arg1 > arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void eq_int_int(int arg1, int arg2) { + asm volatile (".long 0xF17ECAFE"); + result_int_int(arg1 == arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void eq_int_float(int arg1, float arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_float(arg1 == arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void eq_float_int(float arg1, int arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_int(arg1 == arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void eq_float_float(float arg1, float arg2) { + asm volatile (".long 0xF17ECAFE"); + result_float_float(arg1 == arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + + void mod_int_int(int arg1, int arg2) { + asm volatile (".long 0xF17ECAFE"); + result_int_int(arg1 % arg2, arg2); + asm volatile (".long 0xF27ECAFE"); + } + void read_int_reg0_int_int(int arg1, int arg2) { asm volatile (".long 0xF17ECAFE"); result_int_int(dummy_int, arg2); diff --git a/src/coparun/coparun_module.c b/src/coparun/coparun_module.c index 735f573..a2dd7f2 100644 --- a/src/coparun/coparun_module.c +++ b/src/coparun/coparun_module.c @@ -28,8 +28,33 @@ static PyObject* coparun(PyObject* self, PyObject* args) { return PyLong_FromLong(result); } +static PyObject* read_data_mem(PyObject* self, PyObject* args) { + unsigned long rel_addr; + Py_ssize_t length; + + // Parse arguments: unsigned long (relative address), Py_ssize_t (length) + if (!PyArg_ParseTuple(args, "nk", &rel_addr, &length)) { + return NULL; + } + + if (length <= 0) { + PyErr_SetString(PyExc_ValueError, "Length must be positive"); + return NULL; + } + + uint8_t *ptr = data_memory + rel_addr; + + PyObject *result = PyBytes_FromStringAndSize((const char *)ptr, length); + if (!result) { + return PyErr_NoMemory(); + } + + return result; +} + static PyMethodDef MyMethods[] = { {"coparun", coparun, METH_VARARGS, "Pass raw command data to coparun"}, + {"read_data_mem", read_data_mem, METH_VARARGS, "Read memory and return as bytes"}, {NULL, NULL, 0, NULL} }; diff --git a/tests/test_compile.py b/tests/test_compile.py index 6c39bf6..e312e70 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -55,16 +55,16 @@ def test_compile(): r2 = i1 + 9 out = [Write(r1), Write(r2)] - il = copapy.compile_to_instruction_list(out) + il, _ = copapy.compile_to_instruction_list(out, copapy.sdb) - copapy.read_variable(il, r1) - copapy.read_variable(il, r2) + # run program command + il.write_com(binwrite.Command.SET_ENTR_POINT) + il.write_int(0) il.write_com(binwrite.Command.READ_DATA) il.write_int(0) il.write_int(36) - # run program command il.write_com(binwrite.Command.END_PROG) print('* Data to runner:') diff --git a/tests/test_coparun_module.py b/tests/test_coparun_module.py index b081f2c..f79488a 100644 --- a/tests/test_coparun_module.py +++ b/tests/test_coparun_module.py @@ -1,5 +1,5 @@ from coparun_module import coparun -from copapy import Write, const +from copapy import Write, const, Target import copapy from copapy import binwrite @@ -7,32 +7,19 @@ from copapy import binwrite def test_compile(): c1 = const(4) - c2 = const(2) + c2 = const(2) * 4 - i1 = c1 * 2 - r1 = i1 + 7 + (c2 + 7 * 9) + i1 = c2 * 2 + r1 = i1 + 7 + (c1 + 7 * 9) r2 = i1 + 9 - out = [Write(r1), Write(r2)] - il = copapy.compile_to_instruction_list(out) + tg = Target() + tg.compile(r1, r2, c2) + tg.run() - copapy.read_variable(il, r1) - copapy.read_variable(il, r2) - - il.write_com(binwrite.Command.READ_DATA) - il.write_int(0) - il.write_int(36) - - # run program command - il.write_com(binwrite.Command.END_PROG) - - #print('* Data to runner:') - #il.print() - - print('+ run coparun') - result = coparun(il.get_data()) - - assert result == 1 + print(tg.read_value(r1)) + print(tg.read_value(r2)) + print(tg.read_value(c2)) if __name__ == "__main__": diff --git a/tests/test_coparun_module2.py b/tests/test_coparun_module2.py new file mode 100644 index 0000000..203a6f7 --- /dev/null +++ b/tests/test_coparun_module2.py @@ -0,0 +1,40 @@ +from coparun_module import coparun +from copapy import Write, const +import copapy +from copapy import binwrite + + +def test_compile(): + + c1 = const(4) + c2 = const(2) * 4 + + i1 = c2 * 2 + r1 = i1 + 7 + (c1 + 7 * 9) + r2 = i1 + 9 + out = [Write(r1), Write(r2), Write(c2)] + + il, _ = copapy.compile_to_instruction_list(out, copapy.sdb) + + # run program command + il.write_com(binwrite.Command.SET_ENTR_POINT) + il.write_int(0) + + il.write_com(binwrite.Command.READ_DATA) + il.write_int(0) + il.write_int(36) + + # run program command + il.write_com(binwrite.Command.END_PROG) + + #print('* Data to runner:') + #il.print() + + print('+ run coparun') + result = coparun(il.get_data()) + + assert result == 1 + + +if __name__ == "__main__": + test_compile()