import re
import sys
from pathlib import Path
from dataclasses import dataclass
from typing import List, Tuple
@dataclass
class Param:
name: str
type_: str
@dataclass
class Function:
name: str params: List[Param]
def parse_lapack_rs(path: Path) -> List[Function]:
content = path.read_text()
pattern = r'pub fn (\w+_)\s*\(([\s\S]*?)\)(?:\s*->\s*\w+)?;'
functions = []
for match in re.finditer(pattern, content):
name = match.group(1)
params_str = match.group(2)
params = []
if params_str.strip():
param_list = re.split(r',\s*', params_str.strip())
for param in param_list:
param = param.strip()
if not param:
continue
if ':' in param:
parts = param.split(':', 1)
pname = parts[0].strip()
ptype = parts[1].strip()
params.append(Param(pname, ptype))
functions.append(Function(name, params))
return functions
def convert_type(rust_type: str) -> str:
type_map = {
'*const c_int': '*const lapackint',
'*mut c_int': '*mut lapackint',
'*const c_char': '*const c_char',
'*mut c_char': '*mut c_char',
'*const f32': '*const f32',
'*mut f32': '*mut f32',
'*const f64': '*const f64',
'*mut f64': '*mut f64',
'*const __BindgenComplex<f32>': '*const Complex32',
'*mut __BindgenComplex<f32>': '*mut Complex32',
'*const __BindgenComplex<f64>': '*const Complex64',
'*mut __BindgenComplex<f64>': '*mut Complex64',
'c_int': 'c_int',
'size_t': 'usize',
'LAPACK_S_SELECT2': 'Option<SSelectFn2>',
'LAPACK_S_SELECT3': 'Option<SSelectFn3>',
'LAPACK_D_SELECT2': 'Option<DSelectFn2>',
'LAPACK_D_SELECT3': 'Option<DSelectFn3>',
'LAPACK_C_SELECT1': 'Option<CSelectFn1>',
'LAPACK_C_SELECT2': 'Option<CSelectFn2>',
'LAPACK_Z_SELECT1': 'Option<ZSelectFn1>',
'LAPACK_Z_SELECT2': 'Option<ZSelectFn2>',
}
for old, new in type_map.items():
rust_type = rust_type.replace(old, new)
return rust_type
def get_base_name(func_name: str) -> str:
name = func_name.rstrip('_')
if name and name[0] in 'sdcz':
return name[1:]
return name
def generate_type_name(func_name: str) -> str:
name = func_name.rstrip('_')
return name[0].upper() + name[1:] + 'FnPtr'
def generate_backend_macro_call(func: Function) -> str:
name = func.name.rstrip('_')
type_name = generate_type_name(func.name)
params = []
for p in func.params:
converted_type = convert_type(p.type_)
params.append(f" {p.name}: {converted_type}")
params_str = ',\n'.join(params)
return f"""define_lapack_ffi!({name}, {type_name},
{params_str},
);"""
def generate_fortran_macro_call(func: Function) -> str:
name = func.name.rstrip('_')
params = []
for p in func.params:
converted_type = convert_type(p.type_)
params.append(f" {p.name}: {converted_type}")
params_str = ',\n'.join(params)
return f"""export_fortran_symbol!({name},
{params_str},
);"""
def generate_autoregister_extern(func: Function) -> str:
params = []
for p in func.params:
params.append(f" {p.name}: {p.type_}")
params_str = ',\n'.join(params)
return f""" fn {func.name}(
{params_str},
);"""
def generate_autoregister_call(func: Function) -> str:
name = func.name.rstrip('_')
return f" register_{name}(std::mem::transmute({func.name} as *const ()));"
def categorize_functions(functions: List[Function]) -> dict:
categories = {}
for func in functions:
base = get_base_name(func.name)
if base not in categories:
categories[base] = []
categories[base].append(func)
return categories
def main():
lapack_sys_path = Path("/Users/hiroshi/git/lapack-sys/src/lapack.rs")
output_dir = Path("/Users/hiroshi/projects/tensor4all/lapack-inject/src")
print(f"Parsing {lapack_sys_path}...")
functions = parse_lapack_rs(lapack_sys_path)
print(f"Found {len(functions)} functions")
functions = [f for f in functions if f.name not in ['lsame_']]
categories = categorize_functions(functions)
print(f"Found {len(categories)} unique operations")
print("Generating backend.rs...")
backend_lines = [
'//! Fortran LAPACK function pointer registration.',
'//!',
'//! This module provides the infrastructure for registering Fortran LAPACK',
'//! function pointers at runtime. Each function has its own `OnceLock` to allow',
'//! partial registration (only register the functions you need).',
'//!',
'//! Auto-generated from lapack-sys.',
'',
'#![allow(non_camel_case_types)]',
'#![allow(non_snake_case)]',
'#![allow(clippy::too_many_arguments)]',
'',
'use std::ffi::c_char;',
'use std::sync::OnceLock;',
'',
'use num_complex::{Complex32, Complex64};',
'',
'use crate::lapackint;',
'',
'// =============================================================================',
'// Select function types for eigenvalue routines',
'// =============================================================================',
'',
'/// Select function type for real GEES (single)',
'pub type SSelectFn2 = unsafe extern "C" fn(ar: *const f32, ai: *const f32) -> lapackint;',
'/// Select function type for real GEES (double)',
'pub type DSelectFn2 = unsafe extern "C" fn(ar: *const f64, ai: *const f64) -> lapackint;',
'/// Select function type for real (single) with 3 args',
'pub type SSelectFn3 = unsafe extern "C" fn(ar: *const f32, ai: *const f32, b: *const f32) -> lapackint;',
'/// Select function type for real (double) with 3 args',
'pub type DSelectFn3 = unsafe extern "C" fn(ar: *const f64, ai: *const f64, b: *const f64) -> lapackint;',
'/// Select function type for complex GEES (single)',
'pub type CSelectFn1 = unsafe extern "C" fn(w: *const Complex32) -> lapackint;',
'/// Select function type for complex GEES (double)',
'pub type ZSelectFn1 = unsafe extern "C" fn(w: *const Complex64) -> lapackint;',
'/// Select function type for complex (single) with 2 args',
'pub type CSelectFn2 = unsafe extern "C" fn(a: *const Complex32, b: *const Complex32) -> lapackint;',
'/// Select function type for complex (double) with 2 args',
'pub type ZSelectFn2 = unsafe extern "C" fn(a: *const Complex64, b: *const Complex64) -> lapackint;',
'',
'// =============================================================================',
'// Macro for defining LAPACK function types, storage, and registration',
'// =============================================================================',
'',
'/// Macro to define a LAPACK function pointer type, OnceLock storage,',
'/// registration function, and getter function.',
'macro_rules! define_lapack_ffi {',
' ($func_name:ident, $type_name:ident, $( $param:ident : $param_ty:ty ),* $(,)?) => {',
' paste::paste! {',
' /// Fortran function pointer type',
' pub type $type_name = unsafe extern "C" fn( $( $param : $param_ty ),* );',
'',
' static [<$func_name:upper>]: OnceLock<$type_name> = OnceLock::new();',
'',
' #[doc = concat!("Register the Fortran ", stringify!($func_name), " function pointer.")]',
' #[no_mangle]',
' pub unsafe extern "C" fn [<register_ $func_name>](f: $type_name) {',
' let _ = [<$func_name:upper>].set(f);',
' }',
'',
' #[doc = concat!("Get the registered ", stringify!($func_name), " function pointer.")]',
' #[allow(dead_code)]',
' pub(crate) fn [<get_ $func_name>]() -> $type_name {',
' *[<$func_name:upper>].get().expect(concat!(stringify!($func_name), " not registered"))',
' }',
' }',
' };',
'}',
'',
]
for base_name, funcs in sorted(categories.items()):
backend_lines.append(f'// {base_name.upper()}')
for func in sorted(funcs, key=lambda f: f.name):
backend_lines.append(generate_backend_macro_call(func))
backend_lines.append('')
(output_dir / "backend.rs").write_text('\n'.join(backend_lines))
print("Generating fortran.rs...")
fortran_lines = [
'//! Fortran LAPACK symbol exports.',
'//!',
'//! This module exports Fortran-style LAPACK symbols (e.g., `dgesv_`) that call',
'//! the registered function pointers. This allows lapack-inject to be a drop-in',
'//! replacement for lapack-src.',
'//!',
'//! Auto-generated from lapack-sys.',
'',
'#![allow(non_snake_case)]',
'#![allow(clippy::too_many_arguments)]',
'',
'use crate::backend::*;',
'use crate::lapackint;',
'use num_complex::{Complex32, Complex64};',
'use std::ffi::c_char;',
'',
'/// Macro to export a Fortran LAPACK symbol that delegates to the registered function pointer.',
'macro_rules! export_fortran_symbol {',
' ($func_name:ident, $( $param:ident : $param_ty:ty ),* $(,)?) => {',
' paste::paste! {',
' #[no_mangle]',
' pub unsafe extern "C" fn [<$func_name _>](',
' $( $param : $param_ty ),*',
' ) {',
' let f = [<get_ $func_name>]();',
' f($( $param ),*);',
' }',
' }',
' };',
'}',
'',
]
for base_name, funcs in sorted(categories.items()):
fortran_lines.append(f'// {base_name.upper()}')
for func in sorted(funcs, key=lambda f: f.name):
fortran_lines.append(generate_fortran_macro_call(func))
fortran_lines.append('')
(output_dir / "fortran.rs").write_text('\n'.join(fortran_lines))
print("Done!")
print(f"Generated {len(functions)} function bindings")
if __name__ == "__main__":
main()