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

Implement array.fill, array.init_data, and array.init_elem #5637

Merged
merged 4 commits into from
Apr 6, 2023
Merged

Conversation

tlively
Copy link
Member

@tlively tlively commented Apr 6, 2023

These complement array.copy, which we already supported, as an initial complete
set of bulk array operations. Replace the WIP spec tests with the upstream spec
tests, lightly edited for compatibility with Binaryen.

@tlively tlively requested a review from kripken April 6, 2023 18:42
@tlively
Copy link
Member Author

tlively commented Apr 6, 2023

Current dependencies on/for this PR:

This comment was auto-generated by Graphite.

These complement array.copy, which we already supported, as an initial complete
set of bulk array operations. Replace the WIP spec tests with the upstream spec
tests, lightly edited for compatibility with Binaryen.
Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

lgtm with comments addressed + fuzzing

@@ -988,7 +988,8 @@ struct InfoCollector
auto* set = builder.makeArraySet(curr->destRef, curr->destIndex, get);
visitArraySet(set);
}

void visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("unimplemented"); }
void visitArrayInit(ArrayInit* curr) { WASM_UNREACHABLE("uimplemented"); }
Copy link
Member

@kripken kripken Apr 6, 2023

Choose a reason for hiding this comment

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

The fuzzer will crash on these (any testcase with the instructions + --gufa).

I think their implementation can be similar to ArraySet (edit: or ArrayCopy)

Copy link
Member Author

Choose a reason for hiding this comment

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

What should I do to model reading from the data or element segment in ArrayInit?

Copy link
Member

Choose a reason for hiding this comment

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

Looks like TableGet just models the value as a root (an unknown value that could be anything). I think we should do the same here. But that value would need to be written into the array's storage instead of just declaring this expression to be a root.

If it doesn't look obvious how to do that I can look into it. I'm not sure if it will be trivial or not.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. I'm not sure how to hook up the root value with the synthetic write, so I'll leave that to you and look forward to seeing what's involved.

Copy link
Member Author

Choose a reason for hiding this comment

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

My fuzzing did find this after 7591 iterations, so at least it's rare.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I was hoping to fix this before it lands, as that frequency still means it'll be found many times a day. But no worries, I'll look into this right now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, sorry about that.

Copy link
Member

Choose a reason for hiding this comment

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

No problem, I think I found the solution already, PR is up (#5638)

if (offsetVal + readSize > seg->data.size()) {
trap("out of bounds segment access in array.init_data");
}
if (offsetVal + sizeVal > 0 && droppedSegments.count(curr->segment)) {
Copy link
Member

Choose a reason for hiding this comment

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

Should offsetVal + sizeVal be just sizeVal? If the size is 0, it seems like the offset should not matter.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, dropped segments semantically have their length truncated to zero, so if offset and size are both zero, there's no trap, but if either is nonzero there will be a trap. This could perhaps be more clearly expressed, but this code is copied from the implementation of MemoryInit.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Maybe my confusion is about the spec then. It seems odd to trap on trying to load 0 items from offset 17 but not to trap on trying to load 0 items from offset 0 (since both do no actual reads).

Copy link
Member Author

Choose a reason for hiding this comment

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

It's about keeping the bounds checks uniform in the spec. For the spec, the only rule is "trap if offset + length is out of bounds." Since there are no special cases for zero sized accesses, you get the somewhat counterintuitive behavior of trapping on out-of-bounds but not in-bounds or at-bounds zero-sized accesses. Combine that with the bounds changing for dropped segments, and you get some pretty weird behavior for anything that doesn't model things exactly like the spec.

src/wasm-interpreter.h Show resolved Hide resolved
local.get 2
local.get 3
array.fill $a2
)
Copy link
Member

Choose a reason for hiding this comment

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

just fill for now and not init*?

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't parse element segment references yet, so I left ArrayInit as future work.

@tlively tlively enabled auto-merge (squash) April 6, 2023 20:09
@tlively tlively merged commit 4f91c6a into main Apr 6, 2023
@tlively tlively deleted the bulk-array branch April 6, 2023 20:35
@kripken
Copy link
Member

kripken commented Apr 6, 2023

Fuzzer found a bug:

(module
 (type $[mut:i32] (array (mut i32)))
 (type $ref|[mut:i32]|_i32_i32_i32_=>_none (func (param (ref $[mut:i32]) i32 i32 i32)))
 (global $global$0 (mut i32) (i32.const -69))
 (global $global$1 (mut i32) (i32.const 100))
 (memory $0 (shared 16 17))
 (data $0 "")
 (data $1 (i32.const 0) "")
 (data $2 (i32.const 14) "")
 (data $3 (i32.const 30) "")
 (func $0 (param $0 (ref $[mut:i32])) (param $1 i32) (param $2 i32) (param $3 i32)
  (array.init_data $[mut:i32] $0
   (local.get $0)
   (i32.const 0)
   (i32.const 0)
   (i32.const 1)
  )
 )
)
$ bin/wasm-opt w.wasm -all --memory-packing
[wasm-validator error in function 0] unexpected false: array.init_data segment must exist, on 
(array.init_data $[mut:i32] $0
 (local.get $0)
 (i32.const 0)
 (i32.const 0)
 (i32.const 1)
)
Fatal: error after opts

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.

None yet

2 participants