Skip to content

Commit

Permalink
Port QuickJS on ckb-vm
Browse files Browse the repository at this point in the history
including following features:
* CKB syscall bindings
* File system and JavaScript module
* Bytecode
* Documents
* Tests
  • Loading branch information
XuJiandong committed Oct 11, 2023
0 parents commit 98e5383
Show file tree
Hide file tree
Showing 91 changed files with 90,500 additions and 0 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on: [push, pull_request]

jobs:
linux:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install llvm 16
run: sudo apt-get purge --auto-remove llvm python3-lldb-14 llvm-14 && wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 16
- name: Build
run: make all
- name: Install tools
run: make install
- name: Tests
run: make test

macos:
runs-on: macos-latest

steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install LLVM and Clang
run: brew install llvm@16
- name: Build
run: export PATH="/usr/local/opt/llvm/bin:$PATH" && make all
55 changes: 55 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

.vscode

7 changes: 7 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

[submodule "deps/ckb-c-stdlib"]
path = deps/ckb-c-stdlib
url = https://github.com/nervosnetwork/ckb-c-stdlib.git
[submodule "deps/compiler-rt-builtins-riscv"]
path = deps/compiler-rt-builtins-riscv
url = https://github.com/nervosnetwork/compiler-rt-builtins-riscv.git
92 changes: 92 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

CC := clang-16
LD := ld.lld-16
OBJCOPY := llvm-objcopy-16
AR := llvm-ar-16
RANLIB := llvm-ranlib-16

UNAME := $(shell uname)
ifeq ($(UNAME), Darwin)
LD := ld.lld
OBJCOPY := llvm-objcopy
RANLIB := llvm-ranlib
endif

CFLAGS := --target=riscv64 -march=rv64imc_zba_zbb_zbc_zbs
CFLAGS += -g -Os \
-Wall -Werror -Wno-nonnull -Wno-unused-function \
-fno-builtin-printf -fno-builtin-memcmp \
-nostdinc -nostdlib\
-fdata-sections -ffunction-sections

CFLAGS += -I deps/ckb-c-stdlib/libc -I deps/ckb-c-stdlib
CFLAGS += -I include -I include/c-stdlib
CFLAGS += -I deps/compiler-rt-builtins-riscv/compiler-rt/lib/builtins

CFLAGS += -Wextra -Wno-sign-compare -Wno-missing-field-initializers -Wundef -Wuninitialized\
-Wunused -Wno-unused-parameter -Wchar-subscripts -funsigned-char -Wno-unused-function \
-DCONFIG_VERSION=\"2021-03-27-CKB\"
CFLAGS += -Wno-incompatible-library-redeclaration -Wno-implicit-const-int-float-conversion -Wno-invalid-noreturn

CFLAGS += -DCKB_DECLARATION_ONLY
CFLAGS += -D__BYTE_ORDER=1234 -D__LITTLE_ENDIAN=1234 -D__ISO_C_VISIBLE=1999 -D__GNU_VISIBLE
CFLAGS += -DCKB_MALLOC_DECLARATION_ONLY -DCKB_PRINTF_DECLARATION_ONLY -DCONFIG_BIGNUM

LDFLAGS := -static --gc-sections
LDFLAGS += -Ldeps/compiler-rt-builtins-riscv/build -lcompiler-rt

OBJDIR=build

QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o \
$(OBJDIR)/cutils.o $(OBJDIR)/mocked.o $(OBJDIR)/std_module.o $(OBJDIR)/ckb_module.o $(OBJDIR)/ckb_cell_fs.o $(OBJDIR)/libbf.o

STD_OBJS=$(OBJDIR)/string_impl.o $(OBJDIR)/malloc_impl.o $(OBJDIR)/math_impl.o \
$(OBJDIR)/math_log_impl.o $(OBJDIR)/math_pow_impl.o $(OBJDIR)/printf_impl.o $(OBJDIR)/stdio_impl.o \
$(OBJDIR)/locale_impl.o


all: build/ckb-js-vm

deps/compiler-rt-builtins-riscv/build/libcompiler-rt.a:
cd deps/compiler-rt-builtins-riscv && make

build/ckb-js-vm: $(STD_OBJS) $(QJS_OBJS) $(OBJDIR)/impl.o deps/compiler-rt-builtins-riscv/build/libcompiler-rt.a
$(LD) $(LDFLAGS) -o $@ $^
cp $@ $@.debug
$(OBJCOPY) --strip-debug --strip-all $@
ls -lh build/ckb-js-vm

$(OBJDIR)/%.o: quickjs/%.c
@echo build $<
@$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: include/c-stdlib/src/%.c
@echo build $<
@$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: include/%.c
@echo build $<
@$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/impl.o: deps/ckb-c-stdlib/libc/src/impl.c
@echo build $<
@$(CC) $(filter-out -DCKB_DECLARATION_ONLY, $(CFLAGS)) -c -o $@ $<

test:
make -f tests/examples/Makefile
make -f tests/basic/Makefile
cd tests/ckb_js_tests && make all

