HN user

obl

1,695 karma
Posts68
Comments89
View on HN
v8.dev 2mo ago

Leaving the Sea of Nodes in the V8 JIT

obl
20pts2
medium.com 1y ago

The Elusive Frame Timing

obl
2pts0
www.youtube.com 2y ago

2024 EuroLLVM – How Slow Is MLIR [video]

obl
1pts0
www.youtube.com 2y ago

Maya as Editor: The game development approach of Santa Monica Studio [video]

obl
1pts0
www.diva-portal.org 2y ago

Parallel Query Systems: Demand-Driven Incremental Compilers [pdf]

obl
1pts0
www.youtube.com 2y ago

2023 LLVM Dev Meetingg – Mojo: A system programming language [video]

obl
2pts0
www.youtube.com 2y ago

eBPF: Unlocking the Kernel [Official Documentary] [video]

obl
1pts0
www.npopov.com 2y ago

LLVM: Scalar Evolution (SCEV)

obl
1pts0
advances.realtimerendering.com 3y ago

Unreal Engine 5: Nanite deep dive [pdf]

obl
5pts0
verdagon.dev 3y ago

Chasing the Myth of Zero-Overhead Memory Safety

obl
4pts1
gamozolabs.github.io 3y ago

A CPU research kernel for cycle-by-cycle micro-architectural introspection

obl
5pts0
www.ralfj.de 3y ago

From Stacks to Trees: A new aliasing model for Rust

obl
191pts42
docs.google.com 3y ago

V8 Maglev: compiler design doc

obl
49pts17
inko-lang.org 3y ago

Ownership You Can Count on [pdf]

obl
2pts0
nim-lang.org 3y ago

A new runtime for Nim (2019)

obl
2pts0
blog.roblox.com 3y ago

Semantic Subtyping in Luau

obl
2pts0
binsec.github.io 3y ago

SSA Translation is an Abstract Interpretation [pdf]

obl
1pts0
www.youtube.com 3y ago

A Decade Verifying LLVM, or How to Retrofit Soundness in Industrial Software

obl
2pts0
blog.yoshuawuyts.com 3y ago

Const Syntax in Rust

obl
1pts0
www.foonathan.net 3y ago

Carbon’s most exciting feature is its calling convention

obl
243pts217
efabless.com 4y ago

Make Your Own Chips for Free

obl
127pts92
webkit.org 4y ago

The B3 JIT Compiler (2016)

obl
1pts0
jackmin.home.blog 4y ago

Present Latency, DWM and Waitable Swapchains

obl
1pts0
svr-pes20-cppmem.cl.cam.ac.uk 4y ago

CppMem: Interactive C/C++ memory model

obl
3pts0
caseymuratori.com 4y ago

The worst API ever made

obl
5pts0
community.amethyst.rs 5y ago

Archetypal vs. Grouped ECS Architectures

obl
1pts0
lua-users.org 5y ago

Why LuaJIT's interpreter is written in assembly

obl
81pts21
caseymuratori.com 5y ago

The Player Movement System in “The Witness”

obl
3pts4
www.youtube.com 5y ago

Player Movement System for the Witness

obl
2pts0
ec.europa.eu 5y ago

European Commission fines Valve and 5 publishers 8M€ for geo-blocking practices

obl
1pts0

It is not ridiculous at all. Those things have pretty precise definitions and type segregation absolutely does remove a bunch of soundness issues related to type confusion.

You can think of it as the rather classic "Vec of struct + numeric IDs" that is used a lot e.g. in Rust to represent complex graph-like structures.

This combined with bound checking is absolutely memory safe. It has a bunch of correctness issue that can arise due to index confusion but those are not safety issues. When combined with some kind of generational counters those correctness issue also go away but are only caught at runtime not at compile time (and they incur a runtime cost).

Rust's memory safety is about avoiding liveness issues (that become type confusions since all memory allocators will reuse memory for different types), nothing more, nothing less.

Weird that this treats uninitialized variables as unknown values. For example in ex3.c, the program

  int main(){
      int x;
      if (x <= 42){
              assert(x != 12345);
      }
  }
is of course UB in C, even though under a "uninitialized is random" model the program is valid and does not assert (as the model checker concludes).

