Skip to content

Commit a98f371

Browse files
maleadtalexfanqi
andauthored
Initial support for RISC-V (JuliaLang#56105)
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 mmtk#2 1500 MHz 457 s 0 s 280 s 161521 s 0 s mmtk#3 1500 MHz 452 s 0 s 270 s 160911 s 0 s mmtk#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]>
1 parent 6ee784d commit a98f371

26 files changed

+609
-20
lines changed

Make.inc

+12-1
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,12 @@ endif
938938

939939
#If nothing is set default to native unless we are cross-compiling
940940
ifeq ($(MARCH)$(MCPU)$(MTUNE)$(JULIA_CPU_TARGET)$(XC_HOST),)
941-
ifeq ($(ARCH),aarch64) #ARM recommends only setting MCPU for AArch64
941+
ifeq ($(ARCH),aarch64)
942+
# ARM recommends only setting MCPU for AArch64
942943
MCPU=native
944+
else ifneq (,$(findstring riscv64,$(ARCH)))
945+
# RISC-V doesn't have a native option
946+
$(error Building for RISC-V requires a specific MARCH to be set))
943947
else
944948
MARCH=native
945949
MTUNE=native
@@ -995,6 +999,9 @@ endif
995999
ifneq (,$(findstring arm,$(ARCH)))
9961000
DIST_ARCH:=arm
9971001
endif
1002+
ifneq (,$(findstring riscv64,$(ARCH)))
1003+
DIST_ARCH:=riscv64
1004+
endif
9981005

9991006
JULIA_BINARYDIST_FILENAME := julia-$(JULIA_COMMIT)-$(DIST_OS)$(DIST_ARCH)
10001007
endif
@@ -1018,8 +1025,12 @@ ifneq ($(MARCH),)
10181025
CC += -march=$(MARCH)
10191026
CXX += -march=$(MARCH)
10201027
FC += -march=$(MARCH)
1028+
# On RISC-V, don't forward the MARCH ISA string to JULIA_CPU_TARGET,
1029+
# as it's always incompatible with LLVM's CPU target name parser.
1030+
ifeq (,$(findstring riscv64,$(ARCH)))
10211031
JULIA_CPU_TARGET ?= $(MARCH)
10221032
endif
1033+
endif
10231034

10241035
# Set MCPU-specific flags
10251036
ifneq ($(MCPU),)

base/binaryplatforms.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ const arch_mapping = Dict(
597597
"armv7l" => "arm(v7l)?", # if we just see `arm-linux-gnueabihf`, we assume it's `armv7l`
598598
"armv6l" => "armv6l",
599599
"powerpc64le" => "p(ower)?pc64le",
600-
"riscv64" => "riscv64",
600+
"riscv64" => "(rv64|riscv64)",
601601
)
602602
# Keep this in sync with `CPUID.ISAs_by_family`
603603
# These are the CPUID side of the microarchitectures targeted by GCC flags in BinaryBuilder.jl
@@ -631,6 +631,9 @@ const arch_march_isa_mapping = let
631631
"a64fx" => get_set("aarch64", "a64fx"),
632632
"apple_m1" => get_set("aarch64", "apple_m1"),
633633
],
634+
"riscv64" => [
635+
"riscv64" => get_set("riscv64", "riscv64")
636+
],
634637
"powerpc64le" => [
635638
"power8" => get_set("powerpc64le", "power8"),
636639
],

