2021-04-26 08:18:13 +00:00
/*
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
2022-01-08 17:41:38 +00:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-04-26 08:18:13 +00:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-02-18 02:44:25 +00:00
# include <AK/GenericLexer.h>
# include <AK/Hex.h>
2023-01-25 19:19:05 +00:00
# include <AK/MemoryStream.h>
2023-05-28 10:13:43 +00:00
# include <AK/StackInfo.h>
2021-04-26 08:18:13 +00:00
# include <LibCore/ArgsParser.h>
2023-02-09 02:02:46 +00:00
# include <LibCore/File.h>
2022-10-24 00:31:40 +00:00
# include <LibCore/MappedFile.h>
2023-05-20 08:39:14 +00:00
# include <LibFileSystem/FileSystem.h>
2021-05-21 16:40:44 +00:00
# include <LibLine/Editor.h>
2022-01-08 17:41:38 +00:00
# include <LibMain/Main.h>
2021-04-30 20:38:51 +00:00
# include <LibWasm/AbstractMachine/AbstractMachine.h>
2021-06-04 11:54:20 +00:00
# include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
2021-04-27 17:43:01 +00:00
# include <LibWasm/Printer/Printer.h>
2021-04-26 08:18:13 +00:00
# include <LibWasm/Types.h>
2023-04-04 21:24:07 +00:00
# include <LibWasm/Wasi.h>
2021-05-21 16:40:44 +00:00
# include <signal.h>
# include <unistd.h>
RefPtr < Line : : Editor > g_line_editor ;
2023-02-10 00:00:18 +00:00
static OwnPtr < Stream > g_stdout { } ;
2023-01-19 23:37:03 +00:00
static OwnPtr < Wasm : : Printer > g_printer { } ;
2021-05-21 16:40:44 +00:00
static bool g_continue { false } ;
static void ( * old_signal ) ( int ) ;
2023-05-28 10:13:43 +00:00
static StackInfo g_stack_info ;
static Wasm : : DebuggerBytecodeInterpreter g_interpreter ( g_stack_info ) ;
2021-05-21 16:40:44 +00:00
2024-08-04 15:06:50 +00:00
struct ParsedValue {
Wasm : : Value value ;
Wasm : : ValueType type ;
} ;
2021-05-21 16:40:44 +00:00
static void sigint_handler ( int )
{
if ( ! g_continue ) {
signal ( SIGINT , old_signal ) ;
kill ( getpid ( ) , SIGINT ) ;
}
g_continue = false ;
}
2024-02-18 02:44:25 +00:00
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 15:06:50 +00:00
static ErrorOr < ParsedValue > parse_value ( StringView spec )
2024-02-18 02:44:25 +00:00
{
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 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_u128 ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : V128 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " i8.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i8 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " i16.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i16 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " i32.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i32 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I32 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " i64.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < i64 > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : I64 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " f32.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < float > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : F32 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " f64.const " sv ) ) {
lexer . ignore_while ( is_sep ) ;
auto text = lexer . consume_all ( ) ;
2024-08-04 15:06:50 +00:00
return ParsedValue {
. value = TRY ( parse_scalar . operator ( ) < double > ( text ) ) ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : F64 )
} ;
2024-02-18 02:44:25 +00:00
}
if ( lexer . consume_specific ( " v( " sv ) ) {
2024-08-04 15:06:50 +00:00
Vector < ParsedValue > values ;
2024-02-18 02:44:25 +00:00
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 15:06:50 +00:00
auto element_type = values . first ( ) . type ;
2024-02-18 02:44:25 +00:00
for ( auto & value : values ) {
2024-08-04 15:06:50 +00:00
if ( value . type ! = element_type )
2024-02-18 02:44:25 +00:00
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 15:06:50 +00:00
for ( auto & parsed : values ) {
2024-02-18 02:44:25 +00:00
if ( total_size > = 128 )
return Error : : from_string_literal ( " Vector too large " ) ;
2024-08-04 15:06:50 +00: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 02:44:25 +00:00
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 15:06:50 +00:00
return ParsedValue {
. value = Wasm : : Value { result } ,
. type = Wasm : : ValueType ( Wasm : : ValueType : : Kind : : V128 )
} ;
2024-02-18 02:44:25 +00:00
}
return Error : : from_string_literal ( " Invalid value " ) ;
}
2021-06-03 23:12:11 +00:00
static bool post_interpret_hook ( Wasm : : Configuration & , Wasm : : InstructionPointer & ip , Wasm : : Instruction const & instr , Wasm : : Interpreter const & interpreter )
2021-05-21 16:40:44 +00:00
{
if ( interpreter . did_trap ( ) ) {
g_continue = false ;
2021-06-01 15:38:24 +00:00
warnln ( " Trapped when executing ip={} " , ip ) ;
2023-01-19 23:37:03 +00:00
g_printer - > print ( instr ) ;
2021-07-01 12:33:17 +00:00
warnln ( " Trap reason: {} " , interpreter . trap_reason ( ) ) ;
const_cast < Wasm : : Interpreter & > ( interpreter ) . clear_trap ( ) ;
2021-05-21 16:40:44 +00:00
}
return true ;
}
2021-06-03 23:12:11 +00:00
static bool pre_interpret_hook ( Wasm : : Configuration & config , Wasm : : InstructionPointer & ip , Wasm : : Instruction const & instr )
2021-05-21 16:40:44 +00:00
{
static bool always_print_stack = false ;
static bool always_print_instruction = false ;
if ( always_print_stack )
config . dump_stack ( ) ;
if ( always_print_instruction ) {
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " {:0>4} " , ip . value ( ) ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-01-19 23:37:03 +00:00
g_printer - > print ( instr ) ;
2021-05-21 16:40:44 +00:00
}
if ( g_continue )
return true ;
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " {:0>4} " , ip . value ( ) ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-01-19 23:37:03 +00:00
g_printer - > print ( instr ) ;
2023-12-16 14:19:34 +00:00
ByteString last_command = " " ;
2021-05-21 16:40:44 +00:00
for ( ; ; ) {
auto result = g_line_editor - > get_line ( " > " ) ;
if ( result . is_error ( ) ) {
return false ;
}
auto str = result . release_value ( ) ;
g_line_editor - > add_to_history ( str ) ;
if ( str . is_empty ( ) )
str = last_command ;
else
last_command = str ;
auto args = str . split_view ( ' ' ) ;
if ( args . is_empty ( ) )
continue ;
auto & cmd = args [ 0 ] ;
2021-06-09 12:13:47 +00:00
if ( cmd . is_one_of ( " h " , " help " ) ) {
warnln ( " Wasm shell commands " ) ;
warnln ( " Toplevel: " ) ;
warnln ( " - [s]tep Run one instruction " ) ;
warnln ( " - next Alias for step " ) ;
warnln ( " - [c]ontinue Execute until a trap or the program exit point " ) ;
warnln ( " - [p]rint <args...> Print various things (see section on print) " ) ;
warnln ( " - call <fn> <args...> Call the function <fn> with the given arguments " ) ;
warnln ( " - set <args...> Set shell option (see section on settings) " ) ;
warnln ( " - unset <args...> Unset shell option (see section on settings) " ) ;
warnln ( " - [h]elp Print this help " ) ;
warnln ( ) ;
warnln ( " Print: " ) ;
warnln ( " - print [s]tack Print the contents of the stack, including frames and labels " ) ;
warnln ( " - print [[m]em]ory <index> Print the contents of the memory identified by <index> " ) ;
warnln ( " - print [[i]nstr]uction Print the current instruction " ) ;
warnln ( " - print [[f]unc]tion <index> Print the function identified by <index> " ) ;
warnln ( ) ;
warnln ( " Settings: " ) ;
warnln ( " - set print stack Make the shell print the stack on every instruction executed " ) ;
warnln ( " - set print [instr]uction Make the shell print the instruction that will be executed next " ) ;
warnln ( ) ;
continue ;
}
2021-05-21 16:40:44 +00:00
if ( cmd . is_one_of ( " s " , " step " , " next " ) ) {
return true ;
}
if ( cmd . is_one_of ( " p " , " print " ) ) {
if ( args . size ( ) < 2 ) {
warnln ( " Print what? " ) ;
continue ;
}
auto & what = args [ 1 ] ;
if ( what . is_one_of ( " s " , " stack " ) ) {
config . dump_stack ( ) ;
continue ;
}
if ( what . is_one_of ( " m " , " mem " , " memory " ) ) {
if ( args . size ( ) < 3 ) {
warnln ( " print what memory? " ) ;
continue ;
}
2023-12-23 02:59:14 +00:00
auto value = args [ 2 ] . to_number < u64 > ( ) ;
2021-05-21 16:40:44 +00:00
if ( ! value . has_value ( ) ) {
warnln ( " invalid memory index {} " , args [ 2 ] ) ;
continue ;
}
auto mem = config . store ( ) . get ( Wasm : : MemoryAddress ( value . value ( ) ) ) ;
if ( ! mem ) {
warnln ( " invalid memory index {} (not found) " , args [ 2 ] ) ;
continue ;
}
2021-06-17 08:42:58 +00:00
warnln ( " {:>32hex-dump} " , mem - > data ( ) . bytes ( ) ) ;
2021-05-21 16:40:44 +00:00
continue ;
}
if ( what . is_one_of ( " i " , " instr " , " instruction " ) ) {
2023-01-19 23:37:03 +00:00
g_printer - > print ( instr ) ;
2021-05-21 16:40:44 +00:00
continue ;
}
if ( what . is_one_of ( " f " , " func " , " function " ) ) {
if ( args . size ( ) < 3 ) {
warnln ( " print what function? " ) ;
continue ;
}
2023-12-23 02:59:14 +00:00
auto value = args [ 2 ] . to_number < u64 > ( ) ;
2021-05-21 16:40:44 +00:00
if ( ! value . has_value ( ) ) {
warnln ( " invalid function index {} " , args [ 2 ] ) ;
continue ;
}
auto fn = config . store ( ) . get ( Wasm : : FunctionAddress ( value . value ( ) ) ) ;
if ( ! fn ) {
warnln ( " invalid function index {} (not found) " , args [ 2 ] ) ;
continue ;
}
if ( auto * fn_value = fn - > get_pointer < Wasm : : HostFunction > ( ) ) {
warnln ( " Host function at {:p} " , & fn_value - > function ( ) ) ;
continue ;
}
if ( auto * fn_value = fn - > get_pointer < Wasm : : WasmFunction > ( ) ) {
2023-01-19 23:37:03 +00:00
g_printer - > print ( fn_value - > code ( ) ) ;
2021-05-21 16:40:44 +00:00
continue ;
}
}
}
if ( cmd = = " call " sv ) {
if ( args . size ( ) < 2 ) {
warnln ( " call what? " ) ;
continue ;
}
Optional < Wasm : : FunctionAddress > address ;
2023-12-23 02:59:14 +00:00
auto index = args [ 1 ] . to_number < u64 > ( ) ;
2021-05-21 16:40:44 +00:00
if ( index . has_value ( ) ) {
2021-05-23 17:12:19 +00:00
address = config . frame ( ) . module ( ) . functions ( ) [ index . value ( ) ] ;
2021-05-21 16:40:44 +00:00
} else {
auto & name = args [ 1 ] ;
2021-05-23 17:12:19 +00:00
for ( auto & export_ : config . frame ( ) . module ( ) . exports ( ) ) {
2021-05-21 16:40:44 +00:00
if ( export_ . name ( ) = = name ) {
if ( auto addr = export_ . value ( ) . get_pointer < Wasm : : FunctionAddress > ( ) ) {
address = * addr ;
break ;
}
}
}
}
if ( ! address . has_value ( ) ) {
failed_to_find : ;
warnln ( " Could not find a function {} " , args [ 1 ] ) ;
continue ;
}
auto fn = config . store ( ) . get ( * address ) ;
if ( ! fn )
goto failed_to_find ;
auto type = fn - > visit ( [ & ] ( auto & value ) { return value . type ( ) ; } ) ;
if ( type . parameters ( ) . size ( ) + 2 ! = args . size ( ) ) {
warnln ( " Expected {} arguments for call, but found only {} " , type . parameters ( ) . size ( ) , args . size ( ) - 2 ) ;
continue ;
}
2024-08-04 15:06:50 +00:00
Vector < ParsedValue > values_to_push ;
2021-05-21 16:40:44 +00:00
Vector < Wasm : : Value > values ;
2024-02-18 02:44:25 +00:00
auto ok = true ;
for ( size_t index = 2 ; index < args . size ( ) ; + + index ) {
auto r = parse_value ( args [ index ] ) ;
if ( r . is_error ( ) ) {
warnln ( " Failed to parse argument {}: {} " , args [ index ] , r . error ( ) ) ;
ok = false ;
break ;
}
values_to_push . append ( r . release_value ( ) ) ;
}
if ( ! ok )
continue ;
for ( auto & param : type . parameters ( ) ) {
auto v = values_to_push . take_last ( ) ;
2024-08-04 15:06:50 +00:00
if ( v . type ! = param ) {
warnln ( " Type mismatch in argument: expected {}, but got {} " , Wasm : : ValueType : : kind_name ( param . kind ( ) ) , Wasm : : ValueType : : kind_name ( v . type . kind ( ) ) ) ;
2024-02-18 02:44:25 +00:00
ok = false ;
break ;
}
2024-08-04 15:06:50 +00:00
values . append ( v . value ) ;
2024-02-18 02:44:25 +00:00
}
if ( ! ok )
continue ;
2021-05-21 16:40:44 +00:00
2021-05-23 21:34:58 +00:00
Wasm : : Result result { Wasm : : Trap { } } ;
{
2021-05-24 16:54:28 +00:00
Wasm : : BytecodeInterpreter : : CallFrameHandle handle { g_interpreter , config } ;
2023-02-25 07:45:11 +00:00
result = config . call ( g_interpreter , * address , move ( values ) ) . assert_wasm_result ( ) ;
2021-05-23 21:34:58 +00:00
}
2023-02-25 07:45:11 +00:00
if ( result . is_trap ( ) ) {
2021-07-01 12:33:17 +00:00
warnln ( " Execution trapped: {} " , result . trap ( ) . reason ) ;
2023-02-25 07:45:11 +00:00
} else {
if ( ! result . values ( ) . is_empty ( ) )
warnln ( " Returned: " ) ;
2024-08-04 15:06:50 +00:00
size_t index = 0 ;
2023-02-25 07:45:11 +00:00
for ( auto & value : result . values ( ) ) {
2023-03-01 16:24:50 +00:00
g_stdout - > write_until_depleted ( " -> " sv . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2024-08-04 15:06:50 +00:00
g_printer - > print ( value , type . results ( ) [ index ] ) ;
+ + index ;
2023-02-25 07:45:11 +00:00
}
2021-05-21 16:40:44 +00:00
}
continue ;
}
if ( cmd . is_one_of ( " set " , " unset " ) ) {
auto value = ! cmd . starts_with ( ' u ' ) ;
if ( args . size ( ) < 3 ) {
warnln ( " (un)set what (to what)? " ) ;
continue ;
}
if ( args [ 1 ] = = " print " sv ) {
if ( args [ 2 ] = = " stack " sv )
always_print_stack = value ;
else if ( args [ 2 ] . is_one_of ( " instr " , " instruction " ) )
always_print_instruction = value ;
else
warnln ( " Unknown print category '{}' " , args [ 2 ] ) ;
continue ;
}
warnln ( " Unknown set category '{}' " , args [ 1 ] ) ;
continue ;
}
if ( cmd . is_one_of ( " c " , " continue " ) ) {
g_continue = true ;
return true ;
}
warnln ( " Command not understood: {} " , cmd ) ;
}
}
2021-04-26 08:18:13 +00:00
2024-08-21 23:13:37 +00:00
static RefPtr < Wasm : : Module > parse ( StringView filename )
2021-05-10 12:56:17 +00:00
{
2022-10-24 00:31:40 +00:00
auto result = Core : : MappedFile : : map ( filename ) ;
2021-05-10 12:56:17 +00:00
if ( result . is_error ( ) ) {
warnln ( " Failed to open {}: {} " , filename , result . error ( ) ) ;
return { } ;
}
2023-09-12 18:21:23 +00:00
auto parse_result = Wasm : : Module : : parse ( * result . value ( ) ) ;
2021-05-10 12:56:17 +00:00
if ( parse_result . is_error ( ) ) {
warnln ( " Something went wrong, either the file is invalid, or there's a bug with LibWasm! " ) ;
2023-12-16 14:19:34 +00:00
warnln ( " The parse error was {} " , Wasm : : parse_error_to_byte_string ( parse_result . error ( ) ) ) ;
2021-05-10 12:56:17 +00:00
return { } ;
}
return parse_result . release_value ( ) ;
}
2021-06-03 23:12:11 +00:00
static void print_link_error ( Wasm : : LinkError const & error )
2021-05-10 12:56:17 +00:00
{
2021-06-03 23:12:11 +00:00
for ( auto const & missing : error . missing_imports )
2021-05-10 12:56:17 +00:00
warnln ( " Missing import '{}' " , missing ) ;
}
2022-01-08 17:41:38 +00:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
2021-04-26 08:18:13 +00:00
{
2022-07-11 20:42:03 +00:00
StringView filename ;
2021-04-27 17:43:01 +00:00
bool print = false ;
2021-04-30 20:38:51 +00:00
bool attempt_instantiate = false ;
2021-05-21 16:40:44 +00:00
bool debug = false ;
2021-06-03 23:03:10 +00:00
bool export_all_imports = false ;
2021-06-09 12:13:47 +00:00
bool shell_mode = false ;
2023-04-04 21:24:07 +00:00
bool wasi = false ;
2023-12-16 14:19:34 +00:00
ByteString exported_function_to_execute ;
2024-08-04 15:06:50 +00:00
Vector < ParsedValue > values_to_push ;
2023-12-16 14:19:34 +00:00
Vector < ByteString > modules_to_link_in ;
2023-04-04 21:24:07 +00:00
Vector < StringView > args_if_wasi ;
Vector < StringView > wasi_preopened_mappings ;
2021-04-26 08:18:13 +00:00
Core : : ArgsParser parser ;
parser . add_positional_argument ( filename , " File name to parse " , " file " ) ;
2021-05-21 16:40:44 +00:00
parser . add_option ( debug , " Open a debugger " , " debug " , ' d ' ) ;
2021-04-27 17:43:01 +00:00
parser . add_option ( print , " Print the parsed module " , " print " , ' p ' ) ;
2021-04-30 20:38:51 +00:00
parser . add_option ( attempt_instantiate , " Attempt to instantiate the module " , " instantiate " , ' i ' ) ;
2021-04-30 22:49:01 +00:00
parser . add_option ( exported_function_to_execute , " Attempt to execute the named exported function from the module (implies -i) " , " execute " , ' e ' , " name " ) ;
2024-04-20 20:34:56 +00:00
parser . add_option ( export_all_imports , " Export noop functions corresponding to imports " , " export-noop " ) ;
2021-06-09 12:13:47 +00:00
parser . add_option ( shell_mode , " Launch a REPL in the module's context (implies -i) " , " shell " , ' s ' ) ;
2023-04-04 21:24:07 +00:00
parser . add_option ( wasi , " Enable WASI " , " wasi " , ' w ' ) ;
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 12:56:17 +00:00
parser . add_option ( Core : : ArgsParser : : Option {
2022-07-12 20:13:38 +00:00
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
2021-05-10 12:56:17 +00:00
. help_string = " Extra modules to link with, use to resolve imports " ,
. long_name = " link " ,
. short_name = ' l ' ,
. value_name = " file " ,
2023-02-21 11:44:41 +00:00
. accept_value = [ & ] ( StringView str ) {
if ( ! str . is_empty ( ) ) {
modules_to_link_in . append ( str ) ;
2021-05-10 12:56:17 +00:00
return true ;
}
return false ;
} ,
} ) ;
2021-04-30 22:49:01 +00:00
parser . add_option ( Core : : ArgsParser : : Option {
2022-07-12 20:13:38 +00:00
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
2024-02-18 02:44:25 +00:00
. help_string = " Supply arguments to the function (default=0) (T.const:v or v(T.const:v, ...)) " ,
2021-04-30 22:49:01 +00:00
. long_name = " arg " ,
. short_name = 0 ,
2024-02-18 02:44:25 +00:00
. value_name = " value " ,
2023-02-21 11:44:41 +00:00
. accept_value = [ & ] ( StringView str ) - > bool {
2024-02-18 02:44:25 +00:00
auto result = parse_value ( str ) ;
if ( result . is_error ( ) ) {
warnln ( " Failed to parse value: {} " , result . error ( ) ) ;
return false ;
2021-04-30 22:49:01 +00:00
}
2024-02-18 02:44:25 +00:00
values_to_push . append ( result . release_value ( ) ) ;
return true ;
2021-04-30 22:49:01 +00:00
} ,
} ) ;
2023-04-04 21:24:07 +00:00
parser . add_positional_argument ( args_if_wasi , " Arguments to pass to the WASI module " , " args " , Core : : ArgsParser : : Required : : No ) ;
2022-01-08 17:41:38 +00:00
parser . parse ( arguments ) ;
2021-04-26 08:18:13 +00:00
2021-06-09 12:13:47 +00:00
if ( shell_mode ) {
debug = true ;
attempt_instantiate = true ;
}
if ( ! shell_mode & & debug & & exported_function_to_execute . is_empty ( ) ) {
2021-05-21 16:40:44 +00:00
warnln ( " Debug what? (pass -e fn) " ) ;
return 1 ;
}
2021-06-09 12:13:47 +00:00
if ( debug | | shell_mode ) {
2021-05-21 16:40:44 +00:00
old_signal = signal ( SIGINT , sigint_handler ) ;
}
2021-04-30 22:49:01 +00:00
if ( ! exported_function_to_execute . is_empty ( ) )
2021-04-30 20:38:51 +00:00
attempt_instantiate = true ;
2021-05-10 12:56:17 +00:00
auto parse_result = parse ( filename ) ;
2024-08-21 23:13:37 +00:00
if ( parse_result . is_null ( ) )
2021-06-03 23:03:10 +00:00
return 1 ;
2023-02-09 02:02:46 +00:00
g_stdout = TRY ( Core : : File : : standard_output ( ) ) ;
2023-01-19 23:37:03 +00:00
g_printer = TRY ( try_make < Wasm : : Printer > ( * g_stdout ) ) ;
2021-04-30 20:38:51 +00:00
if ( print & & ! attempt_instantiate ) {
2023-01-19 23:37:03 +00:00
Wasm : : Printer printer ( * g_stdout ) ;
2024-08-21 23:13:37 +00:00
printer . print ( * parse_result ) ;
2021-04-27 17:43:01 +00:00
}
2021-04-30 20:38:51 +00:00
if ( attempt_instantiate ) {
Wasm : : AbstractMachine machine ;
2023-04-04 21:24:07 +00:00
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-04 21:24:07 +00:00
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-04 21:24:07 +00:00
LexicalPath mapped_path { string } ;
paths . append ( { move ( host_path ) , move ( mapped_path ) } ) ;
}
}
return paths ; } ,
} ) ;
}
2021-05-21 16:40:44 +00:00
Core : : EventLoop main_loop ;
if ( debug ) {
g_line_editor = Line : : Editor : : construct ( ) ;
2021-05-23 21:34:58 +00:00
g_interpreter . pre_interpret_hook = pre_interpret_hook ;
g_interpreter . post_interpret_hook = post_interpret_hook ;
2021-05-21 16:40:44 +00:00
}
2021-06-09 12:13:47 +00:00
2021-05-10 12:56:17 +00:00
// First, resolve the linked modules
2023-03-06 16:16:25 +00:00
Vector < NonnullOwnPtr < Wasm : : ModuleInstance > > linked_instances ;
2024-08-21 23:13:37 +00:00
Vector < NonnullRefPtr < Wasm : : Module > > linked_modules ;
2021-05-10 12:56:17 +00:00
for ( auto & name : modules_to_link_in ) {
auto parse_result = parse ( name ) ;
2024-08-21 23:13:37 +00:00
if ( parse_result . is_null ( ) ) {
2021-05-10 12:56:17 +00:00
warnln ( " Failed to parse linked module '{}' " , name ) ;
return 1 ;
}
2024-08-21 23:13:37 +00:00
linked_modules . append ( parse_result . release_nonnull ( ) ) ;
2021-05-10 12:56:17 +00:00
Wasm : : Linker linker { linked_modules . last ( ) } ;
for ( auto & instance : linked_instances )
2023-03-06 16:16:25 +00:00
linker . link ( * instance ) ;
2021-05-10 12:56:17 +00:00
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-21 23:13:37 +00:00
Wasm : : Linker linker { * parse_result } ;
2021-05-10 12:56:17 +00:00
for ( auto & instance : linked_instances )
2023-03-06 16:16:25 +00:00
linker . link ( * instance ) ;
2021-06-03 23:03:10 +00:00
2023-04-04 21:24:07 +00:00
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 ) ;
}
2021-06-03 23:03:10 +00:00
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-21 23:13:37 +00:00
auto type = parse_result - > type_section ( ) . types ( ) [ entry . type . get < Wasm : : TypeIndex > ( ) . value ( ) ] ;
2021-06-03 23:03:10 +00:00
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 15:06:50 +00:00
size_t index = 0 ;
2021-06-03 23:03:10 +00:00
for ( auto & argument : arguments ) {
2023-01-25 19:19:05 +00:00
AllocatingMemoryStream stream ;
2024-08-04 15:06:50 +00:00
auto value_type = type . parameters ( ) [ index ] ;
Wasm : : Printer { stream } . print ( argument , value_type ) ;
2021-06-03 23:03:10 +00:00
if ( first )
first = false ;
else
argument_builder . append ( " , " sv ) ;
2023-01-09 11:06:13 +00:00
auto buffer = ByteBuffer : : create_uninitialized ( stream . used_buffer_size ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-03-01 14:27:35 +00:00
stream . read_until_filled ( buffer ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-09-10 21:43:39 +00:00
argument_builder . append ( StringView ( buffer ) . trim_whitespace ( ) ) ;
2024-08-04 15:06:50 +00:00
+ + index ;
2021-06-03 23:03:10 +00:00
}
2023-12-16 14:19:34 +00:00
dbgln ( " [wasm runtime] Stub function {} was called with the following arguments: {} " , name , argument_builder . to_byte_string ( ) ) ;
2021-06-03 23:03:10 +00:00
Vector < Wasm : : Value > result ;
result . ensure_capacity ( type . results ( ) . size ( ) ) ;
2024-08-17 22:40:21 +00:00
for ( auto expect_result : type . results ( ) )
result . append ( Wasm : : Value ( expect_result ) ) ;
2021-06-03 23:03:10 +00:00
return Wasm : : Result { move ( result ) } ;
} ,
2024-07-09 01:09:00 +00:00
type ,
entry . name ) ) ;
2021-06-03 23:03:10 +00:00
exports . set ( entry , * address ) ;
}
linker . link ( exports ) ;
}
2021-05-10 12:56:17 +00:00
auto link_result = linker . finish ( ) ;
if ( link_result . is_error ( ) ) {
warnln ( " Linking main module failed " ) ;
print_link_error ( link_result . error ( ) ) ;
return 1 ;
}
2024-08-21 23:13:37 +00:00
auto result = machine . instantiate ( * parse_result , link_result . release_value ( ) ) ;
2021-04-30 20:38:51 +00:00
if ( result . is_error ( ) ) {
warnln ( " Module instantiation failed: {} " , result . error ( ) . error ) ;
return 1 ;
}
2021-05-10 11:10:49 +00:00
auto module_instance = result . release_value ( ) ;
2021-04-30 20:38:51 +00:00
2021-06-09 12:13:47 +00:00
auto launch_repl = [ & ] {
Wasm : : Configuration config { machine . store ( ) } ;
Wasm : : Expression expression { { } } ;
config . set_frame ( Wasm : : Frame {
* module_instance ,
Vector < Wasm : : Value > { } ,
expression ,
0 ,
} ) ;
Wasm : : Instruction instr { Wasm : : Instructions : : nop } ;
Wasm : : InstructionPointer ip { 0 } ;
g_continue = false ;
pre_interpret_hook ( config , ip , instr ) ;
} ;
2021-06-03 23:12:11 +00:00
auto print_func = [ & ] ( auto const & address ) {
2021-04-30 20:38:51 +00:00
Wasm : : FunctionInstance * fn = machine . store ( ) . get ( address ) ;
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " - Function with address {}, ptr = {} \n " , address . value ( ) , fn ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-04-30 20:38:51 +00:00
if ( fn ) {
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( ByteString : : formatted ( " wasm function? {} \n " , fn - > has < Wasm : : WasmFunction > ( ) ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-04-30 20:38:51 +00:00
fn - > visit (
2021-06-03 23:12:11 +00:00
[ & ] ( Wasm : : WasmFunction const & func ) {
2023-01-19 23:37:03 +00:00
Wasm : : Printer printer { * g_stdout , 3 } ;
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( " type: \n " sv ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-04-30 20:38:51 +00:00
printer . print ( func . type ( ) ) ;
2024-04-04 01:44:40 +00:00
g_stdout - > write_until_depleted ( " code: \n " sv ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-04-30 20:38:51 +00:00
printer . print ( func . code ( ) ) ;
} ,
2021-06-03 23:12:11 +00:00
[ ] ( Wasm : : HostFunction const & ) { } ) ;
2021-04-30 20:38:51 +00:00
}
} ;
if ( print ) {
// Now, let's dump the functions!
2021-05-11 00:14:59 +00:00
for ( auto & address : module_instance - > functions ( ) ) {
2021-04-30 20:38:51 +00:00
print_func ( address ) ;
}
}
2021-06-09 12:13:47 +00:00
if ( shell_mode ) {
launch_repl ( ) ;
return 0 ;
}
2021-04-30 22:49:01 +00:00
if ( ! exported_function_to_execute . is_empty ( ) ) {
2021-04-30 20:38:51 +00:00
Optional < Wasm : : FunctionAddress > run_address ;
Vector < Wasm : : Value > values ;
2021-05-11 00:14:59 +00:00
for ( auto & entry : module_instance - > exports ( ) ) {
2021-04-30 22:49:01 +00:00
if ( entry . name ( ) = = exported_function_to_execute ) {
if ( auto addr = entry . value ( ) . get_pointer < Wasm : : FunctionAddress > ( ) )
run_address = * addr ;
2021-04-30 20:38:51 +00:00
}
}
if ( ! run_address . has_value ( ) ) {
2021-04-30 22:49:01 +00:00
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-04-30 20:38:51 +00:00
return 1 ;
}
2021-04-30 22:49:01 +00:00
for ( auto & param : instance - > get < Wasm : : WasmFunction > ( ) . type ( ) . parameters ( ) ) {
2024-02-18 02:44:25 +00:00
if ( values_to_push . is_empty ( ) ) {
2024-08-17 22:40:21 +00:00
values . append ( Wasm : : Value ( param ) ) ;
2024-08-04 15:06:50 +00:00
} else if ( param = = values_to_push . last ( ) . type ) {
values . append ( values_to_push . take_last ( ) . value ) ;
2024-02-18 02:44:25 +00:00
} else {
2024-08-04 15:06:50 +00: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 02:44:25 +00:00
return 1 ;
}
2021-04-30 22:49:01 +00:00
}
if ( print ) {
outln ( " Executing " ) ;
print_func ( * run_address ) ;
outln ( ) ;
}
2021-04-30 20:38:51 +00:00
2023-02-25 07:45:11 +00:00
auto result = machine . invoke ( g_interpreter , run_address . value ( ) , move ( values ) ) . assert_wasm_result ( ) ;
2021-05-21 16:40:44 +00:00
2021-06-09 12:13:47 +00:00
if ( debug )
launch_repl ( ) ;
2021-05-21 16:40:44 +00:00
2021-07-12 20:06:50 +00:00
if ( result . is_trap ( ) ) {
2024-07-16 15:49:48 +00:00
if ( result . trap ( ) . reason . starts_with ( " exit: " sv ) )
return - result . trap ( ) . reason . substring_view ( 5 ) . to_number < i32 > ( ) . value_or ( - 1 ) ;
2021-07-12 20:06:50 +00:00
warnln ( " Execution trapped: {} " , result . trap ( ) . reason ) ;
} else {
if ( ! result . values ( ) . is_empty ( ) )
warnln ( " Returned: " ) ;
2024-08-04 15:06:50 +00:00
auto result_type = instance - > get < Wasm : : WasmFunction > ( ) . type ( ) . results ( ) ;
size_t index = 0 ;
2021-07-12 20:06:50 +00:00
for ( auto & value : result . values ( ) ) {
2023-03-01 16:24:50 +00:00
g_stdout - > write_until_depleted ( " -> " sv . bytes ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2024-08-04 15:06:50 +00:00
g_printer - > print ( value , result_type [ index ] ) ;
+ + index ;
2021-07-12 20:06:50 +00:00
}
2021-04-30 20:38:51 +00:00
}
}
}
2021-04-26 08:18:13 +00:00
return 0 ;
}