Re: Unboxed representation of non-nullable types in arrays, what would it take?

183 views
Skip to first unread message

Joshua Warner

unread,
Feb 12, 2013, 5:51:21 PM2/12/13
to av...@googlegroups.com
Simon,

I can't say all of the changes that will be required, but I can comment on stuff in the back-end / code generator that I'm most familiar with:

One of the required changes will be, at a minimum, teaching the code generator how to deal with multiple return values.  A more general solution would be teaching it how to deal with structs (as in C-style structs) - in both parameters and return values, potentially in loads and stores, etc...

There's a landmine you'll want to be aware of as far as "inlining" the non-nullable type into another class: you loose the ability to atomically store new values (i.e. not atomically _update_, but atomically _store_ - so you can end up with half of one value, and half of another).  You'd have to impose the additional constraint that the field you intend to inline (whose type is the non-nullable type in question) is final.

And sadly, even if it is final at the java level, there are no guarantees that something won't store into it at the JVM level.  For instance, System.out is a final field that is updated in System.setOut().  It's technically perfectly valid, verifiable bytecode.

-Joshua


On 12 February 2013 09:31, <simon.och...@gmail.com> wrote:
Hi Joel,

I'm currently trying to get up to speed with the codebase (I can read C++, but I'm not familiar with its idioms) and I'm wondering if it might be a good introduction to look into implementing arrays which store non-nullable types (e, g, Scala's AnyVals) without boxing them. (They will be boxed as soon as they are accessed.)

Considering the current architecture and the existing functionality, how hard would that task be?

From a data flow POV, one would need to know/compute the physical size of such a type in memory as the input and create a specialized array representation as the output. But I'm not sure whether unboxing those things into arrays will cause issues with the GC.

Thanks and bye,

Simon

PS: I'm currently compiling/testing Scala the way you described. :-)

--
You received this message because you are subscribed to the Google Groups "Avian" group.
To unsubscribe from this group and stop receiving emails from it, send an email to avian+un...@googlegroups.com.
To post to this group, send email to av...@googlegroups.com.
Visit this group at http://groups.google.com/group/avian?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Joel Dice

unread,
Feb 12, 2013, 5:55:15 PM2/12/13
to av...@googlegroups.com
On Tue, 12 Feb 2013, simon.och...@gmail.com wrote:

> Hi Joel,
>
> I'm currently trying to get up to speed with the codebase (I can read C++,
> but I'm not familiar with its idioms) and I'm wondering if it might be a
> good introduction to look into implementing arrays which store non-nullable
> types (e, g, Scala's AnyVals) without boxing them. (They will be boxed as
> soon as they are accessed.)
>
> Considering the current architecture and the existing functionality, how
> hard would that task be?

