Commit cf5546ba authored by bradnelson's avatar bradnelson Committed by Commit bot

Make typing-asm match spec more closely around load/store, add more tests.

Shifts of integer values are in some contexts collapsed by the parser into single literal AST nodes, rather than a direct representation of the parse tree. Confirming this behavior in tests.

Integer TypedArrays are assumed to load and store "intish" values rather than more fine-grained type information. Reducing the precision of the typing information to match the spec and simplify the wasm generator.

The asm spec requires load and store values of various "float?", "floatish", "double?" and "intish" types to ensure undefined values are not visible and that float32 rounding occurs at the right time. More closely matching this.

Adding additional testing around unsigned / signed comparisons, loads and stores.

Adding addition debug mode printing when asserting about types fail.

BUG= https://code.google.com/p/v8/issues/detail?id=4203
TEST=test-asm-validator, wasm side tests
R=titzer@chromium.org,aseemgarg@chromium.org
LOG=N

Review URL: https://codereview.chromium.org/1471073003

Cr-Commit-Position: refs/heads/master@{#32419}
parent 000baddf
...@@ -58,6 +58,9 @@ class TypeCache final { ...@@ -58,6 +58,9 @@ class TypeCache final {
Type* const kSafeInteger = CreateRange(-kMaxSafeInteger, kMaxSafeInteger); Type* const kSafeInteger = CreateRange(-kMaxSafeInteger, kMaxSafeInteger);
Type* const kPositiveSafeInteger = CreateRange(0.0, kMaxSafeInteger); Type* const kPositiveSafeInteger = CreateRange(0.0, kMaxSafeInteger);
Type* const kUntaggedUndefined =
Type::Intersect(Type::Undefined(), Type::Untagged(), zone());
// Asm.js related types. // Asm.js related types.
Type* const kAsmSigned = kInt32; Type* const kAsmSigned = kInt32;
Type* const kAsmUnsigned = kUint32; Type* const kAsmUnsigned = kUint32;
...@@ -65,6 +68,12 @@ class TypeCache final { ...@@ -65,6 +68,12 @@ class TypeCache final {
Type* const kAsmFixnum = Type::Intersect(kAsmSigned, kAsmUnsigned, zone()); Type* const kAsmFixnum = Type::Intersect(kAsmSigned, kAsmUnsigned, zone());
Type* const kAsmFloat = kFloat32; Type* const kAsmFloat = kFloat32;
Type* const kAsmDouble = kFloat64; Type* const kAsmDouble = kFloat64;
Type* const kAsmFloatQ = Type::Union(kAsmFloat, kUntaggedUndefined, zone());
Type* const kAsmDoubleQ = Type::Union(kAsmDouble, kUntaggedUndefined, zone());
// Not part of the Asm.js type hierarchy, but represents a part of what
// intish encompasses.
Type* const kAsmIntQ = Type::Union(kAsmInt, kUntaggedUndefined, zone());
Type* const kAsmFloatDoubleQ = Type::Union(kAsmFloatQ, kAsmDoubleQ, zone());
// Asm.js size unions. // Asm.js size unions.
Type* const kAsmSize8 = Type::Union(kInt8, kUint8, zone()); Type* const kAsmSize8 = Type::Union(kInt8, kUint8, zone());
Type* const kAsmSize16 = Type::Union(kInt16, kUint16, zone()); Type* const kAsmSize16 = Type::Union(kInt16, kUint16, zone());
...@@ -77,6 +86,11 @@ class TypeCache final { ...@@ -77,6 +86,11 @@ class TypeCache final {
Type::Union(kAsmUnsigned, Type::Union(kAsmDouble, kAsmFloat, zone()), Type::Union(kAsmUnsigned, Type::Union(kAsmDouble, kAsmFloat, zone()),
zone()), zone()),
zone()); zone());
Type* const kAsmIntArrayElement =
Type::Union(Type::Union(kInt8, kUint8, zone()),
Type::Union(Type::Union(kInt16, kUint16, zone()),
Type::Union(kInt32, kUint32, zone()), zone()),
zone());
// The FixedArray::length property always containts a smi in the range // The FixedArray::length property always containts a smi in the range
// [0, FixedArray::kMaxLength]. // [0, FixedArray::kMaxLength].
......
...@@ -41,6 +41,7 @@ AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script, ...@@ -41,6 +41,7 @@ AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script,
script_(script), script_(script),
root_(root), root_(root),
valid_(true), valid_(true),
intish_(0),
stdlib_types_(zone), stdlib_types_(zone),
stdlib_heap_types_(zone), stdlib_heap_types_(zone),
stdlib_math_types_(zone), stdlib_math_types_(zone),
...@@ -155,7 +156,7 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) { ...@@ -155,7 +156,7 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) {
if (literal) { if (literal) {
RECURSE(VisitLiteral(literal, true)); RECURSE(VisitLiteral(literal, true));
} else { } else {
RECURSE(VisitExpressionAnnotation(stmt->expression(), true)); RECURSE(VisitExpressionAnnotation(stmt->expression(), NULL, true));
} }
expected_type_ = old_expected; expected_type_ = old_expected;
result_type = computed_type_; result_type = computed_type_;
...@@ -179,7 +180,7 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) { ...@@ -179,7 +180,7 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) {
Variable* var = proxy->var(); Variable* var = proxy->var();
if (var->location() != VariableLocation::PARAMETER || var->index() != i) if (var->location() != VariableLocation::PARAMETER || var->index() != i)
break; break;
RECURSE(VisitExpressionAnnotation(expr->value(), false)); RECURSE(VisitExpressionAnnotation(expr->value(), var, false));
SetType(var, computed_type_); SetType(var, computed_type_);
type->InitParameter(i, computed_type_); type->InitParameter(i, computed_type_);
good = true; good = true;
...@@ -190,10 +191,20 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) { ...@@ -190,10 +191,20 @@ void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) {
} }
void AsmTyper::VisitExpressionAnnotation(Expression* expr, bool is_return) { void AsmTyper::VisitExpressionAnnotation(Expression* expr, Variable* var,
bool is_return) {
// Normal +x or x|0 annotations. // Normal +x or x|0 annotations.
BinaryOperation* bin = expr->AsBinaryOperation(); BinaryOperation* bin = expr->AsBinaryOperation();
if (bin != NULL) { if (bin != NULL) {
if (var != NULL) {
VariableProxy* left = bin->left()->AsVariableProxy();
if (!left) {
FAIL(expr, "variable name expected in type annotation");
}
if (left->var() != var) {
FAIL(left, "variable type annotation references other variable");
}
}
Literal* right = bin->right()->AsLiteral(); Literal* right = bin->right()->AsLiteral();
if (right != NULL) { if (right != NULL) {
switch (bin->op()) { switch (bin->op()) {
...@@ -593,19 +604,23 @@ void AsmTyper::VisitAssignment(Assignment* expr) { ...@@ -593,19 +604,23 @@ void AsmTyper::VisitAssignment(Assignment* expr) {
Type* type = expected_type_; Type* type = expected_type_;
RECURSE(VisitWithExpectation( RECURSE(VisitWithExpectation(
expr->value(), type, "assignment value expected to match surrounding")); expr->value(), type, "assignment value expected to match surrounding"));
Type* target_type = StorageType(computed_type_);
if (intish_ != 0) { if (intish_ != 0) {
FAIL(expr, "value still an intish"); FAIL(expr, "intish or floatish assignment");
}
Type* target_type = computed_type_;
if (target_type->Is(cache_.kAsmInt)) {
target_type = cache_.kAsmInt;
} }
if (expr->target()->IsVariableProxy()) {
RECURSE(VisitWithExpectation(expr->target(), target_type, RECURSE(VisitWithExpectation(expr->target(), target_type,
"assignment target expected to match value")); "assignment target expected to match value"));
if (intish_ != 0) { } else if (expr->target()->IsProperty()) {
FAIL(expr, "value still an intish"); Property* property = expr->target()->AsProperty();
RECURSE(VisitWithExpectation(property->obj(), Type::Any(),
"bad propety object"));
if (!computed_type_->IsArray()) {
FAIL(property->obj(), "array expected");
} }
IntersectResult(expr, computed_type_); VisitHeapAccess(property, true, target_type);
}
IntersectResult(expr, target_type);
} }
...@@ -637,11 +652,15 @@ Type* AsmTyper::StorageType(Type* type) { ...@@ -637,11 +652,15 @@ Type* AsmTyper::StorageType(Type* type) {
} }
void AsmTyper::VisitHeapAccess(Property* expr) { void AsmTyper::VisitHeapAccess(Property* expr, bool assigning,
Type* assignment_type) {
Type::ArrayType* array_type = computed_type_->AsArray(); Type::ArrayType* array_type = computed_type_->AsArray();
size_t size = array_size_; size_t size = array_size_;
Type* type = array_type->AsArray()->Element(); Type* type = array_type->AsArray()->Element();
if (type->IsFunction()) { if (type->IsFunction()) {
if (assigning) {
FAIL(expr, "assigning to function table is illegal");
}
BinaryOperation* bin = expr->key()->AsBinaryOperation(); BinaryOperation* bin = expr->key()->AsBinaryOperation();
if (bin == NULL || bin->op() != Token::BIT_AND) { if (bin == NULL || bin->op() != Token::BIT_AND) {
FAIL(expr->key(), "expected & in call"); FAIL(expr->key(), "expected & in call");
...@@ -658,6 +677,7 @@ void AsmTyper::VisitHeapAccess(Property* expr) { ...@@ -658,6 +677,7 @@ void AsmTyper::VisitHeapAccess(Property* expr) {
FAIL(right, "call mask must match function table"); FAIL(right, "call mask must match function table");
} }
bin->set_bounds(Bounds(cache_.kAsmSigned)); bin->set_bounds(Bounds(cache_.kAsmSigned));
IntersectResult(expr, type);
} else { } else {
Literal* literal = expr->key()->AsLiteral(); Literal* literal = expr->key()->AsLiteral();
if (literal) { if (literal) {
...@@ -683,8 +703,39 @@ void AsmTyper::VisitHeapAccess(Property* expr) { ...@@ -683,8 +703,39 @@ void AsmTyper::VisitHeapAccess(Property* expr) {
} }
bin->set_bounds(Bounds(cache_.kAsmSigned)); bin->set_bounds(Bounds(cache_.kAsmSigned));
} }
Type* result_type;
if (type->Is(cache_.kAsmIntArrayElement)) {
result_type = cache_.kAsmIntQ;
intish_ = kMaxUncombinedAdditiveSteps;
} else if (type->Is(cache_.kAsmFloat)) {
if (assigning) {
result_type = cache_.kAsmFloatDoubleQ;
} else {
result_type = cache_.kAsmFloatQ;
}
intish_ = 0;
} else if (type->Is(cache_.kAsmDouble)) {
if (assigning) {
result_type = cache_.kAsmFloatDoubleQ;
if (intish_ != 0) {
FAIL(expr, "Assignment of floatish to Float64Array");
}
} else {
result_type = cache_.kAsmDoubleQ;
}
intish_ = 0;
} else {
UNREACHABLE();
}
if (assigning) {
if (!assignment_type->Is(result_type)) {
FAIL(expr, "illegal type in assignment");
}
} else {
IntersectResult(expr, expected_type_);
IntersectResult(expr, result_type);
}
} }
IntersectResult(expr, type);
} }
...@@ -720,12 +771,11 @@ void AsmTyper::VisitProperty(Property* expr) { ...@@ -720,12 +771,11 @@ void AsmTyper::VisitProperty(Property* expr) {
// Only recurse at this point so that we avoid needing // Only recurse at this point so that we avoid needing
// stdlib.Math to have a real type. // stdlib.Math to have a real type.
RECURSE(VisitWithExpectation(expr->obj(), Type::Any(), RECURSE(VisitWithExpectation(expr->obj(), Type::Any(), "bad propety object"));
"property holder expected to be object"));
// For heap view or function table access. // For heap view or function table access.
if (computed_type_->IsArray()) { if (computed_type_->IsArray()) {
VisitHeapAccess(expr); VisitHeapAccess(expr, false, NULL);
return; return;
} }
...@@ -780,6 +830,7 @@ void AsmTyper::VisitCall(Call* expr) { ...@@ -780,6 +830,7 @@ void AsmTyper::VisitCall(Call* expr) {
arg, fun_type->Parameter(i), arg, fun_type->Parameter(i),
"call argument expected to match callee parameter")); "call argument expected to match callee parameter"));
} }
intish_ = 0;
IntersectResult(expr, fun_type->Result()); IntersectResult(expr, fun_type->Result());
} else if (computed_type_->Is(Type::Any())) { } else if (computed_type_->Is(Type::Any())) {
// For foreign calls. // For foreign calls.
...@@ -789,6 +840,7 @@ void AsmTyper::VisitCall(Call* expr) { ...@@ -789,6 +840,7 @@ void AsmTyper::VisitCall(Call* expr) {
RECURSE(VisitWithExpectation(arg, Type::Any(), RECURSE(VisitWithExpectation(arg, Type::Any(),
"foreign call argument expected to be any")); "foreign call argument expected to be any"));
} }
intish_ = kMaxUncombinedAdditiveSteps;
IntersectResult(expr, Type::Number()); IntersectResult(expr, Type::Number());
} else { } else {
FAIL(expr, "invalid callee"); FAIL(expr, "invalid callee");
...@@ -994,13 +1046,17 @@ void AsmTyper::VisitBinaryOperation(BinaryOperation* expr) { ...@@ -994,13 +1046,17 @@ void AsmTyper::VisitBinaryOperation(BinaryOperation* expr) {
IntersectResult(expr, cache_.kAsmInt); IntersectResult(expr, cache_.kAsmInt);
return; return;
} }
} else if (expr->op() == Token::MUL && left_type->Is(cache_.kAsmInt) && } else if (expr->op() == Token::MUL && expr->right()->IsLiteral() &&
right_type->Is(cache_.kAsmDouble)) { right_type->Is(cache_.kAsmDouble)) {
// For unary +, expressed as x * 1.0 // For unary +, expressed as x * 1.0
IntersectResult(expr, cache_.kAsmDouble); IntersectResult(expr, cache_.kAsmDouble);
return; return;
} else if (type->Is(cache_.kAsmFloat) && expr->op() != Token::MOD) { } else if (type->Is(cache_.kAsmFloat) && expr->op() != Token::MOD) {
if (left_intish != 0 || right_intish != 0) {
FAIL(expr, "float operation before required fround");
}
IntersectResult(expr, cache_.kAsmFloat); IntersectResult(expr, cache_.kAsmFloat);
intish_ = 1;
return; return;
} else if (type->Is(cache_.kAsmDouble)) { } else if (type->Is(cache_.kAsmDouble)) {
IntersectResult(expr, cache_.kAsmDouble); IntersectResult(expr, cache_.kAsmDouble);
......
...@@ -65,11 +65,11 @@ class AsmTyper : public AstVisitor { ...@@ -65,11 +65,11 @@ class AsmTyper : public AstVisitor {
void VisitDeclarations(ZoneList<Declaration*>* d) override; void VisitDeclarations(ZoneList<Declaration*>* d) override;
void VisitStatements(ZoneList<Statement*>* s) override; void VisitStatements(ZoneList<Statement*>* s) override;
void VisitExpressionAnnotation(Expression* e, bool is_return); void VisitExpressionAnnotation(Expression* e, Variable* var, bool is_return);
void VisitFunctionAnnotation(FunctionLiteral* f); void VisitFunctionAnnotation(FunctionLiteral* f);
void VisitAsmModule(FunctionLiteral* f); void VisitAsmModule(FunctionLiteral* f);
void VisitHeapAccess(Property* expr); void VisitHeapAccess(Property* expr, bool assigning, Type* assignment_type);
int ElementShiftSize(Type* type); int ElementShiftSize(Type* type);
Type* StorageType(Type* type); Type* StorageType(Type* type);
......
...@@ -14,11 +14,30 @@ ...@@ -14,11 +14,30 @@
CHECK_EQ(index, types.size()); \ CHECK_EQ(index, types.size()); \
} }
#ifdef DEBUG
#define CHECK_TYPE(type) \
if (!types[index].bounds.Narrows(type)) { \
fprintf(stderr, "Expected:\n"); \
fprintf(stderr, " lower: "); \
type.lower->Print(); \
fprintf(stderr, " upper: "); \
type.upper->Print(); \
fprintf(stderr, "Actual:\n"); \
fprintf(stderr, " lower: "); \
types[index].bounds.lower->Print(); \
fprintf(stderr, " upper: "); \
types[index].bounds.upper->Print(); \
} \
CHECK(types[index].bounds.Narrows(type));
#else
#define CHECK_TYPE(type) CHECK(types[index].bounds.Narrows(type));
#endif
#define CHECK_EXPR(ekind, type) \ #define CHECK_EXPR(ekind, type) \
CHECK_LT(index, types.size()); \ CHECK_LT(index, types.size()); \
CHECK(strcmp(#ekind, types[index].kind) == 0); \ CHECK(strcmp(#ekind, types[index].kind) == 0); \
CHECK_EQ(depth, types[index].depth); \ CHECK_EQ(depth, types[index].depth); \
CHECK(types[index].bounds.Narrows(type)); \ CHECK_TYPE(type); \
for (int j = (++depth, ++index, 0); j < 1 ? 1 : (--depth, 0); ++j) for (int j = (++depth, ++index, 0); j < 1 ? 1 : (--depth, 0); ++j)
#define CHECK_VAR(vname, type) \ #define CHECK_VAR(vname, type) \
......
This diff is collapsed.
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