Hacker Newsnew | past | comments | ask | show | jobs | submit | comex's commentslogin

Amazon does not need to pay Google for this. There is no world where Google puts an organic result about the rainforest in the top spot, because it's not what most users are looking for.

At most there might be a world where Google puts someone else's ad above the organic results.


Well, we also know Google isn't trying to help the user leave Google's site as quickly as possible, because they get more ad money when the user clicks on a few pages or does a few searches before finding what they want.

I tried asking for a shot from a live-action remake of My Neighbor Totoro. This is a task I’ve been curious about for a while. Like Sonic, Totoro is the kind of stylized cartoon character that can’t be rendered photorealistically without a great deal of subjective interpretation, which (like in Sonic’s case) is famously easy to get wrong even for humans. Unlike Sonic, Totoro hasn’t had an actual live-action remake, so the model would have to come up with a design itself. I was wondering what it might produce – something good? something horrifying? Unfortunately, neither; it just produced a digital-art style image, despite being asked for a photorealistic one, and kept doing so even when I copied some of the keyword-stuffing from the post. At least it tried. I can’t test this with ChatGPT because it trips the copyright filter.

And you will find similar reasoning in the Citizens United decision with respect to corporations:

> If the First Amendment has any force, it prohibits Congress from fining or jailing citizens, or associations of citizens, for simply engaging in political speech. If the antidistortion rationale were to be accepted, however, it would permit Government to ban political speech simply because the speaker is an association that has taken on the corporate form.


To be clear, that has nothing to do with exceptions, garbage collection, or error ABIs. But I guess you're saying it's a case of tacking on language features to languages that don't have them, since the main desired MS extension is one that sort of resembles class inheritance.

You're completely right, I was wrong - thank you for the correction.

Adding ref to the SEH part of Microsoft C extensions https://learn.microsoft.com/en-us/cpp/cpp/structured-excepti...


I don't think that's quite right. For DMA you would normally use an empty asm block, which is what's typically referred to as a "compiler barrier" and does tell the compiler to discard everything it knows about the contents of a some memory. But std::launder doesn't have the same effect. It only affects type-based optimizations, mainly aliasing, plus the assumption that an object's const fields and vtable can't change.

For example, in this test case:

https://gcc.godbolt.org/z/j3Ko7rf7z

GCC generates a store followed by a load from the same location, because of the asm block (compiler barrier) in between. But if you change `if (1)` to `if (0)`, making it use `std::launder` instead of an asm block, GCC doesn't generate a load. GCC still assumes that the value read back from the pointer must be 42, despite the use of `std::launder`.


This doesn't seem quite right. The asm block case is equivalent to adding a volatile qualifier to the pointer. If you add this qualifier then `std::launder` produces the same codegen.

I think the subtle semantic distinction is that `volatile` is a current property of the type whereas `std::launder` only indicates that it was a former property not visible in the current scope. Within the scope of that trivial function in which the pointer is not volatile, the behavior of `std::launder` is what I'd expect. The practical effect is to limit value propagation of types marked `const` in that memory. Or at least this is my understanding.

DMA memory (and a type residing therein) is often only operationally volatile within narrow, controlled windows of time. The rest of the time you really don't want that volatile qualifier to follow those types around the code.


Apple’s PCC has an explicit design goal to mitigate your point 1, by designing the protocol such that the load balancer has to decide which physical server to route a request to without knowing which user made the request. If you compromise a single physical server, you will get a random sample of requests, but you can’t target any particular user, not even if you also compromise the load balancer. At least that’s the theory; see [1] under the heading “non-targetability”. I have no idea whether OpenPCC replicates this, but I have to imagine they would. The main issue is that you need large scale in order for the “random sample of requests” limitation to actually protect anyone.

[1]: https://security.apple.com/blog/private-cloud-compute/


There are many things one can do to mitigate the (weaker) point 1, including simply not supporting any kind of migration at all. I only bothered to go there to demonstrate that the ability to live migrate is a liability here, not a benefit.

> targeting users should require a wide attack that’s likely to be detected

Regardless, Apple's attacker here doesn't sound like Apple: the "wide attack that's likely to be detected" is going to be detected by them. We even seemingly have to trust them that this magic hardware has the properties they claim it does.

This is way worse than most of these schemes, as if I run one of these on Intel hardware, you inherently are working with multiple parties (me and Intel).

That we trust Apple to not be lying about the entire scheme so they can see the data they are claiming not to be able to see is thereby doing the heavy lifting.


JS shouldn't have a direct equivalent because JS async functions are eager. Once you call an async function, it will keep running even if the caller doesn't await it, or stops awaiting it. So in the scenario described, the function next in line for the lock would always have a chance to acquire and release it. The problem in Rust is that async functions are lazy and only run while they're being polled/awaited (unless wrapped in tasks). A function that's next in line for the lock might never acquire it if it's not being polled, blocking progress for other functions that are being polled.


