mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
wasm: add a webassembly compiler backend, based on using binaryen (#17368)
This commit is contained in:
parent
b9a8a21094
commit
0625caad56
|
@ -10,7 +10,6 @@ on:
|
|||
- 'vlib/builtin/**.v'
|
||||
- 'vlib/v/ast/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/parser/**.v'
|
||||
- 'vlib/v/checker/**.v'
|
||||
- 'vlib/v/gen/c/**.v'
|
||||
|
@ -31,7 +30,6 @@ on:
|
|||
- 'vlib/builtin/**.v'
|
||||
- 'vlib/v/ast/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/parser/**.v'
|
||||
- 'vlib/v/checker/**.v'
|
||||
- 'vlib/v/gen/c/**.v'
|
||||
|
|
95
.github/workflows/wasm_backend_tests_ci.yml
vendored
Normal file
95
.github/workflows/wasm_backend_tests_ci.yml
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
name: wasm backend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '!**'
|
||||
- '!**.md'
|
||||
- 'cmd/tools/builders/**.v'
|
||||
- 'vlib/builtin/**.v'
|
||||
- 'vlib/v/ast/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/parser/**.v'
|
||||
- 'vlib/v/checker/**.v'
|
||||
- 'vlib/v/gen/c/**.v'
|
||||
- 'vlib/v/builder/**.v'
|
||||
- 'vlib/v/cflag/**.v'
|
||||
- 'vlib/v/live/**.v'
|
||||
- 'vlib/v/util/**.v'
|
||||
- 'vlib/v/markused/**.v'
|
||||
- 'vlib/v/preludes/**.v'
|
||||
- 'vlib/v/gen/wasm/**.v'
|
||||
- 'vlib/v/gen/wasm/tests/**.v'
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '!**.md'
|
||||
- 'cmd/tools/builders/**.v'
|
||||
- 'vlib/builtin/**.v'
|
||||
- 'vlib/v/ast/**.v'
|
||||
- 'vlib/v/scanner/**.v'
|
||||
- 'vlib/v/parser/**.v'
|
||||
- 'vlib/v/checker/**.v'
|
||||
- 'vlib/v/gen/c/**.v'
|
||||
- 'vlib/v/builder/**.v'
|
||||
- 'vlib/v/cflag/**.v'
|
||||
- 'vlib/v/live/**.v'
|
||||
- 'vlib/v/util/**.v'
|
||||
- 'vlib/v/markused/**.v'
|
||||
- 'vlib/v/preludes/**.v'
|
||||
- 'vlib/v/gen/wasm/**.v'
|
||||
- 'vlib/v/gen/wasm/tests/**.v'
|
||||
|
||||
concurrency:
|
||||
group: wasm-backend-ci-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
wasm-backend-ubuntu:
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
||||
timeout-minutes: 121
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --quiet -y clang gcc
|
||||
|
||||
- name: Build V
|
||||
run: make -j4 && ./v symlink -githubci
|
||||
|
||||
- name: Install binaryen as build dependency for the V WASM backend
|
||||
run: ./v cmd/tools/install_binaryen.vsh
|
||||
|
||||
- name: Build the V WASM backend
|
||||
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
|
||||
- name: Test the WASM backend
|
||||
run: ./v test vlib/v/gen/wasm/tests/
|
||||
|
||||
- name: Build examples
|
||||
run: VTEST_ONLY=wasm ./v build-examples
|
||||
|
||||
## wasm-backend-macos:
|
||||
## runs-on: macOS-12
|
||||
## if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
||||
## timeout-minutes: 121
|
||||
## steps:
|
||||
## - uses: actions/checkout@v3
|
||||
##
|
||||
## - name: Build V
|
||||
## run: make -j4 && ./v symlink -githubci
|
||||
##
|
||||
## - name: Install binaryen as build dependency for the V WASM backend
|
||||
## run: ./v cmd/tools/install_binaryen.vsh
|
||||
##
|
||||
## - name: Build the V WASM backend
|
||||
## run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
##
|
||||
## - name: Test the WASM backend
|
||||
## run: ./v test vlib/v/gen/wasm/tests/
|
||||
##
|
||||
## - name: Build examples
|
||||
## run: VTEST_ONLY=wasm ./v build-examples
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -108,6 +108,7 @@ flake.nix
|
|||
.envrc
|
||||
|
||||
thirdparty/stdatomic/nix/cpp/*.h
|
||||
thirdparty/binaryen*
|
||||
|
||||
# ignore VLS log
|
||||
vls.log
|
||||
|
@ -120,3 +121,4 @@ vls.log
|
|||
# ignore Intellij files
|
||||
.idea/
|
||||
/*.iml
|
||||
wasm.v
|
||||
|
|
7
cmd/tools/builders/wasm_builder.v
Normal file
7
cmd/tools/builders/wasm_builder.v
Normal file
|
@ -0,0 +1,7 @@
|
|||
module main
|
||||
|
||||
import v.builder.wasmbuilder
|
||||
|
||||
fn main() {
|
||||
wasmbuilder.start()
|
||||
}
|
68
cmd/tools/install_binaryen.vsh
Executable file
68
cmd/tools/install_binaryen.vsh
Executable file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env -S v -raw-vsh-tmp-prefix tmp
|
||||
|
||||
import net.http
|
||||
import json
|
||||
import os
|
||||
|
||||
struct JQ {
|
||||
tag_name string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
root := os.real_path(os.dir(os.getenv_opt('VEXE') or { @VEXE }))
|
||||
os.chdir(root)! // make sure that the workfolder is stable
|
||||
|
||||
tloc := os.join_path(root, 'thirdparty')
|
||||
loc := os.join_path(tloc, 'binaryen')
|
||||
|
||||
if os.exists(loc) {
|
||||
eprintln('thirdparty/binaryen exists, will not overwrite')
|
||||
eprintln('delete the folder, and execute again')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
jq := http.get_text('https://api.github.com/repos/WebAssembly/binaryen/releases/latest')
|
||||
tag := json.decode(JQ, jq)!.tag_name
|
||||
|
||||
name := $if windows {
|
||||
'x86_64-windows'
|
||||
} $else $if macos {
|
||||
$if arm64 {
|
||||
'arm64-macos'
|
||||
} $else {
|
||||
'x86_64-macos'
|
||||
}
|
||||
} $else $if linux {
|
||||
'x86_64-linux'
|
||||
} $else {
|
||||
eprintln('A premade binary library is not available for your system.')
|
||||
eprintln('Build it from source, following the documentation here: https://github.com/WebAssembly/binaryen/#building')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fname := 'binaryen-${tag}'
|
||||
url := 'https://github.com/WebAssembly/binaryen/releases/download/${tag}/${fname}-${name}.tar.gz'
|
||||
|
||||
saveloc := os.join_path(tloc, '${fname}.tar.gz')
|
||||
if !os.exists(saveloc) {
|
||||
println('Downloading archive: ${saveloc}, from url: ${url} ...')
|
||||
http.download_file(url, saveloc)!
|
||||
// defer { os.rm(saveloc) or {}! }
|
||||
}
|
||||
|
||||
mkdir_all(loc)!
|
||||
println(loc)
|
||||
|
||||
println('Extracting `${tloc}/${fname}` to `${tloc}/binaryen` ...')
|
||||
cmd := 'tar -xvf ${saveloc} --directory ${tloc}'
|
||||
if os.system(cmd) != 0 {
|
||||
eprintln('`${cmd}` exited with a non zero exit code')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
println(cmd)
|
||||
println('Moving `${tloc}/${fname}` to `${tloc}/binaryen` ...')
|
||||
|
||||
os.rename_dir('${tloc}/${fname}', loc)!
|
||||
println('Done. You can now use `v -b wasm file.v` .')
|
||||
}
|
|
@ -252,6 +252,18 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
|
|||
$if !macos {
|
||||
skip_files << 'examples/macos_tray/tray.v'
|
||||
}
|
||||
// examples/wasm/mandelbrot/mandelbrot.v requires special compilation flags: `-b wasm -os browser`, skip it for now:
|
||||
skip_files << 'examples/wasm/mandelbrot/mandelbrot.v'
|
||||
|
||||
// TODO: always build the wasm_builder in the future, not just when it was build manually before:
|
||||
wasm_builder_executable := $if !windows {
|
||||
'cmd/tools/builders/wasm_builder'
|
||||
} $else {
|
||||
'cmd/tools/builders/wasm_builder.exe'
|
||||
}
|
||||
if !os.exists(wasm_builder_executable) {
|
||||
skip_files << os.join_path('cmd/tools/builders/wasm_builder.v')
|
||||
}
|
||||
}
|
||||
vargs := _vargs.replace('-progress', '')
|
||||
vexe := pref.vexe_path()
|
||||
|
|
|
@ -3,7 +3,7 @@ module main
|
|||
import os
|
||||
import testing
|
||||
|
||||
const vroot = @VMODROOT
|
||||
const vroot = os.dir(os.real_path(os.getenv_opt('VEXE') or { @VEXE }))
|
||||
|
||||
// build as a project folder
|
||||
const efolders = [
|
||||
|
@ -12,11 +12,14 @@ const efolders = [
|
|||
'examples/vweb_fullstack',
|
||||
]
|
||||
|
||||
pub fn normalised_vroot_path(path string) string {
|
||||
return os.real_path(os.join_path_single(vroot, path)).replace('\\', '/')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
args_string := os.args[1..].join(' ')
|
||||
params := args_string.all_before('build-examples')
|
||||
skip_prefixes := efolders.map(os.real_path(os.join_path_single(vroot, it)).replace('\\',
|
||||
'/'))
|
||||
mut skip_prefixes := efolders.map(normalised_vroot_path(it))
|
||||
res := testing.v_build_failing_skipped(params, 'examples', skip_prefixes, fn (mut session testing.TestSession) {
|
||||
for x in efolders {
|
||||
pathsegments := x.split_any('/')
|
||||
|
|
|
@ -329,6 +329,10 @@ fn main() {
|
|||
tsession.skip_files << 'vlib/net/udp_test.v'
|
||||
}
|
||||
|
||||
if !os.exists('cmd/tools/builders/wasm_builder') {
|
||||
tsession.skip_files << 'vlib/v/gen/wasm/tests/wasm_test.v'
|
||||
}
|
||||
|
||||
mut werror := false
|
||||
mut sanitize_memory := false
|
||||
mut sanitize_address := false
|
||||
|
|
14
cmd/v/v.v
14
cmd/v/v.v
|
@ -192,5 +192,19 @@ fn rebuild(prefs &pref.Preferences) {
|
|||
println('using Go WIP backend...')
|
||||
util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..])
|
||||
}
|
||||
.wasm {
|
||||
assert_wasm_backend_thirdparty()
|
||||
util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_wasm_backend_thirdparty() {
|
||||
vroot := os.dir(pref.vexe_path())
|
||||
if !os.exists('${vroot}/cmd/tools/builders/wasm_builder')
|
||||
&& !os.exists('${vroot}/thirdparty/binaryen') {
|
||||
eprintln('The WebAssembly backend requires `binaryen`, an external library dependency')
|
||||
eprintln('This can be installed with `./cmd/tools/install_binaryen.vsh`, to download prebuilt libraries for your platform')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
|
1
examples/wasm/.gitignore
vendored
Normal file
1
examples/wasm/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.wasm
|
57
examples/wasm/functions.v
Normal file
57
examples/wasm/functions.v
Normal file
|
@ -0,0 +1,57 @@
|
|||
// `pub` functions will be exported.
|
||||
//
|
||||
// ```
|
||||
// v -b wasm -no-builtin functions.v
|
||||
// wasmer functions.wasm -i main.powi <a> <b>
|
||||
// wasmer functions.wasm -i main.gcd <a> <b>
|
||||
// ```
|
||||
|
||||
pub fn gcd(a_ i64, b_ i64) i64 {
|
||||
mut a := a_
|
||||
mut b := b_
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
for b != 0 {
|
||||
a %= b
|
||||
if a == 0 {
|
||||
return b
|
||||
}
|
||||
b %= a
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
pub fn powi(a i64, b i64) i64 {
|
||||
mut b_ := b
|
||||
mut p := a
|
||||
mut v := i64(1)
|
||||
|
||||
if b_ < 0 { // exponent < 0
|
||||
if a == 0 {
|
||||
return -1 // division by 0
|
||||
}
|
||||
return if a * a != 1 {
|
||||
0
|
||||
} else {
|
||||
if (b_ & 1) > 0 {
|
||||
a
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for b_ > 0 {
|
||||
if b_ & 1 > 0 {
|
||||
v *= p
|
||||
}
|
||||
p *= p
|
||||
b_ >>= 1
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
9
examples/wasm/hello_world.v
Normal file
9
examples/wasm/hello_world.v
Normal file
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// $ v -b wasm hello_world.v
|
||||
// $ wasmer hello_world.wasm
|
||||
// Hello WASI!
|
||||
//
|
||||
|
||||
fn main() {
|
||||
println('Hello WASI!')
|
||||
}
|
12
examples/wasm/mandelbrot/README.md
Normal file
12
examples/wasm/mandelbrot/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# V Mandelbrot Example
|
||||
|
||||
1. First, create `mandelbrot.wasm`. Compile with `-os browser`.
|
||||
|
||||
```
|
||||
v -b wasm -os browser mandelbrot.v
|
||||
```
|
||||
|
||||
2. Then, open the `mandelbrot.html` file in the browser.
|
||||
- CORS errors do not allow `mandelbrot.wasm` to be loaded.
|
||||
- Use `python -m http.server 8080`
|
||||
- Use `emrun mandelbrot.html`
|
47
examples/wasm/mandelbrot/mandelbrot.html
Normal file
47
examples/wasm/mandelbrot/mandelbrot.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>V Mandelbrot WebAssembly Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="200" height="200" style="width:100%;height:100%;image-rendering: crisp-edges;"></canvas>
|
||||
<script>
|
||||
var canvas = document.getElementById("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
var memory;
|
||||
|
||||
function get_string(ptr, len) {
|
||||
const buf = new Uint8Array(memory.buffer, ptr, len);
|
||||
const str = new TextDecoder("utf8").decode(buf);
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
const env = {
|
||||
canvas_x: () => canvas.width,
|
||||
canvas_y: () => canvas.height,
|
||||
setpixel: (x, y, c) => {
|
||||
ctx.fillStyle = "rgba(1,1,1,"+(c/255)+")";
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
},
|
||||
__writeln: (ptr, len) => {
|
||||
console.log(get_string(ptr, len))
|
||||
},
|
||||
__panic_abort: (ptr, len) => {
|
||||
throw get_string(ptr, len);
|
||||
}
|
||||
}
|
||||
|
||||
WebAssembly.instantiateStreaming(fetch("mandelbrot.wasm"), {env: env}).then((res) => {
|
||||
memory = res.instance.exports['memory'];
|
||||
|
||||
console.time('main.main')
|
||||
res.instance.exports['main.main']()
|
||||
console.timeEnd('main.main')
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
41
examples/wasm/mandelbrot/mandelbrot.v
Normal file
41
examples/wasm/mandelbrot/mandelbrot.v
Normal file
|
@ -0,0 +1,41 @@
|
|||
fn JS.canvas_x() int
|
||||
fn JS.canvas_y() int
|
||||
fn JS.setpixel(x int, y int, c f64)
|
||||
|
||||
// `main` must be public!
|
||||
pub fn main() {
|
||||
max_x := JS.canvas_x()
|
||||
max_y := JS.canvas_y()
|
||||
|
||||
println('starting main.main!')
|
||||
|
||||
mut y := 0
|
||||
for y < max_y {
|
||||
y += 1
|
||||
mut x := 0
|
||||
for x < max_x {
|
||||
x += 1
|
||||
|
||||
e := (f64(y) / 50) - 1.5
|
||||
f := (f64(x) / 50) - 1.0
|
||||
|
||||
mut a := 0.0
|
||||
mut b := 0.0
|
||||
mut i := 0.0
|
||||
mut j := 0.0
|
||||
mut c := 0.0
|
||||
|
||||
for i * i + j * j < 4 && c < 255 {
|
||||
i = a * a - b * b + e
|
||||
j = 2 * a * b + f
|
||||
a = i
|
||||
b = j
|
||||
c += 1
|
||||
}
|
||||
|
||||
JS.setpixel(x, y, c)
|
||||
}
|
||||
}
|
||||
|
||||
panic('reached the end!')
|
||||
}
|
81
vlib/builtin/wasm/alloc.v
Normal file
81
vlib/builtin/wasm/alloc.v
Normal file
|
@ -0,0 +1,81 @@
|
|||
[has_globals]
|
||||
module builtin
|
||||
|
||||
// Shitty `sbrk` basic `malloc` and `free` impl
|
||||
// TODO: implement pure V `walloc` later
|
||||
|
||||
const wasm_page_size = 64 * 1024
|
||||
|
||||
__global g_heap_base = isize(0)
|
||||
|
||||
fn init() {
|
||||
g_heap_base = __memory_grow(3)
|
||||
if g_heap_base == -1 {
|
||||
panic('g_heap_base: malloc() == nil')
|
||||
}
|
||||
g_heap_base *= wasm_page_size
|
||||
}
|
||||
|
||||
// malloc dynamically allocates a `n` bytes block of memory on the heap.
|
||||
// malloc returns a `byteptr` pointing to the memory address of the allocated space.
|
||||
// unlike the `calloc` family of functions - malloc will not zero the memory block.
|
||||
[unsafe]
|
||||
pub fn malloc(n isize) &u8 {
|
||||
if n <= 0 {
|
||||
panic('malloc(n <= 0)')
|
||||
}
|
||||
|
||||
res := g_heap_base
|
||||
g_heap_base += n
|
||||
|
||||
return &u8(res)
|
||||
}
|
||||
|
||||
// free allows for manually freeing memory allocated at the address `ptr`.
|
||||
// currently does not free any memory.
|
||||
[unsafe]
|
||||
pub fn free(ptr voidptr) {
|
||||
_ := ptr
|
||||
}
|
||||
|
||||
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
|
||||
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
|
||||
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
|
||||
[unsafe]
|
||||
pub fn vcalloc(n isize) &u8 {
|
||||
if n <= 0 {
|
||||
panic('vcalloc(n <= 0)')
|
||||
} else if n == 0 {
|
||||
return &u8(0)
|
||||
}
|
||||
|
||||
res := unsafe { malloc(n) }
|
||||
|
||||
__memory_fill(res, 0, n)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// vmemcpy copies n bytes from memory area src to memory area dest.
|
||||
// The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`.
|
||||
[unsafe]
|
||||
pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr {
|
||||
__memory_copy(dest, const_src, n)
|
||||
return dest
|
||||
}
|
||||
|
||||
// vmemmove copies n bytes from memory area src to memory area dest.
|
||||
// The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`.
|
||||
[unsafe]
|
||||
pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr {
|
||||
__memory_copy(dest, const_src, n)
|
||||
return dest
|
||||
}
|
||||
|
||||
// vmemset fills the first `n` bytes of the memory area pointed to by `s`,
|
||||
// with the constant byte `c`. It returns a pointer to the memory area `s`.
|
||||
[unsafe]
|
||||
pub fn vmemset(s voidptr, c int, n isize) voidptr {
|
||||
__memory_fill(s, c, n)
|
||||
return s
|
||||
}
|
16
vlib/builtin/wasm/browser/builtin.v
Normal file
16
vlib/builtin/wasm/browser/builtin.v
Normal file
|
@ -0,0 +1,16 @@
|
|||
module builtin
|
||||
|
||||
fn JS.__panic_abort(&u8, int)
|
||||
fn JS.__writeln(&u8, int)
|
||||
|
||||
// panic calls the `__panic_abort` JS panic handler.
|
||||
[noreturn]
|
||||
pub fn panic(s string) {
|
||||
JS.__panic_abort(s.str, s.len)
|
||||
for {}
|
||||
}
|
||||
|
||||
// println prints a message with a line end, to stdout. stdout is flushed.
|
||||
pub fn println(s string) {
|
||||
JS.__writeln(s.str, s.len)
|
||||
}
|
5
vlib/builtin/wasm/builtin.v
Normal file
5
vlib/builtin/wasm/builtin.v
Normal file
|
@ -0,0 +1,5 @@
|
|||
module builtin
|
||||
|
||||
fn __memory_grow(size isize) isize
|
||||
fn __memory_fill(dest &u8, value isize, size isize)
|
||||
fn __memory_copy(dest &u8, src &u8, size isize)
|
7
vlib/builtin/wasm/string.v
Normal file
7
vlib/builtin/wasm/string.v
Normal file
|
@ -0,0 +1,7 @@
|
|||
module builtin
|
||||
|
||||
pub struct string {
|
||||
pub:
|
||||
str &u8
|
||||
len int
|
||||
}
|
61
vlib/builtin/wasm/wasi/builtin.v
Normal file
61
vlib/builtin/wasm/wasi/builtin.v
Normal file
|
@ -0,0 +1,61 @@
|
|||
module builtin
|
||||
|
||||
// print prints a message to stdout. Unlike `println` stdout is not automatically flushed.
|
||||
pub fn print(s string) {
|
||||
elm := CIOVec{
|
||||
buf: s.str
|
||||
len: usize(s.len)
|
||||
}
|
||||
|
||||
WASM.fd_write(1, &elm, 1, -1)
|
||||
}
|
||||
|
||||
// println prints a message with a line end, to stdout.
|
||||
pub fn println(s string) {
|
||||
elm := [CIOVec{
|
||||
buf: s.str
|
||||
len: usize(s.len)
|
||||
}, CIOVec{
|
||||
buf: c'\n'
|
||||
len: 1
|
||||
}]!
|
||||
|
||||
WASM.fd_write(1, &elm[0], 2, -1)
|
||||
}
|
||||
|
||||
// eprint prints a message to stderr.
|
||||
pub fn eprint(s string) {
|
||||
elm := CIOVec{
|
||||
buf: s.str
|
||||
len: usize(s.len)
|
||||
}
|
||||
|
||||
WASM.fd_write(2, &elm, 1, -1)
|
||||
}
|
||||
|
||||
// eprintln prints a message with a line end, to stderr.
|
||||
pub fn eprintln(s string) {
|
||||
elm := [CIOVec{
|
||||
buf: s.str
|
||||
len: usize(s.len)
|
||||
}, CIOVec{
|
||||
buf: c'\n'
|
||||
len: 1
|
||||
}]!
|
||||
|
||||
WASM.fd_write(2, &elm[0], 2, -1)
|
||||
}
|
||||
|
||||
// exit terminates execution immediately and returns exit `code` to the shell.
|
||||
[noreturn]
|
||||
pub fn exit(code int) {
|
||||
WASM.proc_exit(code)
|
||||
}
|
||||
|
||||
// panic prints a nice error message, then exits the process with exit code of 1.
|
||||
[noreturn]
|
||||
pub fn panic(s string) {
|
||||
eprint('V panic: ')
|
||||
eprintln(s)
|
||||
exit(1)
|
||||
}
|
232
vlib/builtin/wasm/wasi/int.v
Normal file
232
vlib/builtin/wasm/wasi/int.v
Normal file
|
@ -0,0 +1,232 @@
|
|||
module builtin
|
||||
|
||||
type byte = u8
|
||||
type i32 = int
|
||||
|
||||
const (
|
||||
// digit pairs in reverse order
|
||||
digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
|
||||
)
|
||||
|
||||
// This implementation is the quickest with gcc -O2
|
||||
// str_l returns the string representation of the integer nn with max chars.
|
||||
[direct_array_access; inline]
|
||||
fn (nn int) str_l(max int) string {
|
||||
unsafe {
|
||||
mut n := i64(nn)
|
||||
mut d := 0
|
||||
if n == 0 {
|
||||
return '0'
|
||||
}
|
||||
|
||||
mut is_neg := false
|
||||
if n < 0 {
|
||||
n = -n
|
||||
is_neg = true
|
||||
}
|
||||
mut index := max
|
||||
mut buf := malloc(max + 1)
|
||||
buf[index] = 0
|
||||
index--
|
||||
|
||||
for n > 0 {
|
||||
n1 := int(n / 100)
|
||||
// calculate the digit_pairs start index
|
||||
d = int(u32(int(n) - (n1 * 100)) << 1)
|
||||
n = n1
|
||||
buf[index] = digit_pairs.str[d]
|
||||
index--
|
||||
d++
|
||||
buf[index] = digit_pairs.str[d]
|
||||
index--
|
||||
}
|
||||
index++
|
||||
// remove head zero
|
||||
if d < 20 {
|
||||
index++
|
||||
}
|
||||
// Prepend - if it's negative
|
||||
if is_neg {
|
||||
index--
|
||||
buf[index] = `-`
|
||||
}
|
||||
diff := max - index
|
||||
vmemmove(buf, voidptr(buf + index), diff + 1)
|
||||
/*
|
||||
// === manual memory move for bare metal ===
|
||||
mut c:= 0
|
||||
for c < diff {
|
||||
buf[c] = buf[c+index]
|
||||
c++
|
||||
}
|
||||
buf[c] = 0
|
||||
*/
|
||||
return tos(buf, diff)
|
||||
|
||||
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
||||
}
|
||||
}
|
||||
|
||||
// str returns the value of the `i8` as a `string`.
|
||||
// Example: assert i8(-2).str() == '-2'
|
||||
pub fn (n i8) str() string {
|
||||
return int(n).str_l(5)
|
||||
}
|
||||
|
||||
// str returns the value of the `i16` as a `string`.
|
||||
// Example: assert i16(-20).str() == '-20'
|
||||
pub fn (n i16) str() string {
|
||||
return int(n).str_l(7)
|
||||
}
|
||||
|
||||
// str returns the value of the `u16` as a `string`.
|
||||
// Example: assert u16(20).str() == '20'
|
||||
pub fn (n u16) str() string {
|
||||
return int(n).str_l(7)
|
||||
}
|
||||
|
||||
// str returns the value of the `int` as a `string`.
|
||||
// Example: assert int(-2020).str() == '-2020'
|
||||
pub fn (n int) str() string {
|
||||
return n.str_l(12)
|
||||
}
|
||||
|
||||
// str returns the value of the `u32` as a `string`.
|
||||
// Example: assert u32(20000).str() == '20000'
|
||||
[direct_array_access; inline]
|
||||
pub fn (nn u32) str() string {
|
||||
unsafe {
|
||||
mut n := nn
|
||||
mut d := u32(0)
|
||||
if n == 0 {
|
||||
return '0'
|
||||
}
|
||||
max := 12
|
||||
mut buf := malloc(max + 1)
|
||||
mut index := max
|
||||
buf[index] = 0
|
||||
index--
|
||||
for n > 0 {
|
||||
n1 := n / u32(100)
|
||||
d = ((n - (n1 * u32(100))) << u32(1))
|
||||
n = n1
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
d++
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
}
|
||||
index++
|
||||
// remove head zero
|
||||
if d < u32(20) {
|
||||
index++
|
||||
}
|
||||
diff := max - index
|
||||
vmemmove(buf, voidptr(buf + index), diff + 1)
|
||||
return tos(buf, diff)
|
||||
|
||||
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
||||
}
|
||||
}
|
||||
|
||||
// str returns the value of the `int_literal` as a `string`.
|
||||
[inline]
|
||||
pub fn (n int_literal) str() string {
|
||||
return i64(n).str()
|
||||
}
|
||||
|
||||
// str returns the value of the `i64` as a `string`.
|
||||
// Example: assert i64(-200000).str() == '-200000'
|
||||
[direct_array_access; inline]
|
||||
pub fn (nn i64) str() string {
|
||||
unsafe {
|
||||
mut n := nn
|
||||
mut d := i64(0)
|
||||
if n == 0 {
|
||||
return '0'
|
||||
} else if n == i64(-9223372036854775807 - 1) {
|
||||
// math.min_i64
|
||||
return '-9223372036854775808'
|
||||
}
|
||||
max := 20
|
||||
mut buf := malloc(max + 1)
|
||||
mut is_neg := false
|
||||
if n < 0 {
|
||||
n = -n
|
||||
is_neg = true
|
||||
}
|
||||
mut index := max
|
||||
buf[index] = 0
|
||||
index--
|
||||
for n > 0 {
|
||||
n1 := n / i64(100)
|
||||
d = (u32(n - (n1 * i64(100))) << i64(1))
|
||||
n = n1
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
d++
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
}
|
||||
index++
|
||||
// remove head zero
|
||||
if d < i64(20) {
|
||||
index++
|
||||
}
|
||||
// Prepend - if it's negative
|
||||
if is_neg {
|
||||
index--
|
||||
buf[index] = `-`
|
||||
}
|
||||
diff := max - index
|
||||
vmemmove(buf, voidptr(buf + index), diff + 1)
|
||||
return tos(buf, diff)
|
||||
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
||||
}
|
||||
}
|
||||
|
||||
// str returns the value of the `u64` as a `string`.
|
||||
// Example: assert u64(2000000).str() == '2000000'
|
||||
[direct_array_access; inline]
|
||||
pub fn (nn u64) str() string {
|
||||
unsafe {
|
||||
mut n := nn
|
||||
mut d := u64(0)
|
||||
if n == 0 {
|
||||
return '0'
|
||||
}
|
||||
max := 20
|
||||
mut buf := malloc(max + 1)
|
||||
mut index := max
|
||||
buf[index] = 0
|
||||
index--
|
||||
for n > 0 {
|
||||
n1 := n / 100
|
||||
d = ((n - (n1 * 100)) << 1)
|
||||
n = n1
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
d++
|
||||
buf[index] = digit_pairs[d]
|
||||
index--
|
||||
}
|
||||
index++
|
||||
// remove head zero
|
||||
if d < 20 {
|
||||
index++
|
||||
}
|
||||
diff := max - index
|
||||
vmemmove(buf, voidptr(buf + index), diff + 1)
|
||||
return tos(buf, diff)
|
||||
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
||||
}
|
||||
}
|
||||
|
||||
// str returns the value of the `bool` as a `string`.
|
||||
// Example: assert (2 > 1).str() == 'true'
|
||||
pub fn (b bool) str() string {
|
||||
if b {
|
||||
return 'true'
|
||||
}
|
||||
return 'false'
|
||||
}
|
16
vlib/builtin/wasm/wasi/string.v
Normal file
16
vlib/builtin/wasm/wasi/string.v
Normal file
|
@ -0,0 +1,16 @@
|
|||
module builtin
|
||||
|
||||
// tos creates a V string, given a C style pointer to a 0 terminated block.
|
||||
// Note: the memory block pointed by s is *reused, not copied*!
|
||||
// It will panic, when the pointer `s` is 0.
|
||||
// See also `tos_clone`.
|
||||
[unsafe]
|
||||
pub fn tos(s &u8, len int) string {
|
||||
if s == 0 {
|
||||
panic('tos(): nil string')
|
||||
}
|
||||
return string{
|
||||
str: unsafe { s }
|
||||
len: len
|
||||
}
|
||||
}
|
14
vlib/builtin/wasm/wasi/wasi.v
Normal file
14
vlib/builtin/wasm/wasi/wasi.v
Normal file
|
@ -0,0 +1,14 @@
|
|||
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||
module builtin
|
||||
|
||||
struct CIOVec {
|
||||
buf &u8
|
||||
len usize
|
||||
}
|
||||
|
||||
type Errno = u16
|
||||
type FileDesc = int
|
||||
|
||||
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
|
||||
[noreturn]
|
||||
fn WASM.proc_exit(rval int)
|
|
@ -211,6 +211,24 @@ pub fn file_size(path string) u64 {
|
|||
return 0
|
||||
}
|
||||
|
||||
// rename_dir renames the folder from `src` to `dst`.
|
||||
// Use mv to move or rename a file in a platform independent manner.
|
||||
pub fn rename_dir(src string, dst string) ! {
|
||||
$if windows {
|
||||
w_src := src.replace('/', '\\')
|
||||
w_dst := dst.replace('/', '\\')
|
||||
ret := C._wrename(w_src.to_wide(), w_dst.to_wide())
|
||||
if ret != 0 {
|
||||
return error_with_code('failed to rename ${src} to ${dst}', int(ret))
|
||||
}
|
||||
} $else {
|
||||
ret := C.rename(&char(src.str), &char(dst.str))
|
||||
if ret != 0 {
|
||||
return error_with_code('failed to rename ${src} to ${dst}', ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rename renames the file or folder from `src` to `dst`.
|
||||
// Use mv to move or rename a file in a platform independent manner.
|
||||
pub fn rename(src string, dst string) ! {
|
||||
|
|
|
@ -889,6 +889,7 @@ pub:
|
|||
is_c2v_prefix bool // for `--x` (`x--$`), only for translated code until c2v can handle it
|
||||
pub mut:
|
||||
expr Expr
|
||||
typ Type
|
||||
auto_locked string
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ pub enum Language {
|
|||
v
|
||||
c
|
||||
js
|
||||
wasm
|
||||
amd64 // aka x86_64
|
||||
i386
|
||||
arm64 // 64-bit arm
|
||||
|
@ -887,6 +888,7 @@ pub fn (t &Table) type_size(typ Type) (int, int) {
|
|||
.placeholder, .void, .none_, .generic_inst {}
|
||||
.voidptr, .byteptr, .charptr, .function, .usize, .isize, .any, .thread, .chan {
|
||||
size = t.pointer_size
|
||||
align = t.pointer_size
|
||||
}
|
||||
.i8, .u8, .char, .bool {
|
||||
size = 1
|
||||
|
@ -1075,6 +1077,7 @@ pub:
|
|||
is_flag bool
|
||||
is_multi_allowed bool
|
||||
uses_exprs bool
|
||||
typ Type
|
||||
}
|
||||
|
||||
[minify]
|
||||
|
|
|
@ -197,6 +197,16 @@ pub fn (v Builder) get_builtin_files() []string {
|
|||
if v.pref.backend.is_js() {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||
'js'))
|
||||
} else if v.pref.backend == .wasm {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||
'wasm'))
|
||||
if v.pref.os == .browser {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||
'wasm', 'browser'))
|
||||
} else {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||
'wasm', 'wasi'))
|
||||
}
|
||||
} else {
|
||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin'))
|
||||
}
|
||||
|
|
36
vlib/v/builder/wasmbuilder/wasmbuilder.v
Normal file
36
vlib/v/builder/wasmbuilder/wasmbuilder.v
Normal file
|
@ -0,0 +1,36 @@
|
|||
module wasmbuilder
|
||||
|
||||
import v.pref
|
||||
import v.util
|
||||
import v.builder
|
||||
import v.gen.wasm
|
||||
|
||||
pub fn start() {
|
||||
mut args_and_flags := util.join_env_vflags_and_os_args()[1..]
|
||||
prefs, _ := pref.parse_args([], args_and_flags)
|
||||
builder.compile('build', prefs, compile_wasm)
|
||||
}
|
||||
|
||||
pub fn compile_wasm(mut b builder.Builder) {
|
||||
mut files := b.get_builtin_files()
|
||||
files << b.get_user_files()
|
||||
b.set_module_lookup_paths()
|
||||
if b.pref.is_verbose {
|
||||
println('all .v files:')
|
||||
println(files)
|
||||
}
|
||||
mut name := b.pref.out_name
|
||||
if name.ends_with('/-') || name.ends_with(r'\-') || name == '-' {
|
||||
name = '-'
|
||||
} else if !name.ends_with('.wasm') {
|
||||
name += '.wasm'
|
||||
}
|
||||
build_wasm(mut b, files, name)
|
||||
}
|
||||
|
||||
pub fn build_wasm(mut b builder.Builder, v_files []string, out_file string) {
|
||||
b.front_and_middle_stages(v_files) or { return }
|
||||
util.timing_start('WebAssembly GEN')
|
||||
wasm.gen(b.parsed_files, b.table, out_file, b.pref)
|
||||
util.timing_measure('WebAssembly GEN')
|
||||
}
|
|
@ -35,5 +35,6 @@ fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) ast.Type {
|
|||
} else {
|
||||
node.auto_locked, _ = c.fail_if_immutable(node.expr)
|
||||
}
|
||||
node.typ = typ
|
||||
return typ
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ fn (mut f Fmt) write_language_prefix(lang ast.Language) {
|
|||
match lang {
|
||||
.c { f.write('C.') }
|
||||
.js { f.write('JS.') }
|
||||
.wasm { f.write('WASM.') }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
|
3944
vlib/v/gen/wasm/binaryen/binaryen.c.v
Normal file
3944
vlib/v/gen/wasm/binaryen/binaryen.c.v
Normal file
File diff suppressed because it is too large
Load Diff
91
vlib/v/gen/wasm/cast.v
Normal file
91
vlib/v/gen/wasm/cast.v
Normal file
|
@ -0,0 +1,91 @@
|
|||
module wasm
|
||||
|
||||
import v.ast
|
||||
import v.gen.wasm.binaryen
|
||||
|
||||
fn (mut g Gen) is_signed(typ ast.Type) bool {
|
||||
if typ.is_pure_float() {
|
||||
return true
|
||||
}
|
||||
return typ.is_signed()
|
||||
}
|
||||
|
||||
fn (mut g Gen) unary_cast(from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Op {
|
||||
if is_signed {
|
||||
match from {
|
||||
type_i32 {
|
||||
match to {
|
||||
type_i64 { return binaryen.extendsint32() }
|
||||
type_f32 { return binaryen.convertsint32tofloat32() }
|
||||
type_f64 { return binaryen.convertsint32tofloat64() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
type_i64 {
|
||||
match to {
|
||||
type_i32 { return binaryen.wrapint64() }
|
||||
type_f32 { return binaryen.convertsint64tofloat32() }
|
||||
type_f64 { return binaryen.convertsint64tofloat64() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
type_f32 {
|
||||
match to {
|
||||
type_i32 { return binaryen.truncsfloat32toint32() }
|
||||
type_i64 { return binaryen.truncsfloat32toint64() }
|
||||
type_f64 { return binaryen.promotefloat32() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
type_f64 {
|
||||
match to {
|
||||
type_i32 { return binaryen.truncsfloat64toint32() }
|
||||
type_i64 { return binaryen.truncsfloat64toint64() }
|
||||
type_f32 { return binaryen.demotefloat64() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
} else {
|
||||
match from {
|
||||
type_i32 {
|
||||
match to {
|
||||
type_i64 { return binaryen.extenduint32() }
|
||||
type_f32 { return binaryen.convertuint32tofloat32() }
|
||||
type_f64 { return binaryen.convertuint32tofloat64() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
type_i64 {
|
||||
match to {
|
||||
type_i32 { return binaryen.wrapint64() }
|
||||
type_f32 { return binaryen.convertuint64tofloat32() }
|
||||
type_f64 { return binaryen.convertuint64tofloat64() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
g.w_error('bad cast: from ${from} (is signed: ${is_signed}) to ${to}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) cast_t(expr binaryen.Expression, from ast.Type, to ast.Type) binaryen.Expression {
|
||||
return g.cast(expr, g.get_wasm_type(from), g.is_signed(from), g.get_wasm_type(to))
|
||||
}
|
||||
|
||||
fn (mut g Gen) cast(expr binaryen.Expression, from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Expression {
|
||||
if from == to {
|
||||
return expr
|
||||
}
|
||||
|
||||
// In the official spec, integers are represented in twos complement.
|
||||
// WebAssembly does not keep signedness information in it's types
|
||||
// and uses instructions with variants for signed or unsigned values.
|
||||
//
|
||||
// You only need to know if the original type is signed or not to
|
||||
// perform casting.
|
||||
|
||||
return binaryen.unary(g.mod, g.unary_cast(from, is_signed, to), expr)
|
||||
}
|
1227
vlib/v/gen/wasm/gen.v
Normal file
1227
vlib/v/gen/wasm/gen.v
Normal file
File diff suppressed because it is too large
Load Diff
745
vlib/v/gen/wasm/mem.v
Normal file
745
vlib/v/gen/wasm/mem.v
Normal file
|
@ -0,0 +1,745 @@
|
|||
module wasm
|
||||
|
||||
import v.ast
|
||||
import v.gen.wasm.binaryen
|
||||
import strconv
|
||||
|
||||
type Var = Global | Stack | Temporary | ast.Ident
|
||||
|
||||
fn (v Var) ast_typ() int {
|
||||
if v is Temporary {
|
||||
return v.ast_typ
|
||||
}
|
||||
if v is Stack {
|
||||
return v.ast_typ
|
||||
}
|
||||
if v is Global {
|
||||
return v.ast_typ
|
||||
}
|
||||
panic('unreachable')
|
||||
}
|
||||
|
||||
fn (v Var) address() int {
|
||||
return (v as Stack).address
|
||||
}
|
||||
|
||||
struct Temporary {
|
||||
name string
|
||||
typ binaryen.Type
|
||||
ast_typ ast.Type
|
||||
//
|
||||
idx int
|
||||
}
|
||||
|
||||
struct Stack {
|
||||
name string
|
||||
ast_typ ast.Type
|
||||
//
|
||||
address int
|
||||
}
|
||||
|
||||
struct Global {
|
||||
name string
|
||||
ast_typ ast.Type
|
||||
//
|
||||
abs_address int
|
||||
}
|
||||
|
||||
fn (g Gen) is_pure_type(typ ast.Type) bool {
|
||||
if typ.is_pure_int() || typ.is_pure_float() || typ == ast.char_type_idx || typ.is_real_pointer()
|
||||
|| typ.is_bool() {
|
||||
return true
|
||||
}
|
||||
ts := g.table.sym(typ)
|
||||
if ts.info is ast.Alias {
|
||||
return g.is_pure_type(ts.info.parent_type)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut g Gen) new_global_const(obj ast.ScopeObject) {
|
||||
obj_expr := match obj {
|
||||
ast.ConstField { obj.expr }
|
||||
ast.GlobalField { obj.expr }
|
||||
else { panic('unreachable') }
|
||||
}
|
||||
typ := ast.mktyp(obj.typ)
|
||||
|
||||
size, align := g.get_type_size_align(typ)
|
||||
padding := (align - g.constant_data_offset % align) % align
|
||||
g.globals[obj.name] = GlobalData{
|
||||
init: obj_expr
|
||||
ast_typ: typ
|
||||
abs_address: g.constant_data_offset
|
||||
}
|
||||
g.constant_data_offset += size + padding
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_var_from_ident(ident ast.Ident) Var {
|
||||
mut obj := ident.obj
|
||||
if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] {
|
||||
obj = ident.scope.find(ident.name) or { g.w_error('unknown variable ${ident.name}') }
|
||||
}
|
||||
match mut obj {
|
||||
ast.Var {
|
||||
if obj.name !in g.local_addresses {
|
||||
return g.local_temporaries[g.get_local_temporary(obj.name)]
|
||||
}
|
||||
return g.local_addresses[obj.name]
|
||||
}
|
||||
ast.ConstField {
|
||||
if obj.name !in g.globals {
|
||||
g.new_global_const(obj)
|
||||
}
|
||||
return g.globals[obj.name].to_var(obj.name)
|
||||
}
|
||||
ast.GlobalField {
|
||||
if obj.name !in g.globals {
|
||||
g.new_global_const(obj)
|
||||
}
|
||||
return g.globals[obj.name].to_var(obj.name)
|
||||
}
|
||||
else {
|
||||
g.w_error('unsupported variable type type:${obj} name:${ident.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LocalOrPointer = Var | binaryen.Expression
|
||||
|
||||
fn (mut g Gen) get_or_lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Expression {
|
||||
size, _ := g.table.type_size(expected)
|
||||
|
||||
mut offset := 0
|
||||
mut parent_typ := expected
|
||||
mut is_expr := false
|
||||
|
||||
expr := match lp {
|
||||
binaryen.Expression {
|
||||
is_expr = true
|
||||
lp
|
||||
}
|
||||
Var {
|
||||
match lp {
|
||||
Temporary {
|
||||
parent_typ = lp.ast_typ
|
||||
binaryen.localget(g.mod, lp.idx, g.get_wasm_type(expected))
|
||||
}
|
||||
Stack {
|
||||
parent_typ = lp.ast_typ
|
||||
offset = lp.address
|
||||
g.get_bp()
|
||||
}
|
||||
Global {
|
||||
parent_typ = lp.ast_typ
|
||||
is_expr = true
|
||||
g.literalint(lp.abs_address, ast.int_type)
|
||||
}
|
||||
else {
|
||||
panic('unreachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_expr && parent_typ == expected {
|
||||
return expr
|
||||
}
|
||||
|
||||
return binaryen.load(g.mod, u32(size), g.is_signed(expected), u32(offset), 0, g.get_wasm_type(expected),
|
||||
expr, c'memory')
|
||||
}
|
||||
|
||||
fn (mut g Gen) lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Expression {
|
||||
expr := match lp {
|
||||
binaryen.Expression {
|
||||
lp
|
||||
}
|
||||
Var {
|
||||
match lp {
|
||||
Temporary {
|
||||
binaryen.localget(g.mod, lp.idx, g.get_wasm_type(expected))
|
||||
}
|
||||
Stack {
|
||||
g.get_bp()
|
||||
}
|
||||
Global {
|
||||
g.literalint(lp.abs_address, ast.int_type)
|
||||
}
|
||||
else {
|
||||
panic('unreachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expr
|
||||
}
|
||||
|
||||
fn (mut g Gen) lea_var_from_expr(node ast.Expr) binaryen.Expression {
|
||||
var := g.get_var_from_expr(node)
|
||||
|
||||
return match var {
|
||||
binaryen.Expression {
|
||||
var
|
||||
}
|
||||
Var {
|
||||
match var {
|
||||
Temporary {
|
||||
if g.is_pure_type(var.ast_typ) {
|
||||
g.w_error('lea_var_from_expr: you cannot take the address of a pure temporary')
|
||||
}
|
||||
binaryen.localget(g.mod, var.idx, type_i32)
|
||||
}
|
||||
Stack {
|
||||
g.lea_address(var.address)
|
||||
}
|
||||
Global {
|
||||
g.literalint(var.abs_address, ast.int_type)
|
||||
}
|
||||
else {
|
||||
panic('unreachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) local_or_pointer_add_offset(v Var, offset int) LocalOrPointer {
|
||||
return match v {
|
||||
Temporary {
|
||||
binaryen.binary(g.mod, binaryen.addint32(), binaryen.localget(g.mod, v.idx,
|
||||
type_i32), binaryen.constant(g.mod, binaryen.literalint32(offset)))
|
||||
}
|
||||
Stack {
|
||||
Var(Stack{
|
||||
...v
|
||||
address: v.address + offset
|
||||
})
|
||||
}
|
||||
Global {
|
||||
Var(Global{
|
||||
...v
|
||||
abs_address: v.abs_address + offset
|
||||
})
|
||||
}
|
||||
ast.Ident {
|
||||
panic('unreachable')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: return `Var | binaryen.Expression`
|
||||
fn (mut g Gen) get_var_from_expr(node ast.Expr) LocalOrPointer {
|
||||
return match node {
|
||||
ast.Ident {
|
||||
g.get_var_from_ident(node)
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
address := g.get_var_from_expr(node.expr)
|
||||
offset := g.get_field_offset(node.expr_type, node.field_name)
|
||||
|
||||
match address {
|
||||
binaryen.Expression {
|
||||
binaryen.binary(g.mod, binaryen.addint32(), address, binaryen.constant(g.mod,
|
||||
binaryen.literalint32(offset)))
|
||||
}
|
||||
Var {
|
||||
g.local_or_pointer_add_offset(address, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.IndexExpr {
|
||||
address := g.get_var_from_expr(node.left)
|
||||
|
||||
ts := g.table.sym(node.left_type)
|
||||
deref_type := if g.is_pure_type(node.left_type) {
|
||||
node.left_type.set_nr_muls(node.left_type.nr_muls() - 1)
|
||||
} else if ts.kind == .array_fixed {
|
||||
(ts.info as ast.ArrayFixed).elem_type
|
||||
} else {
|
||||
node.left_type
|
||||
}
|
||||
size, _ := g.get_type_size_align(deref_type)
|
||||
|
||||
index := g.expr(node.index, ast.int_type)
|
||||
mut ptr_address := binaryen.Expression(0)
|
||||
|
||||
if address is binaryen.Expression {
|
||||
ptr_address = address
|
||||
}
|
||||
if address is Var {
|
||||
ptr_address = g.get_var_t(address, ast.voidptr_type)
|
||||
}
|
||||
|
||||
// ptr + index * size
|
||||
binaryen.binary(g.mod, binaryen.addint32(), ptr_address, binaryen.binary(g.mod,
|
||||
binaryen.mulint32(), index, g.literalint(size, ast.int_type)))
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
g.lea_lop(g.get_var_from_expr(node.right), ast.voidptr_type)
|
||||
}
|
||||
else {
|
||||
g.w_error('get_var_from_expr: unexpected `${node.type_name()}`')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_local_temporary(name string) int {
|
||||
if g.local_temporaries.len == 0 {
|
||||
g.w_error('get_local: g.local_temporaries.len == 0')
|
||||
}
|
||||
mut c := g.local_temporaries.len
|
||||
for {
|
||||
c--
|
||||
if g.local_temporaries[c].name == name {
|
||||
return c
|
||||
}
|
||||
if c == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
g.w_error("get_local: cannot get '${name}'")
|
||||
}
|
||||
|
||||
fn (mut g Gen) new_local_temporary_anon_wtyp(w_typ binaryen.Type) int {
|
||||
ret := g.local_temporaries.len
|
||||
g.local_temporaries << Temporary{
|
||||
name: '_'
|
||||
typ: w_typ
|
||||
idx: ret
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
fn (mut g Gen) new_local_temporary_anon(typ ast.Type) int {
|
||||
ret := g.local_temporaries.len
|
||||
g.local_temporaries << Temporary{
|
||||
name: '_'
|
||||
typ: g.get_wasm_type(typ)
|
||||
ast_typ: typ
|
||||
idx: ret
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
fn (mut g Gen) new_local_temporary(name string, typ ast.Type) Temporary {
|
||||
idx := g.local_temporaries.len
|
||||
var := Temporary{
|
||||
name: name
|
||||
typ: g.get_wasm_type(typ)
|
||||
ast_typ: typ
|
||||
idx: idx
|
||||
}
|
||||
g.local_temporaries << var
|
||||
return var
|
||||
}
|
||||
|
||||
fn (mut g Gen) new_local(var ast.Ident, typ ast.Type) {
|
||||
if g.is_pure_type(typ) {
|
||||
g.new_local_temporary(var.name, typ)
|
||||
return
|
||||
}
|
||||
|
||||
ts := g.table.sym(typ)
|
||||
match ts.info {
|
||||
ast.Struct, ast.ArrayFixed {
|
||||
g.allocate_local_var(var.name, typ)
|
||||
}
|
||||
ast.Enum {
|
||||
g.new_local_temporary(var.name, ts.info.typ)
|
||||
}
|
||||
else {
|
||||
g.w_error('new_local: type `${*ts}` (${ts.info.type_name()}) is not a supported local type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) deref(expr binaryen.Expression, expected ast.Type) binaryen.Expression {
|
||||
size, _ := g.get_type_size_align(expected)
|
||||
return binaryen.load(g.mod, u32(size), g.is_signed(expected), 0, 0, g.get_wasm_type(expected),
|
||||
expr, c'memory')
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_field_offset(typ ast.Type, name string) int {
|
||||
ts := g.table.sym(typ)
|
||||
field := ts.find_field(name) or { g.w_error('could not find field `${name}` on init') }
|
||||
if typ !in g.structs {
|
||||
g.get_type_size_align(typ.idx())
|
||||
}
|
||||
return g.structs[typ.idx()].offsets[field.i]
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_type_size_align(typ ast.Type) (int, int) {
|
||||
ts := g.table.sym(typ)
|
||||
if ts.size != -1 && typ in g.structs {
|
||||
return ts.size, ts.align
|
||||
}
|
||||
|
||||
if ts.info !is ast.Struct {
|
||||
return g.table.type_size(typ)
|
||||
}
|
||||
|
||||
ti := ts.info as ast.Struct
|
||||
|
||||
// Code borrowed from native, hope you don't mind!
|
||||
|
||||
mut strc := StructInfo{}
|
||||
mut size := 0
|
||||
mut align := 1
|
||||
for f in ti.fields {
|
||||
f_size, f_align := g.table.type_size(f.typ)
|
||||
if f_size == 0 {
|
||||
strc.offsets << 0
|
||||
continue
|
||||
}
|
||||
padding := (f_align - size % f_align) % f_align
|
||||
strc.offsets << size + padding
|
||||
size += f_size + padding
|
||||
if f_align > align {
|
||||
align = f_align
|
||||
}
|
||||
}
|
||||
size = (size + align - 1) / align * align
|
||||
g.structs[typ.idx()] = strc
|
||||
|
||||
mut ts_ := g.table.sym(typ)
|
||||
ts_.size = size
|
||||
ts_.align = align
|
||||
|
||||
return size, align
|
||||
}
|
||||
|
||||
fn (mut g Gen) allocate_local_var(name string, typ ast.Type) int {
|
||||
size, align := g.get_type_size_align(typ)
|
||||
padding := (align - g.stack_frame % align) % align
|
||||
address := g.stack_frame
|
||||
g.stack_frame += size + padding
|
||||
g.local_addresses[name] = Stack{
|
||||
name: name
|
||||
ast_typ: typ
|
||||
address: address
|
||||
}
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_bp() binaryen.Expression {
|
||||
return binaryen.localget(g.mod, g.bp_idx, type_i32)
|
||||
}
|
||||
|
||||
fn (mut g Gen) lea_address(address int) binaryen.Expression {
|
||||
return if address != 0 {
|
||||
binaryen.binary(g.mod, binaryen.addint32(), g.get_bp(), binaryen.constant(g.mod,
|
||||
binaryen.literalint32(address)))
|
||||
} else {
|
||||
g.get_bp()
|
||||
}
|
||||
}
|
||||
|
||||
// Will automatcally cast value from `var` to `ast_type`, will ignore if struct value.
|
||||
// TODO: When supporting base types on the stack, actually cast them.
|
||||
fn (mut g Gen) get_var_t(var Var, ast_typ ast.Type) binaryen.Expression {
|
||||
return match var {
|
||||
ast.Ident {
|
||||
g.get_var_t(g.get_var_from_ident(var), ast_typ)
|
||||
}
|
||||
Temporary {
|
||||
expr := binaryen.localget(g.mod, var.idx, var.typ)
|
||||
g.cast_t(expr, var.ast_typ, ast_typ)
|
||||
}
|
||||
Stack {
|
||||
address := if var.address != 0 {
|
||||
binaryen.binary(g.mod, binaryen.addint32(), g.get_bp(), binaryen.constant(g.mod,
|
||||
binaryen.literalint32(var.address)))
|
||||
} else {
|
||||
g.get_bp()
|
||||
}
|
||||
|
||||
if g.is_pure_type(var.ast_typ) {
|
||||
g.cast_t(g.deref(address, var.ast_typ), var.ast_typ, ast_typ)
|
||||
} else {
|
||||
address
|
||||
}
|
||||
}
|
||||
Global {
|
||||
address := g.literalint(var.abs_address, ast.int_type)
|
||||
if g.is_pure_type(var.ast_typ) {
|
||||
g.cast_t(g.deref(address, var.ast_typ), var.ast_typ, ast_typ)
|
||||
} else {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[params]
|
||||
struct SetConfig {
|
||||
offset int
|
||||
ast_typ ast.Type
|
||||
}
|
||||
|
||||
fn (mut g Gen) set_to_address(address_expr binaryen.Expression, expr binaryen.Expression, ast_typ ast.Type) binaryen.Expression {
|
||||
return if !g.is_pure_type(ast_typ) {
|
||||
// `expr` is pointer
|
||||
g.blit(expr, ast_typ, address_expr)
|
||||
} else {
|
||||
size, _ := g.table.type_size(ast_typ)
|
||||
binaryen.store(g.mod, u32(size), 0, 0, address_expr, expr, g.get_wasm_type(ast_typ),
|
||||
c'memory')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) set_var_v(address Var, expr binaryen.Expression, cfg SetConfig) binaryen.Expression {
|
||||
return g.set_var(address, expr, cfg)
|
||||
}
|
||||
|
||||
fn (mut g Gen) set_var(address LocalOrPointer, expr binaryen.Expression, cfg SetConfig) binaryen.Expression {
|
||||
ast_typ := if cfg.ast_typ != 0 {
|
||||
cfg.ast_typ
|
||||
} else {
|
||||
(address as Var).ast_typ()
|
||||
}
|
||||
match address {
|
||||
binaryen.Expression {
|
||||
return g.set_to_address(address, expr, ast_typ)
|
||||
}
|
||||
Var {
|
||||
var := address
|
||||
|
||||
return match var {
|
||||
ast.Ident {
|
||||
g.set_var(g.get_var_from_ident(var), expr, cfg)
|
||||
}
|
||||
Temporary {
|
||||
binaryen.localset(g.mod, var.idx, expr)
|
||||
}
|
||||
Stack {
|
||||
if !g.is_pure_type(ast_typ) {
|
||||
// `expr` is pointer
|
||||
g.blit_local(expr, ast_typ, var.address + cfg.offset)
|
||||
} else {
|
||||
size, _ := g.table.type_size(ast_typ)
|
||||
// println("address: ${var.address}, offset: ${cfg.offset}")
|
||||
binaryen.store(g.mod, u32(size), u32(var.address + cfg.offset),
|
||||
0, g.get_bp(), expr, g.get_wasm_type(ast_typ), c'memory')
|
||||
}
|
||||
}
|
||||
Global {
|
||||
address_expr := g.literalint(var.abs_address + cfg.offset, ast.int_type)
|
||||
g.set_to_address(address_expr, expr, ast_typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// zero out stack memory in known local `address`.
|
||||
fn (mut g Gen) zero_fill(ast_typ ast.Type, address int) binaryen.Expression {
|
||||
size, _ := g.get_type_size_align(ast_typ)
|
||||
|
||||
if size <= 4 {
|
||||
zero := g.literalint(0, ast.int_type)
|
||||
return binaryen.store(g.mod, u32(size), u32(address), 0, g.get_bp(), zero, type_i32,
|
||||
c'memory')
|
||||
} else if size <= 8 {
|
||||
zero := g.literalint(0, ast.i64_type)
|
||||
return binaryen.store(g.mod, u32(size), u32(address), 0, g.get_bp(), zero, type_i64,
|
||||
c'memory')
|
||||
}
|
||||
return binaryen.memoryfill(g.mod, g.lea_address(address), g.literalint(0, ast.int_type),
|
||||
g.literalint(size, ast.int_type), c'memory')
|
||||
}
|
||||
|
||||
// `memcpy` from `ptr` to known local `address` in stack memory.
|
||||
fn (mut g Gen) blit_local(ptr binaryen.Expression, ast_typ ast.Type, address int) binaryen.Expression {
|
||||
size, _ := g.get_type_size_align(ast_typ)
|
||||
return binaryen.memorycopy(g.mod, g.lea_address(address), ptr, binaryen.constant(g.mod,
|
||||
binaryen.literalint32(size)), c'memory', c'memory')
|
||||
}
|
||||
|
||||
// `memcpy` from `ptr` to `dest`
|
||||
fn (mut g Gen) blit(ptr binaryen.Expression, ast_typ ast.Type, dest binaryen.Expression) binaryen.Expression {
|
||||
size, _ := g.get_type_size_align(ast_typ)
|
||||
return binaryen.memorycopy(g.mod, dest, ptr, binaryen.constant(g.mod, binaryen.literalint32(size)),
|
||||
c'memory', c'memory')
|
||||
}
|
||||
|
||||
fn (mut g Gen) init_struct(var Var, init ast.StructInit) binaryen.Expression {
|
||||
match var {
|
||||
ast.Ident {
|
||||
return g.init_struct(g.get_var_from_ident(var), init)
|
||||
}
|
||||
Stack {
|
||||
mut exprs := []binaryen.Expression{}
|
||||
|
||||
ts := g.table.sym(var.ast_typ)
|
||||
match ts.info {
|
||||
ast.Struct {
|
||||
if init.fields.len == 0 && !(ts.info.fields.any(it.has_default_expr)) {
|
||||
// Struct definition contains no default initialisers
|
||||
// AND struct init contains no set values.
|
||||
return g.mknblock('STRUCTINIT(ZERO)', [
|
||||
g.zero_fill(var.ast_typ, var.address),
|
||||
])
|
||||
}
|
||||
|
||||
for i, f in ts.info.fields {
|
||||
field_to_be_set := init.fields.map(it.name).contains(f.name)
|
||||
fts := g.table.sym(f.typ)
|
||||
if !field_to_be_set {
|
||||
g.get_type_size_align(var.ast_typ)
|
||||
offset := g.structs[var.ast_typ.idx()].offsets[i]
|
||||
if f.has_default_expr {
|
||||
init_expr := g.expr(f.default_expr, f.typ) // or `unaliased_typ`?
|
||||
exprs << g.set_var_v(var, init_expr, ast_typ: f.typ, offset: offset)
|
||||
} else {
|
||||
if fts.info is ast.Struct {
|
||||
exprs << g.init_struct(Stack{
|
||||
address: var.address + offset
|
||||
ast_typ: f.typ
|
||||
}, ast.StructInit{})
|
||||
} else {
|
||||
exprs << g.zero_fill(f.typ, var.address + offset)
|
||||
}
|
||||
}
|
||||
// TODO: replace invocations of `set_var` with `assign_expr_to_var`?
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
for f in init.fields {
|
||||
field := ts.find_field(f.name) or {
|
||||
g.w_error('could not find field `${f.name}` on init')
|
||||
}
|
||||
offset := g.structs[var.ast_typ.idx()].offsets[field.i]
|
||||
initexpr := g.expr(f.expr, f.expected_type)
|
||||
|
||||
exprs << g.set_var_v(var, initexpr, ast_typ: f.expected_type, offset: offset)
|
||||
}
|
||||
|
||||
return g.mknblock('STRUCTINIT', exprs)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
panic('unreachable')
|
||||
}
|
||||
|
||||
// From native, this should be taken out into `StringLiteral.eval_escape_codes()`
|
||||
fn (mut g Gen) eval_escape_codes(str_lit ast.StringLiteral) string {
|
||||
if str_lit.is_raw {
|
||||
return str_lit.val
|
||||
}
|
||||
|
||||
str := str_lit.val
|
||||
mut buffer := []u8{}
|
||||
|
||||
mut i := 0
|
||||
for i < str.len {
|
||||
if str[i] != `\\` {
|
||||
buffer << str[i]
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// skip \
|
||||
i++
|
||||
match str[i] {
|
||||
`\\`, `'`, `"` {
|
||||
buffer << str[i]
|
||||
i++
|
||||
}
|
||||
`a`, `b`, `f` {
|
||||
buffer << str[i] - u8(90)
|
||||
i++
|
||||
}
|
||||
`n` {
|
||||
buffer << `\n`
|
||||
i++
|
||||
}
|
||||
`r` {
|
||||
buffer << `\r`
|
||||
i++
|
||||
}
|
||||
`t` {
|
||||
buffer << `\t`
|
||||
i++
|
||||
}
|
||||
`u` {
|
||||
i++
|
||||
utf8 := strconv.parse_int(str[i..i + 4], 16, 16) or {
|
||||
g.w_error('invalid \\u escape code (${str[i..i + 4]})')
|
||||
0
|
||||
}
|
||||
i += 4
|
||||
buffer << u8(utf8)
|
||||
buffer << u8(utf8 >> 8)
|
||||
}
|
||||
`v` {
|
||||
buffer << `\v`
|
||||
i++
|
||||
}
|
||||
`x` {
|
||||
i++
|
||||
c := strconv.parse_int(str[i..i + 2], 16, 8) or {
|
||||
g.w_error('invalid \\x escape code (${str[i..i + 2]})')
|
||||
0
|
||||
}
|
||||
i += 2
|
||||
buffer << u8(c)
|
||||
}
|
||||
`0`...`7` {
|
||||
c := strconv.parse_int(str[i..i + 3], 8, 8) or {
|
||||
g.w_error('invalid escape code \\${str[i..i + 3]}')
|
||||
0
|
||||
}
|
||||
i += 3
|
||||
buffer << u8(c)
|
||||
}
|
||||
else {
|
||||
g.w_error('invalid escape code \\${str[i]}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.bytestr()
|
||||
}
|
||||
|
||||
struct ConstantData {
|
||||
offset int
|
||||
data []u8
|
||||
}
|
||||
|
||||
fn (mut g Gen) constant_data_intern_offset(data []u8) ?(int, int) {
|
||||
for d in g.constant_data {
|
||||
if d.data == data {
|
||||
return d.offset, d.data.len
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// (offset, len)
|
||||
fn (mut g Gen) allocate_string(node ast.StringLiteral) (int, int) {
|
||||
data := g.eval_escape_codes(node).bytes()
|
||||
|
||||
// `-prod` will only intern strings.
|
||||
if g.pref.is_prod {
|
||||
if offset, len := g.constant_data_intern_offset(data) {
|
||||
return offset, len
|
||||
}
|
||||
}
|
||||
|
||||
offset := g.constant_data_offset
|
||||
g.constant_data << ConstantData{
|
||||
offset: offset
|
||||
data: data
|
||||
}
|
||||
|
||||
padding := (8 - offset % 8) % 8
|
||||
g.constant_data_offset += data.len + padding
|
||||
|
||||
return offset, data.len
|
||||
}
|
320
vlib/v/gen/wasm/ops.v
Normal file
320
vlib/v/gen/wasm/ops.v
Normal file
|
@ -0,0 +1,320 @@
|
|||
module wasm
|
||||
|
||||
import v.ast
|
||||
import v.token
|
||||
import v.gen.wasm.binaryen
|
||||
|
||||
const (
|
||||
type_none = binaryen.typenone()
|
||||
type_auto = binaryen.typeauto()
|
||||
type_i32 = binaryen.typeint32()
|
||||
type_i64 = binaryen.typeint64()
|
||||
type_f32 = binaryen.typefloat32()
|
||||
type_f64 = binaryen.typefloat64()
|
||||
)
|
||||
|
||||
// "Register size" types such as int, i64 and bool boil down to their WASM counterparts.
|
||||
// Structures and unions are pointers, i32.
|
||||
fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type {
|
||||
typ := ast.mktyp(typ_)
|
||||
if typ == ast.void_type_idx {
|
||||
return wasm.type_none
|
||||
}
|
||||
if typ.is_real_pointer() {
|
||||
return wasm.type_i32
|
||||
}
|
||||
if typ in ast.number_type_idxs {
|
||||
return match typ {
|
||||
ast.isize_type_idx, ast.usize_type_idx, ast.i8_type_idx, ast.u8_type_idx,
|
||||
ast.char_type_idx, ast.rune_type_idx, ast.i16_type_idx, ast.u16_type_idx,
|
||||
ast.int_type_idx, ast.u32_type_idx {
|
||||
wasm.type_i32
|
||||
}
|
||||
ast.i64_type_idx, ast.u64_type_idx, ast.int_literal_type_idx {
|
||||
wasm.type_i64
|
||||
}
|
||||
ast.f32_type_idx {
|
||||
wasm.type_f32
|
||||
}
|
||||
ast.f64_type_idx, ast.float_literal_type_idx {
|
||||
wasm.type_f64
|
||||
}
|
||||
else {
|
||||
wasm.type_i32
|
||||
}
|
||||
}
|
||||
}
|
||||
if typ == ast.bool_type_idx {
|
||||
return wasm.type_i32
|
||||
}
|
||||
ts := g.table.sym(typ)
|
||||
match ts.info {
|
||||
ast.Struct {
|
||||
g.get_type_size_align(typ)
|
||||
return wasm.type_i32 // pointer
|
||||
}
|
||||
ast.MultiReturn {
|
||||
// TODO: cache??
|
||||
mut paraml := ts.info.types.map(g.get_wasm_type(it))
|
||||
return binaryen.typecreate(paraml.data, paraml.len)
|
||||
}
|
||||
ast.Alias {
|
||||
return g.get_wasm_type(ts.info.parent_type)
|
||||
}
|
||||
ast.ArrayFixed {
|
||||
return wasm.type_i32
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
|
||||
}
|
||||
|
||||
fn infix_kind_return_bool(op token.Kind) bool {
|
||||
return op in [.eq, .ne, .gt, .lt, .ge, .le]
|
||||
}
|
||||
|
||||
fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) binaryen.Op {
|
||||
wasm_typ := g.get_wasm_type(typ)
|
||||
|
||||
match wasm_typ {
|
||||
wasm.type_i32 {
|
||||
match op {
|
||||
.plus {
|
||||
return binaryen.addint32()
|
||||
}
|
||||
.minus {
|
||||
return binaryen.subint32()
|
||||
}
|
||||
.mul {
|
||||
return binaryen.mulint32()
|
||||
}
|
||||
.mod {
|
||||
if typ.is_signed() {
|
||||
return binaryen.remsint32()
|
||||
} else {
|
||||
return binaryen.remuint32()
|
||||
}
|
||||
}
|
||||
.div {
|
||||
if typ.is_signed() {
|
||||
return binaryen.divsint32()
|
||||
} else {
|
||||
return binaryen.divuint32()
|
||||
}
|
||||
}
|
||||
.eq {
|
||||
return binaryen.eqint32()
|
||||
}
|
||||
.ne {
|
||||
return binaryen.neint32()
|
||||
}
|
||||
.gt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gtsint32()
|
||||
} else {
|
||||
return binaryen.gtuint32()
|
||||
}
|
||||
}
|
||||
.lt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.ltsint32()
|
||||
} else {
|
||||
return binaryen.ltuint32()
|
||||
}
|
||||
}
|
||||
.ge {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gesint32()
|
||||
} else {
|
||||
return binaryen.geuint32()
|
||||
}
|
||||
}
|
||||
.le {
|
||||
if typ.is_signed() {
|
||||
return binaryen.lesint32()
|
||||
} else {
|
||||
return binaryen.leuint32()
|
||||
}
|
||||
}
|
||||
/*.logical_or {
|
||||
return binaryen.orint32() // TODO: logical or
|
||||
}*/
|
||||
.xor {
|
||||
return binaryen.xorint32()
|
||||
}
|
||||
.pipe {
|
||||
return binaryen.orint32()
|
||||
}
|
||||
.amp {
|
||||
return binaryen.andint32()
|
||||
}
|
||||
.left_shift {
|
||||
return binaryen.shlint32()
|
||||
}
|
||||
.right_shift {
|
||||
return binaryen.shrsint32()
|
||||
}
|
||||
.unsigned_right_shift {
|
||||
return binaryen.shruint32()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
wasm.type_i64 {
|
||||
match op {
|
||||
.plus {
|
||||
return binaryen.addint64()
|
||||
}
|
||||
.minus {
|
||||
return binaryen.subint64()
|
||||
}
|
||||
.mul {
|
||||
return binaryen.mulint64()
|
||||
}
|
||||
.mod {
|
||||
if typ.is_signed() {
|
||||
return binaryen.remsint64()
|
||||
} else {
|
||||
return binaryen.remuint64()
|
||||
}
|
||||
}
|
||||
.div {
|
||||
if typ.is_signed() {
|
||||
return binaryen.divsint64()
|
||||
} else {
|
||||
return binaryen.divuint64()
|
||||
}
|
||||
}
|
||||
.eq {
|
||||
return binaryen.eqint64()
|
||||
}
|
||||
.ne {
|
||||
return binaryen.neint64()
|
||||
}
|
||||
.gt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gtsint64()
|
||||
} else {
|
||||
return binaryen.gtuint64()
|
||||
}
|
||||
}
|
||||
.lt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.ltsint64()
|
||||
} else {
|
||||
return binaryen.ltuint64()
|
||||
}
|
||||
}
|
||||
.ge {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gesint64()
|
||||
} else {
|
||||
return binaryen.geuint64()
|
||||
}
|
||||
}
|
||||
.le {
|
||||
if typ.is_signed() {
|
||||
return binaryen.lesint64()
|
||||
} else {
|
||||
return binaryen.leuint64()
|
||||
}
|
||||
}
|
||||
/*.logical_or {
|
||||
return binaryen.orint64() // TODO: logical or
|
||||
}*/
|
||||
.xor {
|
||||
return binaryen.xorint64()
|
||||
}
|
||||
.pipe {
|
||||
return binaryen.orint64()
|
||||
}
|
||||
.amp {
|
||||
return binaryen.andint64()
|
||||
}
|
||||
.left_shift {
|
||||
return binaryen.shlint64()
|
||||
}
|
||||
.right_shift {
|
||||
return binaryen.shrsint64()
|
||||
}
|
||||
.unsigned_right_shift {
|
||||
return binaryen.shruint64()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
wasm.type_f32 {
|
||||
match op {
|
||||
.plus {
|
||||
return binaryen.addfloat32()
|
||||
}
|
||||
.minus {
|
||||
return binaryen.subfloat32()
|
||||
}
|
||||
.mul {
|
||||
return binaryen.mulfloat32()
|
||||
}
|
||||
.div {
|
||||
return binaryen.divfloat32()
|
||||
}
|
||||
.eq {
|
||||
return binaryen.eqfloat32()
|
||||
}
|
||||
.ne {
|
||||
return binaryen.nefloat32()
|
||||
}
|
||||
.gt {
|
||||
return binaryen.gtfloat32()
|
||||
}
|
||||
.lt {
|
||||
return binaryen.ltfloat32()
|
||||
}
|
||||
.ge {
|
||||
return binaryen.gefloat32()
|
||||
}
|
||||
.le {
|
||||
return binaryen.lefloat32()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
wasm.type_f64 {
|
||||
match op {
|
||||
.plus {
|
||||
return binaryen.addfloat64()
|
||||
}
|
||||
.minus {
|
||||
return binaryen.subfloat64()
|
||||
}
|
||||
.mul {
|
||||
return binaryen.mulfloat64()
|
||||
}
|
||||
.div {
|
||||
return binaryen.divfloat64()
|
||||
}
|
||||
.eq {
|
||||
return binaryen.eqfloat64()
|
||||
}
|
||||
.ne {
|
||||
return binaryen.nefloat64()
|
||||
}
|
||||
.gt {
|
||||
return binaryen.gtfloat64()
|
||||
}
|
||||
.lt {
|
||||
return binaryen.ltfloat64()
|
||||
}
|
||||
.ge {
|
||||
return binaryen.gefloat64()
|
||||
}
|
||||
.le {
|
||||
return binaryen.lefloat64()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
g.w_error('bad infix: op `${op}`')
|
||||
}
|
161
vlib/v/gen/wasm/serialisation.v
Normal file
161
vlib/v/gen/wasm/serialisation.v
Normal file
|
@ -0,0 +1,161 @@
|
|||
module wasm
|
||||
|
||||
import v.ast
|
||||
import v.gen.wasm.binaryen
|
||||
import encoding.binary as bin
|
||||
import math.bits
|
||||
|
||||
fn (mut g Gen) bake_constants_plus_initialisers() []GlobalData {
|
||||
mut initialisers := []GlobalData{}
|
||||
|
||||
for _, global in g.globals {
|
||||
match global.init {
|
||||
/*
|
||||
ast.ArrayInit {
|
||||
// TODO: call a seraliser recursively over all elements
|
||||
|
||||
if !global.init.is_fixed {
|
||||
g.w_error('wasm backend does not support non fixed arrays yet')
|
||||
}
|
||||
for global.init
|
||||
}*/
|
||||
ast.BoolLiteral {
|
||||
g.constant_data << ConstantData{
|
||||
offset: global.abs_address
|
||||
data: [u8(global.init.val)]
|
||||
}
|
||||
}
|
||||
ast.FloatLiteral {
|
||||
mut buf := []u8{len: 8}
|
||||
wtyp := g.get_wasm_type(global.ast_typ)
|
||||
match wtyp {
|
||||
type_f32 {
|
||||
bin.little_endian_put_u32(mut buf, bits.f32_bits(global.init.val.f32()))
|
||||
}
|
||||
type_f64 {
|
||||
bin.little_endian_put_u64(mut buf, bits.f64_bits(global.init.val.f64()))
|
||||
unsafe {
|
||||
buf.len = 4
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
g.constant_data << ConstantData{
|
||||
offset: global.abs_address
|
||||
data: buf
|
||||
}
|
||||
}
|
||||
ast.StringLiteral {
|
||||
offset, len := g.allocate_string(global.init)
|
||||
|
||||
if g.table.sym(global.ast_typ).info !is ast.Struct {
|
||||
mut buf := []u8{len: 4}
|
||||
bin.little_endian_put_u32(mut buf, u32(offset))
|
||||
g.constant_data << ConstantData{
|
||||
offset: global.abs_address
|
||||
data: buf
|
||||
}
|
||||
} else {
|
||||
mut buf := []u8{len: 8}
|
||||
bin.little_endian_put_u32(mut buf, u32(offset))
|
||||
bin.little_endian_put_u32_at(mut buf, u32(len), 4)
|
||||
|
||||
g.constant_data << ConstantData{
|
||||
offset: global.abs_address
|
||||
data: buf
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.IntegerLiteral {
|
||||
mut buf := []u8{len: 8}
|
||||
wtyp := g.get_wasm_type(global.ast_typ)
|
||||
match wtyp {
|
||||
type_i32 {
|
||||
bin.little_endian_put_u32(mut buf, u32(global.init.val.int()))
|
||||
}
|
||||
type_i64 {
|
||||
bin.little_endian_put_u64(mut buf, u64(global.init.val.i64()))
|
||||
unsafe {
|
||||
buf.len = 4
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
g.constant_data << ConstantData{
|
||||
offset: global.abs_address
|
||||
data: buf
|
||||
}
|
||||
}
|
||||
else {
|
||||
initialisers << global
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialisers
|
||||
}
|
||||
|
||||
fn round_up_to_multiple(val int, multiple int) int {
|
||||
return val + (multiple - val % multiple) % multiple
|
||||
}
|
||||
|
||||
fn (mut g Gen) make_vinit() binaryen.Function {
|
||||
runtime_inits := g.bake_constants_plus_initialisers()
|
||||
|
||||
g.bare_function_start()
|
||||
|
||||
mut body := runtime_inits.map(g.set_var_v(it.to_var(''), g.expr(it.init, it.ast_typ)))
|
||||
|
||||
for mod_name in g.table.modules {
|
||||
if mod_name == 'v.reflection' {
|
||||
g.w_error('the wasm backend does not implement `v.reflection` yet')
|
||||
}
|
||||
|
||||
init_fn_name := if mod_name != 'builtin' { '${mod_name}.init' } else { 'init' }
|
||||
if _ := g.table.find_fn(init_fn_name) {
|
||||
body << binaryen.call(g.mod, init_fn_name.str, unsafe { nil }, 0, type_none)
|
||||
}
|
||||
cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' }
|
||||
if _ := g.table.find_fn(cleanup_fn_name) {
|
||||
body << binaryen.call(g.mod, cleanup_fn_name.str, unsafe { nil }, 0, type_none)
|
||||
}
|
||||
}
|
||||
|
||||
return g.bare_function('_vinit', g.mkblock(body))
|
||||
}
|
||||
|
||||
fn (mut g Gen) housekeeping() {
|
||||
// `_vinit` should be used to initialise the WASM module,
|
||||
// then `main.main` can be called safely.
|
||||
vinit := g.make_vinit()
|
||||
|
||||
if g.needs_stack || g.constant_data.len != 0 {
|
||||
data := g.constant_data.map(it.data.data)
|
||||
data_len := g.constant_data.map(it.data.len)
|
||||
data_offsets := g.constant_data.map(binaryen.constant(g.mod, binaryen.literalint32(it.offset)))
|
||||
passive := []bool{len: g.constant_data.len, init: false}
|
||||
|
||||
binaryen.setmemory(g.mod, 1, 4, c'memory', data.data, passive.data, data_offsets.data,
|
||||
data_len.data, data.len, false, false, c'memory')
|
||||
}
|
||||
if g.needs_stack {
|
||||
// `g.constant_data_offset` rounded up to a multiple of 1024
|
||||
offset := round_up_to_multiple(g.constant_data_offset, 1024)
|
||||
|
||||
binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(offset, ast.int_type))
|
||||
}
|
||||
if g.pref.os == .wasi {
|
||||
main_expr := g.mkblock([binaryen.call(g.mod, c'_vinit', unsafe { nil }, 0, type_none),
|
||||
binaryen.call(g.mod, c'main.main', unsafe { nil }, 0, type_none)])
|
||||
binaryen.addfunction(g.mod, c'_start', type_none, type_none, unsafe { nil }, 0,
|
||||
main_expr)
|
||||
binaryen.addfunctionexport(g.mod, c'_start', c'_start')
|
||||
} else {
|
||||
// In `browser` mode, and function can be exported and called regardless.
|
||||
// To avoid uninitialised data, `_vinit` is set to be ran immediately on
|
||||
// WASM module creation.
|
||||
binaryen.setstart(g.mod, vinit)
|
||||
}
|
||||
}
|
86
vlib/v/gen/wasm/tests/arith.vv
Normal file
86
vlib/v/gen/wasm/tests/arith.vv
Normal file
|
@ -0,0 +1,86 @@
|
|||
fn mul(a i64, unsigned u32) f64 {
|
||||
mut one := a
|
||||
|
||||
one *= 2 / unsigned
|
||||
|
||||
return one
|
||||
}
|
||||
|
||||
fn typ(a int) i64 {
|
||||
mut one := a
|
||||
sec := one + i64(a)
|
||||
|
||||
return sec
|
||||
}
|
||||
|
||||
pub fn gcd(a_ i64, b_ i64) i64 {
|
||||
mut a := a_
|
||||
mut b := b_
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
for b != 0 {
|
||||
a %= b
|
||||
if a == 0 {
|
||||
return b
|
||||
}
|
||||
b %= a
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
pub fn lcm(a i64, b i64) i64 {
|
||||
if a == 0 {
|
||||
return a
|
||||
}
|
||||
res := a * (b / gcd(b, a))
|
||||
if res < 0 {
|
||||
return -res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn inc(a f64) int {
|
||||
mut b := a
|
||||
b++
|
||||
b--
|
||||
return int(b)
|
||||
}
|
||||
|
||||
pub fn negate(a int) i64 {
|
||||
return ~a + 1
|
||||
}
|
||||
|
||||
pub fn powi(a i64, b i64) i64 {
|
||||
mut b_ := b
|
||||
mut p := a
|
||||
mut v := i64(1)
|
||||
|
||||
if b_ < 0 { // exponent < 0
|
||||
if a == 0 {
|
||||
return -1 // division by 0
|
||||
}
|
||||
return if a * a != 1 {
|
||||
0
|
||||
} else {
|
||||
if (b_ & 1) > 0 {
|
||||
a
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for b_ > 0 {
|
||||
if b_ & 1 > 0 {
|
||||
v *= p
|
||||
}
|
||||
p *= p
|
||||
b_ >>= 1
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
28
vlib/v/gen/wasm/tests/arrays.vv
Normal file
28
vlib/v/gen/wasm/tests/arrays.vv
Normal file
|
@ -0,0 +1,28 @@
|
|||
struct TEST {
|
||||
a int
|
||||
b i64
|
||||
}
|
||||
|
||||
fn static_arrays() {
|
||||
a := [8]int{}
|
||||
b := [10, 12, 150]!
|
||||
c := [TEST{}, TEST{
|
||||
b: 10
|
||||
}]!
|
||||
}
|
||||
|
||||
fn index_expression() {
|
||||
b := [10, 12, 150]!
|
||||
|
||||
a := b[2]
|
||||
c := 'hello'[4]
|
||||
d := c'hello'[2]
|
||||
}
|
||||
|
||||
fn test_this(index int) int {
|
||||
a := 'hello'
|
||||
if index < a.len && index >= 0 {
|
||||
return a[index]
|
||||
}
|
||||
return 10
|
||||
}
|
18
vlib/v/gen/wasm/tests/builtin.vv
Normal file
18
vlib/v/gen/wasm/tests/builtin.vv
Normal file
|
@ -0,0 +1,18 @@
|
|||
fn test() {
|
||||
print('hello!')
|
||||
println('hello!')
|
||||
panic('nooo!')
|
||||
}
|
||||
|
||||
fn str_methods() {
|
||||
print(128.str())
|
||||
println(i64(-192322).str())
|
||||
println(false.str())
|
||||
}
|
||||
|
||||
fn str_implicit() {
|
||||
println(false)
|
||||
println(true)
|
||||
a := 100
|
||||
println(a + 10)
|
||||
}
|
91
vlib/v/gen/wasm/tests/control_flow.vv
Normal file
91
vlib/v/gen/wasm/tests/control_flow.vv
Normal file
|
@ -0,0 +1,91 @@
|
|||
fn func(a int, cond bool) i64 {
|
||||
mut src := 0
|
||||
|
||||
if cond {
|
||||
src = a
|
||||
} else if cond {
|
||||
src = 22
|
||||
} else if cond {
|
||||
src = 25
|
||||
}
|
||||
|
||||
if cond {
|
||||
src = a
|
||||
} else if cond {
|
||||
src = 22
|
||||
} else if cond {
|
||||
src = 25
|
||||
} else {
|
||||
src = src + src
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
fn test(cond bool) int {
|
||||
return if cond {
|
||||
2
|
||||
} else if !cond {
|
||||
5
|
||||
} else {
|
||||
6
|
||||
}
|
||||
}
|
||||
|
||||
fn boolfor() int {
|
||||
mut val := 0
|
||||
for val == 0 {
|
||||
val++
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
fn inffor() int {
|
||||
mut val := 0
|
||||
for {
|
||||
if val != 0 {
|
||||
break
|
||||
}
|
||||
val++
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
fn addcfor() int {
|
||||
mut val := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
val += i
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
fn labelcfor() {
|
||||
mut val := 0
|
||||
|
||||
hello: for {
|
||||
for {
|
||||
if val == 10 {
|
||||
continue hello
|
||||
}
|
||||
val++
|
||||
|
||||
if val == 100 {
|
||||
break hello
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fn infcfor() int {
|
||||
mut val := 0
|
||||
|
||||
for i := 0; true; i++ {
|
||||
if val >= 10 {
|
||||
return val
|
||||
}
|
||||
val += i
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
57
vlib/v/gen/wasm/tests/misc.vv
Normal file
57
vlib/v/gen/wasm/tests/misc.vv
Normal file
|
@ -0,0 +1,57 @@
|
|||
enum Hello as u64 {
|
||||
a
|
||||
b
|
||||
c = 20 + 10
|
||||
d
|
||||
e
|
||||
}
|
||||
|
||||
fn enums() {
|
||||
mut a := Hello.a
|
||||
a = .c
|
||||
}
|
||||
|
||||
struct AA {
|
||||
a u8
|
||||
b i64
|
||||
}
|
||||
|
||||
fn of() {
|
||||
a := __offsetof(AA, b)
|
||||
b := sizeof(AA)
|
||||
}
|
||||
|
||||
fn constant() int {
|
||||
return 100
|
||||
}
|
||||
|
||||
const hello = 'hello\n'
|
||||
|
||||
const float = 1.0
|
||||
|
||||
const integer = 888
|
||||
|
||||
const runtime_init = constant()
|
||||
|
||||
struct EE {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
fn ptr_arith() {
|
||||
mut a := EE{}
|
||||
mut b := &a.b
|
||||
|
||||
unsafe {
|
||||
*b = 12
|
||||
}
|
||||
println(a.b.str())
|
||||
unsafe {
|
||||
*b = 14
|
||||
}
|
||||
println(a.b.str())
|
||||
unsafe {
|
||||
*b = 102
|
||||
}
|
||||
println((*b).str())
|
||||
}
|
30
vlib/v/gen/wasm/tests/multi_expr.vv
Normal file
30
vlib/v/gen/wasm/tests/multi_expr.vv
Normal file
|
@ -0,0 +1,30 @@
|
|||
fn multi(a i16) i64 {
|
||||
one, two := a, 10
|
||||
return one + two
|
||||
}
|
||||
|
||||
pub fn multireturn(a int) (int, f64, i64) {
|
||||
return 2, a + 2, 10 - a
|
||||
}
|
||||
|
||||
pub fn test() (int, int) {
|
||||
return 25, 15
|
||||
}
|
||||
|
||||
pub fn accept() int {
|
||||
mut a, _ := test()
|
||||
a += 20
|
||||
return a
|
||||
}
|
||||
|
||||
pub fn side_effect() int {
|
||||
return 22
|
||||
}
|
||||
|
||||
pub fn test_side_effect() int {
|
||||
mut a := 15
|
||||
|
||||
_, a = side_effect(), 10
|
||||
|
||||
return a
|
||||
}
|
179
vlib/v/gen/wasm/tests/structs.vv
Normal file
179
vlib/v/gen/wasm/tests/structs.vv
Normal file
|
@ -0,0 +1,179 @@
|
|||
struct AA {
|
||||
a int = 22
|
||||
b i64
|
||||
}
|
||||
|
||||
pub fn zeroed() {
|
||||
_ := AA{}
|
||||
}
|
||||
|
||||
pub fn field() {
|
||||
_ := AA{
|
||||
a: 23
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selector(input int) int {
|
||||
mut a := AA{}
|
||||
|
||||
c := 10 + a.a
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
pub fn reassign(input int) int {
|
||||
mut a := AA{}
|
||||
|
||||
a = AA{
|
||||
b: input
|
||||
}
|
||||
|
||||
return int(a.b + input)
|
||||
}
|
||||
|
||||
struct BB {
|
||||
mut:
|
||||
a i64 = 22
|
||||
b i64
|
||||
c i64
|
||||
}
|
||||
|
||||
pub fn give(val int) int {
|
||||
mut a := BB{}
|
||||
|
||||
a.b = val
|
||||
|
||||
return take(a)
|
||||
}
|
||||
|
||||
pub fn take(input BB) int {
|
||||
return int(input.b)
|
||||
}
|
||||
|
||||
struct BB_ {
|
||||
mut:
|
||||
a i64 = 22
|
||||
b AA_
|
||||
}
|
||||
|
||||
struct AA_ {
|
||||
mut:
|
||||
a i64 = 91
|
||||
b i64 = 92
|
||||
c i64 = 93
|
||||
}
|
||||
|
||||
fn e() BB_ {
|
||||
return BB_{
|
||||
a: 2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make(nval AA_) i64 {
|
||||
val := BB_{
|
||||
b: nval
|
||||
}
|
||||
|
||||
return val.b.b
|
||||
}
|
||||
|
||||
pub fn return_make(nval int) int {
|
||||
val := make(AA_{ b: nval })
|
||||
return int(val)
|
||||
}
|
||||
|
||||
fn my_func(val int) AA_ {
|
||||
return AA_{
|
||||
b: val
|
||||
}
|
||||
}
|
||||
|
||||
fn accept() {
|
||||
my_func(20)
|
||||
}
|
||||
|
||||
struct CC {
|
||||
mut:
|
||||
a i64 = 91
|
||||
b i64 = 92
|
||||
c i64 = 93
|
||||
}
|
||||
|
||||
fn my_func_multi(val int) (CC, CC) {
|
||||
return CC{
|
||||
b: val
|
||||
}, CC{
|
||||
a: val
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept_multi(val int) int {
|
||||
a, b := my_func_multi(val)
|
||||
return int(a.b + b.a)
|
||||
}
|
||||
|
||||
struct Vector {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
fn add(a Vector, b Vector) Vector {
|
||||
return Vector{a.x + b.x, a.y + b.y}
|
||||
}
|
||||
|
||||
pub fn test(a int, b int) (int, int) {
|
||||
vec := Vector{a, b}
|
||||
|
||||
ret := add(vec, Vector{10, 5})
|
||||
|
||||
return ret.x, ret.y
|
||||
}
|
||||
|
||||
struct Aello {
|
||||
a int
|
||||
b i64
|
||||
c int
|
||||
}
|
||||
|
||||
struct Hello {
|
||||
a int = 20
|
||||
b Aello
|
||||
c int = 222
|
||||
}
|
||||
|
||||
pub fn recurse() {
|
||||
a := Hello{}
|
||||
}
|
||||
|
||||
struct DD {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
fn (a DD) + (b DD) DD {
|
||||
return DD{a.x + b.x, a.y + b.y}
|
||||
}
|
||||
|
||||
pub fn valer() (int, int) {
|
||||
mut a := DD{10, 15}
|
||||
|
||||
a += DD{10, 15}
|
||||
|
||||
return a.x, a.y
|
||||
}
|
||||
|
||||
struct TEST {
|
||||
mut:
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
fn postfix_test() {
|
||||
mut a := TEST{}
|
||||
a.b++
|
||||
a.a++
|
||||
}
|
||||
|
||||
fn postfix_test_mut(mut a TEST) {
|
||||
a.b++
|
||||
}
|
78
vlib/v/gen/wasm/tests/wasm_test.v
Normal file
78
vlib/v/gen/wasm/tests/wasm_test.v
Normal file
|
@ -0,0 +1,78 @@
|
|||
import os
|
||||
import benchmark
|
||||
import term
|
||||
|
||||
const is_verbose = os.getenv('VTEST_SHOW_CMD') != ''
|
||||
|
||||
// TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module
|
||||
fn test_wasm() {
|
||||
mut bench := benchmark.new_benchmark()
|
||||
vexe := os.getenv('VEXE')
|
||||
vroot := os.dir(vexe)
|
||||
dir := os.join_path(vroot, 'vlib/v/gen/wasm/tests')
|
||||
files := os.ls(dir) or { panic(err) }
|
||||
//
|
||||
wrkdir := os.join_path(os.vtmp_dir(), 'v', 'tests', 'wasm')
|
||||
os.mkdir_all(wrkdir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(wrkdir) or {}
|
||||
}
|
||||
os.chdir(wrkdir) or {}
|
||||
tests := files.filter(it.ends_with('.vv'))
|
||||
if tests.len == 0 {
|
||||
println('no wasm tests found')
|
||||
assert false
|
||||
}
|
||||
bench.set_total_expected_steps(tests.len)
|
||||
for test in tests {
|
||||
bench.step()
|
||||
full_test_path := os.real_path(os.join_path(dir, test))
|
||||
test_file_name := os.file_name(test)
|
||||
relative_test_path := full_test_path.replace(vroot + '/', '')
|
||||
work_test_path := '${wrkdir}/${test_file_name}'
|
||||
tmperrfile := '${dir}/${test}.tmperr'
|
||||
outfile := '${dir}/${test}.out'
|
||||
// force binaryen to print without colour
|
||||
cmd := '${os.quoted_path(vexe)} -o - -b wasm ${os.quoted_path(full_test_path)} 2> ${os.quoted_path(tmperrfile)}'
|
||||
if is_verbose {
|
||||
println(cmd)
|
||||
}
|
||||
res_wasm := os.execute(cmd)
|
||||
if res_wasm.exit_code != 0 {
|
||||
bench.fail()
|
||||
eprintln(bench.step_message_fail(cmd))
|
||||
|
||||
if os.exists(tmperrfile) {
|
||||
err := os.read_file(tmperrfile) or { panic(err) }
|
||||
eprintln(err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
os.rm(tmperrfile) or {}
|
||||
if expected_ := os.read_file(outfile) {
|
||||
mut expected := expected_
|
||||
expected = expected.trim_right('\r\n').replace('\r\n', '\n')
|
||||
mut found := res_wasm.output.trim_right('\r\n').replace('\r\n', '\n')
|
||||
found = found.trim_space()
|
||||
if expected != found {
|
||||
println(term.red('FAIL'))
|
||||
println('============')
|
||||
println('expected: "${expected}" len=${expected.len}')
|
||||
println('============')
|
||||
println('found:"${found}" len=${found.len}')
|
||||
println('============\n')
|
||||
bench.fail()
|
||||
continue
|
||||
}
|
||||
}
|
||||
bench.ok()
|
||||
eprintln(bench.step_message_ok(relative_test_path))
|
||||
}
|
||||
bench.stop()
|
||||
eprintln(term.h_divider('-'))
|
||||
eprintln(bench.total_message('wasm'))
|
||||
if bench.nfail > 0 {
|
||||
exit(1)
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr
|
|||
'C.${p.check_name()}'
|
||||
} else if language == .js {
|
||||
'JS.${p.check_js_name()}'
|
||||
} else if language == .wasm {
|
||||
'WASM.${p.check_name()}'
|
||||
} else if mod.len > 0 {
|
||||
'${mod}.${p.check_name()}'
|
||||
} else {
|
||||
|
@ -242,6 +244,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
language = .c
|
||||
} else if p.tok.kind == .name && p.tok.lit == 'JS' {
|
||||
language = .js
|
||||
} else if p.tok.kind == .name && p.tok.lit == 'WASM' {
|
||||
language = .wasm
|
||||
}
|
||||
p.fn_language = language
|
||||
if language != .v {
|
||||
|
@ -474,6 +478,8 @@ run them via `v file.v` instead',
|
|||
name = 'C.${name}'
|
||||
} else if language == .js {
|
||||
name = 'JS.${name}'
|
||||
} else if language == .wasm {
|
||||
name = 'WASM.${name}'
|
||||
} else {
|
||||
name = p.prepend_mod(name)
|
||||
}
|
||||
|
|
|
@ -300,6 +300,8 @@ pub fn (mut p Parser) parse_language() ast.Language {
|
|||
ast.Language.c
|
||||
} else if p.tok.lit == 'JS' {
|
||||
ast.Language.js
|
||||
} else if p.tok.lit == 'WASM' {
|
||||
ast.Language.wasm
|
||||
} else {
|
||||
ast.Language.v
|
||||
}
|
||||
|
|
|
@ -2387,6 +2387,9 @@ pub fn (mut p Parser) name_expr() ast.Expr {
|
|||
} else if p.tok.lit == 'JS' {
|
||||
language = ast.Language.js
|
||||
p.check_for_impure_v(language, p.tok.pos())
|
||||
} else if p.tok.lit == 'WASM' {
|
||||
language = ast.Language.wasm
|
||||
p.check_for_impure_v(language, p.tok.pos())
|
||||
}
|
||||
mut mod := ''
|
||||
// p.warn('resetting')
|
||||
|
@ -2484,6 +2487,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
|
|||
mod = 'C'
|
||||
} else if language == .js {
|
||||
mod = 'JS'
|
||||
} else if language == .wasm {
|
||||
mod = 'WASM'
|
||||
} else {
|
||||
if p.tok.lit in p.imports {
|
||||
// mark the imported module as used
|
||||
|
@ -3421,6 +3426,12 @@ fn (mut p Parser) module_decl() ast.Module {
|
|||
'translated' {
|
||||
p.is_translated = true
|
||||
}
|
||||
'wasm_import_namespace' {
|
||||
if !p.pref.is_fmt && p.pref.backend != .wasm {
|
||||
p.error_with_pos('[wasm_import_namespace] is allowed only in the wasm backend',
|
||||
ma.pos)
|
||||
}
|
||||
}
|
||||
else {
|
||||
p.error_with_pos('unknown module attribute `[${ma.name}]`', ma.pos)
|
||||
return mod_node
|
||||
|
@ -3899,6 +3910,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
|
|||
is_flag: is_flag
|
||||
is_multi_allowed: is_multi_allowed
|
||||
uses_exprs: uses_exprs
|
||||
typ: enum_type
|
||||
}
|
||||
is_pub: is_pub
|
||||
})
|
||||
|
|
|
@ -29,6 +29,8 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
|
|||
ast.Language.c
|
||||
} else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot {
|
||||
ast.Language.js
|
||||
} else if p.tok.lit == 'WASM' && p.peek_tok.kind == .dot {
|
||||
ast.Language.wasm
|
||||
} else {
|
||||
ast.Language.v
|
||||
}
|
||||
|
@ -88,6 +90,9 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
|
|||
} else if language == .js {
|
||||
name = 'JS.${name}'
|
||||
orig_name = name
|
||||
} else if language == .wasm {
|
||||
name = 'WASM.${name}'
|
||||
orig_name = name
|
||||
} else {
|
||||
name = p.prepend_mod(name)
|
||||
}
|
||||
|
@ -510,7 +515,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
|
|||
ast.Language.v
|
||||
}
|
||||
if language != .v {
|
||||
p.next() // C || JS
|
||||
p.next() // C || JS | WASM
|
||||
p.next() // .
|
||||
}
|
||||
name_pos := p.tok.pos()
|
||||
|
|
|
@ -112,7 +112,7 @@ pub fn (mut p Preferences) fill_with_defaults() {
|
|||
}
|
||||
if p.os == ._auto {
|
||||
// No OS specifed? Use current system
|
||||
p.os = get_host_os()
|
||||
p.os = if p.backend != .wasm { get_host_os() } else { .wasi }
|
||||
}
|
||||
//
|
||||
p.try_to_use_tcc_by_default()
|
||||
|
|
|
@ -27,6 +27,8 @@ pub enum OS {
|
|||
wasm32
|
||||
wasm32_emscripten
|
||||
wasm32_wasi
|
||||
browser // -b wasm -os browser
|
||||
wasi // -b wasm -os wasi
|
||||
raw
|
||||
all
|
||||
}
|
||||
|
@ -107,6 +109,13 @@ pub fn os_from_string(os_str string) !OS {
|
|||
'wasm32_emscripten' {
|
||||
return .wasm32_emscripten
|
||||
}
|
||||
// Native WASM options:
|
||||
'browser' {
|
||||
return .browser
|
||||
}
|
||||
'wasi' {
|
||||
return .wasi
|
||||
}
|
||||
else {
|
||||
// handle deprecated names:
|
||||
match os_str {
|
||||
|
@ -148,6 +157,8 @@ pub fn (o OS) str() string {
|
|||
.wasm32 { return 'WebAssembly' }
|
||||
.wasm32_emscripten { return 'WebAssembly(Emscripten)' }
|
||||
.wasm32_wasi { return 'WebAssembly(WASI)' }
|
||||
.browser { return 'browser' }
|
||||
.wasi { return 'wasi' }
|
||||
.raw { return 'Raw' }
|
||||
.all { return 'all' }
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ pub enum Backend {
|
|||
js_browser // The JavaScript browser backend
|
||||
js_freestanding // The JavaScript freestanding backend
|
||||
native // The Native backend
|
||||
wasm // The WebAssembly backend
|
||||
}
|
||||
|
||||
pub fn (b Backend) is_js() bool {
|
||||
|
@ -694,7 +695,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
|
|||
res.build_options << '${arg} ${sbackend}'
|
||||
b := backend_from_string(sbackend) or {
|
||||
eprintln('Unknown V backend: ${sbackend}')
|
||||
eprintln('Valid -backend choices are: c, go, interpret, js, js_node, js_browser, js_freestanding, native')
|
||||
eprintln('Valid -backend choices are: c, go, interpret, js, js_node, js_browser, js_freestanding, native, wasm')
|
||||
exit(1)
|
||||
}
|
||||
if b.is_js() {
|
||||
|
@ -786,6 +787,14 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
|
|||
eprintln_cond(show_output, "Note: building an optimized binary takes much longer. It shouldn't be used with `v run`.")
|
||||
eprintln_cond(show_output, 'Use `v run` without optimization, or build an optimized binary with -prod first, then run it separately.')
|
||||
}
|
||||
if res.os in [.browser, .wasi] && res.backend != .wasm {
|
||||
eprintln('OS `${res.os}` forbidden for backends other than wasm')
|
||||
exit(1)
|
||||
}
|
||||
if res.backend == .wasm && res.os !in [.browser, .wasi, ._auto] {
|
||||
eprintln('Native WebAssembly backend OS must be `browser` or `wasi`')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// res.use_cache = true
|
||||
if command != 'doc' && res.out_name.ends_with('.v') {
|
||||
|
@ -980,6 +989,7 @@ pub fn backend_from_string(s string) !Backend {
|
|||
'js_browser' { return .js_browser }
|
||||
'js_freestanding' { return .js_freestanding }
|
||||
'native' { return .native }
|
||||
'wasm' { return .wasm }
|
||||
else { return error('Unknown backend type ${s}') }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user