aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/compiler')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java53
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/DebugInfo.java90
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/Jump.java138
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/LabelAdder.java52
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/LoopLabels.java55
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java59
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/Variable.java168
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableScope.java175
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableStore.java106
9 files changed, 896 insertions, 0 deletions
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
new file mode 100644
index 0000000000..4eb5433529
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ByteCodeUtils.java
@@ -0,0 +1,53 @@
+// 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.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender.Compound;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+
+import java.util.List;
+
+/**
+ * Various utility methods for byte code generation.
+ */
+public class ByteCodeUtils {
+
+ /**
+ * Create a {@link ByteCodeAppender} applying a list of them.
+ *
+ * <p>Exists just because {@link Compound} does not have a constructor taking a list.
+ */
+ public static ByteCodeAppender compoundAppender(List<ByteCodeAppender> code) {
+ return new Compound(code.toArray(new ByteCodeAppender[code.size()]));
+ }
+
+ /**
+ * Builds a {@link StackManipulation} that loads the field.
+ */
+ public static StackManipulation getField(Class<?> clazz, String field) {
+ return FieldAccess.forField(ReflectionUtils.getField(clazz, field)).getter();
+ }
+
+ /**
+ * Builds a {@link StackManipulation} that invokes the method identified via reflection on the
+ * given class, method and parameter types.
+ */
+ public static StackManipulation invoke(
+ Class<?> clazz, String methodName, Class<?>... parameterTypes) {
+ return MethodInvocation.invoke(ReflectionUtils.getMethod(clazz, methodName, parameterTypes));
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/DebugInfo.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/DebugInfo.java
new file mode 100644
index 0000000000..458c0cdbe9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/DebugInfo.java
@@ -0,0 +1,90 @@
+// 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 com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.ASTNode;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A single point of access for references to the AST for debug location purposes.
+ */
+public final class DebugInfo {
+
+ /**
+ * Contains byte code instructions for calls to a DebugInfo instances methods.
+ */
+ public static final class AstAccessors {
+ public final StackManipulation loadAstNode;
+ public final StackManipulation loadLocation;
+
+ private AstAccessors(int index, StackManipulation getAstNode, StackManipulation getLocation) {
+ StackManipulation indexValue = IntegerConstant.forValue(index);
+ this.loadAstNode = new StackManipulation.Compound(indexValue, getAstNode);
+ this.loadLocation = new StackManipulation.Compound(indexValue, getLocation);
+ }
+ }
+
+ private final List<ASTNode> astNodes;
+ private final StackManipulation getAstNode;
+ private final StackManipulation getLocation;
+
+ /**
+ * @param getAstNode A {@link MethodDescription} which can be used to access this instance's
+ * {@link #getAstNode(int)} in a static way.
+ * @param getLocation A {@link MethodDescription} which can be used to access this instance's
+ * {@link #getLocation(int)} in a static way.
+ */
+ public DebugInfo(MethodDescription getAstNode, MethodDescription getLocation) {
+ astNodes = new ArrayList<>();
+ this.getAstNode = MethodInvocation.invoke(getAstNode);
+ this.getLocation = MethodInvocation.invoke(getLocation);
+ }
+
+ /**
+ * Get an {@link ASTNode} for reference at runtime.
+ *
+ * <p>Needed for rule construction which refers back to the function call node to get argument
+ * locations.
+ */
+ public ASTNode getAstNode(int index) {
+ return astNodes.get(index);
+ }
+
+ /**
+ * Get a {@link Location} for reference at runtime.
+ *
+ * <p>Needed to provide source code error locations at runtime.
+ */
+ public Location getLocation(int index) {
+ return getAstNode(index).getLocation();
+ }
+
+ /**
+ * Use this during compilation to add AST nodes needed at runtime.
+ * @return an {@link AstAccessors} instance which can be used to get the info at runtime in the
+ * static context of the byte code
+ */
+ public AstAccessors add(ASTNode node) {
+ astNodes.add(node);
+ return new AstAccessors(astNodes.size() - 1, getAstNode, getLocation);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/Jump.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/Jump.java
new file mode 100644
index 0000000000..e13b1e6fa8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/Jump.java
@@ -0,0 +1,138 @@
+// 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.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Implements byte code for gotos and conditional jumps.
+ */
+public class Jump implements StackManipulation {
+
+ protected final int opCode;
+ protected final Label target;
+ protected final Size stackSizeChange;
+
+ private Jump(int opCode, Label target, Size stackSizeChange) {
+ this.opCode = opCode;
+ this.target = target;
+ this.stackSizeChange = stackSizeChange;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ methodVisitor.visitJumpInsn(opCode, target);
+ return stackSizeChange;
+ }
+
+ @Override
+ public String toString() {
+ return "Jump(" + opCode + ", " + target + ")";
+ }
+
+ /**
+ * Builds a conditional jump for two int operands on the stack.
+ */
+ public static JumpWithoutTarget ifIntOperands(PrimitiveComparison comparison) {
+ return new JumpWithoutTarget(Opcodes.IF_ICMPEQ + comparison.ordinal(), new Size(-2, 0));
+ }
+
+ /**
+ * Builds a conditional jump for one int operand from the stack compared to zero.
+ */
+ public static JumpWithoutTarget ifIntOperandToZero(PrimitiveComparison comparison) {
+ return new JumpWithoutTarget(Opcodes.IFEQ + comparison.ordinal(), new Size(-1, 0));
+ }
+
+ /**
+ * Builds a conditional jump for two reference type operands from the stack.
+ */
+ public static JumpWithoutTarget ifReferenceOperands(ReferenceComparison comparison) {
+ return new JumpWithoutTarget(Opcodes.IF_ACMPEQ + comparison.ordinal(), new Size(-2, 0));
+ }
+
+ /**
+ * Builds a conditional jump for one reference type operand from the stack compared to null.
+ */
+ public static JumpWithoutTarget ifReferenceOperandToNull(ReferenceComparison comparison) {
+ return new JumpWithoutTarget(Opcodes.IFNULL + comparison.ordinal(), new Size(-1, 0));
+ }
+
+ /**
+ * Builds an unconditional jump to the target label.
+ */
+ public static Jump to(Label target) {
+ return new Jump(Opcodes.GOTO, target, new Size(0, 0));
+ }
+
+ /**
+ * Builds an unconditional jump to the label added by the given {@link LabelAdder}.
+ */
+ public static Jump to(LabelAdder target) {
+ return to(target.getLabel());
+ }
+
+ /**
+ * Builder helper class for partially built jumps from conditionals.
+ *
+ * <p>Allows adding a jump target label.
+ */
+ public static final class JumpWithoutTarget {
+
+ protected final int opCode;
+ protected final Size stackSizeChange;
+
+ private JumpWithoutTarget(int opCode, Size stackSizeChange) {
+ this.opCode = opCode;
+ this.stackSizeChange = stackSizeChange;
+ }
+
+ /**
+ * Builds a jump to the given target and the previously initialized conditional.
+ */
+ public Jump to(LabelAdder target) {
+ return new Jump(opCode, target.getLabel(), stackSizeChange);
+ }
+ }
+
+ /**
+ * All primitive comparisons for which there are byte code equivalents.
+ */
+ public enum PrimitiveComparison {
+ EQUAL,
+ NOT_EQUAL,
+ LESS,
+ GREATER_EQUAL,
+ GREATER,
+ LESS_EQUAL;
+ }
+
+ /**
+ * All reference comparisons for which there are byte code equivalents.
+ */
+ public enum ReferenceComparison {
+ EQUAL,
+ NOT_EQUAL;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/LabelAdder.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/LabelAdder.java
new file mode 100644
index 0000000000..348c40d4d3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/LabelAdder.java
@@ -0,0 +1,52 @@
+// 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.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Adds a fresh label to the byte code.
+ */
+public class LabelAdder implements StackManipulation {
+
+ private final Label label;
+
+ public LabelAdder() {
+ this.label = new Label();
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ methodVisitor.visitLabel(label);
+ return new Size(0, 0);
+ }
+
+ @Override
+ public String toString() {
+ return "Label(" + label + ")";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/LoopLabels.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/LoopLabels.java
new file mode 100644
index 0000000000..e1bb9d36b1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/LoopLabels.java
@@ -0,0 +1,55 @@
+// 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 com.google.common.base.Optional;
+import com.google.devtools.build.lib.syntax.FlowStatement.Kind;
+
+import org.objectweb.asm.Label;
+
+/**
+ * Container for passing around current loop labels during compilation.
+ */
+public class LoopLabels {
+ public final Label continueLabel;
+ public final Label breakLabel;
+
+ private LoopLabels(Label continueLabel, Label breakLabel) {
+ this.continueLabel = continueLabel;
+ this.breakLabel = breakLabel;
+ }
+
+ /**
+ * Only use Optional-wrapped instances of this class.
+ */
+ public static Optional<LoopLabels> of(Label continueLabel, Label breakLabel) {
+ return Optional.of(new LoopLabels(continueLabel, breakLabel));
+ }
+
+ public static final Optional<LoopLabels> ABSENT = Optional.absent();
+
+ /**
+ * Get the label for a certain kind of flow statement.
+ */
+ public Label labelFor(Kind kind) {
+ switch (kind) {
+ case BREAK:
+ return breakLabel;
+ case CONTINUE:
+ return continueLabel;
+ default:
+ throw new Error("missing flow kind: " + kind);
+ }
+ }
+}
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
new file mode 100644
index 0000000000..5dff149ee0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/ReflectionUtils.java
@@ -0,0 +1,59 @@
+// 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.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for reflective access of declared methods.
+ */
+public class ReflectionUtils {
+
+ /**
+ * Get a Byte Buddy {@link MethodDescription} for a method from a class.
+ *
+ * @throws Error when the method cannot be found via reflection
+ */
+ public static MethodDescription.ForLoadedMethod getMethod(
+ Class<?> clazz, String name, Class<?>... parameterTypes) {
+ try {
+ return new MethodDescription.ForLoadedMethod(clazz.getMethod(name, parameterTypes));
+ } catch (NoSuchMethodException e) {
+ throw new Error(
+ String.format(
+ "Error when reflectively getting method %s with parameter types %s from class %s",
+ name,
+ Arrays.toString(parameterTypes),
+ clazz),
+ e);
+ }
+ }
+
+ /**
+ * Get a Byte Buddy {@link FieldDescription} for a field of a class.
+ *
+ * @throws Error when the field cannot be found via reflection
+ */
+ public static FieldDescription getField(Class<?> clazz, String name) {
+ try {
+ return new FieldDescription.ForLoadedField(clazz.getField(name));
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(
+ String.format("Error when reflectively getting field %s from class %s", name, clazz), e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/Variable.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/Variable.java
new file mode 100644
index 0000000000..41e2e5d32d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/Variable.java
@@ -0,0 +1,168 @@
+// 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 com.google.devtools.build.lib.syntax.ASTNode;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
+import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
+import com.google.devtools.build.lib.syntax.compiler.Jump.ReferenceComparison;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+/**
+ * Superclass for various variable representations during compile time.
+ */
+public abstract class Variable {
+
+ /**
+ * The index in the byte code local variable array of the method.
+ *
+ * <p>package-protected for access by VariableScope and variable-modifying byte code generators
+ */
+ final int index;
+
+ private Variable(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Store operands on the stack to this variables array slot in byte code.
+ */
+ public abstract ByteCodeAppender store();
+
+ /**
+ * A variable generated for a named local variable in Skylark.
+ *
+ * <p>To get correct Python-style semantics in byte code, we need to allow control-flow paths
+ * along which variables may be "undefined". This must result in a runtime error.
+ */
+ public static class SkylarkVariable extends Variable {
+
+ public final String name;
+ private final ByteCodeAppender store;
+
+ SkylarkVariable(String name, int index) {
+ super(index);
+ this.name = name;
+ this.store = VariableStore.into(this);
+ }
+
+ /**
+ * Builds a ByteCodeAppender for loading this variable which makes sure it was defined before
+ * this use.
+ */
+ public ByteCodeAppender load(VariableScope scope, AstAccessors debugAccessors) {
+ // the variable may not be defined
+ // along the control-flow path taken at runtime, so we need to check for that then
+ LabelAdder end = new LabelAdder();
+ return new ByteCodeAppender.Simple(
+ // we don't generate primitive local variables from Skylark variables currently
+ // we'd also need a more elaborate way to check whether they were undefined
+ MethodVariableAccess.REFERENCE.loadOffset(index),
+ Duplication.SINGLE,
+ Jump.ifReferenceOperandToNull(ReferenceComparison.NOT_EQUAL).to(end),
+ Removal.SINGLE,
+ // not a method parameter or variable defined previously in the method body, must look it
+ // up in the environment passed to the call
+ scope.loadEnvironment(),
+ new TextConstant(name),
+ debugAccessors.loadAstNode,
+ ByteCodeUtils.invoke(
+ SkylarkVariable.class,
+ "lookupUnboundVariable",
+ Environment.class,
+ String.class,
+ ASTNode.class),
+ end);
+ }
+
+ @Override
+ public ByteCodeAppender store() {
+ return store;
+ }
+
+ /**
+ * Looks for the variable in the method calls outside environment and fail with debug info
+ * if not found.
+ */
+ public static Object lookupUnboundVariable(Environment global, String variable, ASTNode node)
+ throws EvalExceptionWithStackTrace {
+ try {
+ return global.lookup(variable);
+ } catch (NoSuchVariableException e) {
+ throw new EvalExceptionWithStackTrace(
+ new EvalException(
+ node.getLocation(),
+ "local variable '" + variable + "' referenced before assignment"),
+ node);
+ }
+ }
+ }
+
+ /**
+ * Parameter of a Skylark function.
+ *
+ * <p>These will always have a value along each intra-procedural control-flow path and thus we
+ * can generate simpler code.
+ */
+ public static final class SkylarkParameter extends SkylarkVariable {
+
+ SkylarkParameter(String name, int index) {
+ super(name, index);
+ }
+
+ @Override
+ public ByteCodeAppender load(VariableScope scope, AstAccessors debugAccessors) {
+ return new ByteCodeAppender.Simple(MethodVariableAccess.REFERENCE.loadOffset(index));
+ }
+ }
+
+ /**
+ * A variable generated for internal use and thus the property holds that all uses are dominated
+ * by their definition and we can actually type it.
+ */
+ public static final class InternalVariable extends Variable {
+
+ public final TypeDescription type;
+ private final ByteCodeAppender store;
+
+ InternalVariable(TypeDescription type, int index) {
+ super(index);
+ this.type = type;
+ this.store = VariableStore.into(this);
+ }
+
+ /**
+ * Builds a simple StackManipulation which loads the variable from its index while taking into
+ * account the correct type.
+ */
+ public StackManipulation load() {
+ return MethodVariableAccess.forType(type).loadOffset(index);
+ }
+
+ @Override
+ public ByteCodeAppender store() {
+ return store;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableScope.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableScope.java
new file mode 100644
index 0000000000..b077cb879f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableScope.java
@@ -0,0 +1,175 @@
+// 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 com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Identifier;
+import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
+import com.google.devtools.build.lib.syntax.compiler.Variable.SkylarkParameter;
+import com.google.devtools.build.lib.syntax.compiler.Variable.SkylarkVariable;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender.Simple;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Tracks variable scopes and and their assignment to indices of the methods local variable array.
+ */
+public class VariableScope {
+
+ /**
+ * The scope of a whole function.
+ *
+ * <p>This manages the variable index allocation for the whole function.
+ * We do not try to re-use variable slots in sub-scopes and once variables are not live anymore.
+ * Instead we rely on the register allocation of the JVM JIT compiler to remove any unnecessary
+ * ones we add.
+ */
+ private static final class FunctionVariableScope extends VariableScope {
+ private final StackManipulation loadEnvironment;
+ private final int parameterCount;
+ private int next;
+
+ private FunctionVariableScope(List<String> parameterNames) {
+ super(null, parameterNames);
+ this.parameterCount = parameterNames.size();
+ InternalVariable environment =
+ freshVariable(new TypeDescription.ForLoadedType(Environment.class));
+ loadEnvironment = MethodVariableAccess.REFERENCE.loadOffset(environment.index);
+ }
+
+ @Override
+ int next() {
+ return next++;
+ }
+
+ @Override
+ public StackManipulation loadEnvironment() {
+ return loadEnvironment;
+ }
+
+ @Override
+ public ByteCodeAppender createLocalVariablesUndefined() {
+ List<ByteCodeAppender> code = new ArrayList<>();
+ int skipped = 0;
+ Simple nullConstant = new ByteCodeAppender.Simple(NullConstant.INSTANCE);
+ for (Variable variable : variables.values()) {
+ if (skipped >= parameterCount) {
+ code.add(nullConstant);
+ code.add(variable.store());
+ } else {
+ skipped++;
+ }
+ }
+ return ByteCodeUtils.compoundAppender(code);
+ }
+ }
+
+ /**
+ * Initialize a new VariableScope for a function.
+ *
+ * <p>Will associate the names in order with the local variable indices beginning at 0.
+ * Additionally adds an unnamed local variable parameter at the end for the
+ * {@link com.google.devtools.build.lib.syntax.Environment}. This is needed for
+ * compiling {@link com.google.devtools.build.lib.syntax.UserDefinedFunction}s.
+ */
+ public static VariableScope function(List<String> parameterNames) {
+ return new FunctionVariableScope(parameterNames);
+ }
+
+ /** only null for the topmost FunctionVariableScope */
+ @Nullable private final VariableScope parent;
+
+ /** default for access by subclass */
+ final Map<String, SkylarkVariable> variables;
+
+ private VariableScope(VariableScope parent) {
+ this.parent = parent;
+ variables = new LinkedHashMap<>();
+ }
+
+ private VariableScope(VariableScope parent, List<String> parameterNames) {
+ this(parent);
+ for (String variable : parameterNames) {
+ variables.put(variable, new SkylarkParameter(variable, next()));
+ }
+ }
+
+ /** delegate next variable index allocation to topmost scope */
+ int next() {
+ return parent.next();
+ }
+
+ // TODO(klaasb) javadoc
+ public SkylarkVariable getVariable(Identifier identifier) {
+ String name = identifier.getName();
+ SkylarkVariable variable = variables.get(name);
+ if (variable == null) {
+ variable = new SkylarkVariable(name, next());
+ variables.put(name, variable);
+ }
+ return variable;
+ }
+
+ /**
+ * @return a {@link StackManipulation} that loads the {@link Environment} parameter of the
+ * function.
+ */
+ public StackManipulation loadEnvironment() {
+ return parent.loadEnvironment();
+ }
+
+ /**
+ * @return a fresh anonymous variable which will never collide with user-defined ones
+ */
+ public InternalVariable freshVariable(TypeDescription type) {
+ return new InternalVariable(type, next());
+ }
+
+ /**
+ * @return a fresh anonymous variable which will never collide with user-defined ones
+ */
+ public InternalVariable freshVariable(Class<?> type) {
+ return freshVariable(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Create a sub scope in which variables can shadow variables from super scopes like this one.
+ *
+ * <p>Sub scopes don't ensure that variables are initialized with null for "undefined".
+ */
+ public VariableScope createSubScope() {
+ return new VariableScope(this);
+ }
+
+ /**
+ * Create code that initializes all variables corresponding to Skylark variables to null.
+ *
+ * <p>This is needed to make sure a byte code variable exists along all code paths and that we
+ * can check at runtime whether it wasn't defined along the path actually taken.
+ */
+ public ByteCodeAppender createLocalVariablesUndefined() {
+ return parent.createLocalVariablesUndefined();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableStore.java b/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableStore.java
new file mode 100644
index 0000000000..cd5af7e903
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableStore.java
@@ -0,0 +1,106 @@
+// 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 com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
+import com.google.devtools.build.lib.syntax.compiler.Variable.SkylarkVariable;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation.Size;
+import net.bytebuddy.implementation.bytecode.StackSize;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// TODO(klaasb) javadoc
+enum VariableStore {
+ INTEGER(Opcodes.ISTORE, StackSize.SINGLE),
+ LONG(Opcodes.LSTORE, StackSize.DOUBLE),
+ FLOAT(Opcodes.FSTORE, StackSize.SINGLE),
+ DOUBLE(Opcodes.DSTORE, StackSize.DOUBLE),
+ REFERENCE(Opcodes.ASTORE, StackSize.SINGLE);
+
+ private final int opCode;
+ private final Size size;
+
+ private VariableStore(int opCode, StackSize size) {
+ this.opCode = opCode;
+ this.size = size.toIncreasingSize();
+ }
+
+ // TODO(klaasb) javadoc
+ private VariableIndexStore into(int index) {
+ return new VariableIndexStore(index);
+ }
+
+ // TODO(klaasb) javadoc
+ public static VariableIndexStore into(SkylarkVariable variable) {
+ return REFERENCE.into(variable.index);
+ }
+
+ // TODO(klaasb) javadoc
+ public static VariableIndexStore into(InternalVariable variable) {
+ return forType(variable.type).into(variable.index);
+ }
+
+ // TODO(klaasb) javadoc
+ class VariableIndexStore implements ByteCodeAppender {
+
+ private int operandIndex;
+
+ private VariableIndexStore(int operandIndex) {
+ this.operandIndex = operandIndex;
+ }
+
+ @Override
+ public ByteCodeAppender.Size apply(
+ MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ methodVisitor.visitVarInsn(opCode, operandIndex);
+ return new ByteCodeAppender.Size(
+ size.getMaximalSize(), Math.max(instrumentedMethod.getStackSize(), operandIndex + 1));
+ }
+
+ @Override
+ public String toString() {
+ return "VariableStore(" + opCode + ", " + operandIndex + ")";
+ }
+ }
+
+ /**
+ * Selects the correct VariableStore value for the given type
+ */
+ public static VariableStore forType(TypeDescription type) {
+ if (type.isPrimitive()) {
+ if (type.represents(long.class)) {
+ return LONG;
+ } else if (type.represents(double.class)) {
+ return DOUBLE;
+ } else if (type.represents(float.class)) {
+ return FLOAT;
+ } else {
+ Preconditions.checkArgument(
+ !type.represents(void.class), "Variables can't be of void type");
+ return INTEGER;
+ }
+ } else {
+ return REFERENCE;
+ }
+ }
+}