[turbofan] Add optional runtime checks for range types
This CL adds the --assert-types flag to d8, which is intended to insert additional runtime checks after typed nodes, verifying the validity of our typing rules. So far, only range types are checked. Thanks to Neil Patil for suggesting something similar. R=neis@chromium.org, tebbi@chromium.org Change-Id: I5eb2c482235ec8cd07ee802ca7c12c86c2d3dc40 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1678372 Commit-Queue: Georg Schmid <gsps@google.com> Reviewed-by: Georg Neis <neis@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Cr-Commit-Position: refs/heads/master@{#62664}
This commit is contained in:
parent
d7479d79c7
commit
2e82ead865
2
BUILD.gn
2
BUILD.gn
@ -1877,6 +1877,8 @@ v8_compiler_sources = [
|
|||||||
"src/compiler/state-values-utils.h",
|
"src/compiler/state-values-utils.h",
|
||||||
"src/compiler/store-store-elimination.cc",
|
"src/compiler/store-store-elimination.cc",
|
||||||
"src/compiler/store-store-elimination.h",
|
"src/compiler/store-store-elimination.h",
|
||||||
|
"src/compiler/add-type-assertions-reducer.cc",
|
||||||
|
"src/compiler/add-type-assertions-reducer.h",
|
||||||
"src/compiler/type-cache.cc",
|
"src/compiler/type-cache.cc",
|
||||||
"src/compiler/type-cache.h",
|
"src/compiler/type-cache.h",
|
||||||
"src/compiler/type-narrowing-reducer.cc",
|
"src/compiler/type-narrowing-reducer.cc",
|
||||||
|
@ -1682,6 +1682,7 @@ extern operator '==' macro Word32Equal(bool, bool): bool;
|
|||||||
extern operator '!=' macro Word32NotEqual(bool, bool): bool;
|
extern operator '!=' macro Word32NotEqual(bool, bool): bool;
|
||||||
|
|
||||||
extern operator '+' macro Float64Add(float64, float64): float64;
|
extern operator '+' macro Float64Add(float64, float64): float64;
|
||||||
|
extern operator '-' macro Float64Sub(float64, float64): float64;
|
||||||
|
|
||||||
extern operator '+' macro NumberAdd(Number, Number): Number;
|
extern operator '+' macro NumberAdd(Number, Number): Number;
|
||||||
extern operator '-' macro NumberSub(Number, Number): Number;
|
extern operator '-' macro NumberSub(Number, Number): Number;
|
||||||
@ -1734,6 +1735,8 @@ extern macro TaggedIsNotSmi(Object): bool;
|
|||||||
extern macro TaggedIsPositiveSmi(Object): bool;
|
extern macro TaggedIsPositiveSmi(Object): bool;
|
||||||
extern macro IsValidPositiveSmi(intptr): bool;
|
extern macro IsValidPositiveSmi(intptr): bool;
|
||||||
|
|
||||||
|
extern macro IsInteger(HeapNumber): bool;
|
||||||
|
|
||||||
extern macro HeapObjectToJSDataView(HeapObject): JSDataView
|
extern macro HeapObjectToJSDataView(HeapObject): JSDataView
|
||||||
labels CastError;
|
labels CastError;
|
||||||
extern macro HeapObjectToJSProxy(HeapObject): JSProxy
|
extern macro HeapObjectToJSProxy(HeapObject): JSProxy
|
||||||
@ -3070,3 +3073,41 @@ macro VerifiedUnreachable(): never {
|
|||||||
StaticAssert(false);
|
StaticAssert(false);
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro Float64IsSomeInfinity(value: float64): bool {
|
||||||
|
if (value == V8_INFINITY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return value == (Convert<float64>(0) - V8_INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@export
|
||||||
|
macro IsIntegerOrSomeInfinity(o: Object): bool {
|
||||||
|
typeswitch (o) {
|
||||||
|
case (Smi): {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case (hn: HeapNumber): {
|
||||||
|
if (Float64IsSomeInfinity(Convert<float64>(hn))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return IsInteger(hn);
|
||||||
|
}
|
||||||
|
case (Object): {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builtin CheckNumberInRange(implicit context: Context)(
|
||||||
|
value: Number, min: Number, max: Number): Undefined {
|
||||||
|
if (IsIntegerOrSomeInfinity(value) && min <= value && value <= max) {
|
||||||
|
return Undefined;
|
||||||
|
} else {
|
||||||
|
Print('Range type assertion failed! (value/min/max)');
|
||||||
|
Print(value);
|
||||||
|
Print(min);
|
||||||
|
Print(max);
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
51
src/compiler/add-type-assertions-reducer.cc
Normal file
51
src/compiler/add-type-assertions-reducer.cc
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 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/add-type-assertions-reducer.h"
|
||||||
|
|
||||||
|
#include "src/compiler/node-properties.h"
|
||||||
|
|
||||||
|
namespace v8 {
|
||||||
|
namespace internal {
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
AddTypeAssertionsReducer::AddTypeAssertionsReducer(Editor* editor,
|
||||||
|
JSGraph* jsgraph, Zone* zone)
|
||||||
|
: AdvancedReducer(editor),
|
||||||
|
jsgraph_(jsgraph),
|
||||||
|
visited_(jsgraph->graph()->NodeCount(), zone) {}
|
||||||
|
|
||||||
|
AddTypeAssertionsReducer::~AddTypeAssertionsReducer() = default;
|
||||||
|
|
||||||
|
Reduction AddTypeAssertionsReducer::Reduce(Node* node) {
|
||||||
|
if (node->opcode() == IrOpcode::kAssertType ||
|
||||||
|
node->opcode() == IrOpcode::kPhi || !NodeProperties::IsTyped(node) ||
|
||||||
|
visited_.Get(node)) {
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
visited_.Set(node, true);
|
||||||
|
|
||||||
|
Type type = NodeProperties::GetType(node);
|
||||||
|
if (!type.IsRange()) {
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* assertion = graph()->NewNode(simplified()->AssertType(type), node);
|
||||||
|
NodeProperties::SetType(assertion, type);
|
||||||
|
|
||||||
|
for (Edge edge : node->use_edges()) {
|
||||||
|
Node* const user = edge.from();
|
||||||
|
DCHECK(!user->IsDead());
|
||||||
|
if (NodeProperties::IsValueEdge(edge) && user != assertion) {
|
||||||
|
edge.UpdateTo(assertion);
|
||||||
|
Revisit(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace compiler
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace v8
|
45
src/compiler/add-type-assertions-reducer.h
Normal file
45
src/compiler/add-type-assertions-reducer.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2019 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.
|
||||||
|
|
||||||
|
#ifndef V8_COMPILER_ADD_TYPE_ASSERTIONS_REDUCER_H_
|
||||||
|
#define V8_COMPILER_ADD_TYPE_ASSERTIONS_REDUCER_H_
|
||||||
|
|
||||||
|
#include "src/common/globals.h"
|
||||||
|
#include "src/compiler/graph-reducer.h"
|
||||||
|
#include "src/compiler/js-graph.h"
|
||||||
|
#include "src/compiler/node-aux-data.h"
|
||||||
|
#include "src/compiler/simplified-operator.h"
|
||||||
|
|
||||||
|
namespace v8 {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
class V8_EXPORT_PRIVATE AddTypeAssertionsReducer final
|
||||||
|
: public NON_EXPORTED_BASE(AdvancedReducer) {
|
||||||
|
public:
|
||||||
|
AddTypeAssertionsReducer(Editor* editor, JSGraph* jsgraph, Zone* zone);
|
||||||
|
~AddTypeAssertionsReducer() final;
|
||||||
|
|
||||||
|
const char* reducer_name() const override {
|
||||||
|
return "AddTypeAssertionsReducer";
|
||||||
|
}
|
||||||
|
|
||||||
|
Reduction Reduce(Node* node) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
JSGraph* const jsgraph_;
|
||||||
|
NodeAuxData<bool> visited_;
|
||||||
|
|
||||||
|
Graph* graph() { return jsgraph_->graph(); }
|
||||||
|
SimplifiedOperatorBuilder* simplified() { return jsgraph_->simplified(); }
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(AddTypeAssertionsReducer);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace compiler
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace v8
|
||||||
|
|
||||||
|
#endif // V8_COMPILER_ADD_TYPE_ASSERTIONS_REDUCER_H_
|
@ -194,6 +194,7 @@ class EffectControlLinearizer {
|
|||||||
void LowerTransitionAndStoreNumberElement(Node* node);
|
void LowerTransitionAndStoreNumberElement(Node* node);
|
||||||
void LowerTransitionAndStoreNonNumberElement(Node* node);
|
void LowerTransitionAndStoreNonNumberElement(Node* node);
|
||||||
void LowerRuntimeAbort(Node* node);
|
void LowerRuntimeAbort(Node* node);
|
||||||
|
Node* LowerAssertType(Node* node);
|
||||||
Node* LowerConvertReceiver(Node* node);
|
Node* LowerConvertReceiver(Node* node);
|
||||||
Node* LowerDateNow(Node* node);
|
Node* LowerDateNow(Node* node);
|
||||||
|
|
||||||
@ -1267,6 +1268,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
|
|||||||
case IrOpcode::kRuntimeAbort:
|
case IrOpcode::kRuntimeAbort:
|
||||||
LowerRuntimeAbort(node);
|
LowerRuntimeAbort(node);
|
||||||
break;
|
break;
|
||||||
|
case IrOpcode::kAssertType:
|
||||||
|
result = LowerAssertType(node);
|
||||||
|
break;
|
||||||
case IrOpcode::kConvertReceiver:
|
case IrOpcode::kConvertReceiver:
|
||||||
result = LowerConvertReceiver(node);
|
result = LowerConvertReceiver(node);
|
||||||
break;
|
break;
|
||||||
@ -5347,6 +5351,30 @@ void EffectControlLinearizer::LowerRuntimeAbort(Node* node) {
|
|||||||
__ Int32Constant(1), __ NoContextConstant());
|
__ Int32Constant(1), __ NoContextConstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node* EffectControlLinearizer::LowerAssertType(Node* node) {
|
||||||
|
DCHECK_EQ(node->opcode(), IrOpcode::kAssertType);
|
||||||
|
Type type = OpParameter<Type>(node->op());
|
||||||
|
DCHECK(type.IsRange());
|
||||||
|
auto range = type.AsRange();
|
||||||
|
|
||||||
|
Node* const input = node->InputAt(0);
|
||||||
|
Node* const min = __ NumberConstant(range->Min());
|
||||||
|
Node* const max = __ NumberConstant(range->Max());
|
||||||
|
|
||||||
|
{
|
||||||
|
Callable const callable =
|
||||||
|
Builtins::CallableFor(isolate(), Builtins::kCheckNumberInRange);
|
||||||
|
Operator::Properties const properties = node->op()->properties();
|
||||||
|
CallDescriptor::Flags const flags = CallDescriptor::kNoFlags;
|
||||||
|
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
||||||
|
graph()->zone(), callable.descriptor(),
|
||||||
|
callable.descriptor().GetStackParameterCount(), flags, properties);
|
||||||
|
__ Call(call_descriptor, __ HeapConstant(callable.code()), input, min, max,
|
||||||
|
__ NoContextConstant());
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Node* EffectControlLinearizer::LowerConvertReceiver(Node* node) {
|
Node* EffectControlLinearizer::LowerConvertReceiver(Node* node) {
|
||||||
ConvertReceiverMode const mode = ConvertReceiverModeOf(node->op());
|
ConvertReceiverMode const mode = ConvertReceiverModeOf(node->op());
|
||||||
Node* value = node->InputAt(0);
|
Node* value = node->InputAt(0);
|
||||||
|
@ -52,6 +52,9 @@ Node* GraphAssembler::HeapConstant(Handle<HeapObject> object) {
|
|||||||
return jsgraph()->HeapConstant(object);
|
return jsgraph()->HeapConstant(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node* GraphAssembler::NumberConstant(double value) {
|
||||||
|
return jsgraph()->Constant(value);
|
||||||
|
}
|
||||||
|
|
||||||
Node* GraphAssembler::ExternalConstant(ExternalReference ref) {
|
Node* GraphAssembler::ExternalConstant(ExternalReference ref) {
|
||||||
return jsgraph()->ExternalConstant(ref);
|
return jsgraph()->ExternalConstant(ref);
|
||||||
|
@ -200,6 +200,7 @@ class GraphAssembler {
|
|||||||
Node* Float64Constant(double value);
|
Node* Float64Constant(double value);
|
||||||
Node* Projection(int index, Node* value);
|
Node* Projection(int index, Node* value);
|
||||||
Node* HeapConstant(Handle<HeapObject> object);
|
Node* HeapConstant(Handle<HeapObject> object);
|
||||||
|
Node* NumberConstant(double value);
|
||||||
Node* CEntryStubConstant(int result_size);
|
Node* CEntryStubConstant(int result_size);
|
||||||
Node* ExternalConstant(ExternalReference ref);
|
Node* ExternalConstant(ExternalReference ref);
|
||||||
|
|
||||||
|
@ -471,6 +471,7 @@
|
|||||||
V(FindOrderedHashMapEntryForInt32Key) \
|
V(FindOrderedHashMapEntryForInt32Key) \
|
||||||
V(PoisonIndex) \
|
V(PoisonIndex) \
|
||||||
V(RuntimeAbort) \
|
V(RuntimeAbort) \
|
||||||
|
V(AssertType) \
|
||||||
V(DateNow)
|
V(DateNow)
|
||||||
|
|
||||||
#define SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(V) V(SpeculativeBigIntAdd)
|
#define SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(V) V(SpeculativeBigIntAdd)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "src/codegen/compiler.h"
|
#include "src/codegen/compiler.h"
|
||||||
#include "src/codegen/optimized-compilation-info.h"
|
#include "src/codegen/optimized-compilation-info.h"
|
||||||
#include "src/codegen/register-configuration.h"
|
#include "src/codegen/register-configuration.h"
|
||||||
|
#include "src/compiler/add-type-assertions-reducer.h"
|
||||||
#include "src/compiler/backend/code-generator.h"
|
#include "src/compiler/backend/code-generator.h"
|
||||||
#include "src/compiler/backend/frame-elider.h"
|
#include "src/compiler/backend/frame-elider.h"
|
||||||
#include "src/compiler/backend/instruction-selector.h"
|
#include "src/compiler/backend/instruction-selector.h"
|
||||||
@ -1421,6 +1422,19 @@ struct EscapeAnalysisPhase {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TypeAssertionsPhase {
|
||||||
|
static const char* phase_name() { return "V8.TFTypeAssertions"; }
|
||||||
|
|
||||||
|
void Run(PipelineData* data, Zone* temp_zone) {
|
||||||
|
GraphReducer graph_reducer(temp_zone, data->graph(),
|
||||||
|
data->jsgraph()->Dead());
|
||||||
|
AddTypeAssertionsReducer type_assertions(&graph_reducer, data->jsgraph(),
|
||||||
|
temp_zone);
|
||||||
|
AddReducer(data, &graph_reducer, &type_assertions);
|
||||||
|
graph_reducer.ReduceGraph();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct SimplifiedLoweringPhase {
|
struct SimplifiedLoweringPhase {
|
||||||
static const char* phase_name() { return "V8.TFSimplifiedLowering"; }
|
static const char* phase_name() { return "V8.TFSimplifiedLowering"; }
|
||||||
|
|
||||||
@ -2230,6 +2244,11 @@ bool PipelineImpl::OptimizeGraph(Linkage* linkage) {
|
|||||||
RunPrintAndVerify(EscapeAnalysisPhase::phase_name());
|
RunPrintAndVerify(EscapeAnalysisPhase::phase_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FLAG_assert_types) {
|
||||||
|
Run<TypeAssertionsPhase>();
|
||||||
|
RunPrintAndVerify(TypeAssertionsPhase::phase_name());
|
||||||
|
}
|
||||||
|
|
||||||
// Perform simplified lowering. This has to run w/o the Typer decorator,
|
// Perform simplified lowering. This has to run w/o the Typer decorator,
|
||||||
// because we cannot compute meaningful types anyways, and the computed types
|
// because we cannot compute meaningful types anyways, and the computed types
|
||||||
// might even conflict with the representation/truncation logic.
|
// might even conflict with the representation/truncation logic.
|
||||||
|
@ -3465,6 +3465,9 @@ class RepresentationSelector {
|
|||||||
return SetOutput(node, MachineRepresentation::kNone);
|
return SetOutput(node, MachineRepresentation::kNone);
|
||||||
case IrOpcode::kStaticAssert:
|
case IrOpcode::kStaticAssert:
|
||||||
return VisitUnop(node, UseInfo::Any(), MachineRepresentation::kTagged);
|
return VisitUnop(node, UseInfo::Any(), MachineRepresentation::kTagged);
|
||||||
|
case IrOpcode::kAssertType:
|
||||||
|
return VisitUnop(node, UseInfo::AnyTagged(),
|
||||||
|
MachineRepresentation::kTagged);
|
||||||
default:
|
default:
|
||||||
FATAL(
|
FATAL(
|
||||||
"Representation inference: unsupported opcode %i (%s), node #%i\n.",
|
"Representation inference: unsupported opcode %i (%s), node #%i\n.",
|
||||||
|
@ -1231,6 +1231,13 @@ const Operator* SimplifiedOperatorBuilder::BigIntAsUintN(int bits) {
|
|||||||
"BigIntAsUintN", 1, 0, 0, 1, 0, 0, bits);
|
"BigIntAsUintN", 1, 0, 0, 1, 0, 0, bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Operator* SimplifiedOperatorBuilder::AssertType(Type type) {
|
||||||
|
DCHECK(type.IsRange());
|
||||||
|
return new (zone()) Operator1<Type>(IrOpcode::kAssertType,
|
||||||
|
Operator::kNoThrow | Operator::kNoDeopt,
|
||||||
|
"AssertType", 1, 0, 0, 1, 0, 0, type);
|
||||||
|
}
|
||||||
|
|
||||||
const Operator* SimplifiedOperatorBuilder::CheckIf(
|
const Operator* SimplifiedOperatorBuilder::CheckIf(
|
||||||
DeoptimizeReason reason, const VectorSlotPair& feedback) {
|
DeoptimizeReason reason, const VectorSlotPair& feedback) {
|
||||||
if (!feedback.IsValid()) {
|
if (!feedback.IsValid()) {
|
||||||
|
@ -888,6 +888,9 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
|
|||||||
// Abort (for terminating execution on internal error).
|
// Abort (for terminating execution on internal error).
|
||||||
const Operator* RuntimeAbort(AbortReason reason);
|
const Operator* RuntimeAbort(AbortReason reason);
|
||||||
|
|
||||||
|
// Abort if the value input does not inhabit the given type
|
||||||
|
const Operator* AssertType(Type type);
|
||||||
|
|
||||||
const Operator* DateNow();
|
const Operator* DateNow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -2348,6 +2348,8 @@ Type Typer::Visitor::TypeFindOrderedHashMapEntryForInt32Key(Node* node) {
|
|||||||
|
|
||||||
Type Typer::Visitor::TypeRuntimeAbort(Node* node) { UNREACHABLE(); }
|
Type Typer::Visitor::TypeRuntimeAbort(Node* node) { UNREACHABLE(); }
|
||||||
|
|
||||||
|
Type Typer::Visitor::TypeAssertType(Node* node) { UNREACHABLE(); }
|
||||||
|
|
||||||
// Heap constants.
|
// Heap constants.
|
||||||
|
|
||||||
Type Typer::Visitor::TypeConstant(Handle<Object> value) {
|
Type Typer::Visitor::TypeConstant(Handle<Object> value) {
|
||||||
|
@ -1544,6 +1544,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
|
|||||||
case IrOpcode::kCheckedTaggedToCompressedSigned:
|
case IrOpcode::kCheckedTaggedToCompressedSigned:
|
||||||
case IrOpcode::kCheckedTaggedToCompressedPointer:
|
case IrOpcode::kCheckedTaggedToCompressedPointer:
|
||||||
case IrOpcode::kCheckedTruncateTaggedToWord32:
|
case IrOpcode::kCheckedTruncateTaggedToWord32:
|
||||||
|
case IrOpcode::kAssertType:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IrOpcode::kCheckFloat64Hole:
|
case IrOpcode::kCheckFloat64Hole:
|
||||||
|
@ -318,6 +318,9 @@ DEFINE_BOOL(future, FUTURE_BOOL,
|
|||||||
|
|
||||||
DEFINE_IMPLICATION(future, write_protect_code_memory)
|
DEFINE_IMPLICATION(future, write_protect_code_memory)
|
||||||
|
|
||||||
|
DEFINE_BOOL(assert_types, false,
|
||||||
|
"generate runtime type assertions to test the typer")
|
||||||
|
|
||||||
// Flags for experimental implementation features.
|
// Flags for experimental implementation features.
|
||||||
DEFINE_BOOL(allocation_site_pretenuring, true,
|
DEFINE_BOOL(allocation_site_pretenuring, true,
|
||||||
"pretenure with allocation sites")
|
"pretenure with allocation sites")
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// Flags: --allow-natives-syntax
|
// Flags: --allow-natives-syntax --no-assert-types
|
||||||
|
|
||||||
// Check that constant-folding of arithmetic results in identical nodes.
|
// Check that constant-folding of arithmetic results in identical nodes.
|
||||||
(function() {
|
(function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user