Skip to content

Commit

Permalink
[GR-24231] Allow Insight scripts to iterate over current stack.
Browse files Browse the repository at this point in the history
PullRequest: graal/7817
  • Loading branch information
Jaroslav Tulach committed Dec 15, 2020
2 parents 85f6846 + 90c629d commit b32be92
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 160 deletions.
1 change: 1 addition & 0 deletions tools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This changelog summarizes major changes between Truffle Tools versions.
## Version 21.0.0

* `--insight` option is no longer considered experimental
* Use `ctx.iterateFrames` to [access whole stack and its variables](docs/Insight-Manual.md#Accessing-whole-Stack)

## Version 20.3.0

Expand Down
72 changes: 30 additions & 42 deletions tools/docs/Insight-Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,11 +692,11 @@ C, C++, Fortran, Rust and inspect with JavaScript, Ruby & co.!

### Minimal Overhead when Accessing Locals

GraalVM [Insight](Insight.md) is capable to access local variables. Is that for free
or is there an inherent slowdown associated with each variable access? The answer
is: **it depends**!
GraalVM [Insight](Insight.md) is capable to access local variables. Moreover it
is almost for free. Insight code accessing local variables blends with the
actual function code defining them and there is no visible slowdown.

Let's demonstrate the issue on [sieve.js](../../vm/benchmarks/agentscript/sieve.js) -
Let's demonstrate the this on [sieve.js](../../vm/benchmarks/agentscript/sieve.js) -
an algorithm to compute hundred thousand of prime numbers. It keeps the
so far found prime numbers in a linked list constructed via following
function:
Expand Down Expand Up @@ -752,51 +752,39 @@ all hundred thousand prime numbers, we print the result. Let's try it:
```bash
$ graalvm/bin/js -e "var count=50" --insight=sieve-filter1.js --file sieve.js | grep Hundred | tail -n 2
Hundred thousand prime numbers from 2 to 1299709 has sum 62260698721
Hundred thousand prime numbers in 288 ms
```

Well, there is a significant slowdown. What is it's reason? The primary reason
for the slowdown is the ability of GraalVM to inline the Insight frame access
to the local variable `frame.number`. Let's demonstrate it. Right now there are three
accesses - let's replace them with a single one:
```js
insight.on('enter', (ctx, frame) => {
let n = frame.number;
sum += n;
if (n > max) {
max = n;
}
}, {
roots: true,
rootNameFilter: 'Filter'
});
Hundred thousand prime numbers in 74 ms
```

after storing the `frame.number` into the temporary variable `n` we get following
performance results:

```bash
$ graalvm/bin/js -e "var count=50" --insight=sieve-filter2.js --file sieve.js | grep Hundred | tail -n 2
Hundred thousand prime numbers from 2 to 1299709 has sum 62260698721
Hundred thousand prime numbers in 151 ms
```
No slowdown at all. [Insight](Insight.md) gives us great instrumentation capabilities - when combined with
the great inlining algorithms of [GraalVM](http://graalvm.org/downloads)
we can even access local variables with almost no performance penalty!

Faster. That confirms our expectations - the access to `frame.number` isn't
inlined - e.g. it is not optimized enough right now. If we just could get
better inlining!
### Accessing whole Stack

Luckily we can. [GraalVM EE](https://www.graalvm.org/downloads/) is known for having better inlining characteristics
than GraalVM CE. Let's try to use it:
There is a way for [Insight](Insight.md) to access the whole execution stack.
Following code snippet shows how to do that:

```bash
$ graalvm-ee/bin/js -e "var count=50" --insight=sieve-filter1.js --file sieve.js | grep Hundred | tail -n 2
Hundred thousand prime numbers from 2 to 1299709 has sum 62260698721
Hundred thousand prime numbers in 76 ms
```js
insight.on("return", function(ctx, frame) {
print("dumping locals");
ctx.iterateFrames((at, vars) => {
for (let p in vars) {
print(` at ${at.name} (${at.source.name}:${at.line}:${at.column}) ${p} has value ${vars[p]}`);
}
});
print("end of locals");
}, {
roots: true
});
```

Voilà! [Insight](Insight.md) gives us great instrumentation capabilities - when combined with
the great inlining algorithms of [GraalVM Enterprise Edition](http://graalvm.org/downloads)
we can even access local variables with almost no performance penalty!
Whenever the [Insight](Insight.md) hook is triggered it prints the current execution
stack with `name` of the function, `source.name`, `line` and `column`. Moreover
it also prints values of all local `vars` at each frame. It is also possible to
modify values of existing variables by assigning new values to them: `vars.n = 42`.
Accessing whole stack is flexible, but unlike [access to locals in the current
execution frame](Insight-Manual.md#modifying-local-variables), it is not fast
operation. Use rarely, if you want your program to continue running at full speed!

<!--
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.tools.agentscript.impl;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;

abstract class AbstractContextObject implements TruffleObject {
static final ArrayObject MEMBERS = ArrayObject.array(
"name", "source", "characters",
"line", "startLine", "endLine",
"column", "startColumn", "endColumn");

@CompilerDirectives.CompilationFinal private String name;
@CompilerDirectives.CompilationFinal(dimensions = 1) private int[] values;

abstract Node getInstrumentedNode();

abstract SourceSection getInstrumentedSourceSection();

Object readMember(String member) throws UnknownIdentifierException {
int index;
switch (member) {
case "name":
if (name == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
name = getInstrumentedNode().getRootNode().getName();
}
return name;
case "characters": {
CompilerDirectives.transferToInterpreter();
final SourceSection ss = getInstrumentedSourceSection();
if (ss == null) {
return NullObject.nullCheck(null);
}
return ss.getCharacters().toString();
}
case "source": {
final SourceSection ss = getInstrumentedSourceSection();
if (ss == null) {
return NullObject.nullCheck(null);
}
return new SourceEventObject(ss.getSource());
}
case "line":
case "startLine":
index = 0;
break;
case "endLine":
index = 1;
break;
case "column":
case "startColumn":
index = 2;
break;
case "endColumn":
index = 3;
break;
default:
throw UnknownIdentifierException.create(member);
}
if (values == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
values = valuesForContext();
}
return values[index];
}

@CompilerDirectives.TruffleBoundary
private int[] valuesForContext() {
final SourceSection section = getInstrumentedSourceSection();
if (section == null) {
return new int[4];
}
return new int[]{
section.getStartLine(),
section.getEndLine(),
section.getStartColumn(),
section.getEndColumn()
};
}
}
Loading

0 comments on commit b32be92

Please sign in to comment.