[wasm] asm.js - Parse and convert asm.js to wasm a function at a time.

Make the AsmWasmBuilder drive the process of typing and potentially parsing
function bodies. This will allow us to keep only a single asm.js function's
AST in memory as we convert to WebAssembly.
This is needed to keep our memory footprint low.

Add some additional output to a few tests that's helpful to see which stage they fail at.

BUG= https://bugs.chromium.org/p/v8/issues/detail?id=4203
LOG=N
R=marja@chromium.org,adamk@chromium.org,aseemgarg@chromium.org,titzer@chromium.org

Review-Url: https://codereview.chromium.org/2398023002
Cr-Commit-Position: refs/heads/master@{#41372}
This commit is contained in:
bradnelson 2016-11-29 16:25:21 -08:00 committed by Commit bot
parent d385ed069b
commit 14e05c1046
17 changed files with 447 additions and 233 deletions

View File

@ -153,27 +153,27 @@ bool IsStdlibMemberValid(i::Isolate* isolate, Handle<JSReceiver> stdlib,
MaybeHandle<FixedArray> AsmJs::ConvertAsmToWasm(ParseInfo* info) {
ErrorThrower thrower(info->isolate(), "Asm.js -> WebAssembly conversion");
wasm::AsmTyper typer(info->isolate(), info->zone(), *(info->script()),
info->literal());
if (!typer.Validate()) {
wasm::AsmWasmBuilder builder(info->isolate(), info->zone(),
info->ast_value_factory(), *info->script(),
info->literal());
Handle<FixedArray> foreign_globals;
auto asm_wasm_result = builder.Run(&foreign_globals);
if (!asm_wasm_result.success) {
DCHECK(!info->isolate()->has_pending_exception());
PrintF("Validation of asm.js module failed: %s\n", typer.error_message());
PrintF("Validation of asm.js module failed: %s\n",
builder.typer()->error_message());
return MaybeHandle<FixedArray>();
}
v8::internal::wasm::AsmWasmBuilder builder(info->isolate(), info->zone(),
info->literal(), &typer);
i::Handle<i::FixedArray> foreign_globals;
auto asm_wasm_result = builder.Run(&foreign_globals);
wasm::ZoneBuffer* module = asm_wasm_result.module_bytes;
wasm::ZoneBuffer* asm_offsets = asm_wasm_result.asm_offset_table;
i::MaybeHandle<i::JSObject> compiled = wasm::CreateModuleObjectFromBytes(
MaybeHandle<JSObject> compiled = wasm::CreateModuleObjectFromBytes(
info->isolate(), module->begin(), module->end(), &thrower,
internal::wasm::kAsmJsOrigin, info->script(), asm_offsets->begin(),
asm_offsets->end());
DCHECK(!compiled.is_null());
wasm::AsmTyper::StdlibSet uses = typer.StdlibUses();
wasm::AsmTyper::StdlibSet uses = builder.typer()->StdlibUses();
Handle<FixedArray> uses_array =
info->isolate()->factory()->NewFixedArray(static_cast<int>(uses.size()));
int count = 0;

View File

@ -19,14 +19,19 @@
#include "src/globals.h"
#include "src/utils.h"
#define FAIL_LINE(line, msg) \
do { \
base::OS::SNPrintF(error_message_, sizeof(error_message_), \
"asm: line %d: %s", (line) + 1, msg); \
return AsmType::None(); \
} while (false)
#define FAIL(node, msg) \
do { \
int line = node->position() == kNoSourcePosition \
? -1 \
: script_->GetLineNumber(node->position()); \
base::OS::SNPrintF(error_message_, sizeof(error_message_), \
"asm: line %d: %s", line + 1, msg); \
return AsmType::None(); \
FAIL_LINE(line, msg); \
} while (false)
#define RECURSE(call) \
@ -90,6 +95,53 @@ Statement* AsmTyper::FlattenedStatements::Next() {
}
}
// ----------------------------------------------------------------------------
// Implementation of AsmTyper::SourceLayoutTracker
bool AsmTyper::SourceLayoutTracker::IsValid() const {
const Section* kAllSections[] = {&use_asm_, &globals_, &functions_, &tables_,
&exports_};
for (size_t ii = 0; ii < arraysize(kAllSections); ++ii) {
const auto& curr_section = *kAllSections[ii];
for (size_t jj = ii + 1; jj < arraysize(kAllSections); ++jj) {
if (curr_section.IsPrecededBy(*kAllSections[jj])) {
return false;
}
}
}
return true;
}
void AsmTyper::SourceLayoutTracker::Section::AddNewElement(
const AstNode& node) {
const int node_pos = node.position();
if (start_ == kNoSourcePosition) {
start_ = node_pos;
} else {
start_ = std::min(start_, node_pos);
}
if (end_ == kNoSourcePosition) {
end_ = node_pos;
} else {
end_ = std::max(end_, node_pos);
}
}
bool AsmTyper::SourceLayoutTracker::Section::IsPrecededBy(
const Section& other) const {
if (start_ == kNoSourcePosition) {
DCHECK_EQ(end_, kNoSourcePosition);
return false;
}
if (other.start_ == kNoSourcePosition) {
DCHECK_EQ(other.end_, kNoSourcePosition);
return false;
}
DCHECK_LE(start_, end_);
DCHECK_LE(other.start_, other.end_);
return other.start_ <= end_;
}
// ----------------------------------------------------------------------------
// Implementation of AsmTyper::VariableInfo
@ -112,10 +164,10 @@ AsmTyper::VariableInfo* AsmTyper::VariableInfo::Clone(Zone* zone) const {
return new_var_info;
}
void AsmTyper::VariableInfo::FirstForwardUseIs(VariableProxy* var) {
DCHECK(first_forward_use_ == nullptr);
void AsmTyper::VariableInfo::SetFirstForwardUse(int source_location) {
DCHECK(source_location_ == -1);
missing_definition_ = true;
first_forward_use_ = var;
source_location_ = source_location;
}
// ----------------------------------------------------------------------------
@ -137,9 +189,11 @@ AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script,
local_scope_(ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone)),
stack_limit_(isolate->stack_guard()->real_climit()),
node_types_(zone_),
module_node_types_(zone_),
function_node_types_(zone_),
fround_type_(AsmType::FroundType(zone_)),
ffi_type_(AsmType::FFIType(zone_)) {
ffi_type_(AsmType::FFIType(zone_)),
function_pointer_tables_(zone_) {
InitializeStdlib();
}
@ -345,7 +399,9 @@ AsmTyper::VariableInfo* AsmTyper::Lookup(Variable* variable) const {
}
void AsmTyper::AddForwardReference(VariableProxy* proxy, VariableInfo* info) {
info->FirstForwardUseIs(proxy);
info->SetFirstForwardUse(proxy->position() == kNoSourcePosition
? -1
: script_->GetLineNumber(proxy->position()));
forward_definitions_.push_back(info);
}
@ -390,13 +446,22 @@ bool AsmTyper::AddLocal(Variable* variable, VariableInfo* info) {
void AsmTyper::SetTypeOf(AstNode* node, AsmType* type) {
DCHECK_NE(type, AsmType::None());
DCHECK(node_types_.find(node) == node_types_.end());
node_types_.insert(std::make_pair(node, type));
if (in_function_) {
DCHECK(function_node_types_.find(node) == function_node_types_.end());
function_node_types_.insert(std::make_pair(node, type));
} else {
DCHECK(module_node_types_.find(node) == module_node_types_.end());
module_node_types_.insert(std::make_pair(node, type));
}
}
AsmType* AsmTyper::TypeOf(AstNode* node) const {
auto node_type_iter = node_types_.find(node);
if (node_type_iter != node_types_.end()) {
auto node_type_iter = function_node_types_.find(node);
if (node_type_iter != function_node_types_.end()) {
return node_type_iter->second;
}
node_type_iter = module_node_types_.find(node);
if (node_type_iter != module_node_types_.end()) {
return node_type_iter->second;
}
@ -434,12 +499,34 @@ AsmTyper::StandardMember AsmTyper::VariableAsStandardMember(Variable* var) {
}
bool AsmTyper::Validate() {
if (!AsmType::None()->IsExactly(ValidateModule(root_))) {
return ValidateBeforeFunctionsPhase() &&
!AsmType::None()->IsExactly(ValidateModuleFunctions(root_)) &&
ValidateAfterFunctionsPhase();
}
bool AsmTyper::ValidateBeforeFunctionsPhase() {
if (!AsmType::None()->IsExactly(ValidateModuleBeforeFunctionsPhase(root_))) {
return true;
}
return false;
}
bool AsmTyper::ValidateInnerFunction(FunctionDeclaration* fun_decl) {
if (!AsmType::None()->IsExactly(ValidateModuleFunction(fun_decl))) {
return true;
}
return false;
}
bool AsmTyper::ValidateAfterFunctionsPhase() {
if (!AsmType::None()->IsExactly(ValidateModuleAfterFunctionsPhase(root_))) {
return true;
}
return false;
}
void AsmTyper::ClearFunctionNodeTypes() { function_node_types_.clear(); }
namespace {
bool IsUseAsmDirective(Statement* first_statement) {
ExpressionStatement* use_asm = first_statement->AsExpressionStatement();
@ -477,89 +564,7 @@ Assignment* ExtractInitializerExpression(Statement* statement) {
} // namespace
// 6.1 ValidateModule
namespace {
// SourceLayoutTracker keeps track of the start and end positions of each
// section in the asm.js source. The sections should not overlap, otherwise the
// asm.js source is invalid.
class SourceLayoutTracker {
public:
SourceLayoutTracker() = default;
bool IsValid() const {
const Section* kAllSections[] = {&use_asm_, &globals_, &functions_,
&tables_, &exports_};
for (size_t ii = 0; ii < arraysize(kAllSections); ++ii) {
const auto& curr_section = *kAllSections[ii];
for (size_t jj = ii + 1; jj < arraysize(kAllSections); ++jj) {
if (curr_section.OverlapsWith(*kAllSections[jj])) {
return false;
}
}
}
return true;
}
void AddUseAsm(const AstNode& node) { use_asm_.AddNewElement(node); }
void AddGlobal(const AstNode& node) { globals_.AddNewElement(node); }
void AddFunction(const AstNode& node) { functions_.AddNewElement(node); }
void AddTable(const AstNode& node) { tables_.AddNewElement(node); }
void AddExport(const AstNode& node) { exports_.AddNewElement(node); }
private:
class Section {
public:
Section() = default;
Section(const Section&) = default;
Section& operator=(const Section&) = default;
void AddNewElement(const AstNode& node) {
const int node_pos = node.position();
if (start_ == kNoSourcePosition) {
start_ = node_pos;
} else {
start_ = std::max(start_, node_pos);
}
if (end_ == kNoSourcePosition) {
end_ = node_pos;
} else {
end_ = std::max(end_, node_pos);
}
}
bool OverlapsWith(const Section& other) const {
if (start_ == kNoSourcePosition) {
DCHECK_EQ(end_, kNoSourcePosition);
return false;
}
if (other.start_ == kNoSourcePosition) {
DCHECK_EQ(other.end_, kNoSourcePosition);
return false;
}
return other.start_ < end_ || other.end_ < start_;
}
private:
int start_ = kNoSourcePosition;
int end_ = kNoSourcePosition;
};
Section use_asm_;
Section globals_;
Section functions_;
Section tables_;
Section exports_;
DISALLOW_COPY_AND_ASSIGN(SourceLayoutTracker);
};
} // namespace
AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
SourceLayoutTracker source_layout;
AsmType* AsmTyper::ValidateModuleBeforeFunctionsPhase(FunctionLiteral* fun) {
DeclarationScope* scope = fun->scope();
if (!scope->is_function_scope()) FAIL(fun, "Not at function scope.");
if (!ValidAsmIdentifier(fun->name()))
@ -594,7 +599,6 @@ AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
}
}
ZoneVector<Assignment*> function_pointer_tables(zone_);
FlattenedStatements iter(zone_, fun->body());
auto* use_asm_directive = iter.Next();
if (use_asm_directive == nullptr) {
@ -616,8 +620,8 @@ AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
if (!IsUseAsmDirective(use_asm_directive)) {
FAIL(fun, "Missing \"use asm\".");
}
source_layout.AddUseAsm(*use_asm_directive);
ReturnStatement* module_return = nullptr;
source_layout_.AddUseAsm(*use_asm_directive);
module_return_ = nullptr;
// *VIOLATION* The spec states that globals should be followed by function
// declarations, which should be followed by function pointer tables, followed
@ -627,40 +631,57 @@ AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
if (auto* assign = ExtractInitializerExpression(current)) {
if (assign->value()->IsArrayLiteral()) {
// Save function tables for later validation.
function_pointer_tables.push_back(assign);
function_pointer_tables_.push_back(assign);
} else {
RECURSE(ValidateGlobalDeclaration(assign));
source_layout.AddGlobal(*assign);
source_layout_.AddGlobal(*assign);
}
continue;
}
if (auto* current_as_return = current->AsReturnStatement()) {
if (module_return != nullptr) {
if (module_return_ != nullptr) {
FAIL(fun, "Multiple export statements.");
}
module_return = current_as_return;
source_layout.AddExport(*module_return);
module_return_ = current_as_return;
source_layout_.AddExport(*module_return_);
continue;
}
FAIL(current, "Invalid top-level statement in asm.js module.");
}
return AsmType::Int(); // Any type that is not AsmType::None();
}
AsmType* AsmTyper::ValidateModuleFunction(FunctionDeclaration* fun_decl) {
RECURSE(ValidateFunction(fun_decl));
source_layout_.AddFunction(*fun_decl);
return AsmType::Int(); // Any type that is not AsmType::None();
}
AsmType* AsmTyper::ValidateModuleFunctions(FunctionLiteral* fun) {
DeclarationScope* scope = fun->scope();
Declaration::List* decls = scope->declarations();
for (Declaration* decl : *decls) {
if (FunctionDeclaration* fun_decl = decl->AsFunctionDeclaration()) {
RECURSE(ValidateFunction(fun_decl));
source_layout.AddFunction(*fun_decl);
RECURSE(ValidateModuleFunction(fun_decl));
continue;
}
}
for (auto* function_table : function_pointer_tables) {
return AsmType::Int(); // Any type that is not AsmType::None();
}
AsmType* AsmTyper::ValidateModuleAfterFunctionsPhase(FunctionLiteral* fun) {
for (auto* function_table : function_pointer_tables_) {
RECURSE(ValidateFunctionTable(function_table));
source_layout.AddTable(*function_table);
source_layout_.AddTable(*function_table);
}
DeclarationScope* scope = fun->scope();
Declaration::List* decls = scope->declarations();
for (Declaration* decl : *decls) {
if (decl->IsFunctionDeclaration()) {
continue;
@ -682,20 +703,20 @@ AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
}
// 6.2 ValidateExport
if (module_return == nullptr) {
if (module_return_ == nullptr) {
FAIL(fun, "Missing asm.js module export.");
}
for (auto* forward_def : forward_definitions_) {
if (forward_def->missing_definition()) {
FAIL(forward_def->first_forward_use(),
"Missing definition for forward declared identifier.");
FAIL_LINE(forward_def->source_location(),
"Missing definition for forward declared identifier.");
}
}
RECURSE(ValidateExport(module_return));
RECURSE(ValidateExport(module_return_));
if (!source_layout.IsValid()) {
if (!source_layout_.IsValid()) {
FAIL(fun, "Invalid asm.js source code layout.");
}

View File

@ -25,6 +25,7 @@ namespace wasm {
class AsmType;
class AsmTyperHarnessBuilder;
class SourceLayoutTracker;
class AsmTyper final {
public:
@ -69,6 +70,11 @@ class AsmTyper final {
AsmTyper(Isolate* isolate, Zone* zone, Script* script, FunctionLiteral* root);
bool Validate();
// Do asm.js validation in phases (to interleave with conversion to wasm).
bool ValidateBeforeFunctionsPhase();
bool ValidateInnerFunction(FunctionDeclaration* decl);
bool ValidateAfterFunctionsPhase();
void ClearFunctionNodeTypes();
const char* error_message() const { return error_message_; }
@ -130,7 +136,7 @@ class AsmTyper final {
bool IsHeap() const { return standard_member_ == kHeap; }
void MarkDefined() { missing_definition_ = false; }
void FirstForwardUseIs(VariableProxy* var);
void SetFirstForwardUse(int source_location);
StandardMember standard_member() const { return standard_member_; }
void set_standard_member(StandardMember standard_member) {
@ -145,7 +151,7 @@ class AsmTyper final {
bool missing_definition() const { return missing_definition_; }
VariableProxy* first_forward_use() const { return first_forward_use_; }
int source_location() const { return source_location_; }
static VariableInfo* ForSpecialSymbol(Zone* zone,
StandardMember standard_member);
@ -157,9 +163,11 @@ class AsmTyper final {
// missing_definition_ is set to true for forward definition - i.e., use
// before definition.
bool missing_definition_ = false;
// first_forward_use_ holds the AST node that first referenced this
// source_location_ holds the line number that first referenced this
// VariableInfo. Used for error messages.
VariableProxy* first_forward_use_ = nullptr;
// TODO(bradnelson): When merged with console change, this should
// become a source location.
int source_location_ = -1;
};
// RAII-style manager for the in_function_ member variable.
@ -199,6 +207,40 @@ class AsmTyper final {
DISALLOW_IMPLICIT_CONSTRUCTORS(FlattenedStatements);
};
class SourceLayoutTracker {
public:
SourceLayoutTracker() = default;
bool IsValid() const;
void AddUseAsm(const AstNode& node) { use_asm_.AddNewElement(node); }
void AddGlobal(const AstNode& node) { globals_.AddNewElement(node); }
void AddFunction(const AstNode& node) { functions_.AddNewElement(node); }
void AddTable(const AstNode& node) { tables_.AddNewElement(node); }
void AddExport(const AstNode& node) { exports_.AddNewElement(node); }
private:
class Section {
public:
Section() = default;
Section(const Section&) = default;
Section& operator=(const Section&) = default;
void AddNewElement(const AstNode& node);
bool IsPrecededBy(const Section& other) const;
private:
int start_ = kNoSourcePosition;
int end_ = kNoSourcePosition;
};
Section use_asm_;
Section globals_;
Section functions_;
Section tables_;
Section exports_;
DISALLOW_COPY_AND_ASSIGN(SourceLayoutTracker);
};
using ObjectTypeMap = ZoneMap<std::string, VariableInfo*>;
void InitializeStdlib();
void SetTypeOf(AstNode* node, AsmType* type);
@ -220,7 +262,10 @@ class AsmTyper final {
// validation failure.
// 6.1 ValidateModule
AsmType* ValidateModule(FunctionLiteral* fun);
AsmType* ValidateModuleBeforeFunctionsPhase(FunctionLiteral* fun);
AsmType* ValidateModuleFunction(FunctionDeclaration* fun_decl);
AsmType* ValidateModuleFunctions(FunctionLiteral* fun);
AsmType* ValidateModuleAfterFunctionsPhase(FunctionLiteral* fun);
AsmType* ValidateGlobalDeclaration(Assignment* assign);
// 6.2 ValidateExport
AsmType* ExportType(VariableProxy* fun_export);
@ -345,13 +390,18 @@ class AsmTyper final {
std::uintptr_t stack_limit_;
bool stack_overflow_ = false;
ZoneMap<AstNode*, AsmType*> node_types_;
ZoneMap<AstNode*, AsmType*> module_node_types_;
ZoneMap<AstNode*, AsmType*> function_node_types_;
static const int kErrorMessageLimit = 128;
AsmType* fround_type_;
AsmType* ffi_type_;
char error_message_[kErrorMessageLimit];
StdlibSet stdlib_uses_;
SourceLayoutTracker source_layout_;
ReturnStatement* module_return_;
ZoneVector<Assignment*> function_pointer_tables_;
DISALLOW_IMPLICIT_CONSTRUCTORS(AsmTyper);
};

View File

@ -19,6 +19,10 @@
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/codegen.h"
#include "src/compiler.h"
#include "src/isolate.h"
#include "src/parsing/parse-info.h"
namespace v8 {
namespace internal {
@ -42,8 +46,9 @@ struct ForeignVariable {
class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
public:
AsmWasmBuilderImpl(Isolate* isolate, Zone* zone, FunctionLiteral* literal,
AsmTyper* typer)
AsmWasmBuilderImpl(Isolate* isolate, Zone* zone,
AstValueFactory* ast_value_factory, Script* script,
FunctionLiteral* literal, AsmTyper* typer)
: local_variables_(ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone)),
functions_(ZoneHashMap::kDefaultHashMapCapacity,
@ -56,12 +61,14 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
literal_(literal),
isolate_(isolate),
zone_(zone),
ast_value_factory_(ast_value_factory),
script_(script),
typer_(typer),
typer_failed_(false),
breakable_blocks_(zone),
foreign_variables_(zone),
init_function_(nullptr),
foreign_init_function_(nullptr),
next_table_index_(0),
function_tables_(ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone)),
imported_function_table_(this) {
@ -92,8 +99,8 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
}
}
i::Handle<i::FixedArray> GetForeignArgs() {
i::Handle<FixedArray> ret = isolate_->factory()->NewFixedArray(
Handle<FixedArray> GetForeignArgs() {
Handle<FixedArray> ret = isolate_->factory()->NewFixedArray(
static_cast<int>(foreign_variables_.size()));
for (size_t i = 0; i < foreign_variables_.size(); ++i) {
ForeignVariable* fv = &foreign_variables_[i];
@ -102,10 +109,21 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
return ret;
}
void Build() {
bool Build() {
InitializeInitFunction();
RECURSE(VisitFunctionLiteral(literal_));
if (!typer_->ValidateBeforeFunctionsPhase()) {
return false;
}
DCHECK(!HasStackOverflow());
VisitFunctionLiteral(literal_);
if (HasStackOverflow()) {
return false;
}
if (typer_failed_) {
return false;
}
BuildForeignInitFunction();
return true;
}
void VisitVariableDeclaration(VariableDeclaration* decl) {}
@ -113,12 +131,62 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
void VisitFunctionDeclaration(FunctionDeclaration* decl) {
DCHECK_EQ(kModuleScope, scope_);
DCHECK_NULL(current_function_builder_);
FunctionLiteral* old_func = decl->fun();
Zone zone(isolate_->allocator(), ZONE_NAME);
DeclarationScope* new_func_scope = nullptr;
if (decl->fun()->body() == nullptr) {
// TODO(bradnelson): Refactor parser so we don't need a
// SharedFunctionInfo to parse a single function,
// or squirrel away the SharedFunctionInfo to use later.
Handle<SharedFunctionInfo> shared =
isolate_->factory()->NewSharedFunctionInfoForLiteral(
decl->fun(), handle(script_, isolate_));
shared->set_is_toplevel(false);
ParseInfo info(&zone, handle(script_, isolate_));
info.set_shared_info(shared);
info.set_toplevel(false);
info.set_language_mode(decl->fun()->scope()->language_mode());
info.set_allow_lazy_parsing(false);
info.set_function_literal_id(shared->function_literal_id());
info.set_ast_value_factory(ast_value_factory_);
info.set_ast_value_factory_owned(false);
// Create fresh function scope to use to parse the function in.
new_func_scope = new (info.zone()) DeclarationScope(
info.zone(), decl->fun()->scope()->outer_scope(), FUNCTION_SCOPE);
info.set_asm_function_scope(new_func_scope);
if (!Compiler::ParseAndAnalyze(&info)) {
typer_failed_ = true;
return;
}
FunctionLiteral* func = info.literal();
DCHECK_NOT_NULL(func);
decl->set_fun(func);
}
if (!typer_->ValidateInnerFunction(decl)) {
typer_failed_ = true;
decl->set_fun(old_func);
if (new_func_scope != nullptr) {
DCHECK_EQ(new_func_scope, decl->scope()->inner_scope());
if (!decl->scope()->RemoveInnerScope(new_func_scope)) {
UNREACHABLE();
}
}
return;
}
current_function_builder_ = LookupOrInsertFunction(decl->proxy()->var());
scope_ = kFuncScope;
RECURSE(Visit(decl->fun()));
decl->set_fun(old_func);
if (new_func_scope != nullptr) {
DCHECK_EQ(new_func_scope, decl->scope()->inner_scope());
if (!decl->scope()->RemoveInnerScope(new_func_scope)) {
UNREACHABLE();
}
}
scope_ = kModuleScope;
current_function_builder_ = nullptr;
local_variables_.Clear();
typer_->ClearFunctionNodeTypes();
}
void VisitStatements(ZoneList<Statement*>* stmts) {
@ -129,6 +197,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
continue;
}
RECURSE(Visit(stmt));
if (typer_failed_) break;
if (stmt->IsJump()) break;
}
}
@ -245,6 +314,10 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
void VisitReturnStatement(ReturnStatement* stmt) {
if (scope_ == kModuleScope) {
if (!typer_->ValidateAfterFunctionsPhase()) {
typer_failed_ = true;
return;
}
scope_ = kExportScope;
RECURSE(Visit(stmt->expression()));
scope_ = kModuleScope;
@ -448,8 +521,9 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
UNREACHABLE();
}
}
RECURSE(VisitStatements(expr->body()));
RECURSE(VisitDeclarations(scope->declarations()));
if (typer_failed_) return;
RECURSE(VisitStatements(expr->body()));
}
void VisitNativeFunctionLiteral(NativeFunctionLiteral* expr) {
@ -660,10 +734,22 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
current_function_builder_ = nullptr;
}
void AddFunctionTable(VariableProxy* table, ArrayLiteral* funcs) {
auto* func_tbl_type = typer_->TypeOf(funcs)->AsFunctionTableType();
DCHECK_NOT_NULL(func_tbl_type);
auto* func_type = func_tbl_type->signature()->AsFunctionType();
struct FunctionTableIndices : public ZoneObject {
uint32_t start_index;
uint32_t signature_index;
};
FunctionTableIndices* LookupOrAddFunctionTable(VariableProxy* table,
Property* p) {
FunctionTableIndices* indices = LookupFunctionTable(table->var());
if (indices != nullptr) {
// Already setup.
return indices;
}
indices = new (zone()) FunctionTableIndices();
auto* func_type = typer_->TypeOf(p)->AsFunctionType();
auto* func_table_type = typer_->TypeOf(p->obj()->AsVariableProxy()->var())
->AsFunctionTableType();
const auto& arguments = func_type->Arguments();
LocalType return_type = TypeFrom(func_type->ReturnType());
FunctionSig::Builder sig(zone(), return_type == kAstStmt ? 0 : 1,
@ -675,38 +761,37 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
sig.AddParam(TypeFrom(arg));
}
uint32_t signature_index = builder_->AddSignature(sig.Build());
InsertFunctionTable(table->var(), next_table_index_, signature_index);
next_table_index_ += funcs->values()->length();
for (int i = 0; i < funcs->values()->length(); ++i) {
VariableProxy* func = funcs->values()->at(i)->AsVariableProxy();
DCHECK_NOT_NULL(func);
builder_->AddIndirectFunction(
LookupOrInsertFunction(func->var())->func_index());
}
}
struct FunctionTableIndices : public ZoneObject {
uint32_t start_index;
uint32_t signature_index;
};
void InsertFunctionTable(Variable* v, uint32_t start_index,
uint32_t signature_index) {
FunctionTableIndices* container = new (zone()) FunctionTableIndices();
container->start_index = start_index;
container->signature_index = signature_index;
indices->start_index = builder_->AllocateIndirectFunctions(
static_cast<uint32_t>(func_table_type->length()));
indices->signature_index = signature_index;
ZoneHashMap::Entry* entry = function_tables_.LookupOrInsert(
v, ComputePointerHash(v), ZoneAllocationPolicy(zone()));
entry->value = container;
table->var(), ComputePointerHash(table->var()),
ZoneAllocationPolicy(zone()));
entry->value = indices;
return indices;
}
FunctionTableIndices* LookupFunctionTable(Variable* v) {
ZoneHashMap::Entry* entry =
function_tables_.Lookup(v, ComputePointerHash(v));
DCHECK_NOT_NULL(entry);
if (entry == nullptr) {
return nullptr;
}
return reinterpret_cast<FunctionTableIndices*>(entry->value);
}
void PopulateFunctionTable(VariableProxy* table, ArrayLiteral* funcs) {
FunctionTableIndices* indices = LookupFunctionTable(table->var());
DCHECK_NOT_NULL(indices);
for (int i = 0; i < funcs->values()->length(); ++i) {
VariableProxy* func = funcs->values()->at(i)->AsVariableProxy();
DCHECK_NOT_NULL(func);
builder_->SetIndirectFunction(
indices->start_index + i,
LookupOrInsertFunction(func->var())->func_index());
}
}
class ImportedFunctionTable {
private:
class ImportedFunctionIndices : public ZoneObject {
@ -727,20 +812,33 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
ZoneAllocationPolicy(builder->zone())),
builder_(builder) {}
void AddImport(Variable* v, const char* name, int name_length) {
ImportedFunctionIndices* indices = new (builder_->zone())
ImportedFunctionIndices(name, name_length, builder_->zone());
ImportedFunctionIndices* LookupOrInsertImport(Variable* v) {
auto* entry = table_.LookupOrInsert(
v, ComputePointerHash(v), ZoneAllocationPolicy(builder_->zone()));
entry->value = indices;
ImportedFunctionIndices* indices;
if (entry->value == nullptr) {
indices = new (builder_->zone())
ImportedFunctionIndices(nullptr, 0, builder_->zone());
entry->value = indices;
} else {
indices = reinterpret_cast<ImportedFunctionIndices*>(entry->value);
}
return indices;
}
void SetImportName(Variable* v, const char* name, int name_length) {
auto* indices = LookupOrInsertImport(v);
indices->name_ = name;
indices->name_length_ = name_length;
for (auto i : indices->signature_to_index_) {
builder_->builder_->SetImportName(i.second, indices->name_,
indices->name_length_);
}
}
// Get a function's index (or allocate if new).
uint32_t LookupOrInsertImport(Variable* v, FunctionSig* sig) {
ZoneHashMap::Entry* entry = table_.Lookup(v, ComputePointerHash(v));
DCHECK_NOT_NULL(entry);
ImportedFunctionIndices* indices =
reinterpret_cast<ImportedFunctionIndices*>(entry->value);
uint32_t LookupOrInsertImportUse(Variable* v, FunctionSig* sig) {
auto* indices = LookupOrInsertImport(v);
WasmModuleBuilder::SignatureMap::iterator pos =
indices->signature_to_index_.find(sig);
if (pos != indices->signature_to_index_.end()) {
@ -901,7 +999,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
if (typer_->TypeOf(target)->AsFFIType() != nullptr) {
const AstRawString* name =
prop->key()->AsLiteral()->AsRawPropertyName();
imported_function_table_.AddImport(
imported_function_table_.SetImportName(
target->var(), reinterpret_cast<const char*>(name->raw_data()),
name->length());
}
@ -910,14 +1008,10 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
return;
}
ArrayLiteral* funcs = expr->value()->AsArrayLiteral();
if (funcs != nullptr &&
typer_->TypeOf(funcs)
->AsFunctionTableType()
->signature()
->AsFunctionType()) {
if (funcs != nullptr) {
VariableProxy* target = expr->target()->AsVariableProxy();
DCHECK_NOT_NULL(target);
AddFunctionTable(target, funcs);
PopulateFunctionTable(target, funcs);
// Only add to the function table. No init needed.
return;
}
@ -952,7 +1046,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
DCHECK_NOT_NULL(key_literal);
if (!key_literal->value().is_null()) {
Handle<Name> name =
i::Object::ToName(isolate_, key_literal->value()).ToHandleChecked();
Object::ToName(isolate_, key_literal->value()).ToHandleChecked();
LocalType type = is_float ? kAstF64 : kAstI32;
foreign_variables_.push_back({name, var, type});
}
@ -1325,7 +1419,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
for (int i = 0; i < args->length(); ++i) {
sig.AddParam(TypeOf(args->at(i)));
}
uint32_t index = imported_function_table_.LookupOrInsertImport(
uint32_t index = imported_function_table_.LookupOrInsertImportUse(
vp->var(), sig.Build());
VisitCallArgs(expr);
current_function_builder_->AddAsmWasmOffset(expr->position());
@ -1348,7 +1442,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
DCHECK_NOT_NULL(p);
VariableProxy* var = p->obj()->AsVariableProxy();
DCHECK_NOT_NULL(var);
FunctionTableIndices* indices = LookupFunctionTable(var->var());
FunctionTableIndices* indices = LookupOrAddFunctionTable(var, p);
Visit(p->key()); // TODO(titzer): should use RECURSE()
// We have to use a temporary for the correct order of evaluation.
@ -1694,6 +1788,9 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
void VisitDeclarations(Declaration::List* decls) {
for (Declaration* decl : *decls) {
RECURSE(Visit(decl));
if (typer_failed_) {
return;
}
}
}
@ -1821,7 +1918,10 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
FunctionLiteral* literal_;
Isolate* isolate_;
Zone* zone_;
AstValueFactory* ast_value_factory_;
Script* script_;
AsmTyper* typer_;
bool typer_failed_;
ZoneVector<std::pair<BreakableStatement*, bool>> breakable_blocks_;
ZoneVector<ForeignVariable> foreign_variables_;
WasmFunctionBuilder* init_function_;
@ -1837,21 +1937,27 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
};
AsmWasmBuilder::AsmWasmBuilder(Isolate* isolate, Zone* zone,
FunctionLiteral* literal, AsmTyper* typer)
: isolate_(isolate), zone_(zone), literal_(literal), typer_(typer) {}
AstValueFactory* ast_value_factory,
Script* script, FunctionLiteral* literal)
: isolate_(isolate),
zone_(zone),
ast_value_factory_(ast_value_factory),
script_(script),
literal_(literal),
typer_(isolate, zone, script, literal) {}
// TODO(aseemgarg): probably should take zone (to write wasm to) as input so
// that zone in constructor may be thrown away once wasm module is written.
AsmWasmBuilder::Result AsmWasmBuilder::Run(
i::Handle<i::FixedArray>* foreign_args) {
AsmWasmBuilderImpl impl(isolate_, zone_, literal_, typer_);
impl.Build();
AsmWasmBuilder::Result AsmWasmBuilder::Run(Handle<FixedArray>* foreign_args) {
AsmWasmBuilderImpl impl(isolate_, zone_, ast_value_factory_, script_,
literal_, &typer_);
bool success = impl.Build();
*foreign_args = impl.GetForeignArgs();
ZoneBuffer* module_buffer = new (zone_) ZoneBuffer(zone_);
impl.builder_->WriteTo(*module_buffer);
ZoneBuffer* asm_offsets_buffer = new (zone_) ZoneBuffer(zone_);
impl.builder_->WriteAsmJsOffsetTable(*asm_offsets_buffer);
return {module_buffer, asm_offsets_buffer};
return {module_buffer, asm_offsets_buffer, success};
}
const char* AsmWasmBuilder::foreign_init_name = "__foreign_init__";

View File

@ -15,6 +15,7 @@ namespace v8 {
namespace internal {
class FunctionLiteral;
class Script;
namespace wasm {
@ -23,20 +24,26 @@ class AsmWasmBuilder {
struct Result {
ZoneBuffer* module_bytes;
ZoneBuffer* asm_offset_table;
bool success;
};
explicit AsmWasmBuilder(Isolate* isolate, Zone* zone, FunctionLiteral* root,
AsmTyper* typer);
explicit AsmWasmBuilder(Isolate* isolate, Zone* zone,
AstValueFactory* ast_value_factory, Script* script,
FunctionLiteral* root);
Result Run(Handle<FixedArray>* foreign_args);
static const char* foreign_init_name;
static const char* single_function_name;
const AsmTyper* typer() { return &typer_; }
private:
Isolate* isolate_;
Zone* zone_;
AstValueFactory* ast_value_factory_;
Script* script_;
FunctionLiteral* literal_;
AsmTyper* typer_;
AsmTyper typer_;
};
} // namespace wasm
} // namespace internal

View File

@ -542,13 +542,15 @@ void DeclarationScope::Analyze(ParseInfo* info, AnalyzeMode mode) {
scope->HoistSloppyBlockFunctions(&factory);
}
// We are compiling one of three cases:
// We are compiling one of four cases:
// 1) top-level code,
// 2) a function/eval/module on the top-level
// 3) a function/eval in a scope that was already resolved.
// 4) an asm.js function
DCHECK(scope->scope_type() == SCRIPT_SCOPE ||
scope->outer_scope()->scope_type() == SCRIPT_SCOPE ||
scope->outer_scope()->already_resolved_);
scope->outer_scope()->already_resolved_ ||
(info->asm_function_scope() && scope->is_function_scope()));
// The outer scope is never lazy.
scope->set_should_eager_compile();

View File

@ -420,6 +420,22 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
void set_is_debug_evaluate_scope() { is_debug_evaluate_scope_ = true; }
bool is_debug_evaluate_scope() const { return is_debug_evaluate_scope_; }
bool RemoveInnerScope(Scope* inner_scope) {
DCHECK_NOT_NULL(inner_scope);
if (inner_scope == inner_scope_) {
inner_scope_ = inner_scope_->sibling_;
return true;
}
for (Scope* scope = inner_scope_; scope != nullptr;
scope = scope->sibling_) {
if (scope->sibling_ == inner_scope) {
scope->sibling_ = scope->sibling_->sibling_;
return true;
}
}
return false;
}
protected:
explicit Scope(Zone* zone);
@ -557,21 +573,6 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
inner_scope->outer_scope_ = this;
}
void RemoveInnerScope(Scope* inner_scope) {
DCHECK_NOT_NULL(inner_scope);
if (inner_scope == inner_scope_) {
inner_scope_ = inner_scope_->sibling_;
return;
}
for (Scope* scope = inner_scope_; scope != nullptr;
scope = scope->sibling_) {
if (scope->sibling_ == inner_scope) {
scope->sibling_ = scope->sibling_->sibling_;
return;
}
}
}
void SetDefaults();
friend class DeclarationScope;

View File

@ -948,18 +948,6 @@ MaybeHandle<Code> GetLazyCode(Handle<JSFunction> function) {
}
Handle<SharedFunctionInfo> NewSharedFunctionInfoForLiteral(
Isolate* isolate, FunctionLiteral* literal, Handle<Script> script) {
Handle<Code> code = isolate->builtins()->CompileLazy();
Handle<ScopeInfo> scope_info = handle(ScopeInfo::Empty(isolate));
Handle<SharedFunctionInfo> result = isolate->factory()->NewSharedFunctionInfo(
literal->name(), literal->materialized_literal_count(), literal->kind(),
code, scope_info);
SharedFunctionInfo::InitFromFunctionLiteral(result, literal);
SharedFunctionInfo::SetScript(result, script);
return result;
}
Handle<SharedFunctionInfo> CompileToplevel(CompilationInfo* info) {
Isolate* isolate = info->isolate();
TimerEventScope<TimerEventCompileCode> timer(isolate);
@ -998,7 +986,7 @@ Handle<SharedFunctionInfo> CompileToplevel(CompilationInfo* info) {
// Allocate a shared function info object.
DCHECK_EQ(kNoSourcePosition, lit->function_token_position());
result = NewSharedFunctionInfoForLiteral(isolate, lit, script);
result = isolate->factory()->NewSharedFunctionInfoForLiteral(lit, script);
result->set_is_toplevel(true);
parse_info->set_shared_info(result);
parse_info->set_function_literal_id(result->function_literal_id());
@ -1577,7 +1565,8 @@ Handle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo(
// Allocate a shared function info object.
Handle<SharedFunctionInfo> result;
if (!maybe_existing.ToHandle(&result)) {
result = NewSharedFunctionInfoForLiteral(isolate, literal, script);
result =
isolate->factory()->NewSharedFunctionInfoForLiteral(literal, script);
result->set_is_toplevel(false);
// If the outer function has been compiled before, we cannot be sure that

View File

@ -2261,6 +2261,17 @@ Handle<SharedFunctionInfo> Factory::NewSharedFunctionInfo(
return shared;
}
Handle<SharedFunctionInfo> Factory::NewSharedFunctionInfoForLiteral(
FunctionLiteral* literal, Handle<Script> script) {
Handle<Code> code = isolate()->builtins()->CompileLazy();
Handle<ScopeInfo> scope_info(ScopeInfo::Empty(isolate()));
Handle<SharedFunctionInfo> result = NewSharedFunctionInfo(
literal->name(), literal->materialized_literal_count(), literal->kind(),
code, scope_info);
SharedFunctionInfo::InitFromFunctionLiteral(result, literal);
SharedFunctionInfo::SetScript(result, script);
return result;
}
Handle<JSMessageObject> Factory::NewJSMessageObject(
MessageTemplate::Template message, Handle<Object> argument,

View File

@ -706,6 +706,9 @@ class V8_EXPORT_PRIVATE Factory final {
MaybeHandle<Code> code,
bool is_constructor);
Handle<SharedFunctionInfo> NewSharedFunctionInfoForLiteral(
FunctionLiteral* literal, Handle<Script> script);
static bool IsFunctionModeWithPrototype(FunctionMode function_mode) {
return (function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE ||
function_mode == FUNCTION_WITH_READONLY_PROTOTYPE);

View File

@ -19,6 +19,7 @@ ParseInfo::ParseInfo(Zone* zone)
extension_(nullptr),
compile_options_(ScriptCompiler::kNoCompileOptions),
script_scope_(nullptr),
asm_function_scope_(nullptr),
unicode_cache_(nullptr),
stack_limit_(0),
hash_seed_(0),

View File

@ -105,6 +105,11 @@ class V8_EXPORT_PRIVATE ParseInfo {
script_scope_ = script_scope;
}
DeclarationScope* asm_function_scope() const { return asm_function_scope_; }
void set_asm_function_scope(DeclarationScope* scope) {
asm_function_scope_ = scope;
}
AstValueFactory* ast_value_factory() const { return ast_value_factory_; }
void set_ast_value_factory(AstValueFactory* ast_value_factory) {
ast_value_factory_ = ast_value_factory;
@ -223,6 +228,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
v8::Extension* extension_;
ScriptCompiler::CompileOptions compile_options_;
DeclarationScope* script_scope_;
DeclarationScope* asm_function_scope_;
UnicodeCache* unicode_cache_;
uintptr_t stack_limit_;
uint32_t hash_seed_;

View File

@ -859,6 +859,12 @@ FunctionLiteral* Parser::ParseFunction(Isolate* isolate, ParseInfo* info) {
}
Handle<SharedFunctionInfo> shared_info = info->shared_info();
DeserializeScopeChain(info, info->maybe_outer_scope_info());
if (info->asm_function_scope()) {
original_scope_ = info->asm_function_scope();
factory()->set_zone(info->zone());
} else {
DCHECK_EQ(factory()->zone(), info->zone());
}
// Initialize parser state.
source = String::Flatten(source);
@ -2631,8 +2637,6 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
// FunctionExpression; even without enclosing parentheses it might be
// immediately invoked.
// - The function literal shouldn't be hinted to eagerly compile.
// - For asm.js functions the body needs to be available when module
// validation is active, because we examine the entire module at once.
// Inner functions will be parsed using a temporary Zone. After parsing, we
// will migrate unresolved variable into a Scope in the main Zone.
@ -2642,8 +2646,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
? can_preparse
: (is_lazy_top_level_function ||
(allow_lazy_ && function_type == FunctionLiteral::kDeclaration &&
eager_compile_hint == FunctionLiteral::kShouldLazyCompile))) &&
!(FLAG_validate_asm && scope()->IsAsmModule());
eager_compile_hint == FunctionLiteral::kShouldLazyCompile)));
bool is_lazy_inner_function =
use_temp_zone && FLAG_lazy_inner_functions && !is_lazy_top_level_function;

View File

@ -271,8 +271,15 @@ uint32_t WasmModuleBuilder::AddSignature(FunctionSig* sig) {
}
}
void WasmModuleBuilder::AddIndirectFunction(uint32_t index) {
indirect_functions_.push_back(index);
uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
uint32_t ret = static_cast<uint32_t>(indirect_functions_.size());
indirect_functions_.resize(indirect_functions_.size() + count);
return ret;
}
void WasmModuleBuilder::SetIndirectFunction(uint32_t indirect,
uint32_t direct) {
indirect_functions_[indirect] = direct;
}
uint32_t WasmModuleBuilder::AddImport(const char* name, int name_length,

View File

@ -230,7 +230,8 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
const WasmInitExpr& init = WasmInitExpr());
void AddDataSegment(const byte* data, uint32_t size, uint32_t dest);
uint32_t AddSignature(FunctionSig* sig);
void AddIndirectFunction(uint32_t index);
uint32_t AllocateIndirectFunctions(uint32_t count);
void SetIndirectFunction(uint32_t indirect, uint32_t direct);
void MarkStartFunction(WasmFunctionBuilder* builder);
// Writing methods.

View File

@ -126,7 +126,7 @@ class AsmTyperHarnessBuilder {
WithGlobal(var_name, type);
auto* var_info = typer_->Lookup(DeclareVariable(var_name));
CHECK(var_info);
var_info->FirstForwardUseIs(nullptr);
var_info->SetFirstForwardUse(-1);
return this;
}

View File

@ -1082,6 +1082,7 @@ function TestForeignFunctionMultipleUse() {
assertEquals(89, module.caller(83, 83.25));
}
print("TestForeignFunctionMultipleUse...");
TestForeignFunctionMultipleUse();
@ -1173,6 +1174,7 @@ function TestForeignVariables() {
TestCase(undefined, 0, NaN, 0, NaN);
}
print("TestForeignVariables...");
TestForeignVariables();
@ -1386,6 +1388,7 @@ assertWasm(7, TestIntegerMultiplyBothWays);
}
return {func: func};
}
print("TestBadAssignDoubleFromIntish...");
Module(stdlib);
assertTrue(%IsNotAsmWasmCode(Module));
})();
@ -1401,6 +1404,7 @@ assertWasm(7, TestIntegerMultiplyBothWays);
}
return {func: func};
}
print("TestBadAssignIntFromDouble...");
Module(stdlib);
assertTrue(%IsNotAsmWasmCode(Module));
})();
@ -1415,6 +1419,7 @@ assertWasm(7, TestIntegerMultiplyBothWays);
}
return {func: func};
}
print("TestBadMultiplyIntish...");
Module(stdlib);
assertTrue(%IsNotAsmWasmCode(Module));
})();
@ -1429,6 +1434,7 @@ assertWasm(7, TestIntegerMultiplyBothWays);
}
return {func: func};
}
print("TestBadCastFromInt...");
Module(stdlib);
assertTrue(%IsNotAsmWasmCode(Module));
})();