2021-04-26 12:48:13 +04:30
/*
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
2022-01-08 18:41:38 +01:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-04-26 12:48:13 +04:30
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-02-18 06:14:25 +03:30
# include <AK/GenericLexer.h>
# include <AK/Hex.h>
2023-01-25 20:19:05 +01:00
# include <AK/MemoryStream.h>
2023-05-28 12:13:43 +02:00
# include <AK/StackInfo.h>
2025-08-20 12:00:21 +02:00
# include <AK/Utf16String.h>
2021-04-26 12:48:13 +04:30
# include <LibCore/ArgsParser.h>
2025-08-08 12:10:39 -07:00
# include <LibCore/EventLoop.h>
2023-02-09 03:02:46 +01:00
# include <LibCore/File.h>
2022-10-24 04:01:40 +03:30
# include <LibCore/MappedFile.h>
2025-08-20 12:00:21 +02:00
# include <LibCrypto/BigInt/SignedBigInteger.h>
2023-05-20 10:39:14 +02:00
# include <LibFileSystem/FileSystem.h>
2025-08-20 12:00:21 +02:00
# include <LibJS/Bytecode/Interpreter.h>
# include <LibJS/Runtime/AbstractOperations.h>
# include <LibJS/Runtime/BigInt.h>
# include <LibJS/Runtime/VM.h>
# include <LibJS/Script.h>
2025-08-08 12:10:39 -07:00
# if !defined(AK_OS_WINDOWS)
# include <LibLine / Editor.h>
# endif
2022-01-08 18:41:38 +01:00
# include <LibMain/Main.h>
2021-05-01 01:08:51 +04:30
# include <LibWasm/AbstractMachine/AbstractMachine.h>
2021-06-04 21:54:20 +10:00
# include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
2021-04-27 22:13:01 +04:30
# include <LibWasm/Printer/Printer.h>
2021-04-26 12:48:13 +04:30
# include <LibWasm/Types.h>
2025-08-08 12:10:39 -07:00
# if !defined(AK_OS_WINDOWS)
# include <LibWasm / Wasi.h>
# endif
2025-04-22 09:48:26 +02:00
# include <math.h>
2021-05-21 21:10:44 +04:30
# include <signal.h>
2023-02-10 01:00:18 +01:00
static OwnPtr < Stream > g_stdout { } ;
2023-01-20 00:37:03 +01:00
static OwnPtr < Wasm : : Printer > g_printer { } ;
2023-05-28 12:13:43 +02:00
static StackInfo g_stack_info ;
2025-08-02 20:23:26 +02:00
static Wasm : : BytecodeInterpreter g_interpreter ( g_stack_info ) ;
2021-05-21 21:10:44 +04:30
2024-08-04 08:06:50 -07:00
struct ParsedValue {
Wasm : : Value value ;
Wasm : : ValueType type ;
} ;
2024-02-18 06:14:25 +03:30
static Optional < u128 > convert_to_uint ( StringView string )
{
if ( string . is_empty ( ) )
return { } ;
u128 value = 0 ;
auto const characters = string . characters_without_null_termination ( ) ;
for ( size_t i = 0 ; i < string . length ( ) ; i + + ) {
if ( characters [ i ] < ' 0 ' | | characters [ i ] > ' 9 ' )
return { } ;
value * = 10 ;
value + = u128 { static_cast < u64 > ( characters [ i ] - ' 0 ' ) , 0 } ;
}
return value ;
}
static Optional < u128 > convert_to_uint_from_hex ( StringView string )
{
if ( string . is_empty ( ) )
return { } ;
u128 value = 0 ;
auto const count = string . length ( ) ;
auto const upper_bound = NumericLimits < u128 > : : max ( ) ;
for ( size_t i = 0 ; i < count ; i + + ) {
char digit = string [ i ] ;
if ( value > ( upper_bound > > 4 ) )
return { } ;
auto digit_val = decode_hex_digit ( digit ) ;
if ( digit_val = = 255 )
return { } ;
value = ( value < < 4 ) + digit_val ;
}
return value ;
}
2024-08-04 08:06:50 -07:00
static ErrorOr < ParsedValue > parse_value ( StringView spec )
2024-02-18 06:14:25 +03:30
{
constexpr auto is_sep = [ ] ( char c ) { return is_ascii_space ( c ) | | c = = ' : ' ; } ;
// Scalar: 'T.const[:\s]v' (i32.const 42)
auto parse_scalar = [ ] < typename T > ( StringView text ) - > ErrorOr < Wasm : : Value > {
if constexpr ( IsFloatingPoint < T > ) {
if ( text . trim_whitespace ( ) . equals_ignoring_ascii_case ( " nan " sv ) ) {
if constexpr ( IsSame < T , float > )
return Wasm : : Value { nanf ( " " ) } ;
else
return Wasm : : Value { nan ( " " ) } ;
}
if ( text . trim_whitespace ( ) . equals_ignoring_ascii_case ( " inf " sv ) ) {
if constexpr ( IsSame < T , float > )
return Wasm : : Value { HUGE_VALF } ;
else
return Wasm : : Value { HUGE_VAL } ;
}
}
if ( auto v = text . to_number < T > ( ) ; v . has_value ( ) )
return Wasm : : Value { * v } ;
return Error : : from_string_literal ( " Invalid scalar value " ) ;
} ;
// Vector: 'v128.const[:\s]v' (v128.const 0x01000000020000000300000004000000) or 'v(T.const[:\s]v, ...)' (v(i32.const 1, i32.const 2, i32.const 3, i32.const 4))
auto parse_u128 = [ ] ( StringView text ) - > ErrorOr < Wasm : : Value > {
u128 value ;
if ( text . starts_with ( " 0x " sv ) ) {
if ( auto v = convert_to_uint_from_hex ( text ) ; v . has_value ( ) )
value = * v ;
else
return Error : : from_string_literal ( " Invalid hex v128 value " ) ;
} else {
if ( auto v = convert_to_uint ( text ) ; v . has_value ( ) )
value = * v ;
else
return Error : : from_string_literal ( " Invalid v128 value " ) ;
}
return Wasm : : Value { value } ;
} ;
GenericLexer lexer ( spec ) ;
if ( lexer . consume_specific ( " v128.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
// The rest of the string is the value
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_u128 ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : V128 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " i8.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i8 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " i16.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i16 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " i32.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i32 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " i64.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i64 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I64 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " f32.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < float > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : F32 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " f64.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < double > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : F64 )
} ;
2024-02-18 06:14:25 +03:30
}
if ( lexer . consume_specific ( " v( " sv ) ) {
2024-08-04 08:06:50 -07:00
Vector < ParsedValue > values ;
2024-02-18 06:14:25 +03:30
for ( ; ; ) {
lexer . ignore_while ( is_sep ) ;
if ( lexer . consume_specific ( " ) " sv ) )
break ;
if ( lexer . is_eof ( ) ) {
warnln ( " Expected ')' to close vector " ) ;
break ;
}
auto value = parse_value ( lexer . consume_until ( is_any_of ( " ,) " sv ) ) ) ;
if ( value . is_error ( ) )
return value . release_error ( ) ;
lexer . consume_specific ( ' , ' ) ;
values . append ( value . release_value ( ) ) ;
}
if ( values . is_empty ( ) )
return Error : : from_string_literal ( " Empty vector " ) ;
2024-08-04 08:06:50 -07:00
auto element_type = values . first ( ) . type ;
2024-02-18 06:14:25 +03:30
for ( auto & value : values ) {
2024-08-04 08:06:50 -07:00
if ( value . type ! = element_type )
2024-02-18 06:14:25 +03:30
return Error : : from_string_literal ( " Mixed types in vector " ) ;
}
unsigned total_size = 0 ;
unsigned width = 0 ;
u128 result = 0 ;
u128 last_value = 0 ;
2024-08-04 08:06:50 -07:00
for ( auto & parsed : values ) {
2024-02-18 06:14:25 +03:30
if ( total_size > = 128 )
return Error : : from_string_literal ( " Vector too large " ) ;
2024-08-04 08:06:50 -07:00
switch ( parsed . type . kind ( ) ) {
case Wasm : : ValueType : : F32 :
case Wasm : : ValueType : : I32 :
width = sizeof ( u32 ) ;
break ;
case Wasm : : ValueType : : F64 :
case Wasm : : ValueType : : I64 :
width = sizeof ( u64 ) ;
break ;
case Wasm : : ValueType : : V128 :
case Wasm : : ValueType : : FunctionReference :
case Wasm : : ValueType : : ExternReference :
VERIFY_NOT_REACHED ( ) ;
}
last_value = parsed . value . value ( ) ;
2024-02-18 06:14:25 +03:30
result | = last_value < < total_size ;
total_size + = width * 8 ;
}
if ( total_size < 128 )
warnln ( " Vector value '{}' is only {} bytes wide, repeating last element " , spec , total_size ) ;
while ( total_size < 128 ) {
// Repeat the last value until we fill the 128 bits
result | = last_value < < total_size ;
total_size + = width * 8 ;
}
2024-08-04 08:06:50 -07:00
return ParsedValue {
. value = Wasm : : Value { result } ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : V128 )
} ;
2024-02-18 06:14:25 +03:30
}
return Error : : from_string_literal ( " Invalid value " ) ;
}
2024-08-22 01:13:37 +02:00
static RefPtr < Wasm : : Module > parse ( StringView filename )
2021-05-10 17:26:17 +04:30
{
2022-10-24 04:01:40 +03:30
auto result = Core : : MappedFile : : map ( filename ) ;
2021-05-10 17:26:17 +04:30
if ( result . is_error ( ) ) {
warnln ( " Failed to open {}: {} " , filename , result . error ( ) ) ;
return { } ;
}
2023-09-12 20:21:23 +02:00
auto parse_result = Wasm : : Module : : parse ( * result . value ( ) ) ;
2021-05-10 17:26:17 +04:30
if ( parse_result . is_error ( ) ) {
warnln ( " Something went wrong, either the file is invalid, or there's a bug with LibWasm! " ) ;
2023-12-16 17:49:34 +03:30
warnln ( " The parse error was {} " , Wasm : : parse_error_to_byte_string ( parse_result . error ( ) ) ) ;
2021-05-10 17:26:17 +04:30
return { } ;
}
return parse_result . release_value ( ) ;
}
2021-06-04 03:42:11 +04:30
static void print_link_error ( Wasm : : LinkError const & error )
2021-05-10 17:26:17 +04:30
{
2021-06-04 03:42:11 +04:30
for ( auto const & missing : error . missing_imports )
2021-05-10 17:26:17 +04:30
warnln ( " Missing import '{}' " , missing ) ;
}
2025-08-20 12:00:21 +02:00
template < typename T >
static ErrorOr < T , Wasm : : Result > trap_for_js_exception ( JS : : VM & vm , JS : : ThrowCompletionOr < T > const & result )
{
if ( ! result . is_error ( ) )
return result . value ( ) ;
auto const & completion = result . error ( ) ;
auto & exception = completion . value ( ) ;
warnln ( " JS exception: {} " , MUST ( exception . to_string ( vm ) ) ) ;
return Wasm : : Trap { ByteString ( " JS exception " ) } ;
}
2025-07-08 12:14:08 +02:00
ErrorOr < int > ladybird_main ( Main : : Arguments arguments )
2021-04-26 12:48:13 +04:30
{
2022-07-11 20:42:03 +00:00
StringView filename ;
2021-04-27 22:13:01 +04:30
bool print = false ;
2025-08-02 20:30:44 +02:00
bool print_compiled = false ;
2021-05-01 01:08:51 +04:30
bool attempt_instantiate = false ;
2021-06-04 03:33:10 +04:30
bool export_all_imports = false ;
2025-08-08 12:10:39 -07:00
[[maybe_unused]] bool wasi = false ;
2025-08-09 06:02:38 +02:00
Optional < u64 > specific_function_address ;
2023-12-16 17:49:34 +03:30
ByteString exported_function_to_execute ;
2024-08-04 08:06:50 -07:00
Vector < ParsedValue > values_to_push ;
2023-12-16 17:49:34 +03:30
Vector < ByteString > modules_to_link_in ;
2023-04-05 00:54:07 +03:30
Vector < StringView > args_if_wasi ;
Vector < StringView > wasi_preopened_mappings ;
2025-08-20 12:00:21 +02:00
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > js_exports ;
Wasm : : AbstractMachine machine ;
auto vm = JS : : VM : : create ( ) ;
auto root_execution_context = JS : : create_simple_execution_context < JS : : GlobalObject > ( * vm ) ;
auto & realm = * root_execution_context - > realm ;
2021-04-26 12:48:13 +04:30
Core : : ArgsParser parser ;
parser . add_positional_argument ( filename , " File name to parse " , " file " ) ;
2021-04-27 22:13:01 +04:30
parser . add_option ( print , " Print the parsed module " , " print " , ' p ' ) ;
2025-08-02 20:30:44 +02:00
parser . add_option ( print_compiled , " Print the compiled module " , " print-compiled " ) ;
2025-08-09 06:02:38 +02:00
parser . add_option ( specific_function_address , " Optional compiled function address to print " , " print-function " , ' f ' , " address " ) ;
2021-05-01 01:08:51 +04:30
parser . add_option ( attempt_instantiate , " Attempt to instantiate the module " , " instantiate " , ' i ' ) ;
2021-05-01 03:19:01 +04:30
parser . add_option ( exported_function_to_execute , " Attempt to execute the named exported function from the module (implies -i) " , " execute " , ' e ' , " name " ) ;
2024-04-21 08:34:56 +12:00
parser . add_option ( export_all_imports , " Export noop functions corresponding to imports " , " export-noop " ) ;
2025-08-08 12:10:39 -07:00
# if !defined(AK_OS_WINDOWS)
2023-04-05 00:54:07 +03:30
parser . add_option ( wasi , " Enable WASI " , " wasi " , ' w ' ) ;
2025-08-08 12:10:39 -07:00
# endif
2025-08-20 12:00:21 +02:00
parser . add_option ( Core : : ArgsParser : : Option {
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
. help_string = " Export js `function(arg...) { source }` returning T as [module].[function] " ,
. long_name = " export-js " ,
. short_name = 0 ,
. value_name = " module.function(arg:T...):T=source " ,
. accept_value = [ & ] ( StringView str ) {
GenericLexer lexer ( str ) ;
// [module] <.> [function] <(> {[name] <:> [type]} <)> (<:> [type])? <=> [text]
auto module = lexer . consume_until ( ' . ' ) ;
if ( ! lexer . consume_specific ( ' . ' ) ) {
warnln ( " Invalid JS export module in '{}' " , str ) ;
return false ;
}
auto fn_name = lexer . consume_until ( is_any_of ( " (=: " sv ) ) ;
struct Arg {
Wasm : : ValueType : : Kind type ;
StringView name ;
} ;
Vector < Arg > formal_params ;
if ( lexer . consume_specific ( ' ( ' ) ) {
while ( ! lexer . consume_specific ( ' ) ' ) ) {
auto name = lexer . consume_until ( is_any_of ( " ,:) " sv ) ) ;
if ( name . is_empty ( ) ) {
warnln ( " Invalid JS export argument name in '{}' " , str ) ;
return false ;
}
auto type = Wasm : : ValueType : : I32 ;
if ( lexer . consume_specific ( ' : ' ) ) {
if ( lexer . consume_specific ( " i32 " sv ) ) {
type = Wasm : : ValueType : : I32 ;
} else if ( lexer . consume_specific ( " i64 " sv ) ) {
type = Wasm : : ValueType : : I64 ;
} else if ( lexer . consume_specific ( " f32 " sv ) ) {
type = Wasm : : ValueType : : F32 ;
} else if ( lexer . consume_specific ( " f64 " sv ) ) {
type = Wasm : : ValueType : : F64 ;
} else if ( lexer . consume_specific ( " v128 " sv ) ) {
type = Wasm : : ValueType : : V128 ;
} else {
warnln ( " Invalid JS export argument type in '{}' " , str ) ;
return false ;
}
}
formal_params . append ( Arg { type , name } ) ;
lexer . consume_specific ( ' , ' ) ;
}
}
Vector < Wasm : : ValueType : : Kind > returns ;
if ( lexer . consume_specific ( ' : ' ) ) {
if ( lexer . consume_specific ( " i32 " sv ) ) {
returns . append ( Wasm : : ValueType : : I32 ) ;
} else if ( lexer . consume_specific ( " i64 " sv ) ) {
returns . append ( Wasm : : ValueType : : I64 ) ;
} else if ( lexer . consume_specific ( " f32 " sv ) ) {
returns . append ( Wasm : : ValueType : : F32 ) ;
} else if ( lexer . consume_specific ( " f64 " sv ) ) {
returns . append ( Wasm : : ValueType : : F64 ) ;
} else if ( lexer . consume_specific ( " v128 " sv ) ) {
returns . append ( Wasm : : ValueType : : V128 ) ;
} else {
warnln ( " Invalid JS export return type in '{}' " , str ) ;
return false ;
}
}
if ( ! lexer . consume_specific ( ' = ' ) | | lexer . is_eof ( ) ) {
warnln ( " Invalid JS export source in '{}' " , str ) ;
return false ;
}
auto source_text = lexer . consume_all ( ) . trim_whitespace ( ) ;
StringBuilder builder ;
builder . append ( " ( " sv ) ;
auto first = true ;
for ( auto & arg : formal_params ) {
if ( ! first )
builder . append ( " , " sv ) ;
first = false ;
builder . append ( arg . name ) ;
}
builder . appendff ( " ) => {} " , source_text ) ;
auto js_function = builder . to_byte_string ( ) ;
auto name = ByteString : : formatted ( " {}.{} " , module , fn_name ) ;
auto script = JS : : Script : : parse ( js_function , realm , name ) ;
if ( script . is_error ( ) ) {
warnln ( " Failed to parse JS export source '{}': " , js_function ) ;
return false ;
}
auto js_script = script . release_value ( ) ;
JS : : Bytecode : : Interpreter interp ( vm ) ;
auto maybe_function = interp . run ( * js_script ) ;
if ( maybe_function . is_error ( ) ) {
warnln ( " Failed to run JS export source '{}' " , js_function ) ;
return false ;
}
auto function_val = maybe_function . release_value ( ) ;
if ( ! function_val . is_function ( ) ) {
warnln ( " JS export source '{}' did not parse as a function " , js_function ) ;
return false ;
}
auto & function = function_val . as_function ( ) ;
Vector < Wasm : : ValueType > results ;
Vector < Wasm : : ValueType > params ;
for ( auto & type : returns )
results . append ( Wasm : : ValueType ( type ) ) ;
for ( auto & arg : formal_params )
params . append ( Wasm : : ValueType ( arg . type ) ) ;
Wasm : : FunctionType function_type = { move ( params ) , move ( results ) } ;
auto host_function = Wasm : : HostFunction {
[ & vm , & function , formal_params , returns , name ] ( Wasm : : Configuration & , Vector < Wasm : : Value > & args ) mutable - > Wasm : : Result {
Vector < JS : : Value > js_args ;
js_args . ensure_capacity ( args . size ( ) ) ;
for ( size_t i = 0 ; i < formal_params . size ( ) ; + + i ) {
auto type = formal_params [ i ] . type ;
if ( i > = args . size ( ) ) {
warnln ( " Not enough arguments provided to JS export function '{}' " , name ) ;
return Wasm : : Trap { ByteString ( " Not enough arguments " ) } ;
}
auto & arg = args [ i ] ;
switch ( type ) {
case Wasm : : ValueType : : I32 :
js_args . append ( JS : : Value ( arg . to < u32 > ( ) ) ) ;
break ;
case Wasm : : ValueType : : I64 :
js_args . append ( JS : : Value ( arg . to < u64 > ( ) ) ) ;
break ;
case Wasm : : ValueType : : F32 :
js_args . append ( JS : : Value ( arg . to < f32 > ( ) ) ) ;
break ;
case Wasm : : ValueType : : F64 :
js_args . append ( JS : : Value ( arg . to < f64 > ( ) ) ) ;
break ;
case Wasm : : ValueType : : V128 : {
auto value = arg . to < u128 > ( ) ;
ReadonlyBytes data { bit_cast < u8 const * > ( & value ) , sizeof ( u128 ) } ;
js_args . append ( vm - > heap ( ) . allocate < JS : : BigInt > ( Crypto : : SignedBigInteger { Crypto : : UnsignedBigInteger { data } } ) ) ;
break ;
}
default :
warnln ( " Unsupported argument type '{}' for JS export function '{}' " , Wasm : : ValueType : : kind_name ( type ) , name ) ;
return Wasm : : Trap { ByteString ( " Unsupported argument type " ) } ;
}
}
auto result = TRY ( trap_for_js_exception ( vm , JS : : call ( vm , function , JS : : js_null ( ) , js_args . span ( ) ) ) ) ;
if ( returns . is_empty ( ) )
return Wasm : : Result { Vector < Wasm : : Value > { } } ;
if ( returns . size ( ) ! = 1 )
return Wasm : : Trap { ByteString ( " NYI " ) } ;
switch ( returns [ 0 ] ) {
case Wasm : : ValueType : : I32 :
return Wasm : : Result { Vector < Wasm : : Value > { Wasm : : Value { TRY ( trap_for_js_exception ( * vm , result . to_u32 ( vm ) ) ) } } } ;
case Wasm : : ValueType : : I64 :
return Wasm : : Result { Vector < Wasm : : Value > { Wasm : : Value { TRY ( trap_for_js_exception ( * vm , result . to_bigint_uint64 ( vm ) ) ) } } } ;
case Wasm : : ValueType : : F32 :
return Wasm : : Result { Vector < Wasm : : Value > { Wasm : : Value { static_cast < f32 > ( TRY ( trap_for_js_exception ( * vm , result . to_double ( vm ) ) ) ) } } } ;
case Wasm : : ValueType : : F64 :
return Wasm : : Result { Vector < Wasm : : Value > { Wasm : : Value { TRY ( trap_for_js_exception ( * vm , result . to_double ( vm ) ) ) } } } ;
case Wasm : : ValueType : : V128 : {
auto value = TRY ( trap_for_js_exception ( * vm , result . to_bigint ( vm ) ) ) ;
u128 out { } ;
Bytes data { bit_cast < u8 * > ( & out ) , sizeof ( u128 ) } ;
if ( value - > big_integer ( ) . unsigned_value ( ) . export_data ( data ) . size ( ) ! = data . size ( ) ) {
dbgln ( " JS export function '{}' returned a v128 value that is not 128 bits wide " , name ) ;
return Wasm : : Trap { ByteString ( " Invalid v128 value " ) } ;
}
return Wasm : : Result { Vector < Wasm : : Value > { Wasm : : Value { out } } } ;
}
default :
warnln ( " Unsupported return type for JS export function '{}' " , name ) ;
return Wasm : : Trap { ByteString ( " Unsupported return type " ) } ;
}
} ,
function_type ,
name ,
} ;
auto host_function_instance = machine . store ( ) . allocate ( move ( host_function ) ) ;
if ( ! host_function_instance . has_value ( ) ) {
warnln ( " Failed to allocate host function instance for '{}' " , name ) ;
return false ;
}
js_exports . set ( { . module = module , . name = fn_name , . type = function_type } , * host_function_instance ) ;
return true ;
} ,
} ) ;
2023-04-05 00:54:07 +03:30
parser . add_option ( Core : : ArgsParser : : Option {
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
. help_string = " Directory mappings to expose via WASI " ,
. long_name = " wasi-map-dir " ,
. short_name = 0 ,
. value_name = " path[:path] " ,
. accept_value = [ & ] ( StringView str ) {
if ( ! str . is_empty ( ) ) {
wasi_preopened_mappings . append ( str ) ;
return true ;
}
return false ;
} ,
} ) ;
2021-05-10 17:26:17 +04:30
parser . add_option ( Core : : ArgsParser : : Option {
2022-07-12 22:13:38 +02:00
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
2021-05-10 17:26:17 +04:30
. help_string = " Extra modules to link with, use to resolve imports " ,
. long_name = " link " ,
. short_name = ' l ' ,
. value_name = " file " ,
2023-02-21 15:14:41 +03:30
. accept_value = [ & ] ( StringView str ) {
if ( ! str . is_empty ( ) ) {
modules_to_link_in . append ( str ) ;
2021-05-10 17:26:17 +04:30
return true ;
}
return false ;
} ,
} ) ;
2021-05-01 03:19:01 +04:30
parser . add_option ( Core : : ArgsParser : : Option {
2022-07-12 22:13:38 +02:00
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
2024-02-18 06:14:25 +03:30
. help_string = " Supply arguments to the function (default=0) (T.const:v or v(T.const:v, ...)) " ,
2021-05-01 03:19:01 +04:30
. long_name = " arg " ,
. short_name = 0 ,
2024-02-18 06:14:25 +03:30
. value_name = " value " ,
2023-02-21 15:14:41 +03:30
. accept_value = [ & ] ( StringView str ) - > bool {
2024-02-18 06:14:25 +03:30
auto result = parse_value ( str ) ;
if ( result . is_error ( ) ) {
warnln ( " Failed to parse value: {} " , result . error ( ) ) ;
return false ;
2021-05-01 03:19:01 +04:30
}
2024-02-18 06:14:25 +03:30
values_to_push . append ( result . release_value ( ) ) ;
return true ;
2021-05-01 03:19:01 +04:30
} ,
} ) ;
2023-04-05 00:54:07 +03:30
parser . add_positional_argument ( args_if_wasi , " Arguments to pass to the WASI module " , " args " , Core : : ArgsParser : : Required : : No ) ;
2022-01-08 18:41:38 +01:00
parser . parse ( arguments ) ;
2021-04-26 12:48:13 +04:30
2021-05-01 03:19:01 +04:30
if ( ! exported_function_to_execute . is_empty ( ) )
2021-05-01 01:08:51 +04:30
attempt_instantiate = true ;
2021-05-10 17:26:17 +04:30
auto parse_result = parse ( filename ) ;
2024-08-22 01:13:37 +02:00
if ( parse_result . is_null ( ) )
2021-06-04 03:33:10 +04:30
return 1 ;
2023-02-09 03:02:46 +01:00
g_stdout = TRY ( Core : : File : : standard_output ( ) ) ;
2023-01-20 00:37:03 +01:00
g_printer = TRY ( try_make < Wasm : : Printer > ( * g_stdout ) ) ;
2021-05-01 01:08:51 +04:30
if ( print & & ! attempt_instantiate ) {
2023-01-20 00:37:03 +01:00
Wasm : : Printer printer ( * g_stdout ) ;
2024-08-22 01:13:37 +02:00
printer . print ( * parse_result ) ;
2021-04-27 22:13:01 +04:30
}
2025-08-02 20:30:44 +02:00
if ( attempt_instantiate | | print_compiled ) {
2025-08-08 12:10:39 -07:00
# if !defined(AK_OS_WINDOWS)
2023-04-05 00:54:07 +03:30
Optional < Wasm : : Wasi : : Implementation > wasi_impl ;
if ( wasi ) {
wasi_impl . emplace ( Wasm : : Wasi : : Implementation : : Details {
. provide_arguments = [ & ] {
Vector < String > strings ;
for ( auto & string : args_if_wasi )
strings . append ( String : : from_utf8 ( string ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
return strings ; } ,
. provide_environment = { } ,
. provide_preopened_directories = [ & ] {
Vector < Wasm : : Wasi : : Implementation : : MappedPath > paths ;
for ( auto & string : wasi_preopened_mappings ) {
auto split_index = string . find ( ' : ' ) ;
if ( split_index . has_value ( ) ) {
2024-01-15 16:23:24 +00:00
LexicalPath host_path { FileSystem : : real_path ( string . substring_view ( 0 , * split_index ) ) . release_value_but_fixme_should_propagate_errors ( ) } ;
2023-04-05 00:54:07 +03:30
LexicalPath mapped_path { string . substring_view ( * split_index + 1 ) } ;
paths . append ( { move ( host_path ) , move ( mapped_path ) } ) ;
} else {
2024-01-15 16:23:24 +00:00
LexicalPath host_path { FileSystem : : real_path ( string ) . release_value_but_fixme_should_propagate_errors ( ) } ;
2023-04-05 00:54:07 +03:30
LexicalPath mapped_path { string } ;
paths . append ( { move ( host_path ) , move ( mapped_path ) } ) ;
}
}
return paths ; } ,
} ) ;
}
2025-08-08 12:10:39 -07:00
# endif
2023-04-05 00:54:07 +03:30
2021-05-21 21:10:44 +04:30
Core : : EventLoop main_loop ;
2021-05-10 17:26:17 +04:30
// First, resolve the linked modules
2023-03-06 17:16:25 +01:00
Vector < NonnullOwnPtr < Wasm : : ModuleInstance > > linked_instances ;
2024-08-22 01:13:37 +02:00
Vector < NonnullRefPtr < Wasm : : Module > > linked_modules ;
2021-05-10 17:26:17 +04:30
for ( auto & name : modules_to_link_in ) {
auto parse_result = parse ( name ) ;
2024-08-22 01:13:37 +02:00
if ( parse_result . is_null ( ) ) {
2021-05-10 17:26:17 +04:30
warnln ( " Failed to parse linked module '{}' " , name ) ;
return 1 ;
}
2024-08-22 01:13:37 +02:00
linked_modules . append ( parse_result . release_nonnull ( ) ) ;
2021-05-10 17:26:17 +04:30
Wasm : : Linker linker { linked_modules . last ( ) } ;
for ( auto & instance : linked_instances )
2023-03-06 17:16:25 +01:00
linker . link ( * instance ) ;
2021-05-10 17:26:17 +04:30
auto link_result = linker . finish ( ) ;
if ( link_result . is_error ( ) ) {
warnln ( " Linking imported module '{}' failed " , name ) ;
print_link_error ( link_result . error ( ) ) ;
return 1 ;
}
auto instantiation_result = machine . instantiate ( linked_modules . last ( ) , link_result . release_value ( ) ) ;
if ( instantiation_result . is_error ( ) ) {
warnln ( " Instantiation of imported module '{}' failed: {} " , name , instantiation_result . error ( ) . error ) ;
return 1 ;
}
linked_instances . append ( instantiation_result . release_value ( ) ) ;
}
2024-08-22 01:13:37 +02:00
Wasm : : Linker linker { * parse_result } ;
2021-05-10 17:26:17 +04:30
for ( auto & instance : linked_instances )
2023-03-06 17:16:25 +01:00
linker . link ( * instance ) ;
2021-06-04 03:33:10 +04:30
2025-08-08 12:10:39 -07:00
# if !defined(AK_OS_WINDOWS)
2023-04-05 00:54:07 +03:30
if ( wasi ) {
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > wasi_exports ;
for ( auto & entry : linker . unresolved_imports ( ) ) {
if ( entry . module ! = " wasi_snapshot_preview1 " sv )
continue ;
auto function = wasi_impl - > function_by_name ( entry . name ) ;
if ( function . is_error ( ) ) {
dbgln ( " wasi function {} not implemented :( " , entry . name ) ;
continue ;
}
auto address = machine . store ( ) . allocate ( function . release_value ( ) ) ;
wasi_exports . set ( entry , * address ) ;
}
linker . link ( wasi_exports ) ;
}
2025-08-08 12:10:39 -07:00
# endif
2023-04-05 00:54:07 +03:30
2025-08-20 12:00:21 +02:00
linker . link ( js_exports ) ;
2021-06-04 03:33:10 +04:30
if ( export_all_imports ) {
HashMap < Wasm : : Linker : : Name , Wasm : : ExternValue > exports ;
for ( auto & entry : linker . unresolved_imports ( ) ) {
if ( ! entry . type . has < Wasm : : TypeIndex > ( ) )
continue ;
2024-08-22 01:13:37 +02:00
auto type = parse_result - > type_section ( ) . types ( ) [ entry . type . get < Wasm : : TypeIndex > ( ) . value ( ) ] ;
2021-06-04 03:33:10 +04:30
auto address = machine . store ( ) . allocate ( Wasm : : HostFunction (
[ name = entry . name , type = type ] ( auto & , auto & arguments ) - > Wasm : : Result {
StringBuilder argument_builder ;
bool first = true ;
2024-08-04 08:06:50 -07:00
size_t index = 0 ;
2021-06-04 03:33:10 +04:30
for ( auto & argument : arguments ) {
2023-01-25 20:19:05 +01:00
AllocatingMemoryStream stream ;
2024-08-04 08:06:50 -07:00
auto value_type = type . parameters ( ) [ index ] ;
Wasm : : Printer { stream } . print ( argument , value_type ) ;
2021-06-04 03:33:10 +04:30
if ( first )
first = false ;
else
argument_builder . append ( " , " sv ) ;
2023-01-09 12:06:13 +01:00
auto buffer = ByteBuffer : : create_uninitialized ( stream . used_buffer_size ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-03-01 15:27:35 +01:00
stream . read_until_filled ( buffer ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-09-10 23:43:39 +02:00
argument_builder . append ( StringView ( buffer ) . trim_whitespace ( ) ) ;
2024-08-04 08:06:50 -07:00
+ + index ;
2021-06-04 03:33:10 +04:30
}
2023-12-16 17:49:34 +03:30
dbgln ( " [wasm runtime] Stub function {} was called with the following arguments: {} " , name , argument_builder . to_byte_string ( ) ) ;
2021-06-04 03:33:10 +04:30
Vector < Wasm : : Value > result ;
result . ensure_capacity ( type . results ( ) . size ( ) ) ;
2024-08-17 15:40:21 -07:00
for ( auto expect_result : type . results ( ) )
result . append ( Wasm : : Value ( expect_result ) ) ;
2021-06-04 03:33:10 +04:30
return Wasm : : Result { move ( result ) } ;
} ,
2024-07-08 18:09:00 -07:00
type ,
entry . name ) ) ;
2021-06-04 03:33:10 +04:30
exports . set ( entry , * address ) ;
}
linker . link ( exports ) ;
}
2021-05-10 17:26:17 +04:30
auto link_result = linker . finish ( ) ;
if ( link_result . is_error ( ) ) {
warnln ( " Linking main module failed " ) ;
print_link_error ( link_result . error ( ) ) ;
return 1 ;
}
2025-08-02 20:30:44 +02:00
2024-08-22 01:13:37 +02:00
auto result = machine . instantiate ( * parse_result , link_result . release_value ( ) ) ;
2021-05-01 01:08:51 +04:30
if ( result . is_error ( ) ) {
warnln ( " Module instantiation failed: {} " , result . error ( ) . error ) ;
return 1 ;
}
2021-05-10 15:40:49 +04:30
auto module_instance = result . release_value ( ) ;
2021-05-01 01:08:51 +04:30
2025-08-02 20:30:44 +02:00
if ( print_compiled ) {
2025-08-09 06:02:38 +02:00
Span < Wasm : : FunctionAddress const > functions = module_instance - > functions ( ) ;
Wasm : : FunctionAddress spec = specific_function_address . value_or ( 0 ) ;
if ( specific_function_address . has_value ( ) )
functions = { & spec , 1 } ;
for ( auto address : functions ) {
2025-08-02 20:30:44 +02:00
auto function = machine . store ( ) . get ( address ) - > get_pointer < Wasm : : WasmFunction > ( ) ;
if ( ! function )
continue ;
auto & expression = function - > code ( ) . func ( ) . body ( ) ;
if ( expression . compiled_instructions . dispatches . is_empty ( ) )
continue ;
ByteString export_name ;
for ( auto & entry : function - > module ( ) . exports ( ) ) {
if ( entry . value ( ) = = address ) {
export_name = ByteString : : formatted ( " '{}' " , entry . name ( ) ) ;
break ;
}
}
TRY ( g_stdout - > write_until_depleted ( ByteString : : formatted ( " Function #{}{} (stack usage = {}): \n " , address . value ( ) , export_name , expression . stack_usage_hint ( ) ) ) ) ;
Wasm : : Printer printer { * g_stdout , 1 } ;
for ( size_t ip = 0 ; ip < expression . compiled_instructions . dispatches . size ( ) ; + + ip ) {
auto & dispatch = expression . compiled_instructions . dispatches [ ip ] ;
ByteString regs ;
auto first = true ;
ssize_t in_count = 0 ;
bool has_out = false ;
# define M(name, _, ins, outs) \
case Wasm : : Instructions : : name . value ( ) : \
in_count = ins ; \
has_out = outs ! = 0 ; \
break ;
switch ( dispatch . instruction - > opcode ( ) . value ( ) ) {
ENUMERATE_WASM_OPCODES ( M )
}
# undef M
constexpr auto reg_name = [ ] ( Wasm : : Dispatch : : RegisterOrStack reg ) - > ByteString {
if ( reg = = Wasm : : Dispatch : : RegisterOrStack : : Stack )
return " stack " sv ;
return ByteString : : formatted ( " reg{} " , to_underlying ( reg ) ) ;
} ;
if ( in_count > - 1 ) {
for ( ssize_t index = 0 ; index < in_count ; + + index ) {
if ( first )
regs = ByteString : : formatted ( " {} ({} " , regs , reg_name ( dispatch . sources [ index ] ) ) ;
else
regs = ByteString : : formatted ( " {}, {} " , regs , reg_name ( dispatch . sources [ index ] ) ) ;
first = false ;
}
if ( has_out ) {
if ( first )
regs = ByteString : : formatted ( " () -> {} " , reg_name ( dispatch . destination ) ) ;
else
regs = ByteString : : formatted ( " {}) -> {} " , regs , reg_name ( dispatch . destination ) ) ;
} else {
if ( first )
regs = ByteString : : formatted ( " () -x " ) ;
else
regs = ByteString : : formatted ( " {}) -x " , regs ) ;
}
}
if ( ! regs . is_empty ( ) )
regs = ByteString : : formatted ( " {{{:<33} }} " , regs ) ;
TRY ( g_stdout - > write_until_depleted ( ByteString : : formatted ( " [{:>03}] " , ip ) ) ) ;
TRY ( g_stdout - > write_until_depleted ( regs . bytes ( ) ) ) ;
printer . print ( * dispatch . instruction ) ;
}
TRY ( g_stdout - > write_until_depleted ( " \n " sv . bytes ( ) ) ) ;
}
}
2021-06-04 03:42:11 +04:30
auto print_func = [ & ] ( auto const & address ) {
2021-05-01 01:08:51 +04:30
Wasm : : FunctionInstance * fn = machine . store ( ) . get ( address ) ;
2024-04-03 21:44:40 -04:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " - Function with address {}, ptr = {} \n " , address . value ( ) , fn ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-05-01 01:08:51 +04:30
if ( fn ) {
2024-04-03 21:44:40 -04:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " wasm function? {} \n " , fn - > has < Wasm : : WasmFunction > ( ) ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-05-01 01:08:51 +04:30
fn - > visit (
2021-06-04 03:42:11 +04:30
[ & ] ( Wasm : : WasmFunction const & func ) {
2023-01-20 00:37:03 +01:00
Wasm : : Printer printer { * g_stdout , 3 } ;
2024-04-03 21:44:40 -04:00
g_stdout - > write_until_depleted ( " type: \n " sv ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-05-01 01:08:51 +04:30
printer . print ( func . type ( ) ) ;
2024-04-03 21:44:40 -04:00
g_stdout - > write_until_depleted ( " code: \n " sv ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-05-01 01:08:51 +04:30
printer . print ( func . code ( ) ) ;
} ,
2021-06-04 03:42:11 +04:30
[ ] ( Wasm : : HostFunction const & ) { } ) ;
2021-05-01 01:08:51 +04:30
}
} ;
if ( print ) {
// Now, let's dump the functions!
2021-05-11 04:44:59 +04:30
for ( auto & address : module_instance - > functions ( ) ) {
2021-05-01 01:08:51 +04:30
print_func ( address ) ;
}
}
2021-05-01 03:19:01 +04:30
if ( ! exported_function_to_execute . is_empty ( ) ) {
2021-05-01 01:08:51 +04:30
Optional < Wasm : : FunctionAddress > run_address ;
Vector < Wasm : : Value > values ;
2021-05-11 04:44:59 +04:30
for ( auto & entry : module_instance - > exports ( ) ) {
2021-05-01 03:19:01 +04:30
if ( entry . name ( ) = = exported_function_to_execute ) {
if ( auto addr = entry . value ( ) . get_pointer < Wasm : : FunctionAddress > ( ) )
run_address = * addr ;
2021-05-01 01:08:51 +04:30
}
}
if ( ! run_address . has_value ( ) ) {
2021-05-01 03:19:01 +04:30
warnln ( " No such exported function, sorry :( " ) ;
return 1 ;
}
auto instance = machine . store ( ) . get ( * run_address ) ;
VERIFY ( instance ) ;
if ( instance - > has < Wasm : : HostFunction > ( ) ) {
warnln ( " Exported function is a host function, cannot run that yet " ) ;
2021-05-01 01:08:51 +04:30
return 1 ;
}
2021-05-01 03:19:01 +04:30
for ( auto & param : instance - > get < Wasm : : WasmFunction > ( ) . type ( ) . parameters ( ) ) {
2024-02-18 06:14:25 +03:30
if ( values_to_push . is_empty ( ) ) {
2024-08-17 15:40:21 -07:00
values . append ( Wasm : : Value ( param ) ) ;
2024-08-04 08:06:50 -07:00
} else if ( param = = values_to_push . last ( ) . type ) {
values . append ( values_to_push . take_last ( ) . value ) ;
2024-02-18 06:14:25 +03:30
} else {
2024-08-04 08:06:50 -07:00
warnln ( " Type mismatch in argument: expected {}, but got {} " , Wasm : : ValueType : : kind_name ( param . kind ( ) ) , Wasm : : ValueType : : kind_name ( values_to_push . last ( ) . type . kind ( ) ) ) ;
2024-02-18 06:14:25 +03:30
return 1 ;
}
2021-05-01 03:19:01 +04:30
}
if ( print ) {
outln ( " Executing " ) ;
print_func ( * run_address ) ;
outln ( ) ;
}
2021-05-01 01:08:51 +04:30
2025-04-22 09:48:26 +02:00
auto result = machine . invoke ( g_interpreter , run_address . value ( ) , move ( values ) ) ;
2021-07-13 00:36:50 +04:30
if ( result . is_trap ( ) ) {
2025-04-22 09:48:26 +02:00
auto trap_reason = result . trap ( ) . format ( ) ;
if ( trap_reason . starts_with ( " exit: " sv ) )
return - trap_reason . substring_view ( 5 ) . to_number < i32 > ( ) . value_or ( - 1 ) ;
warnln ( " Execution trapped: {} " , trap_reason ) ;
2021-07-13 00:36:50 +04:30
} else {
if ( ! result . values ( ) . is_empty ( ) )
warnln ( " Returned: " ) ;
2024-08-04 08:06:50 -07:00
auto result_type = instance - > get < Wasm : : WasmFunction > ( ) . type ( ) . results ( ) ;
size_t index = 0 ;
2021-07-13 00:36:50 +04:30
for ( auto & value : result . values ( ) ) {
2023-03-01 17:24:50 +01:00
g_stdout - > write_until_depleted ( " -> " sv . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2024-08-04 08:06:50 -07:00
g_printer - > print ( value , result_type [ index ] ) ;
+ + index ;
2021-07-13 00:36:50 +04:30
}
2021-05-01 01:08:51 +04:30
}
}
}
2021-04-26 12:48:13 +04:30
return 0 ;
}