/* * Copyright 2017 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "type-checker.h" #define CHECK_RESULT(expr) \ do { \ if (WABT_FAILED(expr)) \ return Result::Error; \ } while (0) #define COMBINE_RESULT(result_var, result) \ do { \ (result_var) = static_cast(static_cast(result_var) | \ static_cast(result)); \ } while (0) namespace wabt { TypeCheckerLabel::TypeCheckerLabel(LabelType label_type, const TypeVector& sig, size_t limit) : label_type(label_type), sig(sig), type_stack_limit(limit), unreachable(false) {} static void WABT_PRINTF_FORMAT(2, 3) print_error(TypeChecker* tc, const char* fmt, ...) { if (tc->error_handler->on_error) { WABT_SNPRINTF_ALLOCA(buffer, length, fmt); tc->error_handler->on_error(buffer, tc->error_handler->user_data); } } Result typechecker_get_label(TypeChecker* tc, size_t depth, TypeCheckerLabel** out_label) { if (depth >= tc->label_stack.size()) { assert(tc->label_stack.size() > 0); print_error(tc, "invalid depth: %" PRIzd " (max %" PRIzd ")", depth, tc->label_stack.size() - 1); *out_label = nullptr; return Result::Error; } *out_label = &tc->label_stack[tc->label_stack.size() - depth - 1]; return Result::Ok; } static Result top_label(TypeChecker* tc, TypeCheckerLabel** out_label) { return typechecker_get_label(tc, 0, out_label); } bool typechecker_is_unreachable(TypeChecker* tc) { TypeCheckerLabel* label; if (WABT_FAILED(top_label(tc, &label))) return true; return label->unreachable; } static void reset_type_stack_to_label(TypeChecker* tc, TypeCheckerLabel* label) { tc->type_stack.resize(label->type_stack_limit); } static Result set_unreachable(TypeChecker* tc) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); label->unreachable = true; reset_type_stack_to_label(tc, label); return Result::Ok; } static void push_label(TypeChecker* tc, LabelType label_type, const TypeVector& sig) { tc->label_stack.emplace_back(label_type, sig, tc->type_stack.size()); } static Result pop_label(TypeChecker* tc) { tc->label_stack.pop_back(); return Result::Ok; } static Result check_label_type(TypeCheckerLabel* label, LabelType label_type) { return label->label_type == label_type ? Result::Ok : Result::Error; } static Result peek_type(TypeChecker* tc, uint32_t depth, Type* out_type) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); if (label->type_stack_limit + depth >= tc->type_stack.size()) { *out_type = Type::Any; return label->unreachable ? Result::Ok : Result::Error; } *out_type = tc->type_stack[tc->type_stack.size() - depth - 1]; return Result::Ok; } static Result top_type(TypeChecker* tc, Type* out_type) { return peek_type(tc, 0, out_type); } static Result pop_type(TypeChecker* tc, Type* out_type) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); Result result = top_type(tc, out_type); if (tc->type_stack.size() > label->type_stack_limit) tc->type_stack.pop_back(); return result; } static Result drop_types(TypeChecker* tc, size_t drop_count) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); if (label->type_stack_limit + drop_count > tc->type_stack.size()) { if (label->unreachable) { reset_type_stack_to_label(tc, label); return Result::Ok; } return Result::Error; } tc->type_stack.erase(tc->type_stack.end() - drop_count, tc->type_stack.end()); return Result::Ok; } static void push_type(TypeChecker* tc, Type type) { if (type != Type::Void) tc->type_stack.push_back(type); } static void push_types(TypeChecker* tc, const TypeVector& types) { for (Type type: types) push_type(tc, type); } static Result check_type_stack_limit(TypeChecker* tc, size_t expected, const char* desc) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); size_t avail = tc->type_stack.size() - label->type_stack_limit; if (!label->unreachable && expected > avail) { print_error(tc, "type stack size too small at %s. got %" PRIzd ", expected at least %" PRIzd, desc, avail, expected); return Result::Error; } return Result::Ok; } static Result check_type_stack_end(TypeChecker* tc, const char* desc) { TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); if (tc->type_stack.size() != label->type_stack_limit) { print_error(tc, "type stack at end of %s is %" PRIzd ", expected %" PRIzd, desc, tc->type_stack.size(), label->type_stack_limit); return Result::Error; } return Result::Ok; } static Result check_type(TypeChecker* tc, Type actual, Type expected, const char* desc) { if (expected != actual && expected != Type::Any && actual != Type::Any) { print_error(tc, "type mismatch in %s, expected %s but got %s.", desc, get_type_name(expected), get_type_name(actual)); return Result::Error; } return Result::Ok; } static Result check_signature(TypeChecker* tc, const TypeVector& sig, const char* desc) { Result result = Result::Ok; COMBINE_RESULT(result, check_type_stack_limit(tc, sig.size(), desc)); for (size_t i = 0; i < sig.size(); ++i) { Type actual = Type::Any; COMBINE_RESULT(result, peek_type(tc, sig.size() - i - 1, &actual)); COMBINE_RESULT(result, check_type(tc, actual, sig[i], desc)); } return result; } static Result pop_and_check_signature(TypeChecker* tc, const TypeVector& sig, const char* desc) { Result result = Result::Ok; COMBINE_RESULT(result, check_signature(tc, sig, desc)); COMBINE_RESULT(result, drop_types(tc, sig.size())); return result; } static Result pop_and_check_call(TypeChecker* tc, const TypeVector& param_types, const TypeVector& result_types, const char* desc) { Result result = Result::Ok; COMBINE_RESULT(result, check_type_stack_limit(tc, param_types.size(), desc)); for (size_t i = 0; i < param_types.size(); ++i) { Type actual = Type::Any; COMBINE_RESULT(result, peek_type(tc, param_types.size() - i - 1, &actual)); COMBINE_RESULT(result, check_type(tc, actual, param_types[i], desc)); } COMBINE_RESULT(result, drop_types(tc, param_types.size())); push_types(tc, result_types); return result; } static Result pop_and_check_1_type(TypeChecker* tc, Type expected, const char* desc) { Result result = Result::Ok; Type actual = Type::Any; COMBINE_RESULT(result, check_type_stack_limit(tc, 1, desc)); COMBINE_RESULT(result, pop_type(tc, &actual)); COMBINE_RESULT(result, check_type(tc, actual, expected, desc)); return result; } static Result pop_and_check_2_types(TypeChecker* tc, Type expected1, Type expected2, const char* desc) { Result result = Result::Ok; Type actual1 = Type::Any; Type actual2 = Type::Any; COMBINE_RESULT(result, check_type_stack_limit(tc, 2, desc)); COMBINE_RESULT(result, pop_type(tc, &actual2)); COMBINE_RESULT(result, pop_type(tc, &actual1)); COMBINE_RESULT(result, check_type(tc, actual1, expected1, desc)); COMBINE_RESULT(result, check_type(tc, actual2, expected2, desc)); return result; } static Result pop_and_check_2_types_are_equal(TypeChecker* tc, Type* out_type, const char* desc) { Result result = Result::Ok; Type right = Type::Any; Type left = Type::Any; COMBINE_RESULT(result, check_type_stack_limit(tc, 2, desc)); COMBINE_RESULT(result, pop_type(tc, &right)); COMBINE_RESULT(result, pop_type(tc, &left)); COMBINE_RESULT(result, check_type(tc, left, right, desc)); *out_type = right; return result; } static Result check_opcode1(TypeChecker* tc, Opcode opcode) { Result result = pop_and_check_1_type(tc, get_opcode_param_type_1(opcode), get_opcode_name(opcode)); push_type(tc, get_opcode_result_type(opcode)); return result; } static Result check_opcode2(TypeChecker* tc, Opcode opcode) { Result result = pop_and_check_2_types(tc, get_opcode_param_type_1(opcode), get_opcode_param_type_2(opcode), get_opcode_name(opcode)); push_type(tc, get_opcode_result_type(opcode)); return result; } Result typechecker_begin_function(TypeChecker* tc, const TypeVector* sig) { tc->type_stack.clear(); tc->label_stack.clear(); push_label(tc, LabelType::Func, *sig); return Result::Ok; } Result typechecker_on_binary(TypeChecker* tc, Opcode opcode) { return check_opcode2(tc, opcode); } Result typechecker_on_block(TypeChecker* tc, const TypeVector* sig) { push_label(tc, LabelType::Block, *sig); return Result::Ok; } Result typechecker_on_br(TypeChecker* tc, size_t depth) { Result result = Result::Ok; TypeCheckerLabel* label; CHECK_RESULT(typechecker_get_label(tc, depth, &label)); if (label->label_type != LabelType::Loop) COMBINE_RESULT(result, check_signature(tc, label->sig, "br")); CHECK_RESULT(set_unreachable(tc)); return result; } Result typechecker_on_br_if(TypeChecker* tc, size_t depth) { Result result = Result::Ok; COMBINE_RESULT(result, pop_and_check_1_type(tc, Type::I32, "br_if")); TypeCheckerLabel* label; CHECK_RESULT(typechecker_get_label(tc, depth, &label)); if (label->label_type != LabelType::Loop) COMBINE_RESULT(result, check_signature(tc, label->sig, "br_if")); return result; } Result typechecker_begin_br_table(TypeChecker* tc) { tc->br_table_sig = Type::Any; return pop_and_check_1_type(tc, Type::I32, "br_table"); } Result typechecker_on_br_table_target(TypeChecker* tc, size_t depth) { Result result = Result::Ok; TypeCheckerLabel* label; CHECK_RESULT(typechecker_get_label(tc, depth, &label)); Type label_sig; if (label->label_type == LabelType::Loop) { label_sig = Type::Void; } else { assert(label->sig.size() <= 1); label_sig = label->sig.size() == 0 ? Type::Void : label->sig[0]; } COMBINE_RESULT(result, check_type(tc, tc->br_table_sig, label_sig, "br_table")); tc->br_table_sig = label_sig; if (label->label_type != LabelType::Loop) COMBINE_RESULT(result, check_signature(tc, label->sig, "br_table")); return result; } Result typechecker_end_br_table(TypeChecker* tc) { return set_unreachable(tc); } Result typechecker_on_call(TypeChecker* tc, const TypeVector* param_types, const TypeVector* result_types) { return pop_and_check_call(tc, *param_types, *result_types, "call"); } Result typechecker_on_call_indirect(TypeChecker* tc, const TypeVector* param_types, const TypeVector* result_types) { Result result = Result::Ok; COMBINE_RESULT(result, pop_and_check_1_type(tc, Type::I32, "call_indirect")); COMBINE_RESULT(result, pop_and_check_call(tc, *param_types, *result_types, "call_indirect")); return result; } Result typechecker_on_compare(TypeChecker* tc, Opcode opcode) { return check_opcode2(tc, opcode); } Result typechecker_on_const(TypeChecker* tc, Type type) { push_type(tc, type); return Result::Ok; } Result typechecker_on_convert(TypeChecker* tc, Opcode opcode) { return check_opcode1(tc, opcode); } Result typechecker_on_current_memory(TypeChecker* tc) { push_type(tc, Type::I32); return Result::Ok; } Result typechecker_on_drop(TypeChecker* tc) { Result result = Result::Ok; Type type = Type::Any; COMBINE_RESULT(result, check_type_stack_limit(tc, 1, "drop")); COMBINE_RESULT(result, pop_type(tc, &type)); return result; } Result typechecker_on_else(TypeChecker* tc) { Result result = Result::Ok; TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); COMBINE_RESULT(result, check_label_type(label, LabelType::If)); COMBINE_RESULT(result, pop_and_check_signature(tc, label->sig, "if true branch")); COMBINE_RESULT(result, check_type_stack_end(tc, "if true branch")); reset_type_stack_to_label(tc, label); label->label_type = LabelType::Else; label->unreachable = false; return result; } static Result on_end(TypeChecker* tc, TypeCheckerLabel* label, const char* sig_desc, const char* end_desc) { Result result = Result::Ok; COMBINE_RESULT(result, pop_and_check_signature(tc, label->sig, sig_desc)); COMBINE_RESULT(result, check_type_stack_end(tc, end_desc)); reset_type_stack_to_label(tc, label); push_types(tc, label->sig); pop_label(tc); return result; } Result typechecker_on_end(TypeChecker* tc) { Result result = Result::Ok; static const char* s_label_type_name[] = {"function", "block", "loop", "if", "if false branch"}; WABT_STATIC_ASSERT(WABT_ARRAY_SIZE(s_label_type_name) == kLabelTypeCount); TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); assert(static_cast(label->label_type) < kLabelTypeCount); if (label->label_type == LabelType::If) { if (label->sig.size() != 0) { print_error(tc, "if without else cannot have type signature."); result = Result::Error; } } const char* desc = s_label_type_name[static_cast(label->label_type)]; COMBINE_RESULT(result, on_end(tc, label, desc, desc)); return result; } Result typechecker_on_grow_memory(TypeChecker* tc) { return check_opcode1(tc, Opcode::GrowMemory); } Result typechecker_on_if(TypeChecker* tc, const TypeVector* sig) { Result result = pop_and_check_1_type(tc, Type::I32, "if"); push_label(tc, LabelType::If, *sig); return result; } Result typechecker_on_get_global(TypeChecker* tc, Type type) { push_type(tc, type); return Result::Ok; } Result typechecker_on_get_local(TypeChecker* tc, Type type) { push_type(tc, type); return Result::Ok; } Result typechecker_on_load(TypeChecker* tc, Opcode opcode) { return check_opcode1(tc, opcode); } Result typechecker_on_loop(TypeChecker* tc, const TypeVector* sig) { push_label(tc, LabelType::Loop, *sig); return Result::Ok; } Result typechecker_on_return(TypeChecker* tc) { Result result = Result::Ok; TypeCheckerLabel* func_label; CHECK_RESULT( typechecker_get_label(tc, tc->label_stack.size() - 1, &func_label)); COMBINE_RESULT(result, pop_and_check_signature(tc, func_label->sig, "return")); CHECK_RESULT(set_unreachable(tc)); return result; } Result typechecker_on_select(TypeChecker* tc) { Result result = Result::Ok; COMBINE_RESULT(result, pop_and_check_1_type(tc, Type::I32, "select")); Type type = Type::Any; COMBINE_RESULT(result, pop_and_check_2_types_are_equal(tc, &type, "select")); push_type(tc, type); return result; } Result typechecker_on_set_global(TypeChecker* tc, Type type) { return pop_and_check_1_type(tc, type, "set_global"); } Result typechecker_on_set_local(TypeChecker* tc, Type type) { return pop_and_check_1_type(tc, type, "set_local"); } Result typechecker_on_store(TypeChecker* tc, Opcode opcode) { return check_opcode2(tc, opcode); } Result typechecker_on_tee_local(TypeChecker* tc, Type type) { Result result = Result::Ok; Type value = Type::Any; COMBINE_RESULT(result, check_type_stack_limit(tc, 1, "tee_local")); COMBINE_RESULT(result, top_type(tc, &value)); COMBINE_RESULT(result, check_type(tc, value, type, "tee_local")); return result; } Result typechecker_on_unary(TypeChecker* tc, Opcode opcode) { return check_opcode1(tc, opcode); } Result typechecker_on_unreachable(TypeChecker* tc) { return set_unreachable(tc); } Result typechecker_end_function(TypeChecker* tc) { Result result = Result::Ok; TypeCheckerLabel* label; CHECK_RESULT(top_label(tc, &label)); COMBINE_RESULT(result, check_label_type(label, LabelType::Func)); COMBINE_RESULT(result, on_end(tc, label, "implicit return", "function")); return result; } } // namespace wabt