202 lines
4.6 KiB
Python
202 lines
4.6 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Generate a performance-oriented C module skeleton.
|
||
|
|
|
||
|
|
Creates:
|
||
|
|
- include/<name>.h
|
||
|
|
- src/<name>.c
|
||
|
|
- tests/test_<name>.c
|
||
|
|
- bench/bench_<name>.c
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import re
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
def normalize_name(raw: str) -> str:
|
||
|
|
name = raw.strip().lower().replace("-", "_")
|
||
|
|
name = re.sub(r"[^a-z0-9_]", "_", name)
|
||
|
|
name = re.sub(r"_+", "_", name).strip("_")
|
||
|
|
if not name:
|
||
|
|
raise ValueError("module name is empty after normalization")
|
||
|
|
if name[0].isdigit():
|
||
|
|
name = f"m_{name}"
|
||
|
|
return name
|
||
|
|
|
||
|
|
|
||
|
|
def write_file(path: Path, content: str, force: bool) -> None:
|
||
|
|
if path.exists() and not force:
|
||
|
|
raise FileExistsError(f"file exists: {path}")
|
||
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
path.write_text(content, encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
def render_header(module: str) -> str:
|
||
|
|
guard = f"{module.upper()}_H"
|
||
|
|
return f"""#ifndef {guard}
|
||
|
|
#define {guard}
|
||
|
|
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
extern "C" {{
|
||
|
|
#endif
|
||
|
|
|
||
|
|
typedef enum {{
|
||
|
|
{module}_ok = 0,
|
||
|
|
{module}_err_null = 1,
|
||
|
|
{module}_err_size = 2
|
||
|
|
}} {module}_status_t;
|
||
|
|
|
||
|
|
{module}_status_t {module}_process_f32(
|
||
|
|
const float *restrict in,
|
||
|
|
float *restrict out,
|
||
|
|
size_t n);
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
}}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def render_source(module: str) -> str:
|
||
|
|
return f"""#include "{module}.h"
|
||
|
|
|
||
|
|
{module}_status_t {module}_process_f32(
|
||
|
|
const float *restrict in,
|
||
|
|
float *restrict out,
|
||
|
|
size_t n)
|
||
|
|
{{
|
||
|
|
if (in == NULL || out == NULL) {{
|
||
|
|
return {module}_err_null;
|
||
|
|
}}
|
||
|
|
if (n == 0) {{
|
||
|
|
return {module}_err_size;
|
||
|
|
}}
|
||
|
|
|
||
|
|
for (size_t i = 0; i < n; ++i) {{
|
||
|
|
out[i] = in[i] * 1.0f;
|
||
|
|
}}
|
||
|
|
|
||
|
|
return {module}_ok;
|
||
|
|
}}
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def render_test(module: str) -> str:
|
||
|
|
return f"""#include <assert.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
|
||
|
|
#include "{module}.h"
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{{
|
||
|
|
float in[4] = {{1.0f, 2.0f, 3.0f, 4.0f}};
|
||
|
|
float out[4] = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||
|
|
|
||
|
|
{module}_status_t st = {module}_process_f32(in, out, 4);
|
||
|
|
assert(st == {module}_ok);
|
||
|
|
for (size_t i = 0; i < 4; ++i) {{
|
||
|
|
assert(out[i] == in[i]);
|
||
|
|
}}
|
||
|
|
|
||
|
|
printf("test_{module}: ok\\n");
|
||
|
|
return 0;
|
||
|
|
}}
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def render_bench(module: str) -> str:
|
||
|
|
return f"""#define _POSIX_C_SOURCE 200809L
|
||
|
|
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <time.h>
|
||
|
|
|
||
|
|
#include "{module}.h"
|
||
|
|
|
||
|
|
static uint64_t ns_now(void)
|
||
|
|
{{
|
||
|
|
struct timespec ts;
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||
|
|
}}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{{
|
||
|
|
const size_t n = 1u << 20;
|
||
|
|
float *in = (float *)malloc(n * sizeof(float));
|
||
|
|
float *out = (float *)malloc(n * sizeof(float));
|
||
|
|
if (!in || !out) {{
|
||
|
|
fprintf(stderr, "alloc failed\\n");
|
||
|
|
free(in);
|
||
|
|
free(out);
|
||
|
|
return 1;
|
||
|
|
}}
|
||
|
|
|
||
|
|
for (size_t i = 0; i < n; ++i) {{
|
||
|
|
in[i] = (float)(i & 1023u);
|
||
|
|
}}
|
||
|
|
|
||
|
|
uint64_t t0 = ns_now();
|
||
|
|
{module}_status_t st = {module}_process_f32(in, out, n);
|
||
|
|
uint64_t t1 = ns_now();
|
||
|
|
if (st != {module}_ok) {{
|
||
|
|
fprintf(stderr, "kernel error: %d\\n", (int)st);
|
||
|
|
free(in);
|
||
|
|
free(out);
|
||
|
|
return 1;
|
||
|
|
}}
|
||
|
|
|
||
|
|
double ns_per_elem = (double)(t1 - t0) / (double)n;
|
||
|
|
printf("{module} ns/elem: %.3f\\n", ns_per_elem);
|
||
|
|
|
||
|
|
free(in);
|
||
|
|
free(out);
|
||
|
|
return 0;
|
||
|
|
}}
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
parser = argparse.ArgumentParser(description="Scaffold a C module with tests and bench")
|
||
|
|
parser.add_argument("--name", required=True, help="Module name (snake_case preferred)")
|
||
|
|
parser.add_argument(
|
||
|
|
"--out-dir",
|
||
|
|
default=".",
|
||
|
|
help="Project root where include/src/tests/bench live",
|
||
|
|
)
|
||
|
|
parser.add_argument("--force", action="store_true", help="Overwrite existing files")
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
module = normalize_name(args.name)
|
||
|
|
root = Path(args.out_dir).resolve()
|
||
|
|
|
||
|
|
write_file(root / "include" / f"{module}.h", render_header(module), args.force)
|
||
|
|
write_file(root / "src" / f"{module}.c", render_source(module), args.force)
|
||
|
|
write_file(root / "tests" / f"test_{module}.c", render_test(module), args.force)
|
||
|
|
write_file(root / "bench" / f"bench_{module}.c", render_bench(module), args.force)
|
||
|
|
|
||
|
|
print(f"created module skeleton: {module}")
|
||
|
|
print(f"root: {root}")
|
||
|
|
print("next:")
|
||
|
|
print(
|
||
|
|
f" gcc -O0 -g3 -fsanitize=address,undefined "
|
||
|
|
f"-Iinclude src/{module}.c tests/test_{module}.c -o test_{module}"
|
||
|
|
)
|
||
|
|
print(
|
||
|
|
f" gcc -O3 -march=native "
|
||
|
|
f"-Iinclude src/{module}.c bench/bench_{module}.c -o bench_{module}"
|
||
|
|
)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|