Skip to content

Commit

Permalink
Compile assignments to byte code and throw errors.
Browse files Browse the repository at this point in the history
Add EvalExceptions for cases in which the interpreter would throw them
during evaluation of the function definition.

--
MOS_MIGRATED_REVID=107376021
  • Loading branch information
fweikert authored and damienmg committed Nov 10, 2015
1 parent b487ac6 commit db8b867
Showing 7 changed files with 150 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -14,6 +14,13 @@

package com.google.devtools.build.lib.syntax;

import com.google.common.base.Optional;
import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
import com.google.devtools.build.lib.syntax.compiler.LoopLabels;
import com.google.devtools.build.lib.syntax.compiler.VariableScope;

import net.bytebuddy.implementation.bytecode.ByteCodeAppender;

/**
* Syntax node for an assignment statement.
*/
@@ -66,4 +73,13 @@ void validate(ValidationEnvironment env) throws EvalException {
expression.validate(env);
lvalue.validate(env, getLocation());
}

@Override
ByteCodeAppender compile(
VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo)
throws EvalException {
return new ByteCodeAppender.Compound(
expression.compile(scope, debugInfo),
lvalue.compileAssignment(this, debugInfo.add(this), scope));
}
}
Original file line number Diff line number Diff line change
@@ -75,8 +75,12 @@ final Object eval(Environment env) throws EvalException, InterruptedException {
/**
* Builds a {@link ByteCodeAppender} that implements this expression by consuming its operands
* from the byte code stack and pushing its result.
*
* @throws EvalException for any error that would have occurred during evaluation of the
* function definition that contains this statement, e.g. type errors.
*/
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) {
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo)
throws EvalException {
throw new UnsupportedOperationException(this.getClass().getSimpleName() + " unsupported.");
}
}
112 changes: 112 additions & 0 deletions src/main/java/com/google/devtools/build/lib/syntax/LValue.java
Original file line number Diff line number Diff line change
@@ -14,11 +14,24 @@

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.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
import com.google.devtools.build.lib.syntax.compiler.VariableScope;

import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
* Class representing an LValue.
@@ -121,4 +134,103 @@ private static void validate(ValidationEnvironment env, Location loc, Expression
public String toString() {
return expr.toString();
}

/**
* Compile an assignment within the given ASTNode to these l-values.
*
* <p>The value to possibly destructure and assign must already be on the stack.
*/
public ByteCodeAppender compileAssignment(ASTNode node, AstAccessors debugAccessors,
VariableScope scope) throws EvalException {
List<ByteCodeAppender> code = new ArrayList<>();
compileAssignment(node, debugAccessors, expr, scope, code);
return ByteCodeUtils.compoundAppender(code);
}

/**
* Called recursively to compile the tree of l-values we might have.
*/
private static void compileAssignment(
ASTNode node,
AstAccessors debugAccessors,
Expression leftValue,
VariableScope scope,
List<ByteCodeAppender> code) throws EvalException {
if (leftValue instanceof Identifier) {
code.add(compileAssignment(scope, (Identifier) leftValue));
} else if (leftValue instanceof ListLiteral) {
List<Expression> lValueExpressions = ((ListLiteral) leftValue).getElements();
compileAssignment(node, debugAccessors, scope, lValueExpressions, code);
} else {
String message = String.format(
"Can't assign to expression '%s', only to variables or nested tuples of variables",
leftValue);
throw new EvalExceptionWithStackTrace(
new EvalException(
node.getLocation(),
message),
node);
}
}

/**
* Assumes a collection of values on the top of the stack and assigns them to the l-value
* expressions given.
*/
private static void compileAssignment(
ASTNode node,
AstAccessors debugAccessors,
VariableScope scope,
List<Expression> lValueExpressions,
List<ByteCodeAppender> code) throws EvalException {
InternalVariable objects = scope.freshVariable(Collection.class);
InternalVariable iterator = scope.freshVariable(Iterator.class);
// convert the object on the stack into a collection and store it to a variable for loading
// multiple times below below
code.add(new ByteCodeAppender.Simple(debugAccessors.loadLocation, EvalUtils.toCollection));
code.add(objects.store());
append(code,
// check that we got exactly the amount of objects in the collection that we need
IntegerConstant.forValue(lValueExpressions.size()),
objects.load(),
debugAccessors.loadLocation, // TODO(bazel-team) load better location within tuple
ByteCodeUtils.invoke(
LValue.class, "checkSize", int.class, Collection.class, Location.class),
// get an iterator to assign the objects
objects.load(),
ByteCodeUtils.invoke(Collection.class, "iterator"));
code.add(iterator.store());
// assign each object to the corresponding l-value
for (Expression lValue : lValueExpressions) {
code.add(
new ByteCodeAppender.Simple(
iterator.load(), ByteCodeUtils.invoke(Iterator.class, "next")));
compileAssignment(node, debugAccessors, lValue, scope, code);
}
}

/**
* Compile assignment to a single identifier.
*/
private static ByteCodeAppender compileAssignment(VariableScope scope, Identifier identifier) {
// don't store to/create the _ "variable" the value is not needed, just remove it
if (identifier.getName().equals("_")) {
return new ByteCodeAppender.Simple(Removal.SINGLE);
}
return scope.getVariable(identifier).store();
}

/**
* Checks that the size of a collection at runtime conforms to the amount of l-value expressions
* we have to assign to.
*/
public static void checkSize(int expected, Collection<?> collection, Location location)
throws EvalException {
int actual = collection.size();
if (expected != actual) {
throw new EvalException(
location,
String.format("lvalue has length %d, but rvalue has has length %d", expected, actual));
}
}
}
Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ void validate(ValidationEnvironment env) throws EvalException {
}

