From 6b38d4a59e3437e475b3a438e0d4f2f37518257f Mon Sep 17 00:00:00 2001 From: Mouse Date: Thu, 4 Apr 2024 15:38:53 +0000 Subject: [PATCH] Deparser: write only (possibly-)changed header fields Leverage compiler to write only (possibly-)changed header fields in deparser instead of always rewriting all of them --- backends/tc/backend.cpp | 426 +++++++++++++++++++++++++++- backends/tc/ebpfCodeGen.cpp | 165 ++++++----- frontends/common/parser_options.cpp | 6 +- ir/dump.cpp | 8 +- 4 files changed, 530 insertions(+), 75 deletions(-) diff --git a/backends/tc/backend.cpp b/backends/tc/backend.cpp index 159fc8098e2..14ca62f95c1 100644 --- a/backends/tc/backend.cpp +++ b/backends/tc/backend.cpp @@ -20,6 +20,7 @@ and limitations under the License. #include "backends/ebpf/ebpfOptions.h" #include "backends/ebpf/target.h" +#include "ir/dump.h" namespace TC { @@ -37,10 +38,421 @@ const cstring pnaParserMeta = "pna_main_parser_input_metadata_t"; const cstring pnaInputMeta = "pna_main_input_metadata_t"; const cstring pnaOutputMeta = "pna_main_output_metadata_t"; +typedef struct mchain MCHAIN; +struct mchain { + const MCHAIN *ulink; // towards root + const MCHAIN *dlink; // towards leaf + const IR::Type_StructLike *str; + int fieldno; +}; + +// This probably should be a class, with kids being a std::vector. +// I'm not good enough at C++ to get that right. +typedef enum { W_LEAF, W_NODE } WKIND; +typedef struct written WRITTEN; +struct written { + WKIND kind; + union { + // nothing if W_LEAF; presence is enough + struct { // if W_NODE + int n; + WRITTEN **v; + } node; + }; +}; + +static WRITTEN *written_leaf() { + WRITTEN *w; + + w = new WRITTEN; + w->kind = W_LEAF; + return (w); +} + +static WRITTEN *written_node(int nkids) { + WRITTEN *w; + int i; + + w = new WRITTEN; + w->kind = W_NODE; + w->node.n = nkids; + w->node.v = new WRITTEN *[nkids]; + for (i = nkids - 1; i >= 0; i--) w->node.v[i] = 0; + return (w); +} + +/* + * XXX + * + * record should not be static. But I'm not sure how else to get + * something generated by the tree walk and readable by the deparser + * code generator. It probably should be attached to the header type + * somehow. "First make it work...". + */ +class setting_optimizer : public Inspector { + public: + bool preorder(const IR::P4Control *); + void postorder(const IR::P4Control *); + bool preorder(const IR::AssignmentStatement *); + P4::TypeMap *typeMap; + setting_optimizer(P4::TypeMap *m) : typeMap(m), incontrol(0) {} + static WRITTEN *record; + static const IR::Type_StructLike *par_type; + static const P4::TypeMap *type_map; + + private: + int incontrol; +}; +WRITTEN *setting_optimizer::record = 0; +const IR::Type_StructLike *setting_optimizer::par_type = 0; +const P4::TypeMap *setting_optimizer::type_map = 0; + +static void indent(int n) { + int i; + + for (i = n; i > 0; i--) std::cout << ' '; +} + +static void gen_member_chain(const IR::Expression *e, const cstring *mem, + void (*fn)(void *, const MCHAIN *), void *arg, int depth = 0, + MCHAIN *below = 0) { + MCHAIN mc; + int i; + + indent(depth); + std::cout << "gen_member_chain\n"; + indent(depth + 2); + std::cout << "expr (" << e << ") [" << e->node_type_name() + << "] = " << static_cast(e) << '\n'; + indent(depth + 2); + std::cout << "member = " << *mem << '\n'; + indent(depth + 2); + std::cout << "below = " << static_cast(below) << '\n'; + mc.str = e->type->to(); + if (!mc.str) { + std::cout << "expr isn't struct-like" << std::endl; + abort(); + } + mc.fieldno = -1; + for (i = mc.str->fields.size() - 1; i >= 0; i--) { + auto f = mc.str->fields[i]; + if (f->name.name == *mem) { + mc.fieldno = i; + break; + } + } + if (mc.fieldno < 0) { + std::cout << "Can't find field in StructLike" << std::endl; + abort(); + } + mc.dlink = below; + mc.ulink = 0; + if (below) below->ulink = &mc; + indent(depth + 2); + std::cout << "found field (" << mc.fieldno << "), recursing\n"; + if (e->is()) { + const IR::Member *m; + m = e->to(); + if (!m) { + std::cout << "Expr is member but can't convert" << std::endl; + abort(); + } + indent(depth + 2); + std::cout << "expr is Member, recursing\n"; + gen_member_chain(m->expr, &m->member.name, fn, arg, depth + 4, &mc); + return; + } + indent(depth + 2); + std::cout << "root (not Member)\n"; + std::cout << "chain:\n"; + { + const MCHAIN *m; + for (m = &mc; m; m = m->dlink) { + std::cout << " " << static_cast(m) + << " u=" << static_cast(m->ulink) + << " d=" << static_cast(m->dlink) << " f=" << m->fieldno + << '\n'; + } + } + (*fn)(arg, &mc); +} + +typedef struct rwrv RWRV; +struct rwrv { + const IR::Type_StructLike *sl; + WRITTEN **w; +}; + +/* + * The return statements would normally specify constructor + * expressions, like + * + * return((RWRV){.sl=f->type->to(),.w=&rv.w[0]->kids[i]}); + * + * But that draws an error about how "C++ designated initializers only + * available with '-std=c++20' or '-std=gnu++20'". So much for C++ + * being a "better C". :-รพ So we have to declare a variable, assign + * to its fields, and then return it. Ugh. + */ +static RWRV record_write_core(const IR::Member *m, int depth) { + RWRV rv; + + indent(depth); + std::cout << "record_write_core: member " << m << ", depth = " << depth << '\n'; + auto e = m->expr; + if (e->is()) { + rv = record_write_core(e->to(), depth + 4); + } else { + rv.sl = setting_optimizer::par_type; + rv.w = &setting_optimizer::record; + } + int sz = rv.sl->fields.size(); + int i; + for (i = 0; i < sz; i++) { + auto f = rv.sl->fields[i]; + if (f->name.name == m->member.name) { + indent(depth); + std::cout << "field [" << i << "]\n"; + if (!rv.w[0]) rv.w[0] = written_node(sz); + // See function comment header + rv.sl = f->type->to(); + rv.w = &rv.w[0]->node.v[i]; + return (rv); + } + } + abort(); +} + +static void do_record_write(void *arg __attribute__((__unused__)), const MCHAIN *ch) { + WRITTEN **w; + + std::cout << "do_record_write:"; + for (const MCHAIN *t = ch; t; t = t->dlink) { + std::cout << " [field " << t->fieldno << '/' << t->str->fields.size() << ']'; + } + std::cout << '\n'; + w = &setting_optimizer::record; + for (const MCHAIN *t = ch; t; t = t->dlink) { + std::cout << "top of loop: t = " << static_cast(t) + << ", w = " << static_cast(w) << ", *w = " << static_cast(*w) + << '\n'; + if (!*w) { + std::cout << "not leaf, no node\n"; + *w = written_node(t->str->fields.size()); + } else { + std::cout << "not leaf, node exists\n"; + if (w[0]->kind != W_NODE) { + std::cout << std::flush; + abort(); + } + if (static_cast(t->str->fields.size()) != w[0]->node.n) { + std::cout << std::flush; + abort(); + } + } + std::cout << "non-leaf: w = " << static_cast(w) + << ", *w = " << static_cast(*w) << '\n'; + w = &w[0]->node.v[t->fieldno]; + std::cout << " updated: w = " << static_cast(w) + << ", *w = " << static_cast(*w) << '\n'; + } + std::cout << " leaf: w = " << static_cast(w) << ", *w = " << static_cast(*w) + << '\n'; + if (!*w) + *w = written_leaf(); + else if (w[0]->kind != W_LEAF) + abort(); + std::cout << "updated: w = " << static_cast(w) << ", *w = " << static_cast(*w) + << '\n'; +} + +static void record_write(const IR::Member *m) { + gen_member_chain(m->expr, &m->member.name, &do_record_write, 0); +} + +static void dump_record(WRITTEN *w = setting_optimizer::record) { + int i; + + if (!w) { + std::cout << '-'; + return; + } + switch (w->kind) { + case W_LEAF: + std::cout << '*'; + break; + case W_NODE: + std::cout << '['; + for (i = 0; i < w->node.n; i++) { + if (i) std::cout << ' '; + dump_record(w->node.v[i]); + } + std::cout << ']'; + break; + default: + abort(); + break; + } +} + +typedef struct w2w W2W; +struct w2w { + int *wantp; + cstring &lastfield; +}; + +static void set_want(void *arg, const MCHAIN *ch) { + WRITTEN *w; + + std::cout << "set_want:"; + for (const MCHAIN *t = ch; t; t = t->dlink) { + std::cout << ' ' << t->fieldno; + } + std::cout << '\n'; + w = setting_optimizer::record; + for (const MCHAIN *t = ch; t; t = t->dlink) { + if (!w) { + std::cout << "w is nil, returning 0\n"; + *(int *)arg = 0; + return; + } + if (w->kind != W_NODE) { + std::cout << "not end of chain, but type isn't node" << std::endl; + abort(); + } + if (static_cast(t->str->fields.size()) != w->node.n) { + std::cout << "field count mismatch" << std::endl; + abort(); + } + if ((t->fieldno < 0) || (t->fieldno >= w->node.n)) { + std::cout << "field number " << t->fieldno << "out of range [0.." << w->node.n << ")" + << std::endl; + abort(); + } + w = w->node.v[t->fieldno]; + } + std::cout << "returning " << (w ? 1 : 0) << '\n'; + *(int *)arg = w ? 1 : 0; +} + +// XXX Maybe should check that the type is correct somehow? +bool want_to_write(const IR::Expression *e, const cstring *field) { + int want; + + std::cout << "checking want_to_write for [" << e->node_type_name() << "] = " << e << ", field " + << *field << '\n'; + if (!e->is()) return (1); + want = 1; + gen_member_chain(e, field, &set_want, &want); + return (want); +} + +// ::preorder returns true -> call down +// ::preorder returns false -> don't call down + +bool setting_optimizer::preorder(const IR::P4Control *c) { + if (incontrol) abort(); + incontrol = 1; + std::cout << "Entering P4Control " << static_cast(c) << '\n'; + return (true); +} + +void setting_optimizer::postorder(const IR::P4Control *c) { + if (!incontrol) abort(); + incontrol = 0; + std::cout << "Leaving P4Control " << static_cast(c) << '\n'; + dump_record(); + std::cout << std::endl; +} + +bool setting_optimizer::preorder(const IR::AssignmentStatement *s) { + if (!incontrol) return (true); + std::cout << "================\n"; + std::cout << "Assignment [" << s->node_type_name() << "] (" << static_cast(s) + << "): " << s << '\n'; + std::cout << " LHS (" << static_cast(s->left) << ") is " << s->left << '\n'; + std::cout << " RHS (" << static_cast(s->right) << ") is " << s->right << '\n'; + if (s->left->is()) { + const IR::Node *n; + for (n = s->left; n->is(); n = n->to()->expr) { + std::cout << "Member name " << n->to()->member.name << '\n'; + } + if (n->is()) { + auto px = n->to(); + std::cout << "Member base path is " << static_cast(px->path) << '\n'; + std::cout << "Path name is " << px->path->name << ", absolute = " << px->path->absolute + << '\n'; + auto ctx = setting_optimizer::getContext(); + dump(ctx); + auto ctl = setting_optimizer::findContext(); + if (ctl != nullptr) { + std::cout << "Found P4Control [" << ctl->node_type_name() << "] (" + << static_cast(ctl) << ")\n"; + std::cout << " name = " << ctl->getName().name << '\n' + << " type = " << ctl->type << '\n' + << " constructorParams = " << ctl->constructorParams << '\n' + << " controlLocals = " << ctl->controlLocals << '\n' + << " getAnnotations() = " << ctl->getAnnotations() << '\n' + << " getTypeParameters() = " << ctl->getTypeParameters() << '\n' + << " getNestedNamespaces() = " << ctl->getNestedNamespaces() << '\n' + << " getDeclarations() = " << ctl->getDeclarations() << '\n' + << " getApplyMethodType() = " << ctl->getApplyMethodType() << '\n' + << " getApplyParameters() = " << ctl->getApplyParameters() << '\n' + << " getConstructorMethodType() = " << ctl->getConstructorMethodType() + << '\n' + << " getConstructorParameters() = " << ctl->getConstructorParameters() + << '\n' + << " getType() = " << ctl->getType() << '\n'; + auto ap = ctl->getApplyParameters(); + // ap is IR::IndexedVector * + auto par = ap->parameters[0]; + // par is an IR::Parameter + std::cout << " [0] = " << par << '\n'; + std::cout << " direction = " << par->direction << '\n' + << " type = " << par->type << '\n' + << " name = " << par->name << '\n'; + // This check works only because of the misfeature elsewhere + // which disallows shadowing parameters. Otherwise we'd + // have to figure out how to tell whether we've got the + // correct variable of this name. + if (par->name == px->path->name) { + auto pt = par->type; + std::cout << "Is parameter 0\n"; + auto tn = pt->to(); + if (tn) { + auto st = typeMap->getTypeType(tn, true); + if (st) { + auto sl = st->to(); + if (sl) { + par_type = sl; + type_map = typeMap; + record_write(s->left->to()); + par_type = 0; + } + } + } + } else { + std::cout << "Not parameter 0\n"; + } + } else { + std::cout << "No P4Control"; + } + } else { + std::cout << "Member base isn't PathExpression\n"; + } + } else { + std::cout << "LHS isn't Member\n"; + } + std::cout << "================\n"; + return (true); +} + bool Backend::process() { + std::cout << "Backend::process beginning" << std::endl; CHECK_NULL(toplevel); if (toplevel->getMain() == nullptr) { ::error("main is missing in the package"); + std::cout << "Backend::process returning false (no main)" << std::endl; return false; // no main } auto refMapEBPF = refMap; @@ -49,10 +461,18 @@ bool Backend::process() { tcIR = new ConvertToBackendIR(toplevel, pipeline, refMap, typeMap, options); genIJ = new IntrospectionGenerator(pipeline, refMap, typeMap); addPasses({parseTCAnno, new P4::ResolveReferences(refMap), - new P4::TypeInference(refMap, typeMap), tcIR, genIJ}); + new P4::TypeInference(refMap, typeMap), tcIR, genIJ, + new setting_optimizer(typeMap)}); toplevel->getProgram()->apply(*this); - if (::errorCount() > 0) return false; - if (!ebpfCodeGen(refMapEBPF, typeMapEBPF)) return false; + if (::errorCount() > 0) { + std::cout << "Backend::process returning false (errors in processing)" << std::endl; + return false; + } + if (!ebpfCodeGen(refMapEBPF, typeMapEBPF)) { + std::cout << "Backend::process returning false (codegen failed)" << std::endl; + return false; + } + std::cout << "Backend::process returning true" << std::endl; return true; } diff --git a/backends/tc/ebpfCodeGen.cpp b/backends/tc/ebpfCodeGen.cpp index e2934596b9b..20d155e8404 100644 --- a/backends/tc/ebpfCodeGen.cpp +++ b/backends/tc/ebpfCodeGen.cpp @@ -254,7 +254,7 @@ void TCIngressPipelinePNA::emit(EBPF::CodeBuilder *builder) { // FIXME: use Target to generate metadata type cstring func_name = (name == "tc-parse") ? "run_parser" : "process"; builder->appendFormat( - "int %s(%s *%s, %s %s *%s, " + "int %s(%s *%s, %s %s *%s/*HDR*/, " "struct pna_global_metadata *%s", func_name, builder->target->packetDescriptorType(), model.CPacketName.str(), parser->headerType->as().kind, @@ -2065,6 +2065,32 @@ void DeparserHdrEmitTranslatorPNA::processMethod(const P4::ExternMethod *method) } } +/* + * This comment added by Mouse, 2023-11-29, and updated by Mouse, + * 2024-01-03. + * + * I have not found any API spec for this method, so I have had to + * guess at some things. + * + * In particular, I do not know what alignment is supposed to be. My + * best guess is that it is the number of bits already loaded in the + * first byte to be loaded, ie, it is the number of bits that 0x80 + * must be shifted right in order to end up at the bit the high bit of + * the value should end up in. This guess is based on observing that + * deparsing an IPv4 packet header passes in 4 for alignment for the + * IHL, 3 for the fragment offset, and 0 for all other fields. + * + * The final alignment value will of course be the input alignment + * value, plus the number of bits written, modulo 8. But we have no + * path via which we could return that, so we have to trust that our + * caller computes alignment correctly for the next field. + * + * I also do not know what noEndiannessConversion is, but in this case + * the Eliza-effect implications are extremely strong. But passing + * this down from our caller seems to me like a very wrong way to + * handle endianness. + */ +extern bool want_to_write(const IR::Expression *, const cstring *); void DeparserHdrEmitTranslatorPNA::emitField(EBPF::CodeBuilder *builder, cstring field, const IR::Expression *hdrExpr, unsigned int alignment, EBPF::EBPFType *type, bool noEndiannessConversion) { @@ -2099,82 +2125,87 @@ void DeparserHdrEmitTranslatorPNA::emitField(EBPF::CodeBuilder *builder, cstring builder->target->emitTraceMessage(builder, msgStr.c_str()); } - if (widthToEmit <= 8) { - emitSize = 8; - } else if (widthToEmit <= 16) { - swap = "bpf_htons"; - emitSize = 16; - } else if (widthToEmit <= 32) { - swap = "htonl"; - emitSize = 32; - } else if (widthToEmit <= 64) { - swap = "htonll"; - emitSize = 64; - } - unsigned shift = - widthToEmit < 8 ? (emitSize - alignment - widthToEmit) : (emitSize - widthToEmit); + if (want_to_write(hdrExpr, &field)) { + if (widthToEmit <= 8) { + emitSize = 8; + } else if (widthToEmit <= 16) { + swap = "bpf_htons"; + emitSize = 16; + } else if (widthToEmit <= 32) { + swap = "htonl"; + emitSize = 32; + } else if (widthToEmit <= 64) { + swap = "htonll"; + emitSize = 64; + } + unsigned shift = + widthToEmit < 8 ? (emitSize - alignment - widthToEmit) : (emitSize - widthToEmit); - if (!swap.isNullOrEmpty() && !noEndiannessConversion) { - builder->emitIndent(); - visit(hdrExpr); - builder->appendFormat(".%s = %s(", field, swap); - visit(hdrExpr); - builder->appendFormat(".%s", field); - if (shift != 0) builder->appendFormat(" << %d", shift); - builder->append(")"); - builder->endOfStatement(true); - } - unsigned bitsInFirstByte = widthToEmit % 8; - if (bitsInFirstByte == 0) bitsInFirstByte = 8; - unsigned bitsInCurrentByte = bitsInFirstByte; - unsigned left = widthToEmit; - for (unsigned i = 0; i < (widthToEmit + 7) / 8; i++) { - builder->emitIndent(); - builder->appendFormat("%s = ((char*)(&", program->byteVar.c_str()); - visit(hdrExpr); - builder->appendFormat(".%s))[%d]", field, i); - builder->endOfStatement(true); - unsigned freeBits = alignment != 0 ? (8 - alignment) : 8; - bitsInCurrentByte = left >= 8 ? 8 : left; - unsigned bitsToWrite = bitsInCurrentByte > freeBits ? freeBits : bitsInCurrentByte; - BUG_CHECK((bitsToWrite > 0) && (bitsToWrite <= 8), "invalid bitsToWrite %d", bitsToWrite); - builder->emitIndent(); - if (alignment == 0 && bitsToWrite == 8) { // write whole byte - builder->appendFormat("write_byte(%s, BYTES(%s) + %d, (%s))", - program->packetStartVar.c_str(), program->offsetVar.c_str(), - i, // do not reverse byte order - program->byteVar.c_str()); - } else { // write partial - shift = (8 - alignment - bitsToWrite); - builder->appendFormat("write_partial(%s + BYTES(%s) + %d, %d, %d, (%s >> %d))", - program->packetStartVar.c_str(), program->offsetVar.c_str(), - i, // do not reverse byte order - bitsToWrite, shift, program->byteVar.c_str(), - widthToEmit > freeBits ? alignment == 0 ? shift : alignment : 0); + if (!swap.isNullOrEmpty() && !noEndiannessConversion) { + builder->emitIndent(); + visit(hdrExpr); + builder->appendFormat(".%s = %s(", field, swap); + visit(hdrExpr); + builder->appendFormat(".%s", field); + if (shift != 0) builder->appendFormat(" << %d", shift); + builder->append(")"); + builder->endOfStatement(true); } - builder->endOfStatement(true); - left -= bitsToWrite; - bitsInCurrentByte -= bitsToWrite; - alignment = (alignment + bitsToWrite) % 8; - bitsToWrite = (8 - bitsToWrite); - if (bitsInCurrentByte > 0) { + unsigned bitsInFirstByte = widthToEmit % 8; + if (bitsInFirstByte == 0) bitsInFirstByte = 8; + unsigned bitsInCurrentByte = bitsInFirstByte; + unsigned left = widthToEmit; + for (unsigned i = 0; i < (widthToEmit + 7) / 8; i++) { builder->emitIndent(); - if (bitsToWrite == 8) { - builder->appendFormat("write_byte(%s, BYTES(%s) + %d + 1, (%s << %d))", - program->packetStartVar.c_str(), program->offsetVar.c_str(), - i, // do not reverse byte order - program->byteVar.c_str(), 8 - alignment % 8); - } else { - builder->appendFormat("write_partial(%s + BYTES(%s) + %d + 1, %d, %d, (%s))", + builder->appendFormat("%s = ((char*)(&", program->byteVar.c_str()); + visit(hdrExpr); + builder->appendFormat(".%s))[%d]", field, i); + builder->endOfStatement(true); + unsigned freeBits = alignment != 0 ? (8 - alignment) : 8; + bitsInCurrentByte = left >= 8 ? 8 : left; + unsigned bitsToWrite = bitsInCurrentByte > freeBits ? freeBits : bitsInCurrentByte; + BUG_CHECK((bitsToWrite > 0) && (bitsToWrite <= 8), "invalid bitsToWrite %d", + bitsToWrite); + builder->emitIndent(); + if (alignment == 0 && bitsToWrite == 8) { // write whole byte + builder->appendFormat("write_byte(%s, BYTES(%s) + %d, (%s))", program->packetStartVar.c_str(), program->offsetVar.c_str(), i, // do not reverse byte order - bitsToWrite, 8 + alignment - bitsToWrite, program->byteVar.c_str()); + } else { // write partial + shift = (8 - alignment - bitsToWrite); + builder->appendFormat( + "write_partial(%s + BYTES(%s) + %d, %d, %d, (%s >> %d))", + program->packetStartVar.c_str(), program->offsetVar.c_str(), + i, // do not reverse byte order + bitsToWrite, shift, program->byteVar.c_str(), + widthToEmit > freeBits ? alignment == 0 ? shift : alignment : 0); } builder->endOfStatement(true); left -= bitsToWrite; + bitsInCurrentByte -= bitsToWrite; + alignment = (alignment + bitsToWrite) % 8; + bitsToWrite = (8 - bitsToWrite); + if (bitsInCurrentByte > 0) { + builder->emitIndent(); + if (bitsToWrite == 8) { + builder->appendFormat("write_byte(%s, BYTES(%s) + %d + 1, (%s << %d))", + program->packetStartVar.c_str(), + program->offsetVar.c_str(), + i, // do not reverse byte order + program->byteVar.c_str(), 8 - alignment % 8); + } else { + builder->appendFormat( + "write_partial(%s + BYTES(%s) + %d + 1, %d, %d, (%s))", + program->packetStartVar.c_str(), program->offsetVar.c_str(), + i, // do not reverse byte order + bitsToWrite, 8 + alignment - bitsToWrite, program->byteVar.c_str()); + } + builder->endOfStatement(true); + left -= bitsToWrite; + } + alignment = (alignment + bitsToWrite) % 8; } - alignment = (alignment + bitsToWrite) % 8; } builder->emitIndent(); builder->appendFormat("%s += %d", program->offsetVar.c_str(), widthToEmit); diff --git a/frontends/common/parser_options.cpp b/frontends/common/parser_options.cpp index 6dd1a31a3ab..278c27fbb5a 100644 --- a/frontends/common/parser_options.cpp +++ b/frontends/common/parser_options.cpp @@ -453,8 +453,12 @@ void ParserOptions::closePreprocessedInput(FILE *inputStream) const { // From (folder, file.ext, suffix) returns // folder/file-suffix.ext static cstring makeFileName(cstring folder, cstring name, cstring baseSuffix) { + static int file_serial = 0; + char fss[64]; Util::PathName filename(name); - Util::PathName newName(filename.getBasename() + baseSuffix + "." + filename.getExtension()); + snprintf(&fss[0], sizeof(fss), "%04d", file_serial++); + Util::PathName newName(std::string(static_cast(&fss[0])) + "." + + filename.getBasename() + baseSuffix + "." + filename.getExtension()); auto result = Util::PathName(folder).join(newName.toString()); return result.toString(); } diff --git a/ir/dump.cpp b/ir/dump.cpp index 7510d55cea8..4c8e5251cd0 100644 --- a/ir/dump.cpp +++ b/ir/dump.cpp @@ -110,17 +110,17 @@ void dump(std::ostream &out, const Visitor::Context *ctxt) { out << indent_t(ctxt->depth - 1); if (ctxt->parent) { if (ctxt->parent->child_name) - out << ctxt->parent->child_name << ": "; + out << ctxt->parent->child_name << ":A "; else - out << ctxt->parent->child_index << ": "; + out << ctxt->parent->child_index << ":B "; } if (ctxt->original != ctxt->node) { - out << "<" << static_cast(ctxt->original) << ":[" << ctxt->original->id + out << "<" << static_cast(ctxt->original) << ":C[" << ctxt->original->id << "] " << ctxt->original->node_type_name(); ctxt->original->dump_fields(out); out << std::endl << indent_t(ctxt->depth - 1) << ">"; } - out << static_cast(ctxt->node) << ":[" << ctxt->node->id << "] " + out << static_cast(ctxt->node) << ":D[" << ctxt->node->id << "] " << ctxt->node->node_type_name(); ctxt->node->dump_fields(out); std::cout << std::endl;