| 1 | /* | 
|---|
| 2 | ** LuaJIT VM builder: library definition compiler. | 
|---|
| 3 | ** Copyright (C) 2005-2014 Mike Pall. See Copyright Notice in luajit.h | 
|---|
| 4 | */ | 
|---|
| 5 |  | 
|---|
| 6 | #include "buildvm.h" | 
|---|
| 7 | #include "lj_obj.h" | 
|---|
| 8 | #include "lj_lib.h" | 
|---|
| 9 |  | 
|---|
| 10 | /* Context for library definitions. */ | 
|---|
| 11 | static uint8_t obuf[8192]; | 
|---|
| 12 | static uint8_t *optr; | 
|---|
| 13 | static char modname[80]; | 
|---|
| 14 | static size_t modnamelen; | 
|---|
| 15 | static char funcname[80]; | 
|---|
| 16 | static int modstate, regfunc; | 
|---|
| 17 | static int ffid, recffid, ffasmfunc; | 
|---|
| 18 |  | 
|---|
| 19 | enum { | 
|---|
| 20 | REGFUNC_OK, | 
|---|
| 21 | REGFUNC_NOREG, | 
|---|
| 22 | REGFUNC_NOREGUV | 
|---|
| 23 | }; | 
|---|
| 24 |  | 
|---|
| 25 | static void libdef_name(const char *p, int kind) | 
|---|
| 26 | { | 
|---|
| 27 | size_t n = strlen(p); | 
|---|
| 28 | if (kind != LIBINIT_STRING) { | 
|---|
| 29 | if (n > modnamelen && p[modnamelen] == '_' && | 
|---|
| 30 | !strncmp(p, modname, modnamelen)) { | 
|---|
| 31 | p += modnamelen+1; | 
|---|
| 32 | n -= modnamelen+1; | 
|---|
| 33 | } | 
|---|
| 34 | } | 
|---|
| 35 | if (n > LIBINIT_MAXSTR) { | 
|---|
| 36 | fprintf(stderr, "Error: string too long: '%s'\n",  p); | 
|---|
| 37 | exit(1); | 
|---|
| 38 | } | 
|---|
| 39 | if (optr+1+n+2 > obuf+sizeof(obuf)) {  /* +2 for caller. */ | 
|---|
| 40 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 41 | exit(1); | 
|---|
| 42 | } | 
|---|
| 43 | *optr++ = (uint8_t)(n | kind); | 
|---|
| 44 | memcpy(optr, p, n); | 
|---|
| 45 | optr += n; | 
|---|
| 46 | } | 
|---|
| 47 |  | 
|---|
| 48 | static void libdef_endmodule(BuildCtx *ctx) | 
|---|
| 49 | { | 
|---|
| 50 | if (modstate != 0) { | 
|---|
| 51 | char line[80]; | 
|---|
| 52 | const uint8_t *p; | 
|---|
| 53 | int n; | 
|---|
| 54 | if (modstate == 1) | 
|---|
| 55 | fprintf(ctx->fp, "  (lua_CFunction)0"); | 
|---|
| 56 | fprintf(ctx->fp, "\n};\n"); | 
|---|
| 57 | fprintf(ctx->fp, "static const uint8_t %s%s[] = {\n", | 
|---|
| 58 | LABEL_PREFIX_LIBINIT, modname); | 
|---|
| 59 | line[0] = '\0'; | 
|---|
| 60 | for (n = 0, p = obuf; p < optr; p++) { | 
|---|
| 61 | n += sprintf(line+n, "%d,", *p); | 
|---|
| 62 | if (n >= 75) { | 
|---|
| 63 | fprintf(ctx->fp, "%s\n", line); | 
|---|
| 64 | n = 0; | 
|---|
| 65 | line[0] = '\0'; | 
|---|
| 66 | } | 
|---|
| 67 | } | 
|---|
| 68 | fprintf(ctx->fp, "%s%d\n};\n#endif\n\n", line, LIBINIT_END); | 
|---|
| 69 | } | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | static void libdef_module(BuildCtx *ctx, char *p, int arg) | 
|---|
| 73 | { | 
|---|
| 74 | UNUSED(arg); | 
|---|
| 75 | if (ctx->mode == BUILD_libdef) { | 
|---|
| 76 | libdef_endmodule(ctx); | 
|---|
| 77 | optr = obuf; | 
|---|
| 78 | *optr++ = (uint8_t)ffid; | 
|---|
| 79 | *optr++ = (uint8_t)ffasmfunc; | 
|---|
| 80 | *optr++ = 0;  /* Hash table size. */ | 
|---|
| 81 | modstate = 1; | 
|---|
| 82 | fprintf(ctx->fp, "#ifdef %sMODULE_%s\n", LIBDEF_PREFIX, p); | 
|---|
| 83 | fprintf(ctx->fp, "#undef %sMODULE_%s\n", LIBDEF_PREFIX, p); | 
|---|
| 84 | fprintf(ctx->fp, "static const lua_CFunction %s%s[] = {\n", | 
|---|
| 85 | LABEL_PREFIX_LIBCF, p); | 
|---|
| 86 | } | 
|---|
| 87 | modnamelen = strlen(p); | 
|---|
| 88 | if (modnamelen > sizeof(modname)-1) { | 
|---|
| 89 | fprintf(stderr, "Error: module name too long: '%s'\n", p); | 
|---|
| 90 | exit(1); | 
|---|
| 91 | } | 
|---|
| 92 | strcpy(modname, p); | 
|---|
| 93 | } | 
|---|
| 94 |  | 
|---|
| 95 | static int find_ffofs(BuildCtx *ctx, const char *name) | 
|---|
| 96 | { | 
|---|
| 97 | int i; | 
|---|
| 98 | for (i = 0; i < ctx->nglob; i++) { | 
|---|
| 99 | const char *gl = ctx->globnames[i]; | 
|---|
| 100 | if (gl[0] == 'f' && gl[1] == 'f' && gl[2] == '_' && !strcmp(gl+3, name)) { | 
|---|
| 101 | return (int)((uint8_t *)ctx->glob[i] - ctx->code); | 
|---|
| 102 | } | 
|---|
| 103 | } | 
|---|
| 104 | fprintf(stderr, "Error: undefined fast function %s%s\n", | 
|---|
| 105 | LABEL_PREFIX_FF, name); | 
|---|
| 106 | exit(1); | 
|---|
| 107 | } | 
|---|
| 108 |  | 
|---|
| 109 | static void libdef_func(BuildCtx *ctx, char *p, int arg) | 
|---|
| 110 | { | 
|---|
| 111 | if (arg != LIBINIT_CF) | 
|---|
| 112 | ffasmfunc++; | 
|---|
| 113 | if (ctx->mode == BUILD_libdef) { | 
|---|
| 114 | if (modstate == 0) { | 
|---|
| 115 | fprintf(stderr, "Error: no module for function definition %s\n", p); | 
|---|
| 116 | exit(1); | 
|---|
| 117 | } | 
|---|
| 118 | if (regfunc == REGFUNC_NOREG) { | 
|---|
| 119 | if (optr+1 > obuf+sizeof(obuf)) { | 
|---|
| 120 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 121 | exit(1); | 
|---|
| 122 | } | 
|---|
| 123 | *optr++ = LIBINIT_FFID; | 
|---|
| 124 | } else { | 
|---|
| 125 | if (arg != LIBINIT_ASM_) { | 
|---|
| 126 | if (modstate != 1) fprintf(ctx->fp, ",\n"); | 
|---|
| 127 | modstate = 2; | 
|---|
| 128 | fprintf(ctx->fp, "  %s%s", arg ? LABEL_PREFIX_FFH : LABEL_PREFIX_CF, p); | 
|---|
| 129 | } | 
|---|
| 130 | if (regfunc != REGFUNC_NOREGUV) obuf[2]++;  /* Bump hash table size. */ | 
|---|
| 131 | libdef_name(regfunc == REGFUNC_NOREGUV ? "": p, arg); | 
|---|
| 132 | } | 
|---|
| 133 | } else if (ctx->mode == BUILD_ffdef) { | 
|---|
| 134 | fprintf(ctx->fp, "FFDEF(%s)\n", p); | 
|---|
| 135 | } else if (ctx->mode == BUILD_recdef) { | 
|---|
| 136 | if (strlen(p) > sizeof(funcname)-1) { | 
|---|
| 137 | fprintf(stderr, "Error: function name too long: '%s'\n", p); | 
|---|
| 138 | exit(1); | 
|---|
| 139 | } | 
|---|
| 140 | strcpy(funcname, p); | 
|---|
| 141 | } else if (ctx->mode == BUILD_vmdef) { | 
|---|
| 142 | int i; | 
|---|
| 143 | for (i = 1; p[i] && modname[i-1]; i++) | 
|---|
| 144 | if (p[i] == '_') p[i] = '.'; | 
|---|
| 145 | fprintf(ctx->fp, "\"%s\",\n", p); | 
|---|
| 146 | } else if (ctx->mode == BUILD_bcdef) { | 
|---|
| 147 | if (arg != LIBINIT_CF) | 
|---|
| 148 | fprintf(ctx->fp, ",\n%d", find_ffofs(ctx, p)); | 
|---|
| 149 | } | 
|---|
| 150 | ffid++; | 
|---|
| 151 | regfunc = REGFUNC_OK; | 
|---|
| 152 | } | 
|---|
| 153 |  | 
|---|
| 154 | static uint32_t find_rec(char *name) | 
|---|
| 155 | { | 
|---|
| 156 | char *p = (char *)obuf; | 
|---|
| 157 | uint32_t n; | 
|---|
| 158 | for (n = 2; *p; n++) { | 
|---|
| 159 | if (strcmp(p, name) == 0) | 
|---|
| 160 | return n; | 
|---|
| 161 | p += strlen(p)+1; | 
|---|
| 162 | } | 
|---|
| 163 | if (p+strlen(name)+1 >= (char *)obuf+sizeof(obuf)) { | 
|---|
| 164 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 165 | exit(1); | 
|---|
| 166 | } | 
|---|
| 167 | strcpy(p, name); | 
|---|
| 168 | return n; | 
|---|
| 169 | } | 
|---|
| 170 |  | 
|---|
| 171 | static void libdef_rec(BuildCtx *ctx, char *p, int arg) | 
|---|
| 172 | { | 
|---|
| 173 | UNUSED(arg); | 
|---|
| 174 | if (ctx->mode == BUILD_recdef) { | 
|---|
| 175 | char *q; | 
|---|
| 176 | uint32_t n; | 
|---|
| 177 | for (; recffid+1 < ffid; recffid++) | 
|---|
| 178 | fprintf(ctx->fp, ",\n0"); | 
|---|
| 179 | recffid = ffid; | 
|---|
| 180 | if (*p == '.') p = funcname; | 
|---|
| 181 | q = strchr(p, ' '); | 
|---|
| 182 | if (q) *q++ = '\0'; | 
|---|
| 183 | n = find_rec(p); | 
|---|
| 184 | if (q) | 
|---|
| 185 | fprintf(ctx->fp, ",\n0x%02x00+(%s)", n, q); | 
|---|
| 186 | else | 
|---|
| 187 | fprintf(ctx->fp, ",\n0x%02x00", n); | 
|---|
| 188 | } | 
|---|
| 189 | } | 
|---|
| 190 |  | 
|---|
| 191 | static void memcpy_endian(void *dst, void *src, size_t n) | 
|---|
| 192 | { | 
|---|
| 193 | union { uint8_t b; uint32_t u; } host_endian; | 
|---|
| 194 | host_endian.u = 1; | 
|---|
| 195 | if (host_endian.b == LJ_ENDIAN_SELECT(1, 0)) { | 
|---|
| 196 | memcpy(dst, src, n); | 
|---|
| 197 | } else { | 
|---|
| 198 | size_t i; | 
|---|
| 199 | for (i = 0; i < n; i++) | 
|---|
| 200 | ((uint8_t *)dst)[i] = ((uint8_t *)src)[n-i-1]; | 
|---|
| 201 | } | 
|---|
| 202 | } | 
|---|
| 203 |  | 
|---|
| 204 | static void libdef_push(BuildCtx *ctx, char *p, int arg) | 
|---|
| 205 | { | 
|---|
| 206 | UNUSED(arg); | 
|---|
| 207 | if (ctx->mode == BUILD_libdef) { | 
|---|
| 208 | int len = (int)strlen(p); | 
|---|
| 209 | if (*p == '"') { | 
|---|
| 210 | if (len > 1 && p[len-1] == '"') { | 
|---|
| 211 | p[len-1] = '\0'; | 
|---|
| 212 | libdef_name(p+1, LIBINIT_STRING); | 
|---|
| 213 | return; | 
|---|
| 214 | } | 
|---|
| 215 | } else if (*p >= '0' && *p <= '9') { | 
|---|
| 216 | char *ep; | 
|---|
| 217 | double d = strtod(p, &ep); | 
|---|
| 218 | if (*ep == '\0') { | 
|---|
| 219 | if (optr+1+sizeof(double) > obuf+sizeof(obuf)) { | 
|---|
| 220 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 221 | exit(1); | 
|---|
| 222 | } | 
|---|
| 223 | *optr++ = LIBINIT_NUMBER; | 
|---|
| 224 | memcpy_endian(optr, &d, sizeof(double)); | 
|---|
| 225 | optr += sizeof(double); | 
|---|
| 226 | return; | 
|---|
| 227 | } | 
|---|
| 228 | } else if (!strcmp(p, "lastcl")) { | 
|---|
| 229 | if (optr+1 > obuf+sizeof(obuf)) { | 
|---|
| 230 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 231 | exit(1); | 
|---|
| 232 | } | 
|---|
| 233 | *optr++ = LIBINIT_LASTCL; | 
|---|
| 234 | return; | 
|---|
| 235 | } else if (len > 4 && !strncmp(p, "top-", 4)) { | 
|---|
| 236 | if (optr+2 > obuf+sizeof(obuf)) { | 
|---|
| 237 | fprintf(stderr, "Error: output buffer overflow\n"); | 
|---|
| 238 | exit(1); | 
|---|
| 239 | } | 
|---|
| 240 | *optr++ = LIBINIT_COPY; | 
|---|
| 241 | *optr++ = (uint8_t)atoi(p+4); | 
|---|
| 242 | return; | 
|---|
| 243 | } | 
|---|
| 244 | fprintf(stderr, "Error: bad value for %sPUSH(%s)\n", LIBDEF_PREFIX, p); | 
|---|
| 245 | exit(1); | 
|---|
| 246 | } | 
|---|
| 247 | } | 
|---|
| 248 |  | 
|---|
| 249 | static void libdef_set(BuildCtx *ctx, char *p, int arg) | 
|---|
| 250 | { | 
|---|
| 251 | UNUSED(arg); | 
|---|
| 252 | if (ctx->mode == BUILD_libdef) { | 
|---|
| 253 | if (p[0] == '!' && p[1] == '\0') p[0] = '\0';  /* Set env. */ | 
|---|
| 254 | libdef_name(p, LIBINIT_STRING); | 
|---|
| 255 | *optr++ = LIBINIT_SET; | 
|---|
| 256 | obuf[2]++;  /* Bump hash table size. */ | 
|---|
| 257 | } | 
|---|
| 258 | } | 
|---|
| 259 |  | 
|---|
| 260 | static void libdef_regfunc(BuildCtx *ctx, char *p, int arg) | 
|---|
| 261 | { | 
|---|
| 262 | UNUSED(ctx); UNUSED(p); | 
|---|
| 263 | regfunc = arg; | 
|---|
| 264 | } | 
|---|
| 265 |  | 
|---|
| 266 | typedef void (*LibDefFunc)(BuildCtx *ctx, char *p, int arg); | 
|---|
| 267 |  | 
|---|
| 268 | typedef struct LibDefHandler { | 
|---|
| 269 | const char *suffix; | 
|---|
| 270 | const char *stop; | 
|---|
| 271 | const LibDefFunc func; | 
|---|
| 272 | const int arg; | 
|---|
| 273 | } LibDefHandler; | 
|---|
| 274 |  | 
|---|
| 275 | static const LibDefHandler libdef_handlers[] = { | 
|---|
| 276 | { "MODULE_", " \t\r\n",	libdef_module,		0 }, | 
|---|
| 277 | { "CF(", ")",		libdef_func,		LIBINIT_CF }, | 
|---|
| 278 | { "ASM(", ")",		libdef_func,		LIBINIT_ASM }, | 
|---|
| 279 | { "ASM_(", ")",		libdef_func,		LIBINIT_ASM_ }, | 
|---|
| 280 | { "REC(", ")",		libdef_rec,		0 }, | 
|---|
| 281 | { "PUSH(", ")",		libdef_push,		0 }, | 
|---|
| 282 | { "SET(", ")",		libdef_set,		0 }, | 
|---|
| 283 | { "NOREGUV",	NULL,		libdef_regfunc,		REGFUNC_NOREGUV }, | 
|---|
| 284 | { "NOREG",	NULL,		libdef_regfunc,		REGFUNC_NOREG }, | 
|---|
| 285 | { NULL,	NULL,		(LibDefFunc)0,		0 } | 
|---|
| 286 | }; | 
|---|
| 287 |  | 
|---|
| 288 | /* Emit C source code for library function definitions. */ | 
|---|
| 289 | void emit_lib(BuildCtx *ctx) | 
|---|
| 290 | { | 
|---|
| 291 | const char *fname; | 
|---|
| 292 |  | 
|---|
| 293 | if (ctx->mode == BUILD_ffdef || ctx->mode == BUILD_libdef || | 
|---|
| 294 | ctx->mode == BUILD_recdef) | 
|---|
| 295 | fprintf(ctx->fp, "/* This is a generated file. DO NOT EDIT! */\n\n"); | 
|---|
| 296 | else if (ctx->mode == BUILD_vmdef) | 
|---|
| 297 | fprintf(ctx->fp, "ffnames = {\n[0]=\"Lua\",\n\"C\",\n"); | 
|---|
| 298 | if (ctx->mode == BUILD_recdef) | 
|---|
| 299 | fprintf(ctx->fp, "static const uint16_t recff_idmap[] = {\n0,\n0x0100"); | 
|---|
| 300 | recffid = ffid = FF_C+1; | 
|---|
| 301 | ffasmfunc = 0; | 
|---|
| 302 |  | 
|---|
| 303 | while ((fname = *ctx->args++)) { | 
|---|
| 304 | char buf[256];  /* We don't care about analyzing lines longer than that. */ | 
|---|
| 305 | FILE *fp; | 
|---|
| 306 | if (fname[0] == '-' && fname[1] == '\0') { | 
|---|
| 307 | fp = stdin; | 
|---|
| 308 | } else { | 
|---|
| 309 | fp = fopen(fname, "r"); | 
|---|
| 310 | if (!fp) { | 
|---|
| 311 | fprintf(stderr, "Error: cannot open input file '%s': %s\n", | 
|---|
| 312 | fname, strerror(errno)); | 
|---|
| 313 | exit(1); | 
|---|
| 314 | } | 
|---|
| 315 | } | 
|---|
| 316 | modstate = 0; | 
|---|
| 317 | regfunc = REGFUNC_OK; | 
|---|
| 318 | while (fgets(buf, sizeof(buf), fp) != NULL) { | 
|---|
| 319 | char *p; | 
|---|
| 320 | /* Simplistic pre-processor. Only handles top-level #if/#endif. */ | 
|---|
| 321 | if (buf[0] == '#' && buf[1] == 'i' && buf[2] == 'f') { | 
|---|
| 322 | int ok = 1; | 
|---|
| 323 | if (!strcmp(buf, "#if LJ_52\n")) | 
|---|
| 324 | ok = LJ_52; | 
|---|
| 325 | else if (!strcmp(buf, "#if LJ_HASJIT\n")) | 
|---|
| 326 | ok = LJ_HASJIT; | 
|---|
| 327 | else if (!strcmp(buf, "#if LJ_HASFFI\n")) | 
|---|
| 328 | ok = LJ_HASFFI; | 
|---|
| 329 | if (!ok) { | 
|---|
| 330 | int lvl = 1; | 
|---|
| 331 | while (fgets(buf, sizeof(buf), fp) != NULL) { | 
|---|
| 332 | if (buf[0] == '#' && buf[1] == 'e' && buf[2] == 'n') { | 
|---|
| 333 | if (--lvl == 0) break; | 
|---|
| 334 | } else if (buf[0] == '#' && buf[1] == 'i' && buf[2] == 'f') { | 
|---|
| 335 | lvl++; | 
|---|
| 336 | } | 
|---|
| 337 | } | 
|---|
| 338 | continue; | 
|---|
| 339 | } | 
|---|
| 340 | } | 
|---|
| 341 | for (p = buf; (p = strstr(p, LIBDEF_PREFIX)) != NULL; ) { | 
|---|
| 342 | const LibDefHandler *ldh; | 
|---|
| 343 | p += sizeof(LIBDEF_PREFIX)-1; | 
|---|
| 344 | for (ldh = libdef_handlers; ldh->suffix != NULL; ldh++) { | 
|---|
| 345 | size_t n, len = strlen(ldh->suffix); | 
|---|
| 346 | if (!strncmp(p, ldh->suffix, len)) { | 
|---|
| 347 | p += len; | 
|---|
| 348 | n = ldh->stop ? strcspn(p, ldh->stop) : 0; | 
|---|
| 349 | if (!p[n]) break; | 
|---|
| 350 | p[n] = '\0'; | 
|---|
| 351 | ldh->func(ctx, p, ldh->arg); | 
|---|
| 352 | p += n+1; | 
|---|
| 353 | break; | 
|---|
| 354 | } | 
|---|
| 355 | } | 
|---|
| 356 | if (ldh->suffix == NULL) { | 
|---|
| 357 | buf[strlen(buf)-1] = '\0'; | 
|---|
| 358 | fprintf(stderr, "Error: unknown library definition tag %s%s\n", | 
|---|
| 359 | LIBDEF_PREFIX, p); | 
|---|
| 360 | exit(1); | 
|---|
| 361 | } | 
|---|
| 362 | } | 
|---|
| 363 | } | 
|---|
| 364 | fclose(fp); | 
|---|
| 365 | if (ctx->mode == BUILD_libdef) { | 
|---|
| 366 | libdef_endmodule(ctx); | 
|---|
| 367 | } | 
|---|
| 368 | } | 
|---|
| 369 |  | 
|---|
| 370 | if (ctx->mode == BUILD_ffdef) { | 
|---|
| 371 | fprintf(ctx->fp, "\n#undef FFDEF\n\n"); | 
|---|
| 372 | fprintf(ctx->fp, | 
|---|
| 373 | "#ifndef FF_NUM_ASMFUNC\n#define FF_NUM_ASMFUNC %d\n#endif\n\n", | 
|---|
| 374 | ffasmfunc); | 
|---|
| 375 | } else if (ctx->mode == BUILD_vmdef) { | 
|---|
| 376 | fprintf(ctx->fp, "}\n\n"); | 
|---|
| 377 | } else if (ctx->mode == BUILD_bcdef) { | 
|---|
| 378 | int i; | 
|---|
| 379 | fprintf(ctx->fp, "\n};\n\n"); | 
|---|
| 380 | fprintf(ctx->fp, "LJ_DATADEF const uint16_t lj_bc_mode[] = {\n"); | 
|---|
| 381 | fprintf(ctx->fp, "BCDEF(BCMODE)\n"); | 
|---|
| 382 | for (i = ffasmfunc-1; i > 0; i--) | 
|---|
| 383 | fprintf(ctx->fp, "BCMODE_FF,\n"); | 
|---|
| 384 | fprintf(ctx->fp, "BCMODE_FF\n};\n\n"); | 
|---|
| 385 | } else if (ctx->mode == BUILD_recdef) { | 
|---|
| 386 | char *p = (char *)obuf; | 
|---|
| 387 | fprintf(ctx->fp, "\n};\n\n"); | 
|---|
| 388 | fprintf(ctx->fp, "static const RecordFunc recff_func[] = {\n" | 
|---|
| 389 | "recff_nyi,\n" | 
|---|
| 390 | "recff_c"); | 
|---|
| 391 | while (*p) { | 
|---|
| 392 | fprintf(ctx->fp, ",\nrecff_%s", p); | 
|---|
| 393 | p += strlen(p)+1; | 
|---|
| 394 | } | 
|---|
| 395 | fprintf(ctx->fp, "\n};\n\n"); | 
|---|
| 396 | } | 
|---|
| 397 | } | 
|---|
| 398 |  | 
|---|
| 399 |  | 
|---|