Skip to content
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

%TypedArray%.prototype.map #349

Closed
wants to merge 0 commits into from
Closed

%TypedArray%.prototype.map #349

wants to merge 0 commits into from

Conversation

jtenner
Copy link
Contributor

@jtenner jtenner commented Nov 27, 2018

So far this is the best I have for implementing map.

This is my first pull request and I hope to contribute much more if this turns out well.

var length = this.length;
var byteLength = this.byteLength;
var byteOffset = this.byteOffset;
var newbuff = allocateUnsafe(byteLength);
Copy link
Member

@MaxGraey MaxGraey Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why just not create var result = new TypedArray<T,V>(this.length) and result.byteOffset = this.byteOffset?

Copy link
Contributor Author

@jtenner jtenner Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in the definition of the class

export abstract class TypedArray<T,V>

I can't create an abstract class with new, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, now I see

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypedArray is abstract because you'd lose the subarray implementation on the concrete class. Though I agree that this should be easier, somehow, because as it stands it is missing GC registration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait! I have an idea.

Copy link
Member

@dcodeIO dcodeIO Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a built-in helper instantiate<T>(...constructorArgs) (here: T is this) that is able to instantiate anything, given the class as a type argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcodeIO I can implement the method on each subclass using the new keyword. Let me submit changes.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Okay so this totally works. It feels a bit hacky, but the code is very clear and does the job. Is there a way to make this method more generic?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

How can I copy byteOffset if the field is readonly?

image

@MaxGraey
Copy link
Member

store<i32>(changetype<usize>(result), this.byteOffset, offsetof<this>("byteOffset"));

@MaxGraey
Copy link
Member

MaxGraey commented Nov 28, 2018

@dcodeIO for better design I think make sense use getter for byteOffset instead readonly field. wdyt?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

And for testing, how should I verify the map method works? Is there a place to modify the test suite?

@MaxGraey
Copy link
Member

https://github.com/AssemblyScript/assemblyscript/blob/master/tests/compiler/std/typedarray.ts

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

One last question, I'm looking through the spec and can't find the reason why we must copy the byteOffset to a new TypedArray. Is there a reason I'm missing?

@MaxGraey
Copy link
Member

MaxGraey commented Nov 28, 2018

see how implemented __get and __set. Btw recommend use __unchecked_get/__unchecked_set interanally

@dcodeIO
Copy link
Member

dcodeIO commented Nov 28, 2018

