Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2003-304
$eventId
ID of event: wwdc2003
$eventContentId
ID of session without event part: 304
$eventShortId
Shortened ID of event: wwdc03
$year
Year of session: 2003
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC03 • Session 304

GCC in Depth

Apple Developer Tools • 44:03

Learn about Apple's implementation of the GNU Compiler (GCC) and how it has been enhanced with faster compile time, improved code generation, and better IDE integration. You'll also hear about the latest Mac OS X linker features and development roadmap.

Speakers: Ron Price, David Edelsohn

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it may have transcription errors.

As you probably all know, we jammed a new GCC 3.3 compiler this week at the show. And today, what I would like to do is introduce you to that compiler, answer any questions you may have that are preventing you from moving forward, and I encourage you to move to 3.3. Also, I want to introduce you to one of our partners from IBM. We work very closely in terms of delivering the 3.3 compiler, particularly focused on the G5 and the new G5 system, and also leave you with some idea of future directions for us.

So we had a challenge in putting together the 3.3 compiler. We wanted to deliver a very robust compiler, because we know that's what you expect. We wanted to address compile speed. We've heard very much from you in terms of compile speed and comparing us against the Code Warrior compiler. And also in terms of code quality, because we were introducing a brand new system this week, G5 system, and we needed to have terrific optimizations for that system coming out of the chute. Well, that's a very hard problem. I've worked in the compiler area for quite a number of years, and usually you get to pick two of those three, and you can deliver either a robust compiler with a lot of great code quality, or you can deliver a robust compiler that's really fast, but to try and deliver a compiler that meets all three of those goals is a real challenge. Thank you.

So I want to talk a little bit about why we were able to accomplish that challenge. And I think you'll see that when you install and use GCC 3.3. First of all, as you know, it's based on the GCC, the Free Software Foundation technology. And today, that technology is very mature and stable and continuing to evolve, primarily from the advent of Linux systems and Linux system vendors putting together compiler production release compilers. Since that time, the GCC compiler has really matured significantly. There are billions of lines of code that have been put through it. We build our own Mac OS X operating system with GCC compiler. We have very extensive test suites, something on the order of 30,000 tests a day and growing all the time. So just a very robust experience. And I want to assure you that when you install the 3.3 compiler, you'll see the same thing. One example is one of our early seeds for 3.3 was one of you folks with an application, about 26 million lines of C++ code. That entire application was migrated to 3.3 with essentially no problems.

So what does Apple contribute? Because we also have been putting a big effort in. And I want to tell you that we're a very active member of the free software community. We have experienced engineers on staff that have worked in that community for quite a while. We are an active maintainer.

The Free Software Foundation has actually recognized some of our efforts, and they have made Darwin a reference platform for GCC releases, something that is really fantastic. New optimizations. As I mentioned, G5 was really important to us for this release. This system, as you've probably seen in some other presentations, is an order of magnitude more complex to deal with than the G4. And our mission, part of our mission, is to make that happen for you so that you don't have to do all of the work of extracting the power from G5. So we've done quite a bit of optimization. You'll hear some about that today. You'll hear some from our partner, IBM, who worked with us on that. As I mentioned, compilation speed is important. And so we made that a high priority within our organization. And I will show you some of the performance improvements in terms of compilation speed. And finally, just general maintenance. Our customers report problems. We fix them. We put them into the community. And so we're very active and correct members, if you will, of the community. So let me get into talking about the three things that we focused on for this release besides robustness.

In the language feature area, as you know, GCC is already very compliant with the ISO and ANSI standards. And so it's mature in that way, but it's not 100% compliant. And so it continues to grow. And in this release, you'll see some of that, particularly with respect to now being able to identify incorrect code that may have slipped by in earlier releases of the compiler. We've also added a feature in Panther, wide character support, a missing feature.

Now as you know, within our own frameworks and within Apple, we encourage you to use Unicode, which is a much more powerful means of dealing with wide characters. But we have heard your desire to have applications more easily ported into our environment. And in some cases that requires wide character. So that will be there with the Panther release. You will not have it with Jaguar.

