You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

526 lines
17 KiB

/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
* Copyright© 2023 Pascal Kuthe.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/config.h"
#include "ngspice/hash.h"
#include "ngspice/memory.h"
#include "ngspice/stringutil.h"
#include "osdidefs.h"
#include <stddef.h>
#include <sys/stat.h>
#include "osdi.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if (!defined HAS_WINGUI) && (!defined __MINGW32__) && (!defined _MSC_VER)
#include <dlfcn.h> /* to load libraries*/
#define OPENLIB(path) dlopen(path, RTLD_NOW | RTLD_LOCAL)
#define GET_SYM(lib, sym) dlsym(lib, sym)
#define FREE_DLERR_MSG(msg)
#else /* ifdef HAS_WINGUI */
#undef BOOLEAN
#include <windows.h>
#include <shlwapi.h>
#define OPENLIB(path) LoadLibrary(path)
#define GET_SYM(lib, sym) ((void *)GetProcAddress(lib, sym))
char *dlerror(void);
#define FREE_DLERR_MSG(msg) free_dlerr_msg(msg)
static void free_dlerr_msg(char *msg);
#endif /* ifndef HAS_WINGUI */
char *inputdir = NULL;
/* Returns true if path is an absolute path and false if it is a
* relative path. No check is done for the existance of the path. */
inline static bool is_absolute_pathname(const char *path) {
#ifdef _WIN32
return !PathIsRelativeA(path);
#else
return path[0] == DIR_TERM;
#endif
} /* end of funciton is_absolute_pathname */
/*-------------------------------------------------------------------------*
Look up the variable sourcepath and try everything in the list in order
if the file isn't in . and it isn't an abs path name.
*-------------------------------------------------------------------------*/
static char *resolve_path(const char *name) {
struct stat st;
#if defined(_WIN32)
/* If variable 'mingwpath' is set: convert mingw /d/... to d:/... */
if (cp_getvar("mingwpath", CP_BOOL, NULL, 0) && name[0] == DIR_TERM_LINUX &&
isalpha_c(name[1]) && name[2] == DIR_TERM_LINUX) {
DS_CREATE(ds, 100);
if (ds_cat_str(&ds, name) != 0) {
fprintf(stderr, "Unable to copy string while resolving path");
controlled_exit(EXIT_FAILURE);
}
char *const buf = ds_get_buf(&ds);
buf[0] = buf[1];
buf[1] = ':';
char *const resolved_path = resolve_path(buf);
ds_free(&ds);
return resolved_path;
}
#endif
/* just try it */
if (stat(name, &st) == 0)
return copy(name);
#if !defined(EXT_ASC) && (defined(__MINGW32__) || defined(_MSC_VER))
wchar_t wname[BSIZE_SP];
if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname,
2 * (int)strlen(name) + 1) == 0) {
fprintf(stderr, "UTF-8 to UTF-16 conversion failed with 0x%x\n",
GetLastError());
fprintf(stderr, "%s could not be converted\n", name);
return NULL;
}
if (_waccess(wname, 0) == 0)
return copy(name);
#endif
return (char *)NULL;
} /* end of function inp_pathresolve */
static char *resolve_input_path(const char *name) {
/* if name is an absolute path name,
* or if we haven't anything to prepend anyway
*/
if (is_absolute_pathname(name)) {
return resolve_path(name);
}
if (name[0] == '~' && name[1] == '/') {
char *const y = cp_tildexpand(name);
if (y) {
char *const r = resolve_path(y);
txfree(y);
return r;
}
}
/*
* If called from a script inputdir != NULL so try relativ to that dir
* Otherwise try relativ to the current workdir and relativ to the
* executables path
*/
if (inputdir) {
DS_CREATE(ds, 100);
int rc_ds = 0;
rc_ds |= ds_cat_str(&ds, inputdir); /* copy the dir name */
const size_t n = ds_get_length(&ds); /* end of copied dir name */
/* Append a directory separator if not present already */
const char ch_last = n > 0 ? inputdir[n - 1] : '\0';
if (ch_last != DIR_TERM
#ifdef _WIN32
&& ch_last != DIR_TERM_LINUX
#endif
) {
rc_ds |= ds_cat_char(&ds, DIR_TERM);
}
rc_ds |= ds_cat_str(&ds, name); /* append the file name */
if (rc_ds != 0) {
(void)fprintf(cp_err, "Unable to build \"dir\" path name "
"in inp_pathresolve_at");
controlled_exit(EXIT_FAILURE);
}
char* const r = resolve_path(ds_get_buf(&ds));
ds_free(&ds);
if (r)
return r;
}
if (Spice_Exec_Path && *Spice_Exec_Path) {
DS_CREATE(ds, 100);
int rc_ds = 0;
rc_ds |= ds_cat_str(&ds, Spice_Exec_Path); /* copy the dir name */
const size_t n = ds_get_length(&ds); /* end of copied dir name */
/* Append a directory separator if not present already */
const char ch_last = n > 0 ? Spice_Exec_Path[n - 1] : '\0';
if (ch_last != DIR_TERM
#ifdef _WIN32
&& ch_last != DIR_TERM_LINUX
#endif
) {
rc_ds |= ds_cat_char(&ds, DIR_TERM);
}
rc_ds |= ds_cat_str(&ds, name); /* append the file name */
if (rc_ds != 0) {
(void)fprintf(cp_err, "Unable to build \"dir\" path name "
"in inp_pathresolve_at");
controlled_exit(EXIT_FAILURE);
}
char* const r = resolve_path(ds_get_buf(&ds));
ds_free(&ds);
if (r)
return r;
}
/* no inputdir, or not found relative to inputdir:
search relative to current working directory */
DS_CREATE(ds, 100);
if (ds_cat_printf(&ds, ".%c%s", DIR_TERM, name) != 0) {
(void)fprintf(cp_err,
"Unable to build \".\" path name in inp_pathresolve_at");
controlled_exit(EXIT_FAILURE);
}
char* const r = resolve_path(ds_get_buf(&ds));
ds_free(&ds);
if (r != (char*)NULL) {
return r;
}
return NULL;
} /* end of function inp_pathresolve_at */
static size_t pad_to_align(size_t alignment, size_t size) {
size_t padding = alignment - size % alignment;
if (padding == alignment) {
return size;
}
return padding + size;
}
/**
* Calculates the offset that the OSDI instance data has from the beginning of
* the instance data allocated by ngspice. This offset is non trivial because
* ngspice must store the terminal pointers before the remaining instance
* data. As a result the offset is not constant and a variable amount of
* padding must be inserted to ensure correct alignment.
*/
static size_t calc_osdi_instance_data_off(const OsdiDescriptor *descr) {
size_t res = sizeof(GENinstance) /* generic data */
+ descr->num_terminals * sizeof(int);
// KLU pointers
#ifdef KLU
res = pad_to_align(alignof(double *), res);
res += ((size_t)descr->num_jacobian_entries) * 2 * sizeof(double *);
#endif
// noise values
res = pad_to_align(alignof(double), res);
res += NSTATVARS * (descr->num_noise_src + 1) * sizeof(double);
return pad_to_align(alignof(max_align_t), res);
}
#ifdef KLU
static size_t calc_osdi_instance_matrix_off(const OsdiDescriptor *descr) {
size_t res = sizeof(GENinstance) /* generic data */
+ descr->num_terminals * sizeof(int);
return pad_to_align(alignof(double *), res);
}
#endif
static size_t calc_osdi_noise_off(const OsdiDescriptor *descr) {
size_t res = sizeof(GENinstance) /* generic data */
+ descr->num_terminals * sizeof(int);
// KLU pointers
#ifdef KLU
res = pad_to_align(alignof(double *), res);
res += ((size_t)descr->num_jacobian_entries) * 2 * sizeof(double *);
#endif
// noise values
res = pad_to_align(alignof(double), res);
return res;
}
#define INVALID_OBJECT \
(OsdiObjectFile) { .num_entries = -1 }
#define EMPTY_OBJECT \
(OsdiObjectFile) {NULL, 0}
#define ERR_AND_RET \
error = dlerror(); \
fprintf(stderr, "Error opening osdi lib \"%s\": %s\n", path, error); \
FREE_DLERR_MSG(error); \
return INVALID_OBJECT;
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define GET_CONST(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (!sym) { \
ERR_AND_RET \
} \
const ty name = *((ty *)sym);
#define GET_PTR(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (!sym) { \
ERR_AND_RET \
} \
const ty *name = (ty *)sym;
#define INIT_CALLBACK(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (sym) { \
ty *slot = (ty *)sym; \
*slot = name; \
}
#define IS_LIM_FUN(fun_name, num_args_, val) \
if (strcmp(lim_table[i].name, fun_name) == 0) { \
if (lim_table[i].num_args == num_args_) { \
lim_table[i].func_ptr = (void *)val; \
continue; \
} else { \
expected_args = num_args_; \
} \
}
static NGHASHPTR known_object_files = NULL;
#define DUMMYDATA ((void *)42)
/**
* Loads an object file from the hard drive with the platform equivalent of
* dlopen. This function checks that the OSDI version of the object file is
* valid and then writes all data into the `registry`.
* If any errors occur an appropriate message is written to errMsg
*
* @param PATH path A path to the shared object file
* @param uint32_t* len The amount of entries already written into `registry`
* @param uint32_t* capacity The amount of space available in `registry`
* before reallocation is required
* @returns -1 on error, 0 otherwise
*/
extern OsdiObjectFile load_object_file(const char *input) {
void *handle;
char *error;
const void *sym;
/* ensure the hashtable exists */
if (!known_object_files) {
known_object_files = nghash_init_pointer(8);
}
const char *path = resolve_input_path(input);
if (!path) {
fprintf(stderr, "Error opening osdi lib \"%s\": No such file or directory!\n",
input);
return INVALID_OBJECT;
}
handle = OPENLIB(path);
if (!handle) {
ERR_AND_RET
}
/* Keep track of loaded shared object files to avoid loading the same model
* multiple times. We use the handle as a key because the same SO will always
* return the SAME pointer as long as dlclose is not called.
* nghash_insert returns NULL if the key (handle) was not already in the table
* and the data (DUMMYDATA) that was previously insered (!= NULL) otherwise*/
if (nghash_insert(known_object_files, handle, DUMMYDATA)) {
txfree(path);
return EMPTY_OBJECT;
}
GET_CONST(OSDI_VERSION_MAJOR, uint32_t);
GET_CONST(OSDI_VERSION_MINOR, uint32_t);
if (OSDI_VERSION_MAJOR != OSDI_VERSION_MAJOR_CURR ||
OSDI_VERSION_MINOR != OSDI_VERSION_MINOR_CURR) {
printf("NGSPICE only supports OSDI v%d.%d but \"%s\" targets v%d.%d!",
OSDI_VERSION_MAJOR_CURR, OSDI_VERSION_MINOR_CURR, path,
OSDI_VERSION_MAJOR, OSDI_VERSION_MINOR);
txfree(path);
return INVALID_OBJECT;
}
GET_CONST(OSDI_NUM_DESCRIPTORS, uint32_t);
GET_PTR(OSDI_DESCRIPTORS, OsdiDescriptor);
INIT_CALLBACK(osdi_log, osdi_log_ptr)
uint32_t lim_table_len = 0;
sym = GET_SYM(handle, "OSDI_LIM_TABLE_LEN");
if (sym) {
lim_table_len = *((uint32_t *)sym);
}
sym = GET_SYM(handle, "OSDI_LIM_TABLE");
OsdiLimFunction *lim_table = NULL;
if (sym) {
lim_table = (OsdiLimFunction *)sym;
} else {
lim_table_len = 0;
}
for (uint32_t i = 0; i < lim_table_len; i++) {
int expected_args = -1;
IS_LIM_FUN("pnjlim", 2, osdi_pnjlim)
IS_LIM_FUN("limvds", 0, osdi_limvds)
IS_LIM_FUN("fetlim", 1, osdi_fetlim)
IS_LIM_FUN("limitlog", 1, osdi_limitlog)
if (expected_args == -1) {
printf("warning(osdi): unkown $limit function \"%s\"", lim_table[i].name);
} else {
printf("warning(osdi): unexpected number of arguments %i (expected %i) "
"for \"%s\", ignoring...",
lim_table[i].num_args, expected_args, lim_table[i].name);
}
}
OsdiRegistryEntry *dst = TMALLOC(OsdiRegistryEntry, OSDI_NUM_DESCRIPTORS);
for (uint32_t i = 0; i < OSDI_NUM_DESCRIPTORS; i++) {
const OsdiDescriptor *descr = &OSDI_DESCRIPTORS[i];
uint32_t dt = descr->num_params + descr->num_opvars;
bool has_m = false;
uint32_t temp = descr->num_params + descr->num_opvars + 1;
for (uint32_t param_id = 0; param_id < descr->num_params; param_id++) {
OsdiParamOpvar *param = &descr->param_opvar[param_id];
for (uint32_t j = 0; j < 1 + param->num_alias; j++) {
char *name = param->name[j];
if (!strcmp(name, "m")) {
has_m = true;
} else if (!strcmp(name, "dt")) {
dt = UINT32_MAX;
} else if (!strcasecmp(name, "dtemp") || !strcasecmp(name, "dt")) {
dt = param_id;
} else if (!strcmp(name, "temp")) {
temp = UINT32_MAX;
} else if (!strcasecmp(name, "temp") ||
!strcasecmp(name, "temperature")) {
temp = param_id;
}
}
}
size_t inst_off = calc_osdi_instance_data_off(descr);
size_t noise_off = calc_osdi_noise_off(descr);
dst[i] = (OsdiRegistryEntry){
.descriptor = descr,
.inst_offset = (uint32_t)inst_off,
.noise_offset = (uint32_t)noise_off,
.dt = dt,
.temp = temp,
.has_m = has_m,
#ifdef KLU
.matrix_ptr_offset = (uint32_t)calc_osdi_instance_matrix_off(descr),
#endif
};
}
txfree(path);
return (OsdiObjectFile){
.entrys = dst,
.num_entries = (int)OSDI_NUM_DESCRIPTORS,
};
}
#ifdef KLU
inline size_t osdi_instance_matrix_ptr_off(const OsdiRegistryEntry *entry) {
return entry->matrix_ptr_offset;
}
inline double **osdi_instance_matrix_ptr(const OsdiRegistryEntry *entry,
GENinstance *inst) {
return (double **)(((char *)inst) + osdi_instance_matrix_ptr_off(entry));
}
#endif
inline double *osdi_noise_data(const OsdiRegistryEntry *entry,
GENinstance *inst) {
return (double *)(((char *)inst) + entry->noise_offset);
}
inline size_t osdi_instance_data_off(const OsdiRegistryEntry *entry) {
return entry->inst_offset;
}
inline void *osdi_instance_data(const OsdiRegistryEntry *entry,
GENinstance *inst) {
return (void *)(((char *)inst) + osdi_instance_data_off(entry));
}
inline OsdiExtraInstData *
osdi_extra_instance_data(const OsdiRegistryEntry *entry, GENinstance *inst) {
OsdiDescriptor *descr = (OsdiDescriptor *)entry->descriptor;
return (OsdiExtraInstData *)(((char *)inst) + entry->inst_offset +
descr->instance_size);
}
inline size_t osdi_model_data_off(void) {
return offsetof(OsdiModelData, data);
}
inline void *osdi_model_data(GENmodel *model) {
return (void *)&((OsdiModelData *)model)->data;
}
inline void *osdi_model_data_from_inst(GENinstance *inst) {
return osdi_model_data(inst->GENmodPtr);
}
inline OsdiRegistryEntry *osdi_reg_entry_model(const GENmodel *model) {
return (OsdiRegistryEntry *)ft_sim->devices[model->GENmodType]
->registry_entry;
}
inline OsdiRegistryEntry *osdi_reg_entry_inst(const GENinstance *inst) {
return osdi_reg_entry_model(inst->GENmodPtr);
}
#if defined(__MINGW32__) || defined(HAS_WINGUI) || defined(_MSC_VER)
/* For reporting error message if formatting fails */
static const char errstr_fmt[] =
"Unable to find message in dlerr(). System code = %lu";
static char errstr[sizeof errstr_fmt - 3 + 3 * sizeof(unsigned long)];
#if !defined (XSPICE)
char *dlerror(void) {
LPVOID lpMsgBuf;
DWORD rc = FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
if (rc == 0) { /* FormatMessage failed */
(void)sprintf(errstr, errstr_fmt, (unsigned long)GetLastError());
return errstr;
}
return lpMsgBuf; /* Return the formatted message */
} /* end of function dlerror */
#endif
/* Free message related to dynamic loading */
static void free_dlerr_msg(char *msg) {
if (msg != errstr) { /* msg is an allocation */
LocalFree(msg);
}
} /* end of function free_dlerr_msg */
#endif /* Windows emulation of dlerr */