Updated docs and doc helper scripts

This commit is contained in:
Nicolas 2025-12-22 15:39:17 +01:00
parent cc3730d696
commit df22438ffe
11 changed files with 148 additions and 25 deletions

3
.gitignore vendored
View File

@ -27,4 +27,5 @@ docs/source/api
/libs/ /libs/
*.core *.core
core core
*.log *.log
docs/source/start.md

View File

@ -147,7 +147,7 @@ The call to the dummy function `result_float_float` ensures that the compiler ke
The machine code for the function above, compiled for x86_64, looks like this: The machine code for the function above, compiled for x86_64, looks like this:
```assembly ```nasm
0000000000000000 <add_float_float>: 0000000000000000 <add_float_float>:
0: f3 0f 58 c1 addss %xmm1,%xmm0 0: f3 0f 58 c1 addss %xmm1,%xmm0
4: e9 00 00 00 00 jmp 9 <.LC1+0x1> 4: e9 00 00 00 00 jmp 9 <.LC1+0x1>
@ -158,7 +158,7 @@ Based on the relocation entry for the `jmp` to the symbol `result_float_float`,
For more complex operations - where inlining is less useful - stencils call a non-stencil function, such as in this example: For more complex operations - where inlining is less useful - stencils call a non-stencil function, such as in this example:
```assembly ```nasm
0000000000000000 <sin_float>: 0000000000000000 <sin_float>:
0: 48 83 ec 08 sub $0x8,%rsp 0: 48 83 ec 08 sub $0x8,%rsp
4: e8 00 00 00 00 call 9 <sin_float+0x9> 4: e8 00 00 00 00 call 9 <sin_float+0x9>

View File

@ -0,0 +1,3 @@
html[data-theme="dark"] .bd-content img {
background-color: transparent !important;
}

43
docs/source/compiler.md Normal file
View File

@ -0,0 +1,43 @@
# How it works
The compilation step starts with tracing the Python code to generate an acyclic directed graph (DAG) of variables and operations. The code can contain functions, closures, branching, and so on, but conditional branching is only allowed when the condition is known at tracing time (a `cp.iif` function exists to work around this). In the next step, this DAG is optimized and linearized into a sequence of operations. Each operation is mapped to a precompiled stencil or a combination of several stencils. A stencil is a piece of machine code with placeholders for memory addresses pointing to other code or data. The compiler generates patch instructions that fill these placeholders with the correct memory addresses.
After compilation, the binary code built from the stencils, the constant data, and the patch instructions is handed to the runner for execution. The runner allocates memory for code and data, copies both into place, applies the patch instructions, and finally executes the code.
The C code for a very simple stencil can look like this:
```c
add_float_float(float arg1, float arg2) {
result_float_float(arg1 + arg2, arg2);
}
```
The call to the dummy function `result_float_float` ensures that the compiler keeps the result and the second operand in registers for later use. The dummy function acts as a placeholder for the next stencil. Copapy uses two virtual registers, which map on most relevant architectures to actual hardware registers. Data that cannot be kept in a register is stored in statically allocated heap memory. Stack memory may be used inside some stencils, but its usage is essentially fixed and independent of the Copapy program, so total memory requirements are known at compile time.
The machine code for the function above, compiled for x86_64, looks like this:
```nasm
0000000000000000 <add_float_float>:
0: f3 0f 58 c1 addss %xmm1,%xmm0
4: e9 00 00 00 00 jmp 9 <.LC1+0x1>
5: R_X86_64_PLT32 result_float_float-0x4
```
Based on the relocation entry for the `jmp` to the symbol `result_float_float`, the `jmp` instruction is stripped when it is the last instruction in a stencil. Thus, a Copapy addition operation results in a single instruction. For stencils containing multiple branch exits, only the final `jmp` is removed; the others are patched to jump to the next stencil.
For more complex operations - where inlining is less useful - stencils call a non-stencil function, such as in this example:
```nasm
0000000000000000 <sin_float>:
0: 48 83 ec 08 sub $0x8,%rsp
4: e8 00 00 00 00 call 9 <sin_float+0x9>
5: R_X86_64_PLT32 sinf-0x4
9: 48 83 c4 08 add $0x8,%rsp
d: e9 00 00 00 00 jmp 12 <.LC0+0x2>
e: R_X86_64_PLT32 result_float-0x4
```
Unlike stencils, non-stencil functions are not stripped and do not need to be tail-call-optimizable.
Non-stencil functions and constants are stored together with the stencils in an ELF object file for each supported CPU architecture. The required non-stencil functions and constants are bundled during compilation. The compiler includes only the data and code required for the specific program.
The whole compilation process is independent of the actual instruction set. It relies purely on relocation entries and symbol metadata from the ELF file generated by the C compiler.

View File

@ -28,6 +28,7 @@ exclude_patterns = []
# html_theme = 'alabaster' # html_theme = 'alabaster'
html_theme = 'pydata_sphinx_theme' html_theme = 'pydata_sphinx_theme'
html_static_path = ['_static'] html_static_path = ['_static']
html_css_files = ['custom.css']
html_theme_options = { html_theme_options = {
"secondary_sidebar_items": ["page-toc"], "secondary_sidebar_items": ["page-toc"],
"footer_start": ["copyright"] "footer_start": ["copyright"]
@ -35,3 +36,4 @@ html_theme_options = {
html_theme_options["footer_end"] = [] html_theme_options["footer_end"] = []
autodoc_inherit_docstrings = True autodoc_inherit_docstrings = True
autoclass_content = 'both'

View File

@ -17,7 +17,8 @@ def extract_sections(md_text: str) -> dict[str, str]:
sections: dict[str, str] = {} sections: dict[str, str] = {}
for _, title, content in pattern.findall(md_text): for _, title, content in pattern.findall(md_text):
sections[title] = content.strip() assert isinstance(content, str)
sections[title] = content.strip().replace('](docs/source/media/', '](media/')
return sections return sections
@ -26,4 +27,8 @@ if __name__ == '__main__':
readme = extract_sections(f.read()) readme = extract_sections(f.read())
with open('docs/source/start.md', 'wt') as f: with open('docs/source/start.md', 'wt') as f:
f.write('\n'.join(readme[s] for s in ['Copapy', 'Current state'])) f.write('\n'.join(f"# {s}\n" + readme[s] for s in ['Copapy', 'Current state', 'Install', 'License']))
with open('docs/source/compiler.md', 'wt') as f:
f.write('# How it works\n')
f.write('\n'.join(readme[s] for s in ['How it works']))

View File

@ -17,7 +17,7 @@ def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title
classes = [ classes = [
name for name, obj in inspect.getmembers(module, inspect.isclass) name for name, obj in inspect.getmembers(module, inspect.isclass)
if (any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude) and if (any(fnmatch.fnmatch(name, pat) for pat in patterns if name not in exclude) and
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__) obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
] ]
@ -38,14 +38,17 @@ def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title
f2.write('```\n\n') f2.write('```\n\n')
def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None: def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = [], path_patterns: list[str] = ['*']) -> None:
"""Write the classes to the file.""" """Write the classes to the file."""
module = importlib.import_module(module_name) module = importlib.import_module(module_name)
functions = [ functions: list[str] = []
name for name, _ in inspect.getmembers(module, inspect.isfunction) for name, fu in inspect.getmembers(module, inspect.isfunction):
if (any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude)) if (any(fnmatch.fnmatch(name, pat) for pat in patterns if name not in exclude)):
] path = inspect.getfile(fu)
if any(fnmatch.fnmatch(path, pat) for pat in path_patterns):
functions.append(name)
if description: if description:
f.write(f'{description}\n\n') f.write(f'{description}\n\n')
@ -77,10 +80,22 @@ if __name__ == "__main__":
os.makedirs('docs/source/api', exist_ok=True) os.makedirs('docs/source/api', exist_ok=True)
with open('docs/source/api/index.md', 'w') as f: with open('docs/source/api/index.md', 'w') as f:
f.write('# Classes and functions\n\n') f.write('# User API\n\n')
write_classes(f, ['*'], 'copapy', title='Classes') write_classes(f, ['*'], 'copapy', title='Classes')
write_functions(f, ['*'], 'copapy', title='Functions') write_functions(f, ['*'], 'copapy', title='General functions', path_patterns=['*_autograd.py', '*_basic_types.py', '*_target.py'])
#write_manual(f, ['../ndfloat', '../floatarray'], title='Types') write_functions(f, ['*'], 'copapy', title='Math functions', path_patterns=['*_math*'], exclude=['get_42'])
write_functions(f, ['*'], 'copapy', title='Vector functions', path_patterns=['*_vectors*'])
write_functions(f, ['*'], 'copapy', title='Matrix functions', path_patterns=['*_matrices*'])
write_manual(f, ['NumLike'], title='Types')
with open('docs/source/api/backend.md', 'w') as f:
f.write('# Backend\n\n')
write_classes(f, ['*'], 'copapy.backend', title='Classes')
write_functions(f, ['*'], 'copapy.backend', title='Functions')

View File

@ -1,9 +1,11 @@
```{toctree} ```{toctree}
:maxdepth: 1 :maxdepth: 1
:hidden: :hidden:
compiler
api/index api/index
api/backend
repo repo
``` ```
```{include} ../../README.md ```{include} start.md
``` ```

View File

@ -12,18 +12,18 @@
fill: #EEEEEE !important; fill: #EEEEEE !important;
} }
#patch_1 path { #patch_1 path {
fill: #444444 !important; fill: #14141400 !important;
} }
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
path { path {
stroke: #444444 !important; stroke: #141414 !important;
} }
text { text {
fill: #444444 !important; fill: #141414 !important;
} }
#patch_1 path { #patch_1 path {
fill: #FFFFFF !important; fill: #FFFFFF00 !important;
} }
} }
#patch_1 path { #patch_1 path {

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,3 +1,55 @@
# Code repository # Code
Primary code repository is on GitHub: [github.com/Nonannet/copapy](https://github.com/Nonannet/copapy).
[Issues](https://github.com/Nonannet/copapy/issues) and [pull requests](https://github.com/Nonannet/copapy/pulls) can be created there.
To get started with development, first clone the repository:
```bash
git clone https://github.com/Nonannet/copapy.git
cd copapy
```
You may set up a virtual environment:
```bash
python -m venv .venv
source .venv/bin/activate # On Windows: `.venv\Scripts\activate`
```
Build and install the package and dev dependencies:
```bash
pip install -e .[dev]
```
If the build fails because no suitable C compiler is installed, you can either install one or use the binary package from PyPI:
```bash
pip install copapy[dev]
```
When running pytest, it will use the binary components from PyPI, but all Python code is executed from the local repository.
To run all tests, you need the stencil object files and the compiled runner. You can download them from GitHub or build them yourself with gcc.
Download the latest binaries from GitHub:
```bash
python tools/get_binaries.py
```
Build the binaries from source on Linux:
```bash
bash tools/build.sh
```
Run the tests:
```bash
pytest
```
Code repository is on GitHub: [github.com/Nonannet/copapy](https://github.com/Nonannet/copapy).

View File

@ -239,18 +239,18 @@ def save_svg_with_theme_styles(pyplot_obj, path):
fill: #EEEEEE !important; fill: #EEEEEE !important;
} }
#patch_1 path { #patch_1 path {
fill: #444444 !important; fill: #14141400 !important;
} }
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
path { path {
stroke: #444444 !important; stroke: #141414 !important;
} }
text { text {
fill: #444444 !important; fill: #141414 !important;
} }
#patch_1 path { #patch_1 path {
fill: #FFFFFF !important; fill: #FFFFFF00 !important;
} }
} }
#patch_1 path { #patch_1 path {