(even in O1 clang gets rid of the whole function, including even the ret instruction, I'm surprised it does not at least leave an ud2 for an empty function to help debugging since it would not cost anything https://godbolt.org/z/eK8cz3EPe )

It would be interesting to see how you were testing for that, because at least on AMD it's fairly certain that a single thread can be shading multiple primitives.

For example, from the ISA docs [1], pixel waves are preloaded with an SGPR containing a bit mask indicating just that :

The new_prim_mask is a 15-bit mask with one bit per quad; a one in this mask indicates that this quad begins a new primitive, a zero indicates it uses the same primitive as the previous quad. The mask is 15 bits, not 16, since the first quad in a wavefront begins a new primitive and so it is not included in the mask

The mask is used by the interp instructions to load the correct interpolants from local memory.

In fact, in the (older) GCN3 docs [2] there is a diagram showing the memory layout of attributes from multiple primitives for a single wavefront (page 99).

That being said, of course I expect this process to be "lazy" : you would not want to buffer execution of a partially filled thread forever, so depending on the workload you might measure different things.

[1] https://developer.amd.com/wp-content/resources/RDNA2_Shader_...

[2] http://developer.amd.com/wordpress/media/2013/12/AMD_GCN3_In...

  In actual hardware shading is done 32 or 64 pixels at a time, not four. The problem above just got worse.
While it's true that there are "wasted" execution in 2x2 quads for derivative computation, it's absolutely not the case that all lanes of a hardware thread (warp / wavefront) have to come from the same triangle. That would be insanely inefficient.

I dont think that it's publicly documented how the "packing" of quads into lanes is done in the rasterizer for modern GPUs. I'd guess something opportunistic (maybe per tile) taking advantage of the general spatial coherency of triangles in mesh order.

If you didn't already, I'd recommend compiling llvm/clang in debug or release+assert mode when working on it. The codebase is quite heavy in debug-only assertions even for relatively trivial things (like missing implementation of some case, some combination of arguments being invalid, ...) which means that it's pretty easy to get into weird crashes down the line with assertions disabled.

Wait, I'm pretty sure operand collection logic & banking is there to keep the number of ports on the SRAM low, so basically you're arbitrating and buffering requests coming from high register count instructions (say 3 input fma) and potentially multiple pipelined SMT threads (not thread in the nvidia sense, thread in the "a whole wavefront/warp" sense).

However to me it seems that's completely orthogonal to the vector lanes : I don't see why two parallel lanes in a single thread (eg a 64-element GCN wavefront) would need cross-connected logic at the register file, since almost all instructions _do not_ read/write data from another lane.

There are a few cross-lane shuffles / reduce instruction but it seems to me that those would be handled in a dedicated execution unit. (they are not really the fast-path/common case)

That sounds very unlikely ?

"The reason" why memory is slower than registers (apart from size obviously) is that register dependencies are static and can be easily tracked by the core. Memory is way harder since you don't know all the addresses in advance (any other random store could actually be storing to the stack).

That's why they have to use costly (associative) structures like the store queue.

The way cores track the stack is usually for return address prediction because it's a huge win for little cost and almost no well behaved program overwrites return addresses manually (low mispredict rate).

As far as I know, all x86 cores emit 2 uops for a push or a pop (load/store + sp adjust). I guess it can save you some frontend bandwidth but that's about it.

Nice article.

Of course if you get some of the benefits of run time specialization, you also get some of the downside, i.e., overspecialization.

The heuristics to know when to stop specializing is a really hard part of those kind of systems.

You could have a program that does a single write() on 100k different files. You could have thousands of processes each writing to a single file and bloating the I$ with useless copies of write.

If I was more optimist, I'd say that this would be a good opportunity to open up silicon design by requiring some amount of transparency that is usually expected of publicly funded research.

However I'm guessing that this initiative has probably close to no leverage on the EDA vendors & the fabs, so at best we'd get the RTL source.

TBH I'd be happy with a fully open 130nm design kit that is actually supported by a fab.

Nice. I'm sure you could do a lot of cool things once you have this working well by playing around with the "butterfly effect".

For example introduce a single X us jitter in the RTC interrupt and see how long it takes or which part of the machine state diverges from the baseline run.

There is a reason why basically every attempt to make this kind of language fast has to support some form of on-stack replacement.

For example, it's hard to optimize even local variable dataflow in python since it's part of the API : you can inspect the local frame of your caller, so you have a problem as soon as your function contains a single call. And no you can't know statically what is the call target since it can be replaced dynamically.

So either you perform the optimization anyway and then have to try to support reconstructing values correctly to support introspection, or you just do that generically with OSR and use the simple introspection on the interpeter.

Either way, there are not a lot of "simple optimization" when the language is so dynamic.

Of course ?

Yes it often makes sense to produce inefficient code for many business reasons (development speed, users don't care, easier platform to deploy to, etc).

However what Slack does would be nowhere near taxing for a modern computer if it were to be done remotely efficiently.

The "core" of the trick is nice : amortizing interpreter dispatch over many items. (ignoring the column layout/SIMD stuff which basically helps in any case)

Essentially it's turning :

  LOAD
    DISPATCH
    OP1
    DISPATCH
    OP2
    ... (once per operation in the expression)
  STORE
  ... (once per row)
into
  DISPATCH
    LOAD
    OP1
    STORE
    LOAD
    OP1
    STORE
    ... (once per row)
  DISPATCH
  ... (once per operation in the expression)
The nice trade-off here is that you don't require code generation to do that, but it's still not optimal.

If you can generate code it's even better to fuse the operations, to get something like :

  LOAD
    OP1
    OP2
    ...
  STORE
  LOAD
  ...
It helps because even though you can tune your batch size to get mostly cached loads and stores, it's still not free.

For example on Haswell you can only issue 1 store per cycle, so if OP is a single add you're leaving up to 3/4 of your theoretical ALU throughput on the table.

What's the point of using this for native applications ?

I mean yeah, it might be a little easier to distribute but it's kind of absurd to agree to throw away 20% performance for a bit of convenience and then sched tears on how the latest intel chips only delivered 5% improvement over the previous ones.

Game Loop 7 years ago

Extrapolating up to one physics frame is still not a good solution, it will be noticeable.

For example a falling object will clip in the ground for tens of ms of displacement which is definitely visually significant.

A better solution is to either only interpolate and live with the additional frames of input latency (fine for a lot of games) or resimulate the next physics frame with the new player input, and live with the additional CPU overhead.

Of course that's a choice for everyone to make, but I feel like in general the benefit of fitting in the platform is overrated.

After all the whole webapp-everything somewhat shows that people don't care that much, they are happy to use GMail for example. Another family of examples would be the big software packages like the Adobe or Autodesk stuff, they tend to have a fairly custom look&feel.

IMO it's probably sufficient for the thing to look "modern" (ie up to date wrt to UI fashion trends). Then you get the huge upside of easier maintenance and less bugs.

You should probably use mipmaps to avoid aliasing when rendering from far away.

I guess you'll have to compute the LOD by hand since neighbor fragments are not necessarily performing the same texture fetches.

Since this is intented to be educational I find it a bit unfortunate to implement function calls using the host (python) stack since it's an important part of an interpreter.

It means that you can't really add things like stack traces, coroutines, exceptions, single stepping, etc and understand how they work under the hood.

I wonder how they are going to pull that off without security implications.

If you've played with compute shaders (or any of the modern "general purpose" shader stuff, ie arbitrary loads & stores etc) you probably know that it's quite easy to crash drivers. Of course you do so generally by provoking some form of UB (although not always, their runtime/compilers are far from bug-free).

But WebGL can't have that, so I don't see how they could pull that off without adding a ton of runtime bound checks to shaders, like they do for index buffers right now but on the GPU side this time.

Not only would that be bad for performance, but I still would never trust this whole stack to run arbitrary code.

It's not free though. If you can actually remove most of the power/area hungry ooo scheduling logic for only 10% less performance on real world workload, you can pack a lot more cores or a bigger GPU on the same chip without blowing up TDP or die size.

Sure, some use case will always require the highest single thread perf at all cost but that's less and less true these days.

It's a cute idea to cobble together a meta-tracing JIT for sqlite's query interpreter using source extraction and gcc.

I think it's basically a local minimum for implementation complexity vs performance though. To push that further you probably need to modify the original interpreter or encode more (any) knowledge about the semantics in the JIT, at which point you lose the main advantage of maintaining only one codebase.

It's disappointing to have this whole business go inside the driver / dx runtime.

I'm sure the vast majority of it could be done from user code (CPU code & compute shaders) and it would be much more profitable for everyone if they'd expose the couple driver hooks they needed to achieve peek performance.

As it is now, it's super opaque and bundled (do you even know what kind of acceleration structure you're using ?) and seems to be going the exact opposite way that GPU programming has been going (more general & programmable).

Haven't tried it yet, but AMD has gone the open-source library way on this. I sure hope it works out well for them.

(also this seems like a nightmare for the wine people)

Beyond the technical aspect, the pedagogical side of this is huge.

An argument often made in favor of Rust's static analysis restrictions is that "worst case scenario, you just drop down to unsafe and you're back to C semantics".

However, this only holds true as long as the rules to uphold in unsafe code are easy to explain. Strict type aliasing in C is already considered as somewhat of a disaster by many, and is way simpler.

I hope they get this right.

Half a gig of machine code...

In the paper they mention trying to limit the code size to 10% of that and getting ~60% of the baseline perf.

However, they got this number by simply dropping to the interpreter for the rest of the code. I'm curious what kind of figures they'd get if they got to this 10% goal by being more aggressive in the type guard widening pass.