diff --git a/README.md b/README.md index 6e03a77..c4953f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # .NET/Mono Bext Codegen > [!WARNING] -> The Codegen is currently a work-in-progress and does not have anything useful implemented in it. +> The Codegen is currently a work-in-progress. This is a codegen for [the B extended compiler](https://github.com/bext-lang/b). It introduces targets that produce [.NET](https://dotnet.microsoft.com/en-us/)/[Mono](https://www.mono-project.com/) compatible binaries. @@ -18,10 +18,25 @@ $ ./build/b -tlist ## Targets and Dependencies -Right now the codegen introduces only one target `ilasm-mono`. It expects `ilasm` and `mono` executables to be available in the `$PATH` environment variables. Lots of Linux distros provide them via the mono packages in their official repos: +Right now the codegen introduces two targets `ilasm-mono` and `ilasm-core`. + +`ilasm-mono` target expects `ilasm` and `mono` executables to be available in the `$PATH` environment variables. Lots of Linux distros provide them via the mono packages in their official repos: ```consols $ sudo xbps-install mono # Void Linux $ sudo apt install mono-devel # Ubuntu ... ``` + +On Windows the `mono` executable isn't used so it doesn't need to be in `$PATH`, instead the `ilasm-mono` target executes the resulting binary using .NET Framework. Both .NET Framework's `ilasm` and [.NET Core's `ilasm`](https://www.nuget.org/packages/runtime.win-x64.Microsoft.NETCore.ILAsm) can be used by the target to compile the binary on Windows. + +.NET Core's `ilasm` can be also used on [Linux](https://www.nuget.org/packages/runtime.linux-x64.Microsoft.NETCore.ILAsm) and [macOS](https://www.nuget.org/packages/runtime.osx-arm64.Microsoft.NETCore.ILAsm) instead of Mono's `ilasm` if desired. + +`ilasm-core` target expects .NET 9 SDK/Runtime (or later) to be installed and the `dotnet` executable to be available in the `$PATH` environment variables. Lots of Linux distros provide them via `dotnet-sdk-[version]` (and `dotnet-runtime-[version]`) packages in their official repos: + +```consols +$ sudo apt install dotnet-sdk-9.0 # Ubuntu +... +``` + +It also expects the `ilasm` executable to be in the `$PATH` environment variables, which can be obtained from either Mono or the .NET Core ILAsm NuGet packages linked above (recommended), the .NET Framework one can be also used on Windows. \ No newline at end of file diff --git a/libb/ilasm-core.b b/libb/ilasm-core.b new file mode 100644 index 0000000..31f0197 --- /dev/null +++ b/libb/ilasm-core.b @@ -0,0 +1,29 @@ +// because Windows +usleep(us) { + __asm__( + "ldarg.0", + "conv.i4", + "ldc.i4 1000", + "div", + "call void [mscorlib]System.Threading.Thread::Sleep(int32)", + "ldc.i8 0", + "ret" + ); +} + +char(s,n) { + __asm__( + "ldarg 0", + "ldarg 1", + "add", + "ldind.i1", + "conv.i8", + "ret" + ); +} + +extrn printf; +__variadic__(printf, 1); +extrn putchar; +extrn getchar; +extrn exit; \ No newline at end of file diff --git a/libb/ilasm-mono.b b/libb/ilasm-mono.b index 0574921..31f0197 100644 --- a/libb/ilasm-mono.b +++ b/libb/ilasm-mono.b @@ -1,7 +1,29 @@ -putchar(x) { +// because Windows +usleep(us) { __asm__( "ldarg.0", - "conv.u2", - "call void class [mscorlib]System.Console::Write(char)" + "conv.i4", + "ldc.i4 1000", + "div", + "call void [mscorlib]System.Threading.Thread::Sleep(int32)", + "ldc.i8 0", + "ret" ); } + +char(s,n) { + __asm__( + "ldarg 0", + "ldarg 1", + "add", + "ldind.i1", + "conv.i8", + "ret" + ); +} + +extrn printf; +__variadic__(printf, 1); +extrn putchar; +extrn getchar; +extrn exit; \ No newline at end of file diff --git a/mod.rs b/mod.rs index 2f45cca..426b0a5 100644 --- a/mod.rs +++ b/mod.rs @@ -1,47 +1,49 @@ use core::ffi::*; use crate::nob::*; use crate::crust::libc::*; +use crate::crust::assoc_lookup_cstr; use crate::lexer::*; use crate::missingf; use crate::ir::*; use crate::arena; use crate::targets::TargetAPI; use crate::params::*; +use crate::shlex::*; +use core::mem::zeroed; -pub unsafe fn load_arg(loc: Loc, arg: Arg, output: *mut String_Builder, data: *const [u8]) { +extern "C" { + fn rand() -> c_int; +} + +pub unsafe fn load_arg(loc: Loc, arg: Arg, output: *mut String_Builder, _data: *const [u8]) { match arg { Arg::Bogus => unreachable!("bogus-amogus"), Arg::AutoVar(index) => { sb_appendf(output, c!(" ldloc V_%zu\n"), index); } - Arg::Deref(..) => missingf!(loc, c!("Deref\n")), + Arg::Deref(index) => { + sb_appendf(output, c!(" ldloc V_%zu\n"), index); + sb_appendf(output, c!(" ldind.i8\n")); + } Arg::RefAutoVar(..) => missingf!(loc, c!("RefAutoVar\n")), - Arg::RefExternal(..) => missingf!(loc, c!("RefExternal\n")), - Arg::External(..) => missingf!(loc, c!("External\n")), + Arg::RefExternal(name) => { + sb_appendf(output, c!(" ldsflda int64 Program::'%s'\n"), name); + } + Arg::External(name) => { + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), name); + } Arg::Literal(literal) => { - sb_appendf(output, c!(" ldc.i8 %zu\n"), literal); + sb_appendf(output, c!(" ldc.i8 %zd\n"), literal); } Arg::DataOffset(offset) => { - let mut p = offset; - sb_appendf(output, c!(" ldstr \""), p); - while (*data)[p] != 0 { - let x = (*data)[p] as i32; - if isprint(x) != 0 { - sb_appendf(output, c!("%c"), x); - } else { - match x { - x if x == '\n' as i32 => { sb_appendf(output, c!("\\n")); } - _ => todo!(), - } - } - p += 1; - } - sb_appendf(output, c!("\"\n")); + sb_appendf(output, c!(" ldsflda valuetype ''/'DataSection' ''::'Data'\n")); + sb_appendf(output, c!(" ldc.i8 %zd\n"), offset); + sb_appendf(output, c!(" add\n")); }, } } -pub unsafe fn call_arg(loc: Loc, fun: Arg, out: *mut String_Builder, arity: usize) { +pub unsafe fn call_arg(loc: Loc, fun: Arg, out: *mut String_Builder, arity: usize, funcs: *const [Func]) { match fun { Arg::Bogus => unreachable!("bogus-amogus"), Arg::AutoVar(..) => missingf!(loc, c!("AutoVar\n")), @@ -49,49 +51,82 @@ pub unsafe fn call_arg(loc: Loc, fun: Arg, out: *mut String_Builder, arity: usiz Arg::RefAutoVar(..) => missingf!(loc, c!("RefAutoVar\n")), Arg::RefExternal(..) => missingf!(loc, c!("RefExternal\n")), Arg::External(name) => { - // TODO: unhardcode the printf - // The main difficulty here will be passing the string, since B ilasm-mono runtime only operates on int64 - // as of right now. Some hack is required in here. Look into the direction of boxing the values. - if strcmp(name, c!("printf")) == 0 { - sb_appendf(out, c!(" call void class [mscorlib]System.Console::Write(string)\n")); - sb_appendf(out, c!(" ldc.i8 0\n")); - } else { - sb_appendf(out, c!(" call int64 class Program::%s("), name); - for i in 0..arity { - if i > 0 { sb_appendf(out, c!(", ")); } - sb_appendf(out, c!("int64")); + let mut is_local_func = false; + for i in 0..funcs.len() { + let func = (*funcs)[i]; + if strcmp(func.name, name) == 0 { + is_local_func = true; + break; } - sb_appendf(out, c!(")\n")); } + + if is_local_func { + sb_appendf(out, c!(" call int64 class Program::'%s'("), name); // If the function we want to call collides with a instruction + // we will get a syntax error so '' are necessary. + } + else { + sb_appendf(out, c!(" ldsfld native int Program::'<%s_fnptr>'\n"), name); + sb_appendf(out, c!(" calli unmanaged cdecl int64(")); + } + + for i in 0..arity { + if i > 0 { sb_appendf(out, c!(", ")); } + sb_appendf(out, c!("int64")); + } + sb_appendf(out, c!(")\n")); }, Arg::Literal(..) => missingf!(loc, c!("Literal\n")), Arg::DataOffset(..) => missingf!(loc, c!("DataOffset\n")), } } -pub unsafe fn generate_function(func: Func, output: *mut String_Builder, data: *const [u8]) { - sb_appendf(output, c!(" .method static int64 '%s' ("), func.name); +pub unsafe fn generate_function(func: Func, output: *mut String_Builder, data: *const [u8], funcs: *const [Func], variadics: *const [(*const c_char, Variadic)]) { + sb_appendf(output, c!(" .method static int64 '%s' ("), func.name); // If the function we want to define collides with a instruction + // we will get a syntax error so '' are necessary. for i in 0..func.params_count { if i > 0 { sb_appendf(output, c!(", ")); } sb_appendf(output, c!("int64")); } sb_appendf(output, c!(") {\n"), func.name); - if func.auto_vars_count > 0 { - sb_appendf(output, c!(" .locals init (")); - for i in 0..func.auto_vars_count { - if i > 0 { sb_appendf(output, c!(", ")); } - sb_appendf(output, c!("int64 V_%zu"), i + 1); + let mut maxstack = 0; + + if !(func.body.count == 1 && matches!((*func.body.items.add(0)).opcode, Op::Asm {..})) { + if func.auto_vars_count > 0 { + sb_appendf(output, c!(" .locals init (")); + for i in 0..func.auto_vars_count { + if i > 0 { sb_appendf(output, c!(", ")); } + sb_appendf(output, c!("int64 V_%zu"), i + 1); + } + sb_appendf(output, c!(")\n")); + } + + maxstack = if func.params_count > 8 { func.params_count } else { 8 }; + + if func.params_count > 0 { + for i in 0..func.params_count { + sb_appendf(output, c!(" ldarg %zu\n"), i); + sb_appendf(output, c!(" stloc V_%zu\n"), i + 1); + } } - sb_appendf(output, c!(")\n")); } for i in 0..func.body.count { let op = *func.body.items.add(i); match op.opcode { Op::Bogus => unreachable!("bogus-amogus"), - Op::UnaryNot {..} => missingf!(op.loc, c!("Op::UnaryNot\n")), - Op::Negate {..} => missingf!(op.loc, c!("Op::Negate\n")), + Op::UnaryNot {result, arg} => { + load_arg(op.loc, arg, output, data); + sb_appendf(output, c!(" ldc.i8 0\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" conv.i8\n")); + sb_appendf(output, c!(" stloc V_%zu\n"), result); + } + Op::Negate {result, arg} => { + load_arg(op.loc, arg, output, data); + sb_appendf(output, c!(" neg\n")); + sb_appendf(output, c!(" stloc V_%zu\n"), result); + } Op::Asm {stmts} => { for i in 0..stmts.count { let stmt = *stmts.items.add(i); @@ -103,8 +138,10 @@ pub unsafe fn generate_function(func: Func, output: *mut String_Builder, data: * load_arg(op.loc, rhs, output, data); match binop { Binop::Plus => sb_appendf(output, c!(" add\n")), - Binop::Minus => missingf!(op.loc, c!("Binop::Minus\n")), - Binop::Mult => missingf!(op.loc, c!("Binop::Mult\n")), + Binop::Minus => sb_appendf(output, c!(" sub\n")), + Binop::Mult => { + sb_appendf(output, c!(" mul\n")) + } Binop::Div => { sb_appendf(output, c!(" div\n")) } @@ -115,42 +152,124 @@ pub unsafe fn generate_function(func: Func, output: *mut String_Builder, data: * sb_appendf(output, c!(" ceq\n")); sb_appendf(output, c!(" conv.i8\n")) } - Binop::NotEqual => missingf!(op.loc, c!("Binop::NotEqual\n")), + Binop::NotEqual => { + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" ldc.i4.0\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" conv.i8\n")) + } Binop::Less => { sb_appendf(output, c!(" clt\n")); sb_appendf(output, c!(" conv.i8\n")) }, Binop::LessEqual => { sb_appendf(output, c!(" cgt\n")); + sb_appendf(output, c!(" ldc.i4.0\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" conv.i8\n")) + } + Binop::Greater => { + sb_appendf(output, c!(" cgt\n")); + sb_appendf(output, c!(" conv.i8\n")) + } + Binop::GreaterEqual => { + sb_appendf(output, c!(" clt\n")); sb_appendf(output, c!(" ldc.i4 0\n")); sb_appendf(output, c!(" ceq\n")); sb_appendf(output, c!(" conv.i8\n")) } - Binop::Greater => missingf!(op.loc, c!("Binop::Greater\n")), - Binop::GreaterEqual => missingf!(op.loc, c!("Binop::GreaterEqual\n")), Binop::BitOr => { sb_appendf(output, c!(" or\n")) } - Binop::BitAnd => missingf!(op.loc, c!("Binop::BitAnd\n")), - Binop::BitShl => missingf!(op.loc, c!("Binop::BitShl\n")), - Binop::BitShr => missingf!(op.loc, c!("Binop::BitShr\n")), + Binop::BitAnd => sb_appendf(output, c!(" and\n")), + Binop::BitShl => { + sb_appendf(output, c!(" conv.i4\n")); //Shift amount must be int32 according to the CLI specification + sb_appendf(output, c!(" shl\n")) + } + Binop::BitShr => { + sb_appendf(output, c!(" conv.i4\n")); //Shift amount must be int32 according to the CLI specification + sb_appendf(output, c!(" shr\n")) + } }; sb_appendf(output, c!(" stloc V_%zu\n"), index); } - Op::Index {..} => missingf!(op.loc, c!("Op::Index\n")), + Op::Index {result, arg, offset} => { + load_arg(op.loc, arg, output, data); + load_arg(op.loc, offset, output, data); + sb_appendf(output, c!(" ldc.i4.8\n")); + sb_appendf(output, c!(" mul\n")); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" stloc V_%zu\n"), result); + } Op::AutoAssign {index, arg} => { load_arg(op.loc, arg, output, data); sb_appendf(output, c!(" stloc V_%zu\n"), index); } - Op::ExternalAssign {..} => missingf!(op.loc, c!("Op::ExternalAssign\n")), - Op::Store {..} => missingf!(op.loc, c!("Op::Store\n")), + Op::ExternalAssign {name, arg} => { + load_arg(op.loc, arg, output, data); + sb_appendf(output, c!(" stsfld int64 Program::'%s'\n"), name); + } + Op::Store {index, arg} => { + sb_appendf(output, c!(" ldloc V_%zu\n"), index); + load_arg(op.loc, arg, output, data); + sb_appendf(output, c!(" stind.i8\n")); + } Op::Funcall {result, fun, args} => { - for i in 0..args.count { + let mut fixed_args = 0; + match fun { + Arg::External(name) | Arg::RefExternal(name) => { + if let Some(variadic) = assoc_lookup_cstr(variadics, name) { + fixed_args = (*variadic).fixed_args; + } + } + _ => {} + } + + let mut id = 0; + let mut filler_args = 0; + let variadic_args = args.count - fixed_args; + if fixed_args != 0 && variadic_args > 0 && fixed_args < 8 { + filler_args = 8 - fixed_args; + + id = rand(); + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brtrue L_%d_%d\n"), op.loc.line_number, id); + for i in 0..args.count { + load_arg(op.loc, *args.items.add(i), output, data); + } + call_arg(op.loc, fun, output, args.count, funcs); + sb_appendf(output, c!(" stloc V_%zu\n"), result); + sb_appendf(output, c!(" br L_%d_%d_end\n"), op.loc.line_number, id); + sb_appendf(output, c!(" L_%d_%d:\n"), op.loc.line_number, id); + for i in 0..fixed_args { + load_arg(op.loc, *args.items.add(i), output, data); + } + for _i in 0..filler_args { + sb_appendf(output, c!(" ldc.i8 0\n")); + } + } + else { + fixed_args = 0; + } + + for i in fixed_args..args.count { load_arg(op.loc, *args.items.add(i), output, data); } - call_arg(op.loc, fun, output, args.count); + + let args_count = args.count + filler_args; + call_arg(op.loc, fun, output, args_count, funcs); sb_appendf(output, c!(" stloc V_%zu\n"), result); + if id != 0 { + sb_appendf(output, c!(" L_%d_%d_end:\n"), op.loc.line_number, id); + } + + // the "+ 1" is the stack item for the function pointer used by calli + // TODO: implement a better way for calculating additional stack space rather than hardcoding 1 + let required_stack_items = args_count + 1; + if required_stack_items > maxstack { + maxstack = required_stack_items; + } } Op::Label {label} => { sb_appendf(output, c!(" L%zu:\n"), label); @@ -162,45 +281,436 @@ pub unsafe fn generate_function(func: Func, output: *mut String_Builder, data: * load_arg(op.loc, arg, output, data); sb_appendf(output, c!(" brfalse L%zu\n"), label); } - Op::Return {..} => missingf!(op.loc, c!("Op::Return\n")), + Op::Return {arg} => { + if let Some(arg) = arg { + load_arg(op.loc, arg, output, data); + } + else { + sb_appendf(output, c!(" ldc.i8 0\n")); + } + sb_appendf(output, c!(" ret\n")); + } } } - sb_appendf(output, c!(" ldc.i8 0\n")); - sb_appendf(output, c!(" ret\n")); + + if func.body.count < 1 || !matches!((*func.body.items.add(func.body.count - 1)).opcode, Op::Asm {..} | Op::Return {..}) { + sb_appendf(output, c!(" ldc.i8 0\n")); + sb_appendf(output, c!(" ret\n")); + } + + if maxstack != 0 { + sb_appendf(output, c!(" .maxstack %d\n"), maxstack); + } + sb_appendf(output, c!(" }\n")); } -pub unsafe fn generate_funcs(funcs: *const [Func], output: *mut String_Builder, data: *const [u8]) { +pub unsafe fn generate_funcs(funcs: *const [Func], output: *mut String_Builder, data: *const [u8], variadics: *const [(*const c_char, Variadic)]) { for i in 0..funcs.len() { let func = (*funcs)[i]; - generate_function(func, output, data); + generate_function(func, output, data, funcs, variadics); + } +} + +pub unsafe fn generate_data_section(output: *mut String_Builder, data: *const [u8]) { + if data.len() > 0 { + sb_appendf(output, c!(".class '' extends [mscorlib]System.Object {\n")); + sb_appendf(output, c!(" .class nested assembly sealed 'DataSection' extends [mscorlib]System.ValueType {\n")); + sb_appendf(output, c!(" .pack 1\n")); + sb_appendf(output, c!(" .size %zu\n"), data.len()); + sb_appendf(output, c!(" }\n")); + sb_appendf(output, c!(" .field assembly static initonly valuetype ''/'DataSection' 'Data' at DataArray\n")); + sb_appendf(output, c!(" .data cil DataArray = bytearray (")); + + for i in 0..data.len() { + if i > 0 { sb_appendf(output, c!(" ")); } + sb_appendf(output, c!("%02X"), (*data)[i] as c_uint); + } + sb_appendf(output, c!(")\n")); + sb_appendf(output, c!("}\n")); + } +} + +pub unsafe fn generate_extrn_lib_resolver(output: *mut String_Builder, lib: *const c_char, use_underscored_name: bool, mono: bool) { + sb_appendf(output, c!(" ldsfld native int Program::'<%s_lib>'\n"), lib); + if use_underscored_name { sb_appendf(output, c!(" ldstr \"_\"\n")); } + sb_appendf(output, c!(" ldarg.0\n")); + if use_underscored_name { sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string)\n")); } + if !mono { + sb_appendf(output, c!(" ldloca.s 0\n")); + sb_appendf(output, c!(" call bool [System.Runtime.InteropServices]System.Runtime.InteropServices.NativeLibrary::TryGetExport(native int, string, native int&)\n")); + } + else { + sb_appendf(output, c!(" call native int Program::''(native int, string)\n")); + sb_appendf(output, c!(" stloc.0\n")); + sb_appendf(output, c!(" ldloc.0\n")); + } + sb_appendf(output, c!(" brtrue Success\n")); +} + +pub unsafe fn generate_loader_helpers(output: *mut String_Builder, linker: *const [*const c_char], mono: bool) { + if !mono { + sb_appendf(output, c!(" .method static bool ''(string, native int&) {\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" call bool [System.Runtime.InteropServices]System.Runtime.InteropServices.NativeLibrary::TryLoad(string, native int&)\n")); + sb_appendf(output, c!(" brfalse.s CurrentDir\n")); + sb_appendf(output, c!(" ldc.i4.1\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" CurrentDir:\n")); + sb_appendf(output, c!(" call string [mscorlib]System.IO.Directory::GetCurrentDirectory()\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" call string [mscorlib]System.IO.Path::Combine(string, string)\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" call bool [System.Runtime.InteropServices]System.Runtime.InteropServices.NativeLibrary::TryLoad(string, native int&)\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); + } + else { + sb_appendf(output, c!(" .method static pinvokeimpl(\"libc\" as \"dlopen\" nomangle ansi cdecl) native int ''(string, int32) preservesig {}\n")); + sb_appendf(output, c!(" .method static pinvokeimpl(\"libc\" as \"dlsym\" nomangle ansi cdecl) native int ''(native int, string) preservesig {}\n")); + sb_appendf(output, c!(" .method static pinvokeimpl(\"kernel32.dll\" as \"LoadLibraryA\" nomangle ansi winapi) native int ''(string) preservesig {}\n")); + sb_appendf(output, c!(" .method static pinvokeimpl(\"kernel32.dll\" as \"GetProcAddress\" nomangle ansi winapi) native int ''(native int, string) preservesig {}\n")); + + sb_appendf(output, c!(" .method static bool ''(string, native int&) {\n")); + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brfalse.s Unix\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" call native int Program::''(string)\n")); + sb_appendf(output, c!(" stind.i\n")); + sb_appendf(output, c!(" br.s Return\n")); + sb_appendf(output, c!(" Unix:\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldc.i4.1\n")); + sb_appendf(output, c!(" call native int Program::''(string, int32)\n")); + sb_appendf(output, c!(" stind.i\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" ldind.i\n")); + sb_appendf(output, c!(" brfalse.s CurrentDir\n")); + sb_appendf(output, c!(" br.s Return\n")); + sb_appendf(output, c!(" CurrentDir:\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" call string [mscorlib]System.IO.Directory::GetCurrentDirectory()\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" call string [mscorlib]System.IO.Path::Combine(string, string)\n")); + sb_appendf(output, c!(" ldc.i4.1\n")); + sb_appendf(output, c!(" call native int Program::''(string, int32)\n")); + sb_appendf(output, c!(" stind.i\n")); + sb_appendf(output, c!(" Return:\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" ldind.i\n")); + sb_appendf(output, c!(" ldsfld native int [mscorlib]System.IntPtr::Zero\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" ldc.i4.0\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); + } + + sb_appendf(output, c!(" .method static native int ''(string) {\n")); + sb_appendf(output, c!(" .locals init (native int lib)\n")); + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brfalse.s Unix\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldstr \".dll\"\n")); + sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string)\n")); + sb_appendf(output, c!(" ldloca.s 0\n")); + if mono { + sb_appendf(output, c!(" call bool Program::''(string, native int&)\n")); + } + else { + sb_appendf(output, c!(" call bool [System.Runtime.InteropServices]System.Runtime.InteropServices.NativeLibrary::TryLoad(string, native int&)\n")); } + sb_appendf(output, c!(" brtrue.s Success\n")); + sb_appendf(output, c!(" br.s Failed\n")); + sb_appendf(output, c!(" Unix:\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldsfld string Program::''\n")); + sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string)\n")); + sb_appendf(output, c!(" ldloca.s 0\n")); + sb_appendf(output, c!(" call bool Program::''(string, native int&)\n")); + sb_appendf(output, c!(" brtrue.s Success\n")); + sb_appendf(output, c!(" ldstr \"lib\"\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldsfld string Program::''\n")); + sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string, string)\n")); + sb_appendf(output, c!(" ldloca.s 0\n")); + sb_appendf(output, c!(" call bool Program::''(string, native int&)\n")); + sb_appendf(output, c!(" brtrue.s Success\n")); + sb_appendf(output, c!(" Failed:\n")); + sb_appendf(output, c!(" call valuetype [mscorlib]System.ConsoleColor [mscorlib]System.Console::get_ForegroundColor()\n")); + sb_appendf(output, c!(" ldc.i4.s 14\n")); + sb_appendf(output, c!(" call void [mscorlib]System.Console::set_ForegroundColor(valuetype [mscorlib]System.ConsoleColor)\n")); + sb_appendf(output, c!(" ldstr \"[WARNING] Unable to load library \"\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string)\n")); + sb_appendf(output, c!(" call void [mscorlib]System.Console::WriteLine(string)\n")); + sb_appendf(output, c!(" call void [mscorlib]System.Console::set_ForegroundColor(valuetype [mscorlib]System.ConsoleColor)\n")); + sb_appendf(output, c!(" ldc.i8 -2\n")); + sb_appendf(output, c!(" conv.i\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" Success:\n")); + sb_appendf(output, c!(" ldloc.0\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); + + if mono { + sb_appendf(output, c!(" .method static native int ''(native int, string) {\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" ldarg.1\n")); + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brfalse.s Unix\n")); + sb_appendf(output, c!(" call native int Program::''(native int, string)\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" Unix:\n")); + sb_appendf(output, c!(" call native int Program::''(native int, string)\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); + } + + sb_appendf(output, c!(" .method static native int ''(string) {\n")); + sb_appendf(output, c!(" .locals init (native int fnptr)\n")); + generate_extrn_lib_resolver(output, c!("libc"), false, mono); + generate_extrn_lib_resolver(output, c!("libc"), true, mono); + for i in 0..linker.len() { + let lib = (*linker)[i]; + generate_extrn_lib_resolver(output, lib, false, mono); + } + sb_appendf(output, c!(" Failed:\n")); + sb_appendf(output, c!(" ldstr \"Unable to resolve extrn \"\n")); + sb_appendf(output, c!(" ldarg.0\n")); + sb_appendf(output, c!(" call string [mscorlib]System.String::Concat(string, string)\n")); + sb_appendf(output, c!(" newobj instance void [mscorlib]System.Exception::.ctor(string)\n")); + sb_appendf(output, c!(" throw\n")); + sb_appendf(output, c!(" Success:\n")); + sb_appendf(output, c!(" ldloc.0\n")); + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); +} + +pub unsafe fn generate_constructor(output: *mut String_Builder, globals: *const [Global], linker: *const [*const c_char], mono: bool, has_variadics: bool, undefined_extrns: *const [*const c_char]) { + sb_appendf(output, c!(" .method static void .cctor() {\n")); + + if undefined_extrns.len() > 0 { + sb_appendf(output, c!(" call valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform [mscorlib]System.Runtime.InteropServices.OSPlatform::get_Windows()\n")); + sb_appendf(output, c!(" call bool [mscorlib]System.Runtime.InteropServices.RuntimeInformation::IsOSPlatform(valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform)\n")); + sb_appendf(output, c!(" stsfld bool Program::''\n")); + sb_appendf(output, c!(" call valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform [mscorlib]System.Runtime.InteropServices.OSPlatform::get_Linux()\n")); + sb_appendf(output, c!(" call bool [mscorlib]System.Runtime.InteropServices.RuntimeInformation::IsOSPlatform(valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform)\n")); + sb_appendf(output, c!(" stsfld bool Program::''\n")); + if has_variadics { + sb_appendf(output, c!(" call valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform [mscorlib]System.Runtime.InteropServices.OSPlatform::get_OSX()\n")); + sb_appendf(output, c!(" call bool [mscorlib]System.Runtime.InteropServices.RuntimeInformation::IsOSPlatform(valuetype [mscorlib]System.Runtime.InteropServices.OSPlatform)\n")); + sb_appendf(output, c!(" call valuetype [mscorlib]System.Runtime.InteropServices.Architecture [mscorlib]System.Runtime.InteropServices.RuntimeInformation::get_ProcessArchitecture()\n")); + sb_appendf(output, c!(" ldc.i4.3\n")); + sb_appendf(output, c!(" ceq\n")); + sb_appendf(output, c!(" and\n")); + sb_appendf(output, c!(" stsfld bool Program::''\n")); + } + + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brfalse.s macOS\n")); + sb_appendf(output, c!(" ldstr \".so\"\n")); + sb_appendf(output, c!(" br.s SetSuffix\n")); + sb_appendf(output, c!(" macOS:\n")); + sb_appendf(output, c!(" ldstr \".dylib\"\n")); + sb_appendf(output, c!(" SetSuffix:\n")); + sb_appendf(output, c!(" stsfld string Program::''\n")); + + sb_appendf(output, c!(" ldsfld bool Program::''\n")); + sb_appendf(output, c!(" brfalse.s libc\n")); + sb_appendf(output, c!(" ldstr \"msvcrt\"\n")); + sb_appendf(output, c!(" call native int Program::''(string)\n")); + sb_appendf(output, c!(" br.s set_libc\n")); + sb_appendf(output, c!(" libc:\n")); + if mono { + sb_appendf(output, c!(" ldnull\n")); + sb_appendf(output, c!(" ldc.i4.1\n")); + sb_appendf(output, c!(" call native int Program::''(string, int32)\n")); + } + else { + sb_appendf(output, c!(" call native int [System.Runtime.InteropServices]System.Runtime.InteropServices.NativeLibrary::GetMainProgramHandle()\n")); + } + sb_appendf(output, c!(" set_libc:\n")); + sb_appendf(output, c!(" stsfld native int Program::''\n")); + + for i in 0..linker.len() { + let lib = (*linker)[i]; + sb_appendf(output, c!(" ldstr \"%s\"\n"), lib); + sb_appendf(output, c!(" call native int Program::''(string)\n")); + sb_appendf(output, c!(" stsfld native int Program::'<%s_lib>'\n"), lib); + } + + for i in 0..undefined_extrns.len() { + let extrn = (*undefined_extrns)[i]; + sb_appendf(output, c!(" ldstr \"%s\"\n"), extrn); + sb_appendf(output, c!(" call native int Program::''(string)\n")); + sb_appendf(output, c!(" stsfld native int Program::'<%s_fnptr>'\n"), extrn); + } + } + + for i in 0..globals.len() { + let global = (*globals)[i]; + let is_array = global.values.count > 1; + if is_array { + sb_appendf(output, c!(" ldc.i8 %zd\n"), global.values.count * 8); + sb_appendf(output, c!(" ldsfld native int Program::''\n")); + sb_appendf(output, c!(" calli unmanaged cdecl int64(int64)")); + sb_appendf(output, c!(" stsfld int64 Program::'%s'\n"), global.name); + } + + for j in 0..global.values.count { + match *global.values.items.add(j) { + ImmediateValue::Literal(lit) => { + if !is_array { + sb_appendf(output, c!(" ldc.i8 %zd\n"), lit); + sb_appendf(output, c!(" stsfld int64 Program::'%s'\n"), global.name) + } else { + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), global.name); + sb_appendf(output, c!(" ldc.i8 %zd\n"), j * 8); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" ldc.i8 %zd\n"), lit); + sb_appendf(output, c!(" stind.i8\n")) + } + }, + ImmediateValue::Name(name) => { + if !is_array { + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), name); + sb_appendf(output, c!(" stsfld int64 Program::'%s'\n"), global.name) + } else { + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), global.name); + sb_appendf(output, c!(" ldc.i8 %zd\n"), j * 8); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), name); + sb_appendf(output, c!(" stind.i8\n")) + } + } + ImmediateValue::DataOffset(offset) => { + if !is_array { + sb_appendf(output, c!(" ldsflda valuetype ''/'DataSection' ''::'Data'\n")); + sb_appendf(output, c!(" ldc.i8 %zd\n"), offset); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" stsfld int64 Program::'%s'\n"), global.name) + } else { + sb_appendf(output, c!(" ldsfld int64 Program::'%s'\n"), global.name); + sb_appendf(output, c!(" ldc.i8 %zd\n"), j * 8); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" ldsflda valuetype ''/'DataSection' ''::'Data'\n")); + sb_appendf(output, c!(" ldc.i8 %zd\n"), offset); + sb_appendf(output, c!(" add\n")); + sb_appendf(output, c!(" stind.i8\n")) + } + }, + }; + } + } + + sb_appendf(output, c!(" ret\n")); + sb_appendf(output, c!(" }\n")); } -pub unsafe fn usage(params: *const [Param]) { - fprintf(stderr(), c!("ilasm_mono codegen for the B compiler\n")); +pub unsafe fn generate_fields(output: *mut String_Builder, globals: *const [Global], extrns: *const [*const c_char], funcs: *const [Func], linker: *const [*const c_char], mono: bool, has_variadics: bool) { + for i in 0..globals.len() { + sb_appendf(output, c!(" .field public static int64 '%s'\n"), (*globals)[i].name); + } + + let mut has_malloc = false; + let mut undefined_extrns: Array<*const c_char> = zeroed(); + + for i in 0..extrns.len() { + let extrn = (*extrns)[i]; + + if strcmp(extrn, c!("malloc")) == 0 { + has_malloc = true; + } + + let mut extrn_defined = false; + for j in 0..funcs.len() { + let func = (*funcs)[j]; + if strcmp(func.name, extrn) == 0 { + extrn_defined = true; + break; + } + } + + if !extrn_defined { + da_append(&mut undefined_extrns, extrn); + sb_appendf(output, c!(" .field public static native int '<%s_fnptr>'\n"), extrn); + } + } + + // we need malloc for initializing global arrays + if !has_malloc && globals.len() > 0 { + da_append(&mut undefined_extrns, c!("malloc")); + sb_appendf(output, c!(" .field public static native int ''\n")); + } + + sb_appendf(output, c!(" .field public static native int ''\n")); + + let has_undefined_extrns = undefined_extrns.count > 0; + for i in 0..linker.len() { + let lib = (*linker)[i]; + sb_appendf(output, c!(" .field public static native int '<%s_lib>'\n"), lib); + } + + if globals.len() > 0 || has_undefined_extrns { + if has_undefined_extrns { + sb_appendf(output, c!(" .field public static string ''\n")); + + sb_appendf(output, c!(" .field public static bool ''\n")); + sb_appendf(output, c!(" .field public static bool ''\n")); + if has_variadics { + sb_appendf(output, c!(" .field public static bool ''\n")); + } + + generate_loader_helpers(output, linker, mono); + } + + generate_constructor(output, globals, linker, mono, has_variadics, da_slice(undefined_extrns)); + } +} + +pub unsafe fn usage(params: *const [Param], mono: bool) { + fprintf(stderr(), c!("%s codegen for the B compiler\n"), if mono { c!("ilasm_mono") } else { c!("ilasm_core") }); fprintf(stderr(), c!("OPTIONS:\n")); print_params_help(params); } -struct ILasm_Mono { +struct ILasm { + link_args: *const c_char, output: String_Builder, cmd: Cmd, + mono: bool, } pub unsafe fn get_apis(targets: *mut Array) { da_append(targets, TargetAPI::V1 { name: c!("ilasm-mono"), file_ext: c!(".exe"), - new, + new: |a, args| { + new(a, args, true) + }, + build: generate_program, + run: run_program, + }); + + da_append(targets, TargetAPI::V1 { + name: c!("ilasm-core"), + file_ext: c!(".b.dll"), + new: |a, args| { + new(a, args, false) + }, build: generate_program, run: run_program, }); } -pub unsafe fn new(a: *mut arena::Arena, args: *const [*const c_char]) -> Option<*mut c_void> { - let gen = arena::alloc_type::(a); - memset(gen as _ , 0, size_of::()); +pub unsafe fn new(a: *mut arena::Arena, args: *const [*const c_char], mono: bool) -> Option<*mut c_void> { + let gen = arena::alloc_type::(a); + memset(gen as _ , 0, size_of::()); + (*gen).mono = mono; let mut help = false; let params = &[ @@ -209,18 +719,22 @@ pub unsafe fn new(a: *mut arena::Arena, args: *const [*const c_char]) -> Option< description: c!("Print this help message"), value: ParamValue::Flag { var: &mut help }, }, + Param { + // This is called "link-args" to remain compatible with the old -L syntax which is automatically turned into -C link-args='...' by the B compiler + name: c!("link-args"), + description: c!("List of native dynamic libraries (without file extension nor the 'lib' prefix) to search for external functions in, Example: -C link-args='raylib'"), + value: ParamValue::String { var: &mut (*gen).link_args, default: c!("") }, + }, ]; if let Err(message) = parse_args(params, args) { - usage(params); + usage(params, mono); log(Log_Level::ERROR, c!("%s"), message); return None; } if help { - usage(params); - fprintf(stderr(), c!("\n")); - fprintf(stderr(), c!("It doesn't really provide any useful parameters yet.\n")); + usage(params, mono); return None; } Some(gen as _) @@ -230,16 +744,42 @@ pub unsafe fn generate_program( gen: *mut c_void, program: *const Program, program_path: *const c_char, garbage_base: *const c_char, _nostdlib: bool, debug: bool, ) -> Option<()> { - let gen = gen as *mut ILasm_Mono; + let gen = gen as *mut ILasm; let output = &mut (*gen).output; let cmd = &mut (*gen).cmd; + let mono = (*gen).mono; - if debug { todo!("Debug information for ilasm-mono") } + if debug { todo!("Debug information for ilasm") } sb_appendf(output, c!(".assembly 'Main' {}\n")); - sb_appendf(output, c!(".module Main.exe\n")); + sb_appendf(output, c!(".assembly extern mscorlib {}\n")); + if !mono { + sb_appendf(output, c!(".assembly extern System.Runtime {}\n")); + sb_appendf(output, c!(".assembly extern System.Runtime.InteropServices {}\n")); + } + sb_appendf(output, c!(".module Main.%s\n"), if mono { c!("exe") } else { c!("dll") }); + + let sliced_data = da_slice((*program).data); + generate_data_section(output, sliced_data); + sb_appendf(output, c!(".class Program extends [mscorlib]System.Object {\n")); - generate_funcs(da_slice((*program).funcs), output, da_slice((*program).data)); + + let mut libs: Array<*const c_char> = zeroed(); + + let mut s: Shlex = zeroed(); + let link_args = (*gen).link_args; + shlex_init(&mut s, link_args, link_args.add(strlen(link_args))); + while !shlex_next(&mut s).is_null() { + da_append(&mut libs, temp_strdup(s.string)); + } + shlex_free(&mut s); + + let linker = da_slice(libs); + let funcs = da_slice((*program).funcs); + let variadics = da_slice((*program).variadics); + generate_fields(output, da_slice((*program).globals), da_slice((*program).extrns), funcs, linker, mono, variadics.len() > 0); + generate_funcs(funcs, output, sliced_data, variadics); + sb_appendf(output, c!(" .method static void Main (string[] args) {\n")); sb_appendf(output, c!(" .entrypoint\n")); sb_appendf(output, c!(" call int64 class Program::main()\n")); @@ -252,27 +792,70 @@ pub unsafe fn generate_program( write_entire_file(output_asm_path, (*output).items as *const c_void, (*output).count)?; log(Log_Level::INFO, c!("generated %s"), output_asm_path); - cmd_append!{ - cmd, - c!("ilasm"), output_asm_path, temp_sprintf(c!("/output:%s"), program_path) + if mono { + cmd_append! { + cmd, + c!("ilasm"), output_asm_path, temp_sprintf(c!("-output:%s"), program_path) + } + } + else { + cmd_append! { + cmd, + c!("ilasm"), c!("-dll"), output_asm_path, temp_sprintf(c!("-output:%s"), program_path) + } } if !cmd_run_sync_and_reset(cmd) { return None; } + if !mono { + let base_path; + if let Some(path) = temp_strip_suffix(program_path, c!(".dll")) { + base_path = path; + } else { + base_path = program_path; + } + + let config_output_path = temp_sprintf(c!("%s.runtimeconfig.json"), base_path); + let config = c!(" + { + \"runtimeOptions\": { + \"tfm\": \"net9.0\", + \"framework\": { + \"name\": \"Microsoft.NETCore.App\", + \"version\": \"9.0.0\" + } + } + }"); + + write_entire_file(config_output_path, config as *const c_void, strlen(config))?; + } + Some(()) } pub unsafe fn run_program( gen: *mut c_void, program_path: *const c_char, run_args: *const [*const c_char], ) -> Option<()> { - let gen = gen as *mut ILasm_Mono; + let gen = gen as *mut ILasm; let cmd = &mut (*gen).cmd; + let mono = (*gen).mono; - cmd_append!{ - cmd, - c!("mono"), program_path, + if mono && !cfg!(target_os = "windows") { + cmd_append! { + cmd, + c!("mono"), + } } + if !mono { + cmd_append! { + cmd, + c!("dotnet"), + } + } + + cmd_append!{ cmd, program_path, } + da_append_many(cmd, run_args); if !cmd_run_sync_and_reset(cmd) { return None; } Some(())