2021-05-04 06:06:59 +03:00
/*
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibCore/File.h>
# include <LibTest/JavaScriptTestRunner.h>
2021-06-04 14:54:20 +03:00
# include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
2021-05-04 06:06:59 +03:00
# include <LibWasm/Types.h>
TEST_ROOT ( " Userland/Libraries/LibWasm/Tests " ) ;
TESTJS_GLOBAL_FUNCTION ( read_binary_wasm_file , readBinaryWasmFile )
{
auto filename = vm . argument ( 0 ) . to_string ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
auto file = Core : : File : : open ( filename , Core : : OpenMode : : ReadOnly ) ;
if ( file . is_error ( ) ) {
2021-08-20 12:16:42 +03:00
vm . throw_exception < JS : : TypeError > ( global_object , file . error ( ) . string ( ) ) ;
2021-05-04 06:06:59 +03:00
return { } ;
}
auto contents = file . value ( ) - > read_all ( ) ;
auto array = JS : : Uint8Array : : create ( global_object , contents . size ( ) ) ;
contents . span ( ) . copy_to ( array - > data ( ) ) ;
return array ;
}
2021-05-07 08:32:58 +03:00
class WebAssemblyModule final : public JS : : Object {
JS_OBJECT ( WebAssemblyModule , JS : : Object ) ;
public :
explicit WebAssemblyModule ( JS : : Object & prototype )
: JS : : Object ( prototype )
{
2021-07-14 22:30:45 +03:00
m_machine . enable_instruction_count_limit ( ) ;
2021-05-07 08:32:58 +03:00
}
2021-05-10 14:10:49 +03:00
static Wasm : : AbstractMachine & machine ( ) { return m_machine ; }
Wasm : : Module & module ( ) { return * m_module ; }
Wasm : : ModuleInstance & module_instance ( ) { return * m_module_instance ; }
2021-06-21 18:40:41 +03:00
static WebAssemblyModule * create ( JS : : GlobalObject & global_object , Wasm : : Module module , HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > const & imports )
2021-05-07 08:32:58 +03:00
{
auto instance = global_object . heap ( ) . allocate < WebAssemblyModule > ( global_object , * global_object . object_prototype ( ) ) ;
instance - > m_module = move ( module ) ;
2021-06-21 18:40:41 +03:00
Wasm : : Linker linker ( * instance - > m_module ) ;
linker . link ( imports ) ;
linker . link ( spec_test_namespace ( ) ) ;
auto link_result = linker . finish ( ) ;
if ( link_result . is_error ( ) ) {
global_object . vm ( ) . throw_exception < JS : : TypeError > ( global_object , " Link failed " ) ;
} else {
if ( auto result = machine ( ) . instantiate ( * instance - > m_module , link_result . release_value ( ) ) ; result . is_error ( ) )
global_object . vm ( ) . throw_exception < JS : : TypeError > ( global_object , result . release_error ( ) . error ) ;
else
instance - > m_module_instance = result . release_value ( ) ;
}
2021-05-07 08:32:58 +03:00
return instance ;
}
void initialize ( JS : : GlobalObject & ) override ;
~ WebAssemblyModule ( ) override = default ;
private :
JS_DECLARE_NATIVE_FUNCTION ( get_export ) ;
JS_DECLARE_NATIVE_FUNCTION ( wasm_invoke ) ;
2021-06-21 18:40:41 +03:00
static HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > const & spec_test_namespace ( )
{
if ( ! s_spec_test_namespace . is_empty ( ) )
return s_spec_test_namespace ;
Wasm : : FunctionType print_i32_type { { Wasm : : ValueType ( Wasm : : ValueType : : I32 ) } , { } } ;
auto address = m_machine . store ( ) . allocate ( Wasm : : HostFunction {
[ ] ( auto & , auto & ) - > Wasm : : Result {
// Noop, this just needs to exist.
return Wasm : : Result { Vector < Wasm : : Value > { } } ;
} ,
print_i32_type } ) ;
s_spec_test_namespace . set ( { " spectest " , " print_i32 " , print_i32_type } , Wasm : : ExternValue { * address } ) ;
return s_spec_test_namespace ;
}
static HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > s_spec_test_namespace ;
2021-05-10 14:10:49 +03:00
static Wasm : : AbstractMachine m_machine ;
2021-05-07 08:32:58 +03:00
Optional < Wasm : : Module > m_module ;
2021-05-11 03:14:59 +03:00
OwnPtr < Wasm : : ModuleInstance > m_module_instance ;
2021-05-07 08:32:58 +03:00
} ;
2021-05-10 14:10:49 +03:00
Wasm : : AbstractMachine WebAssemblyModule : : m_machine ;
2021-06-21 18:40:41 +03:00
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > WebAssemblyModule : : s_spec_test_namespace ;
2021-05-10 14:10:49 +03:00
2021-05-04 06:06:59 +03:00
TESTJS_GLOBAL_FUNCTION ( parse_webassembly_module , parseWebAssemblyModule )
{
auto object = vm . argument ( 0 ) . to_object ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
if ( ! is < JS : : Uint8Array > ( object ) ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Expected a Uint8Array argument to parse_webassembly_module " ) ;
return { } ;
}
auto & array = static_cast < JS : : Uint8Array & > ( * object ) ;
InputMemoryStream stream { array . data ( ) } ;
auto result = Wasm : : Module : : parse ( stream ) ;
if ( result . is_error ( ) ) {
vm . throw_exception < JS : : SyntaxError > ( global_object , Wasm : : parse_error_to_string ( result . error ( ) ) ) ;
return { } ;
}
2021-05-07 08:32:58 +03:00
if ( stream . handle_any_error ( ) ) {
vm . throw_exception < JS : : SyntaxError > ( global_object , " Bianry stream contained errors " ) ;
return { } ;
}
2021-06-21 18:40:41 +03:00
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > imports ;
auto import_value = vm . argument ( 1 ) ;
if ( import_value . is_object ( ) ) {
auto & import_object = import_value . as_object ( ) ;
for ( auto & property : import_object . shape ( ) . property_table ( ) ) {
2021-07-04 00:38:30 +03:00
auto value = import_object . get_without_side_effects ( property . key ) ;
2021-06-21 18:40:41 +03:00
if ( ! value . is_object ( ) | | ! is < WebAssemblyModule > ( value . as_object ( ) ) )
continue ;
auto & module_object = static_cast < WebAssemblyModule & > ( value . as_object ( ) ) ;
for ( auto & entry : module_object . module_instance ( ) . exports ( ) ) {
// FIXME: Don't pretend that everything is a function
imports . set ( { property . key . as_string ( ) , entry . name ( ) , Wasm : : TypeIndex ( 0 ) } , entry . value ( ) ) ;
}
}
}
return WebAssemblyModule : : create ( global_object , result . release_value ( ) , imports ) ;
2021-05-04 06:06:59 +03:00
}
TESTJS_GLOBAL_FUNCTION ( compare_typed_arrays , compareTypedArrays )
{
auto lhs = vm . argument ( 0 ) . to_object ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
if ( ! is < JS : : TypedArrayBase > ( lhs ) ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Expected a TypedArray " ) ;
return { } ;
}
auto & lhs_array = static_cast < JS : : TypedArrayBase & > ( * lhs ) ;
auto rhs = vm . argument ( 1 ) . to_object ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
if ( ! is < JS : : TypedArrayBase > ( rhs ) ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Expected a TypedArray " ) ;
return { } ;
}
auto & rhs_array = static_cast < JS : : TypedArrayBase & > ( * rhs ) ;
return JS : : Value ( lhs_array . viewed_array_buffer ( ) - > buffer ( ) = = rhs_array . viewed_array_buffer ( ) - > buffer ( ) ) ;
}
2021-05-07 08:32:58 +03:00
void WebAssemblyModule : : initialize ( JS : : GlobalObject & global_object )
{
Base : : initialize ( global_object ) ;
2021-07-06 01:12:54 +03:00
define_native_function ( " getExport " , get_export , 1 , JS : : default_attributes ) ;
define_native_function ( " invoke " , wasm_invoke , 1 , JS : : default_attributes ) ;
2021-05-07 08:32:58 +03:00
}
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : get_export )
{
auto name = vm . argument ( 0 ) . to_string ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
auto this_value = vm . this_value ( global_object ) ;
auto object = this_value . to_object ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
if ( ! object | | ! is < WebAssemblyModule > ( object ) ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Not a WebAssemblyModule " ) ;
return { } ;
}
auto instance = static_cast < WebAssemblyModule * > ( object ) ;
2021-05-10 14:10:49 +03:00
for ( auto & entry : instance - > module_instance ( ) . exports ( ) ) {
2021-05-07 08:32:58 +03:00
if ( entry . name ( ) = = name ) {
auto & value = entry . value ( ) ;
if ( auto ptr = value . get_pointer < Wasm : : FunctionAddress > ( ) )
return JS : : Value ( static_cast < unsigned long > ( ptr - > value ( ) ) ) ;
2021-06-21 18:40:41 +03:00
if ( auto v = value . get_pointer < Wasm : : GlobalAddress > ( ) ) {
return m_machine . store ( ) . get ( * v ) - > value ( ) . value ( ) . visit (
[ & ] ( const auto & value ) - > JS : : Value { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
[ & ] ( const Wasm : : Reference & reference ) - > JS : : Value {
return reference . ref ( ) . visit (
[ & ] ( const Wasm : : Reference : : Null & ) - > JS : : Value { return JS : : js_null ( ) ; } ,
[ & ] ( const auto & ref ) - > JS : : Value { return JS : : Value ( static_cast < double > ( ref . address . value ( ) ) ) ; } ) ;
} ) ;
}
vm . throw_exception < JS : : TypeError > ( global_object , String : : formatted ( " '{}' does not refer to a function or a global " , name ) ) ;
2021-05-07 08:32:58 +03:00
return { } ;
}
}
vm . throw_exception < JS : : TypeError > ( global_object , String : : formatted ( " '{}' could not be found " , name ) ) ;
return { } ;
}
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : wasm_invoke )
{
auto address = static_cast < unsigned long > ( vm . argument ( 0 ) . to_double ( global_object ) ) ;
if ( vm . exception ( ) )
return { } ;
Wasm : : FunctionAddress function_address { address } ;
2021-05-10 14:10:49 +03:00
auto function_instance = WebAssemblyModule : : machine ( ) . store ( ) . get ( function_address ) ;
2021-05-07 08:32:58 +03:00
if ( ! function_instance ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Invalid function address " ) ;
return { } ;
}
const Wasm : : FunctionType * type { nullptr } ;
function_instance - > visit ( [ & ] ( auto & value ) { type = & value . type ( ) ; } ) ;
if ( ! type ) {
vm . throw_exception < JS : : TypeError > ( global_object , " Invalid function found at given address " ) ;
return { } ;
}
Vector < Wasm : : Value > arguments ;
if ( type - > parameters ( ) . size ( ) + 1 > vm . argument_count ( ) ) {
vm . throw_exception < JS : : TypeError > ( global_object , String : : formatted ( " Expected {} arguments for call, but found {} " , type - > parameters ( ) . size ( ) + 1 , vm . argument_count ( ) ) ) ;
return { } ;
}
size_t index = 1 ;
for ( auto & param : type - > parameters ( ) ) {
auto value = vm . argument ( index + + ) . to_double ( global_object ) ;
switch ( param . kind ( ) ) {
case Wasm : : ValueType : : Kind : : I32 :
2021-06-01 21:43:27 +03:00
arguments . append ( Wasm : : Value ( param , static_cast < u64 > ( value ) ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : I64 :
2021-06-01 21:43:27 +03:00
arguments . append ( Wasm : : Value ( param , static_cast < u64 > ( value ) ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : F32 :
arguments . append ( Wasm : : Value ( static_cast < float > ( value ) ) ) ;
break ;
case Wasm : : ValueType : : Kind : : F64 :
arguments . append ( Wasm : : Value ( static_cast < double > ( value ) ) ) ;
break ;
case Wasm : : ValueType : : Kind : : FunctionReference :
2021-06-04 02:00:09 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( value ) } } ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : ExternReference :
2021-06-04 02:00:09 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( value ) } } ) ) ;
2021-05-07 08:32:58 +03:00
break ;
2021-06-01 08:18:36 +03:00
case Wasm : : ValueType : : Kind : : NullFunctionReference :
2021-06-04 02:00:09 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Null { Wasm : : ValueType ( Wasm : : ValueType : : Kind : : FunctionReference ) } } ) ) ;
2021-06-01 08:18:36 +03:00
break ;
case Wasm : : ValueType : : Kind : : NullExternReference :
2021-06-04 02:00:09 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Null { Wasm : : ValueType ( Wasm : : ValueType : : Kind : : ExternReference ) } } ) ) ;
2021-06-01 08:18:36 +03:00
break ;
2021-05-07 08:32:58 +03:00
}
}
2021-05-10 14:10:49 +03:00
auto result = WebAssemblyModule : : machine ( ) . invoke ( function_address , arguments ) ;
2021-05-07 08:32:58 +03:00
if ( result . is_trap ( ) ) {
2021-07-01 15:33:17 +03:00
vm . throw_exception < JS : : TypeError > ( global_object , String : : formatted ( " Execution trapped: {} " , result . trap ( ) . reason ) ) ;
2021-05-07 08:32:58 +03:00
return { } ;
}
if ( result . values ( ) . is_empty ( ) )
return JS : : js_null ( ) ;
JS : : Value return_value ;
result . values ( ) . first ( ) . value ( ) . visit (
[ & ] ( const auto & value ) { return_value = JS : : Value ( static_cast < double > ( value ) ) ; } ,
2021-06-04 02:00:09 +03:00
[ & ] ( const Wasm : : Reference & reference ) {
reference . ref ( ) . visit (
[ & ] ( const Wasm : : Reference : : Null & ) { return_value = JS : : js_null ( ) ; } ,
[ & ] ( const auto & ref ) { return_value = JS : : Value ( static_cast < double > ( ref . address . value ( ) ) ) ; } ) ;
} ) ;
2021-05-07 08:32:58 +03:00
return return_value ;
}