"The Geometry of Color in the Light of a Non-Riemannian Space"
1,316 karma · joined March 25, 2012
"The Geometry of Color in the Light of a Non-Riemannian Space"
This seems to imply that what is or is not a chair is a subjective or conditional.
https://support.apple.com/en-us/108351
https://www.audiosciencereview.com/forum/index.php?threads/r...
Intel has DOIT: https://www.intel.com/content/www/us/en/developer/articles/t...
The idea is that the processor will not take shortcuts that take advantage of the values it is processing. For example, a 64-bit division cannot shortcut if the operands are both small, etc.
typedef int (*pfn)(void);
int g(void);
int h(void);
pfn f(double x) {
switch ((long long)x) {
case 0:
return g;
case 17:
return h;
}
}
If I understand your perspective correctly, `f` should return whatever happens to be in rax if the caller does not pass in a number which truncates to 0 or 17? int f(int x) {
switch (x) {
case 0:
return 31;
case 1:
return 28;
case 2:
return 30;
}
}
This code on its own has no undefined behavior.In another translation unit, someone calls `f(3)`. What would you have compilers do in that case?
That path through the program has undefined behavior. However, the two translation units are separate and as such normal tooling will not be able to detect any sort of UB without some kind of whole program static analysis or heavy instrumentation which would harm performance.
int f(int x) {
static int y[] = {42, 43};
return y[x];
}
What behavior should `f(-1)` or `f(100)` have? What is sensible?However, I can't determine where this is originally from...
>>> [ord(x) ^ ord(y) for (x, y) in zip("windows", "sindogs")]
[4, 0, 0, 0, 0, 16, 0]
Sure enough..."In the process, we learned ten lessons about DSAs and DNNs in general and about DNN DSAs specifically that shaped the design of TPUv4i:
1. Logic improves more quickly than wires and SRAM ⇒ TPUv4i has 4 MXUs per core vs 2 for TPUv3 and 1 for TPUv1/v2.
2. Leverage existing compiler optimizations ⇒ TPUv4i evolved from TPUv3 instead of being a brand new ISA.
3. Design for perf/TCO instead of perf/CapEx ⇒ TDP is low, CMEM/HBM are fast, and the die is not big.
4. Backwards ML compatibility enables rapid deployment of trained DNNs ⇒TPUv4i supports bf16 and avoids arithmetic problems by looking like TPUv3 from the XLA compiler’s perspective.
5. Inference DSAs need air cooling for global scale ⇒ Its design and 1.0 GHz clock lowers its TDP to 175W.
6. Some inference apps need floating point arithmetic ⇒ It supports bf16 and int8, so quantization is optional.
7. Production inference normally needs multi-tenancy ⇒ TPUv4i’s HBM capacity can support multiple tenants.
8. DNNs grow ~1.5x annually in memory and compute ⇒ To support DNN growth, TPUv4i has 4 MXUs, fast onand off-chip memory, and ICI to link 4 adjacent TPUs.
9. DNN workloads evolve with DNN breakthroughs ⇒ Its programmability and software stack help pace DNNs.
10. The inference SLO is P99 latency, not batch size ⇒ Backwards ML compatible training tailors DNNs to TPUv4i, yielding batch sizes of 8–128 that raise throughput and meet SLOs. Applications do not restrict batch size."
What is notable about their Itanium efforts is that they chose to use ELF and DWARF and their object file and debugging formats. I think that this was actually quite important as it made it far easier for the x86 port: LLVM has robust support for ELF & DWARF.
I think another thing which helped is that they wrote more of OpenVMS in C which avoided the need for an x86 PL/I compiler, etc.
With certain restrictions, it is possible to do this: Google Native Client [1] has a verifier which checks that programs it executed did not jump into the middle of other instructions, forbade run-time code generation inside of such programs, etc.
[expr.call]: "The lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the argument expression."
[conv.lval]: "... if T has a class type, the conversion copy-initializes the result object from the glvalue."
The way a program can tell if a compiler is compliant to the standard is like so:
struct S { int large[100]; };
int compliant(struct S a, const struct S *b);
int escape(const void *x);
int bad() {
struct S s;
escape(&s);
return compliant(s, &s);
}
int compliant(struct S a, const struct S *b) {
int r = &a != b;
escape(&a);
escape(b);
return r;
}
There are three calls to 'escape'. A programmer may assume that the first and third call to escape observes a different object than the second call to escape and they may assume that 'compliant' returns '1'.Often, such hardware is written using hardware description languages [1] like Verilog or VHDL. These languages are very low level and, in the opinion of some, a little clumsy to use.
XLS aims to provide a system for High-level synthesis [2]. The benefit of such systems is that you can more easily map interesting algorithms to hardware without being super low level.
[1] https://en.wikipedia.org/wiki/Hardware_description_language
This would imply that 1 / (1 / -inf) would now be +inf instead of -inf.
Google makes it pretty clear that they do not sell any personal information to advertisers: https://safety.google/privacy/ads-and-data/
I also wonder if this will be one of those things which youngins' will take for granted someday...
For more details, see http://blog.llvm.org/2010/04/intro-to-llvm-mc-project.html
Steve's book, The Algorithm Design Manual, was pretty inspirational to me in high school. Arch did a compiler, KAI C++, and designed Intel's TBB library (Thread Building Blocks).
The way C++ exceptions are used creates very interesting interactions in the design of their implementation.
There a hidden cost many forget: call frame information restricts what the compiler can do. Why is this the case? Even if exceptions are super rare, the compiler still needs to make sure the program will obey the language rules if an exception is somehow thrown at an appropriate program point.
The compiler must emit instructions which can be represented by the call frame information in order for the unwind to be able to reason about the stack.
This is usually not a problem on Linux because the DWARF CFI is very expressive. That expressiveness comes at a cost: it is quite inefficient when it comes to size.
Other platforms, Windows NT (ARM, x64 and IPF) and iOS, recognized that this increase in size is a bad state of affairs and thus aimed to make the CFI more compact. By doing this, they greatly reduced the size of CFI but unfortunately created restrictions on what a compiler can do.
As for trickiness inherent in C++ exceptions, C++ supports a fairly esoteric feature: exceptions may be rethrown without being in a try or catch:
void rethrow() {
throw;
}
An easy way to make this sort of thing work would be to thread a secret in/out parameter which represents the exception state.But how is this typically implemented?
Well, remember, the ethos of exceptions in C++ is that they are rare. Rare enough that implementors are discouraged from optimizing the speed of C++ exceptions.
Instead, thread local storage is typically used to go from any particular thread back to it's context.
Things get pretty darn complicated pretty quickly with features like dynamic exception specifications:
void callee() throw(double) {
throw 0;
}
void caller() {
try {
callee();
} catch (...) {
puts("got here!");
}
}
On first examination, "got here!" should be unreachable because the call to "callee" results in a violation of the exception specification.However, this is not necessarily the case! What _actually_ happens is that some code runs between the throw and the catch: std::unexpected is called.
Now, std::unexpected might throw an exception of it's own! If this new exception matches the exception specification, the exception would pass into the catch block in "caller". If it doesn't, the exception thrown within std::unexpected might result in another violation!
Wow, this is get complicated... OK, so what happens if it results in another violation? Well, the exception gets cleaned up and replaced with, you guessed it, another exception! We'd be left with an exception of type std::bad_exception leaving std::unexpected and thus "callee". Because the catch clause in "caller" is compatible with std::bad_exception, control is transferred to the catch block.
This is the tip of the iceberg. A huge amount of machinery is sitting around, waiting to engage to make exceptions work.
Let's start with an excellent quote from Wittgenstein. "The limits of my language mean the limits of my world."
Using C as your intermediate language means that your expressiveness is limited to valid C programs. This is workable but only if your language can be mapped to C in _useful_ ways.
For example, let's say your language has behavior similar to scheme's tail-call. How would you get this behavior from a C compiler? You will never be able to make this reliably across optimization levels, etc.
Guaranteed tail-calls are the tip of the iceberg, there are a lot more features which cannot be reasonably mapped onto C.
Real compiler IRs increase your expressivity beyond what the C language designers decided was important.