HNU编译原理实验三cminus_compiler-2022-fall

news2024/11/18 22:47:28

前言:实验难度再次提升,不过会一个就可以做其他部分,很多都是相同的,个人认为更难的还是配置环境部分,真的会折磨死人

lab3 实验报告

实验要求

  1. 第一部分: 了解LLVM IR。通过clang生成的.ll,了解LLVM IR与c代码的对应关系。完成1.3
  2. 第二部分: 了解LightIR。通过助教提供的c++例子,了解LightIR的c++接口及实现。完成2.3
  3. 第三部分: 理解Visitor Pattern。
  4. 实验报告:在report.md中回答3个问题

实验设计

问题1: cpp与.ll的对应

请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。

根据 1.2 gcd例子: 利用clang生成的.ll 进行学习

输入clang -S -emit-llvm gcd_array.c指令:

在这里插入图片描述

查看 gcd_array.ll 文件,并输入lli gcd_array.ll; echo $?指令,验证 gcd_array.ll 的正确性:

在这里插入图片描述

可以看到,返回结果为18,运行gcd_array.c 查看运行结果:

在这里插入图片描述

可以得出其运行后,输出结果也为18。 因此,可以验证 gcd_array.ll 文件正确地对应了 gcd_array.c 文件。

补充assign_hand.ll

查看 assign.c 文件

int main(){
  int a[10];
  a[0] = 10;
  a[1] = a[0] * 2;
  return a[1];
}

很明显运行结果返回20,下面根据上面的测试样例和精简的IR Reference手册,补充补充 assign_hand.ll

; ModuleID = 'assign.c'
source_filename = "assign.c"
; target的开始
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
;target的结束

; 全局main函数的定义
define dso_local i32 @main() #0{
	;第一个基本块的开始
	;语句:int a[10];创建空间,并返回指针
	%1 = alloca [10 x i32]	
	
	;语句:a[0] = 10;获取a[0]指针
	%2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 0
	; 将10存入a[0]中
	store i32 10, i32* %2
	
	;语句:a[1] = a[0] * 2;
	%3 = load i32, i32* %2	; 取出a[0],存到%3中
	%4 = mul i32 %3, 2		; 将值乘以2,存在%4中
	%5 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 1	;获取a[1]指针
	store i32 %4, i32* %5	; 写入a[1]中
	
	; 语句:return a[1]
	ret i32 %4				; 将a[0]返回
}

结果验证:

运行assign.c ,输入命令:gcc -o assign assign.c ./assign;echo $?,结果如下:

在这里插入图片描述

运行刚才补充的assign_hand.ll ,输入命令lli assign_hand.ll; echo $? ,运行结果如下:

在这里插入图片描述

可以看到,结果一样,都为20,说明补充正确。assign_hand.ll文件正确地对应了 assign.c 文件。

补充fun_hand.ll

查看 fun.c 文件

int callee(int a){
  return 2 * a;
}
int main(){
  return callee(110);
}

很明显运行结果返回220,下面根据上面的测试样例和精简的IR Reference手册,补充补充 fun_hand.ll

; ModuleID = 'fun.c'
source_filename = "fun.c"
; target的开始
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
;target的结束
    
; 语句:int callee(int a)。callee 函数的定义
define dso_local i32 @callee(i32) #0{
    ; 语句:return 2 * a
    %2 = mul i32 %0, 2		; 将参数%0乘以2存入%2中
	ret i32 %2				; 返回%2
}

; main 函数的定义
define dso_local i32 @main() #0{
    ; 语句:return callee(110);
	%1 = call i32 @callee(i32 110)	; 调用函数callee,并将结果存到%1中
	ret i32 %1					; 返回%1
}

结果验证:

运行fun.c ,输入命令:gcc -o fun fun.c ./fun;echo $?,结果如下:

在这里插入图片描述

运行刚才补充的fun_hand.ll ,输入命令lli fun_hand.ll; echo $? ,运行结果如下:

在这里插入图片描述

这里有一些要注意的地方。第一点是对callee函数定义时define dso_local i32 @callee(i32)传入的参数如果只有一个int型,可以直接传入i32,默认传入参数为%0,如果写的是i32 %0会报错,初步认为应该和llvm版本有关。

第二点就是 %2 = mul i32 %0, 2这一条语句,为什么不能用%1进行赋值,会报错

在这里插入图片描述

程序也会建议使用%2赋值

补充if_hand.ll

查看 fun.c 文件

int main(){
  float a = 5.555;
  if(a > 1)
    return 233;
  return 0;
}

很明显运行结果返回233,下面根据上面的测试样例和精简的IR Reference手册,补充补充 fun_hand.ll

; ModuleID = 'if.c'
source_filename = "if.c"
; target的开始
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
;target的结束

; main 函数的定义
define dso_local i32 @main() #0{
	;语句:; float a = 5.555;
	%1 = alloca float      ;为a开辟栈空间
	store float 0x40163851E0000000, float* %1  ;将浮点数5.555写入a指向的内存空间
	
	; 语句:if(a > 1)
	%2 = load float, float* %1		; 取出%1所指向的空间中的值赋给%2
	%3 = fcmp ugt float %2, 1.000	; 将其和1进行比较,返回结果到%3中,是布尔型
	br i1 %3, label %lebel_1, label %lebel_2	; 如果%31,则跳转到%4,否则跳转到%5

lebel_1:	; 语句:return 233;
	ret i32 233		; 返回233

lebel_2:	; 语句:return 0;
	ret i32 0		; 返回0
}

结果验证:

运行if.c ,输入命令:gcc -o if if.c ./if;echo $?,结果如下:

在这里插入图片描述

运行刚才补充的if_hand.ll ,输入命令lli if_hand.ll; echo $? ,运行结果如下:

在这里插入图片描述

这里有一个难点就是给float a = 5.555;赋值。如果直接使用store float 5.555, float* %1会报错。 输入clang -S -emit-llvm if.c指令,看了一下样例,发现要写成浮点数的机器表达形式。 即

5.555的二进制表示:101.10001110000101000111101011100001010001111010111
尾数正规化 :1.0110001110000101000111101011100001010001111010111*22
E=2, exp = E + Bias =129,二进制表示为10000001
符号位为0
有效数字M等于 01100011100001010001111
故5.555的IEEE表示为
0 10000001 01100011100001010001111
转换为十六进制为0x40b1c28f
但样例给出的是0x40163851E0000000,后经验证发现样例给的是double型的IEEE标识,但程序和精简的IR Reference手册都说明了float是单精度的,所以我觉得这里还是有点问题

补充while.ll

查看 while.c 文件

int main(){
  int a;
  int i;
  a = 10;
  i = 0;
  while(i < 10){
    i = i + 1;
    a = a + i;
  }
  return a;
}

很明显运行结果返回65,下面根据上面的测试样例和精简的IR Reference手册,补充补充 fun_hand.ll

; ModuleID = 'if.c'
source_filename = "if.c"
; target的开始
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
;target的结束

; main 函数的定义
define dso_local i32 @main() #0{
	;为变量a和i开辟栈空间
	%1=alloca i32
	%2=alloca i32
	;100写入a和i指向的内存空间
	store i32 10,i32* %1
	store i32 0,i32* %2
	;跳到while条件判断中
	br label %label_1

;i < 10
label_1:
	;取出i的值赋给%3
	%3=load i32, i32* %2
	;将i和10进行比较,得到的结果为布尔型赋给%4
	%4 = icmp ult i32 %3, 10
	;如果%41跳到label_2,否则跳到%label_3
	br i1 %4,label %label_2,label %label_3

;循环体的内容	
label_2:
	;i=i+1
	%5=add i32 %3 , 1
	store i32 %5,i32* %2
	;a=a+1,先取出a的值再相加再存回a里
	%6=load i32,i32* %1
	%7=add i32 %6,%5
	store i32 %7,i32* %1
	;跳回条件判断
	br label %label_1
;return a
label_3:
	;取出a的值
	%8=load i32,i32* %1
	;返回a
	ret i32 %8
}

结果验证:

运行while.c ,输入命令:gcc -o while while.c ./while;echo $?,结果如下:

在这里插入图片描述

运行刚才补充的while_hand.ll ,输入命令lli while_hand.ll; echo $? ,运行结果如下:

在这里插入图片描述

补充assign_generator.cpp

首先查看样例gcd_array_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG  // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;  // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到

int main() {
  auto module = new Module("Cminus code");  // module name是什么无关紧要
  auto builder = new IRBuilder(nullptr, module);
  Type *Int32Type = Type::get_int32_type(module);

  // 全局数组,x,y
  auto *arrayType = ArrayType::get(Int32Type, 1);
  auto initializer = ConstantZero::get(Int32Type, module);
  auto x = GlobalVariable::create("x", module, arrayType, false, initializer);// 参数解释:  名字name,所属module,全局变量类型type,
  auto y = GlobalVariable::create("y", module, arrayType, false, initializer);//          是否是常量定义(cminus中没有常量概念,应全都是false),初始化常量(ConstantZero类)

  // gcd函数
  // 函数参数类型的vector
  std::vector<Type *> Ints(2, Int32Type);

  //通过返回值类型与参数类型列表得到函数类型
  auto gcdFunTy = FunctionType::get(Int32Type, Ints);

  // 由函数类型得到函数
  auto gcdFun = Function::create(gcdFunTy,
                                 "gcd", module);

  // BB的名字在生成中无所谓,但是可以方便阅读
  auto bb = BasicBlock::create(module, "entry", gcdFun);
  
  builder->set_insert_point(bb);                        // 一个BB的开始,将当前插入指令点的位置设在bb
  
  auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置
  auto uAlloca = builder->create_alloca(Int32Type);     // 在内存中分配参数u的位置
  auto vAlloca = builder->create_alloca(Int32Type);     // 在内存中分配参数v的位置 

  std::vector<Value *> args;  // 获取gcd函数的形参,通过Function中的iterator
  for (auto arg = gcdFun->arg_begin(); arg != gcdFun->arg_end(); arg++) {
    args.push_back(*arg);   // * 号运算符是从迭代器中取出迭代器当前指向的元素
  }

  builder->create_store(args[0], uAlloca);  // 将参数u store下来
  builder->create_store(args[1], vAlloca);  // 将参数v store下来

  auto vLoad = builder->create_load(vAlloca);           // 将参数v load上来
  auto icmp = builder->create_icmp_eq(vLoad, CONST_INT(0));  // v和0的比较,注意ICMPEQ

  auto trueBB = BasicBlock::create(module, "trueBB", gcdFun);    // true分支
  auto falseBB = BasicBlock::create(module, "falseBB", gcdFun);  // false分支
  auto retBB = BasicBlock::create(
      module, "", gcdFun);  // return分支,提前create,以便true分支可以br

  auto br = builder->create_cond_br(icmp, trueBB, falseBB);  // 条件BR
  DEBUG_OUTPUT // 我调试的时候故意留下来的,以醒目地提醒你这个调试用的宏定义方法
  builder->set_insert_point(trueBB);  // if true; 分支的开始需要SetInsertPoint设置
  auto uLoad = builder->create_load(uAlloca);
  builder->create_store(uLoad, retAlloca);
  builder->create_br(retBB);  // br retBB

  builder->set_insert_point(falseBB);  // if false
  uLoad = builder->create_load(uAlloca);
  vLoad = builder->create_load(vAlloca);
  auto div = builder->create_isdiv(uLoad, vLoad);  // SDIV - div with S flag
  auto mul = builder->create_imul(div, vLoad);  // MUL - mul
  auto sub = builder->create_isub(uLoad, mul);  // the same
  auto call = builder->create_call(gcdFun, {vLoad, sub}); // 创建call指令
  // {vLoad, sub} - 参数array
  builder->create_store(call, retAlloca);
  builder->create_br(retBB);  // br retBB

  builder->set_insert_point(retBB);  // ret分支
  auto retLoad = builder->create_load(retAlloca);
  builder->create_ret(retLoad);

  // funArray函数
  auto Int32PtrType = Type::get_int32_ptr_type(module);   // 单个参数的类型,指针
  std::vector<Type *> IntPtrs(2, Int32PtrType);           // 参数列表类型
  auto funArrayFunType = FunctionType::get(Int32Type, IntPtrs);   // 函数类型
  auto funArrayFun = Function::create(funArrayFunType, "funArray", module);
  bb = BasicBlock::create(module, "entry", funArrayFun);
  builder->set_insert_point(bb);
  auto upAlloca = builder->create_alloca(Int32PtrType);   // u的存放
  auto vpAlloca = builder->create_alloca(Int32PtrType);   // v的存放
  auto aAlloca = builder->create_alloca(Int32Type);       // a的存放
  auto bAlloca = builder->create_alloca(Int32Type);       // b的存放
  auto tempAlloca = builder->create_alloca(Int32Type);    // temp的存放

  std::vector<Value *> args1;  //获取funArrayFun函数的形参,通过Function中的iterator
  for (auto arg = funArrayFun->arg_begin(); arg != funArrayFun->arg_end(); arg++) {
    args1.push_back(*arg); // * 号运算符是从迭代器中取出迭代器当前指向的元素
  }
  builder->create_store(args1[0], upAlloca);              // 将参数u store下来
  builder->create_store(args1[1], vpAlloca);              // 将参数v store下来

  auto u0pLoad = builder->create_load(upAlloca);          // 读取u
  auto u0GEP = builder->create_gep(u0pLoad, {CONST_INT(0)});  // GEP: 获取u[0]地址
  auto u0Load = builder->create_load(u0GEP);              // 从u[0]地址 读取u[0]
  builder->create_store(u0Load, aAlloca);                 // 将u[0] 写入 a
  auto v0pLoad = builder->create_load(vpAlloca);          // 同上
  auto v0GEP = builder->create_gep(v0pLoad, {CONST_INT(0)});
  auto v0Load = builder->create_load(v0GEP);
  builder->create_store(v0Load, bAlloca);

  auto aLoad = builder->create_load(aAlloca);
  auto bLoad = builder->create_load(bAlloca);
  icmp = builder->create_icmp_lt(aLoad, bLoad);
  trueBB = BasicBlock::create(module, "trueBB", funArrayFun);
  falseBB = BasicBlock::create(module, "falseBB", funArrayFun);
  builder->create_cond_br(icmp, trueBB, falseBB);

  builder->set_insert_point(trueBB);
  builder->create_store(aLoad, tempAlloca);
  builder->create_store(bLoad, aAlloca);
  auto tempLoad = builder->create_load(tempAlloca);
  builder->create_store(tempLoad, bAlloca);
  builder->create_br(falseBB);  // 注意在下一个BB之前要Br一下

  builder->set_insert_point(falseBB);
  aLoad = builder->create_load(aAlloca);
  bLoad = builder->create_load(bAlloca);
  call = builder->create_call(gcdFun, {aLoad, bLoad});
  builder->create_ret(call);


  // main函数
  auto mainFun = Function::create(FunctionType::get(Int32Type, {}),
                                  "main", module);
  bb = BasicBlock::create(module, "entry", mainFun);
  // BasicBlock的名字在生成中无所谓,但是可以方便阅读
  builder->set_insert_point(bb);

  retAlloca = builder->create_alloca(Int32Type);
  builder->create_store(CONST_INT(0), retAlloca);             // 默认 ret 0

  auto x0GEP = builder->create_gep(x, {CONST_INT(0), CONST_INT(0)});  // GEP: 这里为什么是{0, 0}呢? (实验报告相关)
  builder->create_store(CONST_INT(90), x0GEP);
  auto y0GEP = builder->create_gep(y, {CONST_INT(0), CONST_INT(0)});  // GEP: 这里为什么是{0, 0}呢? (实验报告相关)
  builder->create_store(CONST_INT(18), y0GEP);

  x0GEP = builder->create_gep(x, {CONST_INT(0), CONST_INT(0)});
  y0GEP = builder->create_gep(y, {CONST_INT(0), CONST_INT(0)});
  call = builder->create_call(funArrayFun, {x0GEP, y0GEP});           // 为什么这里传的是{x0GEP, y0GEP}呢?

  builder->create_ret(call);
  // 给这么多注释了,但是可能你们还是会弄很多bug
  // 所以强烈建议配置AutoComplete,效率会大大提高!
  // 别人配了AutoComplete,只花1小时coding
  // 你没有配AutoComplete,找method花5小时,debug花5小时,肯定哭唧唧!
  // 最后,如果猜不到某个IR指令对应的C++的函数,建议把指令翻译成英语然后在method列表中搜索一下
  // 最后的最后,这个例子只涉及到了一点基本的指令生成,
  // 对于额外的指令,包括数组,在之后的实验中可能需要大家好好搜索一下思考一下,
  // 还有涉及到的C++语法,可以在gitlab上发issue提问或者向大家提供指导,会有bonus哦!
  // 对于这个例子里的代码风格/用法,如果有好的建议也欢迎提出!
  std::cout << module->print();
  delete module;
  return 0;
}

按照gcd_array_generator.cpp来补充assign_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG  // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;  // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
    