Take a look at types.def, which defines various types using a fairly
simple S-expression format (details available here:
https://groups.google.com/group/avian/msg/4bcc66d450d97da1). The approach
I would take is to add a new type to that file that looks like this:

(type valueArray
(object elementType)
(array uintptr_t body))

The elementType field would be a reference to the class of which the
elements in the array are instances, and the body field would be an array
of length N * S, where N is the number of elements and S is the number of
machine words required to store each element (which might be less than one
for small values), taking into account the natural alignment of each
field. Instances of this type would be laid out in memory like this:

|-----------------|
| object header | <- pointer to the valueArray class
|-----------------|
| elementType | <- pointer to the element class
|-----------------|
| size | <- length of array in machine words
|-----------------|
| body | <- as many machine words as indicated by size
| ... |
|-----------------|

To determine the number of elements in the array, you would divide the
size by the size of each element. To determine the address of the element
at index I, you would add 3*(word size) to the address of the array, plus
I*(element size in bytes).

All of this assumes that the value type is completely "flattened", meaning
there are no reference fields. If that assumption is wrong, things will
get more complicated.

Anyway, once you have that type at your disposal, you'll need to add
support to the JIT compiler (compile.cpp) and/or interpreter
(interpret.cpp) to special-case array creation, access, and mutation (e.g.
anewarray, aaload, aastore, arraylength) instructions to do the right
thing for your value types. Also, JNI functions like GetArrayLength and
GetObjectArrayElement would need to be taught about them too if you wanted
to be able to access them from JNI.

Does that help at all?

Joel Dice

unread,
Feb 12, 2013, 6:06:00 PM2/12/13
to av...@googlegroups.com
On Tue, 12 Feb 2013, Joshua Warner wrote:

> Simon,
> I can't say all of the changes that will be required, but I can comment on
> stuff in the back-end / code generator that I'm most familiar with:
>
> One of the required changes will be, at a minimum, teaching the code
> generator how to deal with multiple return values. �A more general solution
> would be teaching it how to deal with structs (as in C-style structs) - in
> both parameters and return values, potentially in loads and stores, etc...

I may be mistaken, but I don't think he's trying to tackle any of that
just yet. You're right, though, that we'll eventually need to work on
that in order to make value types perform well.

> There's a landmine you'll want to be aware of as far as "inlining" the
> non-nullable type into another class: you loose the ability to atomically
> store new values (i.e. not atomically _update_, but atomically _store_ - so
> you can end up with half of one value, and half of another). �You'd have to
> impose the additional constraint that the field you intend to inline (whose
> type is the non-nullable type in question) is final.

Based on earlier discussion, I think this optimization would only apply to
immutable types (i.e. composed entirely of final fields which are either
primitives or final references to other immutable objects).

> And sadly, even if it is final at the java level, there are no guarantees
> that something won't store into it at the JVM level. �For instance,
> System.out is a final field that is updated in System.setOut(). �It's
> technically perfectly valid, verifiable bytecode.

AFAIK, System.setOut has to be implemented using native code precisely
because it can't be implemented in valid, verifiable bytecode. Yes,
native code can break all the rules, but that doesn't mean we can't still
ensure pure Java code (read bytecode with no dirty native tricks) is
typesafe.

Joel Dice

unread,
Feb 12, 2013, 6:24:09 PM2/12/13
to av...@googlegroups.com
On Tue, 12 Feb 2013, simon.och...@gmail.com wrote:

> My first approach would be to not flatten stuff recursively and just keep
> the reference. Do you see any issues with that approach?

That's not necessarily a problem, but it does make the implementation more
complicated since we need to tell the garbage collector about those
references. What reasons might there be to not flatten recursively?

Joshua Warner

unread,
Feb 12, 2013, 6:29:07 PM2/12/13
to av...@googlegroups.com
Like I said, be careful with "inlining" / flattening types where the object itself can be swapped out (as is the case with all class fields, including final, and with array elements).  If you're doing complete updates to the values, you can end up with part of one value and part of another.  There were some early security vulnerabilities in .NET with structs, for this exact reason.  The only way I can think of to guarantee that the field is never updated is (1) make sure it's private, and (2) make sure that nothing in the class writes to it.

-Joshua


--
You received this message because you are subscribed to the Google Groups "Avian" group.
To unsubscribe from this group and stop receiving emails from it, send an email to avian+unsubscribe@googlegroups.com.

Joel Dice

unread,
Feb 12, 2013, 7:20:43 PM2/12/13
to av...@googlegroups.com
On Tue, 12 Feb 2013, simon.och...@gmail.com wrote:

> How would you handle things of variable length, e. g. like String?

I guess I assumed such a type wouldn't be considered since it has a
reference to a mutable field (the char array), although I realize it is
immutable in practice. But yes, if we want to include such cases we'll
need to have some indirection.

Joel Dice

unread,
Feb 12, 2013, 8:53:12 PM2/12/13
to av...@googlegroups.com
On Tue, 12 Feb 2013, simon.och...@gmail.com wrote:

> Well, I guess String is probably a bad example because it makes sense to
> special-case Strings on the VM anyway ...

It's actually a pretty good example, since removing one layer of
indirection from String might have a big impact on performance all by
itself.

> One question: Is there a reason why your primitive/value types don't have a
> size field in the S-expression file but (have to) have one in their memory
> representation?

It's implicit. Every array field has to have a size (so the GC knows how
big it is, among other reasons), so rather than rely on the programmer to
include such a field and tell the code generator (type-generator.cpp) what
it signifies, the code generator just adds one automatically. BTW, I just
checked the code, and the implicit field is actually called "length", not
"size"; my mistake.

Joel Dice

unread,
Feb 25, 2013, 12:12:57 PM2/25/13
to av...@googlegroups.com
On Sun, 24 Feb 2013, simon.och...@gmail.com wrote:

> I have worked through the code base, and I think I understand a lot of the
> small things a bit better now.
> * I intend to not modify the compiler, not the interpreter, because
> without some additional optimizations the only thing it would do would
> be to add box/unbox operations.
> * I would add two flags to constants: ACC_VALUETYPE and ACC_SPECIALIZED.
> + The first one would signal that a class can be optimized/unboxed in
> the way we imagine.
> + The second one would be used to mark data structures which represent
> specializations, e. g. unboxed arrays (or other specialized classes
> later).
> * I use an additional field elementSize for value arrays, so that loads
> and stores don't incur a class-lookup for every operation. Computing the
> length of an array would then be arrayLength/elementSize

That's fine, although it does mean you'll have to find all the places we
retrieve the array length or generate code that does so (e.g. in the
compiler, the interpreter, the JNI, etc.) and make sure they handle these
new arrays properly. Alternatively, we could just generate a new class
for each specialization such that the length really is the length in
elements, and the VMClass.arrayElementSize would be the actual size of
the unboxed element. Then you wouldn't have to change any of array length
code or do division to calculate it. You would have to modify the
compiler and interpreter a bit, but it seems like you'll have to do that
either way.

> A few questions:
>
> * What would be your preferred way to instantiate an instance of some
> class with values from the array (e. g. when loading stuff from an
> array) (assuming that there would be no need to run the constructor) and
> copy the body of instances to the array (e. g. store)?

If you're trying to avoid touching the compiler and interpreter, then that
means doing everything in bytecode, which means the array will have to be
a standard primitive array with fields manually extracted one-by-one from
the array during a load (possibly with bit shifts and masks for smaller
fields), and stored one-by-one during a store. Otherwise, if you don't
mind messing with the compiler, you can just represent the loads and
stores in bytecode as aaload and aastore instructions as usual, which are
converted to inline word-by-word memory copies for value types.

> * There wouldn't be much changes necessary to support 64bit indexed arrays
> (�big arrays�) (apart from the question of how to sneak the additional 4
> bytes through the verifier) is that correct?

Correct, assuming you're only talking about 64-bit architectures, since
the array length field is already 64 bits. If you want big arrays on
32-bit platforms (e.g. file-backed), that's a whole different story.

Joel Dice

unread,
Feb 25, 2013, 3:51:18 PM2/25/13
to av...@googlegroups.com
On Mon, 25 Feb 2013, simon.och...@gmail.com wrote:

> You're right. I just totally messed up to describe it. I use the pre-defined
> length field to store the actual size consumption, just like for the
> existing arrays. When I said "Computing the length of an array would then be
> arrayLength/elementSize" I meant "Computing the number of elements in an
> array ...".

I thing there's still a bit of confusion here. The length field for
exisiting arrays holds the length in elements, i.e. the number of bytes in
a byte array, tne number of shorts in a short array, the number of
pointers in an object array, etc.. So determining the number of elements
in an array requires no calculation, whereas determining the actual size
in memory does require calculation (but only the allocator and GC need to
do that).

> If you're trying to avoid touching the compiler and interpreter
>
>
> No, modifying stuff is fine. It's just important for me that the changes are
> no hacks, but something which can be tested and maintained easily.

Okay, that makes sense.

> Otherwise, if you don't
> mind messing with the compiler, you can just represent the loads
> and
> stores in bytecode as aaload and aastore instructions as usual,
> which are
> converted to inline word-by-word memory copies for value types.
>
>
> Isn't that how it currently works already?

We don't currently store objects by value into an array, so there's not
really a precident.

> My first approach would have been to box/unbox the results of these
> instructions automatically so that it is completely transparent to later
> instructions. Then in the next step I would think about how the accesses
> could be replaced by "unboxed" reads and writes to the array. This would
> require to look at the code not at the instruction-level but at the
> method-level as far as I understand it. �

