Commit f486a343 authored by Nico Hartmann's avatar Nico Hartmann Committed by V8 LUCI CQ

[TurboFan] Add %VerifyType intrinsic

This CL adds a new %VerifyType compiler intrinsic that can be used
by tests and fuzzers to generate a runtime type check of the given
input value. Internally, %VerifyType is lowered to %AssertType
which is why checks are currently limited to range types.

tests to be const-correct.

Drive-by: Add a few consts to NodeProperties accessors to allow
Bug: v8:11724
Change-Id: I06842062d0e8278a5ba011d5a09947fe05b6e85e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2859959
Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74377}
parent 305aa12f
......@@ -30,11 +30,15 @@ Reduction JSIntrinsicLowering::Reduce(Node* node) {
if (node->opcode() != IrOpcode::kJSCallRuntime) return NoChange();
const Runtime::Function* const f =
Runtime::FunctionForId(CallRuntimeParametersOf(node->op()).id());
if (f->function_id == Runtime::kTurbofanStaticAssert) {
return ReduceTurbofanStaticAssert(node);
}
if (f->function_id == Runtime::kIsBeingInterpreted) {
return ReduceIsBeingInterpreted(node);
switch (f->function_id) {
case Runtime::kIsBeingInterpreted:
return ReduceIsBeingInterpreted(node);
case Runtime::kTurbofanStaticAssert:
return ReduceTurbofanStaticAssert(node);
case Runtime::kVerifyType:
return ReduceVerifyType(node);
default:
break;
}
if (f->intrinsic_type != Runtime::IntrinsicType::INLINE) return NoChange();
switch (f->function_id) {
......@@ -288,6 +292,10 @@ Reduction JSIntrinsicLowering::ReduceTurbofanStaticAssert(Node* node) {
return Changed(jsgraph_->UndefinedConstant());
}
Reduction JSIntrinsicLowering::ReduceVerifyType(Node* node) {
return Change(node, simplified()->VerifyType());
}
Reduction JSIntrinsicLowering::ReduceIsBeingInterpreted(Node* node) {
RelaxEffectsAndControls(node);
return Changed(jsgraph_->FalseConstant());
......
......@@ -60,6 +60,7 @@ class V8_EXPORT_PRIVATE JSIntrinsicLowering final
Reduction ReduceIsSmi(Node* node);
Reduction ReduceIsBeingInterpreted(Node* node);
Reduction ReduceTurbofanStaticAssert(Node* node);
Reduction ReduceVerifyType(Node* node);
Reduction ReduceToLength(Node* node);
Reduction ReduceToObject(Node* node);
Reduction ReduceToString(Node* node);
......
......@@ -308,7 +308,7 @@ const CallParameters& CallParametersOf(const Operator* op);
// Defines the arity and the ID for a runtime function call. This is used as a
// parameter by JSCallRuntime operators.
class CallRuntimeParameters final {
class V8_EXPORT_PRIVATE CallRuntimeParameters final {
public:
CallRuntimeParameters(Runtime::FunctionId id, size_t arity)
: id_(id), arity_(arity) {}
......@@ -328,8 +328,8 @@ size_t hash_value(CallRuntimeParameters const&);
std::ostream& operator<<(std::ostream&, CallRuntimeParameters const&);
const CallRuntimeParameters& CallRuntimeParametersOf(const Operator* op);
V8_EXPORT_PRIVATE const CallRuntimeParameters& CallRuntimeParametersOf(
const Operator* op);
// Defines the location of a context slot relative to a specific scope. This is
// used as a parameter by JSLoadContext and JSStoreContext operators and allows
......
......@@ -28,7 +28,7 @@ class V8_EXPORT_PRIVATE NodeProperties {
// Inputs are always arranged in order as follows:
// 0 [ values, context, frame state, effects, control ] node->InputCount()
static int FirstValueIndex(Node* node) { return 0; }
static int FirstValueIndex(const Node* node) { return 0; }
static int FirstContextIndex(Node* node) { return PastValueIndex(node); }
static int FirstFrameStateIndex(Node* node) { return PastContextIndex(node); }
static int FirstEffectIndex(Node* node) { return PastFrameStateIndex(node); }
......@@ -65,6 +65,12 @@ class V8_EXPORT_PRIVATE NodeProperties {
return node->InputAt(FirstValueIndex(node) + index);
}
static const Node* GetValueInput(const Node* node, int index) {
CHECK_LE(0, index);
CHECK_LT(index, node->op()->ValueInputCount());
return node->InputAt(FirstValueIndex(node) + index);
}
static Node* GetContextInput(Node* node) {
CHECK(OperatorProperties::HasContextInput(node->op()));
return node->InputAt(FirstContextIndex(node));
......@@ -249,7 +255,7 @@ class V8_EXPORT_PRIVATE NodeProperties {
// Type.
static bool IsTyped(const Node* node) { return !node->type().IsInvalid(); }
static Type GetType(Node* node) {
static Type GetType(const Node* node) {
DCHECK(IsTyped(node));
return node->type();
}
......
......@@ -490,7 +490,8 @@
V(TransitionAndStoreNumberElement) \
V(TransitionElementsKind) \
V(TypeOf) \
V(UpdateInterruptBudget)
V(UpdateInterruptBudget) \
V(VerifyType)
#define SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(V) \
V(SpeculativeBigIntAdd) \
......
......@@ -3936,6 +3936,21 @@ class RepresentationSelector {
case IrOpcode::kAssertType:
return VisitUnop<T>(node, UseInfo::AnyTagged(),
MachineRepresentation::kTagged);
case IrOpcode::kVerifyType: {
Type inputType = TypeOf(node->InputAt(0));
VisitUnop<T>(node, UseInfo::AnyTagged(), MachineRepresentation::kTagged,
inputType);
if (lower<T>()) {
CHECK_IMPLIES(!FLAG_fuzzing, inputType.CanBeAsserted());
if (inputType.CanBeAsserted()) {
ChangeOp(node, simplified()->AssertType(inputType));
} else {
DeferReplacement(node, node->InputAt(0));
}
}
return;
}
default:
FATAL(
"Representation inference: unsupported opcode %i (%s), node #%i\n.",
......
......@@ -1346,6 +1346,12 @@ const Operator* SimplifiedOperatorBuilder::AssertType(Type type) {
"AssertType", 1, 0, 0, 1, 0, 0, type);
}
const Operator* SimplifiedOperatorBuilder::VerifyType() {
return zone()->New<Operator>(IrOpcode::kVerifyType,
Operator::kNoThrow | Operator::kNoDeopt,
"VerifyType", 1, 0, 0, 1, 0, 0);
}
const Operator* SimplifiedOperatorBuilder::CheckIf(
DeoptimizeReason reason, const FeedbackSource& feedback) {
if (!feedback.IsValid()) {
......
......@@ -1086,6 +1086,10 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
// Abort if the value input does not inhabit the given type
const Operator* AssertType(Type type);
// Abort if the value does not match the node's computed type after
// SimplifiedLowering.
const Operator* VerifyType();
const Operator* DateNow();
// Represents the inputs necessary to construct a fast and a slow API call.
......
......@@ -2359,6 +2359,10 @@ Type Typer::Visitor::TypeRuntimeAbort(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeAssertType(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeVerifyType(Node* node) {
return TypeOrNone(node->InputAt(0));
}
// Heap constants.
Type Typer::Visitor::TypeConstant(Handle<Object> value) {
......
......@@ -1500,6 +1500,15 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kAssertType:
break;
case IrOpcode::kVerifyType:
if (NodeProperties::IsTyped(node)) {
Node* input = NodeProperties::GetValueInput(node, 0);
DCHECK(NodeProperties::IsTyped(input));
CHECK(NodeProperties::GetType(node).Equals(
NodeProperties::GetType(input)));
}
break;
case IrOpcode::kCheckFloat64Hole:
CheckValueInputIs(node, 0, Type::NumberOrHole());
CheckTypeIs(node, Type::NumberOrUndefined());
......
......@@ -231,6 +231,14 @@ RUNTIME_FUNCTION(Runtime_ObserveNode) {
return *obj;
}
RUNTIME_FUNCTION(Runtime_VerifyType) {
// %VerifyType has no effect in the interpreter.
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, obj, 0);
return *obj;
}
static bool IsSuitableForOnStackReplacement(Isolate* isolate,
Handle<JSFunction> function) {
// Keep track of whether we've succeeded in optimizing.
......
......@@ -218,6 +218,7 @@ bool Runtime::IsAllowListedForFuzzing(FunctionId id) {
case Runtime::kGetOptimizationStatus:
case Runtime::kHeapObjectVerify:
case Runtime::kIsBeingInterpreted:
case Runtime::kVerifyType:
return !FLAG_allow_natives_for_differential_fuzzing;
case Runtime::kCompileBaseline:
case Runtime::kBaselineOsr:
......
......@@ -114,7 +114,8 @@ namespace internal {
F(InstantiateAsmJs, 4, 1) \
F(NotifyDeoptimized, 0, 1) \
F(ObserveNode, 1, 1) \
F(ResolvePossiblyDirectEval, 6, 1)
F(ResolvePossiblyDirectEval, 6, 1) \
F(VerifyType, 1, 1)
#define FOR_EACH_INTRINSIC_DATE(F, I) F(DateCurrentTime, 0, 1)
......
......@@ -127,6 +127,7 @@ v8_source_set("cctest_sources") {
"compiler/test-run-unwinding-info.cc",
"compiler/test-run-variables.cc",
"compiler/test-sloppy-equality.cc",
"compiler/test-verify-type.cc",
"compiler/value-helper.cc",
"compiler/value-helper.h",
"disasm-regex-helper.cc",
......
......@@ -198,6 +198,7 @@
# %ObserveNode tests relies on TurboFan.
'test-sloppy-equality/*' : [SKIP],
'test-js-to-wasm/*': [SKIP],
'test-verify-type/*': [SKIP],
}], # variant == nooptimization
##############################################################################
......@@ -663,6 +664,7 @@
'test-run-wasm-exceptions/RunWasmTurbofan_TryCatchCallIndirect': [SKIP],
'test-run-wasm-exceptions/RunWasmTurbofan_TryCatchThrow': [SKIP],
'test-run-wasm-exceptions/RunWasmTurbofan_TryCatchTrapTypeError': [SKIP],
'test-verify-type/*': [SKIP],
# --interpreted-frames-native-stack tests
'test-log/ExternalCodeEventListenerWithInterpretedFramesNativeStack': [SKIP],
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/js-operator.h"
#include "test/cctest/compiler/node-observer-tester.h"
#include "test/common/flag-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
TEST(TestVerifyType) {
FlagScope<bool> allow_natives_syntax(&i::FLAG_allow_natives_syntax, true);
HandleAndZoneScope handle_scope;
Isolate* isolate = handle_scope.main_isolate();
Zone* zone = handle_scope.main_zone();
const char* source =
"function test(b) {\n"
" let x = -8;\n"
" if(b) x = 42;\n"
" return %ObserveNode(%VerifyType(x));\n"
"}\n"
"\n"
"%PrepareFunctionForOptimization(test);\n"
"test(false); test(true);\n"
"%OptimizeFunctionOnNextCall(test);\n"
"test(false);\n";
bool js_intrinsic_lowering_happened = false;
bool simplified_lowering_happened = false;
ModificationObserver observer(
[](const Node* node) {
CHECK_EQ(node->opcode(), IrOpcode::kJSCallRuntime);
CHECK_EQ(CallRuntimeParametersOf(node->op()).id(),
Runtime::kVerifyType);
},
[&](const Node* node, const ObservableNodeState& old_state) {
if (old_state.opcode() == IrOpcode::kJSCallRuntime &&
node->opcode() == IrOpcode::kVerifyType) {
// CallRuntime is lowered to VerifyType in JSIntrinsicLowering.
js_intrinsic_lowering_happened = true;
return NodeObserver::Observation::kContinue;
} else if (old_state.opcode() == IrOpcode::kVerifyType &&
node->opcode() == IrOpcode::kAssertType) {
// VerifyType is lowered to AssertType in SimplifiedLowering.
// AssertType asserts for the type of its value input.
Type asserted_type = OpParameter<Type>(node->op());
CHECK(asserted_type.Equals(Type::Range(-8, 42, zone)));
simplified_lowering_happened = true;
return NodeObserver::Observation::kStop;
} else {
// Every other lowering would be wrong, so fail the test.
UNREACHABLE();
}
return NodeObserver::Observation::kContinue;
});
compiler::ObserveNodeScope scope(isolate, &observer);
CompileRun(source);
CHECK(js_intrinsic_lowering_happened);
CHECK(simplified_lowering_happened);
}
} // namespace compiler
} // namespace internal
} // namespace v8
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