mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
wasm: add a webassembly compiler backend, based on using binaryen (#17368)
This commit is contained in:
parent
b9a8a21094
commit
0625caad56
51 changed files with 7981 additions and 8 deletions
|
@ -10,7 +10,6 @@ on:
|
||||||
- 'vlib/builtin/**.v'
|
- 'vlib/builtin/**.v'
|
||||||
- 'vlib/v/ast/**.v'
|
- 'vlib/v/ast/**.v'
|
||||||
- 'vlib/v/scanner/**.v'
|
- 'vlib/v/scanner/**.v'
|
||||||
- 'vlib/v/scanner/**.v'
|
|
||||||
- 'vlib/v/parser/**.v'
|
- 'vlib/v/parser/**.v'
|
||||||
- 'vlib/v/checker/**.v'
|
- 'vlib/v/checker/**.v'
|
||||||
- 'vlib/v/gen/c/**.v'
|
- 'vlib/v/gen/c/**.v'
|
||||||
|
@ -31,7 +30,6 @@ on:
|
||||||
- 'vlib/builtin/**.v'
|
- 'vlib/builtin/**.v'
|
||||||
- 'vlib/v/ast/**.v'
|
- 'vlib/v/ast/**.v'
|
||||||
- 'vlib/v/scanner/**.v'
|
- 'vlib/v/scanner/**.v'
|
||||||
- 'vlib/v/scanner/**.v'
|
|
||||||
- 'vlib/v/parser/**.v'
|
- 'vlib/v/parser/**.v'
|
||||||
- 'vlib/v/checker/**.v'
|
- 'vlib/v/checker/**.v'
|
||||||
- 'vlib/v/gen/c/**.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
|
.envrc
|
||||||
|
|
||||||
thirdparty/stdatomic/nix/cpp/*.h
|
thirdparty/stdatomic/nix/cpp/*.h
|
||||||
|
thirdparty/binaryen*
|
||||||
|
|
||||||
# ignore VLS log
|
# ignore VLS log
|
||||||
vls.log
|
vls.log
|
||||||
|
@ -120,3 +121,4 @@ vls.log
|
||||||
# ignore Intellij files
|
# ignore Intellij files
|
||||||
.idea/
|
.idea/
|
||||||
/*.iml
|
/*.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 {
|
$if !macos {
|
||||||
skip_files << 'examples/macos_tray/tray.v'
|
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', '')
|
vargs := _vargs.replace('-progress', '')
|
||||||
vexe := pref.vexe_path()
|
vexe := pref.vexe_path()
|
||||||
|
|
|
@ -3,7 +3,7 @@ module main
|
||||||
import os
|
import os
|
||||||
import testing
|
import testing
|
||||||
|
|
||||||
const vroot = @VMODROOT
|
const vroot = os.dir(os.real_path(os.getenv_opt('VEXE') or { @VEXE }))
|
||||||
|
|
||||||
// build as a project folder
|
// build as a project folder
|
||||||
const efolders = [
|
const efolders = [
|
||||||
|
@ -12,11 +12,14 @@ const efolders = [
|
||||||
'examples/vweb_fullstack',
|
'examples/vweb_fullstack',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
pub fn normalised_vroot_path(path string) string {
|
||||||
|
return os.real_path(os.join_path_single(vroot, path)).replace('\\', '/')
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
args_string := os.args[1..].join(' ')
|
args_string := os.args[1..].join(' ')
|
||||||
params := args_string.all_before('build-examples')
|
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) {
|
res := testing.v_build_failing_skipped(params, 'examples', skip_prefixes, fn (mut session testing.TestSession) {
|
||||||
for x in efolders {
|
for x in efolders {
|
||||||
pathsegments := x.split_any('/')
|
pathsegments := x.split_any('/')
|
||||||
|
|
|
@ -329,6 +329,10 @@ fn main() {
|
||||||
tsession.skip_files << 'vlib/net/udp_test.v'
|
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 werror := false
|
||||||
mut sanitize_memory := false
|
mut sanitize_memory := false
|
||||||
mut sanitize_address := 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...')
|
println('using Go WIP backend...')
|
||||||
util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..])
|
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
|
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`.
|
// rename renames the file or folder from `src` to `dst`.
|
||||||
// Use mv to move or rename a file in a platform independent manner.
|
// Use mv to move or rename a file in a platform independent manner.
|
||||||
pub fn rename(src string, dst string) ! {
|
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
|
is_c2v_prefix bool // for `--x` (`x--$`), only for translated code until c2v can handle it
|
||||||
pub mut:
|
pub mut:
|
||||||
expr Expr
|
expr Expr
|
||||||
|
typ Type
|
||||||
auto_locked string
|
auto_locked string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub enum Language {
|
||||||
v
|
v
|
||||||
c
|
c
|
||||||
js
|
js
|
||||||
|
wasm
|
||||||
amd64 // aka x86_64
|
amd64 // aka x86_64
|
||||||
i386
|
i386
|
||||||
arm64 // 64-bit arm
|
arm64 // 64-bit arm
|
||||||
|
@ -887,6 +888,7 @@ pub fn (t &Table) type_size(typ Type) (int, int) {
|
||||||
.placeholder, .void, .none_, .generic_inst {}
|
.placeholder, .void, .none_, .generic_inst {}
|
||||||
.voidptr, .byteptr, .charptr, .function, .usize, .isize, .any, .thread, .chan {
|
.voidptr, .byteptr, .charptr, .function, .usize, .isize, .any, .thread, .chan {
|
||||||
size = t.pointer_size
|
size = t.pointer_size
|
||||||
|
align = t.pointer_size
|
||||||
}
|
}
|
||||||
.i8, .u8, .char, .bool {
|
.i8, .u8, .char, .bool {
|
||||||
size = 1
|
size = 1
|
||||||
|
@ -1075,6 +1077,7 @@ pub:
|
||||||
is_flag bool
|
is_flag bool
|
||||||
is_multi_allowed bool
|
is_multi_allowed bool
|
||||||
uses_exprs bool
|
uses_exprs bool
|
||||||
|
typ Type
|
||||||
}
|
}
|
||||||
|
|
||||||
[minify]
|
[minify]
|
||||||
|
|
|
@ -197,6 +197,16 @@ pub fn (v Builder) get_builtin_files() []string {
|
||||||
if v.pref.backend.is_js() {
|
if v.pref.backend.is_js() {
|
||||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
||||||
'js'))
|
'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 {
|
} else {
|
||||||
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin'))
|
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 {
|
} else {
|
||||||
node.auto_locked, _ = c.fail_if_immutable(node.expr)
|
node.auto_locked, _ = c.fail_if_immutable(node.expr)
|
||||||
}
|
}
|
||||||
|
node.typ = typ
|
||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ fn (mut f Fmt) write_language_prefix(lang ast.Language) {
|
||||||
match lang {
|
match lang {
|
||||||
.c { f.write('C.') }
|
.c { f.write('C.') }
|
||||||
.js { f.write('JS.') }
|
.js { f.write('JS.') }
|
||||||
|
.wasm { f.write('WASM.') }
|
||||||
else {}
|
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()}'
|
'C.${p.check_name()}'
|
||||||
} else if language == .js {
|
} else if language == .js {
|
||||||
'JS.${p.check_js_name()}'
|
'JS.${p.check_js_name()}'
|
||||||
|
} else if language == .wasm {
|
||||||
|
'WASM.${p.check_name()}'
|
||||||
} else if mod.len > 0 {
|
} else if mod.len > 0 {
|
||||||
'${mod}.${p.check_name()}'
|
'${mod}.${p.check_name()}'
|
||||||
} else {
|
} else {
|
||||||
|
@ -242,6 +244,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
language = .c
|
language = .c
|
||||||
} else if p.tok.kind == .name && p.tok.lit == 'JS' {
|
} else if p.tok.kind == .name && p.tok.lit == 'JS' {
|
||||||
language = .js
|
language = .js
|
||||||
|
} else if p.tok.kind == .name && p.tok.lit == 'WASM' {
|
||||||
|
language = .wasm
|
||||||
}
|
}
|
||||||
p.fn_language = language
|
p.fn_language = language
|
||||||
if language != .v {
|
if language != .v {
|
||||||
|
@ -474,6 +478,8 @@ run them via `v file.v` instead',
|
||||||
name = 'C.${name}'
|
name = 'C.${name}'
|
||||||
} else if language == .js {
|
} else if language == .js {
|
||||||
name = 'JS.${name}'
|
name = 'JS.${name}'
|
||||||
|
} else if language == .wasm {
|
||||||
|
name = 'WASM.${name}'
|
||||||
} else {
|
} else {
|
||||||
name = p.prepend_mod(name)
|
name = p.prepend_mod(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,6 +300,8 @@ pub fn (mut p Parser) parse_language() ast.Language {
|
||||||
ast.Language.c
|
ast.Language.c
|
||||||
} else if p.tok.lit == 'JS' {
|
} else if p.tok.lit == 'JS' {
|
||||||
ast.Language.js
|
ast.Language.js
|
||||||
|
} else if p.tok.lit == 'WASM' {
|
||||||
|
ast.Language.wasm
|
||||||
} else {
|
} else {
|
||||||
ast.Language.v
|
ast.Language.v
|
||||||
}
|
}
|
||||||
|
|
|
@ -2387,6 +2387,9 @@ pub fn (mut p Parser) name_expr() ast.Expr {
|
||||||
} else if p.tok.lit == 'JS' {
|
} else if p.tok.lit == 'JS' {
|
||||||
language = ast.Language.js
|
language = ast.Language.js
|
||||||
p.check_for_impure_v(language, p.tok.pos())
|
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 := ''
|
mut mod := ''
|
||||||
// p.warn('resetting')
|
// p.warn('resetting')
|
||||||
|
@ -2484,6 +2487,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
|
||||||
mod = 'C'
|
mod = 'C'
|
||||||
} else if language == .js {
|
} else if language == .js {
|
||||||
mod = 'JS'
|
mod = 'JS'
|
||||||
|
} else if language == .wasm {
|
||||||
|
mod = 'WASM'
|
||||||
} else {
|
} else {
|
||||||
if p.tok.lit in p.imports {
|
if p.tok.lit in p.imports {
|
||||||
// mark the imported module as used
|
// mark the imported module as used
|
||||||
|
@ -3421,6 +3426,12 @@ fn (mut p Parser) module_decl() ast.Module {
|
||||||
'translated' {
|
'translated' {
|
||||||
p.is_translated = true
|
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 {
|
else {
|
||||||
p.error_with_pos('unknown module attribute `[${ma.name}]`', ma.pos)
|
p.error_with_pos('unknown module attribute `[${ma.name}]`', ma.pos)
|
||||||
return mod_node
|
return mod_node
|
||||||
|
@ -3899,6 +3910,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
|
||||||
is_flag: is_flag
|
is_flag: is_flag
|
||||||
is_multi_allowed: is_multi_allowed
|
is_multi_allowed: is_multi_allowed
|
||||||
uses_exprs: uses_exprs
|
uses_exprs: uses_exprs
|
||||||
|
typ: enum_type
|
||||||
}
|
}
|
||||||
is_pub: is_pub
|
is_pub: is_pub
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,6 +29,8 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
|
||||||
ast.Language.c
|
ast.Language.c
|
||||||
} else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot {
|
} else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot {
|
||||||
ast.Language.js
|
ast.Language.js
|
||||||
|
} else if p.tok.lit == 'WASM' && p.peek_tok.kind == .dot {
|
||||||
|
ast.Language.wasm
|
||||||
} else {
|
} else {
|
||||||
ast.Language.v
|
ast.Language.v
|
||||||
}
|
}
|
||||||
|
@ -88,6 +90,9 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
|
||||||
} else if language == .js {
|
} else if language == .js {
|
||||||
name = 'JS.${name}'
|
name = 'JS.${name}'
|
||||||
orig_name = name
|
orig_name = name
|
||||||
|
} else if language == .wasm {
|
||||||
|
name = 'WASM.${name}'
|
||||||
|
orig_name = name
|
||||||
} else {
|
} else {
|
||||||
name = p.prepend_mod(name)
|
name = p.prepend_mod(name)
|
||||||
}
|
}
|
||||||
|
@ -510,7 +515,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
|
||||||
ast.Language.v
|
ast.Language.v
|
||||||
}
|
}
|
||||||
if language != .v {
|
if language != .v {
|
||||||
p.next() // C || JS
|
p.next() // C || JS | WASM
|
||||||
p.next() // .
|
p.next() // .
|
||||||
}
|
}
|
||||||
name_pos := p.tok.pos()
|
name_pos := p.tok.pos()
|
||||||
|
|
|
@ -112,7 +112,7 @@ pub fn (mut p Preferences) fill_with_defaults() {
|
||||||
}
|
}
|
||||||
if p.os == ._auto {
|
if p.os == ._auto {
|
||||||
// No OS specifed? Use current system
|
// 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()
|
p.try_to_use_tcc_by_default()
|
||||||
|
|
|
@ -27,6 +27,8 @@ pub enum OS {
|
||||||
wasm32
|
wasm32
|
||||||
wasm32_emscripten
|
wasm32_emscripten
|
||||||
wasm32_wasi
|
wasm32_wasi
|
||||||
|
browser // -b wasm -os browser
|
||||||
|
wasi // -b wasm -os wasi
|
||||||
raw
|
raw
|
||||||
all
|
all
|
||||||
}
|
}
|
||||||
|
@ -107,6 +109,13 @@ pub fn os_from_string(os_str string) !OS {
|
||||||
'wasm32_emscripten' {
|
'wasm32_emscripten' {
|
||||||
return .wasm32_emscripten
|
return .wasm32_emscripten
|
||||||
}
|
}
|
||||||
|
// Native WASM options:
|
||||||
|
'browser' {
|
||||||
|
return .browser
|
||||||
|
}
|
||||||
|
'wasi' {
|
||||||
|
return .wasi
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// handle deprecated names:
|
// handle deprecated names:
|
||||||
match os_str {
|
match os_str {
|
||||||
|
@ -148,6 +157,8 @@ pub fn (o OS) str() string {
|
||||||
.wasm32 { return 'WebAssembly' }
|
.wasm32 { return 'WebAssembly' }
|
||||||
.wasm32_emscripten { return 'WebAssembly(Emscripten)' }
|
.wasm32_emscripten { return 'WebAssembly(Emscripten)' }
|
||||||
.wasm32_wasi { return 'WebAssembly(WASI)' }
|
.wasm32_wasi { return 'WebAssembly(WASI)' }
|
||||||
|
.browser { return 'browser' }
|
||||||
|
.wasi { return 'wasi' }
|
||||||
.raw { return 'Raw' }
|
.raw { return 'Raw' }
|
||||||
.all { return 'all' }
|
.all { return 'all' }
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub enum Backend {
|
||||||
js_browser // The JavaScript browser backend
|
js_browser // The JavaScript browser backend
|
||||||
js_freestanding // The JavaScript freestanding backend
|
js_freestanding // The JavaScript freestanding backend
|
||||||
native // The Native backend
|
native // The Native backend
|
||||||
|
wasm // The WebAssembly backend
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (b Backend) is_js() bool {
|
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}'
|
res.build_options << '${arg} ${sbackend}'
|
||||||
b := backend_from_string(sbackend) or {
|
b := backend_from_string(sbackend) or {
|
||||||
eprintln('Unknown V backend: ${sbackend}')
|
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)
|
exit(1)
|
||||||
}
|
}
|
||||||
if b.is_js() {
|
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, "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.')
|
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
|
// res.use_cache = true
|
||||||
if command != 'doc' && res.out_name.ends_with('.v') {
|
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_browser' { return .js_browser }
|
||||||
'js_freestanding' { return .js_freestanding }
|
'js_freestanding' { return .js_freestanding }
|
||||||
'native' { return .native }
|
'native' { return .native }
|
||||||
|
'wasm' { return .wasm }
|
||||||
else { return error('Unknown backend type ${s}') }
|
else { return error('Unknown backend type ${s}') }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue