Adds test infrastructure for JS optimizer. Adds simple tests

This commit is contained in:
Alexey Andreev
2016-03-22 13:30:11 +03:00
parent a7fcbb614d
commit af7ddb4572
21 changed files with 473 additions and 2 deletions
@@ -17,6 +17,7 @@ package com.google.gwt.dev.js;
import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.JsLiteral.JsBooleanLiteral;
import com.google.dart.compiler.backend.js.ast.metadata.HasMetadata;
import com.google.gwt.dev.js.parserExceptions.JsParserException;
import com.google.gwt.dev.js.rhino.*;
import com.intellij.util.SmartList;
@@ -460,6 +461,12 @@ public class JsAstMapper {
private JsExpression mapExpression(Node exprNode) throws JsParserException {
JsNode unknown = map(exprNode);
if (unknown instanceof HasMetadata) {
HasMetadata metadataContainer = (HasMetadata) unknown;
metadataContainer.setData("line", exprNode.getLineno());
}
if (unknown instanceof JsExpression) {
return (JsExpression) unknown;
}
@@ -878,6 +885,12 @@ public class JsAstMapper {
private JsStatement mapStatement(Node nodeStmt) throws JsParserException {
JsNode unknown = map(nodeStmt);
if (unknown instanceof HasMetadata) {
HasMetadata metadataContainer = (HasMetadata) unknown;
metadataContainer.setData("line", nodeStmt.getLineno());
}
if (unknown != null) {
if (unknown instanceof JsStatement) {
return (JsStatement) unknown;
@@ -328,8 +328,8 @@ public class IRFactory {
int lineno)
{
if (ifFalse == null)
return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue);
return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue, (Node)ifFalse);
return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue, lineno);
return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue, (Node)ifFalse, lineno);
}
public Object createTernary(Object cond, Object ifTrue, Object ifFalse) {
@@ -416,6 +416,7 @@ public class Node implements Cloneable {
case TokenStream.BREAK:
case TokenStream.CONTINUE:
case TokenStream.WITH:
case TokenStream.IF:
return true;
}
return false;
@@ -0,0 +1,147 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.test.optimizer
import com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
import com.google.dart.compiler.util.TextOutputImpl
import com.google.gwt.dev.js.rhino.CodePosition
import com.google.gwt.dev.js.rhino.ErrorReporter
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.test.BasicTest
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.junit.Assert
import org.junit.Rule
import org.junit.rules.TestName
import org.mozilla.javascript.Context
import java.io.File
abstract class BasicOptimizerTest(private var basePath: String) {
@Rule
@JvmField
var testName = TestName()
protected fun box() {
val methodName = testName.methodName
val baseName = "${BasicTest.TEST_DATA_DIR_PATH}/js-optimizer/$basePath"
val unoptimizedName = "$baseName/$methodName.original.js"
val optimizedName = "$baseName/$methodName.optimized.js"
val unoptimizedCode = FileUtil.loadFile(File(unoptimizedName))
val optimizedCode = FileUtil.loadFile(File(optimizedName))
runFiles(unoptimizedName, optimizedName, unoptimizedCode, optimizedCode)
checkOptimizer(unoptimizedCode, optimizedCode)
}
private fun runFiles(unoptimizedName: String, optimizedName: String, unoptimizedCode: String, optimizedCode: String) {
try {
val ctx = Context.enter()
runScript(ctx, unoptimizedName, unoptimizedCode)
runScript(ctx, optimizedName, optimizedCode)
}
finally {
Context.exit()
}
}
private fun checkOptimizer(unoptimizedCode: String, optimizedCode: String) {
val parserScope = JsFunctionScope(JsRootScope(JsProgram("<js checker>")), "<js fun>")
val unoptimizedAst = parse(unoptimizedCode, errorReporter, parserScope)
updateMetadata(unoptimizedCode, unoptimizedAst)
for (statement in unoptimizedAst) {
object : RecursiveJsVisitor() {
override fun visitFunction(x: JsFunction) {
FunctionPostProcessor(x.body).apply()
super.visitFunction(x)
}
}.accept(statement)
}
val optimizedAst = parse(optimizedCode, errorReporter, parserScope)
Assert.assertEquals(astToString(optimizedAst), astToString(unoptimizedAst))
}
private fun updateMetadata(code: String, ast: List<JsStatement>) {
val comments = findSyntheticComments(code)
for (stmt in ast) {
object : RecursiveJsVisitor() {
override fun visitVars(x: JsVars) {
x.synthetic = x.vars.any { isSyntheticId(it.name.ident) }
super.visitVars(x)
}
override fun visitExpressionStatement(x: JsExpressionStatement) {
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
if (assignment != null) {
val name = assignment.first
x.synthetic = isSyntheticId(name.ident)
}
super.visitExpressionStatement(x)
}
override fun visitIf(x: JsIf) {
val line = x.getData<Int?>("line")
if (line != null && line in comments.indices && comments[line]) {
x.synthetic = true
}
super.visitIf(x)
}
}.accept(stmt)
}
}
private fun findSyntheticComments(code: String): List<Boolean> {
val parts = code.lines()
return parts.map { it.contains("/*synthetic*/") }
}
private fun isSyntheticId(id: String) = id.startsWith("$")
private fun astToString(ast: List<JsStatement>): String {
val output = TextOutputImpl()
val visitor = JsSourceGenerationVisitor(output, null)
for (stmt in ast) {
stmt.accept(visitor)
}
return output.toString()
}
private fun runScript(ctx: Context, fileName: String, code: String) {
val scope = ctx.initStandardObjects()
ctx.evaluateString(scope, code, fileName, 1, null)
val result = ctx.evaluateString(scope, "box()", "unit test", 1, null)
Assert.assertEquals("box() function must return 'OK'", "OK", result)
}
private val errorReporter = object : ErrorReporter {
override fun warning(message: String, startPosition: CodePosition, endPosition: CodePosition) { }
override fun error(message: String, startPosition: CodePosition, endPosition: CodePosition) {
Assert.fail("Error parsing JS file: $message at $startPosition")
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.test.optimizer
import org.junit.Test
class IfStatementReductionTest : BasicOptimizerTest("if-reduction") {
@Test fun assignment() = box()
@Test fun returnStatement() = box()
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.test.optimizer
import org.junit.Test
class TemporaryAssignmentEliminationTest : BasicOptimizerTest("temporary-assignment") {
@Test fun assignment() = box()
@Test fun returnStatement() = box()
@Test fun declaration() = box()
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.test.optimizer
import org.junit.Test
class TemporaryVariableEliminationTest : BasicOptimizerTest("temporary-variable") {
@Test fun declaration() = box()
@Test fun assignment() = box()
}
@@ -0,0 +1,15 @@
function test(n) {
var result;
result = n >= 0 ? n : -n;
return result;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,21 @@
function test(n) {
var result;
/*synthetic*/ if (n >= 0) {
result = n;
}
else {
result = -n;
}
return result;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,13 @@
function test(n) {
return n >= 0 ? n : -n;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,18 @@
function test(n) {
/*synthetic*/ if (n >= 0) {
return n;
}
else {
return -n;
}
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,20 @@
function test(n) {
var result;
if (n >= 0) {
result = n;
}
else {
result = -n;
}
return result;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,23 @@
function test(n) {
var result;
var $tmp;
if (n >= 0) {
$tmp = n;
}
else {
$tmp = -n;
}
result = $tmp;
return result;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,20 @@
function test(n) {
var result;
if (n >= 0) {
result = n;
}
else {
result = -n;
}
return result;
}
function box() {
var result = test(20)
if (result != 20) return "fail1: " + result;
result = test(-20)
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,21 @@
function test(n) {
var $tmp;
if (n >= 0) {
$tmp = n;
}
else {
$tmp = -n;
}
var result = $tmp;
return result;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,18 @@
function test(n) {
if (n >= 0) {
return n;
}
else {
return -n;
}
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,21 @@
function test(n) {
var $tmp;
if (n >= 0) {
$tmp = n;
}
else {
$tmp = -n;
}
return $tmp;
}
function box() {
var result = test(20);
if (result != 20) return "fail1: " + result;
result = test(-20);
if (result != 20) return "fail2: " + result;
return "OK"
}
@@ -0,0 +1,10 @@
function test(a, b, c) {
return a + b + c;
}
function box() {
var result = test(2, 3, 4);
if (result != 9) return "fail: " + result;
return "OK"
}
@@ -0,0 +1,12 @@
function test(a, b, c) {
var $tmp;
$tmp = a + b;
return $tmp + c;
}
function box() {
var result = test(2, 3, 4);
if (result != 9) return "fail: " + result;
return "OK"
}
@@ -0,0 +1,10 @@
function test(a, b, c) {
return a + b + c;
}
function box() {
var result = test(2, 3, 4);
if (result != 9) return "fail: " + result;
return "OK"
}
@@ -0,0 +1,11 @@
function test(a, b, c) {
var $tmp = a + b;
return $tmp + c;
}
function box() {
var result = test(2, 3, 4);
if (result != 9) return "fail: " + result;
return "OK"
}