3 ms·
C++ and Java reference semantics are very different. Java, like Python, has a model of "references passed by value". void caller() { T a = new T(0);
by KMag 3y ago
C++ and Java reference semantics are very different. Java, like Python, has a model of "references passed by value".
void caller() {
T a = new T(0);
T b = new T(1);
callee(a, b);
}
void callee(T a, T b) {
a = b;
}
In Java, a and b are references, but from caller's perspective, callee is a no-op. On the other hand, using C++'s reference semantics, (and replacing T with T&) callee would modify the a object, not just locally change the reference.
- kaba0 3y agoI don’t see its relevance, that’s up to the compiler to “solve”, adhering to reference’s semantics. It is just pointers/transformations either way in machine code, which can be trivially emulated in any language/platform that has an array of bytes available.
- KMag 3y agoYes, the language runtime version of the "Turing tarpit". Any Turing-complete system can emulate any other. However, in this case, you're bounds-checking absolutely every memory operation, whereas with native Java field access, the class verifier proves the field access is legal just once at class load time.
- kaba0 3y agoThe JIT compiler can elide most of those just as well at runtime
- KMag 3y agoThe JVM can elide most of the Java bounds checks because it has much more static information from Java class files than is available from C code. Once you're emulating a memory-unsafe language, you lose most of those static semantic guarantees you get from valid Java code. In the general case, in a memory unsafe language, statically removing bounds checks is equivalent to solving the halting problem. One could definitely add some extra load-time static analysis to the JVM, similar to what the NaCL / PNaCL did at binary load time to greatly increase the opportunities for JVM bounds check removal. However, this would need some extensions to the class file format and/or some new class file annotations to expose some more static information than the JVM has for vanilla Java class files. In addition, there are also plenty of ways one could optimize away some bounds checks based on undefined behavior, which would result in a standards-conforming C implementation, yet break tons of C code in the wild. (There at least used to be a C to Go transpiler that implemented C unions this way.) For instance, it's UB to write to one field of a union and then read another field, so it'd be legal for a compiler to implement a union as a struct. However, there's plenty of code in the wild (such as many nanboxing/nunboxing implementations) that rely on union fields occupying the same physical addresses.
- kaba0 3y agoA JIT compiler doesn’t operate at that compile time, so it simply doesn’t have to solve it statically — it notices that this invocation at this place always uses 46 for n, so it can use it as a constant for its compilation, at which point it is more than possible to elide many checks.
- KMag 3y agoBounds checks are either runtime checks or removed by static analysis. JIT just pushes back the time at which that static analysis is done and increases the amount of context available (largely through inlining). It's still fundamentally static analysis, just run at JIT time, not at class file generation time. My point is that the JIT-time static analysis in the JVM is heavily optimized for memory accesses typical of Java programs. Typical C code is drastically different, especially since pointer arithmetic is common in C code. In order to properly statically check an access via pointer arithmetic from a pointer allocated via malloc(), the JIT would need to be operating over a code fragment that included both the malloc call (to know where the base address is relative to the bound) and the memory access site. Lots of C code has way too much code executing between malloc and your pointer arithmetic to reasonably expect that the JIT is going to have all of that code in the execution trace it's currently optimizing. In the specific case suggested above, just using a giant byte array to represent all of memory, bounds-checking some memory access to an arithmetic-modified pointer gotten from malloc(), the bounds removal would be dependent on the exact address returned by malloc(). In some programs, maybe there's tons of access to a small number of buffers allocated at program start-up time. In that case, current JVMs might do a pretty good job at eliding bounds checks by noticing that the base address is usually one of a small number of values and having a dedicated code path for those common cases. However, if you're doing frequent malloc(), then you're getting tons of different base addresses. In other programs, the malloc() and the access will be close enough together that the JIT'd fragment will include both the malloc and the access, in which case the JVM will have enough context to elide the bounds access. However, it's way too hand-wavy to just say the JVM is good at eliding array bounds checks. The JVM is very good at eliding array bounds checks typical of Java programs. This is very different from eliding array bounds checks typical in memory-unsafe languages.