Skip to content

Commit

Permalink
Initial support for RISC-V (JuliaLang#56105)
Browse files Browse the repository at this point in the history
Rebase and extension of @alexfanqi's initial work on porting Julia to
RISC-V. Requires LLVM 19.

Tested on a VisionFive2, built with:

```make
MARCH := rv64gc_zba_zbb
MCPU := sifive-u74

USE_BINARYBUILDER:=0

DEPS_GIT = llvm
override LLVM_VER=19.1.1
override LLVM_BRANCH=julia-release/19.x
override LLVM_SHA1=julia-release/19.x
```

```julia-repl
❯ ./julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.0-DEV.1374 (2024-10-14)
 _/ |\__'_|_|_|\__'_|  |  riscv/25092a3982* (fork: 1 commits, 0 days)
|__/                   |

julia> versioninfo(; verbose=true)
Julia Version 1.12.0-DEV.1374
Commit 25092a3* (2024-10-14 09:57 UTC)
Platform Info:
  OS: Linux (riscv64-unknown-linux-gnu)
  uname: Linux 6.11.3-1-riscv64 #1 SMP Debian 6.11.3-1 (2024-10-10) riscv64 unknown
  CPU: unknown:
              speed         user         nice          sys         idle          irq
       #1  1500 MHz        922 s          0 s        265 s     160953 s          0 s
       #2  1500 MHz        457 s          0 s        280 s     161521 s          0 s
       #3  1500 MHz        452 s          0 s        270 s     160911 s          0 s
       #4  1500 MHz        638 s         15 s        301 s     161340 s          0 s
  Memory: 7.760246276855469 GB (7474.08203125 MB free)
  Uptime: 16260.13 sec
  Load Avg:  0.25  0.23  0.1
  WORD_SIZE: 64
  LLVM: libLLVM-19.1.1 (ORCJIT, sifive-u74)
Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)
Environment:
  HOME = /home/tim
  PATH = /home/tim/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/games
  TERM = xterm-256color


julia> ccall(:jl_dump_host_cpu, Nothing, ())
CPU: sifive-u74
Features: +zbb,+d,+i,+f,+c,+a,+zba,+m,-zvbc,-zksed,-zvfhmin,-zbkc,-zkne,-zksh,-zfh,-zfhmin,-zknh,-v,-zihintpause,-zicboz,-zbs,-zvknha,-zvksed,-zfa,-ztso,-zbc,-zvknhb,-zihintntl,-zknd,-zvbb,-zbkx,-zkt,-zvkt,-zicond,-zvksh,-zvfh,-zvkg,-zvkb,-zbkb,-zvkned


julia> @code_native debuginfo=:none 1+2.
	.text
	.attribute	4, 16
	.attribute	5, "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0_zba1p0_zbb1p0"
	.file	"+"
	.globl	"julia_+_3003"
	.p2align	1
	.type	"julia_+_3003",@function
"julia_+_3003":
	addi	sp, sp, -16
	sd	ra, 8(sp)
	sd	s0, 0(sp)
	addi	s0, sp, 16
	fcvt.d.l	fa5, a0
	ld	ra, 8(sp)
	ld	s0, 0(sp)
	fadd.d	fa0, fa5, fa0
	addi	sp, sp, 16
	ret
.Lfunc_end0:
	.size	"julia_+_3003", .Lfunc_end0-"julia_+_3003"

	.type	".L+Core.Float64#3005",@object
	.section	.data.rel.ro,"aw",@progbits
	.p2align	3, 0x0
".L+Core.Float64#3005":
	.quad	".L+Core.Float64#3005.jit"
	.size	".L+Core.Float64#3005", 8

.set ".L+Core.Float64#3005.jit", 272467692544
	.size	".L+Core.Float64#3005.jit", 8
	.section	".note.GNU-stack","",@progbits
```

Lots of bugs guaranteed, but with this we at least have a functional
build and REPL for further development by whoever is interested.

Also requires Linux 6.4+, since the fallback processor detection
used here relies on LLVM's `sys::getHostCPUFeatures`, which for
RISC-V is implemented using hwprobe introduced in 6.4. We could
probably add a fallback that parses `/proc/cpuinfo`, either by building
a CPU database much like how we've done for AArch64, or by parsing the
actual ISA string contained there. That would probably also be a good
place to add support for profiles, which are supposedly the way forward
to package RISC-V binaries. That can happen in follow-up PRs though.
For now, on older kernels, use the `-C` arg to Julia to specify an ISA.

Co-authored-by: Alex Fan <[email protected]>
  • Loading branch information
maleadt and alexfanqi authored Oct 16, 2024
1 parent 6ee784d commit a98f371
Show file tree
Hide file tree
Showing 26 changed files with 609 additions and 20 deletions.
13 changes: 12 additions & 1 deletion Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -938,8 +938,12 @@ endif

#If nothing is set default to native unless we are cross-compiling
ifeq ($(MARCH)$(MCPU)$(MTUNE)$(JULIA_CPU_TARGET)$(XC_HOST),)
ifeq ($(ARCH),aarch64) #ARM recommends only setting MCPU for AArch64
ifeq ($(ARCH),aarch64)
# ARM recommends only setting MCPU for AArch64
MCPU=native
else ifneq (,$(findstring riscv64,$(ARCH)))
# RISC-V doesn't have a native option
$(error Building for RISC-V requires a specific MARCH to be set))
else
MARCH=native
MTUNE=native
Expand Down Expand Up @@ -995,6 +999,9 @@ endif
ifneq (,$(findstring arm,$(ARCH)))
DIST_ARCH:=arm
endif
ifneq (,$(findstring riscv64,$(ARCH)))
DIST_ARCH:=riscv64
endif