clean:
rm -f build/*.o
rm -f build/ckb-js-vm
rm -f build/ckb-js-vm.debug
cd tests/ckb_js_tests && make clean

install:
wget 'https://github.com/nervosnetwork/ckb-standalone-debugger/releases/download/v0.111.0/ckb-debugger-linux-x64.tar.gz'
tar zxvf ckb-debugger-linux-x64.tar.gz
mv ckb-debugger ~/.cargo/bin/ckb-debugger
make -f tests/ckb_js_tests/Makefile install-lua

.phony: all clean
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ckb-js-vm
The objective of this project is to develop scripts in JavaScript for CKB-VM, by
adapting [quickjs](https://bellard.org/quickjs/).


## Build
The clang version 16 is required.

```shell
git submodule update --init
make all
```

## Documents
* [Introduction](./docs/intro.md)
* [CKB Syscall Bindings](./docs/syscalls.md)
* [Simple File System and JavaScript Module](./docs/fs.md)


## Examples

* [Fibonacci Number](./tests/examples/fib.js)
* [Calculate PI](./tests/examples/pi_bigint.js)

More [tests and examples](./tests/).
1 change: 1 addition & 0 deletions build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
Empty file added build/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions deps/ckb-c-stdlib
Submodule ckb-c-stdlib added at bdc87e
1 change: 1 addition & 0 deletions deps/compiler-rt-builtins-riscv
137 changes: 137 additions & 0 deletions docs/fs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Simple File System and JavaScript Module

In addition to executing individual JavaScript files, ckb-js-vm also supports
JavaScript modules by Simple File System, files within the file system may be
made available for Javascript to read, import, and execute, e.g. import modules
by `import { * } from "./module.js";`. Each Simple File System contains at least
one entry file named `main.js`, and ckb-js-vm will load this file system from
any cell and execute `main.js` in it.

A file system is represented as a binary file in the format described below. We
may use the script [fs.lua](../tools/fs.lua) to create a file system from given
files or unpack the file system into files.

## How to create a Simple File System

Consider the following two files:

```js
// File main.js
import { fib } from "./fib_module.js";
console.log("fib(10)=", fib(10));
```

```js
// File fib_module.js
export function fib(n) {
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
```

If we want ckb-js-vm to execute this code smoothly, we must package them into a
file system first. To pack them within the current directory into `fib.fs`, you
may run
```shell
find . -name *.js -type f | lua tools/fs.lua pack fib.fs
```

```
// Output
packing file ./fib_module.js to fib_module.js
packing file ./main.js to main.js
```

Note that all file paths piped into the `fs.lua` must be in the relative path
format. The absolute path of a file in the current system is usually meaningless
in the Simple File System.

## How to deploy and use Simple File System

In most cases, it is more resource-efficient to write all JavaScript code in one
file. To enable file system support, we cannot directly use ckb-js-vm as a lock
script, ckb-js-vm must be used as an exec Or the spawn target passes the "-f"
parameter to it.

We wrote an example that uses `spawn` syscall to call ckb-js-vm to demonstrate
how to use the file system.

```sh
$ cd tests/ckb_js_tests
$ make module
```

The key is `spawn_caller`, in this example, `spawn_caller` is the real lock
script, which then calls ckb-js-vm using `spawn` and passes it the `-f`
parameter: ckb-js-vm will then run in file system mode.

## Using JavaScript bytecode to improve performance

When ckb-js-vm executes JavaScript codes, it will first compile them into
bytecode, and then interpret and execute the bytecode. To improve the
performance of ckb-js-vm, we can also choose to directly let ckb-js-vm execute
bytecode.

We define all JavaScript bytecode files to have a `.bc` extension. When
ckb-js-vm obtains a file system, it will first look for the `main.js` file; if
not, it continues to look for the `main.bc` file. When importing a module in
JavaScript codes, e.g. `import { * } from "./module.js`, the steps are similar,
ckb-js-vm will look for `./module.js` or `./module.bc`.

In general, we only need to compile all JavaScript files into corresponding
bytecode files, and then package the bytecode files just like packaging
JavaScript files.

## Unpack Simple File System to Files

To unpack the files contained within a fs, you may run `lua tools/fs.lua unpack fib.fs .`.

## Simple File System On-disk Representation

The on-disk representation of a Simple File System consists of three parts, a
number to represent the number of files contained in this file system, an array
of metadata to store file metadata and an array of binary objects (also called
blob) to store the actual file contents.

A metadata is simply an offset from the start of the blob array and a datum
length. Each file name and file content has a metadata. For each file stored in
the fs, there is four `uint32_t` number in the metadata, i.e. the offset of the
file name in the blob array, the length of the file name, the offset of the file
content in the blob array, and the length of the file content.

We represent the above structures using c struct-like syntax as follows.
```c
struct Blob {
uint32_t offset;
uint32_t length;
}

struct Metadata {
struct Blob file_name;
struct Blob file_content;
}

struct SimpleFileSystem {
uint32_t file_count;
struct Metadata metadata[..];
uint8_t payload[..];
}
```

When serializing the file system into a file, all integers are encoded as a
32-bit little-endian number. The file names are stored as null-terminated
strings.

Below is a binary dump of the file system created from a simple file called
`main.js` with content `console.log('hello world!')`.

```text
00000000 01 00 00 00 00 00 00 00 08 00 00 00 08 00 00 00 |................|
00000010 1c 00 00 00 6d 61 69 6e 2e 6a 73 00 63 6f 6e 73 |....main.js.cons|
00000020 6f 6c 65 2e 6c 6f 67 28 27 68 65 6c 6c 6f 20 77 |ole.log('hello w|
00000030 6f 72 6c 64 21 27 29 0a 00 |orld!')..|
```
Loading

0 comments on commit 98e5383

Please sign in to comment.