int main(){
	auto module = new Module("Cminus code");  // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);      // 创建IRBuilder,该类提供了独立的接口创建各种 IR 指令
	Type* Int32Type = Type::get_int32_type(module);

	// main函数
	auto mainFun = Function::create(FunctionType::get(Int32Type, {}),   /* 创建 main 函数 */	
	auto bb = BasicBlock::create(module, "entry", mainFun); 
    builder->set_insert_point(bb);  
    
    auto retAlloca = builder->create_alloca(Int32Type);  //分配一个保存返回值的内容
    
    auto *arrayType = ArrayType::get(Int32Type, 10);  //数组类型,参数依次是数组元素的类型Int32Type,数组元素个数10
    auto a=builder->create_alloca(arrayType);  //数组a[10]

    auto a0GEP = builder->create_gep(a, {CONST_INT(0), CONST_INT(0)}); //获取a[0]的地址
    builder->create_store(CONST_INT(10), a0GEP);  //将整数10写入a[0]的地址
    auto a0Load = builder->create_load(a0GEP);  //读取a[0]
    auto m=builder->create_imul(a0Load,CONST_INT(2)); //a[0]*2

    auto a1GEP = builder->create_gep(a, {CONST_INT(0), CONST_INT(1)});  //获取a[1]的地址
    builder->create_store(m, a1GEP); //将a[0]*2写入a[1]的地址
    auto a1Load = builder->create_load(a1GEP); //获取a[1]的值

    builder->create_store(a1Load, retAlloca); //将a[1]的值写入retAlloca地址中
    auto retLoad=builder->create_load(retAlloca); //读取retAlloca地址的值
    builder->create_ret(retLoad);  //创建ret指令,即返回

    std::cout << module->print();
    delete module;
    return 0;   
}

结果验证:

输入指令make,编译通过后输入指令./stu_assign_generator>assign.ll

在这里插入图片描述

assign.ll结果如下:

define i32 @main() {
label_entry:
  %op0 = alloca i32
  %op1 = alloca [10 x i32]
  %op2 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 0
  store i32 10, i32* %op2
  %op3 = load i32, i32* %op2
  %op4 = mul i32 %op3, 2
  %op5 = getelementptr [10 x i32], [10 x i32]* %op1, i32 0, i32 1
  store i32 %op4, i32* %op5
  %op6 = load i32, i32* %op5
  store i32 %op6, i32* %op0
  %op7 = load i32, i32* %op0
  ret i32 %op7
}

输入 lli assign.ll; echo $?指令进行测试,结果如下:

在这里插入图片描述

输出结果为 20,结果正确。

只有一个basicblock,对应关系为:

auto bb = BasicBlock::create(module, "entry", mainFun);创建名为entry的基本块,并将其存到变量bb中,对应.ll文件中的label_entry

补充fun_generator.cpp

fun_generator.cpp 代码如下:

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG  // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;  // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
int main() {
  auto module = new Module("fun code");
  auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
  Type *Int32Type = Type::get_int32_type(module);
  
  //函数callee
  std::vector<Type *> Ints(1, Int32Type); // 函数参数类型的vector
  auto calleeFunTy = FunctionType::get(Int32Type, Ints); //通过返回值类型与参数类型列表得到函数类型
  auto calleeFun = Function::create(calleeFunTy,"callee", module);   // 由函数类型得到函数
  
  auto bb = BasicBlock::create(module, "entry", calleeFun);
  builder->set_insert_point(bb);                 // 一个BB的开始,将当前插入指令点的位置设在bb

  auto aAlloca = builder->create_alloca(Int32Type);  // 在内存中分配参数a的位置
 
  std::vector<Value *> args;  // 获取函数的形参,通过Function中的iterator
  for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++) {
    args.push_back(*arg);   // * 号运算符是从迭代器中取出迭代器当前指向的元素
  }
  builder->create_store(args[0], aAlloca);  // 将参数a store下来
  auto aLoad=builder->create_load(aAlloca); //获取参数a的值
  auto m=builder->create_imul(aLoad,CONST_INT(2)); //a*2
  
  auto retAlloca = builder->create_alloca(Int32Type);  //分配一个保存返回值的内容
  builder->create_store(m, retAlloca); //将m的值写入retAlloca地址中
  auto retLoad=builder->create_load(retAlloca); //读取retAlloca地址的值
  builder->create_ret(retLoad);  //创建ret指令,即返回

  // main函数
  auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
  bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
  builder->set_insert_point(bb);  //将当前插入指令点的位置设在bb
  
  auto cs= builder->create_alloca(Int32Type); // 在内存中分配参数cs的位置
  builder->create_store(CONST_INT(110), cs);  //将整数10写入cs的地址
  auto csLoad = builder->create_load(cs);  //读取cs
  auto call = builder->create_call(calleeFun, {csLoad}); //创建call指令
  builder->create_ret(call); //创建ret指令,返回

  std::cout << module->print();
  delete module;
  return 0;
}

结果验证:

输入指令make,编译通过后输入指令./stu_fun_generator >fun.ll

内容如下:

define i32 @callee(i32 %arg0) {
label_entry:
  %op1 = alloca i32
  store i32 %arg0, i32* %op1
  %op2 = load i32, i32* %op1
  %op3 = mul i32 %op2, 2
  %op4 = alloca i32
  store i32 %op3, i32* %op4
  %op5 = load i32, i32* %op4
  ret i32 %op5
}
define i32 @main() {
label_entry:
  %op0 = alloca i32
  store i32 110, i32* %op0
  %op1 = load i32, i32* %op0
  %op2 = call i32 @callee(i32 %op1)
  ret i32 %op2
}

输入 lli fun.ll; echo $?指令进行测试,结果如下:

在这里插入图片描述

结果为220,输出正确

创建了两个块,分别为 auto bb = BasicBlock::create(module, "entry", calleeFun);bb = BasicBlock::create(module, "entry", mainFun); ,其分别对应.ll文件中 callee 函数中的标签label_entrymain函数中的标签label_entry

补充if_generator.cpp

if_generator.cpp代码如下:

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG  // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;  // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
    
int main() {
  auto module = new Module("if code");
  auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
  Type *Int32Type = Type::get_int32_type(module);
  Type *FloatType = Type::get_float_type(module);
  
  // main函数
  auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
  auto bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
  builder->set_insert_point(bb);  //将当前插入指令点的位置设在bb
  
  auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置
  
  auto aAlloca=builder->create_alloca(FloatType); //在内存中分配参数a的位置
  builder->create_store(CONST_FP(5.555), aAlloca);  //将常数5.555写入a的地址
  auto aLoad = builder->create_load(aAlloca);  //读取a
  
  auto cmp = builder->create_fcmp_gt(aLoad, CONST_FP(1.000));  //判断a是否大于1
  auto trueBB = BasicBlock::create(module, "trueBB", mainFun);    // true分支
  auto falseBB = BasicBlock::create(module, "falseBB", mainFun);  // false分支
  auto retBB = BasicBlock::create(module, "", mainFun);  // return分支,提前create,以便true分支可以br
  auto br = builder->create_cond_br(cmp, trueBB, falseBB);  // 条件BR
  
  builder->set_insert_point(trueBB);  // if true; 分支的开始需要SetInsertPoint设置
  builder->create_store(CONST_INT(233), retAlloca); //将整数233存储到返回值的地址中
  builder->create_br(retBB);  // br retBB
  
  builder->set_insert_point(falseBB);  // if false
  builder->create_store(CONST_INT(0), retAlloca);  //将整数0存储到返回值的地址中
  builder->create_br(retBB);  // br retBB

  builder->set_insert_point(retBB);  // ret分支
  auto retLoad = builder->create_load(retAlloca);  //获取返回值地址中值
  builder->create_ret(retLoad);  //创建ret指令,返回

  std::cout << module->print();
  delete module;
  return 0;
}

结果验证:

输入指令make,编译通过后输入指令./stu_if_generator >if.ll

内容如下:

define i32 @main() {
label_entry:
  %op0 = alloca i32
  %op1 = alloca float
  store float 0x40163851e0000000, float* %op1
  %op2 = load float, float* %op1
  %op3 = fcmp ugt float %op2,0x3ff0000000000000
  br i1 %op3, label %label_trueBB, label %label_falseBB
label_trueBB:                                                ; preds = %label_entry
  store i32 233, i32* %op0
  br label %label4
label_falseBB:                                                ; preds = %label_entry
  store i32 0, i32* %op0
  br label %label4
label4:                                                ; preds = %label_trueBB, %label_falseBB
  %op5 = load i32, i32* %op0
  ret i32 %op5
}

输入 lli if.ll; echo $?指令进行测试,结果如下:

在这里插入图片描述

结果为233,输出正确

创建了4个basicblock,对应关系为:

  1. auto bb = BasicBlock::create(module, "entry", mainFun);对应main函数中的标签label_entry
  2. auto trueBB = BasicBlock::create(module, "trueBB", mainFun);对应标签label_trueBB
  3. auto falseBB = BasicBlock::create(module, "falseBB", mainFun);对应标签label_falseBB
  4. auto retBB = BasicBlock::create(module, "", mainFun);对应标签label4

补充while_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG  // 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;  // 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module) // 得到常数值的表示,方便后面多次用到
    
int main() {
  auto module = new Module("while code");
  auto builder = new IRBuilder(nullptr, module); //该类提供了独立的接口创建各种 IR 指令
  Type *Int32Type = Type::get_int32_type(module);

  // main函数
  auto mainFun = Function::create(FunctionType::get(Int32Type, {}),"main", module);
  auto bb = BasicBlock::create(module, "entry", mainFun); //创建BasicBlock
  builder->set_insert_point(bb);  //将当前插入指令点的位置设在bb
  
  auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置
  
  auto aAlloca=builder->create_alloca(Int32Type); //在内存中分配参数a的位置
  auto iAlloca=builder->create_alloca(Int32Type); //在内存中分配参数i的位置
  
  builder->create_store(CONST_INT(10), aAlloca);  //将10写入a的地址
  builder->create_store(CONST_INT(0), iAlloca);  //将0写入i的地址
  
  
  auto condBB = BasicBlock::create(module, "condBB", mainFun);  // cond分支,用于while循环的判断
  auto trueBB = BasicBlock::create(module, "trueBB", mainFun);    // true分支,条件为真时
  auto retBB = BasicBlock::create(module, "retBB", mainFun);  // ret分支,当循环条件不满足时
  builder->create_br(condBB); 
  
  builder->set_insert_point(condBB); // cond分支判断条件,分支的开始需要SetInsertPoint设置
  auto iLoad = builder->create_load(iAlloca);  //获取i地址中的值
  auto cmp = builder->create_icmp_lt(iLoad, CONST_INT(10));  //判断i是否小于10
  auto br = builder->create_cond_br(cmp, trueBB, retBB);  // 条件BR
   
  builder->set_insert_point(trueBB);  // if true; 
  auto aLoad = builder->create_load(aAlloca);  //从a地址读取a
  iLoad = builder->create_load(iAlloca);  //从i地址读取i
  auto add1=builder->create_iadd(iLoad,CONST_INT(1)); //进行i+1
  builder->create_store(add1, iAlloca); //把i+1的值写入i的地址
  iLoad = builder->create_load(iAlloca); //从i地址读取i
  auto add2=builder->create_iadd(iLoad,aLoad);//进行i+a
  builder->create_store(add2, aAlloca); //把i+a的值写入a的地址
  builder->create_br(condBB);  // br condBB
  
  builder->set_insert_point(retBB);  // ret分支
  aLoad = builder->create_load(aAlloca);  //从a地址读取a
  builder->create_store(aLoad,retAlloca ); //把a的值写入返回值的地址
  auto retLoad = builder->create_load(retAlloca);  //获取返回值地址中值
  builder->create_ret(retLoad);  //创建ret指令,返回

  std::cout << module->print();
  delete module;
  return 0;
}

结果验证:

输入指令make,编译通过后输入指令./stu_while_generator >while.ll

内容如下:

define i32 @main() {
label_entry:
  %op0 = alloca i32
  %op1 = alloca i32
  %op2 = alloca i32
  store i32 10, i32* %op1
  store i32 0, i32* %op2
  br label %label_condBB
label_condBB:                                                ; preds = %label_entry, %label_trueBB
  %op3 = load i32, i32* %op2
  %op4 = icmp slt i32 %op3, 10
  br i1 %op4, label %label_trueBB, label %label_retBB
label_trueBB:                                                ; preds = %label_condBB
  %op5 = load i32, i32* %op1
  %op6 = load i32, i32* %op2
  %op7 = add i32 %op6, 1
  store i32 %op7, i32* %op2
  %op8 = load i32, i32* %op2
  %op9 = add i32 %op8, %op5
  store i32 %op9, i32* %op1
  br label %label_condBB
label_retBB:                                                ; preds = %label_condBB
  %op10 = load i32, i32* %op1
  store i32 %op10, i32* %op0
  %op11 = load i32, i32* %op0
  ret i32 %op11
}

输入 lli while.ll; echo $?指令进行测试,结果如下:

