// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "TableShape.h" namespace Luau { namespace Compile { // conservative limit for the loop bound that establishes table array size static const int kMaxLoopBound = 16; static AstExprTable* getTableHint(AstExpr* expr) { // unadorned table literal if (AstExprTable* table = expr->as()) return table; // setmetatable(table literal, ...) if (AstExprCall* call = expr->as(); call && !call->self && call->args.size == 2) if (AstExprGlobal* func = call->func->as(); func && func->name == "setmetatable") if (AstExprTable* table = call->args.data[0]->as()) return table; return nullptr; } struct ShapeVisitor : AstVisitor { struct Hasher { size_t operator()(const std::pair& p) const { return DenseHashPointer()(p.first) ^ std::hash()(p.second); } }; DenseHashMap& shapes; DenseHashMap tables; DenseHashSet, Hasher> fields; DenseHashMap loops; // iterator => upper bound for 1..k ShapeVisitor(DenseHashMap& shapes) : shapes(shapes) , tables(nullptr) , fields(std::pair()) , loops(nullptr) { } void assignField(AstExpr* expr, AstName index) { if (AstExprLocal* lv = expr->as()) { if (AstExprTable** table = tables.find(lv->local)) { std::pair field = {*table, index}; if (!fields.contains(field)) { fields.insert(field); shapes[*table].hashSize += 1; } } } } void assignField(AstExpr* expr, AstExpr* index) { AstExprLocal* lv = expr->as(); if (!lv) return; AstExprTable** table = tables.find(lv->local); if (!table) return; if (AstExprConstantNumber* number = index->as()) { TableShape& shape = shapes[*table]; if (number->value == double(shape.arraySize + 1)) shape.arraySize += 1; } else if (AstExprLocal* iter = index->as()) { if (const unsigned int* bound = loops.find(iter->local)) { TableShape& shape = shapes[*table]; if (shape.arraySize == 0) shape.arraySize = *bound; } } } void assign(AstExpr* var) { if (AstExprIndexName* index = var->as()) { assignField(index->expr, index->index); } else if (AstExprIndexExpr* index = var->as()) { assignField(index->expr, index->index); } } bool visit(AstStatLocal* node) override { // track local -> table association so that we can update table size prediction in assignField if (node->vars.size == 1 && node->values.size == 1) if (AstExprTable* table = getTableHint(node->values.data[0]); table && table->items.size == 0) tables[node->vars.data[0]] = table; return true; } bool visit(AstStatAssign* node) override { for (size_t i = 0; i < node->vars.size; ++i) assign(node->vars.data[i]); for (size_t i = 0; i < node->values.size; ++i) node->values.data[i]->visit(this); return false; } bool visit(AstStatFunction* node) override { assign(node->name); node->func->visit(this); return false; } bool visit(AstStatFor* node) override { AstExprConstantNumber* from = node->from->as(); AstExprConstantNumber* to = node->to->as(); if (from && to && from->value == 1.0 && to->value >= 1.0 && to->value <= double(kMaxLoopBound) && !node->step) loops[node->var] = unsigned(to->value); return true; } }; void predictTableShapes(DenseHashMap& shapes, AstNode* root) { ShapeVisitor visitor{shapes}; root->visit(&visitor); } } // namespace Compile } // namespace Luau