From f4051f45abd14f93de9c2dc7550f209aeedb6310 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Wed, 23 May 2012 02:52:29 +0400 Subject: [PATCH] generate bytecode for script It is just prototype * does not make top level symbols visible as class members yet * does not take parameters * Script class name is hardcoded now --- .../jet/codegen/ClosureAnnotator.java | 9 +- .../jet/codegen/ExpressionCodegen.java | 2 +- .../jet/codegen/NamespaceCodegen.java | 3 + .../jetbrains/jet/codegen/ScriptCodegen.java | 102 ++++++++++++++++++ .../jet/di/InjectorForJvmCodegen.java | 11 ++ .../jet/lang/resolve/java/JdkNames.java | 1 + .../lang/descriptors/ScriptDescriptor.java | 5 + .../jetbrains/jet/lang/psi/JetPsiUtil.java | 7 +- .../jet/lang/resolve/BodyResolver.java | 2 +- .../lang/resolve/TypeHierarchyResolver.java | 6 +- .../codegen/script/helloWorld.ktscript | 2 + .../testData/codegen/script/string.ktscript | 1 + .../codegen/script/topLevelFunction.ktscript | 11 ++ .../jet/codegen/CodegenTestCase.java | 33 ++++-- .../jetbrains/jet/codegen/ScriptGenTest.java | 45 ++++++++ .../jet/di/AllInjectorsGenerator.java | 2 + 16 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/jet/codegen/ScriptCodegen.java create mode 100644 compiler/testData/codegen/script/helloWorld.ktscript create mode 100644 compiler/testData/codegen/script/string.ktscript create mode 100644 compiler/testData/codegen/script/topLevelFunction.ktscript create mode 100644 compiler/tests/org/jetbrains/jet/codegen/ScriptGenTest.java diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ClosureAnnotator.java b/compiler/backend/src/org/jetbrains/jet/codegen/ClosureAnnotator.java index af2f1ea7d68..f71ae90b4d8 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ClosureAnnotator.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ClosureAnnotator.java @@ -86,8 +86,13 @@ public class ClosureAnnotator { private void mapFilesToNamespaces(Collection files) { for (JetFile file : files) { - FqName fqName = JetPsiUtil.getFQName(file); - namespaceName2Files.putValue(fqName, file); + if (file.isScript()) { + namespaceName2Files.putValue(FqName.ROOT, file); + } + else { + FqName fqName = JetPsiUtil.getFQName(file); + namespaceName2Files.putValue(fqName, file); + } } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java index 1c0295919bf..1f64f19a5d3 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java @@ -1340,7 +1340,7 @@ public class ExpressionCodegen extends JetVisitor { // assert !superCall; // callableMethod = ClosureCodegen.asCallableMethod((FunctionDescriptor) fd); //} - if (fd instanceof ExpressionAsFunctionDescriptor || (fd instanceof SimpleFunctionDescriptor && fd.getContainingDeclaration() instanceof FunctionDescriptor)) { + if (fd instanceof ExpressionAsFunctionDescriptor || (fd instanceof SimpleFunctionDescriptor && (fd.getContainingDeclaration() instanceof FunctionDescriptor || fd.getContainingDeclaration() instanceof ScriptDescriptor))) { SimpleFunctionDescriptor invoke = CodegenUtil.createInvoke((FunctionDescriptor) fd); callableMethod = ClosureCodegen.asCallableMethod(invoke); } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java index 708a94c0d43..86a1d2995a5 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/NamespaceCodegen.java @@ -80,6 +80,9 @@ public class NamespaceCodegen { else if (declaration instanceof JetClassOrObject) { state.getInjector().getClassCodegen().generate(context, (JetClassOrObject) declaration); } + else if (declaration instanceof JetScript) { + state.getInjector().getScriptCodegen().generate(context, (JetScript) declaration); + } // else if (declaration instanceof JetFile) { // JetFile childNamespace = (JetFile) declaration; // state.forNamespace(childNamespace).generate(childNamespace); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ScriptCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ScriptCodegen.java new file mode 100644 index 00000000000..288c4dd2248 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ScriptCodegen.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2012 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.jet.codegen; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.ScriptDescriptor; +import org.jetbrains.jet.lang.psi.JetScript; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.java.JdkNames; +import org.jetbrains.jet.lang.types.lang.JetStandardClasses; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.InstructionAdapter; +import sun.tools.tree.InlineReturnStatement; + +import javax.inject.Inject; + +/** + * @author Stepan Koltsov + */ +public class ScriptCodegen { + + public static final String LAST_EXPRESSION_VALUE_FIELD_NAME = "rv"; + + @NotNull + private GenerationState state; + @NotNull + private ClassFileFactory classFileFactory; + @NotNull + private JetTypeMapper jetTypeMapper; + + + @Inject + public void setState(@NotNull GenerationState state) { + this.state = state; + } + + @Inject + public void setClassFileFactory(@NotNull ClassFileFactory classFileFactory) { + this.classFileFactory = classFileFactory; + } + + @Inject + public void setJetTypeMapper(@NotNull JetTypeMapper jetTypeMapper) { + this.jetTypeMapper = jetTypeMapper; + } + + + + public void generate(CodegenContext context, JetScript scriptDeclaration) { + ScriptDescriptor scriptDescriptor = (ScriptDescriptor) state.getBindingContext().get(BindingContext.SCRIPT, scriptDeclaration); + ClassBuilder classBuilder = classFileFactory.newVisitor("Script.class"); + classBuilder.defineClass(scriptDeclaration, + Opcodes.V1_6, + Opcodes.ACC_PUBLIC, + "Script", + null, + JdkNames.JL_OBJECT.getInternalName(), + new String[0]); + + Type blockType = jetTypeMapper.mapType(scriptDescriptor.getReturnType(), MapTypeMode.VALUE); + + classBuilder.newField(null, Opcodes.ACC_PUBLIC, LAST_EXPRESSION_VALUE_FIELD_NAME, blockType.getDescriptor(), null, null); + + MethodVisitor mv = classBuilder.newMethod(scriptDeclaration, Opcodes.ACC_PUBLIC, "", "()V", null, null); + + mv.visitCode(); + + InstructionAdapter instructionAdapter = new InstructionAdapter(mv); + + instructionAdapter.load(0, Type.getObjectType("Script")); + instructionAdapter.invokespecial(JdkNames.JL_OBJECT.getInternalName(), "", "()V"); + + instructionAdapter.load(0, Type.getObjectType("Script")); + StackValue stackValue = new ExpressionCodegen(mv, new FrameMap(), Type.VOID_TYPE, context, state).gen(scriptDeclaration.getBlockExpression()); + if (stackValue.type != Type.VOID_TYPE) { + instructionAdapter.putfield("Script", LAST_EXPRESSION_VALUE_FIELD_NAME, blockType.getDescriptor()); + } + + instructionAdapter.areturn(Type.VOID_TYPE); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + + classBuilder.done(); + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/di/InjectorForJvmCodegen.java b/compiler/backend/src/org/jetbrains/jet/di/InjectorForJvmCodegen.java index 578730b2874..f9d4533882e 100644 --- a/compiler/backend/src/org/jetbrains/jet/di/InjectorForJvmCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/di/InjectorForJvmCodegen.java @@ -28,6 +28,7 @@ import org.jetbrains.jet.codegen.GenerationState; import org.jetbrains.jet.codegen.ClassBuilderFactory; import org.jetbrains.jet.codegen.JetTypeMapper; import org.jetbrains.jet.codegen.ClassCodegen; +import org.jetbrains.jet.codegen.ScriptCodegen; import org.jetbrains.jet.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.jet.codegen.ClassFileFactory; import org.jetbrains.jet.codegen.ClosureAnnotator; @@ -56,6 +57,7 @@ public class InjectorForJvmCodegen { private final ClassBuilderFactory classBuilderFactory; private JetTypeMapper jetTypeMapper; private ClassCodegen classCodegen; + private ScriptCodegen scriptCodegen; private IntrinsicMethods intrinsics; private ClassFileFactory classFileFactory; private ClosureAnnotator closureAnnotator; @@ -80,6 +82,7 @@ public class InjectorForJvmCodegen { this.classBuilderFactory = classBuilderFactory; this.jetTypeMapper = new JetTypeMapper(); this.classCodegen = new ClassCodegen(); + this.scriptCodegen = new ScriptCodegen(); this.intrinsics = new IntrinsicMethods(); this.classFileFactory = new ClassFileFactory(); this.closureAnnotator = new ClosureAnnotator(); @@ -93,6 +96,10 @@ public class InjectorForJvmCodegen { this.classCodegen.setJetTypeMapper(jetTypeMapper); this.classCodegen.setState(generationState); + this.scriptCodegen.setClassFileFactory(classFileFactory); + this.scriptCodegen.setJetTypeMapper(jetTypeMapper); + this.scriptCodegen.setState(generationState); + this.intrinsics.setMyProject(project); this.intrinsics.setMyStdLib(jetStandardLibrary); @@ -130,6 +137,10 @@ public class InjectorForJvmCodegen { return this.classCodegen; } + public ScriptCodegen getScriptCodegen() { + return this.scriptCodegen; + } + public IntrinsicMethods getIntrinsics() { return this.intrinsics; } diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JdkNames.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JdkNames.java index 7e28b65fa27..d1050644415 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JdkNames.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JdkNames.java @@ -22,5 +22,6 @@ package org.jetbrains.jet.lang.resolve.java; public class JdkNames { public static final JvmClassName JL_OBJECT = new JvmClassName("java.lang.Object"); + public static final JvmClassName JL_STRING = new JvmClassName("java.lang.String"); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/ScriptDescriptor.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/ScriptDescriptor.java index 58b3e452209..b9b60fdfdc8 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/ScriptDescriptor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/ScriptDescriptor.java @@ -38,6 +38,11 @@ public class ScriptDescriptor extends DeclarationDescriptorImpl { this.returnType = returnType; } + @NotNull + public JetType getReturnType() { + return returnType; + } + @Override public DeclarationDescriptor substitute(TypeSubstitutor substitutor) { throw new IllegalStateException("nothing to substitute in script"); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java index bf8d3b47c2a..9b3ffe9c70b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java @@ -143,7 +143,12 @@ public class JetPsiUtil { } public static FqName getFQName(JetFile file) { - return getFQName(file.getNamespaceHeader()); + if (file.isScript()) { + return FqName.ROOT; + } + else { + return getFQName(file.getNamespaceHeader()); + } } @Nullable diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BodyResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BodyResolver.java index b5e7bd6b4ef..ee0056116dd 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BodyResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BodyResolver.java @@ -630,7 +630,7 @@ public class BodyResolver { Maps.>newHashMap(), new LabelResolver(), trace, - this.context.getRootScope(), + scope, DataFlowInfo.EMPTY, NO_EXPECTED_TYPE, false); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeHierarchyResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeHierarchyResolver.java index cdd92a9cbbb..be8c077da36 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeHierarchyResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeHierarchyResolver.java @@ -29,6 +29,7 @@ import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.resolve.scopes.RedeclarationHandler; import org.jetbrains.jet.lang.resolve.scopes.WritableScope; +import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl; import org.jetbrains.jet.lang.resolve.scopes.WriteThroughScope; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.SubstitutionUtils; @@ -207,8 +208,9 @@ public class TypeHierarchyResolver { private void processScript(JetScript script) { NamespaceDescriptorImpl ns = namespaceFactory.createNamespaceDescriptorPathIfNeeded(FqName.ROOT); ScriptDescriptor scriptDescriptor = new ScriptDescriptor(ns); - WriteThroughScope scriptScope = new WriteThroughScope( - outerScope, ns.getMemberScope(), new TraceBasedRedeclarationHandler(trace)); + //WriteThroughScope scriptScope = new WriteThroughScope( + // outerScope, ns.getMemberScope(), new TraceBasedRedeclarationHandler(trace)); + WritableScopeImpl scriptScope = new WritableScopeImpl(outerScope, scriptDescriptor, RedeclarationHandler.DO_NOTHING); scriptScope.changeLockLevel(WritableScope.LockLevel.BOTH); context.getScriptScopes().put(script, scriptScope); context.getScripts().put(script, scriptDescriptor); diff --git a/compiler/testData/codegen/script/helloWorld.ktscript b/compiler/testData/codegen/script/helloWorld.ktscript new file mode 100644 index 00000000000..397eda10a7c --- /dev/null +++ b/compiler/testData/codegen/script/helloWorld.ktscript @@ -0,0 +1,2 @@ +// expected: null +System.out!!.println("hello world") diff --git a/compiler/testData/codegen/script/string.ktscript b/compiler/testData/codegen/script/string.ktscript new file mode 100644 index 00000000000..1715a79f1ca --- /dev/null +++ b/compiler/testData/codegen/script/string.ktscript @@ -0,0 +1 @@ +"O" + "K" diff --git a/compiler/testData/codegen/script/topLevelFunction.ktscript b/compiler/testData/codegen/script/topLevelFunction.ktscript new file mode 100644 index 00000000000..0406e414fca --- /dev/null +++ b/compiler/testData/codegen/script/topLevelFunction.ktscript @@ -0,0 +1,11 @@ +// expected: 3628800 + +fun factorial(n: Int): Int { + var product = 1 + for (i in 1..n) { + product *= i + } + return product +} + +factorial(10) diff --git a/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java b/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java index 27b9f899e49..1258db8df17 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java +++ b/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java @@ -16,6 +16,7 @@ package org.jetbrains.jet.codegen; +import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.intellij.testFramework.UsefulTestCase; import org.jetbrains.annotations.NotNull; @@ -33,6 +34,7 @@ import org.jetbrains.jet.parsing.JetParsingTest; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -40,6 +42,8 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author yole @@ -123,8 +127,10 @@ public abstract class CodegenTestCase extends UsefulTestCase { } protected void blackBoxFile(String filename) { - loadFile(filename); - String actual; + String content = loadFile(filename); + Matcher matcher = Pattern.compile("// expected: (.*)").matcher(content); + String expectedValue = matcher.find() ? matcher.group(1) : "OK"; + Object actual; try { actual = blackBox(); } catch (NoClassDefFoundError e) { @@ -134,21 +140,32 @@ public abstract class CodegenTestCase extends UsefulTestCase { System.out.println(generateToText()); throw new RuntimeException(e); } - if (!"OK".equals(actual)) { + if (!Objects.equal(expectedValue, actual)) { System.out.println(generateToText()); } - assertEquals("OK", actual); + assertEquals(expectedValue, actual); } + @NotNull protected String blackBox() throws Exception { ClassFileFactory codegens = generateClassesInFile(); GeneratedClassLoader loader = createClassLoader(codegens); try { - String fqName = NamespaceCodegen.getJVMClassNameForKotlinNs(JetPsiUtil.getFQName(myFile)).getFqName().getFqName(); - Class namespaceClass = loader.loadClass(fqName); - Method method = namespaceClass.getMethod("box"); - return (String) method.invoke(null); + if (myFile.isScript()) { + Class scriptClass = loader.loadClass("Script"); + Object scriptInstance = scriptClass.newInstance(); + Field field = scriptClass.getDeclaredField("rv"); + field.setAccessible(true); + Object result = field.get(scriptInstance); + return result != null ? result.toString() : "null"; + } + else { + String fqName = NamespaceCodegen.getJVMClassNameForKotlinNs(JetPsiUtil.getFQName(myFile)).getFqName().getFqName(); + Class namespaceClass = loader.loadClass(fqName); + Method method = namespaceClass.getMethod("box"); + return (String) method.invoke(null); + } } finally { loader.dispose(); } diff --git a/compiler/tests/org/jetbrains/jet/codegen/ScriptGenTest.java b/compiler/tests/org/jetbrains/jet/codegen/ScriptGenTest.java new file mode 100644 index 00000000000..1ce99f4eccd --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/codegen/ScriptGenTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2012 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.jet.codegen; + +import org.jetbrains.jet.lang.resolve.java.CompilerSpecialMode; + +/** + * @author Stepan Koltsov + */ +public class ScriptGenTest extends CodegenTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + createEnvironmentWithMockJdkAndIdeaAnnotations(CompilerSpecialMode.ALT_HEADERS); + } + + public void testHelloWorld() { + blackBoxFile("script/helloWorld.ktscript"); + } + + public void testString() { + blackBoxFile("script/string.ktscript"); + } + + public void testTopLevelFunction() { + blackBoxFile("script/topLevelFunction.ktscript"); + // TODO: check function is visible as instance field (it is currently not) + } + +} diff --git a/injector-generator/src/org/jetbrains/jet/di/AllInjectorsGenerator.java b/injector-generator/src/org/jetbrains/jet/di/AllInjectorsGenerator.java index 6bff224e6e3..31ba368ee1d 100644 --- a/injector-generator/src/org/jetbrains/jet/di/AllInjectorsGenerator.java +++ b/injector-generator/src/org/jetbrains/jet/di/AllInjectorsGenerator.java @@ -23,6 +23,7 @@ import org.jetbrains.jet.codegen.ClassCodegen; import org.jetbrains.jet.codegen.ClassFileFactory; import org.jetbrains.jet.codegen.GenerationState; import org.jetbrains.jet.codegen.JetTypeMapper; +import org.jetbrains.jet.codegen.ScriptCodegen; import org.jetbrains.jet.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.jet.lang.ModuleConfiguration; import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowDataTraceFactory; @@ -156,6 +157,7 @@ public class AllInjectorsGenerator { generator.addParameter(ClassBuilderFactory.class); generator.addPublicField(JetTypeMapper.class); generator.addPublicField(ClassCodegen.class); + generator.addPublicField(ScriptCodegen.class); generator.addField(true, IntrinsicMethods.class, "intrinsics", null); generator.addPublicField(ClassFileFactory.class); generator.generate("compiler/backend/src", "org.jetbrains.jet.di", "InjectorForJvmCodegen");