Skip to content

Commit dd59b6e

Browse files
committed
whatever
0 parents  commit dd59b6e

10 files changed

+968
-0
lines changed

.envrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
2+
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
3+
fi
4+
5+
use flake .
6+
layout node

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.direnv/
2+
.dfx/
3+
*.wasm*
4+
*.wat
5+
*.o

LICENSE.MD

+661
Large diffs are not rendered by default.

README.md

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# A Zig Canister
2+
3+
Ben Lynn has a blog post series about "Organic Apps", he creates canisters with
4+
`clang`, and `lld` only.
5+
6+
[Quint](https://github.com/q-uint) and [Enzo](https://github.com/EnzoPlayer0ne), over beers, thought it would be fun to create a canister in zig, so
7+
we looked at
8+
Ben's [Hello, Internet Computer](https://fxa77-fiaaa-aaaae-aaana-cai.raw.ic0.app/organic/hello.html)
9+
blog post and decided to port it to Zig the following day.
10+
11+
Anyways, here is the C code from Ben:
12+
```c
13+
#define IMPORT(m,n) __attribute__((import_module(m))) __attribute__((import_name(n)));
14+
#define EXPORT(n) asm(n) __attribute__((visibility("default")))
15+
16+
void reply_append(void*, unsigned) IMPORT("ic0", "msg_reply_data_append");
17+
void reply (void) IMPORT("ic0", "msg_reply");
18+
19+
void go() EXPORT("canister_query hi");
20+
21+
void go() {
22+
char msg[] = "Hello, World!\n";
23+
reply_append(msg, sizeof(msg) - 1);
24+
reply();
25+
}
26+
27+
```
28+
29+
And here is our zig code:
30+
```zig
31+
extern "ic0" fn msg_reply_data_append(ptr: [*]const u8, len: usize) void;
32+
extern "ic0" fn msg_reply() void;
33+
34+
comptime {
35+
@export(go, .{ .name = "canister_query hi", .linkage = .strong });
36+
}
37+
38+
fn go() callconv(.C) void {
39+
const msg = "Hello, World!\n";
40+
msg_reply_data_append(msg, msg.len);
41+
msg_reply();
42+
}
43+
```
44+
45+
Let's compile the C code:
46+
```sh
47+
# we compile the object file
48+
clang --target=wasm32 -c -O3 src/main.c -o main_c.o
49+
# we link the object file
50+
wasm-ld --no-entry --export-dynamic --allow-undefined main_c.o -o main_c.wasm
51+
# translate to wat
52+
wasm2wat main_c.wasm > main_c.wat
53+
# we strip the wasm binary
54+
wasm-strip main_c.wasm
55+
```
56+
57+
Let's compile the Zig code:
58+
```sh
59+
# we compile zig
60+
zig build-exe src/main.zig -target wasm32-freestanding -fno-entry -fstrip --export="canister_query hi"
61+
# translate to wat
62+
wasm2wat main.wasm > main.wat
63+
```
64+
65+
Now let's compare the two WASMs:
66+
```sh
67+
enzo@merlaux:~/code/zig-cdk$ ll *.wasm
68+
-rwxrwxr-x 1 enzo enzo 181 May 22 18:03 main.wasm*
69+
-rwxrwxr-x 1 enzo enzo 308 May 22 18:04 main_c.wasm*
70+
```
71+
72+
We have created a 181 bytes canister in Zig. Let us see if it works. First we need to create a minimal `dfx.json`:
73+
```json
74+
{
75+
"canisters": {
76+
"hello_zig": {
77+
"type": "custom",
78+
"build": "",
79+
"candid": "not.did",
80+
"wasm": "main.wasm"
81+
}
82+
}
83+
}
84+
```
85+
86+
Then let's create a minimal candid file `not.did`:
87+
```
88+
service: {}
89+
```
90+
91+
We check if it actually works, start-up `dfx`:
92+
```sh
93+
dfx start
94+
dfx deploy
95+
```
96+
97+
Now, let's call the canister:
98+
```sh
99+
enzo@merlaux:~/code/zig-cdk$ dfx canister call hello hi --output raw | xxd -r -p
100+
WARN: Cannot fetch Candid interface for hi, sending arguments with inferred types.
101+
Hello, World!
102+
```
103+
104+
IT WORKS!
105+
106+
## Dev
107+
108+
To replicate the code, install `nix` and `direnv`:
109+
110+
- Install nix with [Determinate Systems Nix Installer](https://github.com/DeterminateSystems/nix-installer)
111+
112+
```sh
113+
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
114+
```
115+
116+
- Install direnv - [installation page](https://direnv.net/docs/installation.html)
117+
118+
- [hook direnv into your shell](https://direnv.net/docs/hook.html)
119+
120+
Run `direnv allow`. You should be all set-up!

dfx.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"canisters": {
3+
"hello_zig": {
4+
"type": "custom",
5+
"build": "",
6+
"candid": "not.did",
7+
"wasm": "main.wasm"
8+
}
9+
}
10+
}

flake.lock

+61
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
description = "devenv";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
};
8+
9+
outputs =
10+
{
11+
self,
12+
nixpkgs,
13+
flake-utils,
14+
...
15+
}:
16+
flake-utils.lib.eachDefaultSystem (
17+
system:
18+
let
19+
pkgs = import nixpkgs { inherit system; };
20+
21+
dfxSrc =
22+
if system == "x86_64-linux" then
23+
{
24+
url = "https://github.com/dfinity/sdk/releases/download/0.19.0/dfx-0.19.0-x86_64-linux.tar.gz";
25+
sha256 = "c40387d13ab6ed87349fa21a98835f8d384f867333806ee6b9025891ed96e5c5";
26+
}
27+
else if system == "x86_64-darwin" || system == "aarch64-darwin" then
28+
{
29+
url = "https://github.com/dfinity/sdk/releases/download/0.19.0/dfx-0.19.0-x86_64-darwin.tar.gz";
30+
sha256 = "f61179fa9884f111dbec20c293d77472dcf66d04b0567818fe546437aadd8ce6";
31+
}
32+
else
33+
{ };
34+
35+
dfx = pkgs.stdenv.mkDerivation {
36+
name = "dfx-${system}";
37+
src = pkgs.fetchurl dfxSrc;
38+
39+
buildInputs = [
40+
pkgs.gnutar
41+
pkgs.gzip
42+
];
43+
44+
unpackPhase = "tar -xzf $src";
45+
46+
installPhase = ''
47+
mkdir -p $out/bin
48+
cp dfx $out/bin/
49+
'';
50+
};
51+
in
52+
{
53+
devShell = pkgs.mkShell {
54+
buildInputs = with pkgs; [
55+
# ic
56+
dfx
57+
58+
# zig
59+
zig
60+
61+
# wasm
62+
wabt
63+
64+
# to compile C code
65+
llvmPackages.bintools
66+
llvmPackages.libclang.lib
67+
llvmPackages.clang
68+
69+
# nix
70+
nixfmt-rfc-style
71+
];
72+
73+
shellHook = ''
74+
echo "welcome to your nix shell"
75+
'';
76+
};
77+
}
78+
);
79+
}

not.did

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
service: {}

src/main.c

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#define IMPORT(m,n) __attribute__((import_module(m))) __attribute__((import_name(n)));
2+
#define EXPORT(n) asm(n) __attribute__((visibility("default")))
3+
4+
void reply_append(void*, unsigned) IMPORT("ic0", "msg_reply_data_append");
5+
void reply (void) IMPORT("ic0", "msg_reply");
6+
7+
void go() EXPORT("canister_query hi");
8+
9+
void go() {
10+
char msg[] = "Hello, World!\n";
11+
reply_append(msg, sizeof(msg) - 1);
12+
reply();
13+
}

src/main.zig

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extern "ic0" fn msg_reply_data_append(ptr: [*]const u8, len: usize) void;
2+
extern "ic0" fn msg_reply() void;
3+
4+
comptime {
5+
@export(go, .{ .name = "canister_query hi", .linkage = .strong });
6+
}
7+
8+
fn go() callconv(.C) void {
9+
const msg = "Hello, World!\n";
10+
msg_reply_data_append(msg, msg.len);
11+
msg_reply();
12+
}

0 commit comments

Comments
 (0)