2021-05-04 07:36:59 +04:30
/*
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-03-10 16:20:40 +00:00
# include <LibCore/Stream.h>
2021-05-04 07:36:59 +04:30
# include <LibTest/JavaScriptTestRunner.h>
2021-06-04 21:54:20 +10:00
# include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
2021-05-04 07:36:59 +04:30
# include <LibWasm/Types.h>
2021-11-07 02:15:10 +01:00
# include <string.h>
2021-05-04 07:36:59 +04:30
TEST_ROOT ( " Userland/Libraries/LibWasm/Tests " ) ;
TESTJS_GLOBAL_FUNCTION ( read_binary_wasm_file , readBinaryWasmFile )
{
2022-08-22 11:48:08 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-08-21 14:00:56 +01:00
auto filename = TRY ( vm . argument ( 0 ) . to_string ( vm ) ) ;
2022-03-10 16:20:40 +00:00
auto file = Core : : Stream : : File : : open ( filename , Core : : Stream : : OpenMode : : Read ) ;
2021-10-20 01:58:04 +03:00
if ( file . is_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( strerror ( file . error ( ) . code ( ) ) ) ;
2022-03-10 16:20:40 +00:00
auto file_size = file . value ( ) - > size ( ) ;
if ( file_size . is_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( strerror ( file_size . error ( ) . code ( ) ) ) ;
2022-03-10 16:20:40 +00:00
2022-12-13 20:49:50 +00:00
auto array = TRY ( JS : : Uint8Array : : create ( realm , file_size . value ( ) ) ) ;
2022-03-10 16:20:40 +00:00
auto read = file . value ( ) - > read ( array - > data ( ) ) ;
if ( read . is_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( strerror ( read . error ( ) . code ( ) ) ) ;
2022-03-10 16:20:40 +00:00
2021-10-20 01:58:04 +03:00
return JS : : Value ( array ) ;
2021-05-04 07:36:59 +04:30
}
2021-05-07 10:02:58 +04:30
class WebAssemblyModule final : public JS : : Object {
JS_OBJECT ( WebAssemblyModule , JS : : Object ) ;
public :
explicit WebAssemblyModule ( JS : : Object & prototype )
2022-12-14 12:17:58 +01:00
: JS : : Object ( ConstructWithPrototypeTag : : Tag , prototype )
2021-05-07 10:02:58 +04:30
{
2021-07-15 00:00:45 +04:30
m_machine . enable_instruction_count_limit ( ) ;
2021-05-07 10:02:58 +04:30
}
2021-05-10 15:40:49 +04:30
static Wasm : : AbstractMachine & machine ( ) { return m_machine ; }
Wasm : : Module & module ( ) { return * m_module ; }
Wasm : : ModuleInstance & module_instance ( ) { return * m_module_instance ; }
2022-08-16 00:20:49 +01:00
static JS : : ThrowCompletionOr < WebAssemblyModule * > create ( JS : : Realm & realm , Wasm : : Module module , HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > const & imports )
2021-05-07 10:02:58 +04:30
{
2022-08-16 00:20:49 +01:00
auto & vm = realm . vm ( ) ;
2022-08-27 00:54:55 +01:00
auto * instance = realm . heap ( ) . allocate < WebAssemblyModule > ( realm , * realm . intrinsics ( ) . object_prototype ( ) ) ;
2021-05-07 10:02:58 +04:30
instance - > m_module = move ( module ) ;
2021-06-21 20:10:41 +04:30
Wasm : : Linker linker ( * instance - > m_module ) ;
linker . link ( imports ) ;
linker . link ( spec_test_namespace ( ) ) ;
auto link_result = linker . finish ( ) ;
2021-12-31 14:59:11 +01:00
if ( link_result . is_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Link failed " ) ;
2021-12-31 14:59:11 +01:00
auto result = machine ( ) . instantiate ( * instance - > m_module , link_result . release_value ( ) ) ;
if ( result . is_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( result . release_error ( ) . error ) ;
2021-12-31 14:59:11 +01:00
instance - > m_module_instance = result . release_value ( ) ;
2021-05-07 10:02:58 +04:30
return instance ;
}
2022-08-16 00:20:49 +01:00
void initialize ( JS : : Realm & ) override ;
2021-05-07 10:02:58 +04:30
~ WebAssemblyModule ( ) override = default ;
private :
2021-10-31 17:05:15 +02:00
JS_DECLARE_NATIVE_FUNCTION ( get_export ) ;
JS_DECLARE_NATIVE_FUNCTION ( wasm_invoke ) ;
2021-05-07 10:02:58 +04:30
2021-06-21 20:10:41 +04:30
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 15:40:49 +04:30
static Wasm : : AbstractMachine m_machine ;
2021-05-07 10:02:58 +04:30
Optional < Wasm : : Module > m_module ;
2021-05-11 04:44:59 +04:30
OwnPtr < Wasm : : ModuleInstance > m_module_instance ;
2021-05-07 10:02:58 +04:30
} ;
2021-05-10 15:40:49 +04:30
Wasm : : AbstractMachine WebAssemblyModule : : m_machine ;
2021-06-21 20:10:41 +04:30
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > WebAssemblyModule : : s_spec_test_namespace ;
2021-05-10 15:40:49 +04:30
2021-05-04 07:36:59 +04:30
TESTJS_GLOBAL_FUNCTION ( parse_webassembly_module , parseWebAssemblyModule )
{
2022-08-22 11:48:08 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-08-21 14:00:56 +01:00
auto * object = TRY ( vm . argument ( 0 ) . to_object ( vm ) ) ;
2021-10-20 01:58:04 +03:00
if ( ! is < JS : : Uint8Array > ( object ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Expected a Uint8Array argument to parse_webassembly_module " ) ;
2021-05-04 07:36:59 +04:30
auto & array = static_cast < JS : : Uint8Array & > ( * object ) ;
InputMemoryStream stream { array . data ( ) } ;
2021-08-30 17:30:18 +04:30
ScopeGuard handle_stream_error {
[ & ] {
stream . handle_any_error ( ) ;
}
} ;
2021-05-04 07:36:59 +04:30
auto result = Wasm : : Module : : parse ( stream ) ;
2021-10-20 01:58:04 +03:00
if ( result . is_error ( ) )
2022-12-06 01:12:49 +00:00
return vm . throw_completion < JS : : SyntaxError > ( Wasm : : parse_error_to_deprecated_string ( result . error ( ) ) ) ;
2021-05-07 10:02:58 +04:30
2021-10-20 01:58:04 +03:00
if ( stream . handle_any_error ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : SyntaxError > ( " Binary stream contained errors " ) ;
2021-06-21 20:10:41 +04:30
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-03 22:38:30 +01:00
auto value = import_object . get_without_side_effects ( property . key ) ;
2021-06-21 20:10:41 +04:30
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 00:20:49 +01:00
return JS : : Value ( TRY ( WebAssemblyModule : : create ( realm , result . release_value ( ) , imports ) ) ) ;
2021-05-04 07:36:59 +04:30
}
TESTJS_GLOBAL_FUNCTION ( compare_typed_arrays , compareTypedArrays )
{
2022-08-21 14:00:56 +01:00
auto * lhs = TRY ( vm . argument ( 0 ) . to_object ( vm ) ) ;
2021-10-20 01:58:04 +03:00
if ( ! is < JS : : TypedArrayBase > ( lhs ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Expected a TypedArray " ) ;
2021-05-04 07:36:59 +04:30
auto & lhs_array = static_cast < JS : : TypedArrayBase & > ( * lhs ) ;
2022-08-21 14:00:56 +01:00
auto * rhs = TRY ( vm . argument ( 1 ) . to_object ( vm ) ) ;
2021-10-20 01:58:04 +03:00
if ( ! is < JS : : TypedArrayBase > ( rhs ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Expected a TypedArray " ) ;
2021-05-04 07:36:59 +04:30
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 10:02:58 +04:30
2022-08-16 00:20:49 +01:00
void WebAssemblyModule : : initialize ( JS : : Realm & realm )
2021-05-07 10:02:58 +04:30
{
2022-08-16 00:20:49 +01:00
Base : : initialize ( realm ) ;
2022-08-22 21:47:35 +01: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 10:02:58 +04:30
}
2021-10-31 17:05:15 +02:00
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : get_export )
2021-05-07 10:02:58 +04:30
{
2022-08-21 14:00:56 +01:00
auto name = TRY ( vm . argument ( 0 ) . to_string ( vm ) ) ;
2022-08-20 09:48:43 +01:00
auto this_value = vm . this_value ( ) ;
2022-08-21 14:00:56 +01:00
auto * object = TRY ( this_value . to_object ( vm ) ) ;
2021-10-31 17:05:15 +02:00
if ( ! is < WebAssemblyModule > ( object ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Not a WebAssemblyModule " ) ;
2021-05-07 10:02:58 +04:30
auto instance = static_cast < WebAssemblyModule * > ( object ) ;
2021-05-10 15:40:49 +04:30
for ( auto & entry : instance - > module_instance ( ) . exports ( ) ) {
2021-05-07 10:02:58 +04:30
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 20:10:41 +04:30
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 16:19:22 +04:30
[ & ] ( i32 value ) { return JS : : Value ( static_cast < double > ( value ) ) ; } ,
2022-12-06 22:03:52 +00: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 20:10:41 +04:30
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 ( ) ) ) ; } ) ;
} ) ;
}
2022-12-04 18:02:33 +00:00
return vm . throw_completion < JS : : TypeError > ( DeprecatedString : : formatted ( " '{}' does not refer to a function or a global " , name ) ) ;
2021-05-07 10:02:58 +04:30
}
}
2022-12-04 18:02:33 +00:00
return vm . throw_completion < JS : : TypeError > ( DeprecatedString : : formatted ( " '{}' could not be found " , name ) ) ;
2021-05-07 10:02:58 +04:30
}
2021-10-31 17:05:15 +02:00
JS_DEFINE_NATIVE_FUNCTION ( WebAssemblyModule : : wasm_invoke )
2021-05-07 10:02:58 +04:30
{
2022-08-21 14:00:56 +01:00
auto address = static_cast < unsigned long > ( TRY ( vm . argument ( 0 ) . to_double ( vm ) ) ) ;
2021-05-07 10:02:58 +04:30
Wasm : : FunctionAddress function_address { address } ;
2021-05-10 15:40:49 +04:30
auto function_instance = WebAssemblyModule : : machine ( ) . store ( ) . get ( function_address ) ;
2021-10-31 17:05:15 +02:00
if ( ! function_instance )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Invalid function address " ) ;
2021-05-07 10:02:58 +04:30
2022-04-01 20:58:27 +03:00
Wasm : : FunctionType const * type { nullptr } ;
2021-05-07 10:02:58 +04:30
function_instance - > visit ( [ & ] ( auto & value ) { type = & value . type ( ) ; } ) ;
2021-10-31 17:05:15 +02:00
if ( ! type )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < JS : : TypeError > ( " Invalid function found at given address " ) ;
2021-05-07 10:02:58 +04:30
Vector < Wasm : : Value > arguments ;
2021-10-31 17:05:15 +02:00
if ( type - > parameters ( ) . size ( ) + 1 > vm . argument_count ( ) )
2022-12-04 18:02:33 +00:00
return vm . throw_completion < JS : : TypeError > ( DeprecatedString : : formatted ( " Expected {} arguments for call, but found {} " , type - > parameters ( ) . size ( ) + 1 , vm . argument_count ( ) ) ) ;
2021-05-07 10:02:58 +04:30
size_t index = 1 ;
for ( auto & param : type - > parameters ( ) ) {
2021-08-30 16:19:22 +04:30
auto argument = vm . argument ( index + + ) ;
double double_value = 0 ;
if ( ! argument . is_bigint ( ) )
2022-08-21 14:00:56 +01:00
double_value = TRY ( argument . to_double ( vm ) ) ;
2021-05-07 10:02:58 +04:30
switch ( param . kind ( ) ) {
case Wasm : : ValueType : : Kind : : I32 :
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( param , static_cast < u64 > ( double_value ) ) ) ;
2021-05-07 10:02:58 +04:30
break ;
case Wasm : : ValueType : : Kind : : I64 :
2021-08-30 16:19:22 +04:30
if ( argument . is_bigint ( ) ) {
2022-08-21 14:00:56 +01:00
auto value = TRY ( argument . to_bigint_int64 ( vm ) ) ;
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( param , bit_cast < u64 > ( value ) ) ) ;
} else {
arguments . append ( Wasm : : Value ( param , static_cast < u64 > ( double_value ) ) ) ;
}
2021-05-07 10:02:58 +04:30
break ;
case Wasm : : ValueType : : Kind : : F32 :
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( static_cast < float > ( double_value ) ) ) ;
2021-05-07 10:02:58 +04:30
break ;
case Wasm : : ValueType : : Kind : : F64 :
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( static_cast < double > ( double_value ) ) ) ;
2021-05-07 10:02:58 +04:30
break ;
case Wasm : : ValueType : : Kind : : FunctionReference :
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( double_value ) } } ) ) ;
2021-05-07 10:02:58 +04:30
break ;
case Wasm : : ValueType : : Kind : : ExternReference :
2021-08-30 16:19:22 +04:30
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Func { static_cast < u64 > ( double_value ) } } ) ) ;
2021-05-07 10:02:58 +04:30
break ;
2021-06-01 09:48:36 +04:30
case Wasm : : ValueType : : Kind : : NullFunctionReference :
2021-06-04 03:30:09 +04:30
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Null { Wasm : : ValueType ( Wasm : : ValueType : : Kind : : FunctionReference ) } } ) ) ;
2021-06-01 09:48:36 +04:30
break ;
case Wasm : : ValueType : : Kind : : NullExternReference :
2021-06-04 03:30:09 +04:30
arguments . append ( Wasm : : Value ( Wasm : : Reference { Wasm : : Reference : : Null { Wasm : : ValueType ( Wasm : : ValueType : : Kind : : ExternReference ) } } ) ) ;
2021-06-01 09:48:36 +04:30
break ;
2021-05-07 10:02:58 +04:30
}
}
2021-05-10 15:40:49 +04:30
auto result = WebAssemblyModule : : machine ( ) . invoke ( function_address , arguments ) ;
2021-10-31 17:05:15 +02:00
if ( result . is_trap ( ) )
2022-12-04 18:02:33 +00:00
return vm . throw_completion < JS : : TypeError > ( DeprecatedString : : formatted ( " Execution trapped: {} " , result . trap ( ) . reason ) ) ;
2021-05-07 10:02:58 +04:30
if ( result . values ( ) . is_empty ( ) )
return JS : : js_null ( ) ;
JS : : Value return_value ;
result . values ( ) . first ( ) . value ( ) . visit (
2022-04-01 20:58:27 +03:00
[ & ] ( auto const & value ) { return_value = JS : : Value ( static_cast < double > ( value ) ) ; } ,
2021-08-30 16:19:22 +04:30
[ & ] ( i32 value ) { return_value = JS : : Value ( static_cast < double > ( value ) ) ; } ,
2022-12-06 22:03:52 +00:00
[ & ] ( i64 value ) { return_value = JS : : Value ( JS : : BigInt : : create ( vm , Crypto : : SignedBigInteger { value } ) ) ; } ,
2022-04-01 20:58:27 +03:00
[ & ] ( Wasm : : Reference const & reference ) {
2021-06-04 03:30:09 +04:30
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 10:02:58 +04:30
return return_value ;
}