@@ -2224,20 +2224,58 @@ fn prepare_addr<FE: FuncEnvironment + ?Sized>(
22242224 cmp:: max ( memarg. offset / offset_guard_size * offset_guard_size, 1 )
22252225 } ;
22262226 debug_assert ! ( adjusted_offset > 0 ) ; // want to bounds check at least 1 byte
2227- let base = builder
2228- . ins ( )
2229- . heap_addr ( environ. pointer_type ( ) , heap, addr, adjusted_offset) ;
2227+ let ( addr, offset) = match u32:: try_from ( adjusted_offset) {
2228+ // If our adjusted offset fits within a u32, then we can place the
2229+ // entire offset into the offset of the `heap_addr` instruction. After
2230+ // the `heap_addr` instruction, though, we need to factor the the offset
2231+ // into the returned address. This is either an immediate if the offset
2232+ // further fits within `i32`, or a manual add instruction otherwise.
2233+ //
2234+ // Note that native instructions take a signed offset hence the switch
2235+ // to i32. Note also the lack of overflow checking in the offset
2236+ // addition, which should be ok since if `heap_addr` passed we're
2237+ // guaranteed that this won't overflow.
2238+ Ok ( offset) => {
2239+ let base = builder
2240+ . ins ( )
2241+ . heap_addr ( environ. pointer_type ( ) , heap, addr, offset) ;
2242+ match i32:: try_from ( memarg. offset ) {
2243+ Ok ( val) => ( base, val) ,
2244+ Err ( _) => {
2245+ let adj = builder. ins ( ) . iadd_imm ( base, i64:: from ( offset) ) ;
2246+ ( adj, 0 )
2247+ }
2248+ }
2249+ }
22302250
2231- // Native load/store instructions take a signed `Offset32` immediate, so adjust the base
2232- // pointer if necessary.
2233- let ( addr, offset) = match i32:: try_from ( memarg. offset ) {
2234- Ok ( val) => ( base, val) ,
2251+ // If the adjusted offset doesn't fit within a u32, then this gets
2252+ // pessimized a fair amount. We can't pass the fixed-sized offset to
2253+ // the `heap_addr` instruction, so we need to fold the offset into the
2254+ // address itself. In doing so though we need to check for overflow
2255+ // because that would mean the address is out-of-bounds.
2256+ //
2257+ // Once we have the effective address, offset already folded in, then
2258+ // `heap_addr` is used to verify that the address is indeed in-bounds.
2259+ // The access size of the `heap_addr` is what we were passed in from
2260+ // above.
2261+ //
2262+ // Note that this is generating what's likely to be at least two
2263+ // branches, one for the overflow and one for the bounds check itself.
2264+ // For now though that should hopefully be ok since 4gb+ offsets are
2265+ // relatively odd/rare.
22352266 Err ( _) => {
2236- // Note the switch from u64 offset to i64 here, but this should be
2237- // ok because we're already guaranteed this won't overflow if we
2238- // reach this point after the `heap_addr` instruction above.
2239- let adj = builder. ins ( ) . iadd_imm ( base, memarg. offset as i64 ) ;
2240- ( adj, 0 )
2267+ let index_type = builder. func . heaps [ heap] . index_type ;
2268+ let offset = builder. ins ( ) . iconst ( index_type, memarg. offset as i64 ) ;
2269+ let ( addr, overflow) = builder. ins ( ) . iadd_ifcout ( addr, offset) ;
2270+ builder. ins ( ) . trapif (
2271+ environ. unsigned_add_overflow_condition ( ) ,
2272+ overflow,
2273+ ir:: TrapCode :: HeapOutOfBounds ,
2274+ ) ;
2275+ let base = builder
2276+ . ins ( )
2277+ . heap_addr ( environ. pointer_type ( ) , heap, addr, access_size) ;
2278+ ( base, 0 )
22412279 }
22422280 } ;
22432281
0 commit comments