fixed memory leaks

This commit is contained in:
Justin Fisher 2026-08-02 12:52:54 +02:00
parent e50dee9ccc
commit 13d55715fc
11 changed files with 85 additions and 12 deletions

View File

@ -497,18 +497,24 @@ com_measure_when(
} }
if (has_d2) { if (has_d2) {
/* The loop index runs over d's length; d2 may be shorter
* (e.g. a length-1 measure-result vector on the right hand
* side of WHEN v(x)=NAME). Clamp to d2's last element so
* a short vector reads as a held constant instead of
* running past its allocation. */
int i2 = (i < d2->v_length) ? i : d2->v_length - 1;
if (ac_check) { if (ac_check) {
if (d2->v_compdata) if (d2->v_compdata)
value2 = get_value(meas, d2, i); //d->v_compdata[i].cx_real; value2 = get_value(meas, d2, i2); //d->v_compdata[i].cx_real;
else else
value2 = d2->v_realdata[i]; value2 = d2->v_realdata[i2];
} else if (sp_check) { } else if (sp_check) {
if (d2->v_compdata) if (d2->v_compdata)
value2 = get_value(meas, d2, i); //d->v_compdata[i].cx_real; value2 = get_value(meas, d2, i2); //d->v_compdata[i].cx_real;
else else
value2 = d2->v_realdata[i]; value2 = d2->v_realdata[i2];
} else { } else {
value2 = d2->v_realdata[i]; value2 = d2->v_realdata[i2];
} }
} else { } else {
value2 = NAN; value2 = NAN;

View File

@ -27,6 +27,10 @@ extern bool ft_batchmode;
extern bool rflag; extern bool rflag;
/* Set by INPevaluate's HSPICE-compat bare-.param resolution path
(inpeval.c); see the substitution pre-pass below. */
extern char *inpeval_last_param_name;
/* measure in interactive mode: /* measure in interactive mode:
meas command inside .control ... .endc loop or manually entered. meas command inside .control ... .endc loop or manually entered.
meas has to be followed by the standard tokens (see measure_extract_variables()). meas has to be followed by the standard tokens (see measure_extract_variables()).
@ -44,6 +48,7 @@ com_meas(wordlist *wl)
wordlist *wl_index; wordlist *wl_index;
struct dvec *d; struct dvec *d;
int err = 0; int err = 0;
double subst_val;
int fail; int fail;
double result = 0; double result = 0;
@ -76,7 +81,8 @@ com_meas(wordlist *wl)
vec_found = wl_index->wl_word; vec_found = wl_index->wl_word;
/* token may be already a value, maybe 'LAST', which we have to keep, or maybe a vector */ /* token may be already a value, maybe 'LAST', which we have to keep, or maybe a vector */
if (!cieq(vec_found, "LAST")) { if (!cieq(vec_found, "LAST")) {
INPevaluate(&vec_found, &err, 1); char *orig_word = wl_index->wl_word;
subst_val = INPevaluate(&vec_found, &err, 1);
/* if not a valid number */ /* if not a valid number */
if (err) { if (err) {
/* check if vec_found is a valid vector */ /* check if vec_found is a valid vector */
@ -86,8 +92,18 @@ com_meas(wordlist *wl)
if (d && (d->v_length == 1) && (d->v_numdims == 1)) { if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
/* get its value */ /* get its value */
wl_index->wl_word = tprintf("%e", d->v_realdata[0]); wl_index->wl_word = tprintf("%e", d->v_realdata[0]);
tfree(vec_found); tfree(orig_word);
} }
} else if (inpeval_last_param_name) {
/* INPevaluate resolved a bare .param identifier (e.g.
a prior .measure result registered via
nupa_add_param). Write the value back so the
measure parser sees a number otherwise the
identifier survives as text and com_measure2
treats it as a full vector, which mis-measures
(and over-reads a length-1 result vector). */
wl_index->wl_word = tprintf("%e", subst_val);
tfree(orig_word);
} }
} }
} }
@ -95,7 +111,7 @@ com_meas(wordlist *wl)
else if ((equal_ptr = strchr(token, '=')) != NULL) { else if ((equal_ptr = strchr(token, '=')) != NULL) {
vec_found = equal_ptr + 1; vec_found = equal_ptr + 1;
if (!cieq(vec_found, "LAST")) { if (!cieq(vec_found, "LAST")) {
INPevaluate(&vec_found, &err, 1); subst_val = INPevaluate(&vec_found, &err, 1);
if (err) { if (err) {
d = vec_get(vec_found); d = vec_get(vec_found);
/* Only if we have a single valued vector, replacing /* Only if we have a single valued vector, replacing
@ -106,6 +122,13 @@ com_meas(wordlist *wl)
tprintf("%.*s=%e", lhs_len, token, d->v_realdata[0]); tprintf("%.*s=%e", lhs_len, token, d->v_realdata[0]);
tfree(token); tfree(token);
} }
} else if (inpeval_last_param_name) {
/* bare .param identifier resolved — write the value
back (see whole-token case above) */
int lhs_len = (int)(equal_ptr - token);
wl_index->wl_word =
tprintf("%.*s=%e", lhs_len, token, subst_val);
tfree(token);
} }
} }
} else { } else {

View File

@ -143,6 +143,14 @@ struct CKTcircuit {
* opt-out. */ * opt-out. */
int CKTosdiStepReject; int CKTosdiStepReject;
int CKTosdiStepRejectOff; int CKTosdiStepRejectOff;
/* Set by OSDIsetup when the circuit contains at least one OSDI
* (Verilog-A) device. Gates OSDI-motivated Newton machinery the
* per-node Δv limiter in NIiter so that circuits built purely
* from native SPICE devices keep stock SPICE3 iteration behaviour. */
int CKTosdiPresent;
/* `.option nostaterestore` opt-out of the dctran failed-attempt
* device-state restore (CKTstate0 <- CKTstate1 on timepoint retry). */
int CKTstateRestoreOff;
/* Per-iteration count of huge-finite Jacobian entries clipped by /* Per-iteration count of huge-finite Jacobian entries clipped by
* sanitize_jacobian during CKTload. When > 0, the model evaluation * sanitize_jacobian during CKTload. When > 0, the model evaluation
* is in a numerical regime (e.g. BSIM-BULK near a singular operating * is in a numerical regime (e.g. BSIM-BULK near a singular operating

View File

@ -95,6 +95,8 @@ enum {
OPT_NORESIDCHECK, /* `.option noresidcheck` opts out of axis-4 dual-norm OPT_NORESIDCHECK, /* `.option noresidcheck` opts out of axis-4 dual-norm
* convergence (residual-vector check); revert to * convergence (residual-vector check); revert to
* SPICE3 solution-only behaviour. */ * SPICE3 solution-only behaviour. */
OPT_NOSTATERESTORE, /* `.option nostaterestore` opts out of the dctran
* failed-attempt device-state restore. */
OPT_EQNS, OPT_EQNS,
OPT_REORDTIME, OPT_REORDTIME,
OPT_METHOD, OPT_METHOD,

View File

@ -72,6 +72,9 @@ struct TSKtask {
unsigned int TSKnoDtClear:1; /* `.option nodtclear` disables the unsigned int TSKnoDtClear:1; /* `.option nodtclear` disables the
* small-dt CKTnoncon clear in * small-dt CKTnoncon clear in
* niiter.c */ * niiter.c */
unsigned int TSKnoStateRestore:1; /* `.option nostaterestore` disables
* the dctran failed-attempt state
* restore (CKTstate0 <- CKTstate1) */
unsigned int TSKtryToCompact:1; /* flag for LTRA lines */ unsigned int TSKtryToCompact:1; /* flag for LTRA lines */
unsigned int TSKbadMos3:1; /* flag for MOS3 models */ unsigned int TSKbadMos3:1; /* flag for MOS3 models */
unsigned int TSKkeepOpInfo:1; /* flag for small signal analyses */ unsigned int TSKkeepOpInfo:1; /* flag for small signal analyses */

View File

@ -411,7 +411,7 @@ NIiter(CKTcircuit *ckt, int maxIter)
ckt->CKTrhsOld[0] = 0; ckt->CKTrhsOld[0] = 0;
/* Newton-step limiter (simulator-side $limit substitute). /* Newton-step limiter (simulator-side $limit substitute).
* Always-on during transient (and DC OP) to clamp per-node * Active during transient (and DC OP) to clamp per-node
* |Δv| to ±CKTabsDv between Newton iterations. Matches * |Δv| to ±CKTabsDv between Newton iterations. Matches
* the default behaviour of Spectre/HSPICE DEVlimvds-style * the default behaviour of Spectre/HSPICE DEVlimvds-style
* limiters which fire on every iteration as a model- * limiters which fire on every iteration as a model-
@ -419,6 +419,17 @@ NIiter(CKTcircuit *ckt, int maxIter)
* 0.5 V) a model-parameter-agnostic tolerance, not a * 0.5 V) a model-parameter-agnostic tolerance, not a
* voltage rail. * voltage rail.
* *
* OSDI circuits only (CKTosdiPresent): the limiter exists
* as a substitute for model-supplied $limit calls that
* OSDI/Verilog-A models may lack. Applying it to every
* circuit regressed non-OSDI decks whose behavioral
* macromodels legitimately need multi-kV Newton steps
* (PSpice opamp libs: TABLE sources swing to ±3.5 kV)
* the clamp forced a crawl whose small |Δx| the axis-4
* residual check then correctly refused, so OP, gmin and
* source stepping all failed. Native SPICE devices carry
* their own pnjlim/limvds limiting and never needed this.
*
* Skipped on iteration 1 (no previous iterate to compare). * Skipped on iteration 1 (no previous iterate to compare).
* Skipped when CKTnodes is NULL (matrix not yet built). * Skipped when CKTnodes is NULL (matrix not yet built).
* *
@ -426,7 +437,7 @@ NIiter(CKTcircuit *ckt, int maxIter)
* $limit synthesis pass, the model's own limiters apply * $limit synthesis pass, the model's own limiters apply
* inside descr->eval() this simulator-side limiter then * inside descr->eval() this simulator-side limiter then
* sees already-limited Δv and does nothing additional. */ * sees already-limited Δv and does nothing additional. */
if (iterno > 1 && ckt->CKTnodes != NULL) { if (ckt->CKTosdiPresent && iterno > 1 && ckt->CKTnodes != NULL) {
double dv_max = (ckt->CKTabsDv > 0) ? ckt->CKTabsDv : 0.5; double dv_max = (ckt->CKTabsDv > 0) ? ckt->CKTabsDv : 0.5;
/* Compute current iteration's max|Δv|. Needed both /* Compute current iteration's max|Δv|. Needed both
* for the Stage A scalar scaling and for the Stage B * for the Stage A scalar scaling and for the Stage B

View File

@ -319,6 +319,11 @@ int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt,
OsdiSimParas sim_params_ = get_simparams(ckt); OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_; OsdiSimParas *sim_params = &sim_params_;
/* Mark the circuit as containing OSDI devices — gates OSDI-motivated
* Newton machinery (per-node Δv limiter in NIiter) so purely native
* circuits keep stock iteration behaviour. */
ckt->CKTosdiPresent = 1;
/* setup a temporary buffer */ /* setup a temporary buffer */
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes); uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);

View File

@ -100,6 +100,7 @@ CKTdoJob(CKTcircuit* ckt, int reset, TSKtask* task)
ckt->CKTresidCheckDisabled = task->TSKnoResidCheck; ckt->CKTresidCheckDisabled = task->TSKnoResidCheck;
ckt->CKTosdiStepRejectOff = task->TSKnoOsdiStepReject; ckt->CKTosdiStepRejectOff = task->TSKnoOsdiStepReject;
ckt->CKTdtClearOff = task->TSKnoDtClear; ckt->CKTdtClearOff = task->TSKnoDtClear;
ckt->CKTstateRestoreOff = task->TSKnoStateRestore;
ckt->CKTosdiVlim = task->TSKosdiVlim; ckt->CKTosdiVlim = task->TSKosdiVlim;
ckt->CKTosdiVlimVds = task->TSKosdiVlimVds; ckt->CKTosdiVlimVds = task->TSKosdiVlimVds;
ckt->CKTosdiVlimVgs = task->TSKosdiVlimVgs; ckt->CKTosdiVlimVgs = task->TSKosdiVlimVgs;

View File

@ -50,6 +50,9 @@ CKTsetOpt(CKTcircuit *ckt, JOB *anal, int opt, IFvalue *val)
case OPT_NODTCLEAR: case OPT_NODTCLEAR:
task->TSKnoDtClear = (val->iValue != 0); task->TSKnoDtClear = (val->iValue != 0);
break; break;
case OPT_NOSTATERESTORE:
task->TSKnoStateRestore = (val->iValue != 0);
break;
case OPT_GMIN: case OPT_GMIN:
task->TSKgmin = val->rValue; task->TSKgmin = val->rValue;
break; break;
@ -300,6 +303,7 @@ static IFparm OPTtbl[] = {
{ "noresidcheck", OPT_NORESIDCHECK, IF_SET|IF_FLAG, "Disable axis-4 residual-vector convergence check; SPICE3-only behaviour" }, { "noresidcheck", OPT_NORESIDCHECK, IF_SET|IF_FLAG, "Disable axis-4 residual-vector convergence check; SPICE3-only behaviour" },
{ "noosdistepreject", OPT_NOOSDISTEPREJECT, IF_SET|IF_FLAG, "Disable OSDI axis-3 step rejection (osdiload.c sanitize_jacobian + REJECT_STEP)" }, { "noosdistepreject", OPT_NOOSDISTEPREJECT, IF_SET|IF_FLAG, "Disable OSDI axis-3 step rejection (osdiload.c sanitize_jacobian + REJECT_STEP)" },
{ "nodtclear", OPT_NODTCLEAR, IF_SET|IF_FLAG, "Disable small-dt CKTnoncon clear (niiter.c); revert to abort-from-spurious-noncon at dt<1ps" }, { "nodtclear", OPT_NODTCLEAR, IF_SET|IF_FLAG, "Disable small-dt CKTnoncon clear (niiter.c); revert to abort-from-spurious-noncon at dt<1ps" },
{ "nostaterestore", OPT_NOSTATERESTORE, IF_SET|IF_FLAG, "Disable dctran failed-attempt device-state restore (CKTstate0 <- CKTstate1 on retry)" },
{ "gmin", OPT_GMIN,IF_SET|IF_REAL,"Minimum conductance" }, { "gmin", OPT_GMIN,IF_SET|IF_REAL,"Minimum conductance" },
{ "gshunt", OPT_GSHUNT,IF_SET|IF_REAL,"Shunt conductance" }, { "gshunt", OPT_GSHUNT,IF_SET|IF_REAL,"Shunt conductance" },
{ "reltol", OPT_RELTOL,IF_SET|IF_REAL ,"Relative error tolerence"}, { "reltol", OPT_RELTOL,IF_SET|IF_REAL ,"Relative error tolerence"},

View File

@ -773,8 +773,14 @@ resume:
* retries) until the state explodes and dt collapses to * retries) until the state explodes and dt collapses to
* delmin. Re-prime the working state from the last ACCEPTED * delmin. Re-prime the working state from the last ACCEPTED
* point so every retry starts from physical values, exactly * point so every retry starts from physical values, exactly
* like a first attempt does. */ * like a first attempt does.
if (ckt->CKTstate0 && ckt->CKTstate1) *
* Opt-out via `.option nostaterestore` (suspected of
* regressing non-OSDI example decks on raw-transient-start
* paths uic / optran / mid-run retries where CKTstate1
* may not hold a meaningfully accepted point). */
if (!ckt->CKTstateRestoreOff &&
ckt->CKTstate0 && ckt->CKTstate1)
memcpy(ckt->CKTstate0, ckt->CKTstate1, memcpy(ckt->CKTstate0, ckt->CKTstate1,
(size_t) ckt->CKTnumStates * sizeof(double)); (size_t) ckt->CKTnumStates * sizeof(double));

View File

@ -30,6 +30,10 @@ INPevaluate(char **line, int *error, int gobble)
char *tmpline; char *tmpline;
/* setup */ /* setup */
/* Clear the bare-.param side channel on every entry, as its
* consumers (INPdevParse, com_meas) assume a non-NULL value must
* only ever mean THIS call resolved a bare identifier. */
inpeval_last_param_name = NULL;
tmpline = *line; tmpline = *line;
if (gobble) { if (gobble) {