JULIA_BINARYDIST_FILENAME := julia-$(JULIA_COMMIT)-$(DIST_OS)$(DIST_ARCH)
endif
Expand All @@ -1018,8 +1025,12 @@ ifneq ($(MARCH),)
CC += -march=$(MARCH)
CXX += -march=$(MARCH)
FC += -march=$(MARCH)
# On RISC-V, don't forward the MARCH ISA string to JULIA_CPU_TARGET,
# as it's always incompatible with LLVM's CPU target name parser.
ifeq (,$(findstring riscv64,$(ARCH)))
JULIA_CPU_TARGET ?= $(MARCH)
endif
endif

# Set MCPU-specific flags
ifneq ($(MCPU),)
Expand Down
5 changes: 4 additions & 1 deletion base/binaryplatforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ const arch_mapping = Dict(
"armv7l" => "arm(v7l)?", # if we just see `arm-linux-gnueabihf`, we assume it's `armv7l`
"armv6l" => "armv6l",
"powerpc64le" => "p(ower)?pc64le",
"riscv64" => "riscv64",
"riscv64" => "(rv64|riscv64)",
)
# Keep this in sync with `CPUID.ISAs_by_family`
# These are the CPUID side of the microarchitectures targeted by GCC flags in BinaryBuilder.jl
Expand Down Expand Up @@ -631,6 +631,9 @@ const arch_march_isa_mapping = let
"a64fx" => get_set("aarch64", "a64fx"),
"apple_m1" => get_set("aarch64", "apple_m1"),
],
"riscv64" => [
"riscv64" => get_set("riscv64", "riscv64")
],
"powerpc64le" => [
"power8" => get_set("powerpc64le", "power8"),
],
Expand Down
3 changes: 3 additions & 0 deletions base/cpuid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ const ISAs_by_family = Dict(
"a64fx" => ISA(Set((JL_AArch64_v8_2a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_sha2, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fullfp16, JL_AArch64_sve))),
"apple_m1" => ISA(Set((JL_AArch64_v8_5a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2, JL_AArch64_sha3, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fp16fml, JL_AArch64_fullfp16, JL_AArch64_dotprod, JL_AArch64_rcpc, JL_AArch64_altnzcv))),
],
"riscv64" => [
"riscv64" => ISA(Set{UInt32}()),
],
"powerpc64le" => [
# We have no way to test powerpc64le features yet, so we're only going to declare the lowest ISA:
"power8" => ISA(Set{UInt32}()),
Expand Down
20 changes: 20 additions & 0 deletions cli/trampolines/trampolines_riscv64.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file is a part of Julia. License is MIT: https://julialang.org/license

#include "common.h"
#include "../../src/jl_exported_funcs.inc"

#define SEP ;

#define XX(name) \
.global CNAME(name) SEP \
.cfi_startproc SEP \
.p2align 2 SEP \
CNAME(name)##: SEP \
auipc t3, %pcrel_hi(CNAMEADDR(name)) SEP \
ld t3, %pcrel_lo(CNAME(name))(t3) SEP \
jr t3 SEP \
.cfi_endproc SEP \

JL_RUNTIME_EXPORTED_FUNCS(XX)
JL_CODEGEN_EXPORTED_FUNCS(XX)
#undef XX
9 changes: 6 additions & 3 deletions contrib/generate_precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,15 @@ if Artifacts !== nothing
using Artifacts, Base.BinaryPlatforms, Libdl
artifacts_toml = abspath(joinpath(Sys.STDLIB, "Artifacts", "test", "Artifacts.toml"))
artifact_hash("HelloWorldC", artifacts_toml)
oldpwd = pwd(); cd(dirname(artifacts_toml))
macroexpand(Main, :(@artifact_str("HelloWorldC")))
cd(oldpwd)
artifacts = Artifacts.load_artifacts_toml(artifacts_toml)
platforms = [Artifacts.unpack_platform(e, "HelloWorldC", artifacts_toml) for e in artifacts["HelloWorldC"]]
best_platform = select_platform(Dict(p => triplet(p) for p in platforms))
if best_platform !== nothing
# @artifact errors for unsupported platforms
oldpwd = pwd(); cd(dirname(artifacts_toml))
macroexpand(Main, :(@artifact_str("HelloWorldC")))
cd(oldpwd)
end
dlopen("libjulia$(Base.isdebugbuild() ? "-debug" : "")", RTLD_LAZY | RTLD_DEEPBIND)
"""
end
Expand Down
1 change: 1 addition & 0 deletions contrib/normalize_triplet.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
'i686': "i\\d86",
'aarch64': "(arm|aarch)64",
'armv7l': "arm(v7l)?",
'riscv64': "(rv64|riscv64)",
'powerpc64le': "p(ower)?pc64le",
}
platform_mapping = {
Expand Down
1 change: 1 addition & 0 deletions doc/src/devdocs/build/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Notes for various operating systems:
Notes for various architectures:

* [ARM](https://github.com/JuliaLang/julia/blob/master/doc/src/devdocs/build/arm.md)
* [RISC-V](https://github.com/JuliaLang/julia/blob/master/doc/src/devdocs/build/riscv.md)

## Required Build Tools and External Libraries

Expand Down
103 changes: 103 additions & 0 deletions doc/src/devdocs/build/riscv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# RISC-V (Linux)

Julia has experimental support for 64-bit RISC-V (RV64) processors running
Linux. This file provides general guidelines for compilation, in addition to
instructions for specific devices.

A list of [known issues](https://github.com/JuliaLang/julia/labels/system:riscv)
for RISC-V is available. If you encounter difficulties, please create an issue
including the output from `cat /proc/cpuinfo`.


## Compiling Julia

For now, Julia will need to be compiled entirely from source, i.e., including
all of its dependencies. This can be accomplished with the following
`Make.user`:

```make
USE_BINARYBUILDER := 0
```

Additionally, it is required to indicate what architecture, and optionally which
CPU to build for. This can be done by setting the `MARCH` and `MCPU` variables
in `Make.user`

The `MARCH` variable needs to be set to a RISC-V ISA string, which can be found by
looking at the documentation of your device, or by inspecting `/proc/cpuinfo`. Only
use flags that your compiler supports, e.g., run `gcc -march=help` to see a list of
supported flags. A common value is `rv64gc`, which is a good starting point.

The `MCPU` variable is optional, and can be used to further optimize the
generated code for a specific CPU. If you are unsure, it is recommended to leave
it unset. You can find a list of supported values by running `gcc --target-help`.

For example, if you are using a StarFive VisionFive2, which contains a JH7110
processor based on the SiFive U74, you can set these flags as follows:

```make
MARCH := rv64gc_zba_zbb
MCPU := sifive-u74
```

If you prefer a portable build, you could use:

```make
MARCH := rv64gc

# also set JULIA_CPU_TARGET to the expanded form of rv64gc
# (it normally copies the value of MCPU, which we don't set)
JULIA_CPU_TARGET := generic-rv64,i,m,a,f,d,zicsr,zifencei,c
```

### Cross-compilation

A native build on a RISC-V device may take a very long time, so it's also
possible to cross-compile Julia on a faster machine.

First, get a hold of a RISC-V cross-compilation toolchain that provides
support for C, C++ and Fortran. This can be done by checking-out the
[riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain)
repository and building it as follows:

```sh
sudo mkdir /opt/riscv && sudo chown $USER /opt/riscv
./configure --prefix=/opt/riscv --with-languages=c,c++,fortran
make linux -j$(nproc)
```

Then, install the QEMU user-mode emulator for RISC-V, along with `binfmt`
support to enable execution of RISC-V binaries on the host machine. The
exact steps depend on your distribution, e.g., on Arch Linux it involves
installing the `qemu-user-static` and `qemu-user-static-binfmt` packages.
Note that to actually execute RISC-V binaries, QEMU will need to be able to
find the RISC-V system root, which can be achieved by setting the
`QEMU_LD_PREFIX` environment variable to the path of the root filesystem.

Finally, compile Julia with the following `Make.user` variables (in addition to
the ones from the previous section):

```make
XC_HOST=riscv64-unknown-linux-gnu
OS=Linux
export QEMU_LD_PREFIX=/opt/riscv/sysroot
```

Note that you will have to execute `make` with `PATH` set to include the
cross-compilation toolchain, e.g., by running:

```sh
PATH=/opt/riscv/bin:$PATH make -j$(nproc)
```

Because of the RISC-V sysroot we use being very barren, you may need to
add additional libraries that the Julia build system currently expects
to be available system-wide. For example, the build currently relies on
a system-provided `libz`, so you may need to copy this library from the
Julia build into the system root:

```sh
make -C deps install-zlib
cp -v usr/lib/libz.* /opt/riscv/sysroot/usr/lib
cp -v usr/include/z*.h /opt/riscv/sysroot/usr/include
```
Loading

0 comments on commit a98f371

Please sign in to comment.