diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/compiler')
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; + } + } +} |