Some issues with arrays and memory
RussCoder opened this issue · comments
Hi there!
I have tried out Walt compiler, and I have some questions.
The code, that I have written and run via Node.js
const compileWalt = require('walt-compiler').default;
const buffer = compileWalt(`
import {memory: Memory} from 'js';
let offset: i32 = 0;
//let array: i32[] = 0;
export function init(off: i32) {
offset = off;
}
export function main(): i32 {
let sum: i32 = 0;
let i: i32;
let array: i32[];
array = offset;
for(i = 0; i < 10; i+=1) {
sum += array[i];
}
return sum;
}
`);
require('fs').writeFileSync('bin.wasm', new Uint8Array(buffer));
const mem = new WebAssembly.Memory({ initial: 10 });
const importObj = {
js: {
memory: mem
}
};
WebAssembly.instantiate(buffer, importObj).then(({ instance }) => {
const offset = 100;
const array = new Uint32Array(mem.buffer, offset, 10);
for(var i = 0; i < 10; i++) {
array[i] = 33;
}
instance.exports.init(offset);
console.log(`Main result: ${instance.exports.main()}`);
});
All the code above works as expected, but there are some issues.
-
First of all, I found out that it's impossible to declare arrays as global vars. So as you may see I declare an
let array: i32[]
inside the functionmain
, and the same global declaration is commented out, since with the global declaration and without the local one, there is an error thrown by the compiler. Is it a bug? Or not implemented yet? -
Secondly, as I see the
import {memory: Memory} from 'js';
is compiled to(import "js" "memory" (memory $js.memory 1))
. Ok, but what if I want to import a memory object with 10 pages? I'm not sure, maybe it will work fine with such import even if I pass a memory object with 10 pages (as I actually do in the code above) and if I work with the memory beyond 64*1024 offset, but I would like you to clarify this moment. -
Thirdly, I found out that if I declare a counter inside the
for
loop, to witfor(let i: i32 = 0; ...
, an error occurs. Is it a bug or it's not implemented yet? The same for++
operator.
Also, I have seen inside the binary .wasm file and I saw that you count the address of an array element via something like that:
get_local $l2 // array initial offset
get_local $l1 // index
i32.const 4 // since it's i32
i32.mul // index*4
i32.add // arrayOffset + index*4
It's sane, but I have a suggestion. You can substitute the multiplication with the bitwise shift, which probably will be faster, that is to say substitute arrayOffset + index*4
with arrayOffset + index << 2
. What would you say?
Hey there!
First of all, thank you for reporting all of this in an issue. I rely on others using the tool and reporting inconsistencies and usage issues. Let me address the questions one by one.
Global array
This is a bug, I'll open up an issue to fix this. Should be fine to have a global array, it's simply and i32 offset in the final binary. Probably an inconsistency in the parser.
Memory imports
To allow for a custom memory import type I need to adjust the syntax for Memory definition. It'll probably be a generic type, with syntax similar to Memory<{ initial: #, max: # }>
. This should work for both imports and declarations. Currently, it's only possible to declare a custom memory region with an initializer syntax const memory : Memory = { initial: #, max: # };
, as you found this isn't implemented for imports and an initializer in an import statement would not really work out well/be weird.
Hasn't been a priority since there is a number of ways to work around it and a { initial: 1 }
is permissive enough to work with just about any use-case, for now.
For loop declarations
Declaring variables inside a loop statement is not supported currently. Either in the loop declaration or the loop body. Not that it wouldn't be possible, it hasn't been a priority. Block level variable declarations are not natively supported by wasm(as far as I can tell), it resembles older C in this aspect. Adding such functionality would be pure syntax sugar and would take some creative aliasing of variables to emulate pseudo-blocked scoped variables. I do agree though that it's surprising for sure. I'll open up an issue to track this, but It'll be a bit of time before it's fully implemented. Unless there is a large demand for it of course.
Increment/Decrement
Honestly, both --
and ++
are more trouble than they are worth to implement in the parser. We do support +=
& -=
however. The challenge is that adding a ++
operator, for example, means also supporting prefix and postfix notations which is a giant pain to implement.
Array offsets in binary.
Good point! I need to update some parts of the binary emitted for arrays already so I could make this change. Should be trivial. Thanks for the suggestion!
Ok, thank you for the answers.
Also, I have found out that the initialization for the global variables are required, so the code
let counter: i32;
export function count(): i32 {
counter = 0;
return counter;
}
causes the following error:
const { value } = node.params[0];
^
TypeError: Cannot destructure property `value` of 'undefined' or 'null'.
A bug? Or web assembly requires the initialization?
As for the declaration of variables inside loops, I think it's not required to make an imitation of block scoped variables, you may just make all variables function scoped, as it is in JS when you use var
. It will be enough, I think, since in web assembly all variables are function scoped. But yeas, it's not an important feature for now.
Also, as I see, a semicolon at the end of each line is required (while in JS it's not). It's better to point it out in the documentation.
That's a bug. Immutable(const
) globals require an initializer in WASM but not mutable ones(let
). I'll patch this shortly.
Thanks again, all issues were addressed with v0.4.0