From patchwork Wed Apr 23 16:37:39 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Dr. David Alan Gilbert" X-Patchwork-Id: 341924 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 273BD14011C for ; Thu, 24 Apr 2014 02:40:36 +1000 (EST) Received: from localhost ([::1]:33796 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wd0Dp-0006Ex-Mt for incoming@patchwork.ozlabs.org; Wed, 23 Apr 2014 12:40:33 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50846) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wd0Ba-0002ox-E1 for qemu-devel@nongnu.org; Wed, 23 Apr 2014 12:38:19 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Wd0BV-0008At-GY for qemu-devel@nongnu.org; Wed, 23 Apr 2014 12:38:14 -0400 Received: from mx1.redhat.com ([209.132.183.28]:49857) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Wd0BV-0008Ab-5P for qemu-devel@nongnu.org; Wed, 23 Apr 2014 12:38:09 -0400 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id s3NGc6PV012742 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 23 Apr 2014 12:38:06 -0400 Received: from dgilbert-t530.home.treblig.org (vpn1-7-213.ams2.redhat.com [10.36.7.213]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id s3NGboKu024273; Wed, 23 Apr 2014 12:38:04 -0400 From: "Dr. David Alan Gilbert (git)" To: qemu-devel@nongnu.org Date: Wed, 23 Apr 2014 17:37:39 +0100 Message-Id: <1398271069-22057-7-git-send-email-dgilbert@redhat.com> In-Reply-To: <1398271069-22057-1-git-send-email-dgilbert@redhat.com> References: <1398271069-22057-1-git-send-email-dgilbert@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.27 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: stefanb@linux.vnet.ibm.com, quintela@redhat.com, mdroth@linux.vnet.ibm.com, agraf@suse.de, mst@redhat.com, aliguori@amazon.com, afaerber@suse.de Subject: [Qemu-devel] [RFC PATCH v2 06/16] Visitor: Binary compatible output visitor X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: "Dr. David Alan Gilbert" Binary compatible output visitor to write to a QEMUFile in current migration format. This is based on Michael Roth's series : https://lists.gnu.org/archive/html/qemu-devel/2011-09/msg02466.html Signed-off-by: Dr. David Alan Gilbert --- include/qapi/qemu-file-binary-output-visitor.h | 26 ++ qapi/Makefile.objs | 2 +- qapi/qemu-file-binary-output-visitor.c | 576 +++++++++++++++++++++++++ 3 files changed, 603 insertions(+), 1 deletion(-) create mode 100644 include/qapi/qemu-file-binary-output-visitor.h create mode 100644 qapi/qemu-file-binary-output-visitor.c diff --git a/include/qapi/qemu-file-binary-output-visitor.h b/include/qapi/qemu-file-binary-output-visitor.h new file mode 100644 index 0000000..f563407 --- /dev/null +++ b/include/qapi/qemu-file-binary-output-visitor.h @@ -0,0 +1,26 @@ +/* + * QEMUFile Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Michael Roth + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QEMU_FILE_BINARY_OUTPUT_VISITOR_H +#define QEMU_FILE_BINARY_OUTPUT_VISITOR_H + +#include "visitor.h" + +typedef struct QemuFileBinOutputVisitor QemuFileBinOutputVisitor; + +QemuFileBinOutputVisitor *qemu_file_bin_output_visitor_new(QEMUFile *f); +void qemu_file_bin_output_visitor_cleanup(QemuFileBinOutputVisitor *d); + +Visitor *qemu_file_bin_output_get_visitor(QemuFileBinOutputVisitor *v); + +#endif diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs index 1f9c973..a054d52 100644 --- a/qapi/Makefile.objs +++ b/qapi/Makefile.objs @@ -1,5 +1,5 @@ util-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o util-obj-y += string-input-visitor.o string-output-visitor.o - +util-obj-y += qemu-file-binary-output-visitor.o util-obj-y += opts-visitor.o diff --git a/qapi/qemu-file-binary-output-visitor.c b/qapi/qemu-file-binary-output-visitor.c new file mode 100644 index 0000000..9541e61 --- /dev/null +++ b/qapi/qemu-file-binary-output-visitor.c @@ -0,0 +1,576 @@ +/* + * QEMUFile Output Visitor + * + * Copyright IBM, Corp. 2011 + * Copyright 2014 Red Hat, Inc. and/or its affiliates + * + * Authors: + * Michael Roth + * David Gilbert + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi/qemu-file-binary-output-visitor.h" +#include "qapi/visitor-impl.h" +#include "qapi/qmp/qerror.h" +#include "qemu/queue.h" +#include "qemu-common.h" +#include "hw/hw.h" +#include "migration/migration.h" + +#if 0 +#define DPRINTF(fmt, ...) \ + do { \ + fprintf(stderr, "qfbov/%s/%d: " fmt "\n", __func__, \ + __LINE__, ## __VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +/* TODO: This needs sharing between input/output binary visitors */ +typedef struct { + size_t elem_count; + size_t elem_size; + size_t pos; +} ArrayInfo; + +typedef struct { + Visit_seq_compat_mode mode; + const void *data; +} SeqCompatInfo; + +typedef struct StackEntry { + enum { + QFOV_ARRAY, + QFOV_LIST, + QFOV_STRUCT, + QFOV_SEQCOMPAT, + } type; + ArrayInfo array_info; + SeqCompatInfo seqcompat_info; + bool is_list_head; + QTAILQ_ENTRY(StackEntry) node; +} StackEntry; + +struct QemuFileBinOutputVisitor { + Visitor visitor; + QTAILQ_HEAD(, StackEntry) stack; + QEMUFile *file; +}; + +static QemuFileBinOutputVisitor *to_ov(Visitor *v) +{ + return container_of(v, QemuFileBinOutputVisitor, visitor); +} + +static void qfbo_push(QemuFileBinOutputVisitor *ov, StackEntry *e) +{ + QTAILQ_INSERT_HEAD(&ov->stack, e, node); +} + +static void qfbo_push_array(QemuFileBinOutputVisitor *ov, ArrayInfo ai) +{ + StackEntry *e = g_malloc0(sizeof(*e)); + e->type = QFOV_ARRAY; + e->array_info = ai; + qfbo_push(ov, e); +} + +static void qfbo_push_list(QemuFileBinOutputVisitor *ov) +{ + StackEntry *e = g_malloc0(sizeof(*e)); + e->type = QFOV_LIST; + e->is_list_head = true; + qfbo_push(ov, e); +} + +static void qfbo_push_seqcompat(QemuFileBinOutputVisitor *ov, + SeqCompatInfo sci) +{ + StackEntry *e = g_malloc0(sizeof(*e)); + e->type = QFOV_SEQCOMPAT; + e->seqcompat_info = sci; + qfbo_push(ov, e); +} + +static void qfbo_push_struct(QemuFileBinOutputVisitor *ov) +{ + StackEntry *e = g_malloc0(sizeof(*e)); + e->type = QFOV_STRUCT; + qfbo_push(ov, e); +} + +static StackEntry *qfbo_pop(QemuFileBinOutputVisitor *ov) +{ + StackEntry *e = QTAILQ_FIRST(&ov->stack); + QTAILQ_REMOVE(&ov->stack, e, node); + return e; +} + +static bool qfbo_is_array(QemuFileBinOutputVisitor *ov) +{ + StackEntry *e = QTAILQ_FIRST(&ov->stack); + return e && e->type == QFOV_ARRAY; +} + +static bool qfbo_is_list(QemuFileBinOutputVisitor *ov) +{ + StackEntry *e = QTAILQ_FIRST(&ov->stack); + return e && e->type == QFOV_LIST; +} + +/* If we are in a seqcompat list return true and fill in + * sci with the compat mode + */ +static bool qfbo_is_seqcompat(QemuFileBinOutputVisitor *ov, + SeqCompatInfo *sci) +{ + StackEntry *e = QTAILQ_FIRST(&ov->stack); + if (e && e->type == QFOV_SEQCOMPAT) { + *sci = e->seqcompat_info; + return true; + } + return false; +} + +static void qfbo_start_struct(Visitor *v, void **obj, + const char *kind, const char *name, + size_t unused, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + + qfbo_push_struct(ov); +} + +static void qfbo_end_struct(Visitor *v, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + StackEntry *e = qfbo_pop(ov); + + if (!e || e->type != QFOV_STRUCT) { + error_set(errp, QERR_UNDEFINED_ERROR); + return; + } + g_free(e); +} + +static void qfbo_start_list(Visitor *v, const char *name, + Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qfbo_push_list(ov); +} + +static GenericList *qfbo_next_list(Visitor *v, GenericList **list, + Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + GenericList *entry; + StackEntry *e = QTAILQ_FIRST(&ov->stack); + + /* Some users don't actually have a list */ + if (!list) { + return NULL; + } + entry = *list; + + if (!entry || !qfbo_is_list(ov)) { + error_set(errp, QERR_UNDEFINED_ERROR); + } + + /* The way the list iterator is currently used unfortunately clobbers + * **list by subseqently assigning our return value to the same container. + * This can cause an infinite loop, but we can get around this by tracking + * a bit of state to note when we should pass back the next entry rather + * than the current one. + */ + if (e->is_list_head) { + e->is_list_head = false; + return entry; + } + + *list = entry->next; + return entry->next; +} + +static void qfbo_end_list(Visitor *v, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + StackEntry *e = qfbo_pop(ov); + if (!e || e->type != QFOV_LIST) { + error_set(errp, QERR_UNDEFINED_ERROR); + } + g_free(e); +} + +static void qfbo_start_array(Visitor *v, void **obj, + const char *name, + size_t elem_count, + size_t elem_size, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + ArrayInfo ai = { + .elem_count = elem_count, + .elem_size = elem_size, + .pos = 0 + }; + qfbo_push_array(ov, ai); +} + +static void qfbo_next_array(Visitor *v, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + StackEntry *e = QTAILQ_FIRST(&ov->stack); + if (!qfbo_is_array(ov) || + e->array_info.pos >= e->array_info.elem_count) { + error_set(errp, QERR_UNDEFINED_ERROR); + } + + e->array_info.pos++; +} + +static void qfbo_end_array(Visitor *v, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + StackEntry *e = qfbo_pop(ov); + if (!e || e->type != QFOV_ARRAY) { + error_set(errp, QERR_UNDEFINED_ERROR); + return; + } + g_free(e); +} + +static void qfbo_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + if (obj) { + g_free(*obj); + } +} + +/* A string upto 256 bytes in length (including terminator) + * output as length byte (not including term) followed by text + * (also not including term) + */ +static void qfbo_type_str256(Visitor *v, char *obj, const char *name, + Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + size_t len = strlen(obj); + + DPRINTF("qfbo_type_str256: len=%d str=%s", (int)len, obj); + + if (len > 255) { + error_set(errp, QERR_BUFFER_OVERRUN); + return; + } + qemu_put_byte(ov->file, (uint8_t)len); + qemu_put_buffer(ov->file, (uint8_t *)obj, len); +} + +static void qfbo_type_buffer(Visitor *v, void *data, size_t len, bool async, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + if (async) { + qemu_put_buffer_async(ov->file, data, len); + } else { + qemu_put_buffer(ov->file, data, len); + } +} + +static void qfbo_type_uint8(Visitor *v, uint8_t *obj, + const char *name, + Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_byte(ov->file, *obj); +} + +static void qfbo_type_uint16(Visitor *v, uint16_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_be16(ov->file, *obj); +} + +static void qfbo_type_uint32(Visitor *v, uint32_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_be32(ov->file, *obj); +} + +static void qfbo_type_uint64(Visitor *v, uint64_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_be64(ov->file, *obj); +} + +static void qfbo_type_int8(Visitor *v, int8_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_byte(ov->file, *obj); +} + +static void qfbo_type_int16(Visitor *v, int16_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_sbe16(ov->file, *obj); +} + +static void qfbo_type_int32(Visitor *v, int32_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_sbe32(ov->file, *obj); +} + +static void qfbo_type_int64(Visitor *v, int64_t *obj, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + qemu_put_sbe64(ov->file, *obj); +} + +static void qfbo_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ + uint8_t val = *obj; + qfbo_type_uint8(v, &val, name, errp); +} + +static QEMUFile *qfbo_get_qemufile(Visitor *v) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + + return ov->file; +} + +/* this is a bit odd - I'm using 'get_next_type' to plant the compatibility + * bytes in lists. + */ +static void qfbo_get_next_type(Visitor *v, int *kind, const int *qobjects, + const char *name, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + SeqCompatInfo sci; + + if (qfbo_is_seqcompat(ov, &sci)) { + /* The binary formats used here have a byte on each entry + * identifying the type, or a terminator (that varies + * in different lists). + */ + uint8_t tmpbyte; + tmpbyte = *kind & 0xff; + + DPRINTF("qfbo_get_next_type for %s", name); + + switch (sci.mode) { + case VISIT_SEQ_COMPAT_BYTE0TERM: + qemu_put_byte(ov->file, tmpbyte); + break; + + default: + error_set(errp, QERR_UNDEFINED_ERROR); + return; + } + return; + } + + /* Only dealing with SeqCompat's for the moment */ + error_set(errp, QERR_UNDEFINED_ERROR); +} + +static void qfbo_start_sequence_compat(Visitor *v, const char *name, + Visit_seq_compat_mode compat_mode, + void *opaque, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + SeqCompatInfo sci = { + .mode = compat_mode, + .data = opaque + }; + SectionHeader *sh; + ramsecentry_header *rse_hdr; + + switch (compat_mode) { + case VISIT_SEQ_COMPAT_FILE: + qemu_put_be32(ov->file, QEMU_VM_FILE_MAGIC); + qemu_put_be32(ov->file, QEMU_VM_FILE_VERSION); + break; + + case VISIT_SEQ_COMPAT_SECTION_HEADER: + /* + * for VM_SECTION_FULL and VM_SECTION_START + * 'opaque' points to a struct Sectionheader + */ + sh = opaque; + qemu_put_be32(ov->file, sh->section_id); + qemu_put_byte(ov->file, strlen(sh->idstr)); + qemu_put_buffer(ov->file, (uint8_t *)sh->idstr, strlen(sh->idstr)); + qemu_put_be32(ov->file, sh->instance_id); + qemu_put_be32(ov->file, sh->version_id); + break; + + case VISIT_SEQ_COMPAT_SECTION_MIN: + /* for VM_SECTION_PART/END, the section name->ID is already known */ + sh = opaque; + qemu_put_be32(ov->file, sh->section_id); + break; + + case VISIT_SEQ_COMPAT_SUBSECTION: /* An element in a subsection list */ + sh = opaque; + qemu_put_byte(ov->file, QEMU_VM_SUBSECTION); + qemu_put_byte(ov->file, strlen(sh->idstr)); + qemu_put_buffer(ov->file, (uint8_t *)sh->idstr, strlen(sh->idstr)); + qemu_put_be32(ov->file, sh->version_id); + break; + + case VISIT_SEQ_COMPAT_BYTE0TERM: + case VISIT_SEQ_COMPAT_SUBSECLIST: + case VISIT_SEQ_COMPAT_RAMSECLIST: + case VISIT_SEQ_COMPAT_VMSTATE: + /* These don't need anything in the header on the compatibility side */ + break; + + case VISIT_SEQ_COMPAT_RAMSECENTRY: + rse_hdr = opaque; + qemu_put_be64(ov->file, rse_hdr->addr | rse_hdr->flags); + if ((rse_hdr->flags & + (RAM_SAVE_FLAG_CONTINUE | RAM_SAVE_FLAG_MEM_SIZE | + RAM_SAVE_FLAG_HOOK)) == 0) { + qemu_put_byte(ov->file, strlen(rse_hdr->idstr)); + qemu_put_buffer(ov->file, (uint8_t *)rse_hdr->idstr, + strlen(rse_hdr->idstr)); + } + if (rse_hdr->flags & RAM_SAVE_FLAG_COMPRESS) { + /* + * A page of all 0's - the file format supports all 'n' + * but this hasn't been used for a while, but is supported by the + * decoder. + */ + qemu_put_byte(ov->file, 0); + } else if (rse_hdr->flags & RAM_SAVE_FLAG_XBZRLE) { + qemu_put_byte(ov->file, 1); /* XBZRLE flag - always 1 */ + qemu_put_be16(ov->file, rse_hdr->len); /* Length of encoded data */ + /* save_xbzrle_page sends the data here */ + } + break; + + case VISIT_SEQ_COMPAT_BLOB: + /* + * Nothing in the header, but opaque gets a copy of our QEMUFile - + * other implementations might give a different QEMUFile + */ + *(QEMUFile **)opaque = ov->file; + break; + + } + + DPRINTF("qfbo_start_sequence_compat for %s\n", name); + qfbo_push_seqcompat(ov, sci); + /* We don't need to read anything at this point */ +} + +static void qfbo_end_sequence_compat(Visitor *v, const char *name, + Visit_seq_compat_mode compat_mode, + Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + StackEntry *e = qfbo_pop(ov); + if (!e || e->type != QFOV_SEQCOMPAT) { + error_setg(errp, "bad struct stack %d", e ? e->type : -1); + if (e) { + g_free(e); + } + return; + } + switch (e->seqcompat_info.mode) { + case VISIT_SEQ_COMPAT_BYTE0TERM: + qemu_put_byte(ov->file, 0); + break; + + case VISIT_SEQ_COMPAT_RAMSECLIST: + qemu_put_be64(ov->file, RAM_SAVE_FLAG_EOS); + break; + + default: + break; + } + + if (e->seqcompat_info.mode != compat_mode) { + error_setg(errp, "mismatched seqcompat mode %d/%d", compat_mode, + e->seqcompat_info.mode); + } + + DPRINTF("qfbo_end_sequence_compat %s\n", name); + g_free(e); +} + +static void qfbo_destroy(Visitor *v, Error **errp) +{ + QemuFileBinOutputVisitor *ov = to_ov(v); + + qemu_file_bin_output_visitor_cleanup(ov); +} + +Visitor *qemu_file_bin_output_get_visitor(QemuFileBinOutputVisitor *v) +{ + return &v->visitor; +} + +void qemu_file_bin_output_visitor_cleanup(QemuFileBinOutputVisitor *ov) +{ + g_free(ov); +} + +QemuFileBinOutputVisitor *qemu_file_bin_output_visitor_new(QEMUFile *f) +{ + QemuFileBinOutputVisitor *v; + + v = g_malloc0(sizeof(*v)); + + v->file = f; + + v->visitor.start_struct = qfbo_start_struct; + v->visitor.end_struct = qfbo_end_struct; + v->visitor.start_list = qfbo_start_list; + v->visitor.next_list = qfbo_next_list; + v->visitor.end_list = qfbo_end_list; + v->visitor.start_array = qfbo_start_array; + v->visitor.next_array = qfbo_next_array; + v->visitor.end_array = qfbo_end_array; + v->visitor.type_buffer = qfbo_type_buffer; + v->visitor.type_int = qfbo_type_int64; + v->visitor.type_uint8 = qfbo_type_uint8; + v->visitor.type_uint16 = qfbo_type_uint16; + v->visitor.type_uint32 = qfbo_type_uint32; + v->visitor.type_uint64 = qfbo_type_uint64; + v->visitor.type_int8 = qfbo_type_int8; + v->visitor.type_int16 = qfbo_type_int16; + v->visitor.type_int32 = qfbo_type_int32; + v->visitor.type_int64 = qfbo_type_int64; + v->visitor.type_bool = qfbo_type_bool; + v->visitor.type_str = qfbo_type_str; + v->visitor.type_str256 = qfbo_type_str256; + v->visitor.destroy = qfbo_destroy; + v->visitor.start_sequence_compat = qfbo_start_sequence_compat; + v->visitor.get_next_type = qfbo_get_next_type; + v->visitor.end_sequence_compat = qfbo_end_sequence_compat; + v->visitor.get_qemufile = qfbo_get_qemufile; + + v->visitor.flags = VISITOR_SAVING; + + QTAILQ_INIT(&v->stack); + + return v; +}