The actual paper is:
"The Geometry of Color in the Light of a Non-Riemannian Space"
HN user
The actual paper is:
"The Geometry of Color in the Light of a Non-Riemannian Space"
It seems he is still active in the bioinformatics space: https://github.com/thegenemyers/FASTK
What if it breaks in a way which renders it no longer a chair for you but not others?
This seems to imply that what is or is not a chair is a subjective or conditional.
It was almost certainly added in order to support EFI which uses PE/COFF format binaries.
Internal DACs/amps have gotten a lot better, at least in Apple products.
https://support.apple.com/en-us/108351
https://www.audiosciencereview.com/forum/index.php?threads/r...
ARM has DIT: https://developer.arm.com/documentation/ddi0595/2021-06/AArc...
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.
With chip fabrication technology in the spotlight lately, this book chapter on the history of EUV development and DARPA's role might be of interest to some.
So let's change it up a bit.
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?What behavior should the following have:
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.
That works for a lot of behavior but not everything. For example:
int f(int x) {
static int y[] = {42, 43};
return y[x];
}
What behavior should `f(-1)` or `f(100)` have? What is sensible?This python actually builds a graph under the hood which then gets JIT compiled for CPU/GPU/TPU.
I found more of it in here: https://www.oranlooney.com/post/playfair/
However, I can't determine where this is originally from...
Fun fact: the MSVC C++ ABI gives up if the mangled name is >= 4096 characters, it just replaces the symbol with md5(mangled name): https://github.com/llvm/llvm-project/blob/d32f71a91a432db2d9...
I particularly liked the "Windows" vs "Sindogs" issue. My immediate hunch was that the characters differed by a single bit so I ran a bit of python:
>>> [ord(x) ^ ord(y) for (x, y) in zip("windows", "sindogs")]
[4, 0, 0, 0, 0, 16, 0]
Sure enough...The conclusions section of the paper is a good summary:
"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."
There is this great document (http://www.decus.de/events/alphamigration/vortraege/porting_...) which details the efforts to go from Alpha to Itanium.
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.
I believe that Rice's theorem is about computability, not about whether or not it is possible to validate which CPU instructions a program can contain.
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.
My reading of the C++ standard is that this behavior is effectively mandated and that one can write a program which can tell if an ABI observed the proposed optimization.
[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'.It is a project aimed at making the design of electronic logical easier.
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
Interesting. One issue is treatment of 1 / -inf. This would be -0 in traditional IEEE 754 but would now be +0 IIUC.
This would imply that 1 / (1 / -inf) would now be +inf instead of -inf.
Citation needed.
Google makes it pretty clear that they do not sell any personal information to advertisers: https://safety.google/privacy/ads-and-data/
That's really only a fraction of it. The only definitive documentation that I know of is clang's implementation of its mangling scheme: https://github.com/llvm-mirror/clang/blob/master/lib/AST/Mic...
I use Google Express quite often for grabbing house hold essentials that I don't have the time to make a dedicated trip. I wonder if my habits will change now that I just need to speak up to place an order.
I also wonder if this will be one of those things which youngins' will take for granted someday...
It is the low-level assembler-related machinery in LLVM.
For more details, see http://blog.llvm.org/2010/04/intro-to-llvm-mc-project.html
Clang/C2 doesn't use LLVM, it will use Microsoft's C2 backend.
Wow, the other team members aren't slouches either, Arch D. Robison and Steven Skiena are pretty neat!
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).
I recently reimplemented machinery in our compiler which implements C++ exceptions. I figure it might be useful to share a few interesting issues.
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.
Professional compiler engineer here, C is a mediocre intermediate language.
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.
"SSA-based Compiler Design" [1] is awesome, heaps of interesting algorithms.