Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2a4bcd0
Probe for the dotnet SDK in addition to stand-alone csc and mcs.
sebgod Apr 25, 2026
11d3279
Build C# linked targets via a generated csproj and `dotnet build'.
sebgod Apr 25, 2026
7c6b82c
Tighten Mercury syntax in csproj-based csharp linking helpers.
sebgod Apr 25, 2026
8faef37
Restrict csproj_content/8 to the csharp linked-target inst.
sebgod Apr 25, 2026
6cc4d59
Drop the legacy XML comment text and modernise io.file.m for .NET 10.
sebgod Apr 25, 2026
522ddc2
Implement math.fma, env-var iteration and memory stats for the C# bac…
sebgod Apr 25, 2026
8cffb0c
Implement tuple_arity, tuple_arg and compare_representation for the C…
sebgod Apr 25, 2026
76830fb
Document the .NET 10 / C# 14 transition for the csharp grade.
sebgod Apr 25, 2026
f2038b9
Fix the csproj-based csharp linker on native Windows.
sebgod Apr 28, 2026
5778d00
Add `--csharp-aot' to opt csharp executables into Native AOT publishing.
sebgod Apr 28, 2026
07c9912
Replace System.Reflection-based RTTI in csharp DU walks with an MR_Du…
sebgod Apr 28, 2026
0d15a18
Mark mer_std as Native-AOT-compatible.
sebgod Apr 28, 2026
df9e14d
Fix C# binary stream dual-buffering bug in io module.
sebgod May 1, 2026
deab72b
Use lowercase ""r"" round-trip format for C# float_to_string.
sebgod May 1, 2026
159bf94
Replace exception-based surrogate handling with guards in C# string ops.
sebgod May 1, 2026
4604041
Remove 5 now-passing tests from EXPECT_FAIL_TESTS.csharp.
sebgod May 1, 2026
b037622
Fix C# InvalidCastException for multi/nondet delegate casts
sebgod May 1, 2026
ff265fe
Replace null with Array.Empty<object>() for empty C# arrays
sebgod May 1, 2026
c8ca9a9
Fix C# InvalidCastException for typed arrays from Array.Empty<object>()
sebgod May 1, 2026
3c1088d
Remove 3 now-passing tests from EXPECT_FAIL_TESTS.csharp
sebgod May 1, 2026
cbea8ba
Fix C# DST handling and Errors.SORRY/fatal_error output
sebgod May 2, 2026
34bcfdc
Emit C# record class for Mercury DU types
sebgod May 2, 2026
0247cd3
Remove --csharp-compiler / --csc plumbing.
sebgod May 3, 2026
ca0d4ec
Multi-target the csharp libgrade against net10.0 + netstandard2.0.
sebgod May 3, 2026
77b67c8
Use forward slashes when emitting Mercury subdirectory paths.
sebgod May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 99 additions & 89 deletions Documentation/README.CSharp.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Mercury C# Backend
==================

The Mercury compiler has a backend that generates C# source code, that can be
compiled into bytecode suitable for running using the .NET or Mono runtime
systems. The backend is mostly complete, but some parts of the Mercury standard
library are not yet implemented.
The Mercury compiler has a backend that generates C# source code, that is
compiled into a managed assembly by the .NET 10 SDK and run on the
.NET 10 runtime. The backend is mostly complete, but some parts of the
Mercury standard library are not yet implemented.

The C# backend requires C# 5.0 or higher -- older versions of C# are *not*
supported.
The C# backend requires C# 14 or higher. Older versions of C# (and the
.NET Framework / Mono runtimes) are *not* supported.

Contents
--------
Expand All @@ -26,30 +26,29 @@ Contents
Prerequisites
-------------

In order to use Mercury's C# backend you will need either:
To use Mercury's C# backend you need a working .NET SDK at version 10.0
or above. The `dotnet` command must be on your `PATH`.

* Microsoft .NET 4.5 or above.
* Mono 4.0 or above.
There is no longer any support for Mono or for the .NET Framework
runtime.

Installing the `csharp` grade
-----------------------------

The Mercury compiler uses the grade `csharp` to target C# source code that
is then compiled by a C# compiler.
The Mercury compiler uses the grade `csharp` to target C# source code
that is then compiled by the .NET SDK.

Mercury's autoconfiguration script will cause the `csharp` grade to be installed
if it finds a suitable C# compiler (e.g. `csc`) and .NET runtime in your `PATH`.

You can check if your Mercury installation has been configured to include the
`csharp` grade by looking if `csharp` is included in the output of the Mercury
compiler's `--output-stdlib-grades` option.
Mercury's autoconfiguration script will install the `csharp` grade when
it detects `dotnet` on your `PATH` along with a usable >= 10.0 SDK.
You can check the result by running `mmc --output-stdlib-grades` and
looking for `csharp` in the list.

Compiling programs with the `csharp` grade
------------------------------------------

Once you have a Mercury installation that includes the `csharp` grade, you
can build programs such as `hello.m` or `calculator.m` in the [samples](samples)
directory.
Once you have a Mercury installation that includes the `csharp` grade,
you can build programs such as `hello.m` or `calculator.m` in the
[samples](samples) directory.

```
mmc --grade csharp --make hello
Expand All @@ -58,63 +57,75 @@ directory.
When building programs with the `csharp` grade you *must* use `mmc --make`.
Using `mmake` to build programs using the `csharp` grade is _not_ supported.

Running `csharp` grade programs with Mono
-----------------------------------------

For the example in the previous section on a Unix (or more generally,
non-Windows) system using Mono, the Mercury compiler will generate a process
assembly, e.g. `hello.exe`, and a wrapper shell script named `hello`.

The wrapper shell script will set the `MONO_PATH` environment variable
to point to the location of the Mercury standard library assemblies.
It will then invoke the CLI execution environment on the process assembly.
You can run the program using the wrapper shell script, for example:

```
./hello
```

Running `csharp` grade programs on Windows with .NET
----------------------------------------------------

On Windows, the Mercury compiler will only generate a process assembly, e.g.
`hello.exe`. On Windows there is no need to generate a wrapper shell script.

With .NET, the library assemblies (.dlls) for the Mercury standard
libraries must either (1) reside in (or under) the same directory as the process
assembly (.exe) or (2) be entered into the global assembly cache (GAC).
If neither of these things is done then execution will abort with a message that
begins:

```
Unhandled Exception: System.IO.FileNotFoundException: Could not load file
or assembly 'mer_std', Version=...
```

For (1), you will need to copy the library assemblies from the Mercury library
installation directory into the same directory as the process assembly.
The files for the Mercury library assemblies are located in

```
<prefix>\lib\mercury\lib\csharp
```

where `<prefix>` is the location of the Mercury installation.
Copy all of the .dll files in the above directory into that of the process
assembly.

To enter assemblies into the GAC, run the following command for each
assembly.

```
gacutil /i mer_std.dll
```

Assemblies can be removed from the GAC by doing, for example

```
gacutil /u mer_std.dll
```
Behind the scenes, `mmc --make` generates a `<MainModule>.csproj` next
to the linked target and runs `dotnet build` on it. The .NET SDK
produces:

* `<MainModule>.dll` -- the managed assembly,
* `<MainModule>.exe` -- the apphost / native launcher
(renamed from the bare `<MainModule>`
on Linux and macOS to match
Mercury's csharp_executable convention),
* `<MainModule>.runtimeconfig.json`,
* `<MainModule>.deps.json`,

plus the standard `bin/` and `obj/` MSBuild scratch directories. The
referenced Mercury standard-library assemblies (`mer_std.dll` and so
on) are copied next to the executable via the SDK's
`<Private>true</Private>` reference setting, so no `MONO_PATH`,
wrapper script or GAC registration is required.

You can run the resulting program directly:

```
./hello.exe
```

Trimmed publish
---------------

The generated `<MainModule>.csproj` for libraries carries
`<IsTrimmable>true</IsTrimmable>`, and the hand-written runtime and
standard-library C# code uses no name-based reflection that would defeat
the IL-linker. Downstream consumers can therefore add their own
`<PublishTrimmed>true</PublishTrimmed>` and `dotnet publish` Mercury
applications without losing functionality. `mmc` itself does not
invoke `dotnet publish`.

Native AOT publishing
---------------------

The `--csharp-aot` option flips a `csharp_executable` build from
`dotnet build` to `dotnet publish -p:PublishAot=true -r <rid>`. The
generated csproj adds `<PublishAot>true</PublishAot>`,
`<SelfContained>true</SelfContained>`, `<InvariantGlobalization>true
</InvariantGlobalization>` and a `<RuntimeIdentifier>` derived from
Mercury's target architecture (e.g. `aarch64-w64-mingw32` -> `win-arm64`,
`x86_64-pc-linux-gnu` -> `linux-x64`). `<PublishDir>` is forced to `./`
so the produced native binary lands next to the csproj where Mercury
expects it, identical to the regular build flow. No `<MainModule>.dll`,
`runtimeconfig.json` or `deps.json` companions are emitted; the apphost
is the entire program.

The option is opt-in and the user owns the AOT-cleanliness contract:

* No module reachable from the program's `main/2` may import
`type_desc`, `construct`, `deconstruct` or `term_to_xml`, nor call
the generic forms of `io.write/3` or `compare_representation/3`.
Such uses require runtime reflection, which the AOT compiler trims.
* Every linked Mercury library (the standard library and any `-l`
reference) must have been built AOT-compatible.

If the target architecture cannot be mapped to a .NET RID, the build
falls back to a regular `dotnet build` and prints a notice to the
progress stream. Trim or AOT warnings from `dotnet publish` surface
as a non-zero exit and abort the link step exactly like a normal C#
compilation error.

For `csharp_library` targets, `--csharp-aot` only adds
`<IsAotCompatible>true</IsAotCompatible>` to the generated csproj as
a marker for downstream consumers; the build itself is still a regular
`dotnet build`, and `mmc` does not run `dotnet publish` on libraries.

Limitations
-----------
Expand Down Expand Up @@ -151,23 +162,22 @@ supported or not fully implemented:
The current implementation of `read_binary` does not work with the
way Mercury file streams are implemented for the C# backend.

2. `benchmarking.report_stats/0`
`benchmarking.report_full_memory_stats/0`
2. `store.arg_ref/5`
`store.new_arg_ref/5`

Memory usage statistics are not yet available, and cpu time
is not the same as in the C backends, as per `time.m`.
Due to some limitations in RTTI support, dynamic type checking is
missing for these predicates. They should be used with care.

3. `store.arg_ref/5`
`store.new_arg_ref/5`
3. `deconstruct.functor_number/3`

Due to some limitations in RTTI support, dynamic type checking is missing
for these predicates. They should be used with care.
Not implemented; the C backend implements this through a header
inclusion that is not portable to C#, and the C# RTTI layer does
not yet expose an equivalent functor-number lookup.

4. `math.fma/3`
4. `exception.catch_impl/3` for the `semidet` and `cc_nondet` modes.

This function is not available because it is not supported by C# 5.0.
(It will be supported once the minimum version of C# required by
Mercury increases.)
Currently throws `Sorry, not implemented'. The `det`, `cc_multi`
and `multi` modes are implemented.

Interfacing with C#
-------------------
Expand Down
60 changes: 60 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ Changes that may break compatibility
* We have dropped support for versions of MSVC before version 19.3
(Visual Studio 2022).

* The `csharp` grade now requires the .NET 10 SDK and uses `dotnet build`
to link assemblies. The compiler no longer invokes `csc` or `mcs`
directly. The following options and configure flags have been removed:
`--csharp-compiler`, `--csharp-compiler-type`, `--output-csharp-compiler`,
`--output-csharp-compiler-type`, and `--with-csharp-compiler`. Mono and
the .NET Framework runtime are no longer supported.

* The `--use-subdirs` and `--use-grade-subdirs` options now cause `.mh` files
to be placed in a `Mercury/mhs` subdirectory instead of the current
directory. This reduces clutter in the current directory, but may require
Expand Down Expand Up @@ -1963,6 +1970,59 @@ Portability improvements
* We have updated the script `tools/configure_cross` to support
cross-compiling using clang.

* The `csharp` grade now targets .NET 10 (or later) and C# 14.
The previous Mono and .NET Framework code paths have been removed.
When configure detects a `dotnet` command and a .NET 10+ SDK with
Roslyn (`<sdk>/Roslyn/bincore/csc.dll`), it is preferred over a
stand-alone `csc` or `mcs`; users may force this selection with
`--with-csharp-compiler=dotnet`.

* `mmc --grade csharp --make` now produces its output by generating
a `<MainModule>.csproj` next to the linked target and invoking
`dotnet build` once. MSBuild emits the managed `<MainModule>.dll`,
the apphost binary (`<MainModule>.exe` on Windows; renamed to
`<MainModule>.exe` on Linux and macOS to match Mercury's existing
csharp_executable file-name convention), and the runtimeconfig.json.
As a result, the wrapper shell script and the `MONO_PATH`
environment-variable threading have been removed.

* Library targets in `csharp` grade now carry
`<IsTrimmable>true</IsTrimmable>` so consumers may publish trimmed
binaries with `<PublishTrimmed>true</PublishTrimmed>` from their
own `.csproj`.

* The new `--csharp-aot` option opts a `csharp_executable` build into
Native AOT publishing: `mmc` switches the link step from
`dotnet build` to `dotnet publish -p:PublishAot=true -r <rid>`, and
the generated csproj sets `<PublishAot>`, `<SelfContained>`,
`<InvariantGlobalization>` and a `<RuntimeIdentifier>` derived from
the target architecture. The result is a single self-contained
native binary with no managed `.dll`, `runtimeconfig.json` or
`deps.json` companions. The user is responsible for ensuring no
module reachable from main consumes dynamic RTTI (`type_desc`,
`construct`, `deconstruct`, `term_to_xml`, generic `io.write` or
`compare_representation`) and that every linked Mercury library was
built AOT-compatible. For library targets the option only adds an
`<IsAotCompatible>true</IsAotCompatible>` marker to the csproj;
the build flow is unchanged.

* The C# implementation of `library/io.file.m` has been modernised:
the obsolete `Directory.CreateDirectory(string, DirectorySecurity)`
overload (removed in .NET 5) is gone, the Code Access Security
`SecurityPermission.Demand()` calls have been replaced with
no-ops (CAS is no longer enforced under modern .NET), and the
`__MonoCS__` preprocessor guards around the libc `mkdir` P/Invoke
have been removed in favour of `RuntimeInformation.IsOSPlatform`.

* The C# backend has gained real implementations for several
previously-stubbed standard library predicates: `math.fma/3` and
`math.have_fma/0` now use `System.Math.FusedMultiplyAdd`,
`benchmarking.report_stats/3` and `benchmarking.report_full_memory_stats/2`
now report real GC and process memory information, and
`builtin.tuple_arity/2`, `builtin.tuple_arg/3`, and
`builtin.compare_representation_3_p_0/3` no longer throw a
`Sorry, not implemented' exception.

Changes to the extras distribution
----------------------------------

Expand Down
1 change: 0 additions & 1 deletion browser/MDB_FLAGS.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
--c-include-directory ../mdbcomp/Mercury/mhs
--c-include-directory ../mdbcomp/Mercury/mihs
--csharp-flag -keyfile:../mercury.snk
@CSHARP_DELAYSIGN_FLAG@
-L../boehm_gc
-L../runtime
-L../library
Expand Down
28 changes: 5 additions & 23 deletions compiler/check_options.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
compilation_target::out, word_size::out, gc_method::out,
termination_norm::out, termination_norm::out, trace_level::out,
trace_suppress_items::out, ssdb_trace_level::out, may_be_thread_safe::out,
c_compiler_type::out, csharp_compiler_type::out,
c_compiler_type::out,
reuse_strategy::out,
maybe(feedback_info)::out, env_type::out, env_type::out, env_type::out,
limit_error_contexts_map::out, linked_target_ext_info_map::out,
Expand Down Expand Up @@ -73,7 +73,7 @@

