Skip to content

Instantly share code, notes, and snippets.

@kiyoakii
Last active June 7, 2023 15:19
Show Gist options
  • Select an option

  • Save kiyoakii/e4421d9291403528e8df2a803a6a7717 to your computer and use it in GitHub Desktop.

Select an option

Save kiyoakii/e4421d9291403528e8df2a803a6a7717 to your computer and use it in GitHub Desktop.
This Gist shows how C++ .at() looks like in compiled Wasm code.

The bounds check of .at() is probably implemented by the following wasm instructions:

  • i32.load: This loads a 32-bit value from a memory address given by an operand plus an offset.

    For example, i32.load offset=4 means load a 32-bit value from the address given by the operand plus 4 bytes. This is used to access the elements of the vectors.

  • i32.eq: This compares two 32-bit values for equality and returns 1 if they are equal or 0 if they are not.

    For example, local.get 5 local.get 4 i32.eq means compare the values of local variables 5 and 4 and return the result (push to stack). This is used to check if the index is equal to the size of the vector.

  • i32.le_u: This compares two 32-bit values for unsigned less than or equal and returns 1 if the first value is less than or equal to the second value or 0 if it is not.

    For example, i32.le_u local.get 13 local.get 12 means compare the values of local variables 13 and 12 and return the result. This is used to check if the index is within the bounds of the vector.

  • br_if: This branches to a given label if a condition is true.

    For example, br_if 0 (;@2;) means branch to label @2 if the top of the stack is true (non-zero). This is used to exit a block or a loop if a bounds check fails.

In the with_bc.wat, the bounds checking of .at() is done by comparing the vector size and the index before accessing the memory. For example, in this part of the code:

local.get 15
local.get 16
i32.eq
br_if 0 (;@1;)
local.get 2
i32.load offset=4
local.get 2
i32.load
local.tee 7
i32.eq
br_if 0 (;@1;)
local.get 7
i32.load offset=4
local.get 7
i32.load
i32.sub
i32.const 2
i32.shr_s
local.get 1
i32.gt_u
drop
end
call $abort
unreachable)

The program checks if the vector size (local.get 15) and the index (local.get 16) are equal, and if so, it branches to label @1 which is the end of the function. Otherwise, it checks if the second vector size (local.get 2) and the second index (local.get 7) are equal, and if so, it also branches to label @1. Otherwise, it checks if the second index is greater than the difference between the second vector end and start (local.get 7), and if so, it calls $abort which throws an exception. These checks ensure that the index is within the bounds of the vector before accessing the memory.

Notes about reading wasm code

First, I looked at the function signature and the parameters. The function takes three i32 parameters, which are pointers to the vectors A, B and C. So I knew that local.get 0, local.get 1 and local.get 2 are the pointers to those vectors. I noticed that local 0 is used to store the return value of the function, so I assumed it corresponds to C. local 1 is used as the first parameter of the function $std::__2::vector<int__std::__2::allocator<int>>::at_unsigned_long_, so I assumed it corresponds to A and local 2 corresponds to B.

Second, I looked at how the vectors are initialized and accessed. The vectors are stored as arrays of i32 values, with the first element being the pointer to the data, the second element being the pointer to the end of the data, and the third element being the pointer to the capacity. So I knew that i32.load and i32.store are used to read and write the vector elements, and i32.load offset=4 and i32.store offset=4 are used to read and write the vector end pointers.

Third, I looked at how the loop variables are updated and compared. The loop variables are incremented by one in each iteration, and compared with the vector sizes or indices. So I knew that local.get 13, local.get 7 and local.get 2 are the loop variables for i, j, k respectively. I also knew that local.get 12, local.get 15, local.get 17 and local.get 16 are the vector sizes or indices for A.size(), C.size(), B.size() and C.at(i).size() respectively.

Fourth, I looked at how the matrix elements are computed and stored. The matrix elements are multiplied by each other and added to the previous value. I noticed that local 15 and local 16 are used to store the results of calling $std::__2::vector<int__std::__2::allocator<int>>::at_unsigned_long_ on A and B respectively, so I assumed they correspond to A.at(i).at(k) and B.at(k).at(j) respectively.