If I understand correctly, you're saying the first step is to interpret
aastores and aaloads differently for value types such that aastore does a
memory copy from the dereferenced object to the array and an aaload
creates a new object, does the reverse memory copy, and pushes a reference
to the new object on the stack. Is that right (leaving aside the fact
that e.g. anewarray and multianewarray would also need to be modified)?

> No, 32bit is more or less legacy anyway. :-)

Tell that to the Android/Harmony developers. There's so much casting
between 32-bit ints and pointers in libcore.git it will make you cry. I'm
trying to fix that right now.

Joel Dice

unread,
Feb 26, 2013, 10:58:39 AM2/26/13
to av...@googlegroups.com
On Tue, 26 Feb 2013, simon.och...@gmail.com wrote:

> Hi Joel,
>
> I thing there's still a bit of confusion here. �The length field
> for
> exisiting arrays holds the length in elements, i.e. the number
> of bytes in
> a byte array, tne number of shorts in a short array, the number
> of
> pointers in an object array, etc.. �So determining the number of
> elements
> in an array requires no calculation, whereas determining the
> actual size
> in memory does require calculation (but only the allocator and
> GC need to
> do that).
>
>
> sorry for the confusion here ... when I played around what I did was
> basically to use this definition
>
> (type valueArray
> � (extends jobject)
> � (object elementType)
> � (uint32_t elementSize)
> � (array uintptr_t body))
>
> and initialize it more or less this way in machine.cpp:
>
> makeValueArray(t, elementClass, elementSize, totalSize)
> where
> elementSize�=�classFixedSize(t,�elementClass)�-�8
> and
> totalSize�= (elementSize * count) / 8
>

Yes, that makes sense, and it's similar to what I initially suggested.
However, I'm beginning to prefer an alternative which more closely follows
the precedent of exisiting arrays, where a new class is created on demand
for each specialized array type, unlike the solution above which uses a
single class for all value arrays. It does mean we'll end up with more
classes to keep track of, but it matches the VM's current understanding of
arrays better.

For example, if we have a value type Foo, the first time we create an
array of Foo, we create a new array class for it (which we'll reuse when
additional arrays of Foo are created). The array class will have the
element size and a reference to the element type, while the array itself
will have a reference to the class, a length (in elements, i.e. 3 means 3
elements, not necessarilly 3 words or 3 bytes), and space for the elements
themselves (i.e. the body).

>
> > Isn't that how it currently works already?
>
> We don't currently store objects by value into an array, so
> there's not
> really a precident.
>
>
> I meant how language compilers have to do it currently (box value types when
> stored in arrays, unbox them when they are loaded from arrays).
>
> E. g. with this definition
>
> class Foo(val bar: Int) extends AnyVal {
> � def * (that: Foo) = new Foo(this.bar * that.bar)
> }
>
> the following code:
>
> val fooArray = ...
> val foo: Foo = fooArray(0)
> val newFoo: Foo = foo * foo
>
> would be desugared to something like
>
> val fooArray = ...
> val foo: Int = Foo$.unbox(fooArray(0))
> val newFoo: Int = Foo$.*$extension(foo, foo)
>
> by the language compiler.
>
> My idea would have been to keep the unboxed representation in arrays an
> implementation detail first and box things on load and unbox things on store
> so that the rest of the instructions would keep working without any changes
> to the compiler.
> In the next step I would have tried to elide these box/unbox operations
> completely.

Okay, thanks for the clarification.

> If I understand correctly, you're saying the first step is to
> interpret
> aastores and aaloads differently for value types such that
> aastore does a
> memory copy from the dereferenced object to the array and an
> aaload
> creates a new object, does the reverse memory copy, and pushes a
> reference
> to the new object on the stack. �Is that right (leaving aside
> the fact
> that e.g. anewarray and multianewarray would also need to be
> modified)?
>
>
> Yes, that would have been my first step, do you suggest a different
> approach?

No, that sounds good. I just wanted to make sure I understood correctly.

Joel Dice

unread,
Mar 8, 2013, 3:54:51 PM3/8/13
to av...@googlegroups.com
On Fri, 8 Mar 2013, simon.och...@gmail.com wrote:

