-
-
Notifications
You must be signed in to change notification settings - Fork 827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AVM2 interpreter #404
AVM2 interpreter #404
Conversation
6079a48
to
b71bdf6
Compare
664bf4b
to
ce6f282
Compare
I am releasing the draft lock on this PR and requesting review and merge. Hopefully it passes CircleCI. I'm deliberately walling off a whole bunch of future AVM2 features for future PRs, as mentioned in the PR description. This PR already has 143 commits and makes There are approximately 31 tests, a significant number of which challenged my notions about how this interpreter should work. I think I at least have the object model locked, though I wasn't able to write a test that uses slots. In fact, |
Is there any ETA on this pull request? |
Out of my own curositiy i tried to merge this into master (current as of today) and it seems this branch is way behind. Just based off a cursory observation (im no rust master) we might need to create an avm super type that encases both avm1 and avm2 and cast based upon the information extracted from the swf. |
@jfmherokiller There's no need to do that, I just need to resolve the merge conflicts. AVM1 and AVM2 are supposed to be entirely separate; the conflicts are in other parts of the player where I added AVM2 stuff to. |
I should also point out that this does not contain the capability to play AS3 games. It can't even add numbers yet - it's purely the AVM2 interpreter loop, object model, and just enough builtins to get tests to run. |
the question then i have is how will the player distinguish between an avm1 and avm2 swf? |
ce6f282
to
8950903
Compare
As you can tell by that massive list of new commits I have rebased the AVM2 PR and resolved the merge conflicts. Given @Dinnerbone's recent work with init actions I'm wondering if ABC files should also execute immediately rather than resolving on the action queue. |
I will say I did succed in adding a kind of standin for FileAttributes (I just started using rust today so code will be very basic) in movie_clip.rs. |
So, that's great, but kinda out of scope for this PR. I specifically excluded anything to do with either Whenever this actually gets merged, and we start work on an "AVM2 DisplayObjects" PR, then we'd want to start parsing |
43152b2
to
3eb14bb
Compare
3eb14bb
to
c891b90
Compare
30f52a6
to
eeff5f1
Compare
Turns out this thing had been sitting unmergeable for a good week or two 'cause I didn't notice the fact that beta/nightly clippy tests are now being run. Also, given that #675 landed I'm now considering adjusting the AVM2 |
45bbf03
to
c7cd509
Compare
This is primarily used for interfaces, in case you can't guess by the previous commit.
This test will fail if the AVM2 implementation does not support bodyless methods or bare classes properly.
… stick them in the prototype for later use.
ef5cba2
to
508fcd6
Compare
…the AVM2 runtime.
…hat pulls strings from the ABC file now uses `AvmString`s.
…enting us from making multiple copies of the same string. For good measure, most of the other methods in `value` for retrieving pool primitives now also use `TranslationUnit` instead of `AbcFile`. This is the result of a handful of cascading changes throughout the project, and itself caused a few more.
Holding a `Ref` on a garbage-collected object inherently extends any borrow locks on that object. Since ABC files are references already, taking a `Ref` to them only helps to skip the refcount update. This is less useful than expected: in most situations, using `abc_ref` causes double-borrow panics. The few methods that can use it are going to be fragile in the face of future refactors, so I'm nipping the problem in the bud now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your work on this! I think we should have avm2::AvmString
and avm2::PropertyMap
(at least aliased or copy+pasted from the Avm1 versions for now), as these will almost certainly have differences than the avm1 versions, and we should at least have types in place to make it easier for us to modify these later.
There are also some unimportant naming questions that I feel strongly don't feel strongly about (how closely should we try to mirror the avmplus naming conventions?)
Otherwise I think this is ready to merge, and I will merge this whenever you're ready.
pub enum Value<'gc> { | ||
Undefined, | ||
Null, | ||
Bool(bool), | ||
Number(f64), | ||
String(AvmString<'gc>), | ||
Namespace(Namespace<'gc>), | ||
Object(Object<'gc>), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bikeshed: Should we call this Atom
(since this is what avmplus calls it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no strong opinion either way.
I did use Value
specifically because it matches the AVM1 Value
. I doubt we're going to have people used to hacking on avmplus wondering where Atom
is here, as much as we're going to have people used to hacking on Ruffle's AVM1 interpreter wondering where Value
is.
|
||
#[derive(Copy, Clone, Debug, Collect)] | ||
#[collect(no_drop)] | ||
pub struct TranslationUnit<'gc>(GcCell<'gc, TranslationUnitData<'gc>>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TranslationUnit
feels like a weird name to me. avmplus calls this PoolObject
which I don't think is much better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the name came about while I was switching the AVM2 interpreter away from pulling ABC traits and data directly to storing them in other structures that could be dynamically created (which we need for avm2-nativeclasses
.) I can't call it AbcFile
because that's already in use by swf
, so TranslationUnit
(a term used by C/C++ compiler specifications to refer to a single source file) seemed like a good option, since, as far as I could tell I was getting one DoABC
per source file in my tests...
If you have a better option I'll definitely change the name.
core/src/avm2/script.rs
Outdated
if let Some(method) = write.methods.get(&method_index) { | ||
return Ok(method.clone()); | ||
} | ||
|
||
drop(write); | ||
|
||
let method: Result<Gc<'gc, BytecodeMethod<'gc>>, Error> = | ||
BytecodeMethod::from_method_index(self, Index::new(method_index), mc) | ||
.ok_or_else(|| "Method index does not exist".into()); | ||
let method: Method<'gc> = method?.into(); | ||
|
||
self.0 | ||
.write(mc) | ||
.methods | ||
.insert(method_index, method.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like we should be able to use the entry API with or_insert_with
, although I guess the issue is that from_method_index
does some txunit.read()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timing of when GcCell
locks are held and released is important here (which is why I used the "wrapped GcCell
pattern" here). Due to the way traits work, all four of these methods can recurse into one another on the same stack at any time, so I'm being highly conservative with how long locks are actually being held.
BytecodeMethod
never needs a write lock on the translation unit, and it only ever reads it to get an ABC. In fact, it reads it three times too many because this used to be abc_ref
(which caused other double-borrow problems). I'm going to change this particular constructor into taking both a TranslationUnit
and an AbcFile
, so it doesn't need to hold either read or write locks and we can use entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out that won't work, but not for the reason you'd think: or_insert_with
is infallible. We need to generate a verification error if the method index doesn't exist, which we can't return inside of the or_insert_with
callable. Ergo, I'll be leaving this as-is.
(This seems to be a problem with a lot of Rust methods that take closures...)
…rom_abc_multiname`.
…mplify a struct declaration)
Ok, let's get this in! Thanks for the patience while this PR sat around for a long time. I'm excited to make progress here. (BTW, I meant to say that I don't feel strongly about the name stuff in my earlier post, sorry if that came off the wrong way). |
This implements an AVM2 interpreter. AVM2 is the virtual machine that ActionScript 3.0 programs compile into; it's completely separate from AVM1 and almost became part of the web platform itself in the form of ECMAScript 4 (no published standard exists) and Adobe Tamarin (FOSS but MPL trilicensed instead of Apache).
Notable features include packages, namespaces, access control, E4X, type assertions, a very explicit scope stack, no primitive values (if the one ES4 overview PDF I could find is accurate), and more. Coming from modern JavaScript, or even AS2; this feels like a completely different programming language and it took a while to understand very basic things about the VM. (This is probably why non-Flash web developers outright rejected ES4.) Nevertheless, it's part of Flash, so we need to implement it as it gates a whole bunch of interesting content.
In this PR
Avm2
interpreter stateswf
istype
instructionsjump
iftrue
iffalse
ifstricteq
ifstrictne
Object
,Function
builtinsClass
classAVM2- see future workStageObject
- see future workSymbolClass
tag- see future workFileAttributes
tag & VM isolationFunction.prototype.apply
without workingArray
s, which is outside the scope of this PRcoerce
.PR Roadmap
The following are features out of scope of this PR. Due to the fact that this PR's scope has more or less been completed, I'm working on them in separate branches. If they're relatively "complete" before this PR is merged in, they may be merged in.
avm2-primitives
on my user's repo.avm2-nativeclasses
on my user's repo. Covers defining ES4 classes in Rust.StageObject
-TObject
implementation that holds stage objects, plus native implementations of the builtin display object classes.SymbolClass
tag - required for executing frame scriptsFileAttributes
tag & VM isolation - Already implemented in core: Print swf version on startup, and warn when we run into avm2 #661, but for the purpose of warning users about our lack of AVM2 support. We will need to change this to configuring what VM a particular movie's display objects belong to.