Skip to content

Commit

Permalink
Implement the GC in Rust
Browse files Browse the repository at this point in the history
This implements the current garbage collector in Rust. No changes were made to
the GC design -- it's just ports the one implemented in code generator to Rust.

The goals are:

- Evaluate Rust for Motoko's RTS implementation
- Make the collector easier to read, understand, modify, and extend.

Currently passes the tests locally. We can't run this branch on CI yet as it
needs to download rustc nightly and xargo and the domains are not allowed on the
CI. I think in the final version we'll have to build rustc outselves instead of
downloading.

(Nightly rustc is needed as "core" distributed with rustc is not built with PIC
relocation model on wam32, so we can't use it to generate a shared wasm32
library)

Main changes:

- New Rust crate "motoko-rts" introduced, which currently implements the
  garbage collector. It also has some utilities for printing the heap or
  individual objects, to be used when debugging.

- Nix files updated to download rustc and xargo. These are used to build Rust's
  "core" library with PIC relocation model for wasm32.

- We no longer build memset and memcpy of musl as those are provided by Rust's
  "core" now.

The main algorithm is in `gc.rs`. Rest of the Rust files are helpers, mainly for
debugging.

Other changes:

- I had to update lots of ic-ref-run outputs. See #1854 for the details.

Remaining work and issues:

- There's currently a bug somewhere that causes random failures as I move the
  code around. One example of this is in the last line of `gc.rs` where I have a
  no-op function call `dump_heap()` which when removed causes the test "life" to
  fail with a trap.

- Figure out how to use rustc nightly (with PIC wasm32 libraries) in CI.
  • Loading branch information
osa1 committed Aug 26, 2020
1 parent 4d47198 commit fef42ad
Show file tree
Hide file tree
Showing 121 changed files with 1,257 additions and 593 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
_out
_output
_build
target