> However, I'm beginning to prefer an alternative which more
> closely follows
> the precedent of exisiting arrays, where a new class is created
> on demand
> for each specialized array type, unlike the solution above which
> uses a
> single class for all value arrays. ᅵIt does mean we'll end up
> with more
> classes to keep track of, but it matches the VM's current
> understanding of
> arrays better.
>
> For example, if we have a value type Foo, the first time we
> create an
> array of Foo, we create a new array class for it (which we'll
> reuse when
> additional arrays of Foo are created). ᅵThe array class will
> have the
> element size and a reference to the element type, while the
> array itself
> will have a reference to the class, a length (in elements, i.e.
> 3 means 3
> elements, not necessarilly 3 words or 3 bytes), and space for
> the elements
> themselves (i.e. the body).
>
>
> Something like this?https://github.com/soc/avian/commit/c5c778c5c407460cde79e769b1bfb23226cdb55f

Yes, except the VMClass.objectMask class would also need to change so the
GC knows that its not just an array of references like a traditional
object array. Also, the elementSize should be calculated like this:
(classFixedSize(t, elementClass) - BytesPerWord).

> If yes, then I think we agree that this approach seems to be better. Just
> not sure what's the preferred way on Avian to copy the body of instances
> aroundᅵ e. g. on store and to create instances with bodies taken from that
> array e. g. on load and whether this behavior should happen only on the
> compiler or with both the interpreter and the compiler.

It will be simpler to leave the interpreter alone, since the interpreter
is still going to be slow regardless of such optimizations. For
process=interpret builds, let's just treat value types as reference types
(i.e. isValueClass would always return false, or something like that).

For the compiler, we've got two options:

1. (the easy, less efficient option) Defer to an out-of-line thunk to do
the work, e.g.:

uint64_t arrayLoad(MyThread* t, object array, int32_t index) {
// classStaticTable is actually the element type for array classes:
object arrayClass = objectClass(t, array);
object elementType = classStaticTable(t, arrayClass);
if (isValueClass(t, elementType)) {
unsigned size = classArrayElementSize(t, arrayClass);
PROTECT(t, array);
object box = makeNew(t, elementType);
memcpy(reinterpret_cast<uint8_t*>(&box) + BytesPerWord,
reinterpret_cast<uint8_t*>(array) + ArrayBody + (index * size),
size);
return box;
} else {
return reinterpret_cast<uintptr_t>(objectArrayBody(t, array,
index));
}
}

Then, for the aaload instruction, generate a call to the thunk (after
adding it to the list in thunks.cpp) like this:

