Commit 0a69768a authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[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: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67705}
parent c74010bf
......@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment