Robustification through introspection and analysis tools. (Avoiding - - PowerPoint PPT Presentation

robustification through introspection and analysis tools
SMART_READER_LITE
LIVE PREVIEW

Robustification through introspection and analysis tools. (Avoiding - - PowerPoint PPT Presentation

Robustification through introspection and analysis tools. (Avoiding developer taxes) Stephen.Kennedy @ havok.com Principal Engineer Developer Taxes It's something you do, not because it actually benefits you specifically, but because it


slide-1
SLIDE 1

Robustification through introspection and analysis tools.

(Avoiding developer taxes)

Stephen.Kennedy @ havok.com

Principal Engineer

slide-2
SLIDE 2

Developer Taxes

“It's something you do, not because it actually benefits you specifically, but because it benefits the software landscape as a whole.” (Raymond Chen) 2

slide-3
SLIDE 3

The Classic Game Taxes

Versioning 3 Script Binding Memory Reporting Serialization

slide-4
SLIDE 4

Memory Lane

10 Years of taxes Why we changed Automate Correctness Spread the workload 4

slide-5
SLIDE 5

Structure

Serialization Reflection Memory Reporting Script Binding Versioning 5

slide-6
SLIDE 6

Manual serialization

xtream.TokenMustBe("POINT_A"); xtream>>point_A; bool has_two_bodies = true; if (xtream.GetVersion() >= 1350) { xtream.TokenMustBe("HAS_TWO_BODIES"); xtream>>has_two_bodies; } if (has_two_bodies) { xtream.TokenMustBe("BODY_B"); xtream>>prot_buffer; priv_rigid_body_B = prot_subspace->GetRigidBody(prot_buffer); if (!priv_rigid_body_B) throw_exception("Rigidbody unknown in Spring"); } xtream.TokenMustBe("POINT_B"); xtream>>point_B; //…

6

S

slide-7
SLIDE 7

Reflection Recap (1)

struct A0 { float x; byte y; } struct A1 { float z; byte p; byte q }; struct A2 { byte i[3]; } … struct A1023 { };

7

S

slide-8
SLIDE 8

struct A0 { float x; byte y; } char A0_type[]={ ‚FB‛ }; void save_any( void* obj, char* type ) while(*type) { switch( *type++ ) { case ‘F’:// write(); obj += sizeof(float); case ‘B’:// write(); obj += sizeof(bool); case 0: return; }

Reflection Recap (2)

8

S

slide-9
SLIDE 9

Serialization with Reflection

Straightforward. The problem now is … 9

(mostly)

slide-10
SLIDE 10

Get Reflected

How to reflect our data? How to keep it up to date? Robustness 10

slide-11
SLIDE 11

Manual Reflection

class Foo { public: enum Flags {…}; int i; char* pc; double d; Flags flags; protected: long larr[10]; A* pa; }; RTTI_DESCRIBE_CLASS( Foo, ( RTTI_FIELD(i, RTTI_FLD_PUBLIC), RTTI_PTR(pc, RTTI_FLD_PUBLIC), RTTI_FIELD(d, RTTI_FLD_PUBLIC), RTTI_FIELD(f, RTTI_FLD_PUBLIC), RTTI_ARRAY(larr, RTTI_FLD_PROTECTED), RTTI_PTR(pa, RTTI_FLD_PROTECTED) ) );

11

R

slide-12
SLIDE 12

Parsing Headers

12

R

Dirty Regexes Foo.h

FooReflect.cpp

class Foo { public: int i; char* pc; double d; enum Flags flags; protected: long larr[10]; class B* pb; #ifdef PLATFORM_Y special* s; #endif };

slide-13
SLIDE 13

We Went Full Auto

C++ headers GCC-XML Master.h DB Script1 Script2

13

R

Generate Reflection

slide-14
SLIDE 14

Clang AST Consumer

class RawDumpASTConsumer : public ASTConsumer { virtual void Initialize(ASTContext& Context); virtual void HandleTopLevelDecl(DeclGroupRef DG) { // ... if( const FieldDecl* fd = dyn_cast<FieldDecl>(declIn) ) // ... else if( const CXXMethodDecl* md = dyn_cast<CXXMethodDecl>(declIn) ) // ... else if( const EnumConstantDecl* ed = dyn_cast<EnumConstantDecl>(declIn) ) // ... } };

14

R

slide-15
SLIDE 15

Clang Custom Output

15

File( id=20270, location='Base/Types/Geometry/hkGeometry.h' ) RecordType( id=20271, name='hkGeometry', polymorphic=False, abstract=False, scopeid=20270 ) Method( id=20317, recordid=20271, typeid=20316, name='getTriangle' ) Field( id=20320, recordid=20271, typeid=9089, name='m_vertices' )

R

slide-16
SLIDE 16

Build Integration

Prebuild step runs Clang if necessary Plugins run on the database That’s it 16

R

ClangAST DB Generate Reflection Reflect.cpp Static Analysis

slide-17
SLIDE 17

Runtime Introspection

float bool float bool short float bool bool short float

17

R

struct X { float time; bool used; float pos; bool canmove; short flags; };

slide-18
SLIDE 18

18

R

slide-19
SLIDE 19

Reflection Conclusion

LLVM Clang pass

  • Robust
  • Eliminates out-of-sync errors

DB Consumer pass

  • Pre-compile logic checks
  • Runtime errors → compile errors

Unit Tests

  • Examine reflection data

19

slide-20
SLIDE 20

Language Binding

Expose C++ to script Data: Natural Callables: Harder 20

slide-21
SLIDE 21

Sample Interface

class Timeline { /// Adds a label at the given time and /// returns an id for that label int addLabel( float time, const char* label ); };

21

B

slide-22
SLIDE 22

What is a Binding?

22

Lua State int addLabel( float time, const char* label) { // ... } Lua_String ‚GDC‛ Lua_Num 1.4

B

slide-23
SLIDE 23

Three Parts of a Binding

23

Lua State Lua_String ‚GDC‛ Lua_Num 1.4

float time = lua_... char* label = lua_... push time push label call addLabel pop id lua_set_return( id )

Lua_Int ret = 64

B

slide-24
SLIDE 24

int wrap_timeLine_addLabel(lua_state* s) { TimeLine* tline = lua_checkuserdata(s,1); int arg0 = lua_checkint(s,2); const char* arg1 = lua_checkstring(s,3); int id = tline->addLabel( arg0, arg1 ); lua_pushint(id); return 1; }

Manual Bindings

24

timeLine:addLabel(1,‛x‛)

B

slide-25
SLIDE 25

“Fat” Bindings

1:1 wrapper:native Manual or Generated ~400b per wrapper 25

B

timeLine:addLabel(1,‛x‛) wrap_addLabel() TimeLine::addLabel()

slide-26
SLIDE 26

Slimmer Bindings?

26

wrap_x() wrap_y() wrap_z() wrap_z() wrap_any() data_x data_y data_z data_w

B

slide-27
SLIDE 27

Reflected Function

struct FunctionReflection { const char* name; Callable callable; TypeReflection* argTypes; int numArgs; }; 27

B

// ‚function pointer‛ // Including return type

slide-28
SLIDE 28

Slimmer Binding

28

B

int wrap_anything(lua_state* s) { FunctionReflection* fr = func_get(s); Buffer buf; unpack_args(s, fr->argTypes, buf); (*fr->callable)(buf); return pack_args(s, fr->argTypes, buf); }

slide-29
SLIDE 29

Function Trampoline

29

typedef int (*Func_int__int_charp)(int i, char* c); void trampoline(void** buf, Func_int__int_charp funcptr) { int& ret = *(int*)buf[0]; int& a0 = *(int*)buf[1]; char* a1 = *(char**)buf[2]; ret = (*funcptr)(a0,a1); }

B

slide-30
SLIDE 30

Trampolines

30

call_in_lua() lua_bridge() native(int x) trampoline_int()

B

native(int,char*) trampoline_int_charp()

slide-31
SLIDE 31

Fat vs Slim Memory Cost

31

N * wrap_func() N * func()

N functions T distinct trampolines (T≤N)

1 lua_bridge() N * Reflection T * trampoline() N * func() B

~400*N ~40*N + ~64*T

slide-32
SLIDE 32

Sharing the Trampolines

32

tl:addLabel(1,‛x‛) lua_bridge() trampoline() the_actual_native_function() python_bridge() tl.addLabel(1,’x’)

B

slide-33
SLIDE 33

Bindings Conclusion

Generated “Slim” Bindings

 Crossplatform & Multilanguage!

Marginally slower

 Extra indirection

Considerably smaller 33

slide-34
SLIDE 34

Memory Reporting Goals

More than just block sizes Account for every byte Low maintenance burden Customizable output 34

M

slide-35
SLIDE 35

Memory Reporting

Manual getMemoryStatistics() Buggy Tedious 35

M

void hkpMeshShape::calcContentStatistics ( hkStatisticsCollector* collector ) const { collector->addArray( "SubParts", this->m_subparts ); for( int i = 0; i < this->m_childInfo.getSize(); i++ ) { collector->addReferencedObject( "Child", m_childInfo[i].m_shape ); } hkpShapeCollection::calcContentStatistics(collector); }

slide-36
SLIDE 36

Automatic Reports

Aim for 100% automatic coverage Provide debugging for missing Leapfrog Technique:

 Remember types of (some) allocations  Know offsets of pointers in each type

36

M

slide-37
SLIDE 37

Raw Blocks

Raw pairs of (address,size) 37

M

slide-38
SLIDE 38

Type Roots

? Mesh ? ? BvTree ?

Hooked class

  • perator

new/delete 38

M

slide-39
SLIDE 39

Reflection Walk

Mesh { Section []; }; Section { Vector4[]; Indices[]; }; ? Mesh ? Section BvTree void

39

M

slide-40
SLIDE 40

Reflection Walk

Finds all pointer-to relationships Debug – Time & Stacktrace Verify everything reached vector4[] Mesh int[3][] Section BvTree void

40

M

slide-41
SLIDE 41

Memory Implementation Details

Custom Type Handlers

 slack space – false positives

Obfuscated Pointers

 ptr ^= 1;

Untyped Data

 void*

41

M

slide-42
SLIDE 42

Annotated Memory Dump

D 1287169594 M Win32 “demos.exe" T 0 UNKNOWN T 1 hkNativeFileSystem C 13 0x29656826 0x29828312 … a 0 8 t 0 1 c 0 13 …

  • 1 113 9
  • 2 193

… L 0x29656826 source\common\base\system\io\filesystem\hknativefilesystem.h(90):'hkNativeFileSystem::operator

new'

#L(ocation) <address> <string name>? #C(allstack) <callstack id> <address>+ #T(ype) <type id> <type name> #a(llocation) <alloc id> <size> #t(ype instance) <alloc id> <type id> #c(allstack instance) <alloc id> <callstack id> #o(wns) <alloc id> <'owned' alloc id>+ #M(odule information) <platform-dependent> #D(ate of capture) <timestamp>

42

M

slide-43
SLIDE 43

43

M

slide-44
SLIDE 44

Memory Report Conclusion

Label root blocks & grow with reflection We found offline analysis useful Low maintenance Accounts for all reachable memory

 Or tells you where it came from

44

slide-45
SLIDE 45

Versioning

Change Happens Hitting a moving target Not much in literature Fidelity Separate to Serialization 45

V

slide-46
SLIDE 46

Manual Conditionals

Version number Conditionals Inter-object changes? Large refactor? 46

V

xtream.TokenMustBe("POINT_A"); xtream>>point_A; bool has_two_bodies = true;

if (xtream.GetVersion()>=1350) { xtream.TokenMustBe("HAS_TWO_BODIES"); xtream>>has_two_bodies; }

if (has_two_bodies) { xtream.TokenMustBe("BODY_B"); xtream>>prot_buffer; priv_rigid_body_B = s->GetRigidBody(prot_buffer); if (!priv_rigid_body_B) throw_exception("Rigidbody unknown in Spring"); } xtream.TokenMustBe("POINT_B"); xtream>>point_B; //…

slide-47
SLIDE 47

Ideal Versioning System

Don’t constrain my changes Don’t slow me down Help me fix it up Don’t make me revisit Don’t make me think 47

V

slide-48
SLIDE 48

Snapshot Versioning

Compare all previous vs current metadata Function updates changed members Check up-to-date with CRC of reflection 48

V

slide-49
SLIDE 49

Snapshot example

A { int x; int y; } B { A* a; }

49

V

A { int y; } B { A* a; int x; } { ‚A‛, 0x1234, 0x8989, NULL }, { ‚B‛, 0xabcd, 0xfed4, Update_B }. void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; }

slide-50
SLIDE 50

Version Function

50

V

void Update_B( Object& oldB, Object& newB ) { newB[‚x‛] = oldB[‚a‛].asObject()[‚x‛].asInt(); } A { int x; int y; } B { A* a; } A { int y; } B { A* a; int x; }

slide-51
SLIDE 51

Chaining Snapshot Updates

51

A { int x; int y; } B { A* a; } A { int y; } B { A* a; int x;}

{ ‚A‛, 0x1234, 0x8989, NULL } { ‚B‛, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; }

V1 → V2

A { int y; } B { A* a; int x; } A { int y; int z; } B { A* a; int x; }

{ ‚A‛, 0x8989, 0x52c1, Update_A } { ‚B‛, 0xfed4, 0xf115, NULL }

void Update_A( void* oldA, void* newA ) { // newA->z = 1000; }

V2 → Current

V

Current

slide-52
SLIDE 52

Snapshots Manual v0 v1 v2 Now

What Have We Gained?

52

if version<1 if version<2 if version<1 if version<3 if version<2 if version<1

v0 v1 v2 Now

A { int x; int y; } B { A* a; } A { int y; } B { A* a; int x;} { ‚A‛, 0x1234, 0x8989, NULL } { ‚B‛, 0xabcd, 0xfed4, Update_B } void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; } A { int x; int y; } B { A* a; } A { int y; } B { A* a; int x;} { ‚A‛, 0x1234, 0x8989, NULL } { ‚B‛, 0xabcd, 0xfed4, Update_B } void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; } A { int x; int y; } B { A* a; } A { int y; } B { A* a; int x;} { ‚A‛, 0x1234, 0x8989, NULL } { ‚B‛, 0xabcd, 0xfed4, Update_B } void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; }

V

slide-53
SLIDE 53

Finer Grained Changes

Full snapshots too heavy We hired artists New products, lots of branches

  • No global timeline

53

V

slide-54
SLIDE 54

Patching (1)

B0→B1 B.add(int,“y”)

54

A0 { int x; } B1 { A* a; int y; } A1 { int x; int y; } B2 { A* a; } A0 { int x; } B0 { A* a; }

V

slide-55
SLIDE 55

Patching (2)

B0→B1 B.add(int,“y”)

55

A0 { int x; } B1 { A* a; int y; } A1 { int x; int y; } B2 { A* a; } A0 { int x; } B0 { A* a; }

A0→A1 B1→B2 A.add(int,“y”) A.y = B.y B.rem(“y”)

V

slide-56
SLIDE 56

Atomic Patch Types

Add/Remove/Rename member Add/Remove/Rename class Change member default Cast object type Set parent Depends Function 56

V

slide-57
SLIDE 57

Graph Of Patches

57

B0→B1 A0→A1 B1→B2 A1→A2 B2→B3 C0→C1

V

slide-58
SLIDE 58

Sample Patch

// This block is generated by the metadata comparison. // It is pasted into the source and expands out to a few constant structures. PATCH_BEGIN("hkbLookAtModifier", 2, "hkbLookAtModifier", 3) // Require hkbEventBase version 3 before running this patch PATCH_DEPENDS("hkbEventBase", 3) PATCH_MEMBER_ADDED_VEC_4("neckForwardLS", 0.0f,1.0f,0.0f,0.0f) // user edit: add & remove changed to rename PATCH_MEMBER_RENAMED("headForwardHS", "headForwardLS") // user edit: call a C function here PATCH_FUNCTION( hkbLookAtModifier_2_to_3 ) PATCH_END()

58

V

slide-59
SLIDE 59

Verification

59

V

Previous Reflection

Apply Patches

Patched Reflection

Diff against current reflection

Patches OK Patches Wrong/ Missing

slide-60
SLIDE 60

Versioning Workflow

Make your changes Run a reflection diff utility Copy & paste the suggested patch Edit (add&remove → rename) Write version function if necessary Done 60

V

slide-61
SLIDE 61

Versioning Conclusion

Massively reduced tax Quick to add versioning Cheap to test Non-prescriptive workflow 61

slide-62
SLIDE 62

Conclusion

63 Serialization Memory Reflection Binding Versioning