frame->pushObject(c->call(c->constant(getThunk(t, arrayLoadThunk),
Compiler::AddressType), 0, frame->trace(0, 0), TargetBytesPerWord,
Compiler::ObjectType, 3, c->register_(t->arch->thread()), array, index);

2. (the more difficult but more efficient option) Generate native code to
create the box and copy from the array to the box word-by-word. The key
to making this efficient as possible is to statically analyze the bytecode
to determine the type of the array being loaded from so we know ahead of
time whether it's a value array or reference array, and, if it's the
former, what type of box we need to make and how many words to copy. The
hard part here is probably the bytecode analysis; I suppose we'll need to
add support to the compiler for tracking local and stack reference types.
That would be useful elsewhere, too; it's actually kind of embarrasing
that we haven't done it already.

The aastore case is analagous, except we'd copy from an existing box to
the destination array.

Joel Dice

unread,
Mar 19, 2013, 7:55:22 PM3/19/13
to Joel Dice, av...@googlegroups.com
Hi Simon,

FYI, I'm been thinking more about how to tackle the unboxed value type
problem, not just in arrays, but on the stack and in return values, i.e.
keep them unboxed everywhere except when they really need to be treated as
references.

In particular, I've been reading up on the StackMapTable attribute
(http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.4),
which we could use to cheaply determine the static type of a local
variable at JIT compilation time. As I mentioned in a previous email,
this is necessary to know how e.g. an aaload or aastore should be compiled
based on whether it's for a reference type or a value type. Once we have
that information, the process of dealing with the unboxed representation
everywhere becomes possible since we always know whether we're dealing
with a value type and, if so, how large it is, how to access its fields,
etc.

Anyway, I started hacking something up the weekend before last: basically
adding a new pass in the JIT compiler to create a type table and compute
the true stack frame size (i.e. how much stack we need when all the value
type locals are stored directly instead of as references). That approach
got messy fast because almost all bytecode instructions operate on the
stack and/or local variables, and I realized I'd have to write yet another
giant switch statement which models the effect of each instruction on the
stack frame. Since we already have two such switch statements (in
interpret.cpp and compile.cpp), I really don't want to add a third, and
someday I hope to merge the existing two into one.

So my new plan is to add some logic to the existing instruction parser in
compile.cpp which determines precise type info on the fly as it visits
each instruction (again with the help of StackMapTable). The tricky part
is that we won't know the true frame size until we've visited all the
instructions, so any calculations and offsets based on that number will
have to be delayed. Fortunately, I don't think that will be a big deal
given the two-pass compilation process which is currently in place.

The other tricky part about this whole thing is that the Java VM Spec
won't let us throw NoClassDefFoundErrors just because we can't resolve a
class at compile time -- we have to wait until the code that uses that
class is actually run, try one last time to load the class, and only throw
the error if we still can't do it. So although we'll always know the name
of the type of each local, we won't know if it's a value type or not if we
can't load it during compilation. In that case, we'll have to fall back
to treating them as reference types. That gets really ugly later, since
functions compiled after the types become loadable will know which types
are value types and therefore assume that its parameters will be unboxed
and the return values of functions they call will also be unboxed. In
other words, we'll have methods with multiple calling conventions
depending on when they were compiled which can't call each other without
causing crashes or worse.

The easy way to get around that is to mark such types as always ineligable
for value type optimization; i.e. if a classloader can't provide a class
early enough for the compiler to decide whether it's a value type or not,
then it will be considered a reference type no matter what from then on,
even if it would otherwise qualify as a value type. This is enough of a
corner case that I think a conservative approach is reasonable. 99.99% of
the time, if a classloader can't find a class the first time the VM asks
for it, it will never find it.

Okay, this email is already way too long. I just wanted to let you know
I'm still thinking about this stuff and hope to start writing some code
soon.

BTW, I started working on invokedynamic support this past weekend. I was
hoping it would be pretty quick and easy, but it looks like the interface
with the VM for doing MethodHandle transformations is pretty complicated
and not well-documented, so I'll need to do more research to understand it
all.

Simon Ochsenreither

unread,
Mar 19, 2013, 8:14:57 PM3/19/13
to av...@googlegroups.com, Joel Dice
Hi Joel,

thanks a lot for the update!


The easy way to get around that is to mark such types as always ineligable
for value type optimization; i.e. if a classloader can't provide a class
early enough for the compiler to decide whether it's a value type or not,
then it will be considered a reference type no matter what from then on,
even if it would otherwise qualify as a value type.  This is enough of a
corner case that I think a conservative approach is reasonable.  99.99% of
the time, if a classloader can't find a class the first time the VM asks
for it, it will never find it.

Yes, I think that's perfectly reasonable. I'm just talking about it on the Scala mailing list, because it seems there is no easy way to determine whether some class is a value class, for no good reason.


Okay, this email is already way too long.  I just wanted to let you know
I'm still thinking about this stuff and hope to start writing some code
soon.

I'm also working on the arrayLoad/arrayStore code as we speak.
I was stuck a long time because I didn't saw the
assert(t, classArrayElementSize(t, objectClass(t, array)) == BytesPerWord);
in objectArrayBody and was wondering why it didn't work. :-)

I'm still struggling with not having a nice readable stacktrace (and some missing frames in the debugger), but I'm starting to understand the language a lot better now.

 
BTW, I started working on invokedynamic support this past weekend.  I was
hoping it would be pretty quick and easy, but it looks like the interface
with the VM for doing MethodHandle transformations is pretty complicated
and not well-documented, so I'll need to do more research to understand it
all.

Absolutely. In my opinion, Oracle pulled an OOXML with invokedynamic. Building some "invokelambda" would have been much more sensible.


Thanks and bye,

Simon

Joel Dice

unread,
Mar 19, 2013, 9:27:31 PM3/19/13
to Simon Ochsenreither, av...@googlegroups.com, Joel Dice
On Tue, 19 Mar 2013, Simon Ochsenreither wrote:

> I'm still struggling with not having a nice readable stacktrace (and some
> missing frames in the debugger), but I'm starting to understand the language
> a lot better now.

