diff options
author | Alon Zakai <alonzakai@gmail.com> | 2017-02-01 21:28:50 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-01 21:28:50 -0800 |
commit | 5725c4376cf674378084e700073d0e3a7f3c95f3 (patch) | |
tree | 89cd26f480bcc581e33b0ad02cb4f6381d1cf29a | |
parent | 7bc8f14d8699f56777a763f99ad8098fcf7c0583 (diff) | |
parent | ae6612e4c394af30bb0871ad9735ac853811f807 (diff) | |
download | binaryen-5725c4376cf674378084e700073d0e3a7f3c95f3.tar.gz binaryen-5725c4376cf674378084e700073d0e3a7f3c95f3.tar.bz2 binaryen-5725c4376cf674378084e700073d0e3a7f3c95f3.zip |
Merge pull request #893 from WebAssembly/shrink-asm-parser
Shrink asm.js ast
-rw-r--r-- | src/asm2wasm.h | 415 | ||||
-rw-r--r-- | src/emscripten-optimizer/optimizer-shared.cpp | 66 | ||||
-rw-r--r-- | src/emscripten-optimizer/optimizer.h | 5 | ||||
-rw-r--r-- | src/emscripten-optimizer/parser.cpp | 4 | ||||
-rw-r--r-- | src/emscripten-optimizer/parser.h | 4 | ||||
-rw-r--r-- | src/emscripten-optimizer/simple_ast.cpp | 153 | ||||
-rw-r--r-- | src/emscripten-optimizer/simple_ast.h | 879 | ||||
-rw-r--r-- | src/mixed_arena.h | 76 | ||||
-rw-r--r-- | src/passes/MergeBlocks.cpp | 2 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 5 |
10 files changed, 521 insertions, 1088 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 77be6aad2..eaf4f8c18 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -299,8 +299,8 @@ private: std::map<IString, std::unique_ptr<FunctionType>> importedFunctionTypes; void noteImportedFunctionCall(Ref ast, WasmType resultType, CallImport* call) { - assert(ast[0] == CALL && ast[1][0] == NAME); - IString importName = ast[1][1]->getIString(); + assert(ast[0] == CALL && ast[1]->isString()); + IString importName = ast[1]->getIString(); auto type = make_unique<FunctionType>(); type->name = IString((std::string("type$") + importName.str).c_str(), false); // TODO: make a list of such types type->result = resultType; @@ -358,16 +358,16 @@ public: private: AsmType detectAsmType(Ref ast, AsmData *data) { - if (ast[0] == NAME) { - IString name = ast[1]->getIString(); + if (ast->isString()) { + IString name = ast->getIString(); if (!data->isLocal(name)) { // must be global assert(mappedGlobals.find(name) != mappedGlobals.end()); return wasmToAsmType(mappedGlobals[name].type); } - } else if (ast[0] == SUB && ast[1][0] == NAME) { + } else if (ast->isArray(SUB) && ast[1]->isString()) { // could be a heap access, use view info - auto view = views.find(ast[1][1]->getIString()); + auto view = views.find(ast[1]->getIString()); if (view != views.end()) { return view->second.type; } @@ -456,31 +456,31 @@ private: std::map<unsigned, Ref> tempNums; Literal checkLiteral(Ref ast, bool rawIsInteger = true) { - if (ast[0] == NUM) { + if (ast->isNumber()) { if (rawIsInteger) { - return Literal((int32_t)ast[1]->getInteger()); + return Literal((int32_t)ast->getInteger()); } else { - return Literal(ast[1]->getNumber()); + return Literal(ast->getNumber()); } - } else if (ast[0] == UNARY_PREFIX) { - if (ast[1] == PLUS && ast[2][0] == NUM) { - return Literal((double)ast[2][1]->getNumber()); + } else if (ast->isArray(UNARY_PREFIX)) { + if (ast[1] == PLUS && ast[2]->isNumber()) { + return Literal((double)ast[2]->getNumber()); } - if (ast[1] == MINUS && ast[2][0] == NUM) { - double num = -ast[2][1]->getNumber(); + if (ast[1] == MINUS && ast[2]->isNumber()) { + double num = -ast[2]->getNumber(); if (isSInteger32(num)) return Literal((int32_t)num); if (isUInteger32(num)) return Literal((uint32_t)num); assert(false && "expected signed or unsigned int32"); } - if (ast[1] == PLUS && ast[2][0] == UNARY_PREFIX && ast[2][1] == MINUS && ast[2][2][0] == NUM) { - return Literal((double)-ast[2][2][1]->getNumber()); + if (ast[1] == PLUS && ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == MINUS && ast[2][2]->isNumber()) { + return Literal((double)-ast[2][2]->getNumber()); } - if (ast[1] == MINUS && ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM) { - return Literal((double)-ast[2][2][1]->getNumber()); + if (ast[1] == MINUS && ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == PLUS && ast[2][2]->isNumber()) { + return Literal((double)-ast[2][2]->getNumber()); } - } else if (wasmOnly && ast[0] == CALL && ast[1][0] == NAME && ast[1][1] == I64_CONST) { - uint64_t low = ast[2][0][1]->getNumber(); - uint64_t high = ast[2][1][1]->getNumber(); + } else if (wasmOnly && ast->isArray(CALL) && ast[1]->isString() && ast[1] == I64_CONST) { + uint64_t low = ast[2][0]->getNumber(); + uint64_t high = ast[2][1]->getNumber(); return Literal(uint64_t(low + (high << 32))); } return Literal(); @@ -572,15 +572,15 @@ void Asm2WasmBuilder::processAsm(Ref ast) { Ref asmFunction = ast[1][0]; assert(asmFunction[0] == DEFUN); Ref body = asmFunction[3]; - assert(body[0][0] == STAT && body[0][1][0] == STRING && (body[0][1][1]->getIString() == IString("use asm") || body[0][1][1]->getIString() == IString("almost asm"))); + assert(body[0][0] == STRING && (body[0][1]->getIString() == IString("use asm") || body[0][1]->getIString() == IString("almost asm"))); auto addImport = [&](IString name, Ref imported, WasmType type) { assert(imported[0] == DOT); Ref module = imported[1]; IString moduleName; - if (module[0] == DOT) { + if (module->isArray(DOT)) { // we can have (global.Math).floor; skip the 'Math' - assert(module[1][0] == NAME); + assert(module[1]->isString()); if (module[2] == MATH) { if (imported[2] == IMUL) { assert(Math_imul.isNull()); @@ -620,13 +620,13 @@ void Asm2WasmBuilder::processAsm(Ref ast) { return; } } - std::string fullName = module[1][1]->getCString(); + std::string fullName = module[1]->getCString(); fullName += '.'; fullName += + module[2]->getCString(); moduleName = IString(fullName.c_str(), false); } else { - assert(module[0] == NAME); - moduleName = module[1]->getIString(); + assert(module->isString()); + moduleName = module->getIString(); if (moduleName == ENV) { auto base = imported[2]->getIString(); if (base == TEMP_DOUBLE_PTR) { @@ -709,34 +709,34 @@ void Asm2WasmBuilder::processAsm(Ref ast) { Ref pair = curr[1][j]; IString name = pair[0]->getIString(); Ref value = pair[1]; - if (value[0] == NUM) { + if (value->isNumber()) { // global int - assert(value[1]->getNumber() == 0); + assert(value->getNumber() == 0); allocateGlobal(name, WasmType::i32); } else if (value[0] == BINARY) { // int import - assert(value[1] == OR && value[3][0] == NUM && value[3][1]->getNumber() == 0); + assert(value[1] == OR && value[3]->isNumber() && value[3]->getNumber() == 0); Ref import = value[2]; // env.what addImport(name, import, WasmType::i32); } else if (value[0] == UNARY_PREFIX) { // double import or global assert(value[1] == PLUS); Ref import = value[2]; - if (import[0] == NUM) { + if (import->isNumber()) { // global - assert(import[1]->getNumber() == 0); + assert(import->getNumber() == 0); allocateGlobal(name, WasmType::f64); } else { // import addImport(name, import, WasmType::f64); } } else if (value[0] == CALL) { - assert(value[1][0] == NAME && value[1][1] == Math_fround && value[2][0][0] == NUM && value[2][0][1]->getNumber() == 0); + assert(value[1]->isString() && value[1] == Math_fround && value[2][0]->isNumber() && value[2][0]->getNumber() == 0); allocateGlobal(name, WasmType::f32); } else if (value[0] == DOT) { // simple module.base import. can be a view, or a function. - if (value[1][0] == NAME) { - IString module = value[1][1]->getIString(); + if (value[1]->isString()) { + IString module = value[1]->getIString(); IString base = value[2]->getIString(); if (module == GLOBAL) { if (base == INT8ARRAY) { @@ -768,7 +768,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { bool integer, signed_; AsmType asmType; Ref constructor = value[1]; - if (constructor[0] == DOT) { // global.*Array + if (constructor->isArray(DOT)) { // global.*Array IString heap = constructor[2]->getIString(); if (heap == INT8ARRAY) { bytes = 1; integer = true; signed_ = true; asmType = ASM_INT; @@ -790,8 +790,8 @@ void Asm2WasmBuilder::processAsm(Ref ast) { abort_on("invalid view import", heap); } } else { // *ArrayView that was previously imported - assert(constructor[0] == NAME); - IString viewName = constructor[1]->getIString(); + assert(constructor->isString()); + IString viewName = constructor->getIString(); if (viewName == Int8Array) { bytes = 1; integer = true; signed_ = true; asmType = ASM_INT; } else if (viewName == Int16Array) { @@ -826,7 +826,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { functionTableStarts[name] = segment.data.size(); // this table starts here Ref contents = value[1]; for (unsigned k = 0; k < contents->size(); k++) { - IString curr = contents[k][1]->getIString(); + IString curr = contents[k]->getIString(); segment.data.push_back(curr); } wasm.table.initial = wasm.table.max = segment.data.size(); @@ -850,9 +850,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) { for (unsigned k = 0; k < contents->size(); k++) { Ref pair = contents[k]; IString key = pair[0]->getIString(); - if (pair[1][0] == NAME) { + if (pair[1]->isString()) { // exporting a function - IString value = pair[1][1]->getIString(); + IString value = pair[1]->getIString(); if (key == Name("_emscripten_replace_memory")) { // asm.js memory growth provides this special non-asm function, which we don't need (we use grow_memory) assert(!wasm.checkFunction(value)); @@ -875,9 +875,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } } else { // export a number. create a global and export it - assert(pair[1][0] == NUM); + assert(pair[1]->isNumber()); assert(exported.count(key) == 0); - auto value = pair[1][1]->getInteger(); + auto value = pair[1]->getInteger(); auto global = new Global(); global->name = key; global->type = i32; @@ -1192,17 +1192,15 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { for (unsigned i = 0; i < params->size(); i++) { Ref curr = body[i]; - assert(curr[0] == STAT); - curr = curr[1]; - assert(curr[0] == ASSIGN && curr[2][0] == NAME); - IString name = curr[2][1]->getIString(); - AsmType asmType = detectType(curr[3], nullptr, false, Math_fround, wasmOnly); + auto* assign = curr->asAssignName(); + IString name = assign->target(); + AsmType asmType = detectType(assign->value(), nullptr, false, Math_fround, wasmOnly); Builder::addParam(function, name, asmToWasmType(asmType)); functionVariables.insert(name); asmData.addParam(name, asmType); } unsigned start = params->size(); - while (start < body->size() && body[start][0] == VAR) { + while (start < body->size() && body[start]->isArray(VAR)) { Ref curr = body[start]; for (unsigned j = 0; j < curr[1]->size(); j++) { Ref pair = curr[1][j]; @@ -1231,63 +1229,108 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { std::function<Expression* (Ref)> process = [&](Ref ast) -> Expression* { AstStackHelper astStackHelper(ast); // TODO: only create one when we need it? - IString what = ast[0]->getIString(); - if (what == STAT) { - return process(ast[1]); // and drop return value, if any - } else if (what == ASSIGN) { - if (ast[2][0] == NAME) { - IString name = ast[2][1]->getIString(); - if (functionVariables.has(name)) { - auto ret = allocator.alloc<SetLocal>(); - ret->index = function->getLocalIndex(ast[2][1]->getIString()); - ret->value = process(ast[3]); - ret->setTee(false); - ret->finalize(); - return ret; + if (ast->isString()) { + IString name = ast->getIString(); + if (functionVariables.has(name)) { + // var in scope + auto ret = allocator.alloc<GetLocal>(); + ret->index = function->getLocalIndex(name); + ret->type = asmToWasmType(asmData.getType(name)); + return ret; + } + if (name == DEBUGGER) { + CallImport *call = allocator.alloc<CallImport>(); + call->target = DEBUGGER; + call->type = none; + static bool addedImport = false; + if (!addedImport) { + addedImport = true; + auto import = new Import; // debugger = asm2wasm.debugger; + import->name = DEBUGGER; + import->module = ASM2WASM; + import->base = DEBUGGER; + import->functionType = ensureFunctionType("v", &wasm); + import->kind = ExternalKind::Function; + wasm.addImport(import); } - // global var - assert(mappedGlobals.find(name) != mappedGlobals.end()); - auto* ret = builder.makeSetGlobal(name, process(ast[3])); - // set_global does not return; if our value is trivially not used, don't emit a load (if nontrivially not used, opts get it later) - if (astStackHelper.getParent()[0] == STAT) return ret; - return builder.makeSequence(ret, builder.makeGetGlobal(name, ret->value->type)); - } else if (ast[2][0] == SUB) { - Ref target = ast[2]; - assert(target[1][0] == NAME); - IString heap = target[1][1]->getIString(); - assert(views.find(heap) != views.end()); - View& view = views[heap]; - auto ret = allocator.alloc<Store>(); - ret->bytes = view.bytes; - ret->offset = 0; - ret->align = view.bytes; - ret->ptr = processUnshifted(target[2], view.bytes); - ret->value = process(ast[3]); - ret->valueType = asmToWasmType(view.type); + return call; + } + // global var + assert(mappedGlobals.find(name) != mappedGlobals.end() ? true : (std::cerr << name.str << '\n', false)); + MappedGlobal& global = mappedGlobals[name]; + return builder.makeGetGlobal(name, global.type); + } + if (ast->isNumber()) { + auto ret = allocator.alloc<Const>(); + double num = ast->getNumber(); + if (isSInteger32(num)) { + ret->value = Literal(int32_t(toSInteger32(num))); + } else if (isUInteger32(num)) { + ret->value = Literal(uint32_t(toUInteger32(num))); + } else { + ret->value = Literal(num); + } + ret->type = ret->value.type; + return ret; + } + if (ast->isAssignName()) { + auto* assign = ast->asAssignName(); + IString name = assign->target(); + if (functionVariables.has(name)) { + auto ret = allocator.alloc<SetLocal>(); + ret->index = function->getLocalIndex(assign->target()); + ret->value = process(assign->value()); + ret->setTee(false); ret->finalize(); - if (ret->valueType != ret->value->type) { - // in asm.js we have some implicit coercions that we must do explicitly here - if (ret->valueType == f32 && ret->value->type == f64) { - auto conv = allocator.alloc<Unary>(); - conv->op = DemoteFloat64; - conv->value = ret->value; - conv->type = WasmType::f32; - ret->value = conv; - } else if (ret->valueType == f64 && ret->value->type == f32) { - auto conv = allocator.alloc<Unary>(); - conv->op = PromoteFloat32; - conv->value = ret->value; - conv->type = WasmType::f64; - ret->value = conv; - } else { - abort_on("bad sub[] types", ast); - } - } return ret; } - abort_on("confusing assign", ast); - } else if (what == BINARY) { - if ((ast[1] == OR || ast[1] == TRSHIFT) && ast[3][0] == NUM && ast[3][1]->getNumber() == 0) { + // global var + assert(mappedGlobals.find(name) != mappedGlobals.end()); + auto* ret = builder.makeSetGlobal(name, process(assign->value())); + // set_global does not return; if our value is trivially not used, don't emit a load (if nontrivially not used, opts get it later) + auto parent = astStackHelper.getParent(); + if (!parent || parent->isArray(BLOCK) || parent->isArray(IF)) return ret; + return builder.makeSequence(ret, builder.makeGetGlobal(name, ret->value->type)); + } + if (ast->isAssign()) { + auto* assign = ast->asAssign(); + assert(assign->target()->isArray(SUB)); + Ref target = assign->target(); + assert(target[1]->isString()); + IString heap = target[1]->getIString(); + assert(views.find(heap) != views.end()); + View& view = views[heap]; + auto ret = allocator.alloc<Store>(); + ret->bytes = view.bytes; + ret->offset = 0; + ret->align = view.bytes; + ret->ptr = processUnshifted(target[2], view.bytes); + ret->value = process(assign->value()); + ret->valueType = asmToWasmType(view.type); + ret->finalize(); + if (ret->valueType != ret->value->type) { + // in asm.js we have some implicit coercions that we must do explicitly here + if (ret->valueType == f32 && ret->value->type == f64) { + auto conv = allocator.alloc<Unary>(); + conv->op = DemoteFloat64; + conv->value = ret->value; + conv->type = WasmType::f32; + ret->value = conv; + } else if (ret->valueType == f64 && ret->value->type == f32) { + auto conv = allocator.alloc<Unary>(); + conv->op = PromoteFloat32; + conv->value = ret->value; + conv->type = WasmType::f64; + ret->value = conv; + } else { + abort_on("bad sub[] types", ast); + } + } + return ret; + } + IString what = ast[0]->getIString(); + if (what == BINARY) { + if ((ast[1] == OR || ast[1] == TRSHIFT) && ast[3]->isNumber() && ast[3]->getNumber() == 0) { auto ret = process(ast[2]); // just look through the ()|0 or ()>>>0 coercion fixCallType(ret, i32); return ret; @@ -1344,52 +1387,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { return call; } return ret; - } else if (what == NUM) { - auto ret = allocator.alloc<Const>(); - double num = ast[1]->getNumber(); - if (isSInteger32(num)) { - ret->value = Literal(int32_t(toSInteger32(num))); - } else if (isUInteger32(num)) { - ret->value = Literal(uint32_t(toUInteger32(num))); - } else { - ret->value = Literal(num); - } - ret->type = ret->value.type; - return ret; - } else if (what == NAME) { - IString name = ast[1]->getIString(); - if (functionVariables.has(name)) { - // var in scope - auto ret = allocator.alloc<GetLocal>(); - ret->index = function->getLocalIndex(name); - ret->type = asmToWasmType(asmData.getType(name)); - return ret; - } - if (name == DEBUGGER) { - CallImport *call = allocator.alloc<CallImport>(); - call->target = DEBUGGER; - call->type = none; - static bool addedImport = false; - if (!addedImport) { - addedImport = true; - auto import = new Import; // debugger = asm2wasm.debugger; - import->name = DEBUGGER; - import->module = ASM2WASM; - import->base = DEBUGGER; - import->functionType = ensureFunctionType("v", &wasm); - import->kind = ExternalKind::Function; - wasm.addImport(import); - } - return call; - } - // global var - assert(mappedGlobals.find(name) != mappedGlobals.end() ? true : (std::cerr << name.str << '\n', false)); - MappedGlobal& global = mappedGlobals[name]; - return builder.makeGetGlobal(name, global.type); } else if (what == SUB) { Ref target = ast[1]; - assert(target[0] == NAME); - IString heap = target[1]->getIString(); + assert(target->isString()); + IString heap = target->getIString(); assert(views.find(heap) != views.end()); View& view = views[heap]; auto ret = allocator.alloc<Load>(); @@ -1424,7 +1425,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { fixCallType(ret, f64); return ret; } else if (ast[1] == MINUS) { - if (ast[2][0] == NUM || (ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM)) { + if (ast[2]->isNumber() || (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == PLUS && ast[2][2]->isNumber())) { auto ret = allocator.alloc<Const>(); ret->value = getLiteral(ast); ret->type = ret->value.type; @@ -1454,7 +1455,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { return ret; } else if (ast[1] == B_NOT) { // ~, might be ~~ as a coercion or just a not - if (ast[2][0] == UNARY_PREFIX && ast[2][1] == B_NOT) { + if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) { if (imprecise) { auto ret = allocator.alloc<Unary>(); ret->value = process(ast[2][2]); @@ -1509,8 +1510,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto* ifTrue = process(ast[2]); return builder.makeIf(truncateToInt32(condition), ifTrue, !!ast[3] ? process(ast[3]) : nullptr); } else if (what == CALL) { - if (ast[1][0] == NAME) { - IString name = ast[1][1]->getIString(); + if (ast[1]->isString()) { + IString name = ast[1]->getIString(); if (name == Math_imul) { assert(ast[2]->size() == 2); auto ret = allocator.alloc<Binary>(); @@ -1639,7 +1640,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto num = ast[2]->size(); switch (name.str[0]) { case 'l': { - auto align = num == 2 ? ast[2][1][1]->getInteger() : 0; + auto align = num == 2 ? ast[2][1]->getInteger() : 0; if (name == LOAD1) return builder.makeLoad(1, true, 0, 1, process(ast[2][0]), i32); if (name == LOAD2) return builder.makeLoad(2, true, 0, indexOr(align, 2), process(ast[2][0]), i32); if (name == LOAD4) return builder.makeLoad(4, true, 0, indexOr(align, 4), process(ast[2][0]), i32); @@ -1649,7 +1650,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { break; } case 's': { - auto align = num == 3 ? ast[2][2][1]->getInteger() : 0; + auto align = num == 3 ? ast[2][2]->getInteger() : 0; if (name == STORE1) return builder.makeStore(1, 0, 1, process(ast[2][0]), process(ast[2][1]), i32); if (name == STORE2) return builder.makeStore(2, 0, indexOr(align, 2), process(ast[2][0]), process(ast[2][1]), i32); if (name == STORE4) return builder.makeStore(4, 0, indexOr(align, 4), process(ast[2][0]), process(ast[2][1]), i32); @@ -1788,7 +1789,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { // function pointers auto ret = allocator.alloc<CallIndirect>(); Ref target = ast[1]; - assert(target[0] == SUB && target[1][0] == NAME && target[2][0] == BINARY && target[2][1] == AND && target[2][3][0] == NUM); // FUNCTION_TABLE[(expr) & mask] + assert(target[0] == SUB && target[1]->isString() && target[2][0] == BINARY && target[2][1] == AND && target[2][3]->isNumber()); // FUNCTION_TABLE[(expr) & mask] ret->target = process(target[2]); // TODO: as an optimization, we could look through the mask Ref args = ast[2]; for (unsigned i = 0; i < args->size(); i++) { @@ -1798,7 +1799,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->fullType = fullType->name; ret->type = fullType->result; // we don't know the table offset yet. emit target = target + callImport(tableName), which we fix up later when we know how asm function tables are layed out inside the wasm table. - ret->target = builder.makeBinary(BinaryOp::AddInt32, ret->target, builder.makeCallImport(target[1][1]->getIString(), {}, i32)); + ret->target = builder.makeBinary(BinaryOp::AddInt32, ret->target, builder.makeCallImport(target[1]->getIString(), {}, i32)); return ret; } else if (what == RETURN) { WasmType type = !!ast[1] ? detectWasmType(ast[1], &asmData) : none; @@ -1845,7 +1846,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->name = !!ast[1] ? nameMapper.sourceToUnique(getContinueLabelName(ast[1]->getIString())) : continueStack.back(); return ret; } else if (what == WHILE) { - bool forever = ast[1][0] == NUM && ast[1][1]->getInteger() == 1; + bool forever = ast[1]->isNumber() && ast[1]->getInteger() == 1; auto ret = allocator.alloc<Loop>(); IString out, in; if (!parentLabel.isNull()) { @@ -1890,7 +1891,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { nameMapper.popLabelName(out); return ret; } else if (what == DO) { - if (ast[1][0] == NUM && ast[1][1]->getNumber() == 0) { + if (ast[1]->isNumber() && ast[1]->getNumber() == 0) { // one-time loop, unless there is a continue IString stop; if (!parentLabel.isNull()) { @@ -2020,52 +2021,56 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { // (HEAP32[tempDoublePtr >> 2] = i, Math_fround(HEAPF32[tempDoublePtr >> 2])); // i32->f32 // (HEAP32[tempDoublePtr >> 2] = i, +HEAPF32[tempDoublePtr >> 2]); // i32->f32, no fround // (HEAPF32[tempDoublePtr >> 2] = f, HEAP32[tempDoublePtr >> 2] | 0); // f32->i32 - if (ast[1][0] == ASSIGN && ast[1][2][0] == SUB && ast[1][2][1][0] == NAME && ast[1][2][2][0] == BINARY && ast[1][2][2][1] == RSHIFT && - ast[1][2][2][2][0] == NAME && ast[1][2][2][2][1] == tempDoublePtr && ast[1][2][2][3][0] == NUM && ast[1][2][2][3][1]->getNumber() == 2) { - // (?[tempDoublePtr >> 2] = ?, ?) so far - auto heap = ast[1][2][1][1]->getIString(); - if (views.find(heap) != views.end()) { - AsmType writeType = views[heap].type; - AsmType readType = ASM_NONE; - Ref readValue; - if (ast[2][0] == BINARY && ast[2][1] == OR && ast[2][3][0] == NUM && ast[2][3][1]->getNumber() == 0) { - readType = ASM_INT; - readValue = ast[2][2]; - } else if (ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS) { - readType = ASM_DOUBLE; - readValue = ast[2][2]; - } else if (ast[2][0] == CALL && ast[2][1][0] == NAME && ast[2][1][1] == Math_fround) { - readType = ASM_FLOAT; - readValue = ast[2][2][0]; - } - if (readType != ASM_NONE) { - if (readValue[0] == SUB && readValue[1][0] == NAME && readValue[2][0] == BINARY && readValue[2][1] == RSHIFT && - readValue[2][2][0] == NAME && readValue[2][2][1] == tempDoublePtr && readValue[2][3][0] == NUM && readValue[2][3][1]->getNumber() == 2) { - // pattern looks right! - Ref writtenValue = ast[1][3]; - if (writeType == ASM_INT && (readType == ASM_FLOAT || readType == ASM_DOUBLE)) { - auto conv = allocator.alloc<Unary>(); - conv->op = ReinterpretInt32; - conv->value = process(writtenValue); - conv->type = WasmType::f32; - if (readType == ASM_DOUBLE) { - auto promote = allocator.alloc<Unary>(); - promote->op = PromoteFloat32; - promote->value = conv; - promote->type = WasmType::f64; - return promote; - } - return conv; - } else if (writeType == ASM_FLOAT && readType == ASM_INT) { - auto conv = allocator.alloc<Unary>(); - conv->op = ReinterpretFloat32; - conv->value = process(writtenValue); - if (conv->value->type == f64) { - // this has an implicit f64->f32 in the write to memory - conv->value = builder.makeUnary(DemoteFloat64, conv->value); + if (ast[1]->isAssign()) { + auto* assign = ast[1]->asAssign(); + Ref target = assign->target(); + if (target->isArray(SUB) && target[1]->isString() && target[2]->isArray(BINARY) && target[2][1] == RSHIFT && + target[2][2]->isString() && target[2][2] == tempDoublePtr && target[2][3]->isNumber() && target[2][3]->getNumber() == 2) { + // (?[tempDoublePtr >> 2] = ?, ?) so far + auto heap = target[1]->getIString(); + if (views.find(heap) != views.end()) { + AsmType writeType = views[heap].type; + AsmType readType = ASM_NONE; + Ref readValue; + if (ast[2]->isArray(BINARY) && ast[2][1] == OR && ast[2][3]->isNumber() && ast[2][3]->getNumber() == 0) { + readType = ASM_INT; + readValue = ast[2][2]; + } else if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == PLUS) { + readType = ASM_DOUBLE; + readValue = ast[2][2]; + } else if (ast[2]->isArray(CALL) && ast[2][1]->isString() && ast[2][1] == Math_fround) { + readType = ASM_FLOAT; + readValue = ast[2][2][0]; + } + if (readType != ASM_NONE) { + if (readValue->isArray(SUB) && readValue[1]->isString() && readValue[2]->isArray(BINARY) && readValue[2][1] == RSHIFT && + readValue[2][2]->isString() && readValue[2][2] == tempDoublePtr && readValue[2][3]->isNumber() && readValue[2][3]->getNumber() == 2) { + // pattern looks right! + Ref writtenValue = assign->value(); + if (writeType == ASM_INT && (readType == ASM_FLOAT || readType == ASM_DOUBLE)) { + auto conv = allocator.alloc<Unary>(); + conv->op = ReinterpretInt32; + conv->value = process(writtenValue); + conv->type = WasmType::f32; + if (readType == ASM_DOUBLE) { + auto promote = allocator.alloc<Unary>(); + promote->op = PromoteFloat32; + promote->value = conv; + promote->type = WasmType::f64; + return promote; + } + return conv; + } else if (writeType == ASM_FLOAT && readType == ASM_INT) { + auto conv = allocator.alloc<Unary>(); + conv->op = ReinterpretFloat32; + conv->value = process(writtenValue); + if (conv->value->type == f64) { + // this has an implicit f64->f32 in the write to memory + conv->value = builder.makeUnary(DemoteFloat64, conv->value); + } + conv->type = WasmType::i32; + return conv; } - conv->type = WasmType::i32; - return conv; } } } @@ -2242,12 +2247,12 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { processUnshifted = [&](Ref ptr, unsigned bytes) { auto shifts = bytesToShift(bytes); // HEAP?[addr >> ?], or HEAP8[x | 0] - if ((ptr[0] == BINARY && ptr[1] == RSHIFT && ptr[3][0] == NUM && ptr[3][1]->getInteger() == shifts) || - (bytes == 1 && ptr[0] == BINARY && ptr[1] == OR && ptr[3][0] == NUM && ptr[3][1]->getInteger() == 0)) { + if ((ptr->isArray(BINARY) && ptr[1] == RSHIFT && ptr[3]->isNumber() && ptr[3]->getInteger() == shifts) || + (bytes == 1 && ptr->isArray(BINARY) && ptr[1] == OR && ptr[3]->isNumber() && ptr[3]->getInteger() == 0)) { return process(ptr[2]); // look through it - } else if (ptr[0] == NUM) { + } else if (ptr->isNumber()) { // constant, apply a shift (e.g. HEAP32[1] is address 4) - unsigned addr = ptr[1]->getInteger(); + unsigned addr = ptr->getInteger(); unsigned shifted = addr << shifts; return (Expression*)builder.makeConst(Literal(int32_t(shifted))); } diff --git a/src/emscripten-optimizer/optimizer-shared.cpp b/src/emscripten-optimizer/optimizer-shared.cpp index 57b7921fa..7ba6d4a22 100644 --- a/src/emscripten-optimizer/optimizer-shared.cpp +++ b/src/emscripten-optimizer/optimizer-shared.cpp @@ -53,28 +53,26 @@ HeapInfo parseHeap(const char *name) { } AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFround, bool allowI64) { - switch (node[0]->getCString()[0]) { - case 'n': { - if (node[0] == NUM) { - if (!wasm::isInteger(node[1]->getNumber())) return ASM_DOUBLE; - return ASM_INT; - } else if (node[0] == NAME) { - if (asmData) { - AsmType ret = asmData->getType(node[1]->getCString()); - if (ret != ASM_NONE) return ret; - } - if (!inVarDef) { - if (node[1] == INF || node[1] == NaN) return ASM_DOUBLE; - if (node[1] == TEMP_RET0) return ASM_INT; - return ASM_NONE; - } - // We are in a variable definition, where Math_fround(0) optimized into a global constant becomes f0 = Math_fround(0) - if (ASM_FLOAT_ZERO.isNull()) ASM_FLOAT_ZERO = node[1]->getIString(); - else assert(node[1] == ASM_FLOAT_ZERO); - return ASM_FLOAT; - } - break; + if (node->isString()) { + if (asmData) { + AsmType ret = asmData->getType(node->getCString()); + if (ret != ASM_NONE) return ret; + } + if (!inVarDef) { + if (node == INF || node == NaN) return ASM_DOUBLE; + if (node == TEMP_RET0) return ASM_INT; + return ASM_NONE; } + // We are in a variable definition, where Math_fround(0) optimized into a global constant becomes f0 = Math_fround(0) + if (ASM_FLOAT_ZERO.isNull()) ASM_FLOAT_ZERO = node->getIString(); + else assert(node == ASM_FLOAT_ZERO); + return ASM_FLOAT; + } + if (node->isNumber()) { + if (!wasm::isInteger(node->getNumber())) return ASM_DOUBLE; + return ASM_INT; + } + switch (node[0]->getCString()[0]) { case 'u': { if (node[0] == UNARY_PREFIX) { switch (node[1]->getCString()[0]) { @@ -88,8 +86,8 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr } case 'c': { if (node[0] == CALL) { - if (node[1][0] == NAME) { - IString name = node[1][1]->getIString(); + if (node[1]->isString()) { + IString name = node[1]->getIString(); if (name == MATH_FROUND || name == minifiedFround) return ASM_FLOAT; else if (allowI64 && (name == INT64 || name == INT64_CONST)) return ASM_INT64; else if (name == SIMD_FLOAT32X4 || name == SIMD_FLOAT32X4_CHECK) return ASM_FLOAT32X4; @@ -121,7 +119,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr if (node[0] == SEQ) { return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64); } else if (node[0] == SUB) { - assert(node[1][0] == NAME); + assert(node[1]->isString()); HeapInfo info = parseHeap(node[1][1]->getCString()); if (info.valid) return ASM_NONE; return info.floaty ? ASM_DOUBLE : ASM_INT; // XXX ASM_FLOAT? @@ -141,6 +139,16 @@ static void abort_on(Ref node) { } AsmSign detectSign(Ref node, IString minifiedFround) { + if (node->isString()) { + return ASM_FLEXIBLE; + } + if (node->isNumber()) { + double value = node->getNumber(); + if (value < 0) return ASM_SIGNED; + if (value > uint32_t(-1) || fmod(value, 1) != 0) return ASM_NONSIGNED; + if (wasm::isSInteger32(value)) return ASM_FLEXIBLE; + return ASM_UNSIGNED; + } IString type = node[0]->getIString(); if (type == BINARY) { IString op = node[1]->getIString(); @@ -162,18 +170,10 @@ AsmSign detectSign(Ref node, IString minifiedFround) { case '~': return ASM_SIGNED; default: abort_on(node); } - } else if (type == NUM) { - double value = node[1]->getNumber(); - if (value < 0) return ASM_SIGNED; - if (value > uint32_t(-1) || fmod(value, 1) != 0) return ASM_NONSIGNED; - if (wasm::isSInteger32(value)) return ASM_FLEXIBLE; - return ASM_UNSIGNED; - } else if (type == NAME) { - return ASM_FLEXIBLE; } else if (type == CONDITIONAL) { return detectSign(node[2], minifiedFround); } else if (type == CALL) { - if (node[1][0] == NAME && (node[1][1] == MATH_FROUND || node[1][1] == minifiedFround)) return ASM_NONSIGNED; + if (node[1]->isString() && (node[1] == MATH_FROUND || node[1] == minifiedFround)) return ASM_NONSIGNED; } else if (type == SEQ) { return detectSign(node[2], minifiedFround); } diff --git a/src/emscripten-optimizer/optimizer.h b/src/emscripten-optimizer/optimizer.h index dc73962c5..c3939abf4 100644 --- a/src/emscripten-optimizer/optimizer.h +++ b/src/emscripten-optimizer/optimizer.h @@ -144,11 +144,6 @@ enum AsmSign { extern AsmSign detectSign(cashew::Ref node, cashew::IString minifiedFround); -inline cashew::Ref deStat(cashew::Ref node) { - if (node[0] == cashew::STAT) return node[1]; - return node; -} - cashew::Ref makeAsmCoercedZero(AsmType type); cashew::Ref makeAsmCoercion(cashew::Ref node, AsmType type); diff --git a/src/emscripten-optimizer/parser.cpp b/src/emscripten-optimizer/parser.cpp index 064c0efcf..227ce66a5 100644 --- a/src/emscripten-optimizer/parser.cpp +++ b/src/emscripten-optimizer/parser.cpp @@ -23,9 +23,6 @@ namespace cashew { IString TOPLEVEL("toplevel"), DEFUN("defun"), BLOCK("block"), - STAT("stat"), - ASSIGN("assign"), - NAME("name"), VAR("var"), CONST("const"), CONDITIONAL("conditional"), @@ -39,7 +36,6 @@ IString TOPLEVEL("toplevel"), SEQ("seq"), SUB("sub"), CALL("call"), - NUM("num"), LABEL("label"), BREAK("break"), CONTINUE("continue"), diff --git a/src/emscripten-optimizer/parser.h b/src/emscripten-optimizer/parser.h index 060b71272..5c9aa2c8c 100644 --- a/src/emscripten-optimizer/parser.h +++ b/src/emscripten-optimizer/parser.h @@ -38,9 +38,6 @@ namespace cashew { extern IString TOPLEVEL, DEFUN, BLOCK, - STAT, - ASSIGN, - NAME, VAR, CONST, CONDITIONAL, @@ -54,7 +51,6 @@ extern IString TOPLEVEL, SEQ, SUB, CALL, - NUM, LABEL, BREAK, CONTINUE, diff --git a/src/emscripten-optimizer/simple_ast.cpp b/src/emscripten-optimizer/simple_ast.cpp index dddaeab02..75685f80e 100644 --- a/src/emscripten-optimizer/simple_ast.cpp +++ b/src/emscripten-optimizer/simple_ast.cpp @@ -54,31 +54,124 @@ bool Ref::operator!() { // Arena -Arena arena; +GlobalMixedArena arena; -Arena::~Arena() { - for (auto* chunk : chunks) { - delete[] chunk; - } - for (auto* chunk : arr_chunks) { - delete[] chunk; - } +// Value + +Value& Value::setAssign(Ref target, Ref value) { + asAssign()->target() = target; + asAssign()->value() = value; + return *this; } -Ref Arena::alloc() { - if (chunks.size() == 0 || index == CHUNK_SIZE) { - chunks.push_back(new Value[CHUNK_SIZE]); - index = 0; - } - return &chunks.back()[index++]; +Value& Value::setAssignName(IString target, Ref value) { + asAssignName()->target() = target; + asAssignName()->value() = value; + return *this; +} + +Assign* Value::asAssign() { + assert(isAssign()); + return static_cast<Assign*>(this); +} + +AssignName* Value::asAssignName() { + assert(isAssignName()); + return static_cast<AssignName*>(this); } -ArrayStorage* Arena::allocArray() { - if (arr_chunks.size() == 0 || arr_index == CHUNK_SIZE) { - arr_chunks.push_back(new ArrayStorage[CHUNK_SIZE]); - arr_index = 0; +void Value::stringify(std::ostream &os, bool pretty) { + static int indent = 0; + #define indentify() { for (int i_ = 0; i_ < indent; i_++) os << " "; } + switch (type) { + case String: { + if (str.str) { + os << '"' << str.str << '"'; + } else { + os << "\"(null)\""; + } + break; + } + case Number: { + os << std::setprecision(17) << num; // doubles can have 17 digits of precision + break; + } + case Array: { + if (arr->size() == 0) { + os << "[]"; + break; + } + os << '['; + if (pretty) { + os << std::endl; + indent++; + } + for (size_t i = 0; i < arr->size(); i++) { + if (i > 0) { + if (pretty) os << "," << std::endl; + else os << ", "; + } + indentify(); + (*arr)[i]->stringify(os, pretty); + } + if (pretty) { + os << std::endl; + indent--; + } + indentify(); + os << ']'; + break; + } + case Null: { + os << "null"; + break; + } + case Bool: { + os << (boo ? "true" : "false"); + break; + } + case Object: { + os << '{'; + if (pretty) { + os << std::endl; + indent++; + } + bool first = true; + for (auto i : *obj) { + if (first) { + first = false; + } else { + os << ", "; + if (pretty) os << std::endl; + } + indentify(); + os << '"' << i.first.c_str() << "\": "; + i.second->stringify(os, pretty); + } + if (pretty) { + os << std::endl; + indent--; + } + indentify(); + os << '}'; + break; + } + case Assign_: { + os << "["; + ref->stringify(os, pretty); + os << ", "; + asAssign()->value()->stringify(os, pretty); + os << "]"; + break; + } + case AssignName_: { + os << "[\"" << asAssignName()->target().str << "\""; + os << ", "; + asAssignName()->value()->stringify(os, pretty); + os << "]"; + break; + } } - return &arr_chunks.back()[arr_index++]; } // dump @@ -161,7 +254,7 @@ void traversePre(Ref node, std::function<void (Ref)> visit) { int index = 0; ArrayStorage* arr = &node->getArray(); int arrsize = (int)arr->size(); - Ref* arrdata = arr->data(); + Ref* arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(node, arr)); while (1) { if (index < arrsize) { @@ -173,7 +266,7 @@ void traversePre(Ref node, std::function<void (Ref)> visit) { visit(sub); arr = &sub->getArray(); arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(sub, arr)); } } else { @@ -183,7 +276,7 @@ void traversePre(Ref node, std::function<void (Ref)> visit) { index = back.index; arr = back.arr; arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; } } } @@ -196,7 +289,7 @@ void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function int index = 0; ArrayStorage* arr = &node->getArray(); int arrsize = (int)arr->size(); - Ref* arrdata = arr->data(); + Ref* arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(node, arr)); while (1) { if (index < arrsize) { @@ -208,7 +301,7 @@ void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function visitPre(sub); arr = &sub->getArray(); arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(sub, arr)); } } else { @@ -219,7 +312,7 @@ void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function index = back.index; arr = back.arr; arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; } } } @@ -232,7 +325,7 @@ void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, st int index = 0; ArrayStorage* arr = &node->getArray(); int arrsize = (int)arr->size(); - Ref* arrdata = arr->data(); + Ref* arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(node, arr)); while (1) { if (index < arrsize) { @@ -244,7 +337,7 @@ void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, st index = 0; arr = &sub->getArray(); arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; stack.push_back(TraverseInfo(sub, arr)); } } @@ -256,7 +349,7 @@ void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, st index = back.index; arr = back.arr; arrsize = (int)arr->size(); - arrdata = arr->data(); + arrdata = &(*arr)[0]; } } } @@ -275,8 +368,4 @@ void traverseFunctions(Ref ast, std::function<void (Ref)> visit) { } } -// ValueBuilder - -IStringSet ValueBuilder::statable("assign call binary unary-prefix name num conditional dot new sub seq string object array"); - } // namespace cashew diff --git a/src/emscripten-optimizer/simple_ast.h b/src/emscripten-optimizer/simple_ast.h index 20de952c1..efbd7b6f6 100644 --- a/src/emscripten-optimizer/simple_ast.h +++ b/src/emscripten-optimizer/simple_ast.h @@ -36,6 +36,7 @@ #include "parser.h" #include "snprintf.h" #include "support/safe_integer.h" +#include "mixed_arena.h" #define err(str) fprintf(stderr, str "\n"); #define errv(str, ...) fprintf(stderr, str "\n", __VA_ARGS__); @@ -43,8 +44,8 @@ namespace cashew { -struct Ref; struct Value; +struct Ref; void dump(const char *str, Ref node, bool pretty=false); @@ -73,24 +74,30 @@ struct Ref { // Arena allocation, free it all on process exit -typedef std::vector<Ref> ArrayStorage; - -struct Arena { - #define CHUNK_SIZE 1000 - std::vector<Value*> chunks; - int index; // in last chunk - - std::vector<ArrayStorage*> arr_chunks; - int arr_index; +// A mixed arena for global allocation only, so members do not +// receive an allocator, they all use the global one anyhow +class GlobalMixedArena : public MixedArena { +public: + template<class T> + T* alloc() { + auto* ret = static_cast<T*>(allocSpace(sizeof(T))); + new (ret) T(); + return ret; + } +}; - Arena() : index(0), arr_index(0) {} - ~Arena(); +extern GlobalMixedArena arena; - Ref alloc(); - ArrayStorage* allocArray(); +class ArrayStorage : public ArenaVectorBase<ArrayStorage, Ref> { +public: + void allocate(size_t size) { + allocatedElements = size; + data = static_cast<Ref*>(arena.allocSpace(sizeof(Ref) * allocatedElements)); + } }; -extern Arena arena; +struct Assign; +struct AssignName; // Main value type struct Value { @@ -100,7 +107,9 @@ struct Value { Array = 2, Null = 3, Bool = 4, - Object = 5 + Object = 5, + Assign_ = 6, // ref = target + AssignName_ = 7 }; Type type; @@ -118,6 +127,7 @@ struct Value { ArrayStorage *arr; bool boo; ObjectStorage *obj; + Ref ref; }; // constructors all copy their input @@ -139,7 +149,7 @@ struct Value { } void free() { - if (type == Array) { arr->clear(); arr->shrink_to_fit(); } + if (type == Array) { arr->clear(); } else if (type == Object) delete obj; type = Null; num = 0; @@ -166,14 +176,14 @@ struct Value { Value& setArray(ArrayStorage &a) { free(); type = Array; - arr = arena.allocArray(); + arr = arena.alloc<ArrayStorage>(); *arr = a; return *this; } Value& setArray(size_t size_hint=0) { free(); type = Array; - arr = arena.allocArray(); + arr = arena.alloc<ArrayStorage>(); arr->reserve(size_hint); return *this; } @@ -194,16 +204,28 @@ struct Value { obj = new ObjectStorage(); return *this; } + Value& setAssign(Ref target, Ref value); + Value& setAssignName(IString target, Ref value); bool isString() { return type == String; } bool isNumber() { return type == Number; } bool isArray() { return type == Array; } bool isNull() { return type == Null; } bool isBool() { return type == Bool; } - bool isObject() { return type == Object; } + bool isObject() { return type == Object; } + bool isAssign() { return type == Assign_; } + bool isAssignName() { return type == AssignName_; } bool isBool(bool b) { return type == Bool && b == boo; } // avoid overloading == as it might overload over int + // convenience function to check if something is an array and + // also has a certain string as the first element. This is a + // very common operation as the first element defines the node + // type for most ast nodes + bool isArray(IString name) { + return isArray() && (*this)[0] == name; + } + const char* getCString() { assert(isString()); return str.str; @@ -225,6 +247,9 @@ struct Value { return boo; } + Assign* asAssign(); + AssignName* asAssignName(); + int32_t getInteger() { // convenience function to get a known integer assert(fmod(getNumber(), 1) == 0); int32_t ret = getNumber(); @@ -250,7 +275,7 @@ struct Value { case Bool: setBool(other.boo); break; - case Object: + default: abort(); // TODO } return *this; @@ -271,31 +296,12 @@ struct Value { return boo == other.boo; case Object: return this == &other; // if you want a deep compare, use deepCompare + default: + abort(); } return true; } - bool deepCompare(Ref ref) { - Value& other = *ref; - if (*this == other) return true; // either same pointer, or identical value type (string, number, null or bool) - if (type != other.type) return false; - if (type == Array) { - if (arr->size() != other.arr->size()) return false; - for (unsigned i = 0; i < arr->size(); i++) { - if (!(*arr)[i]->deepCompare((*other.arr)[i])) return false; - } - return true; - } else if (type == Object) { - if (obj->size() != other.obj->size()) return false; - for (auto i : *obj) { - if (other.obj->count(i.first) == 0) return false; - if (i.second->deepCompare((*other.obj)[i.first])) return false; - } - return true; - } - return false; - } - char* parse(char* curr) { #define is_json_space(x) (x == 32 || x == 9 || x == 10 || x == 13) /* space, tab, linefeed/newline, or return */ #define skip() { while (*curr && is_json_space(*curr)) curr++; } @@ -314,7 +320,7 @@ struct Value { skip(); setArray(); while (*curr != ']') { - Ref temp = arena.alloc(); + Ref temp = arena.alloc<Value>(); arr->push_back(temp); curr = temp->parse(curr); skip(); @@ -356,7 +362,7 @@ struct Value { assert(*curr == ':'); curr++; skip(); - Ref value = arena.alloc(); + Ref value = arena.alloc<Value>(); curr = value->parse(curr); (*obj)[key] = value; skip(); @@ -375,78 +381,7 @@ struct Value { return curr; } - void stringify(std::ostream &os, bool pretty=false) { - static int indent = 0; - #define indentify() { for (int i_ = 0; i_ < indent; i_++) os << " "; } - switch (type) { - case String: - if (str.str) { - os << '"' << str.str << '"'; - } else { - os << "\"(null)\""; - } - break; - case Number: - os << std::setprecision(17) << num; // doubles can have 17 digits of precision - break; - case Array: - if (arr->size() == 0) { - os << "[]"; - break; - } - os << '['; - if (pretty) { - os << std::endl; - indent++; - } - for (size_t i = 0; i < arr->size(); i++) { - if (i > 0) { - if (pretty) os << "," << std::endl; - else os << ", "; - } - indentify(); - (*arr)[i]->stringify(os, pretty); - } - if (pretty) { - os << std::endl; - indent--; - } - indentify(); - os << ']'; - break; - case Null: - os << "null"; - break; - case Bool: - os << (boo ? "true" : "false"); - break; - case Object: - os << '{'; - if (pretty) { - os << std::endl; - indent++; - } - bool first = true; - for (auto i : *obj) { - if (first) { - first = false; - } else { - os << ", "; - if (pretty) os << std::endl; - } - indentify(); - os << '"' << i.first.c_str() << "\": "; - i.second->stringify(os, pretty); - } - if (pretty) { - os << std::endl; - indent--; - } - indentify(); - os << '}'; - break; - } - } + void stringify(std::ostream &os, bool pretty=false); // String operations @@ -465,14 +400,14 @@ struct Value { if (old != size) arr->resize(size); if (old < size) { for (auto i = old; i < size; i++) { - (*arr)[i] = arena.alloc(); + (*arr)[i] = arena.alloc<Value>(); } } } Ref& operator[](unsigned x) { assert(isArray()); - return arr->at(x); + return (*arr)[x]; } Value& push_back(Ref r) { @@ -493,18 +428,6 @@ struct Value { return arr->back(); } - void splice(int x, int num) { - assert(isArray()); - arr->erase(arr->begin() + x, arr->begin() + x + num); - } - - void insert(int x, int num) { - arr->insert(arr->begin() + x, num, Ref()); - } - void insert(int x, Ref node) { - arr->insert(arr->begin() + x, 1, node); - } - int indexOf(Ref other) { assert(isArray()); for (size_t i = 0; i < arr->size(); i++) { @@ -515,7 +438,7 @@ struct Value { Ref map(std::function<Ref (Ref node)> func) { assert(isArray()); - Ref ret = arena.alloc(); + Ref ret = arena.alloc<Value>(); ret->setArray(); for (size_t i = 0; i < arr->size(); i++) { ret->push_back(func((*arr)[i])); @@ -525,7 +448,7 @@ struct Value { Ref filter(std::function<bool (Ref node)> func) { assert(isArray()); - Ref ret = arena.alloc(); + Ref ret = arena.alloc<Value>(); ret->setArray(); for (size_t i = 0; i < arr->size(); i++) { Ref curr = (*arr)[i]; @@ -559,281 +482,61 @@ struct Value { } }; -// AST traversals - -// Traverse, calling visit before the children -void traversePre(Ref node, std::function<void (Ref)> visit); - -// Traverse, calling visitPre before the children and visitPost after -void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost); - -// Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children -void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, std::function<void (Ref)> visitPost); - -// Traverses all the top-level functions in the document -void traverseFunctions(Ref ast, std::function<void (Ref)> visit); - -// JS printer - -struct JSPrinter { - bool pretty, finalize; - - char *buffer; - size_t size, used; - - int indent; - bool possibleSpace; // add a space to separate identifiers - - Ref ast; +struct Assign : public Value { + Ref value_; - JSPrinter(bool pretty_, bool finalize_, Ref ast_) : pretty(pretty_), finalize(finalize_), buffer(0), size(0), used(0), indent(0), possibleSpace(false), ast(ast_) {} - - void printAst() { - print(ast); - buffer[used] = 0; + Assign(Ref targetInit, Ref valueInit) { + type = Assign_; + target() = targetInit; + value() = valueInit; } - // Utils + Assign() : Assign(nullptr, nullptr) {} - void ensure(int safety=100) { - if (size < used + safety) { - size = std::max((size_t)1024, size * 2) + safety; - if (!buffer) { - buffer = (char*)malloc(size); - if (!buffer) { - printf("Out of memory allocating %zd bytes for output buffer!", size); - abort(); - } - } else { - char *buf = (char*)realloc(buffer, size); - if (!buf) { - free(buffer); - printf("Out of memory allocating %zd bytes for output buffer!", size); - abort(); - } - buffer = buf; - } - } + Ref& target() { + return ref; } - - void emit(char c) { - maybeSpace(c); - if (!pretty && c == '}' && buffer[used-1] == ';') used--; // optimize ;} into }, the ; is not separating anything - ensure(1); - buffer[used++] = c; + Ref& value() { + return value_; } +}; - void emit(const char *s) { - maybeSpace(*s); - int len = strlen(s); - ensure(len+1); - strncpy(buffer + used, s, len+1); - used += len; - } +struct AssignName : public Value { + IString target_; - void newline() { - if (!pretty) return; - emit('\n'); - for (int i = 0; i < indent; i++) emit(' '); + AssignName(IString targetInit, Ref valueInit) { + type = AssignName_; + target() = targetInit; + value() = valueInit; } - void space() { - if (pretty) emit(' '); - } + AssignName() : AssignName(IString(), nullptr) {} - void safeSpace() { - if (pretty) emit(' '); - else possibleSpace = true; + IString& target() { + return target_; } - - void maybeSpace(char s) { - if (possibleSpace) { - possibleSpace = false; - if (isIdentPart(s)) emit(' '); - } - } - - void print(Ref node) { - ensure(); - IString type = node[0]->getIString(); - //fprintf(stderr, "printing %s\n", type.str); - switch (type.str[0]) { - case 'a': { - if (type == ASSIGN) printAssign(node); - else if (type == ARRAY) printArray(node); - else abort(); - break; - } - case 'b': { - if (type == BINARY) printBinary(node); - else if (type == BLOCK) printBlock(node); - else if (type == BREAK) printBreak(node); - else abort(); - break; - } - case 'c': { - if (type == CALL) printCall(node); - else if (type == CONDITIONAL) printConditional(node); - else if (type == CONTINUE) printContinue(node); - else abort(); - break; - } - case 'd': { - if (type == DEFUN) printDefun(node); - else if (type == DO) printDo(node); - else if (type == DOT) printDot(node); - else abort(); - break; - } - case 'i': { - if (type == IF) printIf(node); - else abort(); - break; - } - case 'l': { - if (type == LABEL) printLabel(node); - else abort(); - break; - } - case 'n': { - if (type == NAME) printName(node); - else if (type == NUM) printNum(node); - else if (type == NEW) printNew(node); - else abort(); - break; - } - case 'o': { - if (type == OBJECT) printObject(node); - break; - } - case 'r': { - if (type == RETURN) printReturn(node); - else abort(); - break; - } - case 's': { - if (type == STAT) printStat(node); - else if (type == SUB) printSub(node); - else if (type == SEQ) printSeq(node); - else if (type == SWITCH) printSwitch(node); - else if (type == STRING) printString(node); - else abort(); - break; - } - case 't': { - if (type == TOPLEVEL) printToplevel(node); - else abort(); - break; - } - case 'u': { - if (type == UNARY_PREFIX) printUnaryPrefix(node); - else abort(); - break; - } - case 'v': { - if (type == VAR) printVar(node); - else abort(); - break; - } - case 'w': { - if (type == WHILE) printWhile(node); - else abort(); - break; - } - default: { - printf("cannot yet print %s\n", type.str); - abort(); - } - } - } - - // print a node, and if nothing is emitted, emit something instead - void print(Ref node, const char *otherwise) { - auto last = used; - print(node); - if (used == last) emit(otherwise); - } - - void printStats(Ref stats) { - bool first = true; - for (size_t i = 0; i < stats->size(); i++) { - Ref curr = stats[i]; - if (!isNothing(curr)) { - if (first) first = false; - else newline(); - print(stats[i]); - } - } + Ref& value() { + return ref; } +}; - void printToplevel(Ref node) { - if (node[1]->size() > 0) { - printStats(node[1]); - } - } +// AST traversals - void printBlock(Ref node) { - if (node->size() == 1 || node[1]->size() == 0) { - emit("{}"); - return; - } - emit('{'); - indent++; - newline(); - printStats(node[1]); - indent--; - newline(); - emit('}'); - } - - void printDefun(Ref node) { - emit("function "); - emit(node[1]->getCString()); - emit('('); - Ref args = node[2]; - for (size_t i = 0; i < args->size(); i++) { - if (i > 0) (pretty ? emit(", ") : emit(',')); - emit(args[i]->getCString()); - } - emit(')'); - space(); - if (node->size() == 3 || node[3]->size() == 0) { - emit("{}"); - return; - } - emit('{'); - indent++; - newline(); - printStats(node[3]); - indent--; - newline(); - emit('}'); - newline(); - } +// Traverse, calling visit before the children +void traversePre(Ref node, std::function<void (Ref)> visit); - bool isNothing(Ref node) { - return (node[0] == TOPLEVEL && node[1]->size() == 0) || (node[0] == STAT && isNothing(node[1])); - } +// Traverse, calling visitPre before the children and visitPost after +void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost); - void printStat(Ref node) { - if (!isNothing(node[1])) { - print(node[1]); - if (buffer[used-1] != ';') emit(';'); - } - } +// Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children +void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, std::function<void (Ref)> visitPost); - void printAssign(Ref node) { - printChild(node[2], node, -1); - space(); - emit('='); - space(); - printChild(node[3], node, 1); - } +// Traverses all the top-level functions in the document +void traverseFunctions(Ref ast, std::function<void (Ref)> visit); - void printName(Ref node) { - emit(node[1]->getCString()); - } +// JS printing support +struct JSPrinter { static char* numToString(double d, bool finalize=true) { bool neg = d < 0; if (neg) d = -d; @@ -955,379 +658,22 @@ struct JSPrinter { } return ret; } - - void printNum(Ref node) { - emit(numToString(node[1]->getNumber(), finalize)); - } - - void printString(Ref node) { - emit('"'); - emit(node[1]->getCString()); - emit('"'); - } - - // Parens optimizing - - bool capturesOperators(Ref node) { - Ref type = node[0]; - return type == CALL || type == ARRAY || type == OBJECT || type == SEQ; - } - - int getPrecedence(Ref node, bool parent) { - Ref type = node[0]; - if (type == BINARY || type == UNARY_PREFIX) { - return OperatorClass::getPrecedence(type == BINARY ? OperatorClass::Binary : OperatorClass::Prefix, node[1]->getIString()); - } else if (type == SEQ) { - return OperatorClass::getPrecedence(OperatorClass::Binary, COMMA); - } else if (type == CALL) { - return parent ? OperatorClass::getPrecedence(OperatorClass::Binary, COMMA) : -1; // call arguments are split by commas, but call itself is safe - } else if (type == ASSIGN) { - return OperatorClass::getPrecedence(OperatorClass::Binary, SET); - } else if (type == CONDITIONAL) { - return OperatorClass::getPrecedence(OperatorClass::Tertiary, QUESTION); - } - // otherwise, this is something that fixes precedence explicitly, and we can ignore - return -1; // XXX - } - - // check whether we need parens for the child, when rendered in the parent - // @param childPosition -1 means it is printed to the left of parent, 0 means "anywhere", 1 means right - bool needParens(Ref parent, Ref child, int childPosition) { - int parentPrecedence = getPrecedence(parent, true); - int childPrecedence = getPrecedence(child, false); - - if (childPrecedence > parentPrecedence) return true; // child is definitely a danger - if (childPrecedence < parentPrecedence) return false; // definitely cool - // equal precedence, so associativity (rtl/ltr) is what matters - // (except for some exceptions, where multiple operators can combine into confusion) - if (parent[0] == UNARY_PREFIX) { - assert(child[0] == UNARY_PREFIX); - if ((parent[1] == PLUS || parent[1] == MINUS) && child[1] == parent[1]) { - // cannot emit ++x when we mean +(+x) - return true; - } - } - if (childPosition == 0) return true; // child could be anywhere, so always paren - if (childPrecedence < 0) return false; // both precedences are safe - // check if child is on the dangerous side - if (OperatorClass::getRtl(parentPrecedence)) return childPosition < 0; - else return childPosition > 0; - } - - void printChild(Ref child, Ref parent, int childPosition=0) { - bool parens = needParens(parent, child, childPosition); - if (parens) emit('('); - print(child); - if (parens) emit(')'); - } - - void printBinary(Ref node) { - printChild(node[2], node, -1); - space(); - emit(node[1]->getCString()); - space(); - printChild(node[3], node, 1); - } - - void printUnaryPrefix(Ref node) { - if (finalize && node[1] == PLUS && (node[2][0] == NUM || - (node[2][0] == UNARY_PREFIX && node[2][1] == MINUS && node[2][2][0] == NUM))) { - // emit a finalized number - int last = used; - print(node[2]); - ensure(1); // we temporarily append a 0 - char *curr = buffer + last; // ensure might invalidate - buffer[used] = 0; - if (strchr(curr, '.')) return; // already a decimal point, all good - char *e = strchr(curr, 'e'); - if (!e) { - emit(".0"); - return; - } - ensure(3); - curr = buffer + last; // ensure might invalidate - char *end = strchr(curr, 0); - while (end >= e) { - end[2] = end[0]; - end--; - } - e[0] = '.'; - e[1] = '0'; - used += 2; - return; - } - if ((buffer[used-1] == '-' && node[1] == MINUS) || - (buffer[used-1] == '+' && node[1] == PLUS)) { - emit(' '); // cannot join - and - to --, looks like the -- operator - } - emit(node[1]->getCString()); - printChild(node[2], node, 1); - } - - void printConditional(Ref node) { - printChild(node[1], node, -1); - space(); - emit('?'); - space(); - printChild(node[2], node, 0); - space(); - emit(':'); - space(); - printChild(node[3], node, 1); - } - - void printCall(Ref node) { - printChild(node[1], node, 0); - emit('('); - Ref args = node[2]; - for (size_t i = 0; i < args->size(); i++) { - if (i > 0) (pretty ? emit(", ") : emit(',')); - printChild(args[i], node, 0); - } - emit(')'); - } - - void printSeq(Ref node) { - printChild(node[1], node, -1); - emit(','); - space(); - printChild(node[2], node, 1); - } - - void printDot(Ref node) { - print(node[1]); - emit('.'); - emit(node[2]->getCString()); - } - - void printSwitch(Ref node) { - emit("switch"); - space(); - emit('('); - print(node[1]); - emit(')'); - space(); - emit('{'); - newline(); - Ref cases = node[2]; - for (size_t i = 0; i < cases->size(); i++) { - Ref c = cases[i]; - if (!c[0]) { - emit("default:"); - } else { - emit("case "); - print(c[0]); - emit(':'); - } - if (c[1]->size() > 0) { - indent++; - newline(); - auto curr = used; - printStats(c[1]); - indent--; - if (curr != used) newline(); - else used--; // avoid the extra indentation we added tentatively - } else { - newline(); - } - } - emit('}'); - } - - void printSub(Ref node) { - printChild(node[1], node, -1); - emit('['); - print(node[2]); - emit(']'); - } - - void printVar(Ref node) { - emit("var "); - Ref args = node[1]; - for (size_t i = 0; i < args->size(); i++) { - if (i > 0) (pretty ? emit(", ") : emit(',')); - emit(args[i][0]->getCString()); - if (args[i]->size() > 1) { - space(); - emit('='); - space(); - print(args[i][1]); - } - } - emit(';'); - } - - static bool ifHasElse(Ref node) { - assert(node[0] == IF); - return node->size() >= 4 && !!node[3]; - } - - void printIf(Ref node) { - emit("if"); - safeSpace(); - emit('('); - print(node[1]); - emit(')'); - space(); - // special case: we need braces to save us from ambiguity, if () { if () } else. otherwise else binds to inner if - // also need to recurse for if () { if () { } else { if () } else - // (note that this is only a problem if the if body has a single element in it, not a block or such, as then - // the block would be braced) - // this analysis is a little conservative - it assumes any child if could be confused with us, which implies - // all other braces vanished (the worst case for us, we are not saved by other braces). - bool needBraces = false; - bool hasElse = ifHasElse(node); - if (hasElse) { - Ref child = node[2]; - while (child[0] == IF) { - if (!ifHasElse(child)) { - needBraces = true; - break; - } - child = child[3]; // continue into the else - } - } - if (needBraces) { - emit('{'); - indent++; - newline(); - print(node[2]); - indent--; - newline(); - emit('}'); - } else { - print(node[2], "{}"); - } - if (hasElse) { - space(); - emit("else"); - safeSpace(); - print(node[3], "{}"); - } - } - - void printDo(Ref node) { - emit("do"); - safeSpace(); - print(node[2], "{}"); - space(); - emit("while"); - space(); - emit('('); - print(node[1]); - emit(')'); - emit(';'); - } - - void printWhile(Ref node) { - emit("while"); - space(); - emit('('); - print(node[1]); - emit(')'); - space(); - print(node[2], "{}"); - } - - void printLabel(Ref node) { - emit(node[1]->getCString()); - space(); - emit(':'); - space(); - print(node[2]); - } - - void printReturn(Ref node) { - emit("return"); - if (!!node[1]) { - emit(' '); - print(node[1]); - } - emit(';'); - } - - void printBreak(Ref node) { - emit("break"); - if (!!node[1]) { - emit(' '); - emit(node[1]->getCString()); - } - emit(';'); - } - - void printContinue(Ref node) { - emit("continue"); - if (!!node[1]) { - emit(' '); - emit(node[1]->getCString()); - } - emit(';'); - } - - void printNew(Ref node) { - emit("new "); - print(node[1]); - } - - void printArray(Ref node) { - emit('['); - Ref args = node[1]; - for (size_t i = 0; i < args->size(); i++) { - if (i > 0) (pretty ? emit(", ") : emit(',')); - print(args[i]); - } - emit(']'); - } - - void printObject(Ref node) { - emit('{'); - indent++; - newline(); - Ref args = node[1]; - for (size_t i = 0; i < args->size(); i++) { - if (i > 0) { - pretty ? emit(", ") : emit(','); - newline(); - } - const char *str = args[i][0]->getCString(); - const char *check = str; - bool needQuote = false; - while (*check) { - if (!isalnum(*check) && *check != '_' && *check != '$') { - needQuote = true; - break; - } - check++; - } - if (needQuote) emit('"'); - emit(str); - if (needQuote) emit('"'); - emit(":"); - space(); - print(args[i][1]); - } - indent--; - newline(); - emit('}'); - } }; // cashew builder class ValueBuilder { - static IStringSet statable; - static Ref makeRawString(const IString& s) { - return &arena.alloc()->setString(s); + return &arena.alloc<Value>()->setString(s); } static Ref makeNull() { - return &arena.alloc()->setNull(); + return &arena.alloc<Value>()->setNull(); } public: static Ref makeRawArray(int size_hint=0) { - return &arena.alloc()->setArray(size_hint); + return &arena.alloc<Value>()->setArray(size_hint); } static Ref makeToplevel() { @@ -1346,8 +692,7 @@ public: } static Ref makeName(IString name) { - return &makeRawArray(2)->push_back(makeRawString(NAME)) - .push_back(makeRawString(name)); + return makeRawString(name); } static void setBlockContent(Ref target, Ref block) { @@ -1449,17 +794,11 @@ public: } static Ref makeStatement(Ref contents) { - if (statable.has(contents[0]->getIString())) { - return &makeRawArray(2)->push_back(makeRawString(STAT)) - .push_back(contents); - } else { - return contents; // only very specific things actually need to be stat'ed - } + return contents; } static Ref makeDouble(double num) { - return &makeRawArray(2)->push_back(makeRawString(NUM)) - .push_back(&arena.alloc()->setNumber(num)); + return &arena.alloc<Value>()->setNumber(num); } static Ref makeInt(uint32_t num) { return makeDouble(double(num)); @@ -1476,10 +815,11 @@ public: static Ref makeBinary(Ref left, IString op, Ref right) { if (op == SET) { - return &makeRawArray(4)->push_back(makeRawString(ASSIGN)) - .push_back(&arena.alloc()->setBool(true)) - .push_back(left) - .push_back(right); + if (left->isString()) { + return &arena.alloc<AssignName>()->setAssignName(left->getIString(), right); + } else { + return &arena.alloc<Assign>()->setAssign(left, right); + } } else if (op == COMMA) { return &makeRawArray(3)->push_back(makeRawString(SEQ)) .push_back(left) @@ -1626,8 +966,8 @@ public: } static Ref makeDot(Ref obj, Ref key) { - assert(key[0] == NAME); - return makeDot(obj, key[1]->getIString()); + assert(key->isString()); + return makeDot(obj, key->getIString()); } static Ref makeNew(Ref call) { @@ -1656,19 +996,6 @@ public: .push_back(value)); } - static Ref makeAssign(Ref target, Ref value) { - return &makeRawArray(3)->push_back(makeRawString(ASSIGN)) - .push_back(&arena.alloc()->setBool(true)) - .push_back(target) - .push_back(value); - } - static Ref makeAssign(IString target, Ref value) { - return &makeRawArray(3)->push_back(makeRawString(ASSIGN)) - .push_back(&arena.alloc()->setBool(true)) - .push_back(makeName(target)) - .push_back(value); - } - static Ref makeSub(Ref obj, Ref index) { return &makeRawArray(2)->push_back(makeRawString(SUB)) .push_back(obj) diff --git a/src/mixed_arena.h b/src/mixed_arena.h index 28b2dd462..52e47fbde 100644 --- a/src/mixed_arena.h +++ b/src/mixed_arena.h @@ -144,39 +144,24 @@ struct MixedArena { // // A vector that allocates in an arena. // -// TODO: consider not saving the allocator, but requiring it be -// passed in when needed, would make this (and thus Blocks etc. -// smaller) -// // TODO: specialize on the initial size of the array -template <typename T> -class ArenaVector { - MixedArena& allocator; +template <typename SubType, typename T> +class ArenaVectorBase { +protected: T* data = nullptr; size_t usedElements = 0, allocatedElements = 0; - void allocate(size_t size) { - allocatedElements = size; - data = static_cast<T*>(allocator.allocSpace(sizeof(T) * allocatedElements)); - } - void reallocate(size_t size) { T* old = data; - allocate(size); + static_cast<SubType*>(this)->allocate(size); for (size_t i = 0; i < usedElements; i++) { data[i] = old[i]; } } public: - ArenaVector(MixedArena& allocator) : allocator(allocator) {} - - ArenaVector(ArenaVector<T>&& other) : allocator(other.allocator) { - *this = other; - } - T& operator[](size_t index) const { assert(index < usedElements); return data[index]; @@ -216,11 +201,21 @@ public: usedElements++; } + void clear() { + usedElements = 0; + } + + void reserve(size_t size) { + if (size > allocatedElements) { + reallocate(size); + } + } + template<typename ListType> void set(const ListType& list) { size_t size = list.size(); if (allocatedElements < size) { - allocate(size); + static_cast<SubType*>(this)->allocate(size); } for (size_t i = 0; i < size; i++) { data[i] = list[i]; @@ -228,14 +223,15 @@ public: usedElements = size; } - void operator=(ArenaVector<T>& other) { + void operator=(SubType& other) { set(other); } - void operator=(ArenaVector<T>&& other) { + void swap(SubType& other) { data = other.data; usedElements = other.usedElements; allocatedElements = other.allocatedElements; + other.data = nullptr; other.usedElements = other.allocatedElements = 0; } @@ -243,10 +239,10 @@ public: // iteration struct Iterator { - const ArenaVector<T>* parent; + const SubType* parent; size_t index; - Iterator(const ArenaVector<T>* parent, size_t index) : parent(parent), index(index) {} + Iterator(const SubType* parent, size_t index) : parent(parent), index(index) {} bool operator!=(const Iterator& other) const { return index != other.index || parent != other.parent; @@ -262,10 +258,38 @@ public: }; Iterator begin() const { - return Iterator(this, 0); + return Iterator(static_cast<const SubType*>(this), 0); } Iterator end() const { - return Iterator(this, usedElements); + return Iterator(static_cast<const SubType*>(this), usedElements); + } + + void allocate(size_t size) { + abort(); // must be implemented in children + } +}; + +// A vector that has an allocator for arena allocation +// +// TODO: consider not saving the allocator, but requiring it be +// passed in when needed, would make this (and thus Blocks etc. +// smaller) + +template <typename T> +class ArenaVector : public ArenaVectorBase<ArenaVector<T>, T> { +private: + MixedArena& allocator; + +public: + ArenaVector(MixedArena& allocator) : allocator(allocator) {} + + ArenaVector(ArenaVector<T>&& other) : allocator(other.allocator) { + *this = other; + } + + void allocate(size_t size) { + this->allocatedElements = size; + this->data = static_cast<T*>(allocator.allocSpace(sizeof(T) * this->allocatedElements)); } }; diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 467ffb4ed..383837beb 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -190,7 +190,7 @@ static void optimizeBlock(Block* curr, Module* module) { for (size_t j = i + 1; j < curr->list.size(); j++) { merged.push_back(curr->list[j]); } - curr->list = merged; + curr->list.swap(merged); more = true; changed = true; break; diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp index 757fc88ca..c2fc00562 100644 --- a/src/wasm-linker.cpp +++ b/src/wasm-linker.cpp @@ -82,11 +82,12 @@ void Linker::layout() { // behavior. for (auto* call : f.second) { auto type = call->type; - auto operands = std::move(call->operands); + ExpressionList operands(out.wasm.allocator); + operands.swap(call->operands); auto target = call->target; CallImport* newCall = ExpressionManipulator::convert<Call, CallImport>(call, out.wasm.allocator); newCall->type = type; - newCall->operands = std::move(operands); + newCall->operands.swap(operands); newCall->target = target; } } |