Method and apparatus for identifying and removing unused software procedures6247175Abstract The present invention is a method and apparatus for identifying and removing unused software procedures from computer software loads at loadbuild time, and includes a compiler, linker, and other software loadbuild tools. The compiler has been adapted by the invention to define identify procedures that are unused by a software load by identifying all procedure calls, entry points, and required procedures in each module used by the software load and to incorporate this information into link records. Based on the information contained in the link records, a linker creates a temporal data structure referred to as a call graph that depicts the operational flow of the software load. The linker processes the call graph and determines those procedures that are called, and thus those procedures that are not called by a software load. The linker or other loadbuilding tool is adapted to remove each "not called" procedure from the linked files while maintaining the integrity of the procedure directories by using several different procedure shell methods or by using tombstone values in the procedure directories. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
Procedure Management Table before Call Graph Processing
Mod table Proc table status call list
1 1p1 not called
1p2 not called
1p3 not called
1p4 not called 1p3
2 2p1 not called 1p2
2p2 not called
2p3 not called 2p3
2p4 not called 2p3
2p5 not called
3 3p1 not called 2p1
3p2 not called 1p1
3p3 not called 3p1, 1p4
3p4 not called 2p3
main1 main1 not called 3p3, 2p2, 1p4
Required List main1 First Loop 1. Take first item off required list (2p5). 2. Mark 2p5 as called. 3. Traverses 2p5 call list. end of list
Procedure Management Table after First Loop
Mod table Proc table status call list
1 1p1 not called
1p2 not called
1p3 not called
1p4 not called 1p3
2 2p1 not called 1p2
2p2 not called
2p3 not called 2p3
2p4 not called 2p3
2p5 called
3p1 not called 2p1
3p2 not called 1p1
3p3 not called 3p1, 1p4
3p4 not called 2p3
main1 main1 not called 3p3, 2p2, 1p4
Required List main1 Second Loop 1. Take first item off required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3). b. Mark 3p3 as called. c. Traverse call list for 3p3. i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a) Take first item off 3p1 call list (2p1) (b) Mark 2p1 as called (c) Traverse call list for 2p1 (1) Take first item off 2p1 call list (1p2) (2) Mark 1p2 as called (3) Traverse call list for 1p2 --end of list
Procedure Management Table after Second Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 not called
1p4 not called 1p3
2 2p1 called 1p2
2p2 not called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required List Third Loop Through the Call Graph Processing 1. Take first item off required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3) b. Mark 3p3 as called c. Traverse call list for 3p3 i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a) Take first item off 3p1 call list (2p1) (b) Mark 2p1 as called (c) Traverse call list for 2p1 (1) Take first item off 2p1 call list (1p2) (2) Mark 1p2 as called (3) Traverse call list for 1p2 (4) Take next item of 2p1 call list ( ) --end of list
Procedure Manageinent Table after Third Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 not called
1p4 not called 1p3
2 2p1 called 1p2
2p2 not called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required list Fourth Loop Through Call Graph Processing 1. Take first item off required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3) b. Mark 3p3 as called c. Traverse call list for 3p3 i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a) Take first item off 3p1 call list (2p1) (b) Mark 2p1 as called (c) Traverse call list for 2p1 (1) Take first item off 2p1 call list (1p2) (2) Mark 1p2 as called (3) Traverse call list for ip2 --end of list (4) Take next item of 2p1 call list ( ) --end of list (d) Take next item off 3p1 call list ( ) --end of list 4. Take next item off 3p3 call list (1p2) 5. 1p2 already marked as called--terminate this iteration
Procedure Management Table after Fourth Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 not called
1p4 not called 1p3
2 2p1 called 1p2
2p2 not called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required list Fifth Loop Through Call Graph Processing 1. Take first item off required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3) b. Mark 3p3 as called c. Traverse call list for 3p3 i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a) Take first item off 3p1 call list (2p1) (b) Mark 2p1 as called (c) Traverse call list for 2p1 (1) Take first item off 2p1 call list (ip2) (2) Mark 1p2 as called (3) Traverse call list for 1p2 --end of list (4) Take next item of 2p1call list ( ) --end of list (d) Take next item off 3p1 call list ( ) 4. Take next item off 3p3 call list (1p2) 5. 1p2 is already marked as called--terminate this iteration 6. Take next item off 3p3 call list ( ) --end of list
Procedure Management Table after Fifth Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 not called
1p4 not called 1p3
2 2p1 called 1p2
2p2 not called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required list Sixth Loop Through Call Graph Processing. 1. Take first item off Required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3) b. Mark 3p3 as called c. Traverse call list for 3p3 i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a) Take first item off 3p1 call list (2p1) (b) Mark 2p1 as called (c) Traverse call list for 2p1 (1) The first item off 2p1 call list (1p2) (2) Mark 1p2 as called (3) Traverse call list for 1p2 --end of list (4) Take next item off 2p1 call list ( ) --end of list (d) Take next item off 3p1 call list ( ) --end of list iv. Take next item off 3p3 call list (1p2) v. ip2 already marked as called--terminate this iteration vi. Take next item off 3p3 call list ( ) --end of list d. Take next item off main call list (2p2) e. Mark 2p2 as called f. Traverse call list for 2p2 ( ) --end of list
Procedure Management Table after Sixth Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 not called
1p4 not called 1p3
2 2p1 called 1p2
2p2 called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required list Seventh Loop Through Call Graph Processing 1. Take first item off Required list (main1) 2. Mark main1 as called 3. Traverse call list for main1 a. Take first item off main1 call list (3p3) b. Mark 3p3 as called c. Traverse call list for 3p3 i. Take first item off 3p3 call list (3p1) ii. Mark 3p1 as called iii. Traverse call list for 3p1 (a.)Take first item off 3p1 call list (2p1) (b.) Mark 2p1 as called (c.)Traverse call list for 2p1 (1.) Take first item off2p1 call list (1p2) (2.) Mark 1p2 as called (3.) Traverse call list for 1p2 end of list (4.) Take next item off 2p1 call list ( ) end of list (d.) Take next item off 3p1 call list ( ) end of list iv. Take next item off 3p3 call list (1p2) vi. 1p2 already marked as called--terminate this iteration vii. Take next item off 3p3 call list ( ) end of list d. Take next item off main call list (2p2) e. Mark 2p2 as called f. Traverse call list for 2p2 ( ) --end of list g. Take next item off main call list (1p4) h. Mark 1p4 as called i. Traverse call list for 1p4 1. Take first item off 1p4 call list (m1p3) 2. Mark 1p3 as called 3. Traverse call list for 1p3 ( ) --end of list
Procedure Management Table after Seventh Loop
Mod table Proc table status call list
1 1p1 not called
1p2 called
1p3 called
1p4 called 1p3
2 2p1 called 1p2
2p2 called
2p3 not called 2p2
2p4 not called 2p3
2p5 called
3 3p1 called 2p1
3p2 not called 1p1
3p3 called 3p1, 1p2
3p4 not called 2p3
main1 main1 called 3p3, 2p2, 1p4
Required list In summary, rather than using additional processing power to loop through and identify all procedures unused by a software load, the present method identifies those procedures that are called by a software load, and any procedure that is not called is not included in the list of called procedures and is therefore determined to be unused. Thus, in accordance with the present invention, based on the information now in the procedure management table, procedures 1p1, 2p3, 2p4, 3p2, and 3p4 are "not called," i.e., they are unused procedures with regard to main1, the software load. Using the call graph processing method of the present invention as described by example and in detail above, the procedure management table for main2 would show that for main2 procedures 1p2, 1p3, 1p4, 2p1, 3p1, and 3p3 are not called, i.e., are unused procedures for software load main2. The linker of the invention has now completed linking modules 1, 2, 3, 4, and 5, as shown in FIG. 3 and described in more detail below. It is understood, of course, that there are numerous different loadbuilding tools that link procedures to form a final software load, including proprietary loadbuilding tools. It is understood that the present invention does not depend on the specific loadbuilding tools that may be used to compile and link to create the software load. 2.0 Removal of Unused Procedures Once the unused procedures are identified, a linker or other loadbuilding tools are adapted by the present invention to remove the unused procedures from the software load. Different methods of the present invention for removing procedures are discussed below. Before discussing removal procedures, we discuss how a procedure call is done. A procedure call is accomplished by using the segment number and procedure number to uniquely identify the procedure to be called. The segment numbers are assigned by the linker at link time and depend upon the structure of the load. The segment numbers are assigned based on the order of the segments listed in the link control file, which lists the segments to be linked together for a particular load, and essentially correspond to the particular modules that are used by the software load. The compiler assigns the procedure number for a particular procedure at compile time. The procedure numbers are based on the order in which the procedures are declared. The segment number, procedure number pair uniquely identify a given procedure across the entire load. Each segment has a procedure directory, which provides access to the individual procedures defined within that segment. The procedure directory is a simple table of self-relative jumps where entry number one contains the distance to the header for procedure one, entry number two contains the distance to the header for procedure two, etc. FIG. 3 shows the linked software load 30 comprising modules 1, 2, 3, and main1. Segment 2 is expanded to show procedures p1, p2, p3, p4, and p5 contained therein and procedure directory 232. Procedure directory 232 shows entry points ep1, ep2, ep3, ep4, and ep5 for each respective procedure directory location at 178, 176, 174, 172, and 170, respectively. Procedure directory 232 identifies the self-relative jumps containing the distance (in bytes) to the header of each procedure. For example, jumps to each respective procedure p1, p2, p3, p4, and p5 from each procedure entry point, ep1, ep2, ep3, ep4, and ep5 are 163, 146, 114, 52, and 20, respectively. The procedure directory 232 also shows an expanded view of procedure p4, illustrating that each procedure contains a header 234, trailer 236, and object code 238. Since the distance to each procedure, also referred to herein as the entry point values, depends on the position of each procedure, the integrity of the procedure directory must be maintained after the unused procedures have been removed from the segment. If this integrity is compromised, then procedure calls made through the procedure directory will point to an incorrect location and the load will act in an unpredictable manner. Several methods of the present invention are used to maintain the integrity of the procedure directories and corresponding segments of a software load are discussed below. (a) Rewriting Procedure Table and Procedure Calls The first method of the present invention comprises a linker or other loadbuilding tools that remove all traces of the unused procedures. The linker fixes up each remaining procedure called with a new procedure number. The procedure numbers would no longer be assigned by the compiler but would be assigned by the linker. After removing the unused procedures the linker would create a new procedure directory for any segment, which had procedures removed. The linker would then go back and fix up all procedure calls with the new procedure numbers. By re-writing both the procedure directory and the procedure calls the integrity of the procedure calls is maintained. There are some complications with this method of maintaining the integrity of the procedure directory after the unused procedures have been removed, which are not immediately obvious. For example, code offsets in the cinfo file are updated by syco.TM.. Cinfo files contain debug information used by the compiler. Code offsets keep track of particular procedures during the compiling and linking process. These Xsyco.TM. updated offsets are later used by 1 copy (listing generator) and idtrack (cross-reference browser) to allow program designers to set proper breakpoints in native code. If the linker removes a procedure completely, Xsyco.TM. would not be able to update the cinfo file properly. Each module is typically used in many loads. For example, if a particular procedure is p10 in load A, and p15 in load B, a debugger would become confused, at best. This could be handled by having a different set of cinfo and cross-reference files for each load. However, this would increase disk usage by approximately 3.8 gig per weekly loadbuild. Other difficulties with the first method for maintaining the integrity of the procedure directory are: Once removed, a procedure cannot be patched back into the load. Since the procedure numbers would be assigned after the unused procedures had been removed, debugging would become very difficult, as procedures could have different procedure numbers in each load. Cross-reference information (cinfo and idtrack files) would grow by a factor of 22 based on the current number of loads used, for example, in the XPM terminals. (b) Procedure Shell Method In accordance with the procedure shell method of the present invention, the integrity of the procedure directory and procedure calls of each linked segment are maintained by rewriting each segment such that the procedure directory for each is modified during loadbuilding such that its entry point values point to valid procedures. First, the unused procedures ("not called") have the object code removed leaving only the procedure name (segment number/procedure number), exit code (trailer), and header information. These stripped down procedures are referred to as procedure shells. To maintain the integrity of the procedure directory and the segment, the segment is rewritten at the time the not called procedure(s) are removed. Generally, the procedure shell method of the invention walks through each segment moving "called" procedures into space previously allocated for procedures that had been determined by the invention to be "not called." This process gathers up the memory space used by the previously existing "not called" procedures and accumulates the memory space at the bottom of each respective segment. In more detail, each procedure contains computer code that takes up a certain amount of memory (bytes), which can be represented by a "delta" value. During rebuilding of the segment, the delta starts at zero (0) and then, for each segment, starting at the last procedure and working toward procedure p1, delta is incremented by the size of any removed procedure. If a procedure higher in the segment has been removed, (i.e., closer to location 0) and the current procedure is "called," then move the current procedure up to the next write location. Update the procedure directory entry for that procedure with the pre-determined delta value. Finally, move the procedure directory to the next write location. Procedure shells methods of the present invention include three main variants: extended shells, empty shells and single shells, which are explained in more detail below. (i) Empty Procedure Shell Method The empty shell method of the present invention maintains the procedure directory and procedure call integrity by using a procedure shell that includes no memory space reserved inside the shell for procedure code. Thus, procedures removed using this method cannot be patched back into the load if they are needed later. FIG. 4 illustrates in detail empty procedure shells 242, 244 as shown by the expanded view of segment 2 of software load 230 (FIG. 3). Empty procedure shells 242, 244 include headers 248, 252 and trailers 246, 250, with no memory space left inside for later patching the removed procedure back in. Each header 248, 252 and trailer 246, 250 take four (4) bytes of memory. FIG. 4 shows that that procedures 1, 2, 3, 4, and 5 of segment 2 are 20, 20, 30 60, and 30 bytes, respectively. When a procedure is called, each procedure entry is through the trailer as is known in the art. Procedure directory entry point values corresponding to ep1, ep2, ep3, ep4, and ep5 are relative from the next address: e.g., if the value at location 160 is read six (6), the place holder is now at 162 in order to get back to location 156, the procedure p5 entry point. Thus, the procedure directory offset for procedure p5 is 6. Referring again to FIG. 4 and by way of example, we step through the segment rewriting method, which is generally applicable to all procedure shell methods and the tombstone method of the present invention. Identifying the Delta Values. Initially, the delta value, which is the amount of memory corresponding to the "not called" or unused procedures, is set to zero (0). Procedure p5 lives (determined by the invention and by way of example and described above as "called"). Thus, the delta value remains zero (0). Procedure p4, however, is dead (determined by the invention and by way of example and described above as "not called"). Thus, delta is incremented by the size of memory space attributable to procedure p4, e.g., delta=sixty (60). In this example, the invention has also determined that procedure p3 is dead. Thus, the delta value is incremented by the size of the memory space attributable to procedure p3, e.g., delta=ninety (90). Procedure p2 and procedure p1 both live. Therefore, the delta value remains at (90). The next step in the method of the invention is to process all procedures. Remember, for all procedure shell methods of the invention, the header and trailer for each dead procedure are not removed with the corresponding procedure code. For FIG. 4, 16 bytes of memory corresponding to the headers and trailers 252, 250, 248, and 246 (hereinafter referred to as the "value HT") of dead procedures p3 and p4 remain in segment 2 after removal of the procedure code corresponding to procedures p3 and p4. Processing all Procedures: 1. Set the next write location to zero (0) corresponding to procedure p1. 2. Process procedure p1 to determine that procedure p1 lives. No removed procedures have been encountered. 3. Keep procedure p1 at the same location. 4. Update procedure directory entry with delta value of old (154) less delta value (90)+value HT of (16)=new self-relative jump value of (80). 5. Move the next write location to (20) and begin processing of procedure p2. 6. Determine that procedure p2 lives and, thus, procedure p2 stays in the same location. 7. Update procedure directory entry value with delta value old (132) less delta value (90)+value HT of (16)=(58). 8. Move the next write location to (40) and begin processing of procedure p3. 9. Procedure p3 is dead. 10. Update procedure directory entry value with old value (100) less delta value (40)+1/2 value HT of (8)=(48). 11. Procedure p4 is dead. Procedure directory entry value remains the same at (38), as delta value and value HT now both equal (0). 12. Procedure p5 lives. Thus, move procedure p5 from location 130 to location 52. 13. Update procedure directory entry value to old value (6) less delta (0) and value HT of (0)=6. 14. Move the next write location to (86). Move Procedure Directory. Move the procedure directory to location 86. Update Segment Header Information with New Procedure Directory Location. In this example, ninety (90) bytes of memory are recovered for use by other software load applications. The advantages of the empty shell method is that no XPM-resident tool changes are required, and the shell is a valid that can be handled by current XPM-resident and non-resident tools. The disadvantages are: (1) there is a memory cost of a basic procedure shell for each removed procedure; (2) once a procedure is removed, it cannot be patched back into a load; and (3) procedure shells can be mistaken for normal procedures. (ii) The Extended Procedure Shell Method The extended procedure shell method is an extension of the empty procedure shell method. The extended procedure shell method uses the basic procedure shell to maintain the integrity of the procedure table and procedure calls. Referring to FIG. 5, extended procedure shells 268 and 270 are expanded. Extended procedure shells 268, 270 retain headers 254, 256 and trailers 258, 260, respectively, but also include extended memory spaces 262, 264 referred to as non-operational space (NOP) to patch procedures p3 and p4 back into the load, if necessary. Each NOP requires 8 bytes of memory, enough memory for jump code instructions patched in to cause a jump to the new procedure location and for the necessary return code instructions. FIG. 5 shows segment 2 of a linked software load main1 and illustrates in more general terms rewriting of segment 2 for the extended shell method of the invention. The procedure for rewriting segment 2 is the same as the rewriting procedure described in relation to the empty shell method above and, thus, a description of the rewriting method of the invention is not repeated. The advantages of the method using the extended shell concept are: A procedure can be patched back into a load if it was removed from the original load and is required later (e.g. a patch to the load references it). No XPM-resident tool changes are required. Since the shell is a valid procedure all tools can process the shells without enhancement. The disadvantages are: Reserving the extended shell space costs memory. Procedure shells can be mistaken for normal procedures. (iii) Single Procedure Shell Method This is a further refinement of the empty shell solution. This solution also maintains the integrity of the procedure directory, the corresponding segment, and the procedure calls by way of the rewriting segment method of the invention discussed in detail above. Referring to FIG. 6, the single procedure shell method generates at most one basic procedure shell 280 per segment. When the first unused procedure is removed, a basic procedure shell 280 is created and the procedure directory entry value for that removed procedure is changed to point at shell 280. Procedure shell 280 includes header 282 and trailer 284, and can contain a non-operational space (NOP) as does extended shells 244, 242 shown in FIG. 5. For any other unused procedures defined in the same segment, their procedure directory entry value points to basic procedure shell (280) created for the first unused procedure. The advantages of the single shell method requires less memory than either the extended shell or empty shell solutions, and also does not require changes to the XPM-resident toolset. However, the disadvantage of the single shell method of the present invention is that memory must be allocated to basic procedure shell (280) for each segment having at least one removed procedure. Furthermore, once a procedure has been patched in, no other procedure for that segment can be patched into the load. Furthermore, procedure shells can be mistaken for normal procedures. c. Tombstone in the Procedure Directory. By placing a tombstone value in the procedure directory, referred to herein as the tombstone method, the integrity of the procedure directory and the procedure calls is maintained. The tombstone value is essentially a placeholder in the procedure directory corresponding to each removed procedure. The tombstone method recovers, as a result of removing the "not called" procedures, more memory than the procedure shell methods described above. When using tombstone values, the linker or Xsyco.TM. removes each of the "not called" procedures, including the procedures' headers and trailers, and place in each corresponding procedure's procedure entry location the tombstone value of zero (0). The tombstone value signifies that a procedure entry had been in this location but had subsequently been removed from the load. Thus, even though the procedure no longer exists the procedure numbers remain unchanged. Since the procedure directory entries are self-relative jumps, a special tombstone value of zero (0) would indicate to XPM-resident and non-resident tools, or to other software loadbuilding tools used to build software loads for PC applications, for example, that no corresponding procedure existed for this entry. The advantages of the tombstone method are that it recovers the most memory, and it is easy to distinguish between a tombstone and a normal procedure. The disadvantages of this procedure are that (1) once removed, a procedure cannot be patched back into a load; (2) some XPM-resident tools may have to be updated to understand a procedure directory value of zero (0); and (3) some real-time impact as xfastcall and get enteric routines have to be updated to check for the tombstone value. FIGS. 7A, 7B, 8A and 8B illustrate in detail rewriting segment 2 in accordance with the tombstone method of the present invention. Referring to FIGS. 7A, 7B, 8A and 8B, the delta values are determined in accordance with the above description of the empty procedure shell method. Segment 2 procedures are processed as follows: 1. Set the write location to zero (0) corresponding to procedure p1. 2. Process procedure p1 to determine that procedure p1 lives. No removed procedures have been encountered. 3. Keep procedure p1 at the same location. 4. Update procedure directory entry with delta value of old (154) less delta value (90)=new self-relative jump value of (64). 5. Move the next write location to 20 and begin processing of procedure p2. 6. Determine that procedure p2 lives and, thus, procedure p2 stays in the same location. 7. Update procedure directory entry value with delta value old (132) less delta value (90))=(42). 8. Move the next write location to location 40 and begin processing of procedure p3. 9. Procedure p3 is dead. 10. Update procedure directory entry value to a tombstone value (0). 11. Begin processing procedure p4. Procedure p4 is dead. Procedure directory entry for procedure p4 is set to a tombstone value (0). 12. Procedure p5 lives. Thus, move procedure p5 from location 130 to location 40. 13. Update procedure directory entry value to old value (6) less delta (0)=(6). 14. Move the next write location to location 70. Move Procedure Directory. Move the procedure directory to location 70. Update Segment Header Information with New Procedure Directory Location. Using the tombstone methods, in this example, 106 bytes of memory are recovered for potential use by other software load applications. Loadbuilding done in accordance with the above described method and apparatus of the present invention was tested on a sample set of software loads with the following results:
Method/ extended empty single Memory
load shell shell shell tombstone recovered
Eci 54 k 58 k 61 k 75 k 54 k-75 k
Eli 74 k 79 k 83 k 101 k 74 k-101 k
Odi 55 k 58 k 61 k 72 k 55 k-72 k
Odt 104 k 113 k 122 k 154 k 104 k-154 k
Ogi 86 k 91 k 96 k 113 k 86 k-113 k
Olg 54 k 59 k 63 k 77 k 54 k-77 k
Xli 67 k 73 k 77 k 93 k 67 k-93 k
xm2 63 k 67 k 79 k 82 k 63 k-82 k
Xsc 62 k 66 k 68 k 80 k 62 k-80 k
While the invention has been described with reference to specific embodiments thereof, it will be appreciated that numerous variations, modifications, and embodiments are possible, and accordingly, all such variations, modifications, and embodiments are to be regarded as being within the spirit and scope of the invention.
|
Same subclass Same class Consider this |
||||||||||