It's worth noting that this is not async/await in the sense of essentially every other language that uses those terms.

In other languages, when the compiler sees an async function, it compiles it into a state machine or 'coroutine', where the function can suspend itself at designated points marked with `await`, and be resumed later.

In Zig, the compiler used to support coroutines but this was removed. In the new design, `async` and `await` are just functions. In the threaded implementation used in the demo, `await` just blocks the thread until the operation is done.

To be fair, the bottom of the post explains that there are two other Io implementations being planned.

One of them is "stackless coroutines", which would be similar to traditional async/await. However, from the discussion so far this seems a bit like vaporware. As discussed in [1], andrewrk explicitly rejected the idea of just (re-)adding normal async/await keywords, and instead wants a different design, as tracked in issue 23446. But in issue 23446 the seems to be zero agreement on how the feature would work, how it would improve on traditional async/await, or how it would avoid function coloring.

The other implementation being planned is "stackful coroutines". From what I can tell, this has more of a plan and is more promising, but there are significant unknowns.

The basis of the design is similar to green threads or fibers. Low-level code generation would be identical to normal synchronous code, with no state machine transform. Instead, a library would implement suspension by swapping out the native register state and stack, just like the OS kernel does when switching between OS threads. By itself, this has been implemented many times before, in libraries for C and in the runtimes of languages like Go. But it has the key limitation that you don't know how much stack to allocate. If you allocate too much stack in advance, you end up being not much cheaper than OS threads; but if you allocate too little stack, you can easily hit stack overflow. Go addresses this by allocating chunks of stack on demand, but that still imposes a cost and a dependency on dynamic allocation.

andrewrk proposes [2] to instead have the compiler calculate the maximum amount of native stack needed by a function and all its callees. In this case, the stack could be sized exactly to fit. In some sense this is similar to async in Rust, where the compiler calculates the size of async function objects based on the amount of state the function and its callees need to store during suspension. But the Zig approach would apply to all function calls rather than treating async as a separate case. As a result, the benefits would extend beyond memory usage in async code. The compiler would statically guarantee the absence of stack overflow, which benefits reliability in all code that uses the feature. This would be particularly useful in embedded where, typically, reliability demands are high and memory available is low. Right now in embedded, people sometimes use a GCC feature ("-fstack-usage") that does a similar calculation, but it's messy enough that people often don't bother. So it would be cool to have this as a first-class feature in Zig.

But.

There's a reason that stack usage calculators are uncommon. If you want to statically bound stack usage:

First, you have to ban recursion, or else add some kind of language mechanism for tracking how many times a function can possibly recurse. Banning recursion is common in embedded code but would be rather annoying for most codebases. Tracking recursion is definitely possible, as shown by proof languages like Agda or Coq that make you prove termination of recursive functions - but those languages have a lot of tools that 'normal' languages don't, so it's unclear how ergonomic such a feature could be in Zig. The issue [2] doesn't have much concrete discussion on how it would work.

Second, you have to ban dynamic calls (i.e. calls to function pointers), because if you don't know what function you're calling, you don't know how much stack it will use. This has been the subject of more concrete design in [3] which proposes a "restricted" function pointer type that can only refer to a statically known set of functions. However, it remains to be seen how ergonomic and composable this will be.

Zooming back out:

Personally, I'm glad that Zig is willing to experiment with these things rather than just copying the same async/await feature as every other language. There is real untapped potential out there. On the other hand, it seems a little early to claim victory, when all that works today is a thread-based I/O library that happens to have "async" and "await" in its function names.

Heck, it seems early to finalize an I/O library design if you don't even know how the fancy high-performance implementations will work. Though to be fair, many applications will get away just fine with threaded I/O, and it's nice to see a modern I/O library design that embraces that as a serious option.

[1] https://github.com/ziglang/zig/issues/6025#issuecomment-3072...

[2] https://github.com/ziglang/zig/issues/157

[3] https://github.com/ziglang/zig/issues/23367


> But it has the key limitation that you don't know how much stack to allocate. If you allocate too much stack in advance, you end up being not much cheaper than OS threads; but if you allocate too little stack, you can easily hit stack overflow.