vector<vector<int>> ijkalgorithm(vector<vector<int>> A,
vector<vector<int>> B)
{
int n = A.size();
// initialise C with 0s
vector<int> tmp(n, 0);
vector<vector<int>> C(n, tmp);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
C.at(i).at(j) += A.at(i).at(k) * B.at(k).at(j);
}
}
}
return C;
}
(func $ijkalgorithm_std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>__std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>_
(type 3) (param i32 i32 i32)
(local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
global.get $__stack_pointer
i32.const 16
i32.sub
local.tee 3
global.set $__stack_pointer
local.get 1
i32.load
local.set 4
local.get 1
i32.load offset=4
local.set 5
local.get 3
i32.const 0
i32.store offset=8
local.get 3
i64.const 0
i64.store
local.get 5
local.get 4
i32.sub
local.tee 6
i32.const 12
i32.div_s
local.set 7
block ;; label = @1
block ;; label = @2
local.get 5
local.get 4
i32.eq
br_if 0 (;@2;)
local.get 7
i32.const 1073741824
i32.ge_u
br_if 1 (;@1;)
local.get 3
local.get 7
i32.const 2
i32.shl
local.tee 4
call $operator_new_unsigned_long_
local.tee 5
i32.store
local.get 3
local.get 5
local.get 4
i32.add
local.tee 8
i32.store offset=8
local.get 5
i32.const 0
local.get 4
call $memset
drop
local.get 3
local.get 8
i32.store offset=4
end
local.get 0
local.get 7
local.get 3
call $std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>::vector_unsigned_long__std::__2::vector<int__std::__2::allocator<int>>_const&_
local.set 9
block ;; label = @2
local.get 6
i32.const 1
i32.lt_s
br_if 0 (;@2;)
local.get 7
i32.const 1
local.get 7
i32.const 1
i32.gt_s
select
local.tee 10
i32.const 2147483646
i32.and
local.set 8
local.get 10
i32.const 1
i32.and
local.set 11
local.get 2
i32.load
local.set 12
local.get 1
i32.load
local.set 13
i32.const 0
local.set 14
local.get 6
i32.const 24
i32.lt_s
local.set 15
loop ;; label = @3
local.get 9
i32.load
local.get 14
i32.const 12
i32.mul
local.tee 7
i32.add
i32.load
local.set 16
local.get 13
local.get 7
i32.add
i32.load
local.set 17
i32.const 0
local.set 2
loop ;; label = @4
local.get 16
local.get 2
i32.const 2
i32.shl
local.tee 5
i32.add
local.tee 6
i32.load
local.set 0
i32.const 0
local.set 4
block ;; label = @5
local.get 15
br_if 0 (;@5;)
i32.const 0
local.set 4
local.get 12
local.set 7
local.get 17
local.set 1
loop ;; label = @6
local.get 6
local.get 0
local.get 7
i32.load
local.get 5
i32.add
i32.load
local.get 1
i32.load
i32.mul
i32.add
local.tee 0
i32.store
local.get 6
local.get 0
local.get 7
i32.const 12
i32.add
i32.load
local.get 5
i32.add
i32.load
local.get 1
i32.const 4
i32.add
i32.load
i32.mul
i32.add
local.tee 0
i32.store
local.get 7
i32.const 24
i32.add
local.set 7
local.get 1
i32.const 8
i32.add
local.set 1
local.get 8
local.get 4
i32.const 2
i32.add
local.tee 4
i32.ne
br_if 0 (;@6;)
end
end
block ;; label = @5
local.get 11
i32.eqz
br_if 0 (;@5;)
local.get 6
local.get 0
local.get 12
local.get 4
i32.const 12
i32.mul
i32.add
i32.load
local.get 5
i32.add
i32.load
local.get 17
local.get 4
i32.const 2
i32.shl
i32.add
i32.load
i32.mul
i32.add
i32.store
end
local.get 2
i32.const 1
i32.add
local.tee 2
local.get 10
i32.ne
br_if 0 (;@4;)
end
local.get 14
i32.const 1
i32.add
local.tee 14
local.get 10
i32.ne
br_if 0 (;@3;)
end
end
block ;; label = @2
local.get 3
i32.load
local.tee 7
i32.eqz
br_if 0 (;@2;)
local.get 3
local.get 7
i32.store offset=4
local.get 7
call $operator_delete_void*_
end
local.get 3
i32.const 16
i32.add
global.set $__stack_pointer
return
end
call $abort
unreachable)
// generated by
(func $ijkalgorithm_std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>__std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>_ (type 3) (param i32 i32 i32)
(local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
global.get $__stack_pointer
i32.const 16
i32.sub
local.tee 3
global.set $__stack_pointer
local.get 1
i32.load
local.set 4
local.get 1
i32.load offset=4
local.set 5
local.get 3
i32.const 0
i32.store offset=8
local.get 3
i64.const 0
i64.store
local.get 5
local.get 4
i32.sub
local.tee 6
i32.const 12
i32.div_s
local.set 7
block ;; label = @1
block ;; label = @2
local.get 5
local.get 4
i32.eq
br_if 0 (;@2;)
local.get 7
i32.const 1073741824
i32.ge_u
br_if 1 (;@1;)
local.get 3
local.get 7
i32.const 2
i32.shl
local.tee 4
call $operator_new_unsigned_long_
local.tee 5
i32.store
local.get 3
local.get 5
local.get 4
i32.add
local.tee 8
i32.store offset=8
local.get 5
i32.const 0
local.get 4
call $memset
drop
local.get 3
local.get 8
i32.store offset=4
end
local.get 0
local.get 7
local.get 3
call $std::__2::vector<std::__2::vector<int__std::__2::allocator<int>>__std::__2::allocator<std::__2::vector<int__std::__2::allocator<int>>>>::vector_unsigned_long__std::__2::vector<int__std::__2::allocator<int>>_const&_
local.set 9
block ;; label = @2
block ;; label = @3
local.get 6
i32.const 1
i32.lt_s
br_if 0 (;@3;)
local.get 7
i32.const 1
local.get 7
i32.const 1
i32.gt_s
select
local.set 10
local.get 1
i32.load offset=4
local.get 1
i32.load
local.tee 11
i32.sub
i32.const 12
i32.div_s
local.set 12
i32.const 0
local.set 13
loop ;; label = @4
local.get 13
local.get 12
i32.eq
br_if 3 (;@1;)
local.get 11
local.get 13
i32.const 12
i32.mul
local.tee 14
i32.add
local.tee 7
i32.load offset=4
local.tee 15
local.get 7
i32.load
local.tee 16
i32.sub
i32.const 2
i32.shr_s
local.set 17
i32.const 0
local.set 1
loop ;; label = @5
local.get 9
i32.load offset=4
local.get 9
i32.load
local.tee 7
i32.sub
i32.const 12
i32.div_s
local.get 13
i32.le_u
br_if 3 (;@2;)
local.get 7
local.get 14
i32.add
local.tee 18
i32.const 4
i32.add
local.set 19
i32.const 0
local.set 7
i32.const 4
local.set 4
local.get 16
local.set 5
loop ;; label = @6
local.get 17
local.get 7
i32.eq
br_if 5 (;@1;)
local.get 2
i32.load offset=4
local.get 2
i32.load
local.tee 0
i32.sub
i32.const 12
i32.div_s
local.get 7
i32.le_u
br_if 5 (;@1;)
local.get 0
local.get 4
i32.add
local.tee 0
i32.load
local.get 0
i32.const -4
i32.add
i32.load
local.tee 0
i32.sub
i32.const 2
i32.shr_s
local.get 1
i32.le_u
br_if 5 (;@1;)
local.get 19
i32.load
local.get 18
i32.load
local.tee 6
i32.sub
i32.const 2
i32.shr_s
local.get 1
i32.le_u
br_if 5 (;@1;)
local.get 6
local.get 1
i32.const 2
i32.shl
local.tee 8
i32.add
local.tee 6
local.get 6
i32.load
local.get 0
local.get 8
i32.add
i32.load
local.get 5
i32.load
i32.mul
i32.add
i32.store
local.get 5
i32.const 4
i32.add
local.set 5
local.get 4
i32.const 12
i32.add
local.set 4
local.get 10
local.get 7
i32.const 1
i32.add
local.tee 7
i32.ne
br_if 0 (;@6;)
end
local.get 1
i32.const 1
i32.add
local.tee 1
local.get 10
i32.ne
br_if 0 (;@5;)
end
local.get 13
i32.const 1
i32.add
local.tee 13
local.get 10
i32.ne
br_if 0 (;@4;)
end
end
block ;; label = @3
local.get 3
i32.load
local.tee 7
i32.eqz
br_if 0 (;@3;)
local.get 3
local.get 7
i32.store offset=4
local.get 7
call $operator_delete_void*_
end
local.get 3
i32.const 16
i32.add
global.set $__stack_pointer
return
end
local.get 15 ;; checks if the vector size (local.get 15)
local.get 16 ;; and the index (local.get 16)
i32.eq ;; are equal
br_if 0 (;@1;)
local.get 2
i32.load offset=4
local.get 2
i32.load
local.tee 7
i32.eq
br_if 0 (;@1;)
local.get 7
i32.load offset=4
local.get 7
i32.load
i32.sub
i32.const 2
i32.shr_s
local.get 1
i32.gt_u
drop
end
call $abort
unreachable)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment