diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 4fb6be1d5f..0476c9dd38 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -1093,11 +1093,11 @@ class BaseCompiler } void loadConstI32(Register r, Stk& src) { -#if defined(JS_CODEGEN_LOONGARCH64) - masm.move32(Imm32(src.i32val()), r); -#else - masm.mov(ImmWord((uint32_t)src.i32val() & 0xFFFFFFFFU), r); -#endif + masm.mov(ImmWord(uint32_t(src.i32val())), r); + } + + void loadConstI32(Register r, int32_t v) { + masm.mov(ImmWord(uint32_t(v)), r); } void loadMemI32(Register r, Stk& src) { @@ -3501,21 +3501,23 @@ ScratchI32 tmp(*this); // ptr and dest may be the same iff dest is I32. // This may destroy ptr even if ptr and dest are not the same. - [[nodiscard]] bool load(MemoryAccessDesc& access, RegI32 ptr, AnyReg dest, - RegI32 tmp1, RegI32 tmp2, RegI32 tmp3) + [[nodiscard]] bool load(MemoryAccessDesc& access, RegI32 ptr, bool omitBoundsCheck, AnyReg dest, + RegI32 tmp1, RegI32 tmp2, RegI32 tmp3) { checkOffset(&access, ptr); OutOfLineCode* ool = nullptr; #ifndef WASM_HUGE_MEMORY - if (access.isPlainAsmJS()) { - ool = new (alloc_) AsmJSLoadOOB(access.type(), dest.any()); - if (!addOutOfLineCode(ool)) - return false; + if (!omitBoundsCheck) { + if (access.isPlainAsmJS()) { + ool = new (alloc_) AsmJSLoadOOB(access.type(), dest.any()); + if (!addOutOfLineCode(ool)) + return false; - masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr.reg, ool->entry()); - } else { - masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr.reg, trap(Trap::OutOfBounds)); + masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, ool->entry()); + } else { + masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds)); + } } #endif @@ -3642,17 +3644,19 @@ ScratchI32 tmp(*this); // ptr and src must not be the same register. // This may destroy ptr but will not destroy src. - [[nodiscard]] bool store(MemoryAccessDesc access, RegI32 ptr, AnyReg src, + [[nodiscard]] bool store(MemoryAccessDesc access, RegI32 ptr, bool omitBoundsCheck, AnyReg src, RegI32 tmp) { checkOffset(&access, ptr); Label rejoin; #ifndef WASM_HUGE_MEMORY - if (access.isPlainAsmJS()) - masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr.reg, &rejoin); - else - masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr.reg, trap(Trap::OutOfBounds)); + if (!omitBoundsCheck) { + if (access.isPlainAsmJS()) + masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, &rejoin); + else + masm.wasmBoundsCheck(Assembler::AboveOrEqual, ptr, trap(Trap::OutOfBounds)); + } #endif // Emit the store @@ -3816,6 +3820,36 @@ ScratchI32 tmp(*this); *r0 = popF64(); } + RegI32 popMemoryAccess(MemoryAccessDesc* access, bool* omitBoundsCheck); + + template void freeOrPushI32(RegI32 r) { + if (freeIt) + freeI32(r); + else + pushI32(r); + } + + template void freeOrPushI64(RegI64 r) { + if (freeIt) + freeI64(r); + else + pushI64(r); + } + + template void freeOrPushF32(RegF32 r) { + if (freeIt) + freeF32(r); + else + pushF32(r); + } + + template void freeOrPushF64(RegF64 r) { + if (freeIt) + freeF64(r); + else + pushF64(r); + } + //////////////////////////////////////////////////////////// // // Sundry helpers. @@ -6499,6 +6533,49 @@ BaseCompiler::emitTeeGlobal() return true; } +// See EffectiveAddressAnalysis::analyzeAsmJSHeapAccess() for comparable Ion code. +// +// TODO / OPTIMIZE (bug 1329576): There are opportunities to generate better +// code by not moving a constant address with a zero offset into a register. + +BaseCompiler::RegI32 +BaseCompiler::popMemoryAccess(MemoryAccessDesc* access, bool* omitBoundsCheck) +{ + // Caller must initialize. + MOZ_ASSERT(!*omitBoundsCheck); + + if (isCompilingAsmJS()) + return popI32(); + + int32_t addrTmp; + if (popConstI32(addrTmp)) { + uint32_t addr = addrTmp; + + // We can eliminate the bounds check if the sum of the constant address + // and the known offset are below the sum of the minimum memory length + // and the offset guard length. + + uint64_t ea = uint64_t(addr) + uint64_t(access->offset()); + uint64_t limit = uint64_t(env_.minMemoryLength) + uint64_t(wasm::OffsetGuardLimit); + + *omitBoundsCheck = ea < limit; + + // Fold the offset into the pointer if we can, as this is always + // beneficial. + + if (ea <= UINT32_MAX) { + addr = uint32_t(ea); + access->clearOffset(); + } + + RegI32 r = needI32(); + loadConstI32(r, int32_t(addr)); + return r; + } + + return popI32(); +} + bool BaseCompiler::emitLoad(ValType type, Scalar::Type viewType) { @@ -6509,8 +6586,7 @@ BaseCompiler::emitLoad(ValType type, Scalar::Type viewType) if (deadCode_) return true; - // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant - // accesses below the minimum heap length. + bool omitBoundsCheck = false; MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS()); @@ -6521,13 +6597,13 @@ BaseCompiler::emitLoad(ValType type, Scalar::Type viewType) switch (type) { case ValType::I32: { - RegI32 rp = popI32(); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); #ifdef JS_CODEGEN_ARM RegI32 rv = IsUnaligned(access) ? needI32() : rp; #else RegI32 rv = rp; #endif - if (!load(access, rp, AnyReg(rv), tmp1, tmp2, tmp3)) + if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2, tmp3)) return false; pushI32(rv); if (rp != rv) @@ -6540,30 +6616,30 @@ BaseCompiler::emitLoad(ValType type, Scalar::Type viewType) #ifdef JS_CODEGEN_X86 rv = abiReturnRegI64; needI64(rv); - rp = popI32(); + rp = popMemoryAccess(&access, &omitBoundsCheck); #else - rp = popI32(); + rp = popMemoryAccess(&access, &omitBoundsCheck); rv = needI64(); #endif - if (!load(access, rp, AnyReg(rv), tmp1, tmp2, tmp3)) + if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2, tmp3)) return false; pushI64(rv); freeI32(rp); break; } case ValType::F32: { - RegI32 rp = popI32(); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); RegF32 rv = needF32(); - if (!load(access, rp, AnyReg(rv), tmp1, tmp2, tmp3)) + if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2, tmp3)) return false; pushF32(rv); freeI32(rp); break; } case ValType::F64: { - RegI32 rp = popI32(); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); RegF64 rv = needF64(); - if (!load(access, rp, AnyReg(rv), tmp1, tmp2, tmp3)) + if (!load(access, rp, omitBoundsCheck, AnyReg(rv), tmp1, tmp2, tmp3)) return false; pushF64(rv); freeI32(rp); @@ -6595,8 +6671,7 @@ BaseCompiler::emitStore(ValType resultType, Scalar::Type viewType) if (deadCode_) return true; - // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant - // accesses below the minimum heap length. + bool omitBoundsCheck = false; MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS()); @@ -6605,9 +6680,9 @@ BaseCompiler::emitStore(ValType resultType, Scalar::Type viewType) switch (resultType) { case ValType::I32: { - RegI32 rp, rv; - pop2xI32(&rp, &rv); - if (!store(access, rp, AnyReg(rv), tmp1)) + RegI32 rv = popI32(); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1)) return false; freeI32(rp); freeI32(rv); @@ -6615,8 +6690,8 @@ BaseCompiler::emitStore(ValType resultType, Scalar::Type viewType) } case ValType::I64: { RegI64 rv = popI64(); - RegI32 rp = popI32(); - if (!store(access, rp, AnyReg(rv), tmp1)) + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1)) return false; freeI32(rp); freeI64(rv); @@ -6624,8 +6699,8 @@ BaseCompiler::emitStore(ValType resultType, Scalar::Type viewType) } case ValType::F32: { RegF32 rv = popF32(); - RegI32 rp = popI32(); - if (!store(access, rp, AnyReg(rv), tmp1)) + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1)) return false; freeI32(rp); freeF32(rv); @@ -6633,8 +6708,8 @@ BaseCompiler::emitStore(ValType resultType, Scalar::Type viewType) } case ValType::F64: { RegF64 rv = popF64(); - RegI32 rp = popI32(); - if (!store(access, rp, AnyReg(rv), tmp1)) + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rv), tmp1)) return false; freeI32(rp); freeF64(rv); @@ -6955,9 +7030,7 @@ BaseCompiler::emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType if (deadCode_) return true; - // TODO / OPTIMIZE (bug 1316831): Disable bounds checking on constant - // accesses below the minimum heap length. - + bool omitBoundsCheck = false; MemoryAccessDesc access(viewType, addr.align, addr.offset, trapIfNotAsmJS()); size_t temps = storeTemps(access); @@ -6966,9 +7039,9 @@ BaseCompiler::emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType if (resultType == ValType::F32 && viewType == Scalar::Float64) { RegF32 rv = popF32(); RegF64 rw = needF64(); - masm.convertFloat32ToDouble(rv.reg, rw.reg); - RegI32 rp = popI32(); - if (!store(access, rp, AnyReg(rw), tmp1)) + masm.convertFloat32ToDouble(rv, rw); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rw), tmp1)) return false; pushF32(rv); freeI32(rp); @@ -6977,9 +7050,9 @@ BaseCompiler::emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType else if (resultType == ValType::F64 && viewType == Scalar::Float32) { RegF64 rv = popF64(); RegF32 rw = needF32(); - masm.convertDoubleToFloat32(rv.reg, rw.reg); - RegI32 rp = popI32(); - if (!store(access, rp, AnyReg(rw), tmp1)) + masm.convertDoubleToFloat32(rv, rw); + RegI32 rp = popMemoryAccess(&access, &omitBoundsCheck); + if (!store(access, rp, omitBoundsCheck, AnyReg(rw), tmp1)) return false; pushF64(rv); freeI32(rp);