Are you using GDB? Make sure you're building Avian with mode=debug so you
don't have to deal with inlined frames and optimized-out variables. Also,
you can get a Java-level stack trace from within GDB by calling
vmPrintTrace(t), where "t" is a vm::Thread*. For example:

Breakpoint 4, (anonymous namespace)::local::JVM_ArrayCopy (t=0x60b3d8,
src=0x7fffffffca80, srcOffset=0, dst=0x7fffffffca90, dstOffset=0,
length=16) at src/classpath-openjdk.cpp:3318
3318 static_cast<uintptr_t>(length) };
(gdb) bt
#0 (anonymous namespace)::local::JVM_ArrayCopy (t=0x60b3d8,
src=0x7fffffffca80, srcOffset=0, dst=0x7fffffffca90, dstOffset=0,
length=16) at src/classpath-openjdk.cpp:3318
#1 0x00002aaaaad8fd48 in vmNativeCall () from
/home/dicej/p/avian/build/linux-x86_64-debug-openjdk/libjvm.so
#2 0x00002aaaaacddbcb in vm::dynamicCall (function=0x2aaaaad5c2ac,
arguments=0x7fffffffc848, argumentTypes=0x7fffffffc838
"\a\a\a\003\a\003\003", argumentCount=7, returnType=0) at
src/avian/x86.h:197
#3 0x00002aaaaacdd039 in (anonymous namespace)::MySystem::call
(this=0x603010, function=0x2aaaaad5c2ac, arguments=0x7fffffffc848,
types=0x7fffffffc838 "\a\a\a\003\a\003\003", count=7, size=56,
returnType=0) at src/vm/system/posix.cpp:782
#4 0x00002aaaaad3bfef in (anonymous namespace)::local::invokeNativeSlow
(t=0x60b3d8, method=0x6fa588, function=0x2aaaaad5c2ac) at
src/compile.cpp:8076
#5 0x00002aaaaad3c2b8 in (anonymous namespace)::local::invokeNative2
(t=0x60b3d8, method=0x6fa588) at src/compile.cpp:8148
#6 0x00002aaaaad3c447 in (anonymous namespace)::local::invokeNative
(t=0x60b3d8) at src/compile.cpp:8180
#7 0x00000000400000a3 in ?? ()
#8 0x0000000000779750 in ?? ()
#9 0x0000000040005612 in ?? ()
#10 0x0000000000779720 in ?? ()
#11 0x0000000000000000 in ?? ()
(gdb) call vmPrintTrace(t)
debug trace for thread 0x60b3d8
at java/lang/System.arraycopy (native)
at java/lang/String.getChars (line 863)
at java/lang/String.toCharArray (line 2802)
at sun/security/util/Debug.<clinit> (line 274)
at java/security/ProtectionDomain.<clinit> (line 118)
at java/lang/ClassLoader.<init> (line 253)
at java/lang/ClassLoader.<init> (line 315)

Joshua Warner

unread,
Mar 19, 2013, 9:28:16 PM3/19/13
to av...@googlegroups.com
Simon,

