aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/compiler/Variable.java
blob: 41e2e5d32d499667e0ae2a665c81b79ec47890c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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;
    }
  }
}