aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Klaas Boesche <klaasb@google.com>2015-11-06 15:12:10 +0000
committerGravatar Florian Weikert <fwe@google.com>2015-11-06 16:40:10 +0000
commit53de62a486c1f6daeedb90289ec145f4e7f73a8d (patch)
tree63989190313b14445cf83a8fd15969cb7dede56e /src
parenta2c60d0594545ec266b23cf0122d664397b69b77 (diff)
Compile list literals to byte code.
-- MOS_MIGRATED_REVID=107231604
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java17
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java60
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeMethodCalls.java21
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/NewObject.java103
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java20
7 files changed, 256 insertions, 2 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index d284427219..29e160c13a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -22,8 +22,11 @@ import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
@@ -111,12 +114,15 @@ public final class EvalUtils {
return false;
}
+ public static final StackManipulation checkValidDictKey =
+ ByteCodeUtils.invoke(EvalUtils.class, "checkValidDictKey", Object.class);
+
/**
* Checks that an Object is a valid key for a Skylark dict.
* @param o an Object to validate
* @throws EvalException if o is not a valid key
*/
- static void checkValidDictKey(Object o) throws EvalException {
+ public static void checkValidDictKey(Object o) throws EvalException {
// TODO(bazel-team): check that all recursive elements are both Immutable AND Comparable.
if (isImmutable(o)) {
return;
@@ -339,6 +345,9 @@ public final class EvalUtils {
return obj;
}
+ public static final StackManipulation toBoolean =
+ ByteCodeUtils.invoke(EvalUtils.class, "toBoolean", Object.class);
+
/**
* @return the truth value of an object, according to Python rules.
* http://docs.python.org/2/library/stdtypes.html#truth-value-testing
@@ -367,6 +376,9 @@ public final class EvalUtils {
}
}
+ public static final StackManipulation toCollection =
+ ByteCodeUtils.invoke(EvalUtils.class, "toCollection", Object.class, Location.class);
+
@SuppressWarnings("unchecked")
public static Collection<?> toCollection(Object o, Location loc) throws EvalException {
if (o instanceof Collection) {
@@ -390,6 +402,9 @@ public final class EvalUtils {
}
}
+ public static final StackManipulation toIterable =
+ ByteCodeUtils.invoke(EvalUtils.class, "toIterable", Object.class, Location.class);
+
@SuppressWarnings("unchecked")
public static Iterable<?> toIterable(Object o, Location loc) throws EvalException {
if (o instanceof String) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java b/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
index 13c4d010b0..18484cccb9 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
@@ -13,8 +13,22 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax;
+import static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
+import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
+import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
+import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
+import com.google.devtools.build.lib.syntax.compiler.NewObject;
+import com.google.devtools.build.lib.syntax.compiler.VariableScope;
+
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
import java.util.ArrayList;
import java.util.Collections;
@@ -102,4 +116,50 @@ public final class ListLiteral extends Expression {
expr.validate(env);
}
}
+
+ @Override
+ ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) {
+ AstAccessors debugAccessors = debugInfo.add(this);
+ List<ByteCodeAppender> listConstruction = new ArrayList<>();
+ if (isTuple()) {
+ append(listConstruction, ByteCodeMethodCalls.BCImmutableList.builder);
+ } else {
+ append(
+ listConstruction,
+ // create a new MutableList object
+ NewObject.fromConstructor(MutableList.class, Mutability.class)
+ .arguments(
+ scope.loadEnvironment(), ByteCodeUtils.invoke(Environment.class, "mutability")));
+ }
+
+ for (Expression expression : exprs) {
+ Preconditions.checkNotNull(
+ expression, "List literal at %s contains null expression", getLocation());
+ ByteCodeAppender compiledValue = expression.compile(scope, debugInfo);
+ if (isTuple()) {
+ listConstruction.add(compiledValue);
+ append(
+ listConstruction,
+ // this re-adds the builder to the stack and we reuse it in the next iteration/after
+ ByteCodeMethodCalls.BCImmutableList.Builder.add);
+ } else {
+ // duplicate the list reference on the stack for reuse in the next iteration/after
+ append(listConstruction, Duplication.SINGLE);
+ listConstruction.add(compiledValue);
+ append(
+ listConstruction,
+ debugAccessors.loadLocation,
+ scope.loadEnvironment(),
+ ByteCodeUtils.cleanInvoke(
+ MutableList.class, "add", Object.class, Location.class, Environment.class));
+ }
+ }
+ if (isTuple()) {
+ append(
+ listConstruction,
+ ByteCodeMethodCalls.BCImmutableList.Builder.build,
+ ByteCodeUtils.invoke(Tuple.class, "create", ImmutableList.class));
+ }
+ return ByteCodeUtils.compoundAppender(listConstruction);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
index 74a3eae6ab..0ac292854e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
@@ -22,6 +22,7 @@ import com.google.devtools.build.lib.syntax.Mutability.Freezable;
import com.google.devtools.build.lib.syntax.Mutability.MutabilityException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -213,7 +214,7 @@ public abstract class SkylarkList implements Iterable<Object>, SkylarkValue {
}
/**
- * Creates a MutableList from contents and an Environment.
+ * Creates a MutableList from contents.
* @param contents the contents of the list
* @return an actually immutable MutableList containing the elements
*/
@@ -222,6 +223,13 @@ public abstract class SkylarkList implements Iterable<Object>, SkylarkValue {
}
/**
+ * Creates a mutable or immutable MutableList depending on the given {@link Mutability}.
+ */
+ public MutableList(Mutability mutability) {
+ this(Collections.EMPTY_LIST, mutability);
+ }
+
+ /**
* Builds a Skylark list (actually immutable) from a variable number of arguments.
* @param env an Environment from which to inherit Mutability, or null for immutable
* @param contents the contents of the list
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeMethodCalls.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeMethodCalls.java
index b93fe3856d..a699a17d46 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeMethodCalls.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeMethodCalls.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax.compiler;
+import com.google.common.collect.ImmutableList;
+
import net.bytebuddy.implementation.bytecode.StackManipulation;
/**
@@ -26,6 +28,25 @@ import net.bytebuddy.implementation.bytecode.StackManipulation;
public class ByteCodeMethodCalls {
/**
+ * Byte code invocations for {@link ImmutableList}.
+ */
+ public static class BCImmutableList {
+ public static final StackManipulation builder =
+ ByteCodeUtils.invoke(ImmutableList.class, "builder");
+
+ /**
+ * Byte code invocations for {@link ImmutableList.Builder}.
+ */
+ public static class Builder {
+ public static final StackManipulation build =
+ ByteCodeUtils.invoke(ImmutableList.Builder.class, "build");
+
+ public static final StackManipulation add =
+ ByteCodeUtils.invoke(ImmutableList.Builder.class, "add", Object.class);
+ }
+ }
+
+ /**
* Byte code invocations for {@link Integer}.
*/
public static class BCInteger {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java
index 4eb5433529..832d4e3e51 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java
@@ -13,8 +13,12 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax.compiler;
+import net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.generic.GenericTypeDescription;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender.Compound;
+import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
@@ -27,6 +31,29 @@ import java.util.List;
public class ByteCodeUtils {
/**
+ * Helper method to wrap {@link StackManipulation}s into a {@link ByteCodeAppender} and add it
+ * to a list of appenders.
+ */
+ public static void append(List<ByteCodeAppender> code, StackManipulation... manipulations) {
+ code.add(new ByteCodeAppender.Simple(manipulations));
+ }
+
+ /**
+ * As {@link #invoke(Class, String, Class...)} and additionally clears the returned value from
+ * the stack, if any.
+ */
+ public static StackManipulation cleanInvoke(
+ Class<?> clazz, String methodName, Class<?>... parameterTypes) {
+ ForLoadedMethod method = ReflectionUtils.getMethod(clazz, methodName, parameterTypes);
+ GenericTypeDescription returnType = method.getReturnType();
+ if (returnType.equals(TypeDescription.VOID)) {
+ return MethodInvocation.invoke(method);
+ }
+ return new StackManipulation.Compound(
+ MethodInvocation.invoke(method), Removal.pop(returnType.asErasure()));
+ }
+
+ /**
* Create a {@link ByteCodeAppender} applying a list of them.
*
* <p>Exists just because {@link Compound} does not have a constructor taking a list.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/NewObject.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/NewObject.java
new file mode 100644
index 0000000000..4b5881b91a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/NewObject.java
@@ -0,0 +1,103 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// 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 com.google.devtools.build.lib.syntax.compiler;
+
+import net.bytebuddy.description.method.MethodDescription.ForLoadedConstructor;
+import net.bytebuddy.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+/**
+ * Builds byte code for new object creation from constructor calls.
+ *
+ * <p>Java byte code for new object creation looks like the following pseudo code:
+ * <pre>
+ * NEW
+ * DUP
+ * ...load constructor parameters...
+ * INVOKESPECIAL constructor
+ * </pre>
+ * This is because a constructor is actually called on the reference to the object itself, which
+ * is left on the stack by NEW.
+ *
+ * This class helps with wrapping the parameter loading with this structure.
+ */
+public class NewObject implements StackManipulation {
+
+ private final ForLoadedConstructor constructor;
+ private final StackManipulation arguments;
+
+ /**
+ * Intermediate state builder for new object construction with missing constructor argument
+ * loading.
+ */
+ public static final class NewObjectBuilder {
+ private final ForLoadedConstructor constructor;
+
+ private NewObjectBuilder(ForLoadedConstructor constructor) {
+ this.constructor = constructor;
+ }
+
+ /**
+ * Adds the argument loading in the correct for new object construction.
+ */
+ public NewObject arguments(StackManipulation... arguments) {
+ return new NewObject(constructor, new StackManipulation.Compound(arguments));
+ }
+
+ /**
+ * Adds the argument loading in the correct for new object construction.
+ */
+ public NewObject arguments(List<StackManipulation> arguments) {
+ return new NewObject(constructor, new StackManipulation.Compound(arguments));
+ }
+ }
+
+ private NewObject(ForLoadedConstructor constructor, StackManipulation arguments) {
+ this.constructor = constructor;
+ this.arguments = arguments;
+ }
+
+ /**
+ * Looks for a constructor in the class with the given parameter types and returns an
+ * intermediate builder.
+ */
+ public static NewObjectBuilder fromConstructor(Class<?> clazz, Class<?>... parameterTypes) {
+ return new NewObjectBuilder(ReflectionUtils.getConstructor(clazz, parameterTypes));
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ methodVisitor.visitTypeInsn(Opcodes.NEW, constructor.getDeclaringType().getInternalName());
+ return new StackManipulation.Compound(
+ Duplication.SINGLE, arguments, MethodInvocation.invoke(constructor))
+ .apply(methodVisitor, implementationContext);
+ }
+
+ @Override
+ public String toString() {
+ return "NewObject(" + constructor + ", " + arguments + ")";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java
index 5dff149ee0..1d7c0daedc 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java
@@ -24,6 +24,26 @@ import java.util.Arrays;
public class ReflectionUtils {
/**
+ * Get a Byte Buddy {@link MethodDescription} for a constructor of a class.
+ *
+ * @throws Error when the constructor cannot be found via reflection
+ */
+ public static MethodDescription.ForLoadedConstructor getConstructor(
+ Class<?> clazz, Class<?>... parameterTypes) {
+ try {
+ return new MethodDescription.ForLoadedConstructor(clazz.getConstructor(parameterTypes));
+ } catch (NoSuchMethodException e) {
+ throw new Error(
+ String.format(
+ "Error when reflectively getting a constructor with parameter"
+ + " types %s from class %s",
+ Arrays.toString(parameterTypes),
+ clazz),
+ e);
+ }
+ }
+
+ /**
* Get a Byte Buddy {@link MethodDescription} for a method from a class.
*
* @throws Error when the method cannot be found via reflection