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