/** * Saves byte code to a temporary directory prefixed with "skylarkbytecode" in the system default * temporary directory. */ private void saveByteCode(Unloaded<CompiledFunction> unloadedImplementation) { if (debugCompiler) { try { if (debugFolder == null) { debugFolder = Files.createTempDirectory("skylarkbytecode").toFile(); } unloadedImplementation.saveIn(debugFolder); } catch (IOException e) { throw new RuntimeException(e); } } }
/** * Generates a subclass of {@link CompiledFunction} with a static method "call" and static methods * for getting information from a {@link DebugInfo} instance. * * <p>The "call" method contains the compiled version of this function's AST. */ 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 String path = location.getPath() != null ? location.getPath().getPathString().replace('/', '$') : ""; String compiledFunctionClassName = CompiledFunction.class.getCanonicalName() + path + "$" + getName(); compilerDebug("Compiling " + getLocationPathAndLine() + " " + getName()); try { int publicStatic = Visibility.PUBLIC.getMask() | Ownership.STATIC.getMask(); TypeDescription.Latent latentCompiledFunctionClass = new TypeDescription.Latent( compiledFunctionClassName, publicStatic | TypeManifestation.FINAL.getMask(), new TypeDescription.ForLoadedType(CompiledFunction.class), Collections.<TypeDescription>emptyList()); MethodDescription getAstNode = new MethodDescription.Latent( latentCompiledFunctionClass, new MethodDescription.Token( "getAstNode", publicStatic | MethodManifestation.FINAL.getMask(), new TypeDescription.ForLoadedType(ASTNode.class), Arrays.asList(new TypeDescription.ForLoadedType(int.class)))); MethodDescription getLocation = new MethodDescription.Latent( latentCompiledFunctionClass, new MethodDescription.Token( "getLocation", publicStatic | MethodManifestation.FINAL.getMask(), new TypeDescription.ForLoadedType(Location.class), Arrays.asList(new TypeDescription.ForLoadedType(int.class)))); DebugInfo debugInfo = new DebugInfo(getAstNode, getLocation); FunctionSignature sig = signature.getSignature(); VariableScope scope = VariableScope.function(sig.getNames()); Implementation compiledImplementation = compileBody(scope, debugInfo); List<Class<?>> parameterTypes = sig.getShape().toClasses(); parameterTypes.add(Environment.class); Unloaded<CompiledFunction> unloadedImplementation = new ByteBuddy() .withClassVisitor(new StackMapFrameClassVisitor(debugCompilerPrintByteCode)) .subclass(CompiledFunction.class) .name(compiledFunctionClassName) .defineMethod( "call", Object.class, parameterTypes, Visibility.PUBLIC, Ownership.STATIC, MethodManifestation.FINAL) .intercept(compiledImplementation) .defineMethod(getAstNode) // TODO(bazel-team) unify the two delegate fields into one, probably needs a custom // ImplementationDelegate that adds it only once? or just create the static field // itself with the correct value and create getAstNode & getLocation with a custom // implementation using it .intercept( MethodDelegation.to(debugInfo, DebugInfo.class, "getAstNodeDelegate") .filter(ElementMatchers.named("getAstNode"))) .defineMethod(getLocation) .intercept( MethodDelegation.to(debugInfo, DebugInfo.class, "getLocationDelegate") .filter(ElementMatchers.named("getLocation"))) .make(); saveByteCode(unloadedImplementation); Class<? extends CompiledFunction> functionClass = unloadedImplementation .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); return Optional.of( ReflectionUtils.getMethod( functionClass, "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(); }