2021-05-04 06:06:59 +03:00
/*
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2023-01-25 22:19:05 +03:00
# include <AK/MemoryStream.h>
2021-05-04 06:06:59 +03:00
# 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>
2021-11-07 04:15:10 +03:00
# include <string.h>
2021-05-04 06:06:59 +03:00
TEST_ROOT ( " Userland/Libraries/LibWasm/Tests " ) ;
TESTJS_GLOBAL_FUNCTION ( read_binary_wasm_file , readBinaryWasmFile )
{
2022-08-22 13:48:08 +03:00
auto & realm = * vm . current_realm ( ) ;
2023-02-16 22:09:11 +03:00
auto error_code_to_string = [ ] ( int code ) {
auto const * error_string = strerror ( code ) ;
return StringView { error_string , strlen ( error_string ) } ;
} ;
2023-01-13 18:29:02 +03:00
auto filename = TRY ( vm . argument ( 0 ) . to_deprecated_string ( vm ) ) ;
2023-02-09 05:02:46 +03:00
auto file = Core : : File : : open ( filename , Core : : File : : OpenMode : : Read ) ;
2021-10-20 01:58:04 +03:00
if ( file . is_error ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( error_code_to_string ( file . error ( ) . code ( ) ) ) ;
2022-03-10 19:20:40 +03:00
auto file_size = file . value ( ) - > size ( ) ;
if ( file_size . is_error ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( error_code_to_string ( file_size . error ( ) . code ( ) ) ) ;
2022-03-10 19:20:40 +03:00
2022-12-13 23:49:50 +03:00
auto array = TRY ( JS : : Uint8Array : : create ( realm , file_size . value ( ) ) ) ;
2022-03-10 19:20:40 +03:00
2023-03-01 19:24:50 +03:00
auto read = file . value ( ) - > read_until_filled ( array - > data ( ) ) ;
2022-03-10 19:20:40 +03:00
if ( read . is_error ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( error_code_to_string ( read . error ( ) . code ( ) ) ) ;
2022-03-10 19:20:40 +03:00
2021-10-20 01:58:04 +03:00
return JS : : Value ( array ) ;
2021-05-04 06:06:59 +03:00
}
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 )
2022-12-14 14:17:58 +03:00
: JS : : Object ( ConstructWithPrototypeTag : : Tag , prototype )
2021-05-07 08:32:58 +03:00
{
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 ; }
2022-08-16 02:20:49 +03:00
static JS : : ThrowCompletionOr < WebAssemblyModule * > create ( JS : : Realm & realm , Wasm : : Module module , HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > const & imports )
2021-05-07 08:32:58 +03:00
{
2022-08-16 02:20:49 +03:00
auto & vm = realm . vm ( ) ;
2023-04-13 01:47:15 +03:00
auto instance = MUST_OR_THROW_OOM ( realm . heap ( ) . allocate < WebAssemblyModule > ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ) ;
2021-05-07 08:32:58 +03:00
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 ( ) ;
2021-12-31 16:59:11 +03:00
if ( link_result . is_error ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Link failed " sv ) ;
2021-12-31 16:59:11 +03:00
auto result = machine ( ) . instantiate ( * instance - > m_module , link_result . release_value ( ) ) ;
if ( result . is_error ( ) )
2022-08-16 22:33:17 +03:00
return vm . throw_completion < JS : : TypeError > ( result . release_error ( ) . error ) ;
2021-12-31 16:59:11 +03:00
instance - > m_module_instance = result . release_value ( ) ;
2022-12-14 20:40:33 +03:00
return instance . ptr ( ) ;
2021-05-07 08:32:58 +03:00
}
2023-08-07 09:41:28 +03:00
void initialize ( JS : : Realm & ) override ;
2021-05-07 08:32:58 +03:00
~ WebAssemblyModule ( ) override = default ;
private :
2021-10-31 18:05:15 +03:00
JS_DECLARE_NATIVE_FUNCTION ( get_export ) ;
JS_DECLARE_NATIVE_FUNCTION ( wasm_invoke ) ;
2021-05-07 08:32:58 +03:00
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 )
{
2022-08-22 13:48:08 +03:00
auto & realm = * vm . current_realm ( ) ;
2023-04-13 16:26:41 +03:00
auto object = TRY ( vm . argument ( 0 ) . to_object ( vm ) ) ;
if ( ! is < JS : : Uint8Array > ( * object ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Expected a Uint8Array argument to parse_webassembly_module " sv ) ;
2021-05-04 06:06:59 +03:00
auto & array = static_cast < JS : : Uint8Array & > ( * object ) ;
2023-01-30 13:05:43 +03:00
FixedMemoryStream stream { array . data ( ) } ;
auto result = Wasm : : Module : : parse ( stream ) ;
2021-10-20 01:58:04 +03:00
if ( result . is_error ( ) )
2022-12-06 04:12:49 +03:00
return vm . throw_completion < JS : : SyntaxError > ( Wasm : : parse_error_to_deprecated_string ( result . error ( ) ) ) ;
2021-05-07 08:32:58 +03:00
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 ( ) ) ;
}
}
}
2022-08-16 02:20:49 +03:00
return JS : : Value ( TRY ( WebAssemblyModule : : create ( realm , result . release_value ( ) , imports ) ) ) ;
2021-05-04 06:06:59 +03:00
}
TESTJS_GLOBAL_FUNCTION ( compare_typed_arrays , compareTypedArrays )
{
2023-04-13 16:26:41 +03:00
auto lhs = TRY ( vm . argument ( 0 ) . to_object ( vm ) ) ;
if ( ! is < JS : : TypedArrayBase > ( * lhs ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Expected a TypedArray " sv ) ;
2021-05-04 06:06:59 +03:00
auto & lhs_array = static_cast < JS : : TypedArrayBase & > ( * lhs ) ;
2023-04-13 16:26:41 +03:00
auto rhs = TRY ( vm . argument ( 1 ) . to_object ( vm ) ) ;
if ( ! is < JS : : TypedArrayBase > ( * rhs ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Expected a TypedArray " sv ) ;
2021-05-04 06:06:59 +03:00
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
2023-08-07 09:41:28 +03:00
void WebAssemblyModule : : initialize ( JS : : Realm & realm )
2021-05-07 08:32:58 +03:00
{
2023-08-07 09:41:28 +03:00
Base : : initialize ( realm ) ;
2022-08-22 23:47:35 +03:00
define_native_function ( realm , " getExport " , get_export , 1 , JS : : default_attributes ) ;
define_native_function ( realm , " invoke " , wasm_invoke , 1 , JS : : default_attributes ) ;
2021-05-07 08:32:58 +03:00
}
2021-10-31 18:05:15 +03:00
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : get_export )
2021-05-07 08:32:58 +03:00
{
2023-01-13 18:29:02 +03:00
auto name = TRY ( vm . argument ( 0 ) . to_deprecated_string ( vm ) ) ;
2022-08-20 11:48:43 +03:00
auto this_value = vm . this_value ( ) ;
2023-04-13 16:26:41 +03:00
auto object = TRY ( this_value . to_object ( vm ) ) ;
if ( ! is < WebAssemblyModule > ( * object ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Not a WebAssemblyModule " sv ) ;
2023-04-13 16:26:41 +03:00
auto & instance = static_cast < WebAssemblyModule & > ( * object ) ;
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 (
2022-04-01 20:58:27 +03:00
[ & ] ( auto const & value ) - > JS : : Value { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
2021-08-30 14:49:22 +03:00
[ & ] ( i32 value ) { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
2022-12-07 01:03:52 +03:00
[ & ] ( i64 value ) - > JS : : Value { return JS : : BigInt : : create ( vm , Crypto : : SignedBigInteger { value } ) ; } ,
2022-04-01 20:58:27 +03:00
[ & ] ( Wasm : : Reference const & reference ) - > JS : : Value {
2021-06-21 18:40:41 +03:00
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 ( ) ) ) ; } ) ;
} ) ;
}
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( TRY_OR_THROW_OOM ( vm , String : : formatted ( " '{}' does not refer to a function or a global " , name ) ) ) ;
2021-05-07 08:32:58 +03:00
}
}
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( TRY_OR_THROW_OOM ( vm , String : : formatted ( " '{}' could not be found " , name ) ) ) ;
2021-05-07 08:32:58 +03:00
}
2021-10-31 18:05:15 +03:00
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : wasm_invoke )
2021-05-07 08:32:58 +03:00
{
2022-08-21 16:00:56 +03:00
auto address = static_cast < unsigned long > ( TRY ( vm . argument ( 0 ) . to_double ( vm ) ) ) ;
2021-05-07 08:32:58 +03:00
Wasm : : FunctionAddress function_address { address } ;
2021-05-10 14:10:49 +03:00
auto function_instance = WebAssemblyModule : : machine ( ) . store ( ) . get ( function_address ) ;
2021-10-31 18:05:15 +03:00
if ( ! function_instance )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Invalid function address " sv ) ;
2021-05-07 08:32:58 +03:00
2022-04-01 20:58:27 +03:00
Wasm : : FunctionType const * type { nullptr } ;
2021-05-07 08:32:58 +03:00
function_instance - > visit ( [ & ] ( auto & value ) { type = & value . type ( ) ; } ) ;
2021-10-31 18:05:15 +03:00
if ( ! type )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( " Invalid function found at given address " sv ) ;
2021-05-07 08:32:58 +03:00
Vector < Wasm : : Value > arguments ;
2021-10-31 18:05:15 +03:00
if ( type - > parameters ( ) . size ( ) + 1 > vm . argument_count ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( TRY_OR_THROW_OOM ( vm , String : : formatted ( " Expected {} arguments for call, but found {} " , type - > parameters ( ) . size ( ) + 1 , vm . argument_count ( ) ) ) ) ;
2021-05-07 08:32:58 +03:00
size_t index = 1 ;
for ( auto & param : type - > parameters ( ) ) {
2021-08-30 14:49:22 +03:00
auto argument = vm . argument ( index + + ) ;
double double_value = 0 ;
if ( ! argument . is_bigint ( ) )
2022-08-21 16:00:56 +03:00
double_value = TRY ( argument . to_double ( vm ) ) ;
2021-05-07 08:32:58 +03:00
switch ( param . kind ( ) ) {
case Wasm : : ValueType : : Kind : : I32 :
2022-12-24 08:22:41 +03:00
arguments . append ( Wasm : : Value ( param , static_cast < i64 > ( double_value ) ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : I64 :
2021-08-30 14:49:22 +03:00
if ( argument . is_bigint ( ) ) {
2022-08-21 16:00:56 +03:00
auto value = TRY ( argument . to_bigint_int64 ( vm ) ) ;
2022-12-24 08:22:41 +03:00
arguments . append ( Wasm : : Value ( param , value ) ) ;
2021-08-30 14:49:22 +03:00
} else {
2022-12-24 08:22:41 +03:00
arguments . append ( Wasm : : Value ( param , static_cast < i64 > ( double_value ) ) ) ;
2021-08-30 14:49:22 +03:00
}
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : F32 :
2021-08-30 14:49:22 +03:00
arguments . append ( Wasm : : Value ( static_cast < float > ( double_value ) ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : F64 :
2021-08-30 14:49:22 +03:00
arguments . append ( Wasm : : Value ( static_cast < double > ( double_value ) ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : FunctionReference :
2021-08-30 14:49:22 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( double_value ) } } ) ) ;
2021-05-07 08:32:58 +03:00
break ;
case Wasm : : ValueType : : Kind : : ExternReference :
2021-08-30 14:49:22 +03:00
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( double_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-10-31 18:05:15 +03:00
if ( result . is_trap ( ) )
2023-02-16 22:09:11 +03:00
return vm . throw_completion < JS : : TypeError > ( TRY_OR_THROW_OOM ( vm , String : : formatted ( " Execution trapped: {} " , result . trap ( ) . reason ) ) ) ;
2021-05-07 08:32:58 +03:00
2023-02-25 10:45:11 +03:00
if ( result . is_completion ( ) )
return result . completion ( ) ;
2021-05-07 08:32:58 +03:00
if ( result . values ( ) . is_empty ( ) )
return JS : : js_null ( ) ;
2023-02-25 11:14:43 +03:00
auto to_js_value = [ & ] ( Wasm : : Value const & value ) {
return value . value ( ) . visit (
[ ] ( auto const & value ) { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
[ ] ( i32 value ) { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
[ & ] ( i64 value ) { return JS : : Value ( JS : : BigInt : : create ( vm , Crypto : : SignedBigInteger { value } ) ) ; } ,
[ ] ( Wasm : : Reference const & reference ) {
return reference . ref ( ) . visit (
[ ] ( const Wasm : : Reference : : Null & ) { return JS : : js_null ( ) ; } ,
[ ] ( const auto & ref ) { return JS : : Value ( static_cast < double > ( ref . address . value ( ) ) ) ; } ) ;
} ) ;
} ;
if ( result . values ( ) . size ( ) = = 1 )
return to_js_value ( result . values ( ) . first ( ) ) ;
return JS : : Array : : create_from < Wasm : : Value > ( * vm . current_realm ( ) , result . values ( ) , [ & ] ( Wasm : : Value value ) {
return to_js_value ( value ) ;
} ) ;
2021-05-07 08:32:58 +03:00
}