在这里插入图片描述

结果为65,输出正确

创建了4个basicblock,对应关系为:

  1. auto bb = BasicBlock::create(module, "entry", mainFun);对应main函数中的标签label_entry
  2. auto condBB = BasicBlock::create(module, "condBB", mainFun);对应标签label_condBB
  3. auto trueBB = BasicBlock::create(module, "trueBB", mainFun);对应标签label_trueBB
  4. auto retBB = BasicBlock::create(module, "retBB", mainFun);对应标签label_retBB

问题2: Visitor Pattern

请指出visitor.cpp中,treeVisitor.visit(exprRoot)执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。

序列请按如下格式指明:

exprRoot->numberF->exprE->numberA->exprD

答:

visitor.cpp进行分析:

class AddSubNode : public Node {  // Specific element class #1
 public:
  // Resolved at runtime, it calls the treeVisitor's overloaded function,
  // corresponding to AddSubNode.
  int accept(TreeVisitor& treeVisitor) override {
    return treeVisitor.visit(*this);
  }
  Node& leftNode;
  Node& rightNode;
  std::string op;
  AddSubNode(Node& left, Node& right, std::string op): leftNode(left), rightNode(right), op(op){}
};

对于加减结点,按照“左中右”的顺序为:左操作数(子结点),中间符号(当前结点),右操作数(子结点)。

class NumberNode : public Node {  // Specific element class #2
 public:
  // Resolved at runtime, it calls the treeVisitor's overloaded function,
  // corresponding to NumberNode.
  int accept(TreeVisitor& treeVisitor) override {
    return treeVisitor.visit(*this);
  }
  int number;
  NumberNode(int number){
    this->number = number;
  }
};

而数字结点仅自身。

class MulDivNode : public Node {  // Specific element class #3
 public:
  // Resolved at runtime, it calls the treeVisitor's overloaded function,
  // corresponding to MulDivNode.
  int accept(TreeVisitor& treeVisitor) override {
    return treeVisitor.visit(*this);
  }
  Node& leftNode;
  Node& rightNode;
  std::string op;
  MulDivNode(Node& left, Node& right, std::string op): leftNode(left), rightNode(right), op(op){}
};

对于乘除结点,按照“左中右”的顺序为:左操作数(子结点),中间符号(当前结点),右操作数(子结点)。

查看 visit 函数代码

int visit(AddSubNode& node) override {
    auto right = node.rightNode.accept(*this);
    auto left = node.leftNode.accept(*this);
    if (node.op == "add") {
      return left + right;
    } 
    else {
      return left - right;
    }
  }

对于加减操作,先遍历右子树再遍历左子树,如果操作符是‘add’就进行加操作,否则进行减操作

int visit(MulDivNode& node) override {
    auto left = node.leftNode.accept(*this);
    auto right = node.rightNode.accept(*this);
    if (node.op == "mul") {
      return left * right;
    } 
    else {
      return left / right;
    }
  }
};

对于乘除操作,先遍历左子树再遍历右子树,如果操作符是‘mul’进行乘操作,否则进行减操作。

int visit(NumberNode& node) override {
    return node.number;
  }

数字节点直接返回它的值

下面查看main函数

  // construct the expression nodes and the tree
  // the expression: 4 * 2 - 2 / 4 + 5
  auto numberA = NumberNode(4);
  auto numberB = NumberNode(2);
  auto exprC = MulDivNode(numberA, numberB, "mul");
  auto exprD = MulDivNode(numberB, numberA, "div");
  auto exprE = AddSubNode(exprC, exprD, "sub");
  auto numberF = NumberNode(5);
  auto exprRoot = AddSubNode(exprE, numberF, "add");

实际上就自底向上构建了一个树:

在这里插入图片描述

然后调用visit函数:

 TreeVisitorCalculator treeVisitor;
  // traverse the tree and calculate
  int result = treeVisitor.visit(exprRoot);
  std::cout << "4 * 2 - 2 / 4 + 5 evaluates: " << result << std::endl;

进入visit函数的具体访问过程如下:

  1. 根节点 exprRoot 是AddSubNode,按照访问顺序先访问右子树,即numberF
  2. numberF是NumberNode,直接返回其节点的值5,故开始访问exprRoot的左子树
  3. 由于exprRoot的第一个左子节点exprE是AddSubNode,所以要遍历完AddSubNode才能返回结果,下面访问exprE的右子树
  4. exprE的右子节点是exprD,是MulDivNode,所以先访问左子树,即numberB,是NumberNode,返回其值2
  5. 接着遍历exprD的右子树,即numberA,是NumberNode,返回其值4,得到两个子树的值后将计算结果2/4存在exprD中
  6. 接着访问exprE的左子树,即exprC,是MulDivNode,所以先遍历左子树,是numberA,是NumberNode,返回其值4
  7. 接着访问exprC的右子树,即numberB,是NumberNode,返回其值2,接着将运算结果2*4的值存在exprC中
  8. 得到了exprC的值(值为8)和exprD的值(值为0.5),回到上一层将exprC-exprD的值存入exprE中
  9. 得到了exprE的值和numberF的值,将exprE+numberF的值存入根节点,遍历结束,最终结果为13(这里有个小细节,计算结果是12.5,但是结果使用int型保存,所以四舍五入为13)

故最终的访问顺序如下:exprRoot->numberF->exprE->exprD->numberB->numberA->exprC->numberA->numberB

问题3: getelementptr

请给出IR.md中提到的两种getelementptr用法的区别,并稍加解释:

  • %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
  • %2 = getelementptr i32, i32* %1 i32 %0

答:

  • 概念:getelementptr指令用于获取数组结构的元素的地址。 它仅执行地址计算,并且不访问内存。
  • 格式: <result> = getelementptr <type>, <type>* <ptrval> [, <type> <idx>]
  • 参数解释:第一个参数是计算基础类型,第二第三个参数表示索引开始的指针类型及指针,[]表示可重复参数,里面表示的数组索引的偏移类型及偏移值。
  1. %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0

下面结合一个例子来说明,这是assign.c

int main(){
  int a[10];
  a[0] = 10;
  a[1] = a[0] * 2;
  return a[1];
}

