Branch: Tag:

2001-07-11

2001-07-11 14:55:00 by Martin Stjernholm <mast@lysator.liu.se>

Moved the PikeCompile object from PCode to Context to share it better.
Various smaller optimizations to produce less garbage.

Rev: server/etc/modules/RXML.pmod/module.pmod:1.198

2:   //   // Created 1999-07-30 by Martin Stjernholm.   // - // $Id: module.pmod,v 1.197 2001/07/11 06:22:07 mast Exp $ + // $Id: module.pmod,v 1.198 2001/07/11 14:55:00 mast Exp $      // Kludge: Must use "RXML.refs" somewhere for the whole module to be   // loaded correctly.
61:      #define MAGIC_HELP_ARG   // #define OBJ_COUNT_DEBUG + // #define RXML_OBJ_DEBUG   // #define RXML_VERBOSE   // #define RXML_COMPILE_DEBUG   // #define RXML_ENCODE_DEBUG
69: Inside #if defined(RXML_OBJ_DEBUG)
     #ifdef RXML_OBJ_DEBUG   # define MARK_OBJECT \ -  Debug.ObjectMarker __object_marker = Debug.ObjectMarker (this_object()) +  mapping|object __object_marker = Debug.ObjectMarker (this_object())   # define MARK_OBJECT_ONLY \ -  Debug.ObjectMarker __object_marker = Debug.ObjectMarker (0) +  mapping|object __object_marker = Debug.ObjectMarker (0)   #else   # define MARK_OBJECT ;   # define MARK_OBJECT_ONLY ;
