aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/compiler/VariableScope.java
blob: b077cb879fc239673e0000274bbedb940d556ca3 (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
169
170
171
172
173
174
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();
  }
}