clang生成的.ll文件如下:

define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca [10 x i32], align 16
  store i32 0, i32* %1, align 4
  %3 = getelementptr inbounds [10 x i32], [10 x i32]* %2, i64 0, i64 0
  store i32 10, i32* %3, align 16
  %4 = getelementptr inbounds [10 x i32], [10 x i32]* %2, i64 0, i64 0
  %5 = load i32, i32* %4, align 16
  %6 = mul nsw i32 %5, 2
  %7 = getelementptr inbounds [10 x i32], [10 x i32]* %2, i64 0, i64 1
  store i32 %6, i32* %7, align 4
  %8 = getelementptr inbounds [10 x i32], [10 x i32]* %2, i64 0, i64 1
  %9 = load i32, i32* %8, align 4
  ret i32 %9
}

很明显%3 = getelementptr inbounds [10 x i32], [10 x i32]* %2, i64 0, i64 0这一条语句正是getelementptr用法的第一种,他对应c语言中的取出a[0]的地址空间。再结合上面的参数说明可知,第一种用法的指针的类型为[10 x i32]* ,指向的是数组a[],然后第二个i32 0代表的数组的首地址位置,即a[0],第三个i32 0代表的是从首地址开始的偏移量。即如果是这么表示%2 = getelementptr [20 x i32], [20 x i32]* %1, i32 2, i32 %0 ,即取出a[2+%0]的位置的地址空间赋给%2。

如果是二维数组a[10][10],要想对a[0][1]赋值,首先要得到其位置,用getelementptr表示为:

%3 = getelementptr inbounds [10 x [10 x i32]], [10 x [10 x i32]]* %2, i64 0, i64 0
%4 = getelementptr inbounds [10 x i32], [10 x i32]* %3, i64 0, i64 1

首先取出a[0][]的位置,赋给%3,再从a[0][]取出a[0][1]的位置赋给%4

三维数组以此类推

2.%2 = getelementptr i32, i32* %1 i32 %0

指针类型为i32* ,即int*,如果放在c中就代表一个一维数组的意思。偏移量为%0,也是获得一个数组a[%0]位置的方法

总结:这两种getelementptr实际上是针对不同的指针类型[10 x i32]*i32*,同样取出a[%0]的方法。

此外,这两种的用法实际上能算作是同一种用法,区别是在于前者是[10 x i32]*型,指针所指向的地址仍旧可以视为指针,从而除指针自身以[10 x i32]为单位的偏移外,还有偏移单位为i32的偏移;而后者是i32*,仅存在i32这一单位的偏移。

而且,这种不仅适用于数组的寻址,也适用于结构体、向量等数据组织方式的寻址,方法和数组的类似。

实验难点

  1. 在if_hand.ll给float a = 5.555;赋值。如果直接使用store float 5.555, float* %1会报错。 输入clang -S -emit-llvm if.c指令,看了一下样例,发现要写成浮点数的机器表达形式。 即

5.555的二进制表示:101.10001110000101000111101011100001010001111010111
尾数正规化 :1.0110001110000101000111101011100001010001111010111*22
E=2, exp = E + Bias =129,二进制表示为10000001
符号位为0
有效数字M等于 01100011100001010001111
故5.555的IEEE表示为
0 10000001 01100011100001010001111
转换为十六进制为0x40b1c28f
但样例给出的是0x40163851E0000000,后经验证发现样例给的是double型的IEEE标识,但程序和精简的IR Reference手册都说明了float是单精度的,所以我觉得这里还是有点问题

  1. 考察两种getelementptr用法除了查阅精简的IR Reference手册,还可以上网查阅其他资料,最重要的是可以利用clang生成不同的.ll文件进行比较验证

实验反馈

实验我认为最难的地方不在于实验本身,在于环境配置。一开始实验文档说llvm的版本要10.0.1,但是用指令下出来的是6.0版本,我就想先试着用一下先,当编译到fun_hand.ll时会报错,给同学的10.0.1版本的却不会报错,我就以为是llvm版本的问题,然后就下载10.0.1的版本,但是gitee上的方法都没啥用,第一个docker下载好了不懂怎么用,只能用第二个自己搭建的方法,但足足花费了4个小时来编译llvm!

实验本身还是挺有意思的,可以根据案例来仿写其他的,但是有些内容要一直重复写就会很烦,如果有错了找半天都找不到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/125111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows10下CLion配置说明

Windows10下CLion配置说明 CLion 是 C/C的 IDE&#xff0c;可以配置多种编译环境&#xff0c;本文以配置MinGW编译环境为例。 安装 CLion 的安装可直接到官网下载 ZIP,文件解压后直接运行即可。我在安装过程中没有遇到困难&#xff0c;网上关于安装的教程很多&#xff0c;如…

TCP连接积压导致服务假死问题

目录 一、事故表现 二、事故问题分析 三、测试环境重现 四、解决方案 一、事故表现 2022-01-14日凌晨00:00开始&#xff0c;TCP_alloc&#xff1a;已分配TCP连接&#xff0c;一直未释放。导致未释放的 TCP连接一直积压。最终服务LOGISTICS-DS-ES-COMMON-SERVICE假死&#x…

jenkins下载安装

目录 1.下载安装 2.初始化配置 3.新建项目 3.1.安装插件 3.2.配置项目 3.2.1.选择项目类型 3.2.1.源码管理 3.3.3.配置maven 1.下载安装 安装清单&#xff1a; JDK 1.8Maven 3.6.3gitJenkins 2.334 环境&#xff1a; centos 7 Jenkins是JAVA编写的&#xff0c;需要JDK环…

年终报告:通过3个维度,回看2022全球电商市场的“多样性”

回顾年中&#xff0c;各国由于经济政策的调整导致贸易壁垒的层层叠加&#xff0c;也有非常复杂多变的因素在发酵&#xff0c;今年下半年突然爆发的俄乌两国的冲突使得原来就脆弱的全球经济再次遭受动荡&#xff0c;这一系列的动荡因素直接导致了今年全球经济的整体不景气&#…

鼠标拖拽菜单栏控制宽度大小及flex实现经典左右两栏布局

