1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

wasm: remove dependency on thirdparty/binaryen, webassembly backend rewrite (#18120)

This commit is contained in:
l-m 2023-07-12 12:24:38 +00:00 committed by GitHub
parent 1c7df29bed
commit c422919481
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2895 additions and 6166 deletions

View File

@ -60,8 +60,10 @@ jobs:
- name: Build V - name: Build V
run: make -j4 && ./v symlink -githubci run: make -j4 && ./v symlink -githubci
- name: Install binaryen as build dependency for the V WASM backend - name: Install wasmer to execute WASM modules
run: ./v cmd/tools/install_binaryen.vsh run: |
curl https://get.wasmer.io -sSfL | sh
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
- name: Build the V WASM backend - name: Build the V WASM backend
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
@ -82,8 +84,10 @@ jobs:
- name: Build V - name: Build V
run: make -j4 && ./v symlink -githubci run: make -j4 && ./v symlink -githubci
- name: Install binaryen as build dependency for the V WASM backend - name: Install wasmer to execute WASM modules
run: ./v cmd/tools/install_binaryen.vsh run: |
curl https://get.wasmer.io -sSfL | sh
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
- name: Build the V WASM backend - name: Build the V WASM backend
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
@ -94,26 +98,26 @@ jobs:
- name: Build examples - name: Build examples
run: VTEST_ONLY=wasm ./v build-examples run: VTEST_ONLY=wasm ./v build-examples
wasm-backend-windows: # wasm-backend-windows:
runs-on: windows-2022 # runs-on: windows-2022
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v' # if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
timeout-minutes: 121 # timeout-minutes: 121
steps: # steps:
- uses: actions/checkout@v3 # - uses: actions/checkout@v3
#
- name: Build V # - name: Build V
run: .\make.bat -msvc # run: .\make.bat -msvc
- name: Symlink V # - name: Symlink V
run: .\v.exe symlink -githubci # run: .\v.exe symlink -githubci
#
- name: Install binaryen as build dependency for the V WASM backend # - name: Install binaryen as build dependency for the V WASM backend
run: v cmd/tools/install_binaryen.vsh # run: v cmd/tools/install_binaryen.vsh
#
- name: Build the V WASM backend # - name: Build the V WASM backend
run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v # run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v
#
- name: Test the WASM backend # - name: Test the WASM backend
run: v -stats test vlib/v/gen/wasm/tests/ # run: v -stats test vlib/v/gen/wasm/tests/
#
- name: Build examples # - name: Build examples
run: $env:VTEST_ONLY='wasm'; v build-examples # run: $env:VTEST_ONLY='wasm'; v build-examples

View File

@ -1,73 +0,0 @@
name: wasm wabt validate tests CI
on:
push:
paths:
- '!**'
- '!**.md'
- 'vlib/builtin/**.v'
- 'vlib/wasm/**.v'
- 'vlib/wasm/tests/**.v'
pull_request:
paths:
- '!**'
- '!**.md'
- 'vlib/builtin/**.v'
- 'vlib/wasm/**.v'
- 'vlib/wasm/tests/**.v'
concurrency:
group: wasm-wabt-ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
wasm-wabt-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: 31
steps:
- uses: actions/checkout@v3
- name: Build V
run: make && ./v symlink -githubci
- name: Install wabt to get the wasm-validate executable
run: v cmd/tools/install_wabt.vsh
- name: Test the WASM backend
run: v test vlib/wasm/
wasm-wabt-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: 31
steps:
- uses: actions/checkout@v3
- name: Build V
run: make && ./v symlink -githubci
- name: Install wabt to get the wasm-validate executable
run: v cmd/tools/install_wabt.vsh
- name: Test the WASM backend
run: v test vlib/wasm/
wasm-wabt-windows:
runs-on: windows-2022
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
timeout-minutes: 31
steps:
- uses: actions/checkout@v3
- name: Build V
run: .\make.bat -msvc
- name: Symlink V
run: .\v.exe symlink -githubci
- name: Install wabt to get the wasm-validate executable
run: v cmd/tools/install_wabt.vsh
- name: Test the WASM backend
run: v test vlib/wasm/

0
cmd/tools/install_wabt.vsh Normal file → Executable file
View File

View File

@ -193,18 +193,7 @@ fn rebuild(prefs &pref.Preferences) {
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 { .wasm {
assert_wasm_backend_thirdparty()
util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..]) 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)
}
}

View File

@ -6,6 +6,20 @@ fn __memory_grow(size usize) usize
fn __memory_fill(dest &u8, value isize, size isize) fn __memory_fill(dest &u8, value isize, size isize)
fn __memory_copy(dest &u8, src &u8, size isize) fn __memory_copy(dest &u8, src &u8, size isize)
// add doc comments for the below functions
// __reinterpret_f32_u32 converts a `u32` to a `f32` without changing the bit pattern.
pub fn __reinterpret_f32_u32(v f32) u32
// __reinterpret_u32_f32 converts a `f32` to a `u32` without changing the bit pattern.
pub fn __reinterpret_u32_f32(v u32) f32
// __reinterpret_f64_u64 converts a `u64` to a `f64` without changing the bit pattern.
pub fn __reinterpret_f64_u64(v f64) u64
// __reinterpret_u64_f64 converts a `f64` to a `u64` without changing the bit pattern.
pub fn __reinterpret_u64_f64(v u64) f64
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. // 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. // vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
// Unlike `v_calloc` vcalloc checks for negative values given in `n`. // Unlike `v_calloc` vcalloc checks for negative values given in `n`.

View File