With a 64-bit address space you can reserve large contiguous chunks (e.g. 2MB), while only allocating the minimum necessary for the optimistic case. The real problem isn't memory usage, per se, it's all the VMA manipulation and noise. In particular, setting up guard pages requires a separate VMA region for each guard (usually two per stack, above and below). Linux recently got a new madvise feature, MADV_GUARD_INSTALL/MADV_GUARD_REMOVE, which lets you add cheap guard pages without installing a distinct, separate guard page. (https://lwn.net/Articles/1011366/) This is the type of feature that could be used to improve the overhead of stackful coroutines/fibers. In theory fibers should be able to outperform explicit async/await code, because in the non-recursive, non-dynamic call case a fiber's stack can be stack-allocated by the caller, thus being no more costly than allocating a similar async/await call frame, yet in the recursive and dynamic call cases you can avoid dynamic frame bouncing, which in the majority of situations is unnecessary--the poor performance of dynamic frame allocation/deallocation in deep dynamic call chains is the reason Go switched from segmented stacks to moveable stacks.

Another major cost of fibers/thread is context switching--most existing solutions save and restore all registers. But for coroutines (stackless or stackful), there's no need to do this. See, e.g., https://photonlibos.github.io/blog/stackful-coroutine-made-f..., which tweaked clang to erase this cost and bring it line with normal function calls.

> Go addresses this by allocating chunks of stack on demand, but that still imposes a cost and a dependency on dynamic allocation.

The dynamic allocation problem exists the same whether using stackless coroutines, stackful coroutines, etc. Fundamentally, async/await in Rust is just creating a linked-list of call frames, like some mainframes do/did. How many Rust users manually OOM check Boxed dyn coroutine creation? Handling dynamic stack growth is technically a problem even in C, it's just that without exceptions and thread-scoped signal handlers there's no easy way to handle overflow so few people bother. (Heck, few even bother on Windows where it's much easier with SEH.) But these are fixable problems, it just requires coordination up-and-down the OS stack and across toolchains. The inability to coordinate these solutions does not turn ugly compromises (async/await) into cool features.

> First, you have to ban recursion, or else add some kind of language mechanism for tracking how many times a function can possibly recurse. [snip] > > Second, you have to ban dynamic calls (i.e. calls to function pointers)

Both of which are the case for async/await in Rust; you have to explicitly Box any async call that Rust can't statically size. We might frame this as being transparent and consistent, except it's not actually consistent because we don't treat "ordinary", non-async calls this way, which still use the traditional contiguous stack that on overflow kills the program. Nobody wants that much consistency (too much of a "good" thing?) because treating each and every call as async, with all the explicit management that would entail with the current semantics would be an indefensible nightmare for the vast majority of use cases.


> If you allocate too much stack in advance, you end up being not much cheaper than OS threads;

Maybe. a smart event loop could track how many frames are in flight at any given time and reuse preallocated frames when their frames dispatch out.


Regarding recursive functions, would it really be that annoying? We kinda take the ability to recurse for granted, yet it is rarely used in practice, and often when it happens it's unintentional and a source of bugs (due to unforeseen re-entrancy). Intuitively it feels that if `recursive` was a required modifier when declaring intentionally recursive functions, like in Fortran, it wouldn't actually be used all that much. Most functions don't need to call via function pointers, either.

Being explicit about it might also allow for some interesting compiler optimizations across shared library boundaries...


Fortran is recursive by default.


> tracking how many times a function can possibly recurse.

> Tracking recursion is definitely possible, as shown by proof languages like Agda or Coq that make you prove termination of recursive functions

Proof languages don't really track how many times a function can possibly recurse, they only care that it will eventually terminate. The amount of recursive steps can even easily depend on the inputs, making it unknown at the moment a function is defined.


Right. They use rules like "a function body must destructure its input and cannot use a constructor" which implies that, since input can't be infinite, the function will terminate. That doesn't mean that the recursion depth is known before running.


The actual rule is more refined than that and doesn't prevent you from using constructors in the body or recursing without deconstructing any input, it just needs to prove the new inputs are "smaller" according to a fixed well-ordered relation. This is most often related to the structure of the input but is not required. For example I can have f(5) recurse into f(6) if that's "smaller" according to my custom relation, but no relation will allow me to continue increasing the argument forever.


This blog post had a better explanation than the one linked from the story IMO:

https://kristoff.it/blog/zig-new-async-io/


So, yes, but you won’t say what the base model is? :)


It seems like a sort of sonnet model as a lot of people are reporting it like to spam documentation on Twitter like sonnet 4.5


Anonymization is supposed to be irreversible. This scheme is reversible by whoever has the key. I don't really get the point of it.


Any stable hash can't truly anonymize IP addresses because there is a finite amount of outputs easily computable via ordinary machines.


Which is why we pepper and salt our hashes.

If you store the blood type of a patient hashed, the problem is that there are only so many blood types. So the same blood type will have the same hash value and attackers could (1) just infer statistically which are which, (2) crack one and get the rest and (3) group users even without cracking the hash.

That means we need to ensure the input values are getting more complex by prefixing them with secrets from elsewhere.

If you have one secret (e.g. stored in an environment variable) that would be the pepper. Adding pepper just makes cracking harder, but since it is the same for each value, it is not enough. But since it is not stored next to the input value it makes attacks harder.

A salt would be a per value secret that is stored for each blood type and prepended on hash.

The two in combination make it much harder to get from the hashed value to the input value without having both salt and pepper.


That’s encryption at rest, but not anonymization, unless you throw away the salt and pepper, at which point the record becomes meaningless since it cannot serve for future comparisons.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: