Skip to content

Commit

Permalink
Added AST inlining/splitting support
Browse files Browse the repository at this point in the history
This change adds the basic infrastructure to manage inlining/splitting
at the AST level in a Truffle-based language.

- this change comes with
  - tests
  - JavaDocs

- updated README
- updated .gitignore
- add Eclipse test launch configuration

Signed-off-by: Stefan Marr <[email protected]>
  • Loading branch information
smarr committed Mar 16, 2018
1 parent 85aaa23 commit c68735f
Show file tree
Hide file tree
Showing 27 changed files with 1,300 additions and 5 deletions.
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
.metadata
build
/build
codacy-coverage.json
graal
/graal
jacoco.exec
jacoco.xml
libs
/libs
mx
src_gen
/src_gen
/docs
21 changes: 21 additions & 0 deletions .settings/BD-Tests.launch
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/BlackDiamonds/tests"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=BlackDiamonds/tests"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="BlackDiamonds"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/>
</launchConfiguration>
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ The benefit of this optimization is to improve interpreter performance, reduce
compilation time, and possibly simplify interpreter debugging since it simplifies
execution drastically.

#### 4. Inlining/Splitting: Support parse-time inlining and run-time splitting

The `inlining` diamond provides infrastructure to support inlining at parse time
and splitting at execution time. Inlining enables us to optimize more complex
structures such as loops, iteration, selection, or other elements that often
take lambdas, closures, or other kind of anonymous methods. If they are provided
lexically, and are trivially non-accessible by language constructs, it can be
very beneficial to inline them on the AST level already to optimize execution
time in the interpreter, which can also reduce compilation time.

This infrastructure provides the basic mechanisms that a language independent.
This includes a general visitor that can adapt lexical scopes for instance also
after simple splitting, which can be necessary, for instance to ensure that
the split methods are independent and specialize independently at run time.


License and Acknowledgements
----------------------------
Expand Down
26 changes: 26 additions & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -236,5 +236,31 @@
<arg line="-l Java -r jacoco.xml" />
</java>
</target>

<target name="docs">
<javadoc
destdir="docs" author="true" version="true" use="true"
windowtitle="Black Diamonds JavaDoc"
sourcepath="src"
packagenames="bd.*"
defaultexcludes="yes"
excludepackagenames="com.oracle.*"
public="true"
classpathref="project.classpath">

<!-- <fileset dir="src" defaultexcludes="yes">
<include name="src/**"/> -->
<!-- <exclude name="com/dummy/test/doc-files/**"/> -->
<!-- </fileset> -->

<!-- <doctitle><![CDATA[<h1>Test</h1>]]></doctitle>
<bottom><![CDATA[<i>Copyright &#169; 2000 Dummy Corp. All Rights Reserved.</i>]]></bottom>
<tag name="todo" scope="all" description="To do:"/>
<group title="Group 1 Packages" packages="com.dummy.test.a*"/>
<group title="Group 2 Packages" packages="com.dummy.test.b*:com.dummy.test.c*"/>
<link offline="true" href="http://docs.oracle.com/javase/7/docs/api/" packagelistLoc="C:\tmp"/>
<link href="http://docs.oracle.com/javase/7/docs/api/"/> -->
</javadoc>
</target>

</project>
3 changes: 3 additions & 0 deletions src/bd/basic/IdProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public interface IdProvider<Id> {
/**
* Gets a Java string and needs to return an identifier. Typically, this is some form of
* symbol or interned string that can be safely compared with reference equality.
*
* @param id the string version of the id
* @return the unique identifier
*/
Id getId(String id);
}
158 changes: 158 additions & 0 deletions src/bd/inlining/InlinableNodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package bd.inlining;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;

import com.oracle.truffle.api.dsl.NodeFactory;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;

import bd.basic.IdProvider;
import bd.basic.ProgramDefinitionError;
import bd.inlining.Inliner.FactoryInliner;
import bd.settings.VmSettings;


/**
* Represents the entry point to access the inlining functionality controlled with
* the @{@link Inline} annotation.
*
* <p>A typical use case would be in a parser, which can use the
* {@link #inline(Object, List, ScopeBuilder, SourceSection)} to request inlining.
* For this purpose, {@link InlinableNodes} takes a list of node classes and factories as
* candidates for inlining.
*
* @param <Id> the type of the identifiers used for mapping to primitives, typically some form
* of interned string construct (see {@link IdProvider})
*/
public final class InlinableNodes<Id> {

/** The id provider is used to map strings in the {@link Inline} annotation to ids. */
private final IdProvider<Id> ids;

/** Inlinable nodes for selector. */
private final HashMap<Id, Inliner> inlinableNodes;

/**
* Initialize this registry for inlinable nodes.
*
* @param ids an id provider to convert strings to identifiers
* @param inlinableNodes list of {@link Node} classes that have the @{@link Inline}
* annotation
* @param inlinableFactories list of {@link NodeFactory}s for classes that have
* the @{@link Inline} annotation
*/
public InlinableNodes(final IdProvider<Id> ids,
final List<Class<? extends Node>> inlinableNodes,
final List<NodeFactory<? extends Node>> inlinableFactories) {
this.ids = ids;
this.inlinableNodes = new HashMap<>();
initializeNodes(inlinableNodes);
initializeFactories(inlinableFactories);
}

private void initializeNodes(final List<Class<? extends Node>> inlinableNodes) {
if (inlinableNodes == null) {
return;
}

for (Class<? extends Node> nodeClass : inlinableNodes) {
Inline[] ann = getInlineAnnotation(nodeClass);
assert ann != null;

Constructor<?>[] ctors = nodeClass.getConstructors();
assert ctors.length == 1 : "We expect nodes marked with Inline to have only one constructor,"
+ " or be used via node factories.";

for (Inline inAn : ann) {
assert !"".equals(inAn.selector());
Id selector = ids.getId(inAn.selector());
assert !this.inlinableNodes.containsKey(selector);

@SuppressWarnings("unchecked")
Inliner inliner = new Inliner(inAn, (Constructor<? extends Node>) ctors[0]);

this.inlinableNodes.put(selector, inliner);
}
}
}

private void initializeFactories(
final List<NodeFactory<? extends Node>> inlinableFactories) {
if (inlinableFactories == null) {
return;
}

for (NodeFactory<? extends Node> fact : inlinableFactories) {
Inline[] ann = getInlineAnnotation(fact);
assert ann != null;

for (Inline inAn : ann) {
assert !"".equals(inAn.selector());
Id selector = ids.getId(inAn.selector());

assert !this.inlinableNodes.containsKey(selector);
Inliner inliner = new FactoryInliner(inAn, fact);
this.inlinableNodes.put(selector, inliner);
}
}
}

/**
* Try to construct an inlined version for a potential node (which is not given here, but
* would be constructed as a fall-back version).
*
* <p>The potential node is identified with a {@code selector} and it is determined whether
* inlining is applicable by using the data from {@link Inline} and matching it with the
* {@code argNodes}.
*
* @param <N> the node type of the return value
* @param <S> the type of the {@link ScopeBuilder}
*
* @param selector to identify a potential inline replacement
* @param argNodes the argument/child nodes to the potential node
* @param builder used for providing context to the inlining operation
* @param source the source section of the potential node
* @return the inlined version of the potential node, or {@code null}, if inlining is not
* applicable
* @throws ProgramDefinitionError in case the inlining would result in a structural violation
* of program definition constraints. The error is language specific and not
* triggered by the inlining logic.
*/
public <N extends Node, S extends ScopeBuilder<S>> N inline(final Id selector,
final List<N> argNodes, final S builder, final SourceSection source)
throws ProgramDefinitionError {
Inliner inliner = inlinableNodes.get(selector);
if (inliner == null || (VmSettings.DYNAMIC_METRICS && inliner.isDisabled())) {
return null;
}

if (!inliner.matches(argNodes)) {
return null;
}

return inliner.create(argNodes, builder, source);
}

/**
* Get the {@link Inline} annotation from a {@link NodeFactory}.
*/
private <T extends Node> Inline[] getInlineAnnotation(final NodeFactory<T> factory) {
Class<?> nodeClass = factory.getNodeClass();
return nodeClass.getAnnotationsByType(Inline.class);
}

/**
* Get the {@link Inline} annotation from a {@link Node} class.
*/
private Inline[] getInlineAnnotation(final Class<? extends Node> nodeClass) {
Inline[] annotations = nodeClass.getAnnotationsByType(Inline.class);
if (annotations == null || annotations.length < 1) {
throw new IllegalArgumentException("The class " + nodeClass.getName()
+ " was registered with InlinableNodes, but was not marked with @Inline."
+ " Please make sure it has the right annotation.");
}
return annotations;
}
}
71 changes: 71 additions & 0 deletions src/bd/inlining/Inline.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package bd.inlining;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
* Annotation that marks nodes as inlined versions for language constructs.
* More complex nodes/constructs are to be replaced by these nodes in the parser.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Repeatable(Inline.Container.class)
public @interface Inline {

/**
* Selector, i.e., identifier for a node to determine that inlining applies.
*
* @return a string identifying the node
*/
String selector();

/**
* The arguments, by index, which need to be inlinable to make this node applicable.
*
* @return indexes of the inlinable argument nodes
*/
int[] inlineableArgIdx();

/**
* The argument nodes might need extra temporary variables when being inlined.
*
* @return indexes of the argument nodes that need a temporary variable
*/
int[] introduceTemps() default {};

/**
* Additional values to be provided as arguments to the node constructor.
*
* @return array value identifies, currently either {@link True} or {@link False}
*/
Class<?>[] additionalArgs() default {};

/**
* Disabled for Dynamic Metrics.
*
* @return true if inlining should not be applied, false otherwise
*/
boolean disabled() default false;

/**
* Represents a true value for an additional argument.
* Can be used for {@link #additionalArgs()}.
*/
class True {}

/**
* Represents a false value for an additional argument.
* Can be used for {@link #additionalArgs()}.
*/
class False {}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface Container {
Inline[] value();
}
}
Loading

0 comments on commit c68735f

Please sign in to comment.