目录 1. 实现的效果如下图所示&#xff1a; 2. 思路 3.代码 3.1 js核心代码简单理解版&#xff1a; 3.2 实际应用-react版 4. 使用flex实现左右两栏式经典布局 4.1 图示&#xff1a; 4.2 代码实例&#xff1a; 1. 实现的效果如下图所示&#xff1a; 2. 思路 1. 使用定…

欧科云链接受北京电视台采访以创新科技助力《反电信网络诈骗法》

近日&#xff0c;欧科云链作为创新科技企业的代表&#xff0c;就《反电信网络诈骗法》实施的相关问题接受了来自北京电视台的采访。 编辑&#xff5c;小O 出品&#xff5c;欧科云链 近年来&#xff0c;随着数字技术的快速发展&#xff0c;越来越多的交易都转移到线上&#xff0…

softnms源码解读(python)

前言 想写这篇文章的原因是最近碰见了一个比较棘手的事情&#xff0c;如果想把一个目标检测模型及其相关的后处理移到嵌入式设备上&#xff0c;不能用c的opencv库&#xff0c;也就不能用cv2.dnn.nms这个函数来进行nms的后处理&#xff0c;需要用c实现&#xff0c;那就必须了解…

重磅综述|Nat Rev Gastroenterol Hepatol:人类胃肠道中的产甲烷古菌

期刊&#xff1a;Nat Rev Gastroenterol Hepatol 影响因子&#xff1a;73.082 发表时间&#xff1a;2022年9月 一、摘要 人类微生物群与人类健康和疾病密切相关。除了细菌、病毒和真核生物外&#xff0c;人类胃肠道中许多古菌与甲烷的产生有关&#xff0c;临床上可…

PMP一般要提前多久备考?

一般是1-3个月吧&#xff0c;PMP考试通过率至少有60%&#xff0c;报培训班可以有80%以上&#xff0c;不用备考太长时间&#xff0c;但时间太短也无法把项目管理的知识学完&#xff0c;1-3个月是最佳备考时间。 &#xff08;字数很多&#xff0c;都是干货&#xff0c;资料在文末…

写给Java程序员的GRPC入门系列(1)

点击上方GRPC专栏看系列 文章目录Abstract前置依赖本文初始状态创建MAVEN module修改依赖测试下一步Abstract 网上有很多GRPC的例子&#xff0c;但是却没有能够写给普通Java开发人员手把手入门少走弯路的教程。 本教程保证按照步骤一步步来你就可以完成GRPC从0到1的构建。 源码…

一文读懂机器学习常用算法的基本概念和适用场景

引用一句英国统计学家George E. P. Box的名言&#xff1a;All models are wrong, but some are useful. 没有哪一种算法能够适用所有情况&#xff0c;只有针对某一种问题更有用的算法。 机器学习算法不会要求一个问题被 100%求解&#xff0c;取而代之的是把问题转化为最优化的…

基于小程序技术栈的跨端框架有哪些?

回顾过去的几年&#xff0c;市场上的跨端开发框架一直在迭代&#xff0c;同时也有新的跨端框架冒出来。在过往的文章中&#xff0c;我们也有盘点过基于HTML5语法实现的跨端开发平台。在本篇文章中&#xff0c;就让我们盘点一下以小程序语法进行转译的跨端平台&#xff0c;以及他…

相机标定笔记(1) -- 相机模型

什么需要相机标定 我们知道&#xff0c;相机的图像是三维世界到2D平面的一个投影。仅从这个2D图像来看&#xff0c;我们无法得知图像中的物体在真实物理世界中有多大&#xff0c;距离相机的距离有多远。那么我们有没有办法从这个2D的图片结合相机的参数获得这些信息呢&#xff…

ElasticSearch-全文检索

docker 下载安装 #es镜像 docker pull elasticsearch:7.4.2 #es的可视化工具 docker pull kibana:7.4.2mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/dataecho "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsear…

2023年北京/成都/南宁山东DAMA-CDGA/CDGP数据治理工程师认证报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

制造业ERP软件如何破解企业质量管理难题?

随着生产制造企业的快速发展&#xff0c;产品的好坏&#xff0c;在很大程度上取决于产品制造的质量管理水平&#xff0c;其水平的高低直接对应产品的竞争力。许多企业都面临着质量管控能力不足、质量检验数据记录不全、部分物料追溯困难等问题&#xff0c;一旦企业的产品出现质…

异步请求池的实现

异步请求池 两种请求模式 pipline请求&#xff1a;A在一个连接上打包多个请求发送给B&#xff0c;B将这些请求的结果打包返回异步请求&#xff1a;A一个连接一个请求&#xff0c;并创建一个线程检查发送的所有请求是否有结果返回&#xff08;借助epoll&#xff09;&#xff0…

F280049C Buffered Digital-to-Analog Converter (DAC)

目录DAC15.1 Introduction15.1.1 Features15.1.2 Block Diagram15.2 Using the DAC15.2.1 Initialization Sequence 初始化顺序15.2.2 DAC Offset AdjustmentDAC偏移调整15.2.3 EPWMSYNCPER Signal EPWMSYNCPER信号15.3 Lock Registers总结代码配置 driverlib代码配置 bitfield…

Blender和C4D有哪些不同?选Blender还是C4D?

众所周知&#xff0c;Blender和Cinema 4D是美术界最著名的两个名字。在比较这两款软件的时候&#xff0c;我们要仔细的看不同的方面。因此&#xff0c;您如何知道使用哪一个以及每个的关键方面是什么&#xff0c;例如渲染、建模、社区等等&#xff01;Cinema 4D 是那些希望在电…

Doris(五)-监控、报警、优化、数据备份及恢复

目录1、监控和报警1.1、Prometheus1.2、Grafana2、优化2.1、查看 QueryProfile2.1.1、使用方式2.1.2、参数说明2.1.3、调试方式2.2、 Join Reorder2.2.1 原理2.2.2 示例2.3 Join 的优化原则2.4 导入导出性能优化2.4.1 FE 配置2.4.3 性能分析2.4.4 Broker 导入大文件2.5 Bitmap …