@Override
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) {
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
AstAccessors debugAccessors = debugInfo.add(this);
List<ByteCodeAppender> listConstruction = new ArrayList<>();
if (isTuple()) {
Original file line number Diff line number Diff line change
@@ -83,7 +83,8 @@ void validate(ValidationEnvironment env) throws EvalException {

@Override
ByteCodeAppender compile(
VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo) {
VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo)
throws EvalException {
ByteCodeAppender compiledExpression = returnExpression.compile(scope, debugInfo);
return new ByteCodeAppender.Compound(
compiledExpression, new ByteCodeAppender.Simple(MethodReturn.REFERENCE));
Original file line number Diff line number Diff line change
@@ -72,9 +72,13 @@ final void exec(Environment env) throws EvalException, InterruptedException {
*
* <p>A statement implementation should never require any particular state of the byte code
* stack and should leave it in the state it was before.
*
* @throws EvalException for any error that would have occurred during evaluation of the
* function definition that contains this statement, e.g. type errors.
*/
ByteCodeAppender compile(
VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo) {
VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo)
throws EvalException {
throw new UnsupportedOperationException(this.getClass().getSimpleName() + " unsupported.");
}
}
Original file line number Diff line number Diff line change
@@ -81,7 +81,8 @@ public class UserDefinedFunction extends BaseFunction {

protected UserDefinedFunction(Identifier function,
FunctionSignature.WithValues<Object, SkylarkType> signature,
ImmutableList<Statement> statements, Environment.Frame definitionGlobals) {
ImmutableList<Statement> statements, Environment.Frame definitionGlobals)
throws EvalException {
super(function.getName(), signature, function.getLocation());
this.statements = statements;
this.definitionGlobals = definitionGlobals;
@@ -174,7 +175,7 @@ private Object callCompiledFunction(Object[] arguments, FuncallExpression ast, E
*
* <p>The "call" method contains the compiled version of this function's AST.
*/
private Optional<Method> buildCompiledFunction() {
private Optional<Method> buildCompiledFunction() throws EvalException {
// replace the / character in the path so we have file system compatible class names
// the java specification mentions that $ should be used in generated code
// see http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8
@@ -253,11 +254,14 @@ private Optional<Method> buildCompiledFunction() {
"call",
parameterTypes.toArray(new Class<?>[parameterTypes.size()]))
.getLoadedMethod());
} catch (EvalException e) {
// don't capture EvalExceptions
throw e;
} catch (Throwable e) {
compilerDebug("Error while compiling", e);
// TODO(bazel-team) don't capture all throwables? couldn't compile this, log somewhere?
return Optional.absent();
}
return Optional.absent();
}

/**
@@ -280,7 +284,8 @@ private void saveByteCode(Unloaded<CompiledFunction> unloadedImplementation) {
/**
* Builds a byte code implementation of the AST.
*/
private Implementation compileBody(VariableScope scope, DebugInfo debugInfo) {
private Implementation compileBody(VariableScope scope, DebugInfo debugInfo)
throws EvalException {
List<ByteCodeAppender> code = new ArrayList<>(statements.size());
code.add(null); // reserve space for later addition of the local variable initializer

0 comments on commit db8b867

Please sign in to comment.