[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:
parent
3bf2935f6a
commit
81008e1752
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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...);
|
||||
|
||||
|
@ -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.
|
||||
})();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user