**/*~
/result*
Expand Down
18 changes: 11 additions & 7 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ let haskellPackages = nixpkgs.haskellPackages.override {
overrides = import nix/haskell-packages.nix nixpkgs subpath;
}; in
let
llvmBuildInputs = [
rtsBuildInputs = [
nixpkgs.clang_10 # for native/wasm building
nixpkgs.lld_10 # for wasm building
nixpkgs.rustc-nightly
nixpkgs.cargo-nightly
nixpkgs.xargo
];

# When compiling natively, we want to use `clang` (which is a nixpkgs
# provided wrapper that sets various include paths etc).
# But for some reason it does not handle building for Wasm well, so
# there we use plain clang-10. There is no stdlib there anyways.
llvmEnv = ''
# When compiling natively, we want to use `clang` (which is a nixpkgs
# provided wrapper that sets various include paths etc).
# But for some reason it does not handle building for Wasm well, so
# there we use plain clang-10. There is no stdlib there anyways.
export CLANG="${nixpkgs.clang_10}/bin/clang"
export WASM_CLANG="clang-10"
export WASM_LD=wasm-ld
Expand Down Expand Up @@ -126,7 +129,7 @@ rec {
src = subpath ./rts;
nativeBuildInputs = [ nixpkgs.makeWrapper ];

buildInputs = llvmBuildInputs;
buildInputs = rtsBuildInputs;

preBuild = ''
${llvmEnv}
Expand Down Expand Up @@ -200,7 +203,7 @@ rec {
wasmtime
nixpkgs.sources.esm
] ++
llvmBuildInputs;
rtsBuildInputs;

checkPhase = ''
patchShebangs .
Expand Down Expand Up @@ -329,6 +332,7 @@ rec {
[ { name = "bin/FileCheck"; path = "${nixpkgs.llvm}/bin/FileCheck";} ];
wabt = nixpkgs.wabt;
wasmtime = nixpkgs.wasmtime;
xargo = nixpkgs.xargo;
wasm = nixpkgs.wasm;

overview-slides = stdenv.mkDerivation {
Expand Down
38 changes: 36 additions & 2 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,30 @@ let
import nixpkgs_src {
inherit system;
overlays = [
(self: super: { sources = import sourcesnix { sourcesFile = ./sources.json; pkgs = super; }; })
# rust nightly
(self: super: let
moz_overlay = import (self.fetchzip {
url = https://github.com/mozilla/nixpkgs-mozilla/archive/efda5b357451dbb0431f983cca679ae3cd9b9829.tar.gz;
sha256 = "11wqrg86g3qva67vnk81ynvqyfj0zxk83cbrf0p9hsvxiwxs8469";
}) self super;
rust-channel = moz_overlay.rustChannelOf { date = "2020-07-22"; channel = "nightly"; };
in rec {
rustc-nightly = rust-channel.rust.override {
targets = [ "wasm32-unknown-unknown" "wasm32-unknown-emscripten" ];
extensions = ["rust-src"];
};
cargo-nightly = rustc-nightly;
rustPlatform-nightly = pkgs.makeRustPlatform {
rustc = rustc-nightly;
cargo = cargo-nightly;
};
})

# add nix/sources.json
(self: super: {
sources = import sourcesnix { sourcesFile = ./sources.json; pkgs = super; };
})

# Selecting the ocaml version
# (self: super: { ocamlPackages = super.ocamlPackages; })
(
Expand All @@ -30,10 +53,21 @@ let
inherit (self) ocamlPackages;
};
};
# wasmtime
wasmtime = self.callPackage ./wasmtime.nix {};
xargo = self.callPackage ./xargo.nix {};
}
)
# nixpkgs's rustc does not include the wasm32-unknown-unknown target, so
# lets add it here.
# (self: super: {
# rustc = super.rustc.overrideAttrs (old: {
# configureFlags = self.lib.lists.forEach old.configureFlags (flag:
# if self.lib.strings.hasPrefix "--target=" flag
# then flag + ",wasm32-unknown-unknown,wasm32-unknown-emscripten"
# else flag
# );
# });
# })
];
};
in
Expand Down
42 changes: 42 additions & 0 deletions nix/xargo.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# xargo is used to build motoko-rts for wasm32. We need to make a shared Wasm
# library for the RTS (that's what moc-ld supports) but Rust ships wasm32
# libraries (core and std) without PIC relocation model, so we use xargo to make
# PIC versions of core and std.

{ rustPlatform-nightly, fetchFromGitHub, lib, python, cmake, llvmPackages, clang, stdenv, darwin, zlib }:

rustPlatform-nightly.buildRustPackage rec {
name = "xargo";

src = fetchFromGitHub {
owner = "japaric";
repo = "${name}";
rev = "16035a7c401262824edcb87e1401fe4b05a5ccc0";
sha256 = "0m1dg7vwmmlpqp20p219gsm7zbnnii6lik6hc2vvfsdmnygf271l";
fetchSubmodules = true;
};

cargoSha256 = "0zzksgi2prgw01m6r4bqjjz902h5g5ich0h3xvb60w4sshlss891";

# nativeBuildInputs = [ python cmake clang ];
# buildInputs = [ llvmPackages.libclang ] ++
# lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Security ];
# LIBCLANG_PATH = "${llvmPackages.libclang}/lib";

doCheck = false;
# error: couldn't lock thumbv6m-panic_abort-eabi's sysroot as read-only
USER = "nobody"; # for xargo tests (if we would run them)

meta = with lib; {
description = "The sysroot manager that lets you build and customize std";
homepage = "https://github.com/japaric/xargo";
license = licenses.mit;
maintainers = [ {
email = "omer.agacan@dfinity.org";
github = "osa1";
githubId = 123123;
name = "Ömer Sinan Ağacan";
} ];
platforms = platforms.unix;
};
}
30 changes: 24 additions & 6 deletions rts/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
SHELL:=bash -O globstar

CLANG ?= clang-10
WASM_CLANG ?= clang-10
WASM_LD ?= wasm-ld-10
Expand All @@ -19,7 +21,7 @@ TOMMATHFILES = \
MUSLFILES = \
pow pow_data sin cos tan asin acos atan atan2 exp exp_data log log_data fmod \
towctrans iswspace iswupper iswlower iswalpha wcschr wcslen \
floor scalbn frexp strnlen memchr memset memcpy snprintf vsnprintf vfprintf \
floor scalbn frexp strnlen memchr snprintf vsnprintf vfprintf \
__math_oflow __math_uflow __math_xflow __math_divzero __math_invalid \
__rem_pio2 __rem_pio2_large __sin __cos __tan \
stubs
Expand Down Expand Up @@ -154,29 +156,45 @@ _build/native/tommath_%.o: %.c rts.h buf.h | _build/native
_build/wasm/musl_%.o: %.c | _build/wasm
$(WASM_CLANG) $(CLANG_FLAGS) $(WASM_FLAGS) $(MUSL_FLAGS) $< --output $@

.PHONY: _build/wasm/libmotoko_rts.a
_build/wasm/libmotoko_rts.a: | _build/wasm
# NB. codegen-units=1 is to make debugging easier, not strictly
# necessary
cd motoko-rts && \
xargo rustc --target=wasm32-unknown-emscripten --release -- \
-Crelocation-model=pic -Ccodegen-units=1
cp motoko-rts/target/wasm32-unknown-emscripten/release/libmotoko_rts.a $@

.PHONY: _build/native/libmotoko_rts.a
_build/native/libmotoko_rts.a: | _build/native
cd motoko-rts && cargo build --release
cp motoko-rts/target/release/libmotoko_rts.a $@

RTS_WASM_O=$(RTSFILES:%=_build/wasm/%.o)
RTS_NATIVE_O=$(RTSFILES:%=_build/native/%.o)
RTS_RUST_WASM_O=_build/wasm/libmotoko_rts.a
RTS_RUST_NATIVE_O=_build/native/libmotoko_rts.a

#
# The actual RTS, as we ship it with the compiler
#

mo-rts.wasm: $(RTS_WASM_O) $(TOMMATH_WASM_O) $(MUSL_WASM_O)
mo-rts.wasm: $(RTS_WASM_O) $(RTS_RUST_WASM_O) $(TOMMATH_WASM_O) $(MUSL_WASM_O)
$(WASM_LD) -o $@ \
--import-memory --shared --no-entry --gc-sections \
--export=__wasm_call_ctors \
--whole-archive \
$+

#
# A simple program to do simple tests of rts.c, using native compilation
#

test_rts: test_rts.c $(RTS_NATIVE_O) $(TOMMATH_NATIVE_O)
test_rts: test_rts.c $(RTS_NATIVE_O) $(RTS_RUST_NATIVE_O) $(TOMMATH_NATIVE_O)
$(CLANG) -o $@ $+

test_leb128: test_leb128.c $(RTS_NATIVE_O) $(TOMMATH_NATIVE_O)
test_leb128: test_leb128.c $(RTS_NATIVE_O) $(RTS_RUST_NATIVE_O) $(TOMMATH_NATIVE_O)
$(CLANG) -o $@ $+

clean:
rm -rf _build mo-rts.wasm test_rts test_leb128

rm -rf _build mo-rts.wasm test_rts test_leb128 motoko-rts/target
4 changes: 4 additions & 0 deletions rts/motoko-rts/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# NB. codegen-units=1 is not necessary, but it generates less .o files for core,
# std, and compiler_builtins and makes it easier to find symbols.
[build]
rustflags = ["-Crelocation-model=pic", "-Ccodegen-units=1"]
8 changes: 8 additions & 0 deletions rts/motoko-rts/.vim/coc-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// https://github.com/rust-analyzer/rust-analyzer/blob/master/editors/code/package.json
{
"rust-analyzer.cargo.target": "wasm32-unknown-emscripten",

// This is required as `cargo check --all-targets` doesn't seem to work well
// on no-std crates, it generates false "duplicate lang item" errors.
"rust-analyzer.checkOnSave.allTargets": false
}
14 changes: 14 additions & 0 deletions rts/motoko-rts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions rts/motoko-rts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "motoko-rts"
version = "0.1.0"
authors = ["Ömer Sinan Ağacan <omeragacan@gmail.com>"]
edition = "2018"

[dependencies]
libc = "0.2.73"

[lib]
crate-type = ["staticlib"]

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
41 changes: 41 additions & 0 deletions rts/motoko-rts/src/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Implements allocation routines used by the generated code and the GC.

use core::arch::wasm32;

use crate::common::rts_trap_with;
use crate::gc;
use crate::types::*;

#[no_mangle]
pub unsafe extern "C" fn alloc_bytes(n: Bytes<u32>) -> SkewedPtr {
alloc_words(bytes_to_words(n))
}

#[no_mangle]
pub unsafe extern "C" fn alloc_words(n: Words<u32>) -> SkewedPtr {
let bytes = words_to_bytes(n);
// Update ALLOCATED
gc::ALLOCATED.0 += bytes.0 as u64;

// Update heap pointer
let old_hp = gc::get_hp();
let new_hp = old_hp + bytes.0 as usize;
gc::set_hp(new_hp);

// Grow memory if needed
grow_memory(new_hp);

skew(old_hp)
}

/// Page allocation. Ensures that the memory up to the given pointer is allocated.
pub(crate) unsafe fn grow_memory(ptr: usize) {
let total_pages_needed = ((ptr / 65536) + 1) as i32;
let current_pages = wasm32::memory_size(0) as i32;
let new_pages_needed = total_pages_needed - current_pages;
if new_pages_needed > 0 {
if wasm32::memory_grow(0, new_pages_needed as usize) == core::usize::MAX {
rts_trap_with("Cannot grow memory\0".as_ptr());
}
}
}
3 changes: 3 additions & 0 deletions rts/motoko-rts/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extern "C" {
pub fn rts_trap_with(msg: *const u8) -> !;
}
Loading

0 comments on commit fef42ad

Please sign in to comment.