diff options
author | Klaas Boesche <klaasb@google.com> | 2015-11-06 15:12:10 +0000 |
---|---|---|
committer | Florian Weikert <fwe@google.com> | 2015-11-06 16:40:10 +0000 |
commit | 53de62a486c1f6daeedb90289ec145f4e7f73a8d (patch) | |
tree | 63989190313b14445cf83a8fd15969cb7dede56e /src | |
parent | a2c60d0594545ec266b23cf0122d664397b69b77 (diff) |
Compile list literals to byte code.
--
MOS_MIGRATED_REVID=107231604
Diffstat (limited to 'src')
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 |