check_option_values(!OptionTable, Target, WordSize, GC_Method,
TermNorm, Term2Norm, TraceLevel, TraceSuppress, SSTraceLevel,
MaybeThreadSafe, C_CompilerType, CSharp_CompilerType,
MaybeThreadSafe, C_CompilerType,
ReuseStrategy, MaybeFeedbackInfo,
HostEnvType, SystemEnvType, TargetEnvType,
LimitErrorContextsMap, LinkExtMap, !:Specs, !IO) :-
Expand All @@ -85,7 +85,7 @@
check_debug_options(!.OptionTable,
TraceLevel, TraceSuppress, SSTraceLevel, !Specs),
check_system_env_options(!.OptionTable,
C_CompilerType, CSharp_CompilerType,
C_CompilerType,
HostEnvType, SystemEnvType, TargetEnvType, !Specs),
check_hlds_dump_options(!OptionTable, !Specs),
check_diagnostics_options(!.OptionTable, LimitErrorContextsMap, !Specs),
Expand Down Expand Up @@ -335,11 +335,11 @@
).

:- pred check_system_env_options(option_table::in,
c_compiler_type::out, csharp_compiler_type::out,
c_compiler_type::out,
env_type::out, env_type::out, env_type::out,
list(error_spec)::in, list(error_spec)::out) is det.

check_system_env_options(OptionTable, C_CompilerType, CSharp_CompilerType,
check_system_env_options(OptionTable, C_CompilerType,
HostEnvType, SystemEnvType, TargetEnvType, !Specs) :-
lookup_string_option(OptionTable, c_compiler_type, C_CompilerTypeStr),
( if convert_c_compiler_type(C_CompilerTypeStr, C_CompilerTypePrime) then
Expand All @@ -363,24 +363,6 @@
add_error(phase_options, CCTpec, !Specs)
),

lookup_string_option(OptionTable, csharp_compiler_type,
CSharp_CompilerTypeStr),
( if
convert_csharp_compiler_type(CSharp_CompilerTypeStr,
CSharp_CompilerTypePrime)
then
CSharp_CompilerType = CSharp_CompilerTypePrime
else
CSharp_CompilerType = csharp_unknown, % dummy
CSCSpec =
[words("Invalid argument"), quote(CSharp_CompilerTypeStr),
words("to the"), quote("--csharp-compiler-type"), words("option;"),
words("must be")] ++
quote_list_to_pieces("or", ["microsoft", "mono", "unknown"]) ++
[suffix("."), nl],
add_error(phase_options, CSCSpec, !Specs)
),

lookup_string_option(OptionTable, host_env_type, HostEnvTypeStr),
( if convert_env_type(HostEnvTypeStr, HostEnvTypePrime) then
HostEnvType = HostEnvTypePrime
Expand Down
7 changes: 6 additions & 1 deletion compiler/file_names.m
Original file line number Diff line number Diff line change
Expand Up @@ -2058,7 +2058,12 @@
;
DirComponents = [_ | _],
Components = DirComponents ++ [CurDirFileName],
FullFileName = dir.relative_path_name_from_components(Components)
% Always use "/" to glue the components together. The result is
% consumed by makefiles (which require "/" on every platform) and
% by the filesystem (Windows accepts "/" as a path separator).
% Using dir.relative_path_name_from_components/1 would emit "\"
% on Windows, which mmake's pattern rules fail to match.
FullFileName = string.join_list("/", Components)
).

%---------------------------------------------------------------------------%
Expand Down
Loading