Objective-C and Objective-C++, of course, we maintain and we do enhancement in this. And one of the problems that you have told us about is that we did not have an exception model, particularly for Objective-C. And so we've added that. That's also available in conjunction with the Panther OS release. And finally, and I know you've heard some of these things in other forums, but finally, we've added another migration tool for you, which is the ability to use inline assembly or port inline assembly from the Code Warrior. So completely compatible with Code Warrior, other than bugs you may find since it's a brand-new feature. But I think it's pretty robust. We've ported quite a bit of code with it.

So compatibility from release to release is always a concern and an issue. The main thing I'd like to point out to you is what you see here is a very short list of issues to deal with. So we're compatible in many, many ways. One of the things that you will want to do, though, is that you will want to rebuild your C++ apps. A necessity in terms of making some enhancements C++, we've had an ABI change. And so you simply just need to recompile all of your C++ apps.

In 3.3, as you know, we've been struggling with precompiled headers over the years in terms of providing a complete solution, providing a very robust solution. And we've had CPP precomp as an early version of that. A number of years served us well but had limitations. And we introduced this time last year PFE. And PFE brought to you entire language coverage, which we didn't have in CPP Precomp. PCH is the next generation of essentially PFE. More robust, faster implementation, and across all languages. And this is a feature that has gone back into the Free Software Foundation and will be in other vendors' releases of compilers in the future. Thank you.

The other thing that we have done within GCC 3.3 is that in looking at the migration of some code, people were getting tripped up a little bit with the flags. We had certain flags that you use for CPP precomp. We have some others for PFE, and now we have PCH flags. And so if you were in your development environment, getting those flags corrected was recognizing that you needed to correct them even was a problem. And so we've made the use of invalid flags for the particular compiler you're using a warning. So you'll get a heads up. If you see warnings from compiler flags now that you hadn't seen before, you should deal with those. They're really there to try and help you out.

One of the things that we get as a result of deprecating CPP precomp is that CPP precomp was built on a preprocessor model which was KNR based. And so you had to flip some switches within the compiler if you wanted to actually use the ANSI standards C preprocessor. Now that we no longer have that encumbrance, We have made the default CPP preprocessor the ANSI standard preprocessor. It aligns us with all other GCC compilers that are being delivered today.

And finally, one other flag that I would just warn you about, because we've seen some resulting difficulty with this as well, is the no standard include flag was originally intended to not look in any system directories. We found that we actually had a problem with the implementation in the 3.1 compiler. And it, in fact, was looking and finding header files in a system directory. Well, that's corrected now. And so if, in fact, you were taking advantage of the fact that certain system directories were being searched, then you'll need to deal with that as well.

Compile speed, really big deal. We began focusing on this about a year ago. And we had an offering-- and I'll show you some performance numbers. We had an offering in December time frame where we made improvements. We continue to improve this. We have set our goals very high in terms of being able to provide speedy compilation. And so let me talk a little bit about what we've done to do that. Most of you, if not all, are familiar with precompiled headers. If you are, just bear with me a little bit, because we find that not all of our customers are using them, and they really are beneficial to make use of. And so the picture that you see here is simply a representation of a precompiled header and the way that gets created is that you can create that with the compiler and what happens is the header file is pre-compiled and it's saved in an intermediate form for the compiler. Then when you begin to compile your own program where you make reference to a header that is pre-compiled, the compiler is able to restore that state and continue the compilation process. Thank you.

This makes the assumption that header files are a significant portion of your actual compilation process, and that's certainly true if you're using large frameworks like Carbon or Cocoa or AppKit or others like that. So your code is typically a very small portion of what is actually compiled when you're building files like that. To make the best use of pre-compiled headers, it works best if you can actually identify a common set of headers that are used by all of the files that you're building in a project. What you can do with that is that you can create a prefix header, as we call it, which is a pre-compiled header that contains those common header files. Then you simply include that prefix header in your file that you're building and the compiler will restore a significant amount of state, bypass all of that compilation process and move straight into compiling the rest of the code.

The next feature is predictive compilation. And I know that this has been talked about a little bit in the Xcode discussions. I have a picture maybe that will help you understand. In predictive compilation, we once again are making the assumption that the header files are a big portion of your compilation process. And furthermore, if you're going to modify a header, you do that in the context of modifying that header file, not the source file you're editing. And so what happens with predictive compilation is that when you begin editing a file, the compiler in the background begins the compilation process on the header files, and it will proceed up until the point where your code begins, the code that you're working on. And once you save that code, the compiler proceeds on and predictably compiles that file for you.

