001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.tools.header_gen;
014    
015    import java.io.FileOutputStream;
016    import java.io.IOException;
017    import java.io.PrintStream;
018    import java.util.Arrays;
019    import org.jikesrvm.ArchitectureSpecific;
020    import org.jikesrvm.VM;
021    import org.jikesrvm.classloader.RVMClass;
022    import org.jikesrvm.classloader.RVMField;
023    import org.jikesrvm.classloader.TypeReference;
024    import org.jikesrvm.objectmodel.ObjectModel;
025    import org.jikesrvm.objectmodel.ThinLockConstants;
026    import org.jikesrvm.runtime.ArchEntrypoints;
027    import org.jikesrvm.runtime.Entrypoints;
028    import org.jikesrvm.runtime.RuntimeEntrypoints;
029    import org.jikesrvm.scheduler.RVMThread;
030    import org.jikesrvm.runtime.FileSystem;
031    import org.vmmagic.unboxed.Address;
032    import org.vmmagic.unboxed.Offset;
033    
034    /**
035     * Emit a header file containing declarations required to access VM
036     * data structures from C++.
037     * Posix version: AIX PPC, Linux PPC, Linux IA32
038     */
039    public class GenerateInterfaceDeclarations {
040    
041      static PrintStream out;
042      static final GenArch arch;
043    
044      static {
045        GenArch tmp = null;
046        try {
047          tmp =
048              (GenArch) Class.forName(VM.BuildForIA32 ? "org.jikesrvm.tools.header_gen.GenArch_ia32" : "org.jikesrvm.tools.header_gen.GenArch_ppc").newInstance();
049        } catch (Exception e) {
050          e.printStackTrace();
051          System.exit(-1);     // we must *not* go on if the above has failed
052        }
053        arch = tmp;
054      }
055    
056      static void p(String s) {
057        out.print(s);
058      }
059    
060      static void p(String s, Offset off) {
061        if (VM.BuildFor64Addr) {
062          out.print(s + off.toLong());
063        } else {
064          out.print(s + VM.addressAsHexString(off.toWord().toAddress()));
065        }
066      }
067    
068      static void pln(String s) {
069        out.println(s);
070      }
071    
072      static void pln(String s, Address addr) {
073        out.print("const Address " + s + VM.addressAsHexString(addr) + ";\n");
074      }
075    
076      static void pln(String s, Offset off) {
077        out.print("const Offset " + s + VM.addressAsHexString(off.toWord().toAddress()) + ";\n");
078      }
079    
080      static void pln() {
081        out.println();
082      }
083    
084      GenerateInterfaceDeclarations() {
085      }
086    
087      static int bootImageDataAddress = 0;
088      static int bootImageCodeAddress = 0;
089      static int bootImageRMapAddress = 0;
090      static String outFileName;
091    
092      public static void main(String[] args) throws Exception {
093    
094        // Process command line directives.
095        //
096        for (int i = 0, n = args.length; i < n; ++i) {
097          if (args[i].equals("-da")) {              // image address
098            if (++i == args.length) {
099              System.err.println("Error: The -da flag requires an argument");
100              System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
101            }
102            bootImageDataAddress = Integer.decode(args[i]);
103            continue;
104          }
105          if (args[i].equals("-ca")) {              // image address
106            if (++i == args.length) {
107              System.err.println("Error: The -ca flag requires an argument");
108              System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
109            }
110            bootImageCodeAddress = Integer.decode(args[i]);
111            continue;
112          }
113          if (args[i].equals("-ra")) {              // image address
114            if (++i == args.length) {
115              System.err.println("Error: The -ra flag requires an argument");
116              System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
117            }
118            bootImageRMapAddress = Integer.decode(args[i]);
119            continue;
120          }
121          if (args[i].equals("-out")) {              // output file
122            if (++i == args.length) {
123              System.err.println("Error: The -out flag requires an argument");
124              System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
125            }
126            outFileName = args[i];
127            continue;
128          }
129          System.err.println("Error: unrecognized command line argument: " + args[i]);
130          System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
131        }
132    
133        if (bootImageDataAddress == 0) {
134          System.err.println("Error: Must specify boot image data load address.");
135          System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
136        }
137        if (bootImageCodeAddress == 0) {
138          System.err.println("Error: Must specify boot image code load address.");
139          System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
140        }
141        if (bootImageRMapAddress == 0) {
142          System.err.println("Error: Must specify boot image ref map load address.");
143          System.exit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
144        }
145        if (outFileName == null) {
146          out = System.out;
147        } else {
148          try {
149            // We'll let an unhandled exception throw an I/O error for us.
150            out = new PrintStream(new FileOutputStream(outFileName));
151          } catch (IOException e) {
152            reportTrouble("Caught an exception while opening" + outFileName + " for writing: " + e.toString());
153          }
154        }
155    
156        VM.initForTool();
157    
158        emitStuff();
159        if (out.checkError()) {
160          reportTrouble("an output error happened");
161        }
162        //    try {
163        out.close();              // exception thrown up.
164        //    } catch (IOException e) {
165        //      reportTrouble("An output error when closing the output: " + e.toString());
166        //    }
167        System.exit(0);
168      }
169    
170      private static void reportTrouble(String msg) {
171        System.err.println(
172            "org.jikesrvm.tools.header_gen.GenerateInterfaceDeclarations: While we were creating InterfaceDeclarations.h, there was a problem.");
173        System.err.println(msg);
174        System.err.print("The build system will delete the output file");
175        if (outFileName != null) {
176          System.err.print(" ");
177          System.err.print(outFileName);
178        }
179        System.err.println();
180    
181        System.exit(1);
182      }
183    
184      private static void emitStuff() {
185        p("/*------ MACHINE GENERATED by ");
186        p("org.jikesrvm.tools.header_gen.GenerateInterfaceDeclarations.java: DO NOT EDIT");
187        p("------*/\n\n");
188    
189        pln("#if defined NEED_BOOT_RECORD_DECLARATIONS || defined NEED_VIRTUAL_MACHINE_DECLARATIONS");
190        pln("#include <inttypes.h>");
191        if (VM.BuildFor32Addr) {
192          pln("#define Address uint32_t");
193          pln("#define Offset int32_t");
194          pln("#define Extent uint32_t");
195          pln("#define Word uint32_t");
196          pln("#define JavaObject_t uint32_t");
197        } else {
198          pln("#define Address uint64_t");
199          pln("#define Offset int64_t");
200          pln("#define Extent uint64_t");
201          pln("#define Word uint64_t");
202          pln("#define JavaObject_t uint64_t");
203        }
204        pln("#endif /* NEED_BOOT_RECORD_DECLARATIONS || NEED_VIRTUAL_MACHINE_DECLARATIONS */");
205        pln();
206    
207        if (VM.PortableNativeSync) {
208          pln("#define PORTABLE_NATIVE_SYNC 1");
209          pln();
210        }
211    
212        pln("#ifdef NEED_BOOT_RECORD_DECLARATIONS");
213        emitBootRecordDeclarations();
214        pln("#endif /* NEED_BOOT_RECORD_DECLARATIONS */");
215        pln();
216    
217        pln("#ifdef NEED_BOOT_RECORD_INITIALIZATION");
218        emitBootRecordInitialization();
219        pln("#endif /* NEED_BOOT_RECORD_INITIALIZATION */");
220        pln();
221    
222        pln("#ifdef NEED_VIRTUAL_MACHINE_DECLARATIONS");
223        emitVirtualMachineDeclarations(bootImageDataAddress, bootImageCodeAddress, bootImageRMapAddress);
224        pln("#endif /* NEED_VIRTUAL_MACHINE_DECLARATIONS */");
225        pln();
226    
227        pln("#ifdef NEED_EXIT_STATUS_CODES");
228        emitExitStatusCodes();
229        pln("#endif /* NEED_EXIT_STATUS_CODES */");
230        pln();
231    
232        pln("#ifdef NEED_ASSEMBLER_DECLARATIONS");
233        emitAssemblerDeclarations();
234        pln("#endif /* NEED_ASSEMBLER_DECLARATIONS */");
235    
236        pln("#ifdef NEED_MEMORY_MANAGER_DECLARATIONS");
237        pln("#define MAXHEAPS " + org.jikesrvm.mm.mminterface.MemoryManager.getMaxHeaps());
238        pln("#endif /* NEED_MEMORY_MANAGER_DECLARATIONS */");
239        pln();
240    
241      }
242    
243      static void emitCDeclarationsForJavaType(String Cname, RVMClass cls) {
244    
245        // How many instance fields are there?
246        //
247        RVMField[] allFields = cls.getDeclaredFields();
248        int fieldCount = 0;
249        for (RVMField field : allFields) {
250          if (!field.isStatic()) {
251            fieldCount++;
252          }
253        }
254    
255        // Sort them in ascending offset order
256        //
257        SortableField[] fields = new SortableField[fieldCount];
258        for (int i = 0, j = 0; i < allFields.length; i++) {
259          if (!allFields[i].isStatic()) {
260            fields[j++] = new SortableField(allFields[i]);
261          }
262        }
263        Arrays.sort(fields);
264    
265        // Emit field declarations
266        //
267        p("struct " + Cname + " {\n");
268    
269        // Set up cursor - scalars will waste 4 bytes on 64-bit arch
270        //
271        boolean needsAlign = VM.BuildFor64Addr;
272        int addrSize = VM.BuildFor32Addr ? 4 : 8;
273    
274        // Header Space for objects
275        int startOffset = ObjectModel.objectStartOffset(cls);
276        Offset current = Offset.fromIntSignExtend(startOffset);
277        for (int i = 0; current.sLT(fields[0].f.getOffset()); i++) {
278          pln("  uint32_t    headerPadding" + i + ";\n");
279          current = current.plus(4);
280        }
281    
282        for (int i = 0; i < fields.length; i++) {
283          RVMField field = fields[i].f;
284          TypeReference t = field.getType();
285          Offset offset = field.getOffset();
286          String name = field.getName().toString();
287          // Align by blowing 4 bytes if needed
288          if (needsAlign && current.plus(4).EQ(offset)) {
289            pln("  uint32_t    padding" + i + ";");
290            current = current.plus(4);
291          }
292          if (!current.EQ(offset)) {
293            System.err.printf("current (%d) and offset (%d) are neither identical nor differ by 4",
294                              current.toInt(),
295                              offset.toInt());
296            System.exit(1);
297          }
298          if (t.isIntType()) {
299            current = current.plus(4);
300            p("   uint32_t " + name + ";\n");
301          } else if (t.isLongType()) {
302            current = current.plus(8);
303            p("   uint64_t " + name + ";\n");
304          } else if (t.isWordLikeType()) {
305            p("   Address " + name + ";\n");
306            current = current.plus(addrSize);
307          } else if (t.isArrayType() && t.getArrayElementType().isWordLikeType()) {
308            p("   Address * " + name + ";\n");
309            current = current.plus(addrSize);
310          } else if (t.isArrayType() && t.getArrayElementType().isIntType()) {
311            p("   unsigned int * " + name + ";\n");
312            current = current.plus(addrSize);
313          } else if (t.isReferenceType()) {
314            p("   JavaObject_t " + name + ";\n");
315            current = current.plus(addrSize);
316          } else {
317            System.err.println("Unexpected field " + name + " with type " + t);
318            throw new RuntimeException("unexpected field type");
319          }
320        }
321    
322        p("};\n");
323      }
324    
325      static void emitBootRecordDeclarations() {
326        RVMClass bootRecord = TypeReference.findOrCreate(org.jikesrvm.runtime.BootRecord.class).resolve().asClass();
327        emitCDeclarationsForJavaType("BootRecord", bootRecord);
328      }
329    
330      // Emit declarations for BootRecord object.
331      //
332      static void emitBootRecordInitialization() {
333        RVMClass bootRecord = TypeReference.findOrCreate(org.jikesrvm.runtime.BootRecord.class).resolve().asClass();
334        RVMField[] fields = bootRecord.getDeclaredFields();
335    
336        // emit function declarations
337        //
338        for (int i = fields.length; --i >= 0;) {
339          RVMField field = fields[i];
340          if (field.isStatic()) {
341            continue;
342          }
343          String fieldName = field.getName().toString();
344          int suffixIndex = fieldName.indexOf("IP");
345          if (suffixIndex > 0) {
346            // java field "xxxIP" corresponds to C function "xxx"
347            String functionName = fieldName.substring(0, suffixIndex);
348            // e. g.,
349            // extern "C" void sysFOOf();
350            p("extern \"C\" int " + functionName + "();\n");
351          } else if (fieldName.equals("sysJavaVM")) {
352            p("extern struct Java " + fieldName + ";\n");
353          }
354        }
355    
356        // emit field initializers
357        //
358        p("extern \"C\" void setLinkage(BootRecord* br){\n");
359        for (int i = fields.length; --i >= 0;) {
360          RVMField field = fields[i];
361          if (field.isStatic()) {
362            continue;
363          }
364    
365          String fieldName = field.getName().toString();
366          if (fieldName.indexOf("gcspy") > -1 && !VM.BuildWithGCSpy) {
367            continue;  // ugh.  NOTE: ugly hack to side-step unconditional inclusion of GCSpy stuff
368          }
369          int suffixIndex = fieldName.indexOf("IP");
370          if (suffixIndex > 0) {
371            // java field "xxxIP" corresponds to C function "xxx"
372            String functionName = fieldName.substring(0, suffixIndex);
373            // e. g.,
374            //sysFOOIP = (int) sysFOO;
375            p("  br->" + fieldName + " = (intptr_t)" + functionName + ";\n");
376          } else if (fieldName.equals("sysJavaVM")) {
377            p("  br->" + fieldName + " = (intptr_t)&" + fieldName + ";\n");
378          }
379        }
380    
381        p("}\n");
382      }
383    
384      // Emit virtual machine class interface information.
385      //
386      static void emitVirtualMachineDeclarations(int bootImageDataAddress, int bootImageCodeAddress,
387                                                 int bootImageRMapAddress) {
388    
389        // load address for the boot image
390        //
391        p("static const void *bootImageDataAddress                     = (void*)0x" +
392          Integer.toHexString(bootImageDataAddress) +
393          ";\n");
394        p("static const void *bootImageCodeAddress                     = (void *)0x" +
395          Integer.toHexString(bootImageCodeAddress) +
396          ";\n");
397        p("static const void *bootImageRMapAddress                     = (void *)0x" +
398          Integer.toHexString(bootImageRMapAddress) +
399          ";\n");
400    
401        // values in Constants, from Configuration
402        //
403        p("static const int Constants_STACK_SIZE_GUARD          = " +
404          ArchitectureSpecific.StackframeLayoutConstants
405              .STACK_SIZE_GUARD +
406                                ";\n");
407    
408        p("static const int Constants_INVISIBLE_METHOD_ID       = " +
409          ArchitectureSpecific.StackframeLayoutConstants
410              .INVISIBLE_METHOD_ID +
411                                   ";\n");
412        p("static const int ThinLockConstants_TL_THREAD_ID_SHIFT= " + ThinLockConstants.TL_THREAD_ID_SHIFT + ";\n");
413        p("static const int Constants_STACKFRAME_HEADER_SIZE    = " +
414          ArchitectureSpecific.StackframeLayoutConstants
415              .STACKFRAME_HEADER_SIZE +
416                                      ";\n");
417        p("static const int Constants_STACKFRAME_METHOD_ID_OFFSET = " +
418          ArchitectureSpecific.StackframeLayoutConstants
419              .STACKFRAME_METHOD_ID_OFFSET +
420                                           ";\n");
421        p("static const int Constants_STACKFRAME_FRAME_POINTER_OFFSET    = " +
422          ArchitectureSpecific.StackframeLayoutConstants
423              .STACKFRAME_FRAME_POINTER_OFFSET +
424                                               ";\n");
425        pln("Constants_STACKFRAME_SENTINEL_FP             = ",
426            ArchitectureSpecific.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP);
427        p("\n");
428    
429        // values in ObjectModel
430        //
431        pln("ObjectModel_ARRAY_LENGTH_OFFSET = ", ObjectModel.getArrayLengthOffset());
432        pln();
433    
434        // values in RuntimeEntrypoints
435        //
436        p("static const int Runtime_TRAP_UNKNOWN        = " + RuntimeEntrypoints.TRAP_UNKNOWN + ";\n");
437        p("static const int Runtime_TRAP_NULL_POINTER   = " + RuntimeEntrypoints.TRAP_NULL_POINTER + ";\n");
438        p("static const int Runtime_TRAP_ARRAY_BOUNDS   = " + RuntimeEntrypoints.TRAP_ARRAY_BOUNDS + ";\n");
439        p("static const int Runtime_TRAP_DIVIDE_BY_ZERO = " + RuntimeEntrypoints.TRAP_DIVIDE_BY_ZERO + ";\n");
440        p("static const int Runtime_TRAP_STACK_OVERFLOW = " + RuntimeEntrypoints.TRAP_STACK_OVERFLOW + ";\n");
441        p("static const int Runtime_TRAP_CHECKCAST      = " + RuntimeEntrypoints.TRAP_CHECKCAST + ";\n");
442        p("static const int Runtime_TRAP_REGENERATE     = " + RuntimeEntrypoints.TRAP_REGENERATE + ";\n");
443        p("static const int Runtime_TRAP_JNI_STACK     = " + RuntimeEntrypoints.TRAP_JNI_STACK + ";\n");
444        p("static const int Runtime_TRAP_MUST_IMPLEMENT = " + RuntimeEntrypoints.TRAP_MUST_IMPLEMENT + ";\n");
445        p("static const int Runtime_TRAP_STORE_CHECK = " + RuntimeEntrypoints.TRAP_STORE_CHECK + ";\n");
446        pln();
447    
448        // values in FileSystem
449        //
450        p("static const int FileSystem_OPEN_READ                 = " + FileSystem.OPEN_READ + ";\n");
451        p("static const int FileSystem_OPEN_WRITE                 = " + FileSystem.OPEN_WRITE + ";\n");
452        p("static const int FileSystem_OPEN_MODIFY                 = " + FileSystem.OPEN_MODIFY + ";\n");
453        p("static const int FileSystem_OPEN_APPEND                 = " + FileSystem.OPEN_APPEND + ";\n");
454        p("static const int FileSystem_SEEK_SET                 = " + FileSystem.SEEK_SET + ";\n");
455        p("static const int FileSystem_SEEK_CUR                 = " + FileSystem.SEEK_CUR + ";\n");
456        p("static const int FileSystem_SEEK_END                 = " + FileSystem.SEEK_END + ";\n");
457        p("static const int FileSystem_STAT_EXISTS                 = " + FileSystem.STAT_EXISTS + ";\n");
458        p("static const int FileSystem_STAT_IS_FILE                 = " + FileSystem.STAT_IS_FILE + ";\n");
459        p("static const int FileSystem_STAT_IS_DIRECTORY                 = " + FileSystem.STAT_IS_DIRECTORY + ";\n");
460        p("static const int FileSystem_STAT_IS_READABLE                 = " + FileSystem.STAT_IS_READABLE + ";\n");
461        p("static const int FileSystem_STAT_IS_WRITABLE                 = " + FileSystem.STAT_IS_WRITABLE + ";\n");
462        p("static const int FileSystem_STAT_LAST_MODIFIED                 = " +
463          FileSystem
464              .STAT_LAST_MODIFIED +
465                                  ";\n");
466        p("static const int FileSystem_STAT_LENGTH                 = " + FileSystem.STAT_LENGTH + ";\n");
467    
468        // Value in org.mmtk.vm.Constants:
469        p("static const int MMTk_Constants_BYTES_IN_PAGE            = " + org.mmtk.utility.Constants.BYTES_IN_PAGE + ";\n");
470    
471        // fields in RVMThread
472        //
473        Offset offset = Entrypoints.threadStackField.getOffset();
474        pln("RVMThread_stack_offset = ", offset);
475        offset = Entrypoints.stackLimitField.getOffset();
476        pln("RVMThread_stackLimit_offset = ", offset);
477        offset = Entrypoints.threadExceptionRegistersField.getOffset();
478        pln("RVMThread_exceptionRegisters_offset = ", offset);
479        offset = Entrypoints.jniEnvField.getOffset();
480        pln("RVMThread_jniEnv_offset = ", offset);
481        offset = Entrypoints.execStatusField.getOffset();
482        pln("RVMThread_execStatus_offset = ", offset);
483        // constants in RVMThread
484        pln("static const int RVMThread_TERMINATED = "+RVMThread.TERMINATED+";");
485        // fields in Registers
486        //
487        offset = ArchEntrypoints.registersGPRsField.getOffset();
488        pln("Registers_gprs_offset = ", offset);
489        offset = ArchEntrypoints.registersFPRsField.getOffset();
490        pln("Registers_fprs_offset = ", offset);
491        offset = ArchEntrypoints.registersIPField.getOffset();
492        pln("Registers_ip_offset = ", offset);
493    
494        offset = ArchEntrypoints.registersInUseField.getOffset();
495        pln("Registers_inuse_offset = ", offset);
496    
497        // fields in JNIEnvironment
498        offset = Entrypoints.JNIExternalFunctionsField.getOffset();
499        pln("JNIEnvironment_JNIExternalFunctions_offset = ", offset);
500    
501        arch.emitArchVirtualMachineDeclarations();
502      }
503    
504      // Codes for exit(3).
505      static void emitExitStatusCodes() {
506        pln("/* Automatically generated from the exitStatus declarations in ExitStatus.java */");
507        pln("const int EXIT_STATUS_EXECUTABLE_NOT_FOUND                 = " + VM.EXIT_STATUS_EXECUTABLE_NOT_FOUND + ";");
508        pln("const int EXIT_STATUS_COULD_NOT_EXECUTE                    = " + VM.EXIT_STATUS_COULD_NOT_EXECUTE + ";");
509        pln("const int EXIT_STATUS_MISC_TROUBLE                         = " + VM.EXIT_STATUS_MISC_TROUBLE + ";");
510        pln("const int EXIT_STATUS_IMPOSSIBLE_LIBRARY_FUNCTION_ERROR    = " +
511            VM.EXIT_STATUS_IMPOSSIBLE_LIBRARY_FUNCTION_ERROR + ";");
512        pln("const int EXIT_STATUS_SYSCALL_TROUBLE                      = " + VM.EXIT_STATUS_SYSCALL_TROUBLE + ";");
513        pln("const int EXIT_STATUS_TIMER_TROUBLE                        = " + VM.EXIT_STATUS_TIMER_TROUBLE + ";");
514        pln("const int EXIT_STATUS_UNSUPPORTED_INTERNAL_OP              = " + VM.EXIT_STATUS_UNSUPPORTED_INTERNAL_OP + ";");
515        pln("const int EXIT_STATUS_UNEXPECTED_CALL_TO_SYS               = " + VM.EXIT_STATUS_UNEXPECTED_CALL_TO_SYS + ";");
516        pln("const int EXIT_STATUS_DYING_WITH_UNCAUGHT_EXCEPTION        = " +
517            VM.EXIT_STATUS_DYING_WITH_UNCAUGHT_EXCEPTION + ";");
518        pln("const int EXIT_STATUS_BOGUS_COMMAND_LINE_ARG               = " + VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG + ";");
519        pln("const int EXIT_STATUS_JNI_TROUBLE                          = " + VM.EXIT_STATUS_JNI_TROUBLE + ";");
520        pln("const int EXIT_STATUS_BAD_WORKING_DIR                      = " + VM.EXIT_STATUS_BAD_WORKING_DIR + ";");
521      }
522    
523      // Emit assembler constants.
524      //
525      static void emitAssemblerDeclarations() {
526        arch.emitArchAssemblerDeclarations();
527      }
528    }
529    
530    
531