Skip to content

Commit

Permalink
Clarify that linear memory sizes are limited by available resources.
Browse files Browse the repository at this point in the history
  • Loading branch information
sunfishcode committed Jul 6, 2015
1 parent 9c66811 commit 57bdfbe
Showing 1 changed file with 3 additions and 2 deletions.
5 changes: 3 additions & 2 deletions AstSemantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ address for an access is out-of-bounds, the effective address will always also
be out-of-bounds. This is intended to simplify folding of offsets into complex
address modes in hardware, and to simplify bounds checking optimizations.

In the MVP, address operands and offset attributes have type `int32`, and heap
sizes are limited to 4 GiB. In the future, to support
In the MVP, address operands and offset attributes have type `int32`, and linear
memory sizes are limited to 4 GiB (of course, actual sizes are further limited
by [available resources](Nondeterminism.md)). In the future, to support
[>4GiB linear memory](FutureFeatures.md#heaps-bigger-than-4gib), there will be

This comment has been minimized.

Copy link
@titzer

titzer Jul 7, 2015

I'm not sure I agree with a "mode" that changes the types of these bytecodes, and the v8-native-prototype just has different bytecodes for heap accesses that take a 64-bit index. We should probably have an issue to discuss that, but until then we might want to avoid committing to one choice.

This comment has been minimized.

Copy link
@lukewagner

lukewagner Jul 7, 2015

Member

If there are separate opcodes, wouldn't that imply that you could use int32-indexed loads even with a 64-bit heap? While this certainly could work, I was thinking it might complicate internal heap optimizations just by adding another case to consider (in particular, concerning wraparound of int32). That's the nice thing about having the signature of these ops be depending on the heap size declaration.

This comment has been minimized.

Copy link
@titzer

titzer via email Jul 7, 2015

This comment has been minimized.

Copy link
@lukewagner

lukewagner Jul 8, 2015

Member

I'm thinking about out-of-bounds test hoisting which wants to move the constant addition after the test and into the load.

This comment has been minimized.

Copy link
@titzer

titzer via email Jul 8, 2015

This comment has been minimized.

Copy link
@lukewagner

lukewagner Jul 9, 2015

Member

More specifically, I've been thinking about a specific scheme for bounds-checking hoisting with load_mem_with_offset. Let's say you're on 32-bit so you compile:

load_mem_with_offset_i32(base, 4)
load_mem_with_offset_i32(base, 8)

into

check_i32(base)
load_i32(HEAP+base+4)
load_i32(HEAP+base+8)

Where base is an int32 and check_i32 is an internal op that ensures base is in bounds. For this to be valid, a single guard page needs to be placed after the heap (so that if base is in-bounds but base+4 is out-of-bounds, the code will safely fault). However, if the heap length is > UINT32_MAX (accessible via int64 loads), I can't place a guard page since since there is valid memory. This is fixable in various ways, but it adds complexity and chance for bugs. I expect this isn't the only heap optimization strategy with corner cases like this.

This comment has been minimized.

Copy link
@jfbastien

jfbastien Jul 9, 2015

Member

FWIW, NaCl's x86-64 sandbox allocated 84GiB of virtual address space for this. NaCl lives in its own process though, this is unlikely to be a good idea when the process' address space is shared!

This comment has been minimized.

Copy link
@titzer

titzer via email Jul 9, 2015

This comment has been minimized.

Copy link
@jfbastien

jfbastien Jul 9, 2015

Member

IIUC the check is security-critical, so adding it to the IR doesn't mean that the compiler has less work to do. Unless it gives us a useful hint? I'm not sure I see it at the moment.

This comment has been minimized.

Copy link
@sunfishcode

sunfishcode Jul 9, 2015

Author Member

Meaningfully guarding loop array accesses outside of their loops would require checking ranges, and to be interesting it'd have to support ranges with non-immediate sizes. Is there a way we can design this so that the implementation can trust the check without having to prove safety from first principles anyway?

This comment has been minimized.

Copy link
@titzer

titzer via email Jul 9, 2015

This comment has been minimized.

Copy link
@sunfishcode

sunfishcode Jul 9, 2015

Author Member

Interesting. Proving that store(ptr+i) is covered by check(ptr, 4000) still requires induction variable analysis, so these hints wouldn't be a major simplification, but they would work in cases where the implementation can't automatically hoist checks due to side effects inside the loop.

On the other hand, such hints will add some overhead for implementations that don't otherwise have a bounds checking cost, and implementations that don't do any induction variable analysis.

I'd be inclined to categorize this as a FutureFeature. Do you agree?

a mode in which they have type `int64`.

Expand Down

4 comments on commit 57bdfbe

@lukewagner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of explicit check operations keeps coming up; I guess what we need to see before including this is a demonstration of a significant category of optimizations where the engine wouldn't have been able to achieve the same optimization by the normal bounds-check optimizations, but with the check inserted, the engine is able to prove safety. I can imagine cases, I'd just like to see it in practice. Because there seems to be a very real risk that overzealous insertion of checks could make things slower (viz., if they aren't found to be redundant with the internal checks inside loads/stores (think baseline compiler)).

@jfbastien
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is interesting. @titzer could you file this as a separate bug? We could explore it, and I'd be OK for it to be in MVP if we can show it's useful, or leave for FutureFeatures if we don't get to it soon enough.

@bmeurer
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukewagner The compiler should be able to hoist bounds checks for multiple loads/stores with the same base using the guard page(s) trick without any need for traps/signal handlers, if the semantics for out-of-bounds memory access are: oob loads return an undefined value, and oob stores put values into an undefined location. In that case

load_i32(base+4)
load_i32(base+8)
store_i32(base+N, value)

could be turned into something like

tmp_base = clamp(base, limit)
load_i32(tmp_base+4)
load_i32(tmp_base+8)
store_i32(tmp_base+N, value)

given N+3 bytes of guard memory (the VM is free to pick appropriate sizes, and could even adopt dynamically to some degree). The advantage of this approach is that it should also work well with 64-bit addressing in the future.

@lukewagner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bmeurer Given the shared goal of reducing sources of nondeterminism, we would really like the semantics for OOB to be "always trap". This is especially important for OOB since it's such a common bug and we expect a plethora of impl strategies across hardware and browsers so you run a real risk of apps becoming dependent on the impl details of one strategy (certainly we see this in native C++ apps).

Please sign in to comment.