You can see in the timeline that I provided here a representation of that. The final feature, and all of these, by the way, I'm mentioning, even though they are driven by Xcode, required modification to the compiler to be able to work synergistically with Xcode to be able to provide these compilation features. In distributed compilation, the Xcode is able to actually distribute your compiles out to other machines that have been so designated is to be available for that processing for you. And so it'll make maximum use. It sends over the source file. It sends over header files that you may need. So there's no setting up the environment, especially on the different machines.

The only requirement is that you're running the same generation of OS. And so if you're doing this on Panther, you'll be sharing all Panther machines. But this is a way that can really speed up your performance as well. So let me just show you the result of this. If you look at this chart, as I mentioned over on the left in the Jaguar time frame, we introduced PFE, and that was our first language-wide performance enhancement. The application, by the way, here that I'm showing the data from It's what I would call a moderately sized C++ app. It's about 250,000 lines of C++ code. It's Carbon based. And it has over 1,000 files.

The middle point, which we offered in December tool set, primarily was the addition of being able to use the dual processor on your tower. So that using both processors we were able to significantly reduce the amount of compile time. Still had PFE though. With the introduction this week of GCC 3.0 and Xcode, PCH is the new capability and you'll see on the chart there the performance over and above PFE we're able to obtain with PCH. I wanted to draw your attention though to one other point on there and that's the the green one down at the bottom that says distributed build. This is the result of building this application with six XSERVs dual processor XSERVs. Six because we had them available. Actually don't know that this app required the use of all six, but essentially you have a means now of adding horsepower to get faster performance, even above dual processor.

So code quality. And I mentioned that we had a big focus on code quality, particularly with respect to the G5 processor. When you're in the process of trying to focus on code quality within compilers, probably the most important tool you can have is a real benchmark. And that's a benchmark that is in a harness so it's easy to run. It provides reproducible results. It represents applications similar to what you deal with. And prior to G5, we had used an internal benchmark that was called Skidmarks. And Skidmarks was composed of kernels of code from various applications that we felt were important in Apple.

When we reached G5, though, we had a problem. The problem was that not only were those files small enough to fit into cache so that they didn't really represent true applications on the system, but also they weren't self-checking such that if we broke an optimization, the code might still run and produce incorrect results. We didn't have an idea of that. We just knew how fast it ran. So we looked around in the industry, and we chose the spec benchmark. And there are pluses and minuses with the spec benchmark. You can argue as to whether they're representative of Macintosh applications or not, but we determined that they were close enough that we could make some good progress. And, oh, by the way, we also needed to produce some spec numbers. for this new processor. So, spec represents 12 integer benchmarks, 14 floating point benchmarks. They're real applications that have been encapsulated into the test bench, and they run in a harness, and they're predictable and validate their results.

So let me just show you the results of G5 performance with the spec benchmarks. And what I'm comparing here is our 3.1 compiler versus our 3.3 compiler today. So the compiler you're using today versus the one that hopefully you'll be using later on this afternoon. You can see from specint-- and this is an aggregate across the 12 benchmarks in specint-- that we actually have a 17% performance increase over what we're able to extract from the 3.1 compiler. Correspondingly for floating point, and this is a floating point machine, as has been noted in almost every presentation. In floating point, we're able to get 30% better performance than we can with the 3.1 compiler on a G5. And this is a single processor G5 2 gigahertz.

So what does this mean for you in terms of how can you take advantage and build your code for the G5 and get performance? Well, there are several situations that you may be dealing with. First of all, you may have an app that you want to run on more systems than just the G5. You may want to have that app which has some computationally intensive code, but by and large, the rest of the app is not dependent upon the performance of a G5 system. And so you might break that app up such that you put the computational intensity portion into a runtime library. You tune that very heavily for G5 and leave the rest of your code as G4 or G3, and you can produce runtime libraries for each category of system. You can do that by turning on the MCPU Frag and getting G5 instructions and M-Tune and getting G5 scheduling.

Let's suppose though that your app also uses long longs or double data types. Then you may want to take advantage of the 64-bit arithmetic power of the G5. And what we would provide if you use the MPowerPC64 switch is that you actually enable 64-bit arithmetic and it does amazing things then with your long long arithmetic or your doubles and loading and storing 64 bits and doing arithmetic on that. Thank you.

