// API point linkage stubs, as generated by jextract. // // Any type has a number of _API points_ that may be applied to values // of that type. For example, C++ classes may supply API points for // field access, method call, implicit conversions, etc. Making a // subclass is a complex API point. Fortran arrays may be read, // written, sliced, and aliased with other arrays. // // Some API points are defined in terms of an OS-specific ABI, which // means that on any given system there is a specific series of // machine instructions that operate the API point. For ANSI C, all // API points, except macros, are defined by an ABI. For C++, ABI // support may be partial and/or unstable. // // ABI-defined API points are data access (structs and arrays) and // function calls (both named and via a function pointer). On some // systems the ABI may also specify the mechanics of name mangling, // virtual function calls, and subclass layout. // // A C++ inline function consists of code that is replicated into // client uses of that function. Unix-like ABIs do not directly // represent the action of an inline function, and so API features // built from function inlining are not supported by thoses ABIs. // // An ABI-defined API point can be operated by a metadata-driven // mechanism, such as libffi, or the JVM's native call generator. // Other API points a real compiler to directly emit code, at compile // time, to operate a particular API point on a particular variable. // // If an ABI could include enough AST or IR capabilities to represent // a function body, that function could be exported to applications // without direct inlining at compile time. The inlining would take // place during linking or JIT compilation. This in fact is what the // JVM does, since its ABI can encode most methods using bytecodes. // This more powerful representation allows more optimizations to // occur after link time. // // On Unix-like systems, nearly all API points can be supported at // least indirectly by the system ABI. One simple way to do this is // by wrapping the essential action of each API point (for each type) // into a a _machine code stub_ which contains the code that the // compiler would generate to operate that API point. The stub itself // must be callable using the ABI; typically it is a function with // arguments drawn from a limited set of types (pointers and other // scalars). If the type being operated on is complex, the stub // requires the caller to put the type's value in memory first, and // then pass a pointer to the stub. In this way, a wide variety of // non-ABI-capable operations can be expressed using little snippets // of binary code wrapped in ABI-capable entry points. These little // snippets are called out-of-line, and so may cost performance and // prevent some optimizations. But they are convenient and often good // enough. // // The jextract tool scans a header file (or other API specification) // and finds API points to make available to a Java programmer. It // emits metadata in Java native form, which is to say it emits a // bundle (JAR) of class-files. The classes are purely abstract // interfaces describing the shape of the APIs, not their contents. // Annotations are used to bind ABI parameters to particular names. // For example, a struct field might be annotated with its type, name, // and offset, and a function might be annotated with its type, name, // and linker symbol. Elements that can be easily computed from the // Java types and names need not be repeated in annotations. // // When the Java application runs, it loads the extracted metadata and // runs a _binder_ on it, which gives implementations to all the // interfaces, implementations which are consistent with the ABI // requirements. For example, a struct field might be accessed with // a call to a "get" or "put" operation from the "Unsafe" facility, // computing the address using the offset associated with the field. // // An inline function cannot (in the general case) be represented // fully using metadata, so the jextract tool must also emit a machine // code stub which wraps the function (as if it were out-of-line). // The jextract tool must also leave enough "clues" in the metadata to // enable the binder to associate each API point with the correct // stub. These stubs should be emitted in two forms: First, as C++ // code, for purposes of debugging and porting. Second, as a DLL to // be loaded into the JVM with the associated library. // // Here are some examples of C++ classes and associated suites of // machine code stubs. namespace ns_C { // concrete class C class C { public: static void sm(); static int sf; C(int ca); C(); // default ctor // per-instance: virtual ~C(); int f; void pm(); // "plain" = non-virtual, non-static virtual void vm(); }; namespace jx_ns_C { // JX API points: static void jx_call_sm() { C::sm(); } static int* jx_ref_sf() { return &C::sf; } static int jx_get_sf() { return *jx_ref_sf(); } static void jx_set_sf(int v) { *jx_ref_sf() = v; } static void* jx_auto_C(void* fn(void*, C*), void* a, int ca) { C x(ca); return fn(a, &x); } static C* jx_new_C(int ca) { return new C(ca); } static C* jx_new_C() { return new C; } static C* jx_newa_C(int len, int ca) { return new C[len]; } //requires C::C() static void jx_del_C(C* x) { delete x; } static int* jx_ref_f(C* x) { return &x->f; } static int jx_get_f(C* x) { return *jx_ref_f(x); } // also set_f static void jx_call_m(C* x) { x->pm(); } static void jx_callv_vm(C* x) { x->vm(); } static void jx_callq_C_vm(C* x) { x->C::vm(); } // like Java invokespecial } } namespace ns_BD0 { // single base class B and subclass D, simplified API class B { public: virtual void vm(); }; namespace jx_ns_B { // JX API points: static void jx_callv_vm(B* x) { x->vm(); } static void jx_callq_B_vm(B* x) { x->B::vm(); } // like Java invokespecial //void* jx_display[] = { (void*)&jx_callv_vm, (void*)&jx_callq_B_vm }; } class D : public B { public: D(); virtual void vm(); // override virtual int vm2(); // new }; namespace jx_ns_D { // JX API points: static void jx_callv_vm(D* x) { x->vm(); } static void jx_callq_D_vm(D* x) { x->D::vm(); } static int jx_callv_vm2(D* x) { return x->vm2(); } static int jx_callq_D_vm2(D* x) { return x->D::vm2(); } } } namespace ns_BD { // single base class B and subclass D, full API class B { public: static void sm(); static int sf; B(); // per-instance: virtual ~B(); int f; void pm(); // "plain" = non-virtual, non-static virtual void vm(); }; namespace jx_ns_B { // JX API points: static void jx_call_sm() { B::sm(); } static int* jx_ref_sf() { return &B::sf; } static int jx_get_sf() { return *jx_ref_sf(); } // also set_sf static void* jx_auto_B(void* fn(void*, B*), void* a) { B x; return fn(a, &x); } static B* jx_new_B() { return new B(); } static void jx_del_B(B* x) { delete x; } static int* jx_ref_f(B* x) { return &x->f; } static int jx_get_f(B* x) { return *jx_ref_f(x); } // also set_f static void jx_call_m(B* x) { x->pm(); } static void jx_callv_vm(B* x) { x->vm(); } static void jx_callq_B_vm(B* x) { x->B::vm(); } // like Java invokespecial } class D : public B { public: static void sm(); static int sf; D(); virtual ~D(); // per-instance: int f; void pm(); // "plain" = non-virtual, non-static virtual void vm(); }; namespace jx_ns_D { // JX API points: static void jx_call_sm() { D::sm(); } static int* jx_ref_sf() { return &D::sf; } static int jx_get_sf() { return *jx_ref_sf(); } // also set_sf static void* jx_auto_D(void* fn(void*, D*), void* a) { D x; return fn(a, &x); } static D* jx_new_D() { return new D(); } static void jx_del_D(D* x) { delete x; } static int* jx_ref_f(D* x) { return &x->f; } static int jx_get_f(D* x) { return *jx_ref_f(x); } // also set_f static void jx_call_m(D* x) { x->pm(); } static void jx_callv_vm(D* x) { x->vm(); } static void jx_callq_D_vm(D* x) { x->D::vm(); } // like Java invokespecial } } namespace ns_B_sub { // single base class B and generic subclass class B { public: B(int ca); // per-instance: virtual ~B(); virtual void vm(); virtual int vm2(int) = 0; }; namespace jx_ns_B { // JX API points: static void jx_del_B(B* x) { delete x; } static void jx_callv_vm(B* x) { x->vm(); } static void jx_callq_B_vm(B* x) { x->B::vm(); } static int jx_callv_vm2(B* x, int a) { return x->vm2(a); } //no jx_callq_B_vm2 for abstract method } // this class is universal enough to allow subclassing from a managed language class jx_sub_B; // one instance of vtable class is created per set of method overrides // (e.g., per subclass or object prototype) struct jx_vt_B { public: typedef struct { void* metadata[4]; } jx_mdt; // user scratch area typedef struct { void* impldata[2]; } jx_idt; // user scratch area typedef void jx_ctft(jx_sub_B* self, jx_idt* id, int ca); typedef void jx_dtft(jx_sub_B* self); typedef void jx_mft_vm(jx_sub_B* self); typedef int jx_mft_vm2(jx_sub_B* self, int a); jx_mdt jx_metadata; jx_ctft* jx_ctimpl; jx_dtft* jx_dtimpl; jx_mft_vm* jx_impl_vm; jx_mft_vm2* jx_impl_vm2; }; namespace jx_ns_jx_vt_B { /* ... jx bindings for POD struct ... */ } class jx_sub_B : public B { public: jx_vt_B* const jx_vtable_B; jx_vt_B::jx_idt jx_impldata; jx_sub_B(jx_vt_B* vt, jx_vt_B::jx_idt* id, int ca) : B(ca), jx_vtable_B(vt) { vt->jx_ctimpl(this, id, ca); } virtual ~jx_sub_B() { jx_vtable_B->jx_dtimpl(this); } // per-instance: virtual void vm() { jx_vtable_B->jx_impl_vm(this); } virtual int vm2(int a) { return jx_vtable_B->jx_impl_vm2(this, a); } // (could use operator new machinery to extend impl. data) }; namespace jx_ns_jx_sub_B { // JX API points: static void* jx_auto_jx_sub_B(void* fn(void*, jx_sub_B*), void* a, jx_vt_B* vt, jx_vt_B::jx_idt* id, int ca) { jx_sub_B x(vt, id, ca); return fn(a, &x); } static jx_sub_B* jx_new_jx_sub_B(jx_vt_B* vt, jx_vt_B::jx_idt* id, int ca) { return new jx_sub_B(vt, id, ca); } static void jx_del_jx_sub_B(jx_sub_B* x) { delete x; } static void jx_callv_vm(jx_sub_B* x) { x->vm(); } static void jx_callq_jx_sub_B_vm(jx_sub_B* x) { x->jx_sub_B::vm(); } static int jx_callv_vm2(jx_sub_B* x, int a) { return x->vm2(a); } static int jx_callq_jx_sub_B_vm2(jx_sub_B* x, int a) { return x->jx_sub_B::vm2(a); } // access to vtable (getter only, since ptr is const) static jx_vt_B* jx_get_jx_vtable_B(jx_sub_B* x) { return x->jx_vtable_B; } // access to impldata (ref-er only, since it is a POD struct) static jx_vt_B::jx_idt* jx_ref_jx_impldata(jx_sub_B* x) { return &x->jx_impldata; } } } // test code to exercise generic subclassing // g++ -o cppapi cppapi.cpp && ./cppapi namespace ns_B_sub { extern int main(int ac, char* av[]); } #include #include #include int main(int ac, char* av[]) { printf("testing ns_B_sub...\n"); ns_B_sub::main(ac, av); printf("testing ns_B_sub: done\n"); } namespace ns_B_sub { // test implementation of generic subclass of B: inline long i(void* x) { return (long)x; } B::B(int ca) { printf("new B::B(%lx, %d)\n", i(this), ca); } B::~B() { printf("delete B::B(%lx)\n", i(this)); } void B::vm() { printf("B::vm(%lx)\n", i(this)); } // helpers for virtual subclass: const char* &myf1(jx_sub_B* self); int &myf2(jx_sub_B* self); const char* mymeta(jx_sub_B* self); int main(int ac, char* av[]) { // make a virtual subclass of B: const char* metadata = "foo-class"; jx_vt_B myvt; *(const char**)&myvt.jx_metadata = metadata; // use the scratch area { // plug in the virtual function implementations extern jx_vt_B::jx_ctft myct; extern jx_vt_B::jx_dtft mydt; extern jx_vt_B::jx_mft_vm myvm; extern jx_vt_B::jx_mft_vm2 myvm2; myvt.jx_ctimpl = myct; myvt.jx_dtimpl = mydt; myvt.jx_impl_vm = myvm; myvt.jx_impl_vm2 = myvm2; } // make an instance of this virtual subclass using the JX-able API: int ca = 42, f2 = 99; const char* impldata = "foo"; B* myb = ns_B_sub::jx_ns_jx_sub_B::jx_new_jx_sub_B (&myvt, (jx_vt_B::jx_idt*)&impldata, ca); assert(!strcmp(metadata, mymeta((jx_sub_B*)myb))); assert(!strcmp(impldata, myf1((jx_sub_B*)myb))); myf2((jx_sub_B*)myb) = f2; // set up an extra field outside the constructor: // exercise it and then delete it, using only the B API: myb->vm(); int a = 99, r = myb->vm2(a); printf("vm2(%d) returns %d\n", a, r); delete myb; return 0; { /* OUTPUT: testing ns_B_sub... new B::B(7faa96402790, 42) myct self=7faa96402790 id=7fff5c8b0aa8/foo ca=42... myct self=7faa96402790, meta=foo-class, f1=foo, f2=0 myvm self=7faa96402790, meta=foo-class, f1=foo, f2=99 super: B::vm(7faa96402790) myvm2 a=99 self=7faa96402790, meta=foo-class, f1=foo, f2=99 vm2(99) returns 100 mydt self=7faa96402790, meta=foo-class, f1=foo, f2=99 delete B::B(7faa96402790) testing ns_B_sub: done */ } } // The following code uses direct C++ API for jx_sub_B. // A managed language would use indirect access via jx_ns_jx_sub_B. static char* dump(jx_sub_B* self) { static char buf[1000]; sprintf(buf, "self=%lx, meta=%s, f1=%s, f2=%d", i(self), mymeta(self), myf1(self), myf2(self)); return buf; } void myct(jx_sub_B* self, jx_vt_B::jx_idt* id, int ca) { printf("myct self=%lx id=%lx/%s ca=%d...\n", i(self), i(id), *(const char**)id, ca); myf1(self) = *(const char**)id; myf2(self) = 0; // tidy initialization printf("myct %s\n", dump(self)); } void mydt(jx_sub_B* self) { printf("mydt %s\n", dump(self)); } void myvm(jx_sub_B* self) { printf("myvm %s\n", dump(self)); self->B::vm(); // use jx_callq_B_vm } int myvm2(jx_sub_B* self, int a) { printf("myvm2 a=%d %s\n", a, dump(self)); return a+1; } // helpers, merely for clarity of exposition: const char* &myf1(jx_sub_B* self) { jx_vt_B::jx_idt* id = &self->jx_impldata; void* f1ref = (char*) id + 0; return *(const char**)f1ref; } int &myf2(jx_sub_B* self) { jx_vt_B::jx_idt* id = &self->jx_impldata; void* f2ref = (char*) id + sizeof(char*); return *(int*)f2ref; } const char* mymeta(jx_sub_B* self) { return *(const char**)&self->jx_vtable_B->jx_metadata; } }