[wasm-gc] Implement ref.as_non_null, optimize struct instructions.
Implement the instruction ref.as_non_null, as per the wasm gc extension. Changes: - Add the respective wasm opcode, move some asmjs opcodes around. - Add a new type of wasm trap, IllegalCast. - Modify wasm decoding and compilation pipeline. - Add a minimal test. - In wasm-compiler, generalize Unreachable to Trap. - Optimize struct.get and struct.set for non-null types. Bug: v8:7748 Change-Id: If2f794306c7cbfabc06e4f64988132346085d6dd Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2187616 Commit-Queue: Manos Koukoutos <manoskouk@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#67705}
This commit is contained in:
parent
c74010bf47
commit
0a69768a4e
@ -323,6 +323,7 @@ extern enum MessageTemplate {
|
||||
kWasmTrapBrOnExnNullRef,
|
||||
kWasmTrapRethrowNullRef,
|
||||
kWasmTrapNullDereference,
|
||||
kWasmTrapIllegalCast,
|
||||
...
|
||||
}
|
||||
|
||||
|
@ -264,4 +264,8 @@ namespace wasm {
|
||||
builtin ThrowWasmTrapNullDereference(): JSAny {
|
||||
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapNullDereference));
|
||||
}
|
||||
|
||||
builtin ThrowWasmTrapIllegalCast(): JSAny {
|
||||
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapIllegalCast));
|
||||
}
|
||||
}
|
||||
|
@ -1594,7 +1594,8 @@ enum class LoadSensitivity {
|
||||
V(TrapTableOutOfBounds) \
|
||||
V(TrapBrOnExnNullRef) \
|
||||
V(TrapRethrowNullRef) \
|
||||
V(TrapNullDereference)
|
||||
V(TrapNullDereference) \
|
||||
V(TrapIllegalCast)
|
||||
|
||||
enum KeyedAccessLoadMode {
|
||||
STANDARD_LOAD,
|
||||
|
@ -554,6 +554,7 @@ namespace internal {
|
||||
T(WasmTrapBrOnExnNullRef, "br_on_exn on nullref value") \
|
||||
T(WasmTrapRethrowNullRef, "rethrowing nullref value") \
|
||||
T(WasmTrapNullDereference, "dereferencing a null pointer") \
|
||||
T(WasmTrapIllegalCast, "illegal cast") \
|
||||
T(WasmExceptionError, "wasm exception") \
|
||||
/* Asm.js validation related */ \
|
||||
T(AsmJsInvalid, "Invalid asm.js: %") \
|
||||
|
@ -299,6 +299,13 @@ Node* WasmGraphBuilder::RefFunc(uint32_t function_index) {
|
||||
Uint32Constant(function_index), effect(), control()));
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::RefAsNonNull(Node* arg,
|
||||
wasm::WasmCodePosition position) {
|
||||
TrapIfTrue(wasm::kTrapIllegalCast, gasm_->WordEqual(arg, RefNull()),
|
||||
position);
|
||||
return arg;
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::NoContextConstant() {
|
||||
return mcgraph()->IntPtrConstant(0);
|
||||
}
|
||||
@ -1115,8 +1122,9 @@ Node* WasmGraphBuilder::Return(Vector<Node*> vals) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::Unreachable(wasm::WasmCodePosition position) {
|
||||
TrapIfFalse(wasm::TrapReason::kTrapUnreachable, Int32Constant(0), position);
|
||||
Node* WasmGraphBuilder::Trap(wasm::TrapReason reason,
|
||||
wasm::WasmCodePosition position) {
|
||||
TrapIfFalse(reason, Int32Constant(0), position);
|
||||
Return(Vector<Node*>{});
|
||||
return nullptr;
|
||||
}
|
||||
@ -5135,24 +5143,29 @@ Node* WasmGraphBuilder::ArrayNew(uint32_t array_index,
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StructGet(Node* struct_object,
|
||||
const wasm::StructType* type,
|
||||
uint32_t field_index,
|
||||
const wasm::StructType* struct_type,
|
||||
uint32_t field_index, CheckForNull null_check,
|
||||
wasm::WasmCodePosition position) {
|
||||
MachineType machine_type = FieldType(type, field_index);
|
||||
Node* offset = FieldOffset(mcgraph(), type, field_index);
|
||||
TrapIfTrue(wasm::kTrapNullDereference,
|
||||
gasm_->WordEqual(struct_object, RefNull()), position);
|
||||
if (null_check == kWithNullCheck) {
|
||||
TrapIfTrue(wasm::kTrapNullDereference,
|
||||
gasm_->WordEqual(struct_object, RefNull()), position);
|
||||
}
|
||||
MachineType machine_type = FieldType(struct_type, field_index);
|
||||
Node* offset = FieldOffset(mcgraph(), struct_type, field_index);
|
||||
return gasm_->Load(machine_type, struct_object, offset);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StructSet(Node* struct_object,
|
||||
const wasm::StructType* type,
|
||||
const wasm::StructType* struct_type,
|
||||
uint32_t field_index, Node* field_value,
|
||||
CheckForNull null_check,
|
||||
wasm::WasmCodePosition position) {
|
||||
TrapIfTrue(wasm::kTrapNullDereference,
|
||||
gasm_->WordEqual(struct_object, RefNull()), position);
|
||||
return StoreStructFieldUnchecked(mcgraph(), gasm_.get(), struct_object, type,
|
||||
field_index, field_value);
|
||||
if (null_check == kWithNullCheck) {
|
||||
TrapIfTrue(wasm::kTrapNullDereference,
|
||||
gasm_->WordEqual(struct_object, RefNull()), position);
|
||||
}
|
||||
return StoreStructFieldUnchecked(mcgraph(), gasm_.get(), struct_object,
|
||||
struct_type, field_index, field_value);
|
||||
}
|
||||
|
||||
class WasmDecorator final : public GraphDecorator {
|
||||
|
@ -162,6 +162,10 @@ class WasmGraphBuilder {
|
||||
kRetpoline = true,
|
||||
kNoRetpoline = false
|
||||
};
|
||||
enum CheckForNull : bool { // --
|
||||
kWithNullCheck = true,
|
||||
kWithoutNullCheck = false
|
||||
};
|
||||
|
||||
V8_EXPORT_PRIVATE WasmGraphBuilder(
|
||||
wasm::CompilationEnv* env, Zone* zone, MachineGraph* mcgraph,
|
||||
@ -187,6 +191,7 @@ class WasmGraphBuilder {
|
||||
Node* EffectPhi(unsigned count, Node** effects_and_control);
|
||||
Node* RefNull();
|
||||
Node* RefFunc(uint32_t function_index);
|
||||
Node* RefAsNonNull(Node* arg, wasm::WasmCodePosition position);
|
||||
Node* Uint32Constant(uint32_t value);
|
||||
Node* Int32Constant(int32_t value);
|
||||
Node* Int64Constant(int64_t value);
|
||||
@ -245,7 +250,7 @@ class WasmGraphBuilder {
|
||||
Node* arr[] = {fst, more...};
|
||||
return Return(ArrayVector(arr));
|
||||
}
|
||||
Node* Unreachable(wasm::WasmCodePosition position);
|
||||
Node* Trap(wasm::TrapReason reason, wasm::WasmCodePosition position);
|
||||
|
||||
Node* CallDirect(uint32_t index, Vector<Node*> args, Vector<Node*> rets,
|
||||
wasm::WasmCodePosition position);
|
||||
@ -369,10 +374,11 @@ class WasmGraphBuilder {
|
||||
|
||||
Node* StructNew(uint32_t struct_index, const wasm::StructType* type,
|
||||
Vector<Node*> fields);
|
||||
Node* StructGet(Node* struct_object, const wasm::StructType* type,
|
||||
uint32_t field_index, wasm::WasmCodePosition position);
|
||||
Node* StructSet(Node* struct_object, const wasm::StructType* type,
|
||||
uint32_t field_index, Node* value,
|
||||
Node* StructGet(Node* struct_object, const wasm::StructType* struct_type,
|
||||
uint32_t field_index, CheckForNull null_check,
|
||||
wasm::WasmCodePosition position);
|
||||
Node* StructSet(Node* struct_object, const wasm::StructType* struct_type,
|
||||
uint32_t field_index, Node* value, CheckForNull null_check,
|
||||
wasm::WasmCodePosition position);
|
||||
Node* ArrayNew(uint32_t array_index, const wasm::ArrayType* type,
|
||||
Node* length, Node* initial_value);
|
||||
|
@ -1513,6 +1513,10 @@ class LiftoffCompiler {
|
||||
unsupported(decoder, kAnyRef, "func");
|
||||
}
|
||||
|
||||
void RefAsNonNull(FullDecoder* decoder, const Value& arg, Value* result) {
|
||||
unsupported(decoder, kAnyRef, "ref.as_non_null");
|
||||
}
|
||||
|
||||
void Drop(FullDecoder* decoder, const Value& value) {
|
||||
auto& slot = __ cache_state()->stack_state.back();
|
||||
// If the dropped slot contains a register, decrement it's use count.
|
||||
@ -3372,6 +3376,11 @@ class LiftoffCompiler {
|
||||
unsupported(decoder, kGC, "array.new");
|
||||
}
|
||||
|
||||
void PassThrough(FullDecoder* decoder, const Value& from, Value* to) {
|
||||
// TODO(7748): Implement.
|
||||
unsupported(decoder, kGC, "");
|
||||
}
|
||||
|
||||
private:
|
||||
// Emit additional source positions for return addresses. Used by debugging to
|
||||
// OSR frames with different sets of breakpoints.
|
||||
|
@ -844,6 +844,7 @@ enum class LoadTransformationKind : uint8_t {
|
||||
F(F64Const, Value* result, double value) \
|
||||
F(RefNull, Value* result) \
|
||||
F(RefFunc, uint32_t function_index, Value* result) \
|
||||
F(RefAsNonNull, const Value& arg, Value* result) \
|
||||
F(Drop, const Value& value) \
|
||||
F(DoReturn, Vector<Value> values) \
|
||||
F(LocalGet, Value* result, const LocalIndexImmediate<validate>& imm) \
|
||||
@ -916,7 +917,8 @@ enum class LoadTransformationKind : uint8_t {
|
||||
F(StructSet, const Value& struct_object, \
|
||||
const FieldIndexImmediate<validate>& field, const Value& field_value) \
|
||||
F(ArrayNew, const ArrayIndexImmediate<validate>& imm, const Value& length, \
|
||||
const Value& initial_value, Value* result)
|
||||
const Value& initial_value, Value* result) \
|
||||
F(PassThrough, const Value& from, Value* to)
|
||||
|
||||
// Generic Wasm bytecode decoder with utilities for decoding immediates,
|
||||
// lengths, etc.
|
||||
@ -1573,6 +1575,7 @@ class WasmDecoder : public Decoder {
|
||||
case kExprTableGet:
|
||||
case kExprLocalTee:
|
||||
case kExprMemoryGrow:
|
||||
case kExprRefAsNonNull:
|
||||
return {1, 1};
|
||||
case kExprLocalSet:
|
||||
case kExprGlobalSet:
|
||||
@ -2235,6 +2238,35 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
len = 1 + imm.length;
|
||||
break;
|
||||
}
|
||||
case kExprRefAsNonNull: {
|
||||
CHECK_PROTOTYPE_OPCODE(anyref);
|
||||
auto value = Pop();
|
||||
switch (value.type.kind()) {
|
||||
case ValueType::kRef: {
|
||||
auto* result =
|
||||
Push(ValueType(ValueType::kRef, value.type.ref_index()));
|
||||
CALL_INTERFACE_IF_REACHABLE(PassThrough, value, result);
|
||||
break;
|
||||
}
|
||||
case ValueType::kOptRef: {
|
||||
auto* result =
|
||||
Push(ValueType(ValueType::kRef, value.type.ref_index()));
|
||||
CALL_INTERFACE_IF_REACHABLE(RefAsNonNull, value, result);
|
||||
break;
|
||||
}
|
||||
case ValueType::kNullRef:
|
||||
// TODO(7748): Fix this once the standard clears up (see
|
||||
// https://github.com/WebAssembly/function-references/issues/21).
|
||||
CALL_INTERFACE_IF_REACHABLE(Unreachable);
|
||||
EndControl();
|
||||
break;
|
||||
default:
|
||||
this->error(this->pc_ + 1,
|
||||
"invalid agrument type to ref.as_non_null");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kExprLocalGet: {
|
||||
LocalIndexImmediate<validate> imm(this, this->pc_);
|
||||
if (!this->Validate(this->pc_, imm)) break;
|
||||
@ -2968,7 +3000,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
auto struct_obj =
|
||||
Pop(0, ValueType(ValueType::kOptRef, field.struct_index.index));
|
||||
auto* value = Push(field.struct_index.struct_type->field(field.index));
|
||||
// TODO(7748): Optimize this when struct type is null/ref
|
||||
CALL_INTERFACE_IF_REACHABLE(StructGet, struct_obj, field, value);
|
||||
break;
|
||||
}
|
||||
@ -2980,7 +3011,6 @@ class WasmFullDecoder : public WasmDecoder<validate> {
|
||||
0, ValueType(field.struct_index.struct_type->field(field.index)));
|
||||
auto struct_obj =
|
||||
Pop(0, ValueType(ValueType::kOptRef, field.struct_index.index));
|
||||
// TODO(7748): Optimize this when struct type is null/ref
|
||||
CALL_INTERFACE_IF_REACHABLE(StructSet, struct_obj, field, field_value);
|
||||
break;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "src/wasm/decoder.h"
|
||||
#include "src/wasm/function-body-decoder-impl.h"
|
||||
#include "src/wasm/function-body-decoder.h"
|
||||
#include "src/wasm/value-type.h"
|
||||
#include "src/wasm/wasm-limits.h"
|
||||
#include "src/wasm/wasm-linkage.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
@ -259,6 +260,10 @@ class WasmGraphBuildingInterface {
|
||||
result->node = BUILD(RefFunc, function_index);
|
||||
}
|
||||
|
||||
void RefAsNonNull(FullDecoder* decoder, const Value& arg, Value* result) {
|
||||
result->node = BUILD(RefAsNonNull, arg.node, decoder->position());
|
||||
}
|
||||
|
||||
void Drop(FullDecoder* decoder, const Value& value) {}
|
||||
|
||||
void DoReturn(FullDecoder* decoder, Vector<Value> values) {
|
||||
@ -307,7 +312,7 @@ class WasmGraphBuildingInterface {
|
||||
}
|
||||
|
||||
void Unreachable(FullDecoder* decoder) {
|
||||
BUILD(Unreachable, decoder->position());
|
||||
BUILD(Trap, wasm::TrapReason::kTrapUnreachable, decoder->position());
|
||||
}
|
||||
|
||||
void Select(FullDecoder* decoder, const Value& cond, const Value& fval,
|
||||
@ -614,16 +619,24 @@ class WasmGraphBuildingInterface {
|
||||
|
||||
void StructGet(FullDecoder* decoder, const Value& struct_object,
|
||||
const FieldIndexImmediate<validate>& field, Value* result) {
|
||||
using CheckForNull = compiler::WasmGraphBuilder::CheckForNull;
|
||||
CheckForNull null_check = struct_object.type.kind() == ValueType::kRef
|
||||
? CheckForNull::kWithoutNullCheck
|
||||
: CheckForNull::kWithNullCheck;
|
||||
result->node =
|
||||
BUILD(StructGet, struct_object.node, field.struct_index.struct_type,
|
||||
field.index, decoder->position());
|
||||
field.index, null_check, decoder->position());
|
||||
}
|
||||
|
||||
void StructSet(FullDecoder* decoder, const Value& struct_object,
|
||||
const FieldIndexImmediate<validate>& field,
|
||||
const Value& field_value) {
|
||||
using CheckForNull = compiler::WasmGraphBuilder::CheckForNull;
|
||||
CheckForNull null_check = struct_object.type.kind() == ValueType::kRef
|
||||
? CheckForNull::kWithoutNullCheck
|
||||
: CheckForNull::kWithNullCheck;
|
||||
BUILD(StructSet, struct_object.node, field.struct_index.struct_type,
|
||||
field.index, field_value.node, decoder->position());
|
||||
field.index, field_value.node, null_check, decoder->position());
|
||||
}
|
||||
|
||||
void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate<validate>& imm,
|
||||
@ -633,6 +646,10 @@ class WasmGraphBuildingInterface {
|
||||
initial_value.node);
|
||||
}
|
||||
|
||||
void PassThrough(FullDecoder* decoder, const Value& from, Value* to) {
|
||||
to->node = from.node;
|
||||
}
|
||||
|
||||
private:
|
||||
SsaEnv* ssa_env_ = nullptr;
|
||||
compiler::WasmGraphBuilder* builder_;
|
||||
|
@ -116,6 +116,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
|
||||
CASE_REF_OP(Null, "null")
|
||||
CASE_REF_OP(IsNull, "is_null")
|
||||
CASE_REF_OP(Func, "func")
|
||||
CASE_REF_OP(AsNonNull, "as_non_null")
|
||||
CASE_I32_OP(ConvertI64, "wrap_i64")
|
||||
CASE_CONVERT_OP(Convert, INT, F32, "f32", "trunc")
|
||||
CASE_CONVERT_OP(Convert, INT, F64, "f64", "trunc")
|
||||
@ -467,6 +468,7 @@ bool WasmOpcodes::IsAnyRefOpcode(WasmOpcode opcode) {
|
||||
case kExprRefNull:
|
||||
case kExprRefIsNull:
|
||||
case kExprRefFunc:
|
||||
case kExprRefAsNonNull:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -60,7 +60,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, const WasmFeatures&);
|
||||
V(F32Const, 0x43, _) \
|
||||
V(F64Const, 0x44, _) \
|
||||
V(RefNull, 0xd0, _) \
|
||||
V(RefFunc, 0xd2, _)
|
||||
V(RefFunc, 0xd2, _) \
|
||||
V(RefAsNonNull, 0xd3, _)
|
||||
|
||||
// Load memory expressions.
|
||||
#define FOREACH_LOAD_MEM_OPCODE(V) \
|
||||
@ -229,7 +230,7 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, const WasmFeatures&);
|
||||
|
||||
#define FOREACH_SIMPLE_PROTOTYPE_OPCODE(V) \
|
||||
V(RefIsNull, 0xd1, i_r) \
|
||||
V(RefEq, 0xd3, i_rr) /* made-up opcode, guessing future spec (GC) */
|
||||
V(RefEq, 0xd5, i_rr) // made-up opcode, guessing future spec (GC)
|
||||
|
||||
// For compatibility with Asm.js.
|
||||
// These opcodes are not spec'ed (or visible) externally; the idea is
|
||||
@ -247,8 +248,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, const WasmFeatures&);
|
||||
V(F64Pow, 0xce, d_dd) \
|
||||
V(F64Mod, 0xcf, d_dd) \
|
||||
V(I32AsmjsDivS, 0xe7, i_ii) \
|
||||
V(I32AsmjsDivU, 0xd4, i_ii) \
|
||||
V(I32AsmjsRemS, 0xd5, i_ii) \
|
||||
V(I32AsmjsDivU, 0xe8, i_ii) \
|
||||
V(I32AsmjsRemS, 0xe9, i_ii) \
|
||||
V(I32AsmjsRemU, 0xd6, i_ii) \
|
||||
V(I32AsmjsLoadMem8S, 0xd7, i_i) \
|
||||
V(I32AsmjsLoadMem8U, 0xd8, i_i) \
|
||||
|
@ -83,7 +83,8 @@ WASM_EXEC_TEST(BasicStruct) {
|
||||
kExprEnd};
|
||||
j->EmitCode(j_code, sizeof(j_code));
|
||||
|
||||
// Test struct.set, struct refs types in globals and if-results.
|
||||
// Test struct.set, ref.as_non_null,
|
||||
// struct refs types in globals and if-results.
|
||||
uint32_t k_global_index = builder->AddGlobal(kOptRefType, true);
|
||||
WasmFunctionBuilder* k = builder->AddFunction(sigs.i_v());
|
||||
uint32_t k_field_index = 0;
|
||||
@ -91,10 +92,10 @@ WASM_EXEC_TEST(BasicStruct) {
|
||||
byte k_code[] = {
|
||||
WASM_SET_GLOBAL(k_global_index, WASM_STRUCT_NEW(type_index, WASM_I32V(55),
|
||||
WASM_I32V(66))),
|
||||
WASM_STRUCT_GET(
|
||||
type_index, k_field_index,
|
||||
WASM_IF_ELSE_R(kOptRefType, WASM_I32V(1),
|
||||
WASM_GET_GLOBAL(k_global_index), WASM_REF_NULL)),
|
||||
WASM_STRUCT_GET(type_index, k_field_index,
|
||||
WASM_REF_AS_NON_NULL(WASM_IF_ELSE_R(
|
||||
kOptRefType, WASM_I32V(1),
|
||||
WASM_GET_GLOBAL(k_global_index), WASM_REF_NULL))),
|
||||
kExprEnd};
|
||||
k->EmitCode(k_code, sizeof(k_code));
|
||||
|
||||
|
@ -435,6 +435,7 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
|
||||
#define WASM_REF_NULL kExprRefNull
|
||||
#define WASM_REF_FUNC(val) kExprRefFunc, val
|
||||
#define WASM_REF_IS_NULL(val) val, kExprRefIsNull
|
||||
#define WASM_REF_AS_NON_NULL(val) val, kExprRefAsNonNull
|
||||
|
||||
#define WASM_ARRAY_NEW(index, default_value, length) \
|
||||
default_value, length, WASM_GC_OP(kExprArrayNew), static_cast<byte>(index)
|
||||
|
Loading…
Reference in New Issue
Block a user