Believe me, you'll applaud once you've had a chance to taste it, if you're using that. We have always encouraged you to use the optimization for space-OS because that in the system can have a big impact. And so we're not discouraging that now. If you have code that is not computationally intensive, by all means, build it with -OS. What we are encouraging you though is be a little more adventuresome if, in fact, you want to get performance out of this machine. You need to deal with the higher levels of optimization, O2, O3 if at all possible, and it lends itself to your code. You want to deal with things like the alignment of loops and jumps and functions and make sure that they're aligned on proper boundaries so that you can, if you follow along, some of the architectural types of discussions-- and David will be talking about this a little bit in a moment-- Alignment can be very important in terms of extracting speed. So let me call David Edelsohn up to the stage. David and others within IBM have worked very heavily with us on optimizing for the G5 and the 3.3 release. David? Thanks very much, John.

So my name is David Edelsohn, and I work at IBM Research. And my main focus there is on open source issues and technology, particularly GCC. I'm glad to see so many people here at the conference interested in GCC. So first, let me give you a little idea of what I'm going to talk about in this presentation. I'm going to talk about the PowerPC 970 processor and issues in the processor that are important for a compiler to target to get the highest performance. We're also going to talk about how we have optimized GCC to extract that best performance on this processor that Apple is now using. And we're going to mention a little bit about the future optimizations that are going to be coming down the pike in later releases of GCC and about how IBM and Apple are both working together with the open source community to engage them and produce the best compiler for the PowerPC processors. So let's start with a little bit of information about this big step in processor performance. We now have a 64-bit PowerPC processor on the desktop. The PowerPC architecture was designed from day one as a 64-bit architecture. The previous implementations have been the 32-bit subset, and now we have an implementation in Apple's computers that use the full 64-bit architecture. This architecture has instructions that operate on both 32-bit sized data and 64-bit sized data. And in 64-bit mode, when it's operating on twice the amount of data and it's the same performance whether you're in the 32-bit mode or 64-bit mode, same latency, the same speed of the instructions, and you're operating on twice the amount of data. This processor has a lot of resources available at its disposal to attack whatever application you have to throw at it. It has two complete symmetric floating-point units. It has two symmetric load-store units. It has two almost-symmetric instruction integer units in this processor, and these are a lot of capabilities now to bring to bear on your problems, and this increase in resources is an increase in performance for your application.

So this is a pictorial depiction of the processor core in the 970. And I just want you to have a visual representation for this processor in the upcoming slides. So let's go through and talk about how an instruction flows through this processor. First, instructions are fetched from the L1 cache into the instruction queue. And in this instruction queue, the instructions are then decoded and placed into the dispatch groups. And up to five instructions can be dispatched at a time to the various instruction function unit issue queues. Thank you. From the issue queues, you can dispatch to any of these 12 function units in the processor. So this is a lot of resources, a very powerful chip, a lot of complexity that the compiler needs to harness to give you the best performance.

So what are some of these challenges that the compiler needs to deal with? First of all, the processor is dealing with internal ops, that the actual function units are operating on operations that are mostly one-for-one equivalent to PowerPC instructions, but some instructions that are more complicated than the PowerPC architecture are broken apart, and that's what the function units actually operate on. Examples of these instructions are load algebraic, which is a load with a sign extension, a load of an update form, which is a load and then updating the address register, various forms of stores, which although not all of them are cracked, is actually issued as a register access and as an address generation. And implicit compares, which are the mnemonics that have a dot at the end where one performs a logical or arithmetic operation and then compares that result with zero and stores result in a condition register. And finally, the multi-field condition register operations where one is performing a condition register logical operation on multiple condition register fields at the same time. Those are examples of the user application instructions that are cracked. Another area that's a complexity is the dispatch groups. So for these dispatch groups, the dispatch group isn't completely symmetric and which instruction can be placed into which slot in the dispatch group. For instance, move to and from special purpose registers is placed in slot one. Divisions are placed in slot two. And the slots also are directed to various versions of the dual function units. So this is another area where the compiler needs to be careful to achieve the best performance. And finally, the instructions are brought from the instruction queue into the dispatch groups in order.