Not sure about setting `byteOffset´, as you are dealing with an entirely new array with a new backing buffer. Modifying byteOffset there might actually lead to out of bounds loads and stores.

@MaxGraey
Copy link
Member

@dcodeIO yeah, you are right

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Yeah this pull request isn't gonna be easy.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Looks like something is broken. Can someone help me identify the problem?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Now it looks like I have a problem with the function callbacks.

 Error: Compile error
  ERROR AS200: Conversion from type '(val: i8, index: i32) => i8' to '(value: i8, index: i32, array: Int8Array) => i8' requires an explicit cast.

   assert(isInt8ArrayEqual(testMapArray1.map(squarei8), [1, 4, 9]));
                                             ~~~~~~~~
   in std/typedarray.ts(370,42)

I went to check out how Array.prototype.map performs the map operation and I see the callback parameter looks like this.

  map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {

Am I missing something?

@MaxGraey
Copy link
Member

Yes, currently signature of callback should fully declare. Change:

function squarei8(val: i8, index: i32): i8 { ... }

to

function squarei8(val: i8, index: i32, array: i8[]): i8 { ... }

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Okay. It looks like I've gotten it to the point where the tests look like they are supposed to look. There's still some issues though.

var length = this.length;
var result = new Int8Array(this.length);
for (let i = 0; i < length; ++i) {
result.__unchecked_set(i, callbackfn(this.__unchecked_get(i), i, this));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be written as unchecked(result[i] = callbackfn(this.__unchecked_get(i), i, this)), which will ensure that it still works whenever indexed access becomes more optimized or changes otherwise.

Copy link
Contributor Author

@jtenner jtenner Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks dcode, but when I try to use it, it errors about the type because it doesn't have an index signature.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a TS error that results from the .d.ts not having it, but should compile with AS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried compiling with AS and it fails when trying to run the tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's strange. Really shouldn't use __unchecked_set here.

Copy link
Member

@dcodeIO dcodeIO Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, the low-level way to write this is:

import { HEADER_SIZE } from "./internal/arraybuffer";
  map(callbackfn: (value: i8, index: i32, array: Int8Array) => i8): Int8Array {
    var length = this.length;
    var result = new Int8Array(length);
    var src = changetype<usize>(this.buffer) + HEADER_SIZE + this.byteOffset;
    var dst = changetype<usize>(result.buffer) + HEADER_SIZE; // byteOffset is 0 for a new array
    for (let i = 0; i < length; ++i) {
      let off = <usize>i << alignof<i8>();
      store<i8>(dst + off, callbackfn(load<i8>(src + off), i, this));
      // can actually skip alignof<T> for i8/u8 because it's 0 anyway, but added it for clarity
    }
    return result;
  }

@MaxGraey please correct me if I made a mistake :)

@MaxGraey
Copy link
Member

Also you should run npm run test:compiler -- --create for update test snapshots

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

I somehow broke the bool test.

@MaxGraey
Copy link
Member

MaxGraey commented Nov 28, 2018

It seems you need use npm run build first because build dist files outdated currently. So run:

npm run build

and

npm run test:compiler -- --create

later.
And don't forget exclude updated dist folder from commit.
@dcodeIO could you update dists?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

@MaxGraey Okay. What do you mean by disclude dist folder? Should I delete it and commit with empty dist folder?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

@MaxGraey I'm confused by what this means. Should I leave the dist files as they are, from when I cloned the repo?

When I ran the build to do the testing it updated the files and I can't disclude them.

Should I use this?

git update-index --assume-unchanged dist/*

@dcodeIO
Copy link
Member

dcodeIO commented Nov 28, 2018

When working on the compiler, make sure to run npm run clean to remove the dist files before running the tests. That makes sure that the sources are being run. Then, when committing, regardless of whether you decided to run npm run build for yourself, make sure to not commit any changes to dist/*. CI checks that, hence this PR fails.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Okay. So all the tests pass. Pushing now. Please check my work, because it modified the dist folder.

@dcodeIO
Copy link
Member

dcodeIO commented Nov 28, 2018

Modifying the dist folder will make CI fail automatically, unfortunately. Let me suggest that you git reset --soft HASH to the last commit from the master repo right before your first commit, and make a new condensed one from your changes with changes to the dist files excluded followed by an git push -f.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

@dcodeIO sorry about this. When I was asked to run npm build, it caused the dist folder to be overwritten. Is it possible to restore the files back to their original contents by just copy/pasting them instead? I'm afraid of git magic right now and I don't want to lose all my work :)

Edit: It does look like the files are diffed using git, so I restored their contents manually.

@dcodeIO
Copy link
Member

dcodeIO commented Nov 28, 2018

What you'd usually do in such a scenario is, for example

$> git reset --soft 7596d732849c26d9ee80de2d295647c235fd5d4b

that keeps your local changes but resets the tree to the state before you made your first commit, followed by a

$> git add .
$> git reset dist/*

to add your changes back, except those to the dist files. Then creating a new first commit on your branch through

$> git commit -m "New commit message"

and force-pushing it to your branch, effectively replacing the chain of commits that was there before with your new single one

$> git push -f

Hope that helps :)

@jtenner
Copy link
Contributor Author

jtenner commented Nov 28, 2018

Yeah thanks for the tip. It all seems to work now. Should I change the documentation, add some comments? It didn't seem like I needed any since it was all very obvious what the code was doing.

}

export class Uint8ClampedArray extends TypedArray<u8,u32> {
static readonly BYTES_PER_ELEMENT: usize = sizeof<u8>();

@inline @operator("[]=")
protected __set(index: i32, value: i32): void {
super.__set(index, max(min(value, 255), 0));
super.__unchecked_set(index, max(min(value, 255), 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must have accidentally replaced this. I am sorry. Let me change it back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing this function back to super.__set causes this error to occur when testing compiler/std/typedarray.ts

Fatal: Module::addFunction: $~lib/internal/typedarray/TypedArray<u8,u32>#__set already exists

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try renaming the function itself to __setClamped as a workaround.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be you forget replace result.__set to result.__setClamped for map inside Uint8ClampedArray?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it turns out, I don't think that matters because the values will already be clamped, and changing the function names simply does not resolve the issue.

image

image

It is trying to add the __set method to the table when it already exists apparently. Could this be a compiler error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can bring this chat to slack if you want, but I'll be around to work on this again tomorrow. Thank you @MaxGraey and @dcodeIO for your amazing help.

Copy link
Member

@dcodeIO dcodeIO Nov 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is most likely a compiler bug that involves super.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where can I look to help?

std/assembly/typedarray.ts Outdated Show resolved Hide resolved
dcodeIO added a commit that referenced this pull request Nov 29, 2018
@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

The "Operation not supported" issue on unchecked(... this[i]) should be fixed by the above commit. Should now now be possible to use that syntax instead of __unchecked_get.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

image

We still get a bug when I tried to switch over to normal array access involving tslint. There is a signature problem.

Note that the cursor in the picture is where the error occurred. (I should have copied over the line numbers)

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

Seems to originate from TypedArray<T,V> being an internal class only, with no declaration whatsoever that could include the index signature. Try to disable tslint on this exact line with a trailing // tslint:disable-line.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

Actually I think the typescript compiler throws the error from within tslint. It says specifically:

ERROR: Unsafe use of expression of type 'any'. [no-unsafe-any]

This is a TypeScript error. Not a tslint one.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

image

Of course adding this signature doesn't work, and causes the build to completely freak out with syntax errors. This is gonna be good.

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

Well, while I hoped we could have avoided it, it appears that using __unchecked_get etc. is indeed the only viable option at this point :)

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

We still need to figure out what the problem with is with the unclamped array problem anyway.

Testing compiler/std/typedarray.ts

Fatal: Module::addFunction: $~lib/internal/typedarray/TypedArray<u8,u32>#__set already exists
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! assemblyscript@0.5.0 test:compiler: `node tests/compiler`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the assemblyscript@0.5.0 test:compiler script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\jtenner\AppData\Roaming\npm-cache\_logs\2018-11-29T14_51_25_586Z-debug.log
npm ERR! Test failed.  See above for more details.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! assemblyscript@0.5.0 make: `npm run clean && npm test && npm run build && npm test`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the assemblyscript@0.5.0 make script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

If I am not mistaken, perhaps there's a problem with operator overriding? I think it might have something to do with using the parent class's function name because I've renamed the functions in the Uint8UnclampedArray class.

 @inline @operator("[]=")
  protected __setClamped(index: i32, value: i32): void {
    super.__set(index, max(min(value, 255), 0));
  }

  @inline @operator("{}=")
  protected __unchecked_setClamped(index: i32, value: i32): void {
    super.__unchecked_set(index, max(min(value, 255), 0));
  }

The only place that uses the identifier __set is in the TypedArray<T, V> class.

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

I think the problem is that through calling __set manually, that function becomes compiled already, while when a normal array access like something[i] is used, that only sees that the full accessor hasn't been compiled yet and compiles both the getter and the setter, which then results in the error due to the duplicate setter. Should be easy to fix, going to take a look :)

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

@dcodeIO

Can't I just make a generic map function like this in TypedArray<T, V>?

export abstract class  TypedArray<T, V> {
  map(callbackfn: (value: T, index: i32, array: Int16Array) => T): this {
    var length = this.length;
    var result = instantiate<this>(length);
    for (let i = 0; i < length; ++i) {
      result.__unchecked_set(i, callbackfn(this.__unchecked_get(i), i, this));
    }
    return result;
  }
}

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

Problem is that this (currently differs from how TS resolves this) is TypedArray<T,V> there, which is abstract. Nonetheless, it should work because abstract isn't checked currently, but would require an overload on each concrete class changetypeing from super.map's return type to the concrete type, similar to subarray.

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

One solution could be to check whether a function makes use of the this type and compiling a suitable version of this function for each subclass, like one would expect. That'd make overloading and changetypeing unnecessary, but not sure how much work this would be.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

@dcodeIO I can implement this no problem! Also, we need to override the default functionality for Uint8ClampedArray and this will cause the other problem to still occur. In the meantime, I'll switch over the implementations to use instantiate<this>().

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

I just realized something. __unchecked_set(index: i32, value: V) accepts type V. How do I convert T to V? Also, is this the reason why it needs to recompile the __set function?

@dcodeIO
Copy link
Member

dcodeIO commented Nov 29, 2018

The idea behind the unconvenient T and V notation is that an Int8Array, for example, handles the i8 type T while that's actually a native i32 type V. Doing so allows us to omit masking/sign-extension at some occasions. For any signed small integer T, V is i32, for any unsigned small integer, V is u32. Hence, conversion works by <V>valueOfT.

Maybe renaming V to TNative would be clearer?

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

Alright. The problem with this is that V and T are generic any type parameters. They do not overlap, and thus we get a tsc error like this:

image

Edit: Actually, I have an idea.

@jtenner
Copy link
Contributor Author

jtenner commented Nov 29, 2018

// in internal/typedarray.ts
  map(callbackfn: (value: T, index: i32) => V): TypedArray<T, V> {
    var length = this.length;
    var result = instantiate<TypedArray<T, V>>(length);

    for (let i = 0; i < length; ++i) {
      result.__unchecked_set(
        i,
        callbackfn(this.__unchecked_get(i), i),
      );
    }
    return result;
  }

// for Int8Array
  map(callbackfn: (value: i8, index: i32, array: Int8Array) => i8): Int8Array {
    return <Int8Array>super.map((input: i8, idx: i32) => <i32>callbackfn(input, idx, this));
  }

@jtenner jtenner closed this Nov 30, 2018
willemneal pushed a commit to willemneal/assemblyscript that referenced this pull request Dec 26, 2018
willemneal pushed a commit to willemneal/assemblyscript that referenced this pull request Dec 26, 2018
willemneal pushed a commit to willemneal/assemblyscript that referenced this pull request Dec 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants