260 likes | 424 Views
A Type System to Assure Scope Safety within Safety-Critical Java Modules. Kelvin Nilsen, Ph.D., CTO October, 2006. Why do we “need” scoped memory?. The effort required to develop and maintain proofs of hard real-time garbage collection behavior is “very large.” Depends on:
E N D
A Type System to Assure Scope Safetywithin Safety-Critical Java Modules Kelvin Nilsen, Ph.D., CTOOctober, 2006
Why do we “need” scoped memory? • The effort required to develop and maintain proofs of hard real-time garbage collection behavior is “very large.” Depends on: • Application-specific behavior • Rates of allocation, … and the timing of deallocations in order to derive • An upper bound on live memory usage • These attributes defy automated static analysis • CPU time consumed by garbage collection depends on working buffer size, and vice versa By the time you have analyzed your application’s dynamic memory allocation behavior in sufficient detail to prove compliance with hard real-time constraints, the benefits that automatic garbage collection normally offers to the development process havebeen largely defeated
Other attractions to scoped memory • The implementation is much simpler than incremental, defragmenting, real-time garbage collection, reducing the costs of safety certification • The latency of real-time GC typically ranges from 100 μs to several ms, compared to a few μs for scoped memory • The throughput performance is up to three times greater than traditional Java execution • The memory footprint requirements can be orders of magnitude smaller than traditional Java
Why is scoped memory so difficult to use? • In safety-critical software, we must prove the absence of scoped-memory protocol errors (IllegalAssignmentError, InaccessibleAreaException, MemoryAccessError, MemoryScopeException, MemoryTypeConflictException, OutOfMemoryError, ScopedCycleException, StackOverflowError, ThrowBoundaryError) • Proofs by automated static analysis of RTSJ source code are generally intractable
Development complexity is also very high • Software engineers like to tackle complexity by “divide and conquer” • But scoped memory requires every programmer to worry about what every other programmer is doing • My code will only function correctly if it is passed scope-appropriate arguments • I can only invoke your method if it promises to deal properly with scope-resident arguments • What about consistency of overriding method implementations? • And what are the expectations of the RTSJ and traditional Java libraries?
Relevance to development and maintenance • If I am to implement method foo(), what can I assume regarding the relative scope nesting of incoming arguments and the result I return • If I am to modify or override method foo(), what must I honor/assume regarding the relative scope nesting of incoming arguments and my return result • If I want to invoke method foo(), are there any scope-relevant constraints on the arguments I can pass? • If I want to allocate additional objects in the currently active “scope”, what code do I have to modify to enlarge this scope?
Goals of this work • Provide increased confidence that scoped-memory programs function without generating exceptions, enabling safety certification of components that allocate using nested scopes • Improve software generality, performance, and footprint of scoped-memory programs • Allow scalable composition of independently developed scoped-memory modules • Improve portability of “real-time Java”components
Context • This paper describes a “type system” rather than “whole program data-flow analysis” • Each method is verified independently to assure implementation is consistent with annotated interface definition • At each method invocation, we prove that arguments are consistent with method’s annotated interface definition • Type checking is deterministic and straightforward, assuring that multiple vendors can enforce same rules and that type system is upwards compatible with dynamic class loading and byte-code verification
Why emphasize this point? “What should be the role of Data flow Analysis (DFA) in the SCJ specification? My claim is NONE! We should not hard-wire expectations about a particular DFA algorithm in the SCJ spec. Instead of DFA the SCJ specification should specify a Type System. In a nutshell: The difference between a type system and a DFA algorithm is that doing DFA for an object oriented language requires interprocedural analysis (the analysis algorithm has to cross method boundaries). A type system is modular - each method is checked in isolation. This means two things: (1) type checking is fast, (2) type checking is less precise. So: if type-checking is less precise why would we want that in SCJ? Here are three reasons: 1] DFA is brittle in face of change. 2] DFA results are hard to relate back to the user. 3] DFA algorithms are complex and hard to standardize.” Jan Vitek, email dated June 12, 2006
The Development Environment Non-Portable, Non-Scalable, Unmaintainable Portable, Scalable, Maintainable
The Thread Stack Model Initially, the run-time stack (grows downward) for the main thread represents all non-immortal memory.
Memory for third spawned thread’s stack Memory for second spawned thread’s stack Memory for first spawned thread’s stack The Thread Stack Model The main thread may spawn additional threads, setting aside part of its own stack to represent the stack memory for the spawned threads. Any of the spawned threads may in turn carve up its stack in order to spawn “grandchildren” threads.
But objects residing in the parent thread’s stack below the point from which the child thread was spawned are not visible to the child thread. And outer-nested objects are not allowed to see objects residing in more inner-nested scopes. And a child thread may see scoped objects that reside in the parent thread’s stack above the point at which the child thread was spawned. Individual threads populate their run-time stacks as appropriate. Each thread’s scoped objects can see scoped objects allocated In more outer-nested scopes of the same thread. The Thread Stack Model
After the child threads have joined with the parent thread, their memory is fully reclaimed (and defragmented). The Thread Stack Model The parent thread is required to join with its spawned threads before returning from the context from which it spawned those threads.. Thread-3 Thread-2 Note that illustrated stack modelis simpler than what is describedin paper. Thread-1
Scoped Memory Annotations [1] package samples;[2][3] import javax.realtime.util.sc.*;[4][5] public class Complex {[6] public float real, imaginary;[7][8] public @ScopedThis Complex(float r, float i) {[9] real = r;[10] imaginary = i;[11] }[12] [13] public @CallerAllocatedResult @ScopedPure Complex add(Complex arg)[14] { [15] float r, i;[16] r = this.real + arg.real;[17] i = this.imaginary + arg.imaginary;[18] return new Complex(r, i); // syntactic sugar[19] }
Explicit RTSJ API [12] [13] public @CallerAllocatedResult @ScopedPure Complex add(Complex arg)[14] { [15] float r, i;[16] r = this.real + arg.real;[17] i = this.imaginary + arg.imaginary;// without syntactic sugar[18] return ScopeManagement.allocInCallerContext(Complex.class, r, i);[19] } Personally, I prefer the sweetened syntax, but the paper makes the point that the type system is independent of the syntax used for object allocation, method invocation, and method prologue implementations.
Quiz • Is the following code legal? Safe? class List { @Scoped List car, cdr; @Scoped @ScopedThis List setcdr(@Scoped List cdr) { this.cdr = cdr; } } No, the typesystem can’tdeterminerelative nestingof “this” and “cdr”arguments
Quiz This code is legal, butnot necessarily safe. Allowing checkedassignments is amission-criticalextension to beexercised atprogrammer discretion. • What about this? class List { @Scoped List car, cdr; @AllowCheckedScopedLinks @Scoped @ScopedThis List setcdr(@Scoped List cdr) throws IllegalAssignmentException { this.cdr = cdr; } }
Quiz • And this? @NestedReentrantScope class List { @Scoped List car, cdr; @Scoped @ScopedThis List setcdr(@Scoped List cdr) { this.cdr = cdr; } } Yes, the verifier enforcesthat all @Scopedarguments toinstance methods of a@NestedReentrantClassobject reside at samescope level as theobject itself
Quiz • How about? class FFT { @Scoped Complex a, b; @ScopedPure FFT(Complex arg1, Complex arg2) { a = arg1; b = arg2; } } Yes, the verifier enforcesthat all @Scopedarguments to a@ScopedThis constructorreside at same ormore outer scope thanthis
Quiz • How about? class FFT { . . . @CallerAllocatedResult static FFT doFFT() { Complex c1 = new Complex(1.0, 0.0); Complex c2 = new Complex(0.0, 1.0); return new FFT(c1, c2); } } No, the verifier enforcesthat all @Scopedarguments to a@ScopedThis constructorreside at same ormore outer scope thanthis
Quiz No, the verifier requires thatany method that attemptsto allocate in immortalmemory carry the@ImmortalAllocationannotation • Last question: class FFT { . . . Complex temp; static void doOtherFFT() { Complex c1 = new Complex(1.0, 0.0); Complex c2 = new Complex(0.0, 1.0); temp = c1.add(c2); } }
Experience • Sample applications • The PERC Pico “extended subset” of traditional Java and RTSJ libraries comprises about 20,000 lines of code • FFT traditional Java benchmark from Java Grande • Traditional Java CaffeineMark • Benefits • When the code compiles, the code is scope safe • 8-226% throughput improvement over Sun HotSpot
Generalizations • @NonNull annotation elides null-pointer checking after enabling proof that reference value not equal to null • Static analysis of scope memory requirements and worst-case CPU requirements • Can generalize to scoped memory implementation of producer/consumer
Summary • A scope-safe type system: • Provides increased confidence that scoped-memory programs function without generating exceptions, • Enables safety certification of components that allocate using nested scopes, • Improves software generality, performance, and footprint of scoped-memory programs, • Allows scalable composition of independently developed scoped-memory modules, and • Improves portability of “real-time Java”components