But then once the dispatch groups are dispatched to the various function units, to the issue queues for the function units, the function units are able to pull the instructions out of order as resources are available for them to produce the most performance and get the most parallelism out of the resources available. And then the instructions are recombined at the end and completed as an entire dispatch group. So there's a lot of parallelism available if the compiler is able to take advantage of it.

So what have we done in the compiler to achieve this performance? First of all, we're preferring the non-cracked forms of instructions to again allow the processor the greatest amount of flexibility in which instructions it can dispatch. As I mentioned about the dispatch groups, the compiler is also trying to order the instructions in the sequence that they will be placed into dispatch slots to allow the maximum number of instructions to be dispatched at one time, to again allow the maximum amount of function units to be in use to work on the various problems. There's also the issue of balancing the use of the function units. We have these two dual function units, and we want to make sure that one unit is not starved while the other one is overloaded.

So we need to make sure that we're issuing instructions to keep both units occupied and get the maximum performance from this chip. Additionally, there are issues with dependent instructions, where it's best for the chip if those dependent instructions are placed into separate dispatch groups to avoid stalls within the processor. And we've also done various branch optimizations to deal with other restrictions in the processor that could potentially produce stalls. So there's a lot of issues there that the compiler needs to pay attention to. And finally, as Ron mentioned, a very important issue is the alignment of instructions because the processor instruction fetch is fetching 32 bytes at a time. It's eight instructions into the instruction queue, but it's fetching those instructions on a 32-byte boundary. So if one is jumping into the middle of a group of instructions that isn't on a 32-byte boundary, such as a loop, such as a jump to the beginning of a function or a label, then you're wasting the instruction fetch bandwidth if that's not on the appropriate boundary. So again, we have additional options in the compiler, and the compiler's been tuned to generate instructions on that boundary if it isn't going to decrease the performance by adding too many other instructions to achieve that alignment.

So I want to make sure that you understand that while this processor has a lot of capabilities that the compiler can leverage, that this processor works very well with any sort of code that you're going to throw at. That if you're working on G3 or G4 code, there's a lot of dynamic capability in the processor to extract the most performance out of that. You can work with the compiler to achieve the maximum performance and get that little extra percent, but if you're working on an application that needs to be targeted at G3 and G4 and the new G5, that application will scream on all of these processors, and it will scream on the G5 as well. So this processor will not just fall down if presented with G4 code.

So how can you help the compiler to produce better code now that we've discussed what the compiler -- what we've done in the compiler groups to try to help tune it for this processor? First of all, one thing that you need to do is to make sure that this -- that the compiler can be as aggressive as possible in its transformations of the code. And one of these areas is to try to make sure that the compiler doesn't need to be more conservative than it need be. An area where this is important is in aliasing. That's where the compiler cannot tell or where two different memory -- two different accesses to memory could potentially -- two variables could point to the same place in memory.

And so if the compiler cannot distinguish this, the compiler needs to be more conservative to ensure that it's going to perform the calculations in the appropriate order. So what you can do to allow the compiler to see and understand more of the procedure that you're working on is to use local variables and to avoid taking the addresses of variables. And this also includes areas where the calling convention may implicitly pass something by an address, because then the compiler needs to sort of throw up its hands if it can't fully understand the consequences of what the -- something may be happening behind its back.

So that's one area where you can help the compiler produce more effective code. Another area is in simplifying loops, where If you can make this loop as simple as possible and as direct as possible, the compiler has much more opportunity to make transformations that will improve performance. One example of this is to not have changes in flow control inside the loop. So for instance, if you have a loop that has a branch inside the loop, if it's possible, it would be better to move that branch, that conditional, outside of the loop and actually duplicate the loop in both branches of the conditional. And that allows the compiler to then optimize each of those loops much more effectively when it can better understand exactly what's going to happen. Another area is indexes into arrays. Again, try to have as simplified an index as possible. So again, the compiler can understand what's going on and make transformations with prefetching and other sorts of optimizations that would be much more effective in the throughput of this processor. And finally is obeying the type rules. The type rules are something that is essentially an agreement between the programmer and the language about how you're going to use the various types. And if you use the types appropriately and don't start changing accesses behind the compiler's back in ways that it doesn't expect, you can use the most aggressive optimizations, as Ron mentioned, and get the best performance from your program. So let me talk a little bit about where this technology came from. That a lot of this work that we've been doing comes from IBM's vast experience in developing compilers over the past decades, including some of the earliest compilers for computers. And so we have a worldwide research and development organization that has been brought to bear in partnership with Apple in improving this compiler for the G5 processor. Some of this involves taking the technology that IBM has developed for its power 4 processor from which the 970 was derived and used in developing IBM's own compiler, taking that knowledge, taking that experience and driving that technology and that understanding into GCC. So leveraging all of that history of development. And we're trying to -- using all of that to exploit this processor and give Apple and its customers the best performance possible.

