Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FunC v0.5.0: syntax, refactoring, bugfixes, and a testing framework #1026

Open
wants to merge 30 commits into
base: testnet
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0bc6305
[FunC] Change some fields to enums instead of integers
unserialize Apr 16, 2024
a5d2a10
[FunC] Enrich and refactor testing framework, add negative tests
unserialize Apr 19, 2024
cbd7896
[FunC] CMake option -DFUNC_DEBUG for development purposes
unserialize Apr 20, 2024
bac4e3d
[FunC] Enrich testing framework, add fif output patterns
unserialize Apr 21, 2024
18050a7
[FunC] Auto-inline functions-wrappers `T f(...args) { return anotherF…
unserialize Apr 26, 2024
c74e49d
[FunC] Enrich testing framework, add code hash checking
unserialize Apr 27, 2024
a174f85
[FunC] Apply camelCase to some tests to ensure code_hash remains unch…
unserialize Apr 27, 2024
30572c7
[FunC] Support traditional // and /**/ comments
unserialize Apr 30, 2024
4994ae8
[FunC] Convert stdlib.fc to traditional-style //comments
unserialize Apr 30, 2024
f217a7d
[FunC] Forbid auto-creating undefined symbols
unserialize May 2, 2024
a3e9e03
[FunC] Fixed some impure specifiers in stdlib.fc
unserialize May 3, 2024
85c60d1
[FunC] Make all functions impure by default, add "pure" specifier
unserialize May 3, 2024
ef5719d
[FunC] Forbid impure operations inside pure functions
unserialize May 3, 2024
0628e17
[FunC] Use td::OptionParser in func-main.cpp
unserialize May 5, 2024
acf0043
[FunC] Add pragma remove-unused-functions for simple dead code elimin…
unserialize May 6, 2024
cdef830
[FunC] Add `builtin` keyword to be used in stdlib later on
unserialize May 7, 2024
de57087
[FunC] Add builtin functions to stdlib.fc
unserialize May 8, 2024
bb86dc0
[FunC] Add an ability to deprecate pragmas
unserialize May 10, 2024
aaf3ca3
[FunC] Deprecate pragma allow-post-modification
unserialize May 10, 2024
1e4b20a
[FunC] Deprecate pragma compute-asm-ltr
unserialize May 10, 2024
aee5173
[FunC] Refactor allow-post-modification, stop producing disabled Op::…
unserialize May 10, 2024
7afa929
[FunC] Change priority of `& | ^` operators to a more intuitive one
unserialize May 11, 2024
7b8268d
[FunC] Deprecate `method_id` specifier, introduce `get` keyword
unserialize May 21, 2024
8932c51
[FunC] Produce an error on get methods hash (method_id) collision
unserialize May 21, 2024
2da85a6
[FunC] Fix an issue of funcfiftlib.wasm which truncated long fif output
unserialize May 22, 2024
49a0d32
[FunC] Drop a folder crypto/func/test, it's unused
unserialize May 22, 2024
3520184
[FunC] Fix a bug with << operator to zero value
unserialize Jun 13, 2024
5867d52
[FunC] Bump FunC version to v0.5.0
unserialize Jun 13, 2024
79721d2
[FunC] Require parenthesis in tricky bitwise precedence cases
unserialize Jun 21, 2024
e2467b8
[FunC] Reserve '!' for the future, identifiers can't start with it
unserialize Jun 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ test/regression-tests.cache/
**/*build*/
.idea
.vscode
dev/
.DS_Store
zlib/
libsodium/
libmicrohttpd-0.9.77-w32-bin/
Expand Down
4 changes: 4 additions & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ target_link_libraries(func PUBLIC ton_crypto src_parser git ton_block)
if (WINGETOPT_FOUND)
target_link_libraries_system(func wingetopt)
endif()
if (${FUNC_DEBUG}) # -DFUNC_DEBUG=1 in CMake options => #define FUNC_DEBUG (for development purposes)
message(STATUS "FUNC_DEBUG is ON")
target_compile_definitions(func PRIVATE FUNC_DEBUG=1)
endif()

if (USE_EMSCRIPTEN)
add_executable(funcfiftlib funcfiftlib/funcfiftlib.cpp ${FUNC_LIB_SOURCE})
Expand Down
19 changes: 12 additions & 7 deletions crypto/fift/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class MemoryFileLoader : public fift::FileLoader {
std::map<std::string, std::string, std::less<>> files_;
};

td::Result<fift::SourceLookup> create_source_lookup(std::string main, bool need_preamble = true, bool need_asm = true,
td::Result<fift::SourceLookup> create_source_lookup(std::string&& main, bool need_preamble = true, bool need_asm = true,
bool need_ton_util = true, bool need_lisp = true,
bool need_w3_code = true, bool need_fift_ext = true,
bool need_disasm = true, std::string dir = "") {
Expand Down Expand Up @@ -187,7 +187,7 @@ td::Result<fift::SourceLookup> run_fift(fift::SourceLookup source_lookup, std::o
} // namespace
td::Result<FiftOutput> mem_run_fift(std::string source, std::vector<std::string> args, std::string fift_dir) {
std::stringstream ss;
TRY_RESULT(source_lookup, create_source_lookup(source, true, true, true, true, true, true, true, fift_dir));
TRY_RESULT(source_lookup, create_source_lookup(std::move(source), true, true, true, true, true, true, true, fift_dir));
TRY_RESULT_ASSIGN(source_lookup, run_fift(std::move(source_lookup), &ss, true, std::move(args)));
FiftOutput res;
res.source_lookup = std::move(source_lookup);
Expand All @@ -205,16 +205,21 @@ td::Result<FiftOutput> mem_run_fift(SourceLookup source_lookup, std::vector<std:
td::Result<fift::SourceLookup> create_mem_source_lookup(std::string main, std::string fift_dir, bool need_preamble,
bool need_asm, bool need_ton_util, bool need_lisp,
bool need_w3_code) {
return create_source_lookup(main, need_preamble, need_asm, need_ton_util, need_lisp, need_w3_code, false, false,
return create_source_lookup(std::move(main), need_preamble, need_asm, need_ton_util, need_lisp, need_w3_code, false, false,
fift_dir);
}

td::Result<td::Ref<vm::Cell>> compile_asm(td::Slice asm_code, std::string fift_dir, bool is_raw) {
std::stringstream ss;
TRY_RESULT(source_lookup,
create_source_lookup(PSTRING() << "\"Asm.fif\" include\n " << (is_raw ? "<{" : "") << asm_code << "\n"
<< (is_raw ? "}>c" : "") << " boc>B \"res\" B>file",
true, true, true, false, false, false, false, fift_dir));
std::string sb;
sb.reserve(asm_code.size() + 100);
sb.append("\"Asm.fif\" include\n ");
sb.append(is_raw ? "<{" : "");
sb.append(asm_code.data(), asm_code.size());
sb.append(is_raw ? "}>c" : "");
sb.append(" boc>B \"res\" B>file");

TRY_RESULT(source_lookup, create_source_lookup(std::move(sb), true, true, true, false, false, false, false, fift_dir));
TRY_RESULT(res, run_fift(std::move(source_lookup), &ss));
TRY_RESULT(boc, res.read_file("res"));
return vm::std_boc_deserialize(std::move(boc.data));
Expand Down
17 changes: 1 addition & 16 deletions crypto/func/abscode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,6 @@ void VarDescrList::show(std::ostream& os) const {
os << " ]\n";
}

void Op::flags_set_clear(int set, int clear) {
flags = (flags | set) & ~clear;
for (auto& op : block0) {
op.flags_set_clear(set, clear);
}
for (auto& op : block1) {
op.flags_set_clear(set, clear);
}
}
void Op::split_vars(const std::vector<TmpVar>& vars) {
split_var_list(left, vars);
split_var_list(right, vars);
Expand Down Expand Up @@ -296,7 +287,7 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
if (noreturn()) {
dis += "<noret> ";
}
if (!is_pure()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

if (impure()) {
dis += "<impure> ";
}
switch (cl) {
Expand Down Expand Up @@ -469,12 +460,6 @@ void Op::show_block(std::ostream& os, const Op* block, const std::vector<TmpVar>
os << pfx << "}";
}

void CodeBlob::flags_set_clear(int set, int clear) {
for (auto& op : ops) {
op.flags_set_clear(set, clear);
}
}

std::ostream& operator<<(std::ostream& os, const CodeBlob& code) {
code.print(os);
return os;
Expand Down
49 changes: 40 additions & 9 deletions crypto/func/analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,10 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
case _Tuple:
case _UnTuple: {
// left = EXEC right;
if (!next_var_info.count_used(left) && is_pure()) {
if (!next_var_info.count_used(left) && !impure()) {
// all variables in `left` are not needed
if (edit) {
disable();
set_disabled();
}
return std_compute_used_vars(true);
}
Expand All @@ -372,7 +372,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
case _SetGlob: {
// GLOB = right
if (right.empty() && edit) {
disable();
set_disabled();
}
return std_compute_used_vars(right.empty());
}
Expand All @@ -399,7 +399,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
}
if (!cnt && edit) {
// all variables in `left` are not needed
disable();
set_disabled();
}
return set_var_info(std::move(new_var_info));
}
Expand Down Expand Up @@ -860,15 +860,45 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
}
}

bool Op::set_noreturn(bool nr) {
if (nr) {
void Op::set_disabled(bool flag) {
if (flag) {
flags |= _Disabled;
} else {
flags &= ~_Disabled;
}
}


bool Op::set_noreturn(bool flag) {
if (flag) {
flags |= _NoReturn;
} else {
flags &= ~_NoReturn;
}
return nr;
return flag;
}

void Op::set_impure(const CodeBlob &code) {
// todo calling this function with `code` is a bad design (flags are assigned after Op is constructed)
// later it's better to check this somewhere in code.emplace_back()
if (code.flags & CodeBlob::_ForbidImpure) {
throw src::ParseError(where, "An impure operation in a pure function");
}
flags |= _Impure;
}

void Op::set_impure(const CodeBlob &code, bool flag) {
if (flag) {
if (code.flags & CodeBlob::_ForbidImpure) {
throw src::ParseError(where, "An impure operation in a pure function");
}
flags |= _Impure;
} else {
flags &= ~_Impure;
}
}


bool Op::mark_noreturn() {
switch (cl) {
case _Nop:
Expand All @@ -888,13 +918,14 @@ bool Op::mark_noreturn() {
case _Call:
return set_noreturn(next->mark_noreturn());
case _Return:
return set_noreturn(true);
return set_noreturn();
case _If:
case _TryCatch:
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls
return set_noreturn((block0->mark_noreturn() & (block1 && block1->mark_noreturn())) | next->mark_noreturn());
case _Again:
block0->mark_noreturn();
return set_noreturn(true);
return set_noreturn();
case _Until:
return set_noreturn(block0->mark_noreturn() | next->mark_noreturn());
case _While:
Expand Down
3 changes: 3 additions & 0 deletions crypto/func/asmops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ void AsmOpList::show_var_ext(std::ostream& os, std::pair<var_idx_t, const_idx_t>
os << '_' << i;
} else {
var_names_->at(i).show(os, 2);
// if (!var_names_->at(i).v_type->is_int()) {
// os << '<'; var_names_->at(i).v_type->print(os); os << '>';
// }
}
if ((unsigned)j < constants_.size() && constants_[j].not_null()) {
os << '=' << constants_[j];
Expand Down
63 changes: 52 additions & 11 deletions crypto/func/auto-tests/legacy_tester.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,68 @@
const fs = require('fs/promises');
// Usage: `node legacy_tests.js` from current dir, providing some env (see getenv() calls).
// This is a JS version of legacy_tester.py to test FunC compiled to WASM.

const fs = require('fs');
const path = require('path')
const process = require('process');
const { compileWasm, compileFile } = require('./wasm_tests_common');


/** @return {string} */
function getenv(name, def = null) {
if (name in process.env)
return process.env[name]
if (def === null) {
console.log(`Environment variable ${name} is not set`)
process.exit(1)
}
return def
}

const FUNCFIFTLIB_MODULE = getenv('FUNCFIFTLIB_MODULE')
const FUNCFIFTLIB_WASM = getenv('FUNCFIFTLIB_WASM')
const TESTS_DIR = "legacy_tests"

/**
* @return {{filename: string, code_hash: BigInt}[]}
*/
function load_legacy_tests_list(jsonl_filename) {
let contents = fs.readFileSync(jsonl_filename)
let results = [...contents.toString().matchAll(/^\[\s*"(.*?)"\s*,\s*(.*?)\s*]/gms)]
return results.map((line) => ({
filename: line[1].trim(),
code_hash: BigInt(line[2]),
}))
}

async function main() {
const tests = JSON.parse((await fs.readFile('../legacy_tests.json')).toString('utf-8'))
const tests = load_legacy_tests_list('legacy_tests.jsonl')

for (const [filename, hashstr] of tests) {
if (filename.includes('storage-provider')) continue;
for (let ti = 0; ti < tests.length; ++ti) {
const {filename: filename_rel, code_hash} = tests[ti]
const filename = path.join(TESTS_DIR, filename_rel)
console.log(`Running test ${ti + 1}/${tests.length}: ${filename_rel}`)

const mod = await compileWasm()
if (filename.includes('storage-provider')) {
console.log(" Skip");
continue;
}

const response = await compileFile(mod, filename);
const wasmModule = await compileWasm(FUNCFIFTLIB_MODULE, FUNCFIFTLIB_WASM)
const response = compileFile(wasmModule, filename);

if (response.status !== 'ok') {
console.error(response);
throw new Error('Could not compile ' + filename);
throw new Error(`Could not compile ${filename}`);
}

if (BigInt('0x' + response.codeHashHex) !== BigInt(hashstr)) {
throw new Error('Compilation result is different for ' + filename);
if (BigInt('0x' + response.codeHashHex) !== code_hash) {
throw new Error(`Code hash is different for ${filename}`);
}

console.log(filename, 'ok');
console.log(' OK ');
}

console.log(`Done ${tests.length}`)
}

main()
main().catch(console.error)
Loading
Loading