@ -7,7 +7,7 @@ pub fn print(s string) {
len: usize(s.len) len: usize(s.len)
} }
WASM.fd_write(1, &elm, 1, -1) WASM.fd_write(1, &elm, 1, 0)
} }
// println prints a message with a line end, to stdout. // println prints a message with a line end, to stdout.
@ -20,7 +20,7 @@ pub fn println(s string) {
len: 1 len: 1
}]! }]!
WASM.fd_write(1, &elm[0], 2, -1) WASM.fd_write(1, &elm[0], 2, 0)
} }
// eprint prints a message to stderr. // eprint prints a message to stderr.
@ -30,7 +30,7 @@ pub fn eprint(s string) {
len: usize(s.len) len: usize(s.len)
} }
WASM.fd_write(2, &elm, 1, -1) WASM.fd_write(2, &elm, 1, 0)
} }
// eprintln prints a message with a line end, to stderr. // eprintln prints a message with a line end, to stderr.
@ -43,7 +43,7 @@ pub fn eprintln(s string) {
len: 1 len: 1
}]! }]!
WASM.fd_write(2, &elm[0], 2, -1) WASM.fd_write(2, &elm[0], 2, 0)
} }
// exit terminates execution immediately and returns exit `code` to the shell. // exit terminates execution immediately and returns exit `code` to the shell.
@ -57,6 +57,5 @@ pub fn exit(code int) {
pub fn panic(s string) { pub fn panic(s string) {
eprint('V panic: ') eprint('V panic: ')
eprintln(s) eprintln(s)
_ := *&u8(-1)
exit(1) exit(1)
} }

View File

@ -52,21 +52,18 @@ fn (nn int) str_l(max int) string {
} }
diff := max - index diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1) 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(buf, diff)
// return tos(memdup(&buf[0] + index, (max - index)), (max - index)) // return tos(memdup(&buf[0] + index, (max - index)), (max - index))
} }
} }
// str returns the value of the `u8` as a `string`.
// Example: assert u8(2).str() == '2'
pub fn (n u8) str() string {
return int(n).str_l(5)
}
// str returns the value of the `i8` as a `string`. // str returns the value of the `i8` as a `string`.
// Example: assert i8(-2).str() == '-2' // Example: assert i8(-2).str() == '-2'
pub fn (n i8) str() string { pub fn (n i8) str() string {

View File

@ -1,4 +1,3 @@
[wasm_import_namespace: 'wasi_snapshot_preview1']
module builtin module builtin
struct CIOVec { struct CIOVec {
@ -9,6 +8,9 @@ struct CIOVec {
type Errno = u16 type Errno = u16
type FileDesc = int type FileDesc = int
[wasm_import_namespace: 'wasi_snapshot_preview1']
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
[wasm_import_namespace: 'wasi_snapshot_preview1']
[noreturn] [noreturn]
fn WASM.proc_exit(rval int) fn WASM.proc_exit(rval int)

View File

@ -82,12 +82,44 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) { if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) {
exit(0) exit(0)
} }
compiled_file := os.real_path(b.pref.out_name) mut compiled_file := b.pref.out_name
if b.pref.backend == .wasm && !compiled_file.ends_with('.wasm') {
compiled_file += '.wasm'
}
compiled_file = os.real_path(compiled_file)
mut run_args := []string{cap: b.pref.run_args.len + 1}
run_file := if b.pref.backend.is_js() { run_file := if b.pref.backend.is_js() {
node_basename := $if windows { 'node.exe' } $else { 'node' } node_basename := $if windows { 'node.exe' } $else { 'node' }
os.find_abs_path_of_executable(node_basename) or { os.find_abs_path_of_executable(node_basename) or {
panic('Could not find `${node_basename}` in system path. Do you have Node.js installed?') panic('Could not find `${node_basename}` in system path. Do you have Node.js installed?')
} }
} else if b.pref.backend == .wasm {
mut actual_run := ['wasmer', 'wasmtime', 'wavm', 'wasm3']
mut actual_rf := ''
// -autofree bug
// error: cannot convert 'struct string' to 'struct _option_string'
// mut actual_rf := ?string(none)
for runtime in actual_run {
basename := $if windows { runtime + '.exe' } $else { runtime }
if rf := os.find_abs_path_of_executable(basename) {
if basename == 'wavm' {
run_args << 'run'
}
actual_rf = rf
break
}
}
if actual_rf == '' {
panic('Could not find `wasmer`, `wasmtime`, `wavm`, or `wasm3` in system path. Do you have any installed?')
}
actual_rf
} else if b.pref.backend == .golang { } else if b.pref.backend == .golang {
go_basename := $if windows { 'go.exe' } $else { 'go' } go_basename := $if windows { 'go.exe' } $else { 'go' }
os.find_abs_path_of_executable(go_basename) or { os.find_abs_path_of_executable(go_basename) or {
@ -96,8 +128,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
} else { } else {
compiled_file compiled_file
} }
mut run_args := []string{cap: b.pref.run_args.len + 1} if b.pref.backend.is_js() || b.pref.backend == .wasm {
if b.pref.backend.is_js() {
run_args << compiled_file run_args << compiled_file
} else if b.pref.backend == .golang { } else if b.pref.backend == .golang {
run_args << ['run', compiled_file] run_args << ['run', compiled_file]

File diff suppressed because it is too large Load Diff

View File

@ -1,91 +0,0 @@
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)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,73 @@
// Copyright (c) 2023 l-m.dev. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module wasm module wasm
import v.ast import v.ast
import v.token import v.token
import v.gen.wasm.binaryen import wasm
const ( pub fn (mut g Gen) as_numtype(a wasm.ValType) wasm.NumType {
type_none = binaryen.typenone() if a in [.funcref_t, .externref_t, .v128_t] {
type_auto = binaryen.typeauto() g.w_error("as_numtype: called with '${a}'")
type_i32 = binaryen.typeint32() }
type_i64 = binaryen.typeint64()
type_f32 = binaryen.typefloat32() return unsafe { wasm.NumType(a) }
type_f64 = binaryen.typefloat64() }
)
// unwraps int_literal to i64_t
pub fn (mut g Gen) get_wasm_type_int_literal(typ_ ast.Type) wasm.ValType {
if typ_ == ast.int_literal_type_idx {
return wasm.ValType.i64_t
}
return g.get_wasm_type(typ_)
}
// "Register size" types such as int, i64 and bool boil down to their WASM counterparts. // "Register size" types such as int, i64 and bool boil down to their WASM counterparts.
// Structures and unions are pointers, i32. // Structures and unions are pointers, i32.
fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type { pub fn (mut g Gen) get_wasm_type(typ_ ast.Type) wasm.ValType {
typ := ast.mktyp(typ_) typ := ast.mktyp(typ_)
if typ == ast.void_type_idx { if typ == ast.void_type_idx {
return wasm.type_none g.w_error("get_wasm_type: called with 'void'")
} }
if typ.is_any_kind_of_pointer() { if typ.is_ptr() || typ.is_pointer() {
g.needs_stack = true return wasm.ValType.i32_t
return wasm.type_i32
} }
if typ in ast.number_type_idxs { if typ in ast.number_type_idxs {
return match typ { return match typ {
ast.isize_type_idx, ast.usize_type_idx, ast.i8_type_idx, ast.u8_type_idx, 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.char_type_idx, ast.rune_type_idx, ast.i16_type_idx, ast.u16_type_idx,
ast.int_type_idx, ast.u32_type_idx { ast.int_type_idx, ast.u32_type_idx {
wasm.type_i32 wasm.ValType.i32_t
} }
ast.i64_type_idx, ast.u64_type_idx, ast.int_literal_type_idx { ast.i64_type_idx, ast.u64_type_idx {
wasm.type_i64 wasm.ValType.i64_t
} }
ast.f32_type_idx { ast.f32_type_idx {
wasm.type_f32 wasm.ValType.f32_t
} }
ast.f64_type_idx, ast.float_literal_type_idx { ast.f64_type_idx {
wasm.type_f64 wasm.ValType.f64_t
} }
else { else {
wasm.type_i32 wasm.ValType.i32_t
} }
} }
} }
if typ == ast.bool_type_idx { if typ == ast.bool_type_idx {
return wasm.type_i32 return wasm.ValType.i32_t
} }
ts := g.table.sym(typ) ts := g.table.sym(typ)
match ts.info { match ts.info {
ast.Struct { ast.Struct {
g.get_type_size_align(typ) g.pool.type_size(typ)
return wasm.type_i32 // pointer return wasm.ValType.i32_t // 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 { ast.Alias {
return g.get_wasm_type(ts.info.parent_type) return g.get_wasm_type(ts.info.parent_type)
} }
ast.ArrayFixed { ast.ArrayFixed {
return wasm.type_i32 return wasm.ValType.i32_t // pointer
} }
ast.Enum { ast.Enum {
return g.get_wasm_type(ts.info.typ) return g.get_wasm_type(ts.info.typ)
@ -74,251 +78,68 @@ fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type {
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}") g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
} }
fn infix_kind_return_bool(op token.Kind) bool { pub fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) {
return op in [.eq, .ne, .gt, .lt, .ge, .le] if g.is_param_type(typ) {
} eprintln(*g.table.sym(typ))
panic('unimplemented')
fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) binaryen.Op { }
wasm_typ := g.get_wasm_type(typ)
wasm_typ := g.as_numtype(g.get_wasm_type(typ))
match wasm_typ {
wasm.type_i32 { match op {
match op { .plus {
.plus { g.func.add(wasm_typ)
return binaryen.addint32() }
} .minus {
.minus { g.func.sub(wasm_typ)
return binaryen.subint32() }
} .mul {
.mul { g.func.mul(wasm_typ)
return binaryen.mulint32() }
} .mod {
.mod { g.func.rem(wasm_typ, typ.is_signed())
if typ.is_signed() { }
return binaryen.remsint32() .div {
} else { g.func.div(wasm_typ, typ.is_signed())
return binaryen.remuint32() }
} .eq {
} g.func.eq(wasm_typ)
.div { }
if typ.is_signed() { .ne {
return binaryen.divsint32() g.func.ne(wasm_typ)
} else { }
return binaryen.divuint32() .gt {
} g.func.gt(wasm_typ, typ.is_signed())
} }
.eq { .lt {
return binaryen.eqint32() g.func.lt(wasm_typ, typ.is_signed())
} }
.ne { .ge {
return binaryen.neint32() g.func.ge(wasm_typ, typ.is_signed())
} }
.gt { .le {
if typ.is_signed() { g.func.le(wasm_typ, typ.is_signed())
return binaryen.gtsint32() }
} else { .xor {
return binaryen.gtuint32() g.func.b_xor(wasm_typ)
} }
} .pipe {
.lt { g.func.b_or(wasm_typ)
if typ.is_signed() { }
return binaryen.ltsint32() .amp {
} else { g.func.b_and(wasm_typ)
return binaryen.ltuint32() }
} .left_shift {
} g.func.b_shl(wasm_typ)
.ge { }
if typ.is_signed() { .right_shift {
return binaryen.gesint32() g.func.b_shr(wasm_typ, true)
} else { }
return binaryen.geuint32() .unsigned_right_shift {
} g.func.b_shr(wasm_typ, false)
} }
.le { else {
if typ.is_signed() { g.w_error('bad infix: op `${op}`')
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}`')
} }

View File

@ -1,168 +0,0 @@
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()
stack_base := round_up_to_multiple(g.constant_data_offset, 1024)
heap_base := if g.needs_stack {
stack_base + 1024 * 16 // 16KiB of stack
} else {
stack_base
}
pages_needed := heap_base / (1024 * 64) + 1
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, pages_needed, pages_needed + 4, c'memory', data.data,
passive.data, data_offsets.data, data_len.data, data.len, false, false, c'memory')
binaryen.addglobal(g.mod, c'__heap_base', type_i32, false, g.literalint(heap_base,
ast.int_type))
}
if g.needs_stack {
// `g.constant_data_offset` rounded up to a multiple of 1024
binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(stack_base, 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)
}
}

View File

@ -0,0 +1,12 @@
import v.ast
import v.gen.wasm.serialise
fn test_alignment() {
table := ast.new_table()
mut pool := serialise.new_pool(table)
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
pool.append(ast.FloatLiteral{ val: '0' }, ast.f32_type) // +3, +4
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
assert pool.buf.len == 9
}

View File

@ -0,0 +1,449 @@
// Copyright (c) 2023 l-m.dev. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module serialise
import v.ast
// import v.eval
import math.bits
import strconv
[noinit]
pub struct Pool {
mut:
table &ast.Table
// eval eval.Eval
structs map[ast.Type]StructInfo
strings []StringInfo // string intern
pub:
null_terminated bool
intern_strings bool
store_relocs bool
pub mut:
buf []u8
relocs []Reloc
highest_alignment int
}
struct StringInfo {
pos int
len int
}
pub struct StructInfo {
pub mut:
offsets []int
}
pub struct Reloc {
pub:
pos int
offset int
}
pub fn (mut p Pool) type_struct_info(typ ast.Type) ?StructInfo {
ts := p.table.sym(typ)
if ts.info !is ast.Struct {
return none
}
if typ.idx() in p.structs {
return p.structs[typ.idx()]
}
// will cache inside `p.structs`
p.type_size(typ)
return p.structs[typ.idx()]
}
pub fn (mut p Pool) type_size(typ ast.Type) (int, int) {
ts := p.table.sym(typ)
if ts.size != -1 && typ.idx() in p.structs {
return ts.size, ts.align
}
if ts.info is ast.Enum {
return p.table.type_size(ts.info.typ)
}
if ts.info !is ast.Struct {
return p.table.type_size(typ)
}
ti := ts.info as ast.Struct
// code borrowed from native, inserted in wasm, and now here!
mut strc := StructInfo{}
mut size := 0
mut align := 1
for f in ti.fields {
f_size, f_align := p.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
p.structs[typ.idx()] = strc
mut ts_ := p.table.sym(typ)
ts_.size = size
ts_.align = align
return size, align
}
[params]
pub struct PoolOpts {
null_terminated bool = true
intern_strings bool = true
store_relocs bool = true
}
pub fn new_pool(table &ast.Table, opts PoolOpts) Pool {
return Pool{
table: table
null_terminated: opts.null_terminated
intern_strings: opts.intern_strings
store_relocs: opts.store_relocs
}
}
fn (mut p Pool) zero_fill(size int) {
// TODO: eventually support a way to utilise a BSS section
for i := 0; i < size; i++ {
p.buf << 0
}
}
fn (mut p Pool) alignment(align int) int {
if align > p.highest_alignment {
p.highest_alignment = align
}
padding := (align - p.buf.len % align) % align
p.zero_fill(padding)
pos := p.buf.len
return pos
}
/*
fn (mut p Pool) append_struct(init ast.StructInit) ?int {
old_len := p.buf.len
size, align := p.type_size(v.typ)
ts := g.table.sym(v.typ)
ts_info := ts.info as ast.Struct
pos := p.alignment(align)
if init.fields.len == 0 && !(ts_info.fields.any(it.has_default_expr)) {
for i := 0 ; i < size ; i++ {
p.buf << 0
}
return pos
}
/* for i, f in ts_info.fields {
field_to_be_set := init.fields.map(it.name).filter(f.name)
} */
/* for i, f in ts_info.fields {
field_to_be_set := init.fields.map(it.name).contains(f.name)
if !field_to_be_set {
offset := g.structs[v.typ.idx()].offsets[i]
offset_var := g.offset(v, f.typ, offset)
fsize, _ := g.get_type_size_align(f.typ)
if f.has_default_expr {
g.expr(f.default_expr, f.typ)
g.set(offset_var)
} else {
g.zero_fill(offset_var, fsize)
}
}
}
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[v.typ.idx()].offsets[field.i]
offset_var := g.offset(v, f.expected_type, offset)
g.expr(f.expr, f.expected_type)
g.set(offset_var)
} */
return pos
}*/
pub fn eval_escape_codes_raw(str string) !string {
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 {
return error('invalid \\u escape code (${str[i..i + 4]})')
}
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 {
return error('invalid \\x escape code (${str[i..i + 2]})')
}
i += 2
buffer << u8(c)
}
`0`...`7` {
c := strconv.parse_int(str[i..i + 3], 8, 8) or {
return error('invalid escape code \\${str[i..i + 3]}')
}
i += 3
buffer << u8(c)
}
else {
return error('invalid escape code \\${str[i]}')
}
}
}
return buffer.bytestr()
}
pub fn eval_escape_codes(str_lit ast.StringLiteral) !string {
if str_lit.is_raw {
return str_lit.val
}
return eval_escape_codes_raw(str_lit.val)
}
pub fn (mut p Pool) append_string(val string) int {
data := val.bytes()
if p.intern_strings {
for str in p.strings {
if data.len > str.len || (p.null_terminated && data.len != str.len) {
continue
}
// TODO: aggressive string interning if `p.null_terminated`
if p.buf[str.pos..str.pos + data.len] == data {
return str.pos
}
}
}
pos := p.buf.len
p.buf << data
if p.null_terminated {
p.buf << 0
}
p.strings << StringInfo{
pos: pos
len: data.len
}
return pos
}
pub fn (mut p Pool) append(init ast.Expr, typ ast.Type) (int, bool) {
match init {
ast.BoolLiteral {
pos := p.buf.len
p.buf << u8(init.val)
return pos, true
}
ast.FloatLiteral {
assert typ.is_pure_float()
mut pos := 0
if typ == ast.f32_type {
pos = p.alignment(4)
p.u32(bits.f32_bits(init.val.f32()))
} else {
pos = p.alignment(8)
p.u64(bits.f64_bits(init.val.f64()))
}
return pos, true
}
ast.IntegerLiteral {
assert typ.is_pure_int()
size, align := p.table.type_size(typ)
pos := p.alignment(align)
match size {
1 {
p.u8(u8(init.val.i8()))
}
2 {
p.u16(u16(init.val.i16()))
}
4 {
p.u32(u32(init.val.int()))
}
8 {
p.u64(u64(init.val.i64()))
}
else {}
}
return pos, true
}
ast.CharLiteral {
// 3 extra bytes for improved program correctness, thank me later
rne := u32(eval_escape_codes_raw(init.val) or { panic('Pool.append: ${err}') }.runes()[0])
pos := p.alignment(4)
p.u32(rne)
return pos, true
}
ast.StringLiteral {
val := eval_escape_codes(init) or { panic('Pool.append: ${err}') }
str_pos := p.append_string(val)
if typ != ast.string_type {
// c'str'
return str_pos, true
}
_, align := p.type_size(ast.string_type)
tss := p.table.sym(ast.string_type).info as ast.Struct
pos := p.alignment(align)
for field in tss.fields {
match field.name {
'str' {
p.ptr(str_pos)
}
'len' {
p.u32(u32(val.len))
}
'is_lit' {
p.u32(1)
}
else {
panic('ast.string: field `${field.name}` is unknown')
}
}
}
return pos, true
}
else {
size, align := p.type_size(typ)
pos := p.alignment(align)
p.zero_fill(size)
return pos, false
}
}
}
fn (mut p Pool) u64(v u64) {
p.buf << u8(v)
p.buf << u8(v >> u64(8))
p.buf << u8(v >> u64(16))
p.buf << u8(v >> u64(24))
p.buf << u8(v >> u64(32))
p.buf << u8(v >> u64(40))
p.buf << u8(v >> u64(48))
p.buf << u8(v >> u64(56))
}
fn (mut p Pool) u32(v u32) {
p.buf << u8(v)
p.buf << u8(v >> u32(8))
p.buf << u8(v >> u32(16))
p.buf << u8(v >> u32(24))
}
fn (mut p Pool) u16(v u16) {
p.buf << u8(v)
p.buf << u8(v >> u32(8))
}
fn (mut p Pool) u8(v u8) {
p.buf << v
}
fn (mut p Pool) ptr(offset int) int {
assert p.table.pointer_size in [1, 2, 4, 8]
pos := p.buf.len // p.alignment(p.table.pointer_size)
if p.store_relocs {
p.relocs << Reloc{
pos: pos
offset: offset
}
}
match p.table.pointer_size {
1 {
p.u8(u8(offset))
}
2 {
p.u16(u16(offset))
}
4 {
p.u32(u32(offset))
}
8 {
p.u64(u64(offset))
}
else {}
}
return pos
}

View File

@ -84,3 +84,60 @@ pub fn powi(a i64, b i64) i64 {
return v return v
} }
pub fn sqrti(a i64) i64 {
mut x := a
mut q, mut r := i64(1), i64(0)
for ; q <= x; {
q <<= 2
}
for ; q > 1; {
q >>= 2
t := x - r - q
r >>= 1
if t >= 0 {
x = t
r += q
}
}
return r
}
fn main() {
println('--- test printing numbers')
println(-20)
println(0)
println(22)
println(-1000000022)
println('--- test powi')
println(powi(2, 62))
println(powi(0, -2))
println(powi(2, -1))
println('--- test sqrti')
println(sqrti(i64(123456789) * i64(123456789)))
println(sqrti(144))
println(sqrti(0))
println('--- test negate')
println(negate(20))
println(negate(-1))
println('--- test inc')
println(inc(20))
println(inc(-1))
println('--- test lcm')
println(lcm(2, 3))
println(lcm(-2, 3))
println(lcm(-2, -3))
println(lcm(0, 0))
println('--- test gcd')
println(gcd(6, 9))
println(gcd(6, -9))
println(gcd(-6, -9))
println(gcd(0, 0))
}

View File

@ -0,0 +1,29 @@
--- test printing numbers
-20
0
22
-1000000022
--- test powi
4611686018427387904
-1
0
--- test sqrti
123456789
12
0
--- test negate
-20
1
--- test inc
20
-1
--- test lcm
6
6
6
0
--- test gcd
3
3
3
0

View File

@ -3,12 +3,14 @@ struct TEST {
b i64 b i64
} }
fn static_arrays() { fn static_arrays() (int, int, i64) {
a := [8]int{} a := [8]int{}
b := [10, 12, 150]! b := [10, 12, 150]!
c := [TEST{}, TEST{ c := [TEST{}, TEST{
b: 10 b: 10
}]! }]!
return a[2], b[1], c[1].b
} }
fn index_expression() { fn index_expression() {
@ -16,7 +18,11 @@ fn index_expression() {
a := b[2] a := b[2]
c := 'hello'[4] c := 'hello'[4]
d := c'hello'[2] d := unsafe { c'hello'[2] }
println(a)
println(c)
println(d)
} }
fn test_this(index int) int { fn test_this(index int) int {
@ -31,9 +37,31 @@ struct AA {
a [10]&int a [10]&int
} }
fn main() { fn test_stuff() &int {
a := AA{} a := AA{}
mut b := &int(0) mut b := &int(0)
b = a.a[2] b = a.a[2]
return b
}
fn main() {
println('--- static_arrays()')
a, b, c := static_arrays()
println(a)
println(b)
println(c)
println('--- index_expression()')
index_expression()
println('--- test_this()')
println(test_this(2))
println(test_this(10))
println(test_this(-1))
println('--- test_stuff()')
v := test_stuff()
println(v)
} }

View File

@ -0,0 +1,14 @@
--- static_arrays()
0
12
10
--- index_expression()
150
111
108
--- test_this()
108
10
10
--- test_stuff()
0

View File

@ -1,7 +1,6 @@
fn test() { fn test() {
print('hello!') print('hello!')
println('hello!') println('hello!')
panic('nooo!')
} }
fn str_methods() { fn str_methods() {
@ -16,3 +15,19 @@ fn str_implicit() {
a := 100 a := 100
println(a + 10) println(a + 10)
} }
fn assertions() {
assert true, 'hello'
assert true
// assert false, 'no can do'
}
fn main() {
test()
str_methods()
str_implicit()
assertions()
// panic('nooo!')
}

View File

@ -0,0 +1,6 @@
hello!hello!
128-192322
false
false
true
110

View File

@ -59,15 +59,17 @@ fn addcfor() int {
return val return val
} }
fn labelcfor() { fn labelcfor() (int, int) {
mut idx := 0
mut val := 0 mut val := 0
hello: for { hello: for {
for { for {
val++
if val == 10 { if val == 10 {
continue hello continue hello
} }
val++ idx++
if val == 100 { if val == 100 {
break hello break hello
@ -75,6 +77,8 @@ fn labelcfor() {
} }
break break
} }
return val, idx
} }
fn infcfor() int { fn infcfor() int {
@ -89,3 +93,32 @@ fn infcfor() int {
return 0 return 0
} }
fn main() {
println('--- func()')
println(func(10, true))
println(func(0, false))
println(func(0, true))
println('--- test()')
println(test(true))
println(test(false))
println(test(true && false))
println('--- boolfor()')
println(boolfor())
println('--- inffor()')
println(inffor())
println('--- addcfor()')
println(addcfor())
println('--- labelcfor()')
a, b := labelcfor()
println(a)
println(b)
println('--- infcfor()')
println(infcfor())
}

View File

@ -0,0 +1,19 @@
--- func()
10
0
0
--- test()
2
5
5
--- boolfor()
1
--- inffor()
1
--- addcfor()
45
--- labelcfor()
100
99
--- infcfor()
10

View File

@ -6,9 +6,10 @@ enum Hello as u64 {
e e
} }
fn enums() { fn enums() Hello {
mut a := Hello.a mut a := Hello.a
a = .c a = .c
return a
} }
struct AA { struct AA {
@ -19,6 +20,7 @@ struct AA {
fn of() { fn of() {
a := __offsetof(AA, b) a := __offsetof(AA, b)
b := sizeof(AA) b := sizeof(AA)
_, _ := a, b
} }
fn constant() int { fn constant() int {
@ -55,3 +57,35 @@ fn ptr_arith() {
} }
println((*b).str()) println((*b).str())
} }
fn defer_if(cond bool) {
if cond {
defer {
println('defer_if: defer!')
}
}
println('defer_if: start')
}
fn run_defer() {
defer {
println('defer!')
}
println('before defer')
defer_if(true)
defer_if(false)
}
fn main() {
println('ptr_arith')
ptr_arith()
run_defer()
println('constants')
println(runtime_init)
println(hello)
// println(float)
println(integer)
println('enums')
println(int(enums()))
println(sizeof(Hello))
}

View File

@ -0,0 +1,17 @@
ptr_arith
12
14
102
before defer
defer_if: start
defer_if: defer!
defer_if: start
defer!
constants
100
hello
888
enums
30
8

View File

@ -3,7 +3,7 @@ fn multi(a i16) i64 {
return one + two return one + two
} }
pub fn multireturn(a int) (int, f64, i64) { pub fn multireturn(a int) (int, i64, i64) {
return 2, a + 2, 10 - a return 2, a + 2, 10 - a
} }
@ -21,10 +21,94 @@ pub fn side_effect() int {
return 22 return 22
} }
pub fn test_side_effect() int { pub fn run_side_effect() int {
mut a := 15 mut a := 15
_, a = side_effect(), 10 _, a = side_effect(), 10
return a return a
} }
fn run_normal_stuff() {
println(multi(2))
{
a, b, c := multireturn(20)
println(a)
println(b)
println(c)
}
println(accept())
println(run_side_effect())
}
fn if_cond(cond bool) (int, int) {
return if cond { 10, 15 } else { 30, 35 }
}
struct AA {
a int
b int
}
fn take_struct(test AA) int {
return test.a
}
fn give_two_struct(cond bool) (AA, AA) {
return if cond {
AA{
a: 15
}, AA{
a: 15
}
} else {
AA{
a: 20
}, AA{
a: 592
}
}
}
fn give_struct() int {
return take_struct(AA{ a: 15 })
}
fn if_struct_test(cond bool) AA {
a := if cond {
AA{}
} else {
AA{
a: 125
}
}
return a
}
fn run_stmt_exprs() {
mut a := 0
mut b := 0
a, b = if_cond(true)
println(true)
println(a)
println(b)
a, b = if_cond(false)
println(false)
println(a)
println(b)
println(give_struct())
mut struc := if_struct_test(false)
println(struc.a)
struc = if_struct_test(true)
println(struc.a)
c, d := give_two_struct(false)
println(c.a)
println(d.a)
}
fn main() {
println('--- normal multi exprs')
run_normal_stuff()
println('--- struct based multi exprs/stmt exprs')
run_stmt_exprs()
}

View File

@ -0,0 +1,19 @@
--- normal multi exprs
12
2
22
-10
45
10
--- struct based multi exprs/stmt exprs
true
10
15
false
30
35
15
125
0
20
592

View File

@ -142,7 +142,7 @@ struct Hello {
} }
pub fn recurse() { pub fn recurse() {
a := Hello{} _ := Hello{}
} }
struct DD { struct DD {
@ -177,3 +177,25 @@ fn postfix_test() {
fn postfix_test_mut(mut a TEST) { fn postfix_test_mut(mut a TEST) {
a.b++ a.b++
} }
fn main() {
zeroed()
field()
println(selector(0))
println(reassign(10))
println(give(42))
println(return_make(123))
accept()
println(accept_multi(5))
x, y := test(2, 3)
println(x)
println(y)
recurse()
z, w := valer()
println(z)
println(w)
postfix_test()
mut a := TEST{}
postfix_test_mut(mut a)
println(a.b)
}

View File

@ -0,0 +1,10 @@
32
20
42
123
10
12
8
20
30
1

View File

@ -6,6 +6,23 @@ 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 // TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module
fn test_wasm() { fn test_wasm() {
mut runtimes := ['wasmer', 'wasmtime', 'wavm', 'wasm3']
mut runtime_found := false
for runtime in runtimes {
basename := $if windows { runtime + '.exe' } $else { runtime }
if rf := os.find_abs_path_of_executable(basename) {
runtime_found = true
break
}
}
if !runtime_found {
eprintln('cannot find suitable wasm runtime, exiting...')
exit(0)
}
mut bench := benchmark.new_benchmark() mut bench := benchmark.new_benchmark()
vexe := os.getenv('VEXE') vexe := os.getenv('VEXE')
vroot := os.dir(vexe) vroot := os.dir(vexe)
@ -33,7 +50,7 @@ fn test_wasm() {
tmperrfile := '${dir}/${test}.tmperr' tmperrfile := '${dir}/${test}.tmperr'
outfile := '${dir}/${test}.out' outfile := '${dir}/${test}.out'
// force binaryen to print without colour // 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)}' cmd := '${os.quoted_path(vexe)} -b wasm run ${os.quoted_path(full_test_path)} 2> ${os.quoted_path(tmperrfile)}'
if is_verbose { if is_verbose {
println(cmd) println(cmd)
} }
@ -65,6 +82,9 @@ fn test_wasm() {
bench.fail() bench.fail()
continue continue
} }
} else {
os.write_file(outfile, res_wasm.output.trim_right('\r\n').replace('\r\n',
'\n'))!
} }
bench.ok() bench.ok()
eprintln(bench.step_message_ok(relative_test_path)) eprintln(bench.step_message_ok(relative_test_path))

View File

@ -0,0 +1,25 @@
This command compiles the given target, along with their dependencies,
into an executable using the WebAssembly code generation backend.
Usage:
v -b wasm [-options] ['run'] <target.v|target_directory> [run options]
For more general build help, see also `v help build`.
# Interfacing WebAssembly code generation, passing options to it:
-wasm-validate
After compiling the WebAssembly module, execute wasm-validate to validate the module.
Useful for debugging the compiler.
-os <browser|wasi>, -target-os <browser|wasi>
Change the target WebAssembly execution environment that V compiles for.
The `wasi` target is the default execution enviroment.
When targeting WASI, the generated WebAssembly module can be run in a variety of environments that support the WASI specification.
WASI provides a standardized interface to interact with the host operating system, allowing WebAssembly modules to perform tasks like file I/O, networking, and more.
The specific version of the WASI specification targeted by V is 'wasi_snapshot_preview1'.
The `browser` target is an experimental environment that compiles for a stripped down builtin, for use in browsers.
The produced WebAssembly module will have functions exported that are `pub` and inside the `module main`.
See `examples/wasm/mandelbrot` for an example.

View File

@ -45,6 +45,7 @@ NB: the build flags are shared with the run command too:
* `js_node` - V outputs JS source code to run with nodejs. * `js_node` - V outputs JS source code to run with nodejs.
* `js_freestanding` - V outputs JS source code with no hard runtime dependency. * `js_freestanding` - V outputs JS source code with no hard runtime dependency.
* `native` - V outputs a native executable directly (see -arch x64|arm64 and -os linux|macos) (EXPERIMENTAL). * `native` - V outputs a native executable directly (see -arch x64|arm64 and -os linux|macos) (EXPERIMENTAL).
* `wasm` - V outputs a WebAssembly module directly (see -os wasi|browser) (EXPERIMENTAL).
-d <flag>[=<value>], -define <flag>[=<value>] -d <flag>[=<value>], -define <flag>[=<value>]
Define the provided flag. Define the provided flag.
@ -209,6 +210,7 @@ NB: the build flags are shared with the run command too:
For C-specific build flags, use `v help build-c`. For C-specific build flags, use `v help build-c`.
For JS-specific build flags, use `v help build-js`. For JS-specific build flags, use `v help build-js`.
For Native-specific build flags, use `v help build-native`. For Native-specific build flags, use `v help build-native`.
For WebAssembly-specific build flags, use `v help build-wasm`.
See also: See also:
`v help run` for documentation regarding `v run`. `v help run` for documentation regarding `v run`.

View File

@ -228,6 +228,7 @@ pub mut:
// checker settings: // checker settings:
checker_match_exhaustive_cutoff_limit int = 12 checker_match_exhaustive_cutoff_limit int = 12
thread_stack_size int = 8388608 // Change with `-thread-stack-size 4194304`. Note: on macos it was 524288, which is too small for more complex programs with many nested callexprs. thread_stack_size int = 8388608 // Change with `-thread-stack-size 4194304`. Note: on macos it was 524288, which is too small for more complex programs with many nested callexprs.
wasm_validate bool // validate webassembly code, by calling `wasm-validate`
} }
pub fn parse_args(known_external_commands []string, args []string) (&Preferences, string) { pub fn parse_args(known_external_commands []string, args []string) (&Preferences, string) {
@ -303,6 +304,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
arg := args[i] arg := args[i]
current_args := args[i..].clone() current_args := args[i..].clone()
match arg { match arg {
'-wasm-validate' {
res.wasm_validate = true
}
'-apk' { '-apk' {
res.is_apk = true res.is_apk = true
res.build_options << arg res.build_options << arg

View File

@ -666,6 +666,21 @@ pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr {
ast.UnsafeExpr { ast.UnsafeExpr {
node.expr = t.expr(mut node.expr) node.expr = t.expr(mut node.expr)
} }
// segfaults with vlib/v/tests/const_fixed_array_containing_references_to_itself_test.v
/*
ast.Ident {
mut obj := node.obj
if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] {
obj = node.scope.find(node.name) or { return node }
}
match mut obj {
ast.ConstField {
obj.expr = t.expr(mut obj.expr)
}
else {}
}
}*/
else {} else {}
} }
return node return node

View File

@ -163,7 +163,7 @@ pub fn launch_tool(is_verbose bool, tool_name string, args []string) {
for emodule in emodules { for emodule in emodules {
check_module_is_installed(emodule, is_verbose) or { panic(err) } check_module_is_installed(emodule, is_verbose) or { panic(err) }
} }
mut compilation_command := '${os.quoted_path(vexe)} -skip-unused ' mut compilation_command := '${os.quoted_path(vexe)} '
if tool_name in ['vself', 'vup', 'vdoctor', 'vsymlink'] { if tool_name in ['vself', 'vup', 'vdoctor', 'vsymlink'] {
// These tools will be called by users in cases where there // These tools will be called by users in cases where there
// is high chance of there being a problem somewhere. Thus // is high chance of there being a problem somewhere. Thus

View File

@ -110,26 +110,22 @@ fn (mut mod Module) patch(ft Function) {
mod.buf << ft.code[ptr..] mod.buf << ft.code[ptr..]
} }
// name fn (mut mod Module) name(name string) {
pub fn (mut mod Module) name(name string) {
mod.u32(u32(name.len)) mod.u32(u32(name.len))
mod.buf << name.bytes() mod.buf << name.bytes()
} }
// start_subsection fn (mut mod Module) start_subsection(sec Subsection) int {
pub fn (mut mod Module) start_subsection(sec Subsection) int {
mod.buf << u8(sec) mod.buf << u8(sec)
return mod.patch_start() return mod.patch_start()
} }
// start_section fn (mut mod Module) start_section(sec Section) int {
pub fn (mut mod Module) start_section(sec Section) int {
mod.buf << u8(sec) mod.buf << u8(sec)
return mod.patch_start() return mod.patch_start()
} }
// end_section fn (mut mod Module) end_section(tpatch int) {
pub fn (mut mod Module) end_section(tpatch int) {
mod.patch_len(tpatch) mod.patch_len(tpatch)
} }
@ -160,18 +156,14 @@ pub fn (mut mod Module) compile() []u8 {
{ {
mod.u32(u32(mod.fn_imports.len + mod.global_imports.len)) mod.u32(u32(mod.fn_imports.len + mod.global_imports.len))
for ft in mod.fn_imports { for ft in mod.fn_imports {
mod.u32(u32(ft.mod.len)) mod.name(ft.mod)
mod.buf << ft.mod.bytes() mod.name(ft.name)
mod.u32(u32(ft.name.len))
mod.buf << ft.name.bytes()
mod.buf << 0x00 // function mod.buf << 0x00 // function
mod.u32(u32(ft.tidx)) mod.u32(u32(ft.tidx))
} }
for gt in mod.global_imports { for gt in mod.global_imports {
mod.u32(u32(gt.mod.len)) mod.name(gt.mod)
mod.buf << gt.mod.bytes() mod.name(gt.name)
mod.u32(u32(gt.name.len))
mod.buf << gt.name.bytes()
mod.buf << 0x03 // global mod.buf << 0x03 // global
mod.global_type(gt.typ, gt.is_mut) mod.global_type(gt.typ, gt.is_mut)
} }
@ -244,7 +236,7 @@ pub fn (mut mod Module) compile() []u8 {
continue continue
} }
lsz++ lsz++
mod.name(ft.name) mod.name(ft.export_name or { ft.name })
mod.buf << 0x00 // function mod.buf << 0x00 // function
mod.u32(u32(ft.idx + mod.fn_imports.len)) mod.u32(u32(ft.idx + mod.fn_imports.len))
} }

View File

@ -43,4 +43,11 @@ mut:
locals []FunctionLocal locals []FunctionLocal
pub: pub:
name string name string
pub mut:
export_name ?string
}
// export_name sets the export name of the function to `name`
pub fn (mut func Function) export_name(name string) {
func.export_name = name
} }

View File

@ -861,6 +861,17 @@ pub fn (mut func Function) cast_trapping(a NumType, is_signed bool, b NumType) {
func.cast(a, is_signed, b) func.cast(a, is_signed, b)
} }
// reinterpret returns a value which has the same bit-pattern as its operand value, in its result type.
// WebAssembly instruction: `f32.reinterpret_i32`, `i32.reinterpret_f32`, `f64.reinterpret_i64`, `i64.reinterpret_f64`.
pub fn (mut func Function) reinterpret(a NumType) {
match a {
.f32_t { func.code << 0xBC } // i32.reinterpret_f32
.i32_t { func.code << 0xBE } // f32.reinterpret_i32
.f64_t { func.code << 0xBD } // i64.reinterpret_f64
.i64_t { func.code << 0xBF } // f64.reinterpret_i64
}
}
// unreachable denotes a point in code that should not be reachable, it is an unconditional trap. // unreachable denotes a point in code that should not be reachable, it is an unconditional trap.
// WebAssembly instruction: `unreachable`. // WebAssembly instruction: `unreachable`.
pub fn (mut func Function) unreachable() { pub fn (mut func Function) unreachable() {
@ -919,7 +930,7 @@ pub fn (mut func Function) c_return() {
func.code << 0x0F // return func.code << 0x0F // return
} }
// c_end ends the block or loop with the label passed in at `label`. // c_end ends the block, loop or if expression with the label passed in at `label`.
pub fn (mut func Function) c_end(label LabelIndex) { pub fn (mut func Function) c_end(label LabelIndex) {
assert func.label == label, 'c_end: called with an invalid label ${label}' assert func.label == label, 'c_end: called with an invalid label ${label}'
func.label-- func.label--
@ -945,11 +956,6 @@ pub fn (mut func Function) c_br_if(label LabelIndex) {
func.u32(u32(v)) func.u32(u32(v))
} }
// c_end_if closes the current if expression.
pub fn (mut func Function) c_end_if() {
func.code << 0x0B // END expression opcode
}
// call calls a locally defined function. // call calls a locally defined function.
// If this function does not exist when calling `compile` on the module, it will panic. // If this function does not exist when calling `compile` on the module, it will panic.
// WebAssembly instruction: `call`. // WebAssembly instruction: `call`.