84:   # undef MARK_OBJECT   # undef MARK_OBJECT_ONLY   # define MARK_OBJECT \ -  mapping __object_marker = (["count": ++all_constants()->_obj_count]) +  mapping|object __object_marker = (["count": ++all_constants()->_obj_count])   # define MARK_OBJECT_ONLY \ -  mapping __object_marker = (["count": ++all_constants()->_obj_count]) +  mapping|object __object_marker = (["count": ++all_constants()->_obj_count])   # endif   # define OBJ_COUNT (__object_marker ? "[" + __object_marker->count + "]" : "")   #else
384:    EVAL_ARGS_FUNC argfunc = 0; \    if (mixed err = catch { \    _res = _frame->_eval (_ctx, _parser, _type); \ -  if (PCode p_code = _parser->p_code) p_code->add_frame (_frame); \ +  if (PCode p_code = _parser->p_code) \ +  p_code->add_frame (_frame, _ctx); \    }) { \ -  if (PCode p_code = _parser->p_code) p_code->add_frame (_frame); \ +  if (PCode p_code = _parser->p_code) \ +  p_code->add_frame (_frame, _ctx); \    if (objectp (err) && ([object] err)->thrown_at_unwind) { \    UNWIND_STATE ustate = _ctx->unwind_state; \    if (!ustate) ustate = _ctx->unwind_state = ([]); \
1704:    mixed res;    PCode p_code;    int orig_make_p_code = make_p_code; -  make_p_code = 1; +  init_make_p_code();    Parser parser = type->get_parser (this_object(), tag_set, 0, 1);    mixed err = catch {    parser->write_end (to_parse);
1721:       // Internals:    -  int make_p_code; -  // Nonzero if the parsers should compile along with the evaluation. -  // Relevant p-code objects are assumed to exist. -  +     final Parser new_parser (Type top_level_type, void|int _make_p_code)    // Returns a new parser object to start parsing with this context.    // Normally TagSet.`() should be used instead of this.
1732: Inside #if defined(MODULE_DEBUG)
  #ifdef MODULE_DEBUG    if (in_use || frame) fatal_error ("Context already in use.\n");   #endif -  return top_level_type->get_parser (this_object(), tag_set, 0, -  make_p_code = _make_p_code); +  if ((make_p_code = _make_p_code) > 0) init_make_p_code(); +  return top_level_type->get_parser (this_object(), tag_set, 0, _make_p_code);    }      #ifdef DEBUG
1751:    foo = describe_backtrace (backtrace());   #endif    tag_set->call_eval_finish_funs (this_object()); +  if (p_code_comp) p_code_comp->do_delayed_resolving();    }    }   
1810:    // Used to record the result of any add_runtime_tag() and    // remove_runtime_tag() calls since the last time the parsers ran.    +  int make_p_code; +  // Nonzero if the parsers should compile along with the evaluation. +  +  PikeCompile p_code_comp; +  // The PikeCompile object for collecting any produce Pike byte code +  // during p-code compilation. +  +  final void init_make_p_code() +  { +  make_p_code = 1; +  if (!p_code_comp) p_code_comp = PikeCompile(); +  } +     static void create (void|TagSet _tag_set, void|RequestID _id)    // Normally TagSet.`() should be used instead of this.    {
2453:    //! It must never restore an invalid state which might cause errors    //! or invalid results in later calls to the @tt{do_*@} functions.    +  //! @decl PCode result_p_code; +  //! +  //! Set this to an @[RXML.PCode] object to make this tag a p-code +  //! cache frame. When the content is evaluated, the p-code object +  //! will receive the result of the evaluation, with the exception of +  //! any nested p-code cache frames which remains unevaluated. +     // Services:       final mixed get_var (string var, void|string scope_name, void|Type want_type)
2727:    mixed res = nil;    Parser subparser = 0;    int orig_make_p_code = ctx->make_p_code; -  ctx->make_p_code = flags & FLAG_COMPILE_RESULT; +        mixed err = catch {    for (; i < sizeof (exec); i++) {
2740:    piece = elem;    }    else { +  if ((ctx->make_p_code = flags & FLAG_COMPILE_RESULT)) +  ctx->init_make_p_code();    subparser = result_type->get_parser (ctx, ctx->tag_set, evaler,    ctx->make_p_code);    if (evaler->recover_errors && !(flags & FLAG_DONT_RECOVER)) {
2752:    subparser->finish ([string] elem); // Might unwind.    piece = subparser->eval(); // Might unwind.    if (PCode p_code = subparser->p_code) { -  // FIXME: Share PCode and/or PikeCompile objects. +  // Could perhaps collect adjacent PCode objects here.    p_code->finish();    exec[i] = p_code;    }
2783:    // Can't count on that sprintf ("%t", ...) on an object    // returns "object".    if (([object] elem)->is_RXML_Frame) { +  if ((ctx->make_p_code = flags & FLAG_COMPILE_RESULT)) +  ctx->init_make_p_code();    THIS_TAG_DEBUG ("Exec[%d]: Evaluating%s frame %O\n", i,    ctx->make_p_code ? " and compiling" : "",    elem);    piece = ([object(Frame)] elem)->_eval (    ctx, evaler, result_type); // Might unwind.    if (ctx->make_p_code) { -  // FIXME: Share PCode and/or PikeCompile objects. -  PCode p_code = PCode (result_type, 0); -  p_code->add_frame ([object(Frame)] elem); +  // Could perhaps collect adjacent PCode objects here. +  PCode p_code = PCode (result_type, ctx->tag_set); +  p_code->add_frame ([object(Frame)] elem, ctx);    p_code->finish();    exec[i] = p_code;    }
2994:       String.Buffer fn_text;    function(string...:void) fn_text_add; +  PCode sub_p_code = 0; +  PikeCompile comp;    if (p_code) {    fn_text_add = (fn_text = String.Buffer())->add;    fn_text_add ("mixed tmp;\n"); -  +  sub_p_code = PCode (0); +  comp = ctx->p_code_comp;    }       if (splice_arg) {    // Note: This assumes an XML-like parser. -  Parser parser = splice_arg_type->get_parser (ctx, ctx->tag_set, 0, p_code); +  Parser parser = splice_arg_type->get_parser (ctx, ctx->tag_set, 0, +  sub_p_code);    THIS_TAG_DEBUG ("Evaluating splice argument %s\n",    utils->format_short (splice_arg));   #ifdef MODULE_DEBUG
3019:   #endif    if (p_code)    fn_text_add ( -  "return ", p_code->bind (tag->_eval_splice_args), "(ctx,", -  p_code->bind (xml_tag_parser->parse_tag_args), "((", -  parser->p_code->_compile_text(), ")||\"\"),", -  p_code->bind (splice_req_types), ")+([\n"); +  "return ", comp->bind (tag->_eval_splice_args), "(ctx,", +  comp->bind (xml_tag_parser->parse_tag_args), "((", +  sub_p_code->compile_text (comp), ")||\"\"),", +  comp->bind (splice_req_types), ")+([\n");    splice_arg_type->give_back (parser, ctx->tag_set);    args = tag->_eval_splice_args (    ctx, xml_tag_parser->parse_tag_args (splice_arg || ""), splice_req_types);
3040:    foreach (indices (raw_args), string arg) {    Type t = atypes[arg] || tag->def_arg_type;    if (t->parser_prog != PNone) { -  Parser parser = t->get_parser (ctx, ctx_tag_set, 0, p_code); +  Parser parser = t->get_parser (ctx, ctx_tag_set, 0, sub_p_code);    THIS_TAG_DEBUG ("Evaluating and compiling "    "argument value %s with %O\n",    utils->format_short (raw_args[arg]), parser);
3050:    utils->format_short (arg),    utils->format_short (args[arg]));    fn_text_add (sprintf ("%O: %s,\n", arg, -  parser->p_code->_compile_text())); +  sub_p_code->compile_text (comp)));    t->give_back (parser, ctx_tag_set);    }    else {    args[arg] = raw_args[arg]; -  fn_text_add (sprintf ("%O: %s,\n", arg, -  p_code->bind (raw_args[arg]))); +  fn_text_add (sprintf ("%O: %s,\n", arg, comp->bind (raw_args[arg])));    }    }    else
3086:       if (p_code) {    fn_text_add ("]);\n"); -  func = p_code->add_func ( +  func = ctx->p_code_comp->add_func (    "mapping(string:mixed)", "object ctx, object evaler", fn_text->get());    }    }
3213:    ] = state;    m_delete (ctx->unwind_state, this_object());    if (!sizeof (ctx->unwind_state)) ctx->unwind_state = 0; -  ctx->make_p_code = orig_make_p_code || flags & FLAG_COMPILE_INPUT; +  ctx->make_p_code = orig_make_p_code;    THIS_TAG_TOP_DEBUG ("Continuing evaluation" +    (piece ? " with stream piece\n" : "\n"));    }
3245: Inside #if defined(DEBUG)
   if (content && !stringp (content))    fatal_error ("content is not a string in unparsed frame: %O.\n", content);   #endif -  if (flags & FLAG_COMPILE_INPUT) ctx->make_p_code = 1; +  if (flags & FLAG_COMPILE_INPUT || ctx->make_p_code < 0) +  ctx->init_make_p_code();    THIS_TAG_TOP_DEBUG ("Evaluating%s unparsed\n",    ctx->make_p_code ? " and compiling" : "");    if (PCode p_code = ctx->make_p_code &&
3372:    if (finished > 1)    // Looped once. Always compile since it's    // likely we'll loop again. -  ctx->make_p_code = 1; -  object(PCode)|int p_code = ctx->make_p_code && -  ((evaler->is_RXML_PCode ? evaler : evaler->p_code) || 1); +  ctx->init_make_p_code();    if (this_object()->local_tags) {    subevaler = content_type->get_parser (    ctx, [object(TagSet)] this_object()->local_tags, -  evaler, p_code); +  evaler, ctx->make_p_code);    subevaler->_local_tag_set = 1;    THIS_TAG_DEBUG ("Iter[%d]: Parsing%s content %s "    "with %O from local_tags\n", debug_iter, -  p_code ? " and compiling" : "", +  ctx->make_p_code ? " and compiling" : "",    utils->format_short (in_content), subevaler);    }    else {    subevaler = content_type->get_parser ( -  ctx, ctx->tag_set, evaler, p_code); +  ctx, ctx->tag_set, evaler, ctx->make_p_code);   #ifdef DEBUG    if (content_type->parser_prog != PNone)    THIS_TAG_DEBUG ("Iter[%d]: Parsing%s content %s "    "with %O\n", debug_iter, -  p_code ? " and compiling" : "", +  ctx->make_p_code ? " and compiling" : "",    utils->format_short (in_content), subevaler);   #endif    } -  p_code = subevaler->p_code; +     if (evaler->recover_errors && !(flags & FLAG_DONT_RECOVER)) {    subevaler->recover_errors = 1; -  if (p_code) p_code->recover_errors = 1; +  if (PCode p_code = subevaler->p_code) p_code->recover_errors = 1;    }    subevaler->finish (in_content); // Might unwind.    finished = 1;
3408:    else {    THIS_TAG_DEBUG ("Iter[%d]: Evaluating compiled content\n",    debug_iter); -  subevaler = in_content; // Evaling with p-code. +  subevaler = in_content;    }       eval_sub:
3461:    }    }    -  if (subevaler->p_code) ctx->make_p_code = 1; +  if (subevaler->p_code) ctx->init_make_p_code();    subevaler->finish(); // Might unwind.    finished = 1;    } while (1); // Only loops when an unwound subevaler has been recovered.
4169:    val = nil;    }    -  if (evaler->p_code) -  evaler->p_code->add (VarRef (splitted[0], splitted[1..], encoding, want_type)); +  if (PCode p_code = evaler->p_code) +  p_code->add (VarRef (splitted[0], splitted[1..], encoding, want_type));    }    context->frame_depth--;    return val;
4588:    void|Parser|PCode parent, void|int|PCode make_p_code)    //! Returns a parser instance initialized with the given context. If    //! @[make_p_code] is nonzero, the parser will be initialized with a -  //! new @[PCode] object. If it's a @[PCode] object, the new one will -  //! share the same @[PikeCompile] object, which is useful to achieve -  //! larger compilation units. +  //! @[PCode] object so that it compiles during evaluation. If +  //! @[make_p_code] is a @[PCode] object, it's reset and used +  //! directly, otherwise a new object is created.    { -  PCode p_code = -  objectp (make_p_code) ? make_p_code->_sub_p_code (this_object(), tag_set) : -  make_p_code ? PCode (this_object(), tag_set) : -  0; +  PCode p_code = 0; +  if (make_p_code) +  if (objectp (make_p_code)) (p_code = make_p_code)->reset (this_object(), tag_set); +  else p_code = PCode (this_object(), tag_set); + #ifdef DEBUG +  if (make_p_code && !ctx->p_code_comp) +  error ("Context has no p_code_comp for p-coding parser.\n"); + #endif       Parser p;    if (_p_cache) { // It's a tag set parser.
5832:    return bindings[id];    }    -  static object pike_compile_resolver = class (object master) +  private mapping(mixed:mixed) delayed_resolve_places = ([]); +  +  void delayed_resolve (mixed what, mixed index)    { -  +  COMP_MSG ("%O delayed_resolve %O\n", this_object(), what[index]); + #ifdef DEBUG +  if (!zero_type (delayed_resolve_places[what])) +  error ("Multiple indices per thing to delay resolve not handled.\n"); +  if (!bindings[what[index]] && !cur_ids[what[index]]) +  error ("Unknown binding %O.\n", what[index]); + #endif +  delayed_resolve_places[what] = index; +  } +  +  void do_delayed_resolving() +  { +  COMP_MSG ("%O do_delayed_resolving\n", this_object()); +  foreach (indices (delayed_resolve_places), mixed what) { +  mixed index = delayed_resolve_places[what]; +  if (stringp (what[index])) what[index] = resolve (what[index]); +  } +  delayed_resolve_places = ([]); +  } +  +  static void destroy() +  { +  do_delayed_resolving(); +  } +  +  static class Resolver (object master) +  // Can't keep the instantiated Resolver object around since it +  // introduces a cyclic reference. +  {    void compile_error (string file, int line, string err)    {master->compile_error (file, line, err);}   
5846:    if (!zero_type (val = bindings[id])) return val;    return master->resolv (id, file);    } -  } (master()); +  }       object compile()    {
5854:    code->add("mixed _encode() { } void _decode(mixed v) { }\n"    "constant is_RXML_pike_code = 1;\n"    "constant is_RXML_encodable = 1;\n" + #ifdef RXML_OBJ_DEBUG +  // Don't want to encode the cloning of +  // Debug.ObjectMarker in the __INIT that is dumped, +  // since that debug might not be wanted when the dump is +  // decoded. +  "mapping|object __object_marker = ", +  bind (Debug.ObjectMarker ("object(compiled RXML code)")), ";\n" + #else    LITERAL (MARK_OBJECT) ";\n" -  + #endif   #ifdef DEBUG    "string _sprintf() {return \"object(compiled RXML code)\" + "    LITERAL (OBJ_COUNT)
5867: Inside #if defined(DEBUG)
  #ifdef DEBUG    if (mixed err = catch {   #endif -  res = predef::compile (txt, pike_compile_resolver); +  res = predef::compile (txt, Resolver (master()));   #ifdef DEBUG    }) {    report_debug ("Failed program: %s\n", txt);
5908:    //! @[id] is put into the context if given.    {    Context ctx = tag_set ? tag_set->new_context (id) : Context (0, id); -  ctx->make_p_code = 1; // Always extend the compilation to unvisited parts. +  ctx->make_p_code = -1; // Always extend the compilation to unvisited parts.    return ctx;    }   
5962:    // Returns a compiled function for doing the evaluation. The    // function will receive a context to do the evaluation in.    -  static void create (Type _type, void|TagSet _tag_set, void|PikeCompile _comp) +  static void create (Type _type, void|TagSet _tag_set)    { -  type = _type, tag_set = _tag_set, comp = _comp; +  if (_type) { // 0 if we're being decoded. +  type = _type, tag_set = _tag_set; +  p_code = allocate (16);    } -  +  }          // Internals:    -  static array p_code = allocate (16); +  static array p_code = 0;    static int length = 0; -  static PikeCompile comp; +  static int fully_resolved = 0;    -  +  void reset (Type _type, void|TagSet _tag_set) +  { +  type = _type, tag_set = _tag_set; +  p_code = allocate (16); +  length = 0; +  } +     void add (mixed entry)    {    if (length >= sizeof (p_code)) p_code += allocate (sizeof (p_code));
5986:    /* Maybe zap vars too, if it exists? */ \    } while (0)    -  void add_frame (Frame frame) +  void add_frame (Frame frame, Context ctx)    {    if (length + 3 > sizeof (p_code)) p_code += allocate (sizeof (p_code));    p_code[length] = frame->tag || frame; // To make new frames from.
5996:    utils->format_short (frame->args));   #endif    p_code[length + 1] = frame; // Cached for reuse. -  p_code[length + 2] = frame->_save(); +  array frame_state = p_code[length + 2] = frame->_save(); +  if (stringp (frame_state[0])) +  ctx->p_code_comp->delayed_resolve (frame_state, 0);    RESET_FRAME (frame);    length += 3;    }
6023:       while (1) { // Loops only if errors are catched.    if (mixed err = catch { -  for (; pos < length; pos++) { -  mixed item = p_code[pos]; -  if (objectp (item)) -  if (item->is_RXML_p_code_frame) { -  Frame frame; -  if ((frame = p_code[pos + 1])) { -  // Relying on the interpreter lock here. -  p_code[pos + 1] = 0; -  string|EVAL_ARGS_FUNC argfunc = p_code[pos + 2][0]; -  if (stringp (argfunc)) -  frame->args = p_code[pos + 2][0] = comp->resolve (argfunc); -  } -  else { -  if (item->is_RXML_Tag) frame = item->Frame(), frame->tag = item; -  else frame = item->_clone_empty(); +  + #define EVAL_LOOP(RESOLVE_ARGFUNC_1, RESOLVE_ARGFUNC_2) \ +  do for (; pos < length; pos++) { \ +  mixed item = p_code[pos]; \ +  if (objectp (item)) \ +  if (item->is_RXML_p_code_frame) { \ +  Frame frame; \ +  if ((frame = p_code[pos + 1])) { \ +  /* Relying on the interpreter lock here. */ \ +  p_code[pos + 1] = 0; \ +  RESOLVE_ARGFUNC_1; \ +  } \ +  else { \ +  if (item->is_RXML_Tag) \ +  frame = item->Frame(), frame->tag = item; \ +  else frame = item->_clone_empty(); \ +  RESOLVE_ARGFUNC_2; \ +  frame->_restore (p_code[pos + 2]); \ +  } \ +  item = frame->_eval ( \ +  context, this_object(), type); /* Might unwind. */ \ +  if (!p_code[pos + 1]) { \ +  RESET_FRAME (frame); \ +  /* Race here, but it doesn't matter much. */ \ +  p_code[pos + 1] = frame; \ +  } \ +  pos += 2; \ +  } \ +  else if (item->is_RXML_p_code_entry) \ +  item = item->get (context); /* Might unwind. */ \ +  if (item != nil) \ +  parts[ppos++] = item; \ +  if (string errmsgs = m_delete (context->misc, this_object())) \ +  parts[ppos++] = errmsgs; \ +  } while (0) +  +  if (fully_resolved) +  EVAL_LOOP (;, ;); +  else +  EVAL_LOOP ({    array frame_state = p_code[pos + 2]; -  string|EVAL_ARGS_FUNC argfunc = frame_state[0]; -  if (stringp (argfunc)) frame_state[0] = comp->resolve (argfunc); -  frame->_restore (frame_state); -  } -  item = frame->_eval (context, this_object(), type); // Might unwind. -  if (!p_code[pos + 1]) { -  RESET_FRAME (frame); -  // Race here, but it doesn't matter much. -  p_code[pos + 1] = frame; -  } -  pos += 2; -  } -  else if (item->is_RXML_p_code_entry) -  item = item->get (context); // Might unwind. -  if (item != nil) -  parts[ppos++] = item; -  if (string errmsgs = m_delete (context->misc, this_object())) -  parts[ppos++] = errmsgs; -  } +  if (stringp (frame_state[0])) +  frame->args = frame_state[0] = +  context->p_code_comp->resolve (frame_state[0]); +  }, { +  array frame_state = p_code[pos + 2]; +  if (stringp (frame_state[0])) +  frame_state[0] = context->p_code_comp->resolve (frame_state[0]); +  });       context->eval_finish();   
6095:    error ("Should not get here.\n");    }    -  PCode _sub_p_code (Type _type, void|TagSet _tag_set) -  //! Returns a new p-code object which shares the same @[PikeCompile] -  //! instance as this one. -  { -  return PCode (_type, _tag_set, (comp || (comp = PikeCompile()))); -  } -  -  //! Accessors for the corresponding functions in the embedded -  //! @[PikeCompile] object. -  string bind (mixed val) -  {return (comp || (comp = PikeCompile()))->bind (val);} -  string add_var (string type, void|string init) -  {return (comp || (comp = PikeCompile()))->add_var (type, init);} -  string add_func (string rettype, string arglist, string def) -  {return (comp || (comp = PikeCompile()))->add_func (rettype, arglist, def);} -  mixed resolve (string id) -  {return comp->resolve (id);} -  -  string _compile_text() +  string compile_text (PikeCompile comp)    //! Returns a string containing a Pike expression that evaluates the    //! value of this @[PCode] object, assuming the current context is    //! in a variable named @tt{ctx@} and the parent evaluator in
6182:       mixed _encode()    { -  // Resolve all argument functions to their actual definitions -  // before encoding. +  finish();    for (int pos = 0; pos < length; pos++) {    mixed item = p_code[pos];    if (objectp (item) && item->is_RXML_p_code_frame) {    p_code[pos + 1] = 0; // Don't encode the cached frame. -  string|EVAL_ARGS_FUNC argfunc = p_code[pos + 2][0]; -  if (stringp (argfunc)) p_code[pos + 2][0] = comp->resolve (argfunc); -  pos += 2; + #ifdef DEBUG +  if (stringp (p_code[pos + 2][0])) +  error ("Unresolved argument function in frame at position %d.\n", pos); + #endif    }    } -  finish(); +  fully_resolved = 1;       return ({tag_set, type, recover_errors, p_code});    }
6202:    {    [tag_set, type, recover_errors, p_code] = v;    length = sizeof (p_code); +  fully_resolved = 1;       // Instantiate the cached frames, mainly so that any errors in    // their restore functions due to old data are triggered here and
6231:   constant is_RXML_encodable = 1;   static object rxml_module = this_object();    - static class PCodec + class PCodec   {    static TagSet resolve_composite_tag_set (int pos, string compname)    {
6280:    }    case "type":    return reg_types[what[1]] (@what[2..]); +  case "ObjectMarker": +  return Debug.ObjectMarker ( + #ifdef RXML_OBJ_DEBUG +  what[1] + #else +  0 // Avoids the debug printouts, at least. + #endif +  );    }    }   
6390:    return "utils";    else if (what == xml_tag_parser)    return "xml_tag_parser"; + #ifdef RXML_OBJ_DEBUG +  else if (object_program (what) == Debug.ObjectMarker) +  return ({"ObjectMarker", +  reverse (array_sscanf (reverse (what->id), "]%*d[%s")[0])}); + #endif    else if (what->is_RXML_encodable) {    ENCODE_MSG ("encoding object %O recursively\n", what);    return ([])[0];