Now let me mention a little bit about where GCC is going in the future. There's a lot of very exciting optimizations that are going to be included in the compiler in the future, which will yet further enhance the performance of this processor. We have a new register allocator that is coming. There is improvements in the instruction scheduler, which will allow even better ability to model the complicated dispatch groups that I mentioned. There's work on software pipelining, which will help improve the floating point performance.

There's work on an SSA optimization infrastructure, which allows one to describe the program in a way that allows much more aggressive optimizations. This will allow easier implementation of loop optimizations and auto-vectorization in the future. Further work on inter-procedural optimizations to be able to, again, have the compiler understand more of the program, understand what's going to happen with aliasing, and work past limitations that the programmer may not have intended. profile-directed feedback so that the compiler can reorder the instructions to take advantage of how the application is actually run with the data sets that you have, and of course, continued improvement in the compiler speed to more rapidly generate the code.

So all of this stems from this partnership that IBM and Apple have in developing and further improving GCC. We are both very committed to GCC and to the open source community. There are a large number of developers for GCC inside Apple and inside IBM. I am a member of the GCC steering committee, and a member of Ron's team is on the GCC steering committee. I'm one of the maintainers of the PowerPC port of the compiler, and a member of Ron's team is my co-maintainer on the compiler. And we are fully engaged with this community to help design the future of this compiler and taking a leadership position to make sure that this is the best compiler for Apple systems and for the PowerPC.

And so I just want to let you know that not only are we working aggressively on the processor and the hardware, but there's a lot of excitement to come in further improving the performance of the system from the software and the work on the compiler as well. So I hope that you're excited as well. And now I also just want to quickly mention a thanks to John Hannibal Stokes for the use of one of his graphics in this talk. And now I'll hand it back over to Ron.

Actually, it's a pretty exciting time for us. In the compiler world, when you're presented with a challenge like this, it just makes your day, or makes your year. I mean, Future directions. Well, as you can see, we've just embarked on this journey with the new processor and extracting the performance from that. And we really consider our role to be one of trying to make this processor work for you without you having to jump through hoops or go through a lot of gyrations.

Right now, we're telling you, please do a few gyrations if you want to get the performance out. But we're working, as David showed you, on any number of areas within the GCC community to take the optimization capabilities of the GNU compiler to the next level. We also don't consider our work done in terms of compile speed. I think you will agree we've made some really dramatic progress in the past year. And if I were you, I would expect from us that will make as good a progress in the coming year because we're going to nail performance.

That's really important. And we want to remove this compiler from being an obstruction to you getting your work done. In terms of language features, we'll move along at a slower pace because the language is moving at a slower pace today. But you can count on us continuing to move towards full compliance with the ANSI and ISO standards.

So even though it's being jammed today, it is being offered to you as a part of the Xcode tools package. There's documentation in that package that you can reach. There's the release notes. One piece of documentation that you'll probably want to look for is in terms of performance tuning your code.

And I would encourage you to attend the session, which occurs the next session. I don't think it's in this hall. I may actually have a slide on that. Okay. Anyway, there's a performance session during the next period of time, and I would encourage you to attend that. I was at the CHUD session that preceded this, and I was happy to see an overflow room there. We've got some dramatic capabilities with our tools to help you improve the performance of your code. If you have contact that you want to make, Gafi DiGiorgi, who's our technology manager, and you'll meet him in just a moment, his email address. There's a feedback address for the development tools, and of course, we're always looking for bug reports. Well, I guess we're not happily looking for bug reports, but we appreciate the feedback. back.

Here's the 305 in Presidio as the performance tools section that I was talking to. Some of the other sessions that you might be interested in is Apple Script Studio, Carbon Development and so forth. We encourage you tomorrow, there will be a feedback forum where you can come and let Let us know what you would like to feedback to us and give us your impression of the tools.