1 /*
2  * Copyright (C) 2010-2011 Marcin Kościelnicki <koriakin@0x04.net>
3  * Copyright (C) 2010 Francisco Jerez <currojerez@riseup.net>
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include "rnndec.h"
27 #include <assert.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <inttypes.h>
32 #include "util.h"
33 
rnndec_newcontext(struct rnndb * db)34 struct rnndeccontext *rnndec_newcontext(struct rnndb *db) {
35 	struct rnndeccontext *res = calloc (sizeof *res, 1);
36 	res->db = db;
37 	res->colors = &envy_null_colors;
38 	return res;
39 }
40 
rnndec_varadd(struct rnndeccontext * ctx,char * varset,const char * variant)41 int rnndec_varadd(struct rnndeccontext *ctx, char *varset, const char *variant) {
42 	struct rnnenum *en = rnn_findenum(ctx->db, varset);
43 	if (!en) {
44 		fprintf (stderr, "Enum %s doesn't exist in database!\n", varset);
45 		return 0;
46 	}
47 	int i, j;
48 	for (i = 0; i < en->valsnum; i++)
49 		if (!strcasecmp(en->vals[i]->name, variant)) {
50 			struct rnndecvariant *ci = calloc (sizeof *ci, 1);
51 			ci->en = en;
52 			ci->variant = i;
53 			ADDARRAY(ctx->vars, ci);
54 			return 1;
55 		}
56 
57 	if (i == en->valsnum) {
58 		fprintf (stderr, "Variant %s doesn't exist in enum %s!\n", variant, varset);
59 		return 0;
60 	}
61 
62 	for (j = 0; j < ctx->varsnum; j++) {
63 		if (ctx->vars[j]->en == en) {
64 			ctx->vars[j]->variant = i;
65 			break;
66 		}
67 	}
68 
69 	if (i == ctx->varsnum) {
70 		struct rnndecvariant *ci = calloc (sizeof *ci, 1);
71 		ci->en = en;
72 		ci->variant = i;
73 		ADDARRAY(ctx->vars, ci);
74 	}
75 
76 	return 1;
77 }
78 
rnndec_varmatch(struct rnndeccontext * ctx,struct rnnvarinfo * vi)79 int rnndec_varmatch(struct rnndeccontext *ctx, struct rnnvarinfo *vi) {
80 	if (vi->dead)
81 		return 0;
82 	int i;
83 	for (i = 0; i < vi->varsetsnum; i++) {
84 		int j;
85 		for (j = 0; j < ctx->varsnum; j++)
86 			if (vi->varsets[i]->venum == ctx->vars[j]->en)
87 				break;
88 		if (j == ctx->varsnum) {
89 			fprintf (stderr, "I don't know which %s variant to use!\n", vi->varsets[i]->venum->name);
90 		} else {
91 			if (!vi->varsets[i]->variants[ctx->vars[j]->variant])
92 				return 0;
93 		}
94 	}
95 	return 1;
96 }
97 
98 /* see https://en.wikipedia.org/wiki/Half-precision_floating-point_format */
float16i(uint16_t val)99 static uint32_t float16i(uint16_t val)
100 {
101 	uint32_t sign = ((uint32_t)(val & 0x8000)) << 16;
102 	uint32_t frac = val & 0x3ff;
103 	int32_t  expn = (val >> 10) & 0x1f;
104 
105 	if (expn == 0) {
106 		if (frac) {
107 			/* denormalized number: */
108 			int shift = __builtin_clz(frac) - 21;
109 			frac <<= shift;
110 			expn = -shift;
111 		} else {
112 			/* +/- zero: */
113 			return sign;
114 		}
115 	} else if (expn == 0x1f) {
116 		/* Inf/NaN: */
117 		return sign | 0x7f800000 | (frac << 13);
118 	}
119 
120 	return sign | ((expn + 127 - 15) << 23) | (frac << 13);
121 }
float16(uint16_t val)122 static float float16(uint16_t val)
123 {
124 	union { uint32_t i; float f; } u;
125 	u.i = float16i(val);
126 	return u.f;
127 }
128 
rnndec_decode_enum_val(struct rnndeccontext * ctx,struct rnnvalue ** vals,int valsnum,uint64_t value)129 static const char *rnndec_decode_enum_val(struct rnndeccontext *ctx,
130 		struct rnnvalue **vals, int valsnum, uint64_t value)
131 {
132 	int i;
133 	for (i = 0; i < valsnum; i++)
134 		if (rnndec_varmatch(ctx, &vals[i]->varinfo) &&
135 				vals[i]->valvalid && vals[i]->value == value)
136 			return vals[i]->name;
137 	return NULL;
138 }
139 
rnndec_decode_enum(struct rnndeccontext * ctx,const char * enumname,uint64_t enumval)140 const char *rnndec_decode_enum(struct rnndeccontext *ctx, const char *enumname, uint64_t enumval)
141 {
142 	struct rnnenum *en = rnn_findenum (ctx->db, enumname);
143 	if (en) {
144 		return rnndec_decode_enum_val(ctx, en->vals, en->valsnum, enumval);
145 	}
146 	return NULL;
147 }
148 
149 /* The name UNK%u is used as a placeholder for bitfields that exist but
150  * have an unknown function.
151  */
is_unknown(const char * name)152 static int is_unknown(const char *name)
153 {
154 	unsigned u;
155 	return sscanf(name, "UNK%u", &u) == 1;
156 }
157 
rnndec_decodeval(struct rnndeccontext * ctx,struct rnntypeinfo * ti,uint64_t value)158 char *rnndec_decodeval(struct rnndeccontext *ctx, struct rnntypeinfo *ti, uint64_t value) {
159 	int width = ti->high - ti->low + 1;
160 	char *res = 0;
161 	int i;
162 	struct rnnvalue **vals;
163 	int valsnum;
164 	struct rnnbitfield **bitfields;
165 	int bitfieldsnum;
166 	char *tmp;
167 	const char *ctmp;
168 	uint64_t mask, value_orig;
169 	if (!ti)
170 		goto failhex;
171 	value_orig = value;
172 	value = (value & typeinfo_mask(ti)) >> ti->low;
173 	value <<= ti->shr;
174 
175 	switch (ti->type) {
176 		case RNN_TTYPE_ENUM:
177 			vals = ti->eenum->vals;
178 			valsnum = ti->eenum->valsnum;
179 			goto doenum;
180 		case RNN_TTYPE_INLINE_ENUM:
181 			vals = ti->vals;
182 			valsnum = ti->valsnum;
183 			goto doenum;
184 		doenum:
185 			ctmp = rnndec_decode_enum_val(ctx, vals, valsnum, value);
186 			if (ctmp) {
187 				asprintf (&res, "%s%s%s", ctx->colors->eval, ctmp, ctx->colors->reset);
188 				if (ti->addvariant) {
189 					rnndec_varadd(ctx, ti->eenum->name, ctmp);
190 				}
191 				break;
192 			}
193 			goto failhex;
194 		case RNN_TTYPE_BITSET:
195 			bitfields = ti->ebitset->bitfields;
196 			bitfieldsnum = ti->ebitset->bitfieldsnum;
197 			goto dobitset;
198 		case RNN_TTYPE_INLINE_BITSET:
199 			bitfields = ti->bitfields;
200 			bitfieldsnum = ti->bitfieldsnum;
201 			goto dobitset;
202 		dobitset:
203 			mask = 0;
204 			for (i = 0; i < bitfieldsnum; i++) {
205 				if (!rnndec_varmatch(ctx, &bitfields[i]->varinfo))
206 					continue;
207 				uint64_t type_mask = typeinfo_mask(&bitfields[i]->typeinfo);
208 				if (((value & type_mask) == 0) && is_unknown(bitfields[i]->name))
209 					continue;
210 				mask |= type_mask;
211 				if (bitfields[i]->typeinfo.type == RNN_TTYPE_BOOLEAN) {
212 					const char *color = is_unknown(bitfields[i]->name) ?
213 							ctx->colors->err : ctx->colors->mod;
214 					if (value & type_mask) {
215 						if (!res)
216 							asprintf (&res, "%s%s%s", color, bitfields[i]->name, ctx->colors->reset);
217 						else {
218 							asprintf (&tmp, "%s | %s%s%s", res, color, bitfields[i]->name, ctx->colors->reset);
219 							free(res);
220 							res = tmp;
221 						}
222 					}
223 					continue;
224 				}
225 				char *subval;
226 				if (is_unknown(bitfields[i]->name) && (bitfields[i]->typeinfo.type != RNN_TTYPE_A3XX_REGID)) {
227 					uint64_t field_val = value & type_mask;
228 					field_val = (field_val & typeinfo_mask(&bitfields[i]->typeinfo)) >> bitfields[i]->typeinfo.low;
229 					field_val <<= bitfields[i]->typeinfo.shr;
230 					asprintf (&subval, "%s%#"PRIx64"%s", ctx->colors->err, field_val, ctx->colors->reset);
231 				} else {
232 					subval = rnndec_decodeval(ctx, &bitfields[i]->typeinfo, value & type_mask);
233 				}
234 				if (!res)
235 					asprintf (&res, "%s%s%s = %s", ctx->colors->rname, bitfields[i]->name, ctx->colors->reset, subval);
236 				else {
237 					asprintf (&tmp, "%s | %s%s%s = %s", res, ctx->colors->rname, bitfields[i]->name, ctx->colors->reset, subval);
238 					free(res);
239 					res = tmp;
240 				}
241 				free(subval);
242 			}
243 			if (value & ~mask) {
244 				if (!res)
245 					asprintf (&res, "%s%#"PRIx64"%s", ctx->colors->err, value & ~mask, ctx->colors->reset);
246 				else {
247 					asprintf (&tmp, "%s | %s%#"PRIx64"%s", res, ctx->colors->err, value & ~mask, ctx->colors->reset);
248 					free(res);
249 					res = tmp;
250 				}
251 			}
252 			if (!res)
253 				asprintf (&res, "%s0%s", ctx->colors->num, ctx->colors->reset);
254 			asprintf (&tmp, "{ %s }", res);
255 			free(res);
256 			return tmp;
257 		case RNN_TTYPE_SPECTYPE:
258 			return rnndec_decodeval(ctx, &ti->spectype->typeinfo, value);
259 		case RNN_TTYPE_HEX:
260 			asprintf (&res, "%s%#"PRIx64"%s", ctx->colors->num, value, ctx->colors->reset);
261 			break;
262 		case RNN_TTYPE_FIXED:
263 			if (value & UINT64_C(1) << (width-1)) {
264 				asprintf (&res, "%s-%lf%s", ctx->colors->num,
265 						((double)((UINT64_C(1) << width) - value)) / ((double)(1 << ti->radix)),
266 						ctx->colors->reset);
267 				break;
268 			}
269 			/* fallthrough */
270 		case RNN_TTYPE_UFIXED:
271 			asprintf (&res, "%s%lf%s", ctx->colors->num,
272 					((double)value) / ((double)(1LL << ti->radix)),
273 					ctx->colors->reset);
274 			break;
275 		case RNN_TTYPE_A3XX_REGID:
276 			asprintf (&res, "%sr%"PRIu64".%c%s", ctx->colors->num, (value >> 2), "xyzw"[value & 0x3], ctx->colors->reset);
277 			break;
278 		case RNN_TTYPE_UINT:
279 			asprintf (&res, "%s%"PRIu64"%s", ctx->colors->num, value, ctx->colors->reset);
280 			break;
281 		case RNN_TTYPE_INT:
282 			if (value & UINT64_C(1) << (width-1))
283 				asprintf (&res, "%s-%"PRIi64"%s", ctx->colors->num, (UINT64_C(1) << width) - value, ctx->colors->reset);
284 			else
285 				asprintf (&res, "%s%"PRIi64"%s", ctx->colors->num, value, ctx->colors->reset);
286 			break;
287 		case RNN_TTYPE_BOOLEAN:
288 			if (value == 0) {
289 				asprintf (&res, "%sFALSE%s", ctx->colors->eval, ctx->colors->reset);
290 			} else if (value == 1) {
291 				asprintf (&res, "%sTRUE%s", ctx->colors->eval, ctx->colors->reset);
292 			}
293 			break;
294 		case RNN_TTYPE_FLOAT: {
295 			union { uint64_t i; float f; double d; } val;
296 			val.i = value;
297 			if (width == 64)
298 				asprintf(&res, "%s%f%s", ctx->colors->num,
299 					val.d, ctx->colors->reset);
300 			else if (width == 32)
301 				asprintf(&res, "%s%f%s", ctx->colors->num,
302 					val.f, ctx->colors->reset);
303 			else if (width == 16)
304 				asprintf(&res, "%s%f%s", ctx->colors->num,
305 					float16(value), ctx->colors->reset);
306 			else
307 				goto failhex;
308 
309 			break;
310 		}
311 		failhex:
312 		default:
313 			asprintf (&res, "%s%#"PRIx64"%s", ctx->colors->num, value, ctx->colors->reset);
314 			break;
315 	}
316 	if (value_orig & ~typeinfo_mask(ti)) {
317 		asprintf (&tmp, "%s | %s%#"PRIx64"%s", res, ctx->colors->err, value_orig & ~typeinfo_mask(ti), ctx->colors->reset);
318 		free(res);
319 		res = tmp;
320 	}
321 	return res;
322 }
323 
appendidx(struct rnndeccontext * ctx,char * name,uint64_t idx,struct rnnenum * index)324 static char *appendidx (struct rnndeccontext *ctx, char *name, uint64_t idx, struct rnnenum *index) {
325 	char *res;
326 	const char *index_name = NULL;
327 
328 	if (index)
329 		index_name = rnndec_decode_enum_val(ctx, index->vals, index->valsnum, idx);
330 
331 	if (index_name)
332 		asprintf (&res, "%s[%s%s%s]", name, ctx->colors->eval, index_name, ctx->colors->reset);
333 	else
334 		asprintf (&res, "%s[%s%#"PRIx64"%s]", name, ctx->colors->num, idx, ctx->colors->reset);
335 
336 	free (name);
337 	return res;
338 }
339 
340 /* This could probably be made to work for stripes too.. */
get_array_idx_offset(struct rnndelem * elem,uint64_t addr,uint64_t * idx,uint64_t * offset)341 static int get_array_idx_offset(struct rnndelem *elem, uint64_t addr, uint64_t *idx, uint64_t *offset)
342 {
343 	if (elem->offsets) {
344 		int i;
345 		for (i = 0; i < elem->offsetsnum; i++) {
346 			uint64_t o = elem->offsets[i];
347 			if ((o <= addr) && (addr < (o + elem->stride))) {
348 				*idx = i;
349 				*offset = addr - o;
350 				return 0;
351 			}
352 		}
353 		return -1;
354 	} else {
355 		if (addr < elem->offset)
356 			return -1;
357 
358 		*idx = (addr - elem->offset) / elem->stride;
359 		*offset = (addr - elem->offset) % elem->stride;
360 
361 		if (elem->length && (*idx >= elem->length))
362 			return -1;
363 
364 		return 0;
365 	}
366 }
367 
trymatch(struct rnndeccontext * ctx,struct rnndelem ** elems,int elemsnum,uint64_t addr,int write,int dwidth,uint64_t * indices,int indicesnum)368 static struct rnndecaddrinfo *trymatch (struct rnndeccontext *ctx, struct rnndelem **elems, int elemsnum, uint64_t addr, int write, int dwidth, uint64_t *indices, int indicesnum) {
369 	struct rnndecaddrinfo *res;
370 	int i, j;
371 	for (i = 0; i < elemsnum; i++) {
372 		if (!rnndec_varmatch(ctx, &elems[i]->varinfo))
373 			continue;
374 		uint64_t offset, idx;
375 		char *tmp, *name;
376 		switch (elems[i]->type) {
377 			case RNN_ETYPE_REG:
378 				if (addr < elems[i]->offset)
379 					break;
380 				if (elems[i]->stride) {
381 					idx = (addr-elems[i]->offset)/elems[i]->stride;
382 					offset = (addr-elems[i]->offset)%elems[i]->stride;
383 				} else {
384 					idx = 0;
385 					offset = addr-elems[i]->offset;
386 				}
387 				if (offset >= elems[i]->width/dwidth)
388 					break;
389 				if (elems[i]->length && idx >= elems[i]->length)
390 					break;
391 				res = calloc (sizeof *res, 1);
392 				res->typeinfo = &elems[i]->typeinfo;
393 				res->width = elems[i]->width;
394 				asprintf (&res->name, "%s%s%s", ctx->colors->rname, elems[i]->name, ctx->colors->reset);
395 				for (j = 0; j < indicesnum; j++)
396 					res->name = appendidx(ctx, res->name, indices[j], NULL);
397 				if (elems[i]->length != 1)
398 					res->name = appendidx(ctx, res->name, idx, elems[i]->index);
399 				if (offset) {
400 					asprintf (&tmp, "%s+%s%#"PRIx64"%s", res->name, ctx->colors->err, offset, ctx->colors->reset);
401 					free(res->name);
402 					res->name = tmp;
403 				}
404 				return res;
405 			case RNN_ETYPE_STRIPE:
406 				for (idx = 0; idx < elems[i]->length || !elems[i]->length; idx++) {
407 					if (addr < elems[i]->offset + elems[i]->stride * idx)
408 						break;
409 					offset = addr - (elems[i]->offset + elems[i]->stride * idx);
410 					int extraidx = (elems[i]->length != 1);
411 					int nindnum = (elems[i]->name ? 0 : indicesnum + extraidx);
412 					uint64_t nind[nindnum];
413 					if (!elems[i]->name) {
414 						for (j = 0; j < indicesnum; j++)
415 							nind[j] = indices[j];
416 						if (extraidx)
417 							nind[indicesnum] = idx;
418 					}
419 					res = trymatch (ctx, elems[i]->subelems, elems[i]->subelemsnum, offset, write, dwidth, nind, nindnum);
420 					if (!res)
421 						continue;
422 					if (!elems[i]->name)
423 						return res;
424 					asprintf (&name, "%s%s%s", ctx->colors->rname, elems[i]->name, ctx->colors->reset);
425 					for (j = 0; j < indicesnum; j++)
426 						name = appendidx(ctx, name, indices[j], NULL);
427 					if (elems[i]->length != 1)
428 						name = appendidx(ctx, name, idx, elems[i]->index);
429 					asprintf (&tmp, "%s.%s", name, res->name);
430 					free(name);
431 					free(res->name);
432 					res->name = tmp;
433 					return res;
434 				}
435 				break;
436 			case RNN_ETYPE_ARRAY:
437 				if (get_array_idx_offset(elems[i], addr, &idx, &offset))
438 					break;
439 				asprintf (&name, "%s%s%s", ctx->colors->rname, elems[i]->name, ctx->colors->reset);
440 				for (j = 0; j < indicesnum; j++)
441 					name = appendidx(ctx, name, indices[j], NULL);
442 				if (elems[i]->length != 1)
443 					name = appendidx(ctx, name, idx, elems[i]->index);
444 				if ((res = trymatch (ctx, elems[i]->subelems, elems[i]->subelemsnum, offset, write, dwidth, 0, 0))) {
445 					asprintf (&tmp, "%s.%s", name, res->name);
446 					free(name);
447 					free(res->name);
448 					res->name = tmp;
449 					return res;
450 				}
451 				res = calloc (sizeof *res, 1);
452 				asprintf (&tmp, "%s+%s%#"PRIx64"%s", name, ctx->colors->err, offset, ctx->colors->reset);
453 				free(name);
454 				res->name = tmp;
455 				return res;
456 			default:
457 				break;
458 		}
459 	}
460 	return 0;
461 }
462 
rnndec_checkaddr(struct rnndeccontext * ctx,struct rnndomain * domain,uint64_t addr,int write)463 int rnndec_checkaddr(struct rnndeccontext *ctx, struct rnndomain *domain, uint64_t addr, int write) {
464 	struct rnndecaddrinfo *res = trymatch(ctx, domain->subelems, domain->subelemsnum, addr, write, domain->width, 0, 0);
465 	if (res) {
466 		free(res->name);
467 		free(res);
468 	}
469 	return res != NULL;
470 }
471 
rnndec_decodeaddr(struct rnndeccontext * ctx,struct rnndomain * domain,uint64_t addr,int write)472 struct rnndecaddrinfo *rnndec_decodeaddr(struct rnndeccontext *ctx, struct rnndomain *domain, uint64_t addr, int write) {
473 	struct rnndecaddrinfo *res = trymatch(ctx, domain->subelems, domain->subelemsnum, addr, write, domain->width, 0, 0);
474 	if (res)
475 		return res;
476 	res = calloc (sizeof *res, 1);
477 	asprintf (&res->name, "%s%#"PRIx64"%s", ctx->colors->err, addr, ctx->colors->reset);
478 	return res;
479 }
480 
tryreg(struct rnndeccontext * ctx,struct rnndelem ** elems,int elemsnum,int dwidth,const char * name,uint64_t * offset)481 static unsigned tryreg(struct rnndeccontext *ctx, struct rnndelem **elems, int elemsnum,
482 		int dwidth, const char *name, uint64_t *offset)
483 {
484 	int i;
485 	unsigned ret;
486 	const char *suffix = strchr(name, '[');
487 	unsigned n = suffix ? (suffix - name) : strlen(name);
488 	const char *dotsuffix = strchr(name, '.');
489 	unsigned dotn = dotsuffix ? (dotsuffix - name) : strlen(name);
490 
491 	const char *child = NULL;
492 	unsigned idx = 0;
493 
494 	if (suffix) {
495 		const char *tmp = strchr(suffix, ']');
496 		idx = strtol(suffix+1, NULL, 0);
497 		child = tmp+2;
498 	}
499 
500 	for (i = 0; i < elemsnum; i++) {
501 		struct rnndelem *elem = elems[i];
502 		if (!rnndec_varmatch(ctx, &elem->varinfo))
503 			continue;
504 		int match = elem->name && (strlen(elem->name) == n) && !strncmp(elem->name, name, n);
505 		switch (elem->type) {
506 			case RNN_ETYPE_REG:
507 				if (match) {
508 					assert(!suffix);
509 					*offset = elem->offset;
510 					return 1;
511 				}
512 				break;
513 			case RNN_ETYPE_STRIPE:
514 				if (elem->name) {
515 					if (!dotsuffix)
516 						break;
517 					if (strlen(elem->name) != dotn || strncmp(elem->name, name, dotn))
518 						break;
519 				}
520 				ret = tryreg(ctx, elem->subelems, elem->subelemsnum, dwidth,
521 					elem->name ? dotsuffix : name, offset);
522 				if (ret)
523 					return 1;
524 				break;
525 			case RNN_ETYPE_ARRAY:
526 				if (match) {
527 					assert(suffix);
528 					ret = tryreg(ctx, elem->subelems, elem->subelemsnum, dwidth, child, offset);
529 					if (ret) {
530 						*offset += elem->offset + (idx * elem->stride);
531 						return 1;
532 					}
533 				}
534 				break;
535 			default:
536 				break;
537 		}
538 	}
539 	return 0;
540 }
541 
rnndec_decodereg(struct rnndeccontext * ctx,struct rnndomain * domain,const char * name)542 uint64_t rnndec_decodereg(struct rnndeccontext *ctx, struct rnndomain *domain, const char *name)
543 {
544 	uint64_t offset;
545 	if (tryreg(ctx, domain->subelems, domain->subelemsnum, domain->width, name, &offset)) {
546 		return offset;
547 	} else {
548 		return 0;
549 	}
550 }
551