base/cpuid.jl

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ const ISAs_by_family = Dict(
6161
"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))),
6262
"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))),
6363
],
64+
"riscv64" => [
65+
"riscv64" => ISA(Set{UInt32}()),
66+
],
6467
"powerpc64le" => [
6568
# We have no way to test powerpc64le features yet, so we're only going to declare the lowest ISA:
6669
"power8" => ISA(Set{UInt32}()),

cli/trampolines/trampolines_riscv64.S

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
#include "common.h"
4+
#include "../../src/jl_exported_funcs.inc"
5+
6+
#define SEP ;
7+
8+
#define XX(name) \
9+
.global CNAME(name) SEP \
10+
.cfi_startproc SEP \
11+
.p2align 2 SEP \
12+
CNAME(name)##: SEP \
13+
auipc t3, %pcrel_hi(CNAMEADDR(name)) SEP \
14+
ld t3, %pcrel_lo(CNAME(name))(t3) SEP \
15+
jr t3 SEP \
16+
.cfi_endproc SEP \
17+
18+
JL_RUNTIME_EXPORTED_FUNCS(XX)
19+
JL_CODEGEN_EXPORTED_FUNCS(XX)
20+
#undef XX

contrib/generate_precompile.jl

+6-3
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,15 @@ if Artifacts !== nothing
202202
using Artifacts, Base.BinaryPlatforms, Libdl
203203
artifacts_toml = abspath(joinpath(Sys.STDLIB, "Artifacts", "test", "Artifacts.toml"))
204204
artifact_hash("HelloWorldC", artifacts_toml)
205-
oldpwd = pwd(); cd(dirname(artifacts_toml))
206-
macroexpand(Main, :(@artifact_str("HelloWorldC")))
207-
cd(oldpwd)
208205
artifacts = Artifacts.load_artifacts_toml(artifacts_toml)
209206
platforms = [Artifacts.unpack_platform(e, "HelloWorldC", artifacts_toml) for e in artifacts["HelloWorldC"]]
210207
best_platform = select_platform(Dict(p => triplet(p) for p in platforms))
208+
if best_platform !== nothing
209+
# @artifact errors for unsupported platforms
210+
oldpwd = pwd(); cd(dirname(artifacts_toml))
211+
macroexpand(Main, :(@artifact_str("HelloWorldC")))
212+
cd(oldpwd)
213+
end
211214
dlopen("libjulia$(Base.isdebugbuild() ? "-debug" : "")", RTLD_LAZY | RTLD_DEEPBIND)
212215
"""
213216
end

contrib/normalize_triplet.py

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
'i686': "i\\d86",
1515
'aarch64': "(arm|aarch)64",
1616
'armv7l': "arm(v7l)?",
17+
'riscv64': "(rv64|riscv64)",
1718
'powerpc64le': "p(ower)?pc64le",
1819
}
1920
platform_mapping = {

doc/src/devdocs/build/build.md

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Notes for various operating systems:
148148
Notes for various architectures:
149149

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

152153
## Required Build Tools and External Libraries
153154

doc/src/devdocs/build/riscv.md

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# RISC-V (Linux)
2+
3+
Julia has experimental support for 64-bit RISC-V (RV64) processors running
4+
Linux. This file provides general guidelines for compilation, in addition to
5+
instructions for specific devices.
6+
7+
A list of [known issues](https://github.com/JuliaLang/julia/labels/system:riscv)
8+
for RISC-V is available. If you encounter difficulties, please create an issue
9+
including the output from `cat /proc/cpuinfo`.
10+
11+
12+
## Compiling Julia
13+
14+
For now, Julia will need to be compiled entirely from source, i.e., including
15+
all of its dependencies. This can be accomplished with the following
16+
`Make.user`:
17+
18+
```make
19+
USE_BINARYBUILDER := 0
20+
```
21+
22+
Additionally, it is required to indicate what architecture, and optionally which
23+
CPU to build for. This can be done by setting the `MARCH` and `MCPU` variables
24+
in `Make.user`
25+
26+
The `MARCH` variable needs to be set to a RISC-V ISA string, which can be found by
27+
looking at the documentation of your device, or by inspecting `/proc/cpuinfo`. Only
28+
use flags that your compiler supports, e.g., run `gcc -march=help` to see a list of
29+
supported flags. A common value is `rv64gc`, which is a good starting point.
30+
31+
The `MCPU` variable is optional, and can be used to further optimize the
32+
generated code for a specific CPU. If you are unsure, it is recommended to leave
33+
it unset. You can find a list of supported values by running `gcc --target-help`.
34+
35+
For example, if you are using a StarFive VisionFive2, which contains a JH7110
36+
processor based on the SiFive U74, you can set these flags as follows:
37+
38+
```make
39+
MARCH := rv64gc_zba_zbb
40+
MCPU := sifive-u74
41+
```
42+
43+
If you prefer a portable build, you could use:
44+
45+
```make
46+
MARCH := rv64gc
47+
48+
# also set JULIA_CPU_TARGET to the expanded form of rv64gc
49+
# (it normally copies the value of MCPU, which we don't set)
50+
JULIA_CPU_TARGET := generic-rv64,i,m,a,f,d,zicsr,zifencei,c
51+
```
52+
53+
### Cross-compilation
54+
55+
A native build on a RISC-V device may take a very long time, so it's also
56+
possible to cross-compile Julia on a faster machine.
57+
58+
First, get a hold of a RISC-V cross-compilation toolchain that provides
59+
support for C, C++ and Fortran. This can be done by checking-out the
60+
[riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain)
61+
repository and building it as follows:
62+
63+
```sh
64+
sudo mkdir /opt/riscv && sudo chown $USER /opt/riscv
65+
./configure --prefix=/opt/riscv --with-languages=c,c++,fortran
66+
make linux -j$(nproc)
67+
```
68+
69+
Then, install the QEMU user-mode emulator for RISC-V, along with `binfmt`
70+
support to enable execution of RISC-V binaries on the host machine. The
71+
exact steps depend on your distribution, e.g., on Arch Linux it involves
72+
installing the `qemu-user-static` and `qemu-user-static-binfmt` packages.
73+
Note that to actually execute RISC-V binaries, QEMU will need to be able to
74+
find the RISC-V system root, which can be achieved by setting the
75+
`QEMU_LD_PREFIX` environment variable to the path of the root filesystem.
76+
77+
Finally, compile Julia with the following `Make.user` variables (in addition to
78+
the ones from the previous section):
79+
80+
```make
81+
XC_HOST=riscv64-unknown-linux-gnu
82+
OS=Linux
83+
export QEMU_LD_PREFIX=/opt/riscv/sysroot
84+
```
85+
86+
Note that you will have to execute `make` with `PATH` set to include the
87+
cross-compilation toolchain, e.g., by running:
88+
89+
```sh
90+
PATH=/opt/riscv/bin:$PATH make -j$(nproc)
91+
```
92+
93+
Because of the RISC-V sysroot we use being very barren, you may need to
94+
add additional libraries that the Julia build system currently expects
95+
to be available system-wide. For example, the build currently relies on
96+
a system-provided `libz`, so you may need to copy this library from the
97+
Julia build into the system root:
98+
99+
```sh
100+
make -C deps install-zlib
101+
cp -v usr/lib/libz.* /opt/riscv/sysroot/usr/lib
102+
cp -v usr/include/z*.h /opt/riscv/sysroot/usr/include
103+
```

0 commit comments

Comments
 (0)