blob: 39dbe7b9870389628c36d01b89ddfa93477bb45a [file] [log] [blame]
// 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.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 {
Object value = global.lookup(variable);
if (value == null) {
throw new EvalExceptionWithStackTrace(
new EvalException(
node.getLocation(),
"local variable '" + variable + "' referenced before assignment"),
node);
}
return value;
}
}
/**
* 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;
}
}
}