There was exactly one place in V3Task, handling DPI arguments when we relied on cleanOut of AstCExpr being false for masking. Made that code do the relevant masking via a few new run-time functions, which also eliminates some special cases in the relevant V3Task functions.
This commit is contained in:
parent
4ef6f00423
commit
61c64e4a3b
|
|
@ -42,10 +42,18 @@ static inline void VL_SET_W_SVBV(int obits, WDataOutP owp, const svBitVecVal* lw
|
|||
for (int i = 0; i < words - 1; ++i) owp[i] = lwp[i];
|
||||
owp[words - 1] = lwp[words - 1] & VL_MASK_I(obits);
|
||||
}
|
||||
static inline QData VL_SET_Q_SVBV(const svBitVecVal* lwp) VL_MT_SAFE {
|
||||
return VL_SET_QII(lwp[1], lwp[0]);
|
||||
static inline void VL_SET_Q_SVBV(int obits, QData& out, const svBitVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_Q(obits) & VL_SET_QII(lwp[1], lwp[0]);
|
||||
}
|
||||
static inline void VL_SET_I_SVBV(int obits, IData& out, const svBitVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0];
|
||||
}
|
||||
static inline void VL_SET_S_SVBV(int obits, SData& out, const svBitVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0];
|
||||
}
|
||||
static inline void VL_SET_C_SVBV(int obits, CData& out, const svBitVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0];
|
||||
}
|
||||
static inline IData VL_SET_I_SVBV(const svBitVecVal* lwp) VL_MT_SAFE { return lwp[0]; }
|
||||
|
||||
// Convert Verilator internal data to svBitVecVal
|
||||
static inline void VL_SET_SVBV_W(int obits, svBitVecVal* owp, const WDataInP lwp) VL_MT_SAFE {
|
||||
|
|
@ -65,10 +73,18 @@ static inline void VL_SET_W_SVLV(int obits, WDataOutP owp, const svLogicVecVal*
|
|||
for (int i = 0; i < words - 1; ++i) owp[i] = lwp[i].aval;
|
||||
owp[words - 1] = lwp[words - 1].aval & VL_MASK_I(obits);
|
||||
}
|
||||
static inline QData VL_SET_Q_SVLV(const svLogicVecVal* lwp) VL_MT_SAFE {
|
||||
return VL_SET_QII(lwp[1].aval, lwp[0].aval);
|
||||
static inline void VL_SET_Q_SVLV(int obits, QData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_Q(obits) & VL_SET_QII(lwp[1].aval, lwp[0].aval);
|
||||
}
|
||||
static inline void VL_SET_I_SVLV(int obits, IData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0].aval;
|
||||
}
|
||||
static inline void VL_SET_S_SVLV(int obits, SData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0].aval;
|
||||
}
|
||||
static inline void VL_SET_C_SVLV(int obits, CData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
|
||||
out = VL_MASK_I(obits) & lwp[0].aval;
|
||||
}
|
||||
static inline IData VL_SET_I_SVLV(const svLogicVecVal* lwp) VL_MT_SAFE { return lwp[0].aval; }
|
||||
|
||||
// Convert Verilator internal data to svLogicVecVal
|
||||
// Note these functions never create X/Z in svLogicVecVal
|
||||
|
|
|
|||
|
|
@ -159,9 +159,8 @@ AstCStmt::AstCStmt(FileLine* fl, const string& textStmt)
|
|||
addExprsp(new AstText{fl, textStmt, true});
|
||||
}
|
||||
|
||||
AstCExpr::AstCExpr(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut)
|
||||
AstCExpr::AstCExpr(FileLine* fl, const string& textStmt, int setwidth)
|
||||
: ASTGEN_SUPER_CExpr(fl)
|
||||
, m_cleanOut{cleanOut}
|
||||
, m_pure{true} {
|
||||
addExprsp(new AstText{fl, textStmt, true});
|
||||
if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED);
|
||||
|
|
|
|||
|
|
@ -564,22 +564,20 @@ public:
|
|||
};
|
||||
class AstCExpr final : public AstNodeExpr {
|
||||
// @astgen op1 := exprsp : List[AstNode] // Expressions to print
|
||||
const bool m_cleanOut;
|
||||
bool m_pure; // Pure optimizable
|
||||
public:
|
||||
// Emit C textual expr function (like AstUCFunc)
|
||||
AstCExpr(FileLine* fl, AstNode* exprsp)
|
||||
: ASTGEN_SUPER_CExpr(fl)
|
||||
, m_cleanOut{true}
|
||||
, m_pure{false} {
|
||||
addExprsp(exprsp);
|
||||
dtypeFrom(exprsp);
|
||||
}
|
||||
inline AstCExpr(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut = true);
|
||||
inline AstCExpr(FileLine* fl, const string& textStmt, int setwidth);
|
||||
ASTGEN_MEMBERS_AstCExpr;
|
||||
bool isGateOptimizable() const override { return m_pure; }
|
||||
bool isPredictOptimizable() const override { return m_pure; }
|
||||
bool cleanOut() const override { return m_cleanOut; }
|
||||
bool cleanOut() const override { return true; }
|
||||
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
|
||||
string emitC() override { V3ERROR_NA_RETURN(""); }
|
||||
bool sameNode(const AstNode* /*samep*/) const override { return true; }
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
|
|||
subFuncp->slow(false);
|
||||
FileLine* const flp = procp->fileline();
|
||||
AstNodeExpr* const condp = new AstCExpr{
|
||||
flp, "VL_LIKELY(!vlSymsp->_vm_contextp__->gotFinish())", 1, true};
|
||||
flp, "VL_LIKELY(!vlSymsp->_vm_contextp__->gotFinish())", 1};
|
||||
AstLoop* const loopp = new AstLoop{flp};
|
||||
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
|
||||
loopp->addStmtsp(bodyp);
|
||||
|
|
|
|||
215
src/V3Task.cpp
215
src/V3Task.cpp
|
|
@ -289,46 +289,50 @@ public:
|
|||
// DPI related utility functions
|
||||
|
||||
struct TaskDpiUtils final {
|
||||
static std::vector<std::pair<AstUnpackArrayDType*, int>>
|
||||
unpackDimsAndStrides(AstNodeDType* dtypep) {
|
||||
std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides;
|
||||
if (AstUnpackArrayDType* const unpackp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType)) {
|
||||
const std::vector<AstUnpackArrayDType*> dims = unpackp->unpackDimensions();
|
||||
dimStrides.resize(dims.size(), {nullptr, 0});
|
||||
dimStrides.back() = {dims.back(), 1};
|
||||
for (ssize_t i = dims.size() - 2; i >= 0; --i) {
|
||||
dimStrides[i].first = dims[i];
|
||||
dimStrides[i].second = dimStrides[i + 1].second * dims[i + 1]->elementsConst();
|
||||
}
|
||||
// Returns a vector of ('elements', 'stride') pairs for each unpacked dimension
|
||||
static std::vector<std::pair<int, int>> unpackDimsAndStrides(AstVar* varp) {
|
||||
AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
|
||||
std::vector<std::pair<int, int>> dimStrides;
|
||||
AstUnpackArrayDType* const unpackp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType);
|
||||
if (!unpackp) return dimStrides;
|
||||
|
||||
const std::vector<AstUnpackArrayDType*> dims = unpackp->unpackDimensions();
|
||||
const size_t nDims = dims.size();
|
||||
dimStrides.resize(nDims);
|
||||
// Stride of fastest varying dimension is 1.
|
||||
dimStrides[nDims - 1].first = dims.back()->elementsConst();
|
||||
dimStrides[nDims - 1].second = 1;
|
||||
// Rest are densly packed
|
||||
for (ssize_t i = nDims - 2; i >= 0; --i) {
|
||||
dimStrides[i].first = dims[i]->elementsConst();
|
||||
dimStrides[i].second = dimStrides[i + 1].first * dimStrides[i + 1].second;
|
||||
}
|
||||
return dimStrides;
|
||||
}
|
||||
static bool dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt,
|
||||
string& ket) {
|
||||
ket.clear();
|
||||
if (portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::CHANDLE) {
|
||||
frstmt = "VL_CVT_VP_Q(" + frName;
|
||||
ket = ")";
|
||||
} else if (portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::STRING) {
|
||||
frstmt = "VL_CVT_N_CSTR(" + frName;
|
||||
ket = ")";
|
||||
} else if ((portp->basicp() && portp->basicp()->isDpiPrimitive())) {
|
||||
frstmt = frName;
|
||||
} else {
|
||||
const string frSvType = portp->basicp()->isDpiBitVec() ? "SVBV" : "SVLV";
|
||||
if (portp->isWide()) {
|
||||
// Need to convert to wide, using special function
|
||||
frstmt = "VL_SET_W_" + frSvType + "(" + cvtToStr(portp->width()) + ", ";
|
||||
return true;
|
||||
} else {
|
||||
const AstNodeDType* const dtypep = portp->dtypep()->skipRefp();
|
||||
frstmt = "VL_SET_" + string{dtypep->charIQWN()} + "_" + frSvType + "(";
|
||||
if (VN_IS(dtypep, UnpackArrayDType)) frstmt += "&";
|
||||
frstmt += frName;
|
||||
ket = ")";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// Returns the prefix of a function-call like statement used to convert
|
||||
// from IEEE DPI data types to internal types, and a bool that is true
|
||||
// if type uses svBitVecVal/svLogicVecVal and ther result is returned via
|
||||
// an output parameter, o false if uses C primitive type and result is the
|
||||
// return value.
|
||||
static std::pair<std::string, bool> dpiToInternalCvtStmt(AstVar* varp) {
|
||||
AstBasicDType* const basicp = varp->basicp();
|
||||
|
||||
// DPI types using Primitive C types
|
||||
if (basicp->keyword() == VBasicDTypeKwd::CHANDLE) return {"VL_CVT_VP_Q(", false};
|
||||
if (basicp->keyword() == VBasicDTypeKwd::STRING) return {"VL_CVT_N_CSTR(", false};
|
||||
if (basicp->isDpiPrimitive()) return {"(", false};
|
||||
|
||||
// DPI types using svBitVecVal/svLogicVecVal
|
||||
UASSERT_OBJ(basicp->isDpiBitVec() || basicp->isDpiLogicVec(), varp,
|
||||
"Should use svBitVecVal/svLogicVecVal");
|
||||
|
||||
const std::string vecType = basicp->isDpiBitVec() ? "SVBV" : "SVLV";
|
||||
const AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
|
||||
const char sizeChar = dtypep->width() <= 8 ? 'C'
|
||||
: dtypep->width() <= 16 ? 'S'
|
||||
: *dtypep->charIQWN();
|
||||
const std::string& size = std::to_string(dtypep->width());
|
||||
return {"VL_SET_"s + sizeChar + "_" + vecType + "(" + size + ", ", true};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -673,8 +677,8 @@ class TaskVisitor final : public VNVisitor {
|
|||
UASSERT_OBJ(snp, refp, "Missing scoping context");
|
||||
ccallp->addArgsp(snp);
|
||||
// __Vfilenamep
|
||||
ccallp->addArgsp(new AstCExpr{
|
||||
refp->fileline(), "\"" + refp->fileline()->filenameEsc() + "\"", 64, true});
|
||||
ccallp->addArgsp(
|
||||
new AstCExpr{refp->fileline(), "\"" + refp->fileline()->filenameEsc() + "\"", 64});
|
||||
// __Vlineno
|
||||
ccallp->addArgsp(new AstConst(refp->fileline(), refp->fileline()->lineno()));
|
||||
}
|
||||
|
|
@ -770,67 +774,48 @@ class TaskVisitor final : public VNVisitor {
|
|||
return new AstCStmt{portp->fileline(), stmt};
|
||||
}
|
||||
|
||||
AstNode* createAssignDpiToInternal(AstVarScope* portvscp, const string& frName) {
|
||||
AstNodeStmt* createAssignDpiToInternal(AstVarScope* vscp, const std::string& rhsName) {
|
||||
// Create assignment from DPI temporary into internal format
|
||||
// DPI temporary is scalar or 1D array (if unpacked array)
|
||||
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
|
||||
AstVar* const portp = portvscp->varp();
|
||||
string frstmt;
|
||||
string ket;
|
||||
const bool useSetWSvlv = TaskDpiUtils::dpiToInternalFrStmt(portp, frName, frstmt, ket);
|
||||
// Use a AstCExpr, as we want V3Clean to mask off bits that don't make sense.
|
||||
int cwidth = VL_IDATASIZE;
|
||||
if (!useSetWSvlv && portp->basicp()) {
|
||||
if (portp->basicp()->keyword().isBitLogic()) {
|
||||
cwidth = VL_EDATASIZE * portp->widthWords();
|
||||
} else {
|
||||
cwidth = portp->basicp()->keyword().width();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides
|
||||
= TaskDpiUtils::unpackDimsAndStrides(portp->dtypep());
|
||||
const int total = dimStrides.empty() ? 1
|
||||
: dimStrides.front().first->elementsConst()
|
||||
* dimStrides.front().second;
|
||||
AstNode* newp = nullptr;
|
||||
const int widthWords = portp->basicp()->widthWords();
|
||||
AstVar* const varp = vscp->varp();
|
||||
FileLine* const flp = vscp->fileline();
|
||||
std::string cvt;
|
||||
bool useSvVec;
|
||||
std::tie(cvt, useSvVec) = TaskDpiUtils::dpiToInternalCvtStmt(varp);
|
||||
const std::vector<std::pair<int, int>> strides = TaskDpiUtils::unpackDimsAndStrides(varp);
|
||||
// Total number of elements in unpacked array
|
||||
const int total = strides.empty() ? 1 : strides[0].first * strides[0].second;
|
||||
// Number of words per element/primitive type
|
||||
const int widthWords = varp->basicp()->widthWords();
|
||||
// Number of bits in the C expression result, for masking.
|
||||
const int cwidth = widthWords * VL_EDATASIZE;
|
||||
// The resulting list of statements
|
||||
AstNodeStmt* stmtsp = nullptr;
|
||||
for (int i = 0; i < total; ++i) {
|
||||
AstNodeExpr* srcp = new AstVarRef{portvscp->fileline(), portvscp, VAccess::WRITE};
|
||||
// extract a scalar from multi-dimensional array (internal format)
|
||||
for (auto&& dimStride : dimStrides) {
|
||||
const size_t dimIdx = (i / dimStride.second) % dimStride.first->elementsConst();
|
||||
srcp = new AstArraySel(portvscp->fileline(), srcp, dimIdx);
|
||||
AstNodeExpr* lhsp = new AstVarRef{flp, vscp, VAccess::WRITE};
|
||||
|
||||
// Extract a scalar from multi-dimensional array (internal format)
|
||||
for (const auto& stride : strides) {
|
||||
lhsp = new AstArraySel(flp, lhsp, (i / stride.second) % stride.first);
|
||||
}
|
||||
AstNode* stmtp = nullptr;
|
||||
// extract a scalar from DPI temporary var that is scalar or 1D array
|
||||
if (useSetWSvlv) {
|
||||
AstNode* const linesp = new AstText{portvscp->fileline(), frstmt + ket};
|
||||
linesp->addNext(srcp);
|
||||
linesp->addNext(
|
||||
new AstText{portvscp->fileline(),
|
||||
", " + frName + " + " + cvtToStr(i * widthWords) + ");"});
|
||||
stmtp = new AstCStmt{portvscp->fileline(), linesp};
|
||||
|
||||
// Extract a scalar from DPI temporary var that is scalar or 1D array
|
||||
if (useSvVec) {
|
||||
const std::string offset = std::to_string(i * widthWords);
|
||||
AstCStmt* const cstmtp = new AstCStmt{flp, nullptr};
|
||||
cstmtp->addExprsp(new AstText{flp, cvt});
|
||||
cstmtp->addExprsp(lhsp);
|
||||
cstmtp->addExprsp(new AstText{flp, ", " + rhsName + " + " + offset + ");"});
|
||||
stmtsp = AstNode::addNext(stmtsp, cstmtp);
|
||||
} else {
|
||||
string from = frstmt;
|
||||
if (!dimStrides.empty()) {
|
||||
// e.g. time is 64bit svLogicVector
|
||||
const int coef = portp->basicp()->isDpiLogicVec() ? widthWords : 1;
|
||||
from += "[" + cvtToStr(i * coef) + "]";
|
||||
}
|
||||
from += ket;
|
||||
AstNodeExpr* const rhsp = new AstSel{
|
||||
portp->fileline(), new AstCExpr{portp->fileline(), from, cwidth, false}, 0,
|
||||
portp->width()};
|
||||
stmtp = new AstAssign{portp->fileline(), srcp, rhsp};
|
||||
}
|
||||
if (i > 0) {
|
||||
newp->addNext(stmtp);
|
||||
} else {
|
||||
newp = stmtp;
|
||||
const std::string elem = strides.empty() ? "" : "[" + std::to_string(i) + "]";
|
||||
AstNodeExpr* rhsp = new AstCExpr{flp, cvt + rhsName + elem + ")", cwidth};
|
||||
rhsp = new AstSel{flp, rhsp, 0, varp->width()};
|
||||
stmtsp = AstNode::addNext(stmtsp, new AstAssign{flp, lhsp, rhsp});
|
||||
}
|
||||
}
|
||||
return newp;
|
||||
return stmtsp;
|
||||
}
|
||||
|
||||
AstCFunc* makeDpiExportDispatcher(AstNodeFTask* nodep, AstVar* rtnvarp) {
|
||||
|
|
@ -2024,42 +2009,30 @@ string V3Task::assignInternalToDpi(AstVar* portp, bool isPtr, const string& frSu
|
|||
return stmt;
|
||||
}
|
||||
|
||||
string V3Task::assignDpiToInternal(const string& lhsName, AstVar* varp) {
|
||||
// Create assignment from DPI temporary into internal format
|
||||
// DPI temporary is scalar or 1D array (if unpacked array)
|
||||
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
|
||||
const string frName = varp->name();
|
||||
string frstmt;
|
||||
string ket;
|
||||
const bool useSetWSvlv = TaskDpiUtils::dpiToInternalFrStmt(varp, frName, frstmt, ket);
|
||||
|
||||
const std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides
|
||||
= TaskDpiUtils::unpackDimsAndStrides(varp->dtypep());
|
||||
const int total = dimStrides.empty()
|
||||
? 1
|
||||
: dimStrides.front().first->elementsConst() * dimStrides.front().second;
|
||||
// Create assignment from DPI temporary into internal format
|
||||
// DPI temporary is scalar or 1D array (if unpacked array)
|
||||
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
|
||||
std::string V3Task::assignDpiToInternal(const std::string& lhsName, AstVar* varp) {
|
||||
std::string cvt;
|
||||
bool useSvVec;
|
||||
std::tie(cvt, useSvVec) = TaskDpiUtils::dpiToInternalCvtStmt(varp);
|
||||
const std::vector<std::pair<int, int>> strides = TaskDpiUtils::unpackDimsAndStrides(varp);
|
||||
const int total = strides.empty() ? 1 : strides[0].first * strides[0].second;
|
||||
const int widthWords = varp->basicp()->widthWords();
|
||||
string statements;
|
||||
for (int i = 0; i < total; ++i) {
|
||||
string lhs = lhsName;
|
||||
std::string lhs = lhsName;
|
||||
// extract a scalar from multi-dimensional array (internal format)
|
||||
for (auto&& dimStride : dimStrides) {
|
||||
const size_t dimIdx = (i / dimStride.second) % dimStride.first->elementsConst();
|
||||
lhs += "[" + cvtToStr(dimIdx) + "]";
|
||||
for (const auto& stride : strides) {
|
||||
lhs += "[" + std::to_string((i / stride.second) % stride.first) + "]";
|
||||
}
|
||||
// extract a scalar from DPI temporary var that is scalar or 1D array
|
||||
if (useSetWSvlv) {
|
||||
statements += frstmt + ket + " " + lhs + ", " + frName + " + "
|
||||
+ cvtToStr(i * widthWords) + ");\n";
|
||||
if (useSvVec) {
|
||||
const std::string offset = std::to_string(i * widthWords);
|
||||
statements += cvt + " " + lhs + ", " + varp->name() + " + " + offset + ");\n";
|
||||
} else {
|
||||
string rhs = frstmt;
|
||||
if (!dimStrides.empty()) {
|
||||
// e.g. time is 64bit svLogicVector
|
||||
const int coef = varp->basicp()->isDpiLogicVec() ? widthWords : 1;
|
||||
rhs += "[" + cvtToStr(i * coef) + "]";
|
||||
}
|
||||
rhs += ket;
|
||||
statements += lhs + " = " + rhs + ";\n";
|
||||
const std::string elem = strides.empty() ? "" : "[" + std::to_string(i) + "]";
|
||||
statements += lhs + " = " + cvt + varp->name() + elem + ")" + ";\n";
|
||||
}
|
||||
}
|
||||
return statements;
|
||||
|
|
|
|||
|
|
@ -1186,7 +1186,7 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
if (constp->isZero()) {
|
||||
// We have to await forever instead of simply returning in case we're deep in a
|
||||
// callstack
|
||||
AstCExpr* const foreverp = new AstCExpr{flp, "VlForever{}", 0, true};
|
||||
AstCExpr* const foreverp = new AstCExpr{flp, "VlForever{}", 0};
|
||||
foreverp->dtypeSetVoid(); // TODO: this is sloppy but harmless
|
||||
AstCAwait* const awaitp = new AstCAwait{flp, foreverp};
|
||||
awaitp->dtypeSetVoid();
|
||||
|
|
|
|||
|
|
@ -6708,7 +6708,7 @@ class WidthVisitor final : public VNVisitor {
|
|||
classp->baseMostClassp()->needRNG(true);
|
||||
v3Global.useRandomizeMethods(true);
|
||||
AstCExpr* const newp
|
||||
= new AstCExpr{nodep->fileline(), "__Vm_rng.get_randstate()", 1, true};
|
||||
= new AstCExpr{nodep->fileline(), "__Vm_rng.get_randstate()", 1};
|
||||
newp->dtypeSetString();
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
|
|
@ -6721,7 +6721,7 @@ class WidthVisitor final : public VNVisitor {
|
|||
classp->baseMostClassp()->needRNG(true);
|
||||
v3Global.useRandomizeMethods(true);
|
||||
AstCExpr* const newp
|
||||
= new AstCExpr{nodep->fileline(), "__Vm_rng.set_randstate(", 1, true};
|
||||
= new AstCExpr{nodep->fileline(), "__Vm_rng.set_randstate(", 1};
|
||||
newp->addExprsp(exprp->unlinkFrBack());
|
||||
newp->addExprsp(new AstText{nodep->fileline(), ")", true});
|
||||
newp->dtypeSetString();
|
||||
|
|
|
|||
Loading…
Reference in New Issue