You can also get GDB-level stack traces if you compile in bootimage mode (obviously, in debug mode as well, so symbols aren't stripped), and with the frame pointer disabled.  In bootimage mode, all of the pre-compiled java methods are given symbols in the generated object.  You'll also be able to set breakpoints in jitted java code from GDB, etc.

In otherwords, use this command:

make mode=debug use-frame-pointer=true bootimage=true

Hope that helps,
Joshua


Simon Ochsenreither

unread,
Mar 19, 2013, 9:34:25 PM3/19/13
to av...@googlegroups.com
Yes, I'm building with mode=debug already, I'll try vmPrintTrace and use-frame-pointer=true bootimage=true.

Thanks a lot!

Simon Ochsenreither

unread,
Mar 19, 2013, 9:52:21 PM3/19/13
to av...@googlegroups.com
I tried use-frame-pointer=true bootimage=true, I'm using QtCreator's debug interface (which I think uses gdb underneath), but now I don't have any stackframe information at all. It's probably a QtCreator issue.

That's what I'm currently playing with, probably pretty broken: https://github.com/soc/avian/compare/value-array

Simon Ochsenreither

unread,
Mar 21, 2013, 11:08:33 PM3/21/13
to av...@googlegroups.com, Joel Dice
I was stuck a long time because I didn't saw the
assert(t, classArrayElementSize(t, objectClass(t, array)) == BytesPerWord);
in objectArrayBody and was wondering why it didn't work. :-)

Ok, correction. It fails in type-declarations.cpp's arrayBody:

inline object&
arrayBody(Thread* t UNUSED, object o, unsigned i) {
  assert(t, t->m->unsafe or instanceOf(t, arrayBodyUnsafe(t, t->m->types, Machine::ArrayType), o));
  assert(t, i < arrayLength(t, o));  // <========
  return reinterpret_cast<object&>(reinterpret_cast<uint8_t*>(o)[ArrayBody + (i * 8)]);
}


I think it's legit and my arrayStore/arrayLoad changes are at fault, considering that the code I'm executed didn't even trigger the value class codepaths.
 
I'm still struggling with not having a nice readable stacktrace (and some missing frames in the debugger), but I'm starting to understand the language a lot better now.

After Joshua's helpful suggestion, I finally got more helpful stacktraces in QtCreator with use-frame-pointer and without bootimage.

Simon Ochsenreither

unread,
Sep 1, 2013, 8:25:01 PM9/1/13
to av...@googlegroups.com
I brought my earlier hacks back to life. This is the current status: https://github.com/soc/avian/compare/topic;array-of-value-types

It doesn't work yet, but I think I have now a better impression of what's still missing:
  • makeArray/initArray/... probably need some fixes to take value types into account when allocating the memory (or is this better done somewhere else?)
  • Verify that the arrayLoad/arrayStore thunks work correctly
  • Look into what needs to be done for anewarray/multiarray instructions
  • Set the right object mask, so that the GC doesn't walk over the array elements ... not sure how the appropriate one would look like.

Any hints/suggestions? Anything I'm missing?

Joshua Warner

unread,
Sep 3, 2013, 8:02:41 PM9/3/13
to av...@googlegroups.com
Any hints/suggestions? Anything I'm missing?

* Just a note - makeArray, initArray, etc. are actually generated from types.def.  You'll either have to modify that, modify the generator code (src/tools/type-generator), or find another approach.
* I'm not sure if the GC understands arrays with elements that are more than a word.  This might require modifying some interfaces. (not that that should discourage you)

-Joshua

Simon Ochsenreither

unread,
Sep 4, 2013, 10:50:00 AM9/4/13
to av...@googlegroups.com

* Just a note - makeArray, initArray, etc. are actually generated from types.def.  You'll either have to modify that, modify the generator code (src/tools/type-generator), or find another approach.

Yes, that's why I'm a bit reluctant. Adding a special case to the code generator doesn't feel like the right approach.
 
* I'm not sure if the GC understands arrays with elements that are more than a word.  This might require modifying some interfaces. (not that that should discourage you)

I think if there is a way to tell the GC that the array doesn't contain any references (like arrays of primitive types), that would be enough for now (let's ignore value types with reference members for now ...).

Anyway ... I'm still trying to figure out why the code seg faults ... it looks like as if the instructions generated in compile.cpp are not entirely correct in my code.

Joshua Warner

unread,
Sep 4, 2013, 11:45:16 AM9/4/13
to av...@googlegroups.com


I think if there is a way to tell the GC that the array doesn't contain any references (like arrays of primitive types), that would be enough for now (let's ignore value types with reference members for now ...).

That should be easier.  The approach I would take would be lying to the GC that the array is an array of ints (or other non-pointer type), and that it's length is actually it's length in ints, rather than in elements.
 

Anyway ... I'm still trying to figure out why the code seg faults ... it looks like as if the instructions generated in compile.cpp are not entirely correct in my code.

If you can show some examples of the segfault in GDB, I may be able to give some pointers.

-Joshua 
Reply all
Reply to author
Forward
0 new messages