[wasm][memory64] Fix typing of memory.grow

If memory64 is enabled, memory.grow should consume and return an i64
instead of i32.
This CL implements this for both TurboFan and Liftoff, and adds
validation and execution tests at different layers.

R=manoskouk@chromium.org

Bug: v8:10949
Change-Id: I0b725dbd0d5767bda4609747c1f4aad163c35304
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2773800
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73542}
This commit is contained in:
Clemens Backes 2021-03-19 11:47:38 +01:00 committed by Commit Bot
parent 3bf2935f6a
commit 81008e1752
9 changed files with 121 additions and 11 deletions

View File

@ -2275,7 +2275,28 @@ Node* WasmGraphBuilder::BuildCcallConvertFloat(Node* input,
Node* WasmGraphBuilder::MemoryGrow(Node* input) {
needs_stack_check_ = true;
return gasm_->CallRuntimeStub(wasm::WasmCode::kWasmMemoryGrow, input);
if (!env_->module->is_memory64) {
// For 32-bit memories, just call the builtin.
return gasm_->CallRuntimeStub(wasm::WasmCode::kWasmMemoryGrow, input);
}
// If the input is not a positive int32, growing will always fail
// (growing negative or requesting >= 256 TB).
Node* old_effect = effect();
Diamond is_32_bit(graph(), mcgraph()->common(),
gasm_->Uint64LessThanOrEqual(input, Int64Constant(kMaxInt)),
BranchHint::kTrue);
is_32_bit.Chain(control());
SetControl(is_32_bit.if_true);
Node* grow_result = gasm_->ChangeInt32ToInt64(gasm_->CallRuntimeStub(
wasm::WasmCode::kWasmMemoryGrow, gasm_->TruncateInt64ToInt32(input)));
Node* diamond_result = is_32_bit.Phi(MachineRepresentation::kWord64,
grow_result, gasm_->Int64Constant(-1));
SetEffectControl(is_32_bit.EffectPhi(effect(), old_effect), is_32_bit.merge);
return diamond_result;
}
Node* WasmGraphBuilder::Throw(uint32_t exception_index,

View File

@ -127,6 +127,7 @@ RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) {
isolate, handle(instance->memory_object(), isolate), delta_pages);
// The WasmMemoryGrow builtin which calls this runtime function expects us to
// always return a Smi.
DCHECK(!isolate->has_pending_exception());
return Smi::FromInt(ret);
}

View File

@ -2987,11 +2987,23 @@ class LiftoffCompiler {
LiftoffRegister input = pinned.set(__ PopToRegister());
__ SpillAllRegisters();
constexpr Register kGpReturnReg = kGpReturnRegisters[0];
static_assert(kLiftoffAssemblerGpCacheRegs & kGpReturnReg.bit(),
"first return register is a cache register (needs more "
"complex code here otherwise)");
LiftoffRegister result = pinned.set(LiftoffRegister(kGpReturnReg));
LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
Label done;
if (env_->module->is_memory64) {
// If the high word is not 0, this will always fail (would grow by
// >=256TB). The int32_t value will be sign-extended below.
__ LoadConstant(result, WasmValue(int32_t{-1}));
if (kNeedI64RegPair) {
__ emit_cond_jump(kUnequal /* neq */, &done, kI32, input.high_gp());
input = input.low();
} else {
LiftoffRegister high_word = __ GetUnusedRegister(kGpReg, pinned);
__ emit_i64_shri(high_word, input, 32);
__ emit_cond_jump(kUnequal /* neq */, &done, kI32, high_word.gp());
}
}
WasmMemoryGrowDescriptor descriptor;
DCHECK_EQ(0, descriptor.GetStackParameterCount());
@ -3009,7 +3021,16 @@ class LiftoffCompiler {
__ Move(result.gp(), kReturnRegister0, kI32);
}
__ PushRegister(kI32, result);
__ bind(&done);
if (env_->module->is_memory64) {
LiftoffRegister result64 = result;
if (kNeedI64RegPair) result64 = __ GetUnusedRegister(kGpRegPair, pinned);
__ emit_type_conversion(kExprI64SConvertI32, result64, result, nullptr);
__ PushRegister(kI64, result64);
} else {
__ PushRegister(kI32, result);
}
}
OwnedVector<DebugSideTable::Entry::Value> GetCurrentDebugSideTableEntries(

View File

@ -3194,8 +3194,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
MemoryIndexImmediate<validate> imm(this, this->pc_ + 1);
// This opcode will not be emitted by the asm translator.
DCHECK_EQ(kWasmOrigin, this->module_->origin);
Value value = Peek(0, 0, kWasmI32);
Value result = CreateValue(kWasmI32);
ValueType mem_type = this->module_->is_memory64 ? kWasmI64 : kWasmI32;
Value value = Peek(0, 0, mem_type);
Value result = CreateValue(mem_type);
CALL_INTERFACE_IF_REACHABLE(MemoryGrow, value, &result);
Drop(value);
Push(result);

View File

@ -98,6 +98,26 @@ WASM_EXEC_TEST(MemorySize) {
CHECK_EQ(kNumPages, r.Call());
}
WASM_EXEC_TEST(MemoryGrow) {
// TODO(clemensb): Implement memory64 in the interpreter.
if (execution_tier == TestExecutionTier::kInterpreter) return;
Memory64Runner<int64_t, int64_t> r(execution_tier);
r.builder().SetMaxMemPages(13);
r.builder().AddMemory(kWasmPageSize);
BUILD(r, WASM_MEMORY_GROW(WASM_LOCAL_GET(0)));
CHECK_EQ(1, r.Call(6));
CHECK_EQ(7, r.Call(1));
CHECK_EQ(-1, r.Call(-1));
CHECK_EQ(-1, r.Call(int64_t{1} << 31));
CHECK_EQ(-1, r.Call(int64_t{1} << 32));
CHECK_EQ(-1, r.Call(int64_t{1} << 33));
CHECK_EQ(-1, r.Call(int64_t{1} << 63));
CHECK_EQ(-1, r.Call(6)); // Above the maximum of 13.
CHECK_EQ(8, r.Call(5)); // Just at the maximum of 13.
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -570,6 +570,11 @@ class WasmRunner : public WasmRunnerBase {
lower_simd) {}
ReturnType Call(ParamTypes... p) {
Isolate* isolate = CcTest::InitIsolateOnce();
// Save the original context, because CEntry (for runtime calls) will
// reset / invalidate it when returning.
SaveContext save_context(isolate);
DCHECK(compiled_);
if (interpret()) return CallInterpreter(p...);

View File

@ -81,3 +81,28 @@ function BasicMemory64Tests(num_pages) {
// let num_pages = 5 * 1024 * 1024 * 1024 / kPageSize;
// BasicMemory64Tests(num_pages);
//})();
(function TestGrow64() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addMemory64(1, 10, false);
builder.addFunction('grow', makeSig([kWasmI64], [kWasmI64]))
.addBody([
kExprLocalGet, 0, // local.get 0
kExprMemoryGrow, 0, // memory.grow 0
])
.exportFunc();
let instance = builder.instantiate();
assertEquals(1n, instance.exports.grow(2n));
assertEquals(3n, instance.exports.grow(1n));
assertEquals(-1n, instance.exports.grow(-1n));
assertEquals(-1n, instance.exports.grow(1n << 31n));
assertEquals(-1n, instance.exports.grow(1n << 32n));
assertEquals(-1n, instance.exports.grow(1n << 33n));
assertEquals(-1n, instance.exports.grow(1n << 63n));
assertEquals(-1n, instance.exports.grow(7n)); // Above the of 10.
assertEquals(4n, instance.exports.grow(6n)); // Just at the maximum of 10.
})();

View File

@ -68,6 +68,11 @@ constexpr size_t kMaxByteSizedLeb128 = 127;
using F = std::pair<ValueType, bool>;
// Used to construct fixed-size signatures: MakeSig().Returns(...).Params(...);
FixedSizeSignature<ValueType> MakeSig() {
return FixedSizeSignature<ValueType>{{}};
}
enum MemoryType { kMemory32, kMemory64 };
// A helper for tests that require a module environment for functions,
@ -5010,6 +5015,19 @@ TEST_P(FunctionBodyDecoderTestOnBothMemoryTypes, MemorySize) {
{WASM_MEMORY_SIZE, kExprI64Eqz, kExprDrop});
}
TEST_P(FunctionBodyDecoderTestOnBothMemoryTypes, MemoryGrow) {
builder.InitializeMemory(GetParam());
// memory.grow is i32->i32 memory32.
Validate(!is_memory64(), sigs.i_i(), {WASM_MEMORY_GROW(WASM_LOCAL_GET(0))});
// memory.grow is i64->i64 memory32.
Validate(is_memory64(), sigs.l_l(), {WASM_MEMORY_GROW(WASM_LOCAL_GET(0))});
// any other combination always fails.
auto sig_l_i = MakeSig().Returns(kWasmI64).Params(kWasmI32);
ExpectFailure(&sig_l_i, {WASM_MEMORY_GROW(WASM_LOCAL_GET(0))});
auto sig_i_l = MakeSig().Returns(kWasmI32).Params(kWasmI64);
ExpectFailure(&sig_i_l, {WASM_MEMORY_GROW(WASM_LOCAL_GET(0))});
}
#undef B1
#undef B2
#undef B3

View File

@ -48,9 +48,7 @@
'proposals/memory64/elem': [FAIL],
'proposals/memory64/float_memory64': [FAIL],
'proposals/memory64/imports': [FAIL],
'proposals/memory64/load64': [FAIL],
'proposals/memory64/memory64': [FAIL],
'proposals/memory64/memory_grow64': [FAIL],
'proposals/memory64/memory_trap64': [FAIL],
}], # ALWAYS