1995-08-09 08:06:35 -07:00
|
|
|
/***********************************************************************/
|
|
|
|
/* */
|
2011-07-27 07:17:02 -07:00
|
|
|
/* OCaml */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* */
|
|
|
|
/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
|
|
|
|
/* */
|
1996-04-30 07:53:58 -07:00
|
|
|
/* Copyright 1996 Institut National de Recherche en Informatique et */
|
1999-11-17 10:59:06 -08:00
|
|
|
/* en Automatique. All rights reserved. This file is distributed */
|
2001-12-07 05:41:02 -08:00
|
|
|
/* under the terms of the GNU Library General Public License, with */
|
|
|
|
/* the special exception on linking described in file ../LICENSE. */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* */
|
|
|
|
/***********************************************************************/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
1995-05-04 03:15:53 -07:00
|
|
|
/* Structured output */
|
|
|
|
|
2003-12-15 10:10:51 -08:00
|
|
|
/* The interface of this file is "intext.h" */
|
|
|
|
|
1996-05-28 05:41:37 -07:00
|
|
|
#include <string.h>
|
1996-04-01 07:24:38 -08:00
|
|
|
#include "alloc.h"
|
2000-02-10 06:04:59 -08:00
|
|
|
#include "custom.h"
|
1995-05-04 03:15:53 -07:00
|
|
|
#include "fail.h"
|
|
|
|
#include "gc.h"
|
|
|
|
#include "intext.h"
|
|
|
|
#include "io.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "mlvalues.h"
|
|
|
|
#include "reverse.h"
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static uintnat obj_counter; /* Number of objects emitted so far */
|
|
|
|
static uintnat size_32; /* Size in words of 32-bit block for struct. */
|
|
|
|
static uintnat size_64; /* Size in words of 64-bit block for struct. */
|
2004-07-19 06:21:10 -07:00
|
|
|
|
|
|
|
static int extern_ignore_sharing; /* Flag to ignore sharing */
|
|
|
|
static int extern_closures; /* Flag to allow externing code pointers */
|
1995-05-04 03:15:53 -07:00
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Trail mechanism to undo forwarding pointers put inside objects */
|
1995-05-04 03:15:53 -07:00
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
struct trail_entry {
|
|
|
|
value obj; /* address of object + initial color in low 2 bits */
|
|
|
|
value field0; /* initial contents of field 0 */
|
1995-05-04 03:15:53 -07:00
|
|
|
};
|
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
struct trail_block {
|
|
|
|
struct trail_block * previous;
|
|
|
|
struct trail_entry entries[ENTRIES_PER_TRAIL_BLOCK];
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct trail_block extern_trail_first;
|
|
|
|
static struct trail_block * extern_trail_block;
|
|
|
|
static struct trail_entry * extern_trail_cur, * extern_trail_limit;
|
|
|
|
|
|
|
|
/* Forward declarations */
|
|
|
|
|
|
|
|
static void extern_out_of_memory(void);
|
|
|
|
static void extern_invalid_argument(char *msg);
|
|
|
|
|
|
|
|
/* Initialize the trail */
|
|
|
|
|
|
|
|
static void init_extern_trail(void)
|
|
|
|
{
|
|
|
|
extern_trail_block = &extern_trail_first;
|
|
|
|
extern_trail_cur = extern_trail_block->entries;
|
|
|
|
extern_trail_limit = extern_trail_block->entries + ENTRIES_PER_TRAIL_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Replay the trail, undoing the in-place modifications
|
|
|
|
performed on objects */
|
|
|
|
|
|
|
|
static void extern_replay_trail(void)
|
|
|
|
{
|
|
|
|
struct trail_block * blk, * prevblk;
|
|
|
|
struct trail_entry * ent, * lim;
|
|
|
|
|
|
|
|
blk = extern_trail_block;
|
|
|
|
lim = extern_trail_cur;
|
|
|
|
while (1) {
|
|
|
|
for (ent = &(blk->entries[0]); ent < lim; ent++) {
|
|
|
|
value obj = ent->obj;
|
|
|
|
color_t colornum = obj & 3;
|
|
|
|
obj = obj & ~3;
|
|
|
|
Hd_val(obj) = Coloredhd_hd(Hd_val(obj), colornum);
|
|
|
|
Field(obj, 0) = ent->field0;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
if (blk == &extern_trail_first) break;
|
|
|
|
prevblk = blk->previous;
|
|
|
|
free(blk);
|
|
|
|
blk = prevblk;
|
|
|
|
lim = &(blk->entries[ENTRIES_PER_TRAIL_BLOCK]);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Protect against a second call to extern_replay_trail */
|
|
|
|
extern_trail_block = &extern_trail_first;
|
|
|
|
extern_trail_cur = extern_trail_block->entries;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Set forwarding pointer on an object and add corresponding entry
|
|
|
|
to the trail. */
|
|
|
|
|
|
|
|
static void extern_record_location(value obj)
|
1997-04-11 06:57:04 -07:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
header_t hdr;
|
|
|
|
|
|
|
|
if (extern_ignore_sharing) return;
|
|
|
|
if (extern_trail_cur == extern_trail_limit) {
|
|
|
|
struct trail_block * new_block = malloc(sizeof(struct trail_block));
|
|
|
|
if (new_block == NULL) extern_out_of_memory();
|
|
|
|
new_block->previous = extern_trail_block;
|
|
|
|
extern_trail_block = new_block;
|
|
|
|
extern_trail_cur = extern_trail_block->entries;
|
|
|
|
extern_trail_limit = extern_trail_block->entries + ENTRIES_PER_TRAIL_BLOCK;
|
1997-04-11 06:57:04 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
hdr = Hd_val(obj);
|
|
|
|
extern_trail_cur->obj = obj | Colornum_hd(hdr);
|
|
|
|
extern_trail_cur->field0 = Field(obj, 0);
|
|
|
|
extern_trail_cur++;
|
|
|
|
Hd_val(obj) = Bluehd_hd(hdr);
|
|
|
|
Field(obj, 0) = (value) obj_counter;
|
|
|
|
obj_counter++;
|
1997-04-11 06:57:04 -07:00
|
|
|
}
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
/* To buffer the output */
|
1995-05-04 03:15:53 -07:00
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
static char * extern_userprovided_output;
|
|
|
|
static char * extern_ptr, * extern_limit;
|
|
|
|
|
|
|
|
struct output_block {
|
|
|
|
struct output_block * next;
|
|
|
|
char * end;
|
|
|
|
char data[SIZE_EXTERN_OUTPUT_BLOCK];
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct output_block * extern_output_first, * extern_output_block;
|
1996-04-01 07:24:38 -08:00
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
static void init_extern_output(void)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_userprovided_output = NULL;
|
|
|
|
extern_output_first = malloc(sizeof(struct output_block));
|
|
|
|
if (extern_output_first == NULL) caml_raise_out_of_memory();
|
|
|
|
extern_output_block = extern_output_first;
|
|
|
|
extern_output_block->next = NULL;
|
|
|
|
extern_ptr = extern_output_block->data;
|
|
|
|
extern_limit = extern_output_block->data + SIZE_EXTERN_OUTPUT_BLOCK;
|
|
|
|
}
|
|
|
|
|
2006-01-04 08:55:50 -08:00
|
|
|
static void close_extern_output(void)
|
|
|
|
{
|
|
|
|
if (extern_userprovided_output == NULL){
|
|
|
|
extern_output_block->end = extern_ptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
static void free_extern_output(void)
|
|
|
|
{
|
|
|
|
struct output_block * blk, * nextblk;
|
|
|
|
|
|
|
|
if (extern_userprovided_output != NULL) return;
|
|
|
|
for (blk = extern_output_first; blk != NULL; blk = nextblk) {
|
|
|
|
nextblk = blk->next;
|
|
|
|
free(blk);
|
|
|
|
}
|
|
|
|
extern_output_first = NULL;
|
1996-04-01 07:24:38 -08:00
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void grow_extern_output(intnat required)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
struct output_block * blk;
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat extra;
|
1996-04-01 07:24:38 -08:00
|
|
|
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_userprovided_output != NULL) {
|
|
|
|
extern_replay_trail();
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_failwith("Marshal.to_buffer: buffer overflow");
|
1997-07-02 11:16:15 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_output_block->end = extern_ptr;
|
|
|
|
if (required <= SIZE_EXTERN_OUTPUT_BLOCK / 2)
|
|
|
|
extra = 0;
|
|
|
|
else
|
|
|
|
extra = required;
|
|
|
|
blk = malloc(sizeof(struct output_block) + extra);
|
|
|
|
if (blk == NULL) extern_out_of_memory();
|
|
|
|
extern_output_block->next = blk;
|
|
|
|
extern_output_block = blk;
|
|
|
|
extern_output_block->next = NULL;
|
|
|
|
extern_ptr = extern_output_block->data;
|
|
|
|
extern_limit = extern_output_block->data + SIZE_EXTERN_OUTPUT_BLOCK + extra;
|
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static intnat extern_output_length(void)
|
2004-07-19 06:21:10 -07:00
|
|
|
{
|
|
|
|
struct output_block * blk;
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len;
|
2004-07-19 06:21:10 -07:00
|
|
|
|
|
|
|
if (extern_userprovided_output != NULL) {
|
|
|
|
return extern_ptr - extern_userprovided_output;
|
|
|
|
} else {
|
|
|
|
for (len = 0, blk = extern_output_first; blk != NULL; blk = blk->next)
|
|
|
|
len += blk->end - blk->data;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Exception raising, with cleanup */
|
|
|
|
|
|
|
|
static void extern_out_of_memory(void)
|
|
|
|
{
|
|
|
|
extern_replay_trail();
|
|
|
|
free_extern_output();
|
|
|
|
caml_raise_out_of_memory();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void extern_invalid_argument(char *msg)
|
|
|
|
{
|
|
|
|
extern_replay_trail();
|
|
|
|
free_extern_output();
|
|
|
|
caml_invalid_argument(msg);
|
1996-04-01 07:24:38 -08:00
|
|
|
}
|
|
|
|
|
1997-07-02 11:16:15 -07:00
|
|
|
/* Write characters, integers, and blocks in the output buffer */
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
#define Write(c) \
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr >= extern_limit) grow_extern_output(1); \
|
1996-04-01 07:24:38 -08:00
|
|
|
*extern_ptr++ = (c)
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void writeblock(char *data, intnat len)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + len > extern_limit) grow_extern_output(len);
|
2000-10-12 11:05:42 -07:00
|
|
|
memmove(extern_ptr, data, len);
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_ptr += len;
|
|
|
|
}
|
|
|
|
|
2002-02-19 06:37:44 -08:00
|
|
|
#if ARCH_FLOAT_ENDIANNESS == 0x01234567 || ARCH_FLOAT_ENDIANNESS == 0x76543210
|
|
|
|
#define writeblock_float8(data,ndoubles) \
|
|
|
|
writeblock((char *)(data), (ndoubles) * 8)
|
|
|
|
#else
|
|
|
|
#define writeblock_float8(data,ndoubles) \
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_serialize_block_float_8((data), (ndoubles))
|
2002-02-19 06:37:44 -08:00
|
|
|
#endif
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void writecode8(int code, intnat val)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 2 > extern_limit) grow_extern_output(2);
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val;
|
|
|
|
extern_ptr += 2;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void writecode16(int code, intnat val)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 3 > extern_limit) grow_extern_output(3);
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val >> 8;
|
|
|
|
extern_ptr[2] = val;
|
|
|
|
extern_ptr += 3;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void write32(intnat val)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 4 > extern_limit) grow_extern_output(4);
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_ptr[0] = val >> 24;
|
|
|
|
extern_ptr[1] = val >> 16;
|
|
|
|
extern_ptr[2] = val >> 8;
|
|
|
|
extern_ptr[3] = val;
|
|
|
|
extern_ptr += 4;
|
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static void writecode32(int code, intnat val)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 5 > extern_limit) grow_extern_output(5);
|
1996-04-01 07:24:38 -08:00
|
|
|
extern_ptr[0] = code;
|
|
|
|
extern_ptr[1] = val >> 24;
|
|
|
|
extern_ptr[2] = val >> 16;
|
|
|
|
extern_ptr[3] = val >> 8;
|
|
|
|
extern_ptr[4] = val;
|
|
|
|
extern_ptr += 5;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
2005-09-22 07:21:50 -07:00
|
|
|
static void writecode64(int code, intnat val)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
|
|
|
int i;
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 9 > extern_limit) grow_extern_output(9);
|
1996-04-01 07:24:38 -08:00
|
|
|
*extern_ptr ++ = code;
|
|
|
|
for (i = 64 - 8; i >= 0; i -= 8) *extern_ptr++ = val >> i;
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Marshal the given value in the output buffer */
|
|
|
|
|
1997-09-02 05:55:01 -07:00
|
|
|
static void extern_rec(value v)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
|
|
|
tailcall:
|
|
|
|
if (Is_long(v)) {
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat n = Long_val(v);
|
1995-05-04 03:15:53 -07:00
|
|
|
if (n >= 0 && n < 0x40) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_INT + n);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (n >= -(1 << 7) && n < (1 << 7)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode8(CODE_INT8, n);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else if (n >= -(1 << 15) && n < (1 << 15)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode16(CODE_INT16, n);
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
2006-05-04 05:41:26 -07:00
|
|
|
} else if (n < -((intnat)1 << 31) || n >= ((intnat)1 << 31)) {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode64(CODE_INT64, n);
|
1995-05-04 03:15:53 -07:00
|
|
|
#endif
|
|
|
|
} else
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_INT32, n);
|
1997-07-02 11:16:15 -07:00
|
|
|
return;
|
|
|
|
}
|
2008-01-03 01:37:10 -08:00
|
|
|
if (Is_in_value_area(v)) {
|
1995-05-04 03:15:53 -07:00
|
|
|
header_t hd = Hd_val(v);
|
|
|
|
tag_t tag = Tag_hd(hd);
|
|
|
|
mlsize_t sz = Wosize_hd(hd);
|
2001-10-26 12:35:14 -07:00
|
|
|
|
2002-07-30 05:59:13 -07:00
|
|
|
if (tag == Forward_tag) {
|
2003-10-16 16:22:23 -07:00
|
|
|
value f = Forward_val (v);
|
2008-07-28 05:03:55 -07:00
|
|
|
if (Is_block (f)
|
|
|
|
&& (!Is_in_value_area(f) || Tag_val (f) == Forward_tag
|
|
|
|
|| Tag_val (f) == Lazy_tag || Tag_val (f) == Double_tag)){
|
2003-10-16 16:22:23 -07:00
|
|
|
/* Do not short-circuit the pointer. */
|
|
|
|
}else{
|
|
|
|
v = f;
|
|
|
|
goto tailcall;
|
|
|
|
}
|
2002-07-30 05:59:13 -07:00
|
|
|
}
|
1995-05-04 03:15:53 -07:00
|
|
|
/* Atoms are treated specially for two reasons: they are not allocated
|
|
|
|
in the externed block, and they are automatically shared. */
|
|
|
|
if (sz == 0) {
|
|
|
|
if (tag < 16) {
|
1996-04-01 07:24:38 -08:00
|
|
|
Write(PREFIX_SMALL_BLOCK + tag);
|
1995-05-04 03:15:53 -07:00
|
|
|
} else {
|
1996-04-01 07:24:38 -08:00
|
|
|
writecode32(CODE_BLOCK32, hd);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
1997-07-02 11:16:15 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* Check if already seen */
|
2004-07-19 06:21:10 -07:00
|
|
|
if (Color_hd(hd) == Caml_blue) {
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat d = obj_counter - (uintnat) Field(v, 0);
|
2004-07-19 06:21:10 -07:00
|
|
|
if (d < 0x100) {
|
|
|
|
writecode8(CODE_SHARED8, d);
|
|
|
|
} else if (d < 0x10000) {
|
|
|
|
writecode16(CODE_SHARED16, d);
|
|
|
|
} else {
|
|
|
|
writecode32(CODE_SHARED32, d);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
return;
|
1997-07-02 11:16:15 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
|
1997-07-02 11:16:15 -07:00
|
|
|
/* Output the contents of the object */
|
|
|
|
switch(tag) {
|
|
|
|
case String_tag: {
|
2003-12-16 10:09:44 -08:00
|
|
|
mlsize_t len = caml_string_length(v);
|
1997-07-02 11:16:15 -07:00
|
|
|
if (len < 0x20) {
|
|
|
|
Write(PREFIX_SMALL_STRING + len);
|
|
|
|
} else if (len < 0x100) {
|
|
|
|
writecode8(CODE_STRING8, len);
|
|
|
|
} else {
|
|
|
|
writecode32(CODE_STRING32, len);
|
1995-07-27 10:41:09 -07:00
|
|
|
}
|
1997-07-02 11:16:15 -07:00
|
|
|
writeblock(String_val(v), len);
|
|
|
|
size_32 += 1 + (len + 4) / 4;
|
|
|
|
size_64 += 1 + (len + 8) / 8;
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_record_location(v);
|
1997-07-02 11:16:15 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Double_tag: {
|
|
|
|
if (sizeof(double) != 8)
|
|
|
|
extern_invalid_argument("output_value: non-standard floats");
|
|
|
|
Write(CODE_DOUBLE_NATIVE);
|
2002-02-19 06:37:44 -08:00
|
|
|
writeblock_float8((double *) v, 1);
|
1997-07-02 11:16:15 -07:00
|
|
|
size_32 += 1 + 2;
|
|
|
|
size_64 += 1 + 1;
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_record_location(v);
|
1997-07-02 11:16:15 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Double_array_tag: {
|
|
|
|
mlsize_t nfloats;
|
|
|
|
if (sizeof(double) != 8)
|
|
|
|
extern_invalid_argument("output_value: non-standard floats");
|
|
|
|
nfloats = Wosize_val(v) / Double_wosize;
|
|
|
|
if (nfloats < 0x100) {
|
|
|
|
writecode8(CODE_DOUBLE_ARRAY8_NATIVE, nfloats);
|
|
|
|
} else {
|
|
|
|
writecode32(CODE_DOUBLE_ARRAY32_NATIVE, nfloats);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
2002-02-19 06:37:44 -08:00
|
|
|
writeblock_float8((double *) v, nfloats);
|
1997-07-02 11:16:15 -07:00
|
|
|
size_32 += 1 + nfloats * 2;
|
|
|
|
size_64 += 1 + nfloats;
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_record_location(v);
|
1997-07-02 11:16:15 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Abstract_tag:
|
2004-04-13 10:12:46 -07:00
|
|
|
extern_invalid_argument("output_value: abstract value (Abstract)");
|
1997-07-02 11:16:15 -07:00
|
|
|
break;
|
|
|
|
case Infix_tag:
|
|
|
|
writecode32(CODE_INFIXPOINTER, Infix_offset_hd(hd));
|
|
|
|
extern_rec(v - Infix_offset_hd(hd));
|
|
|
|
break;
|
2000-02-10 06:04:59 -08:00
|
|
|
case Custom_tag: {
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat sz_32, sz_64;
|
2000-02-10 06:04:59 -08:00
|
|
|
char * ident = Custom_ops_val(v)->identifier;
|
2005-09-22 07:21:50 -07:00
|
|
|
void (*serialize)(value v, uintnat * wsize_32,
|
|
|
|
uintnat * wsize_64)
|
2002-07-23 07:12:03 -07:00
|
|
|
= Custom_ops_val(v)->serialize;
|
2006-09-20 04:14:37 -07:00
|
|
|
if (serialize == NULL)
|
2004-04-13 10:12:46 -07:00
|
|
|
extern_invalid_argument("output_value: abstract value (Custom)");
|
2000-02-10 06:04:59 -08:00
|
|
|
Write(CODE_CUSTOM);
|
|
|
|
writeblock(ident, strlen(ident) + 1);
|
|
|
|
Custom_ops_val(v)->serialize(v, &sz_32, &sz_64);
|
|
|
|
size_32 += 2 + ((sz_32 + 3) >> 2); /* header + ops + data */
|
|
|
|
size_64 += 2 + ((sz_64 + 7) >> 3);
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_record_location(v);
|
2000-02-10 06:04:59 -08:00
|
|
|
break;
|
|
|
|
}
|
1997-07-02 11:16:15 -07:00
|
|
|
default: {
|
2004-07-19 06:21:10 -07:00
|
|
|
value field0;
|
1997-07-02 11:16:15 -07:00
|
|
|
mlsize_t i;
|
|
|
|
if (tag < 16 && sz < 8) {
|
|
|
|
Write(PREFIX_SMALL_BLOCK + tag + (sz << 4));
|
2002-11-25 06:40:32 -08:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
2006-05-04 05:41:26 -07:00
|
|
|
} else if (hd >= ((uintnat)1 << 32)) {
|
2002-11-25 06:40:32 -08:00
|
|
|
writecode64(CODE_BLOCK64, Whitehd_hd (hd));
|
|
|
|
#endif
|
1997-07-02 11:16:15 -07:00
|
|
|
} else {
|
2000-01-02 08:10:00 -08:00
|
|
|
writecode32(CODE_BLOCK32, Whitehd_hd (hd));
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
1997-07-02 11:16:15 -07:00
|
|
|
size_32 += 1 + sz;
|
|
|
|
size_64 += 1 + sz;
|
2004-07-19 06:21:10 -07:00
|
|
|
field0 = Field(v, 0);
|
|
|
|
extern_record_location(v);
|
|
|
|
if (sz == 1) {
|
|
|
|
v = field0;
|
|
|
|
} else {
|
|
|
|
extern_rec(field0);
|
|
|
|
for (i = 1; i < sz - 1; i++) extern_rec(Field(v, i));
|
|
|
|
v = Field(v, i);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
goto tailcall;
|
|
|
|
}
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
}
|
2004-07-19 06:21:10 -07:00
|
|
|
else if ((char *) v >= caml_code_area_start &&
|
|
|
|
(char *) v < caml_code_area_end) {
|
1997-07-02 11:16:15 -07:00
|
|
|
if (!extern_closures)
|
|
|
|
extern_invalid_argument("output_value: functional value");
|
2004-01-02 11:23:29 -08:00
|
|
|
writecode32(CODE_CODEPOINTER, (char *) v - caml_code_area_start);
|
2004-01-01 08:42:43 -08:00
|
|
|
writeblock((char *) caml_code_checksum(), 16);
|
2004-07-19 06:21:10 -07:00
|
|
|
} else {
|
|
|
|
extern_invalid_argument("output_value: abstract value (outside heap)");
|
1997-07-02 11:16:15 -07:00
|
|
|
}
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
|
1997-07-02 11:16:15 -07:00
|
|
|
enum { NO_SHARING = 1, CLOSURES = 2 };
|
|
|
|
static int extern_flags[] = { NO_SHARING, CLOSURES };
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
static intnat extern_value(value v, value flags)
|
1995-05-04 03:15:53 -07:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat res_len;
|
1997-07-02 11:16:15 -07:00
|
|
|
int fl;
|
|
|
|
/* Parse flag list */
|
2003-12-29 14:15:02 -08:00
|
|
|
fl = caml_convert_flag_list(flags, extern_flags);
|
1997-07-02 11:16:15 -07:00
|
|
|
extern_ignore_sharing = fl & NO_SHARING;
|
|
|
|
extern_closures = fl & CLOSURES;
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Initializations */
|
|
|
|
init_extern_trail();
|
1995-05-04 03:15:53 -07:00
|
|
|
obj_counter = 0;
|
|
|
|
size_32 = 0;
|
|
|
|
size_64 = 0;
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Write magic number */
|
|
|
|
write32(Intext_magic_number);
|
|
|
|
/* Set aside space for the sizes */
|
|
|
|
extern_ptr += 4*4;
|
|
|
|
/* Marshal the object */
|
|
|
|
extern_rec(v);
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Record end of output */
|
2006-01-04 08:55:50 -08:00
|
|
|
close_extern_output();
|
2004-07-19 06:21:10 -07:00
|
|
|
/* Undo the modifications done on externed blocks */
|
|
|
|
extern_replay_trail();
|
1996-04-01 07:24:38 -08:00
|
|
|
/* Write the sizes */
|
2004-07-19 06:21:10 -07:00
|
|
|
res_len = extern_output_length();
|
1996-07-01 05:43:28 -07:00
|
|
|
#ifdef ARCH_SIXTYFOUR
|
2006-05-04 05:41:26 -07:00
|
|
|
if (res_len >= ((intnat)1 << 32) ||
|
|
|
|
size_32 >= ((intnat)1 << 32) || size_64 >= ((intnat)1 << 32)) {
|
1995-05-04 03:15:53 -07:00
|
|
|
/* The object is so big its size cannot be written in the header.
|
2002-11-25 06:40:32 -08:00
|
|
|
Besides, some of the array lengths or string lengths or shared offsets
|
1995-05-04 03:15:53 -07:00
|
|
|
it contains may have overflowed the 32 bits used to write them. */
|
2004-07-19 06:21:10 -07:00
|
|
|
free_extern_output();
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_failwith("output_value: object too big");
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
|
|
|
#endif
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_userprovided_output != NULL)
|
|
|
|
extern_ptr = extern_userprovided_output + 4;
|
|
|
|
else {
|
|
|
|
extern_ptr = extern_output_first->data + 4;
|
|
|
|
extern_limit = extern_output_first->data + SIZE_EXTERN_OUTPUT_BLOCK;
|
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
write32(res_len - 5*4);
|
|
|
|
write32(obj_counter);
|
|
|
|
write32(size_32);
|
|
|
|
write32(size_64);
|
|
|
|
return res_len;
|
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
void caml_output_val(struct channel *chan, value v, value flags)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len;
|
2004-07-19 06:21:10 -07:00
|
|
|
struct output_block * blk, * nextblk;
|
2000-02-07 06:07:31 -08:00
|
|
|
|
2003-12-29 14:15:02 -08:00
|
|
|
if (! caml_channel_binary_mode(chan))
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_failwith("output_value: not a binary channel");
|
2004-07-19 06:21:10 -07:00
|
|
|
init_extern_output();
|
1997-07-02 11:16:15 -07:00
|
|
|
len = extern_value(v, flags);
|
2004-01-01 08:42:43 -08:00
|
|
|
/* During [caml_really_putblock], concurrent [caml_output_val] operations
|
|
|
|
can take place (via signal handlers or context switching in systhreads),
|
2004-07-19 06:21:10 -07:00
|
|
|
and [extern_output_first] may change. So, save it in a local variable. */
|
|
|
|
blk = extern_output_first;
|
|
|
|
while (blk != NULL) {
|
|
|
|
caml_really_putblock(chan, blk->data, blk->end - blk->data);
|
|
|
|
nextblk = blk->next;
|
|
|
|
free(blk);
|
|
|
|
blk = nextblk;
|
|
|
|
}
|
1997-08-29 08:37:22 -07:00
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLprim value caml_output_value(value vchan, value v, value flags)
|
1997-08-29 08:37:22 -07:00
|
|
|
{
|
1999-11-29 11:03:05 -08:00
|
|
|
CAMLparam3 (vchan, v, flags);
|
1997-08-29 08:37:22 -07:00
|
|
|
struct channel * channel = Channel(vchan);
|
1999-11-29 11:03:05 -08:00
|
|
|
|
|
|
|
Lock(channel);
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_output_val(channel, v, flags);
|
1999-11-29 11:03:05 -08:00
|
|
|
Unlock(channel);
|
|
|
|
CAMLreturn (Val_unit);
|
1995-05-04 03:15:53 -07:00
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLprim value caml_output_value_to_string(value v, value flags)
|
1996-04-01 07:24:38 -08:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len, ofs;
|
1996-04-01 07:24:38 -08:00
|
|
|
value res;
|
2006-09-20 04:14:37 -07:00
|
|
|
struct output_block * blk, * nextblk;
|
2004-07-19 06:21:10 -07:00
|
|
|
|
|
|
|
init_extern_output();
|
1997-07-02 11:16:15 -07:00
|
|
|
len = extern_value(v, flags);
|
2006-09-20 04:14:37 -07:00
|
|
|
/* PR#4030: it is prudent to save extern_output_first before allocating
|
|
|
|
the result, as in caml_output_val */
|
|
|
|
blk = extern_output_first;
|
2003-12-29 14:15:02 -08:00
|
|
|
res = caml_alloc_string(len);
|
2006-09-20 04:14:37 -07:00
|
|
|
ofs = 0;
|
|
|
|
while (blk != NULL) {
|
2004-07-19 06:21:10 -07:00
|
|
|
int n = blk->end - blk->data;
|
|
|
|
memmove(&Byte(res, ofs), blk->data, n);
|
|
|
|
ofs += n;
|
2006-09-20 04:14:37 -07:00
|
|
|
nextblk = blk->next;
|
|
|
|
free(blk);
|
|
|
|
blk = nextblk;
|
2004-07-19 06:21:10 -07:00
|
|
|
}
|
1996-04-01 07:24:38 -08:00
|
|
|
return res;
|
|
|
|
}
|
1997-07-02 11:16:15 -07:00
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLprim value caml_output_value_to_buffer(value buf, value ofs, value len,
|
|
|
|
value v, value flags)
|
1997-07-02 11:16:15 -07:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len_res;
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_userprovided_output = &Byte(buf, Long_val(ofs));
|
|
|
|
extern_ptr = extern_userprovided_output;
|
|
|
|
extern_limit = extern_userprovided_output + Long_val(len);
|
1997-07-02 11:16:15 -07:00
|
|
|
len_res = extern_value(v, flags);
|
|
|
|
return Val_long(len_res);
|
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_output_value_to_malloc(value v, value flags,
|
|
|
|
/*out*/ char ** buf,
|
2005-09-22 07:21:50 -07:00
|
|
|
/*out*/ intnat * len)
|
1998-06-11 05:53:45 -07:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len_res;
|
2004-07-19 06:21:10 -07:00
|
|
|
char * res;
|
|
|
|
struct output_block * blk;
|
|
|
|
|
|
|
|
init_extern_output();
|
2000-07-28 05:23:25 -07:00
|
|
|
len_res = extern_value(v, flags);
|
2004-07-19 06:21:10 -07:00
|
|
|
res = malloc(len_res);
|
|
|
|
if (res == NULL) extern_out_of_memory();
|
|
|
|
*buf = res;
|
2000-07-28 05:23:25 -07:00
|
|
|
*len = len_res;
|
2004-07-19 06:21:10 -07:00
|
|
|
for (blk = extern_output_first; blk != NULL; blk = blk->next) {
|
|
|
|
int n = blk->end - blk->data;
|
|
|
|
memmove(res, blk->data, n);
|
|
|
|
res += n;
|
|
|
|
}
|
|
|
|
free_extern_output();
|
1998-06-11 05:53:45 -07:00
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport intnat caml_output_value_to_block(value v, value flags,
|
|
|
|
char * buf, intnat len)
|
2001-12-03 07:45:03 -08:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat len_res;
|
2004-07-19 06:21:10 -07:00
|
|
|
extern_userprovided_output = buf;
|
|
|
|
extern_ptr = extern_userprovided_output;
|
|
|
|
extern_limit = extern_userprovided_output + len;
|
2001-12-03 07:45:03 -08:00
|
|
|
len_res = extern_value(v, flags);
|
|
|
|
return len_res;
|
|
|
|
}
|
|
|
|
|
2000-02-10 06:04:59 -08:00
|
|
|
/* Functions for writing user-defined marshallers */
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_int_1(int i)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 1 > extern_limit) grow_extern_output(1);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr[0] = i;
|
|
|
|
extern_ptr += 1;
|
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_int_2(int i)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 2 > extern_limit) grow_extern_output(2);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr[0] = i >> 8;
|
|
|
|
extern_ptr[1] = i;
|
|
|
|
extern_ptr += 2;
|
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_int_4(int32 i)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 4 > extern_limit) grow_extern_output(4);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr[0] = i >> 24;
|
|
|
|
extern_ptr[1] = i >> 16;
|
|
|
|
extern_ptr[2] = i >> 8;
|
|
|
|
extern_ptr[3] = i;
|
|
|
|
extern_ptr += 4;
|
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_int_8(int64 i)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_serialize_block_8(&i, 1);
|
2000-02-10 06:04:59 -08:00
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_float_4(float f)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_serialize_block_4(&f, 1);
|
2000-02-10 06:04:59 -08:00
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
CAMLexport void caml_serialize_float_8(double f)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2008-08-04 04:45:58 -07:00
|
|
|
caml_serialize_block_float_8(&f, 1);
|
2000-02-10 06:04:59 -08:00
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport void caml_serialize_block_1(void * data, intnat len)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + len > extern_limit) grow_extern_output(len);
|
2000-10-12 11:05:42 -07:00
|
|
|
memmove(extern_ptr, data, len);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr += len;
|
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport void caml_serialize_block_2(void * data, intnat len)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 2 * len > extern_limit) grow_extern_output(2 * len);
|
2000-02-10 06:04:59 -08:00
|
|
|
#ifndef ARCH_BIG_ENDIAN
|
2004-05-17 10:10:00 -07:00
|
|
|
{
|
|
|
|
unsigned char * p;
|
|
|
|
char * q;
|
|
|
|
for (p = data, q = extern_ptr; len > 0; len--, p += 2, q += 2)
|
|
|
|
Reverse_16(q, p);
|
|
|
|
extern_ptr = q;
|
|
|
|
}
|
2000-02-10 06:04:59 -08:00
|
|
|
#else
|
2000-10-12 11:05:42 -07:00
|
|
|
memmove(extern_ptr, data, len * 2);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr += len * 2;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport void caml_serialize_block_4(void * data, intnat len)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 4 * len > extern_limit) grow_extern_output(4 * len);
|
2000-02-10 06:04:59 -08:00
|
|
|
#ifndef ARCH_BIG_ENDIAN
|
2004-05-17 10:10:00 -07:00
|
|
|
{
|
|
|
|
unsigned char * p;
|
|
|
|
char * q;
|
|
|
|
for (p = data, q = extern_ptr; len > 0; len--, p += 4, q += 4)
|
|
|
|
Reverse_32(q, p);
|
|
|
|
extern_ptr = q;
|
|
|
|
}
|
2000-02-10 06:04:59 -08:00
|
|
|
#else
|
2000-10-12 11:05:42 -07:00
|
|
|
memmove(extern_ptr, data, len * 4);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr += len * 4;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport void caml_serialize_block_8(void * data, intnat len)
|
2000-02-10 06:04:59 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 8 * len > extern_limit) grow_extern_output(8 * len);
|
2000-02-10 06:04:59 -08:00
|
|
|
#ifndef ARCH_BIG_ENDIAN
|
2004-05-17 10:10:00 -07:00
|
|
|
{
|
|
|
|
unsigned char * p;
|
|
|
|
char * q;
|
|
|
|
for (p = data, q = extern_ptr; len > 0; len--, p += 8, q += 8)
|
|
|
|
Reverse_64(q, p);
|
|
|
|
extern_ptr = q;
|
|
|
|
}
|
2000-02-10 06:04:59 -08:00
|
|
|
#else
|
2000-10-12 11:05:42 -07:00
|
|
|
memmove(extern_ptr, data, len * 8);
|
2000-02-10 06:04:59 -08:00
|
|
|
extern_ptr += len * 8;
|
|
|
|
#endif
|
|
|
|
}
|
2002-02-19 06:37:44 -08:00
|
|
|
|
2005-09-22 07:21:50 -07:00
|
|
|
CAMLexport void caml_serialize_block_float_8(void * data, intnat len)
|
2002-02-19 06:37:44 -08:00
|
|
|
{
|
2004-07-19 06:21:10 -07:00
|
|
|
if (extern_ptr + 8 * len > extern_limit) grow_extern_output(8 * len);
|
2002-02-19 06:37:44 -08:00
|
|
|
#if ARCH_FLOAT_ENDIANNESS == 0x01234567
|
|
|
|
memmove(extern_ptr, data, len * 8);
|
|
|
|
extern_ptr += len * 8;
|
|
|
|
#elif ARCH_FLOAT_ENDIANNESS == 0x76543210
|
2006-09-20 04:14:37 -07:00
|
|
|
{
|
2004-06-19 09:02:07 -07:00
|
|
|
unsigned char * p;
|
|
|
|
char * q;
|
|
|
|
for (p = data, q = extern_ptr; len > 0; len--, p += 8, q += 8)
|
|
|
|
Reverse_64(q, p);
|
|
|
|
extern_ptr = q;
|
|
|
|
}
|
2002-02-19 06:37:44 -08:00
|
|
|
#else
|
2006-09-20 04:14:37 -07:00
|
|
|
{
|
2004-06-19 09:02:07 -07:00
|
|
|
unsigned char * p;
|
|
|
|
char * q;
|
|
|
|
for (p = data, q = extern_ptr; len > 0; len--, p += 8, q += 8)
|
|
|
|
Permute_64(q, 0x01234567, p, ARCH_FLOAT_ENDIANNESS);
|
|
|
|
extern_ptr = q;
|
|
|
|
}
|
2002-02-19 06:37:44 -08:00
|